aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/audit/audisp
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/audit/audisp')
-rw-r--r--framework/src/audit/audisp/Makefile.am40
-rw-r--r--framework/src/audit/audisp/audispd-builtins.c330
-rw-r--r--framework/src/audit/audisp/audispd-builtins.h43
-rw-r--r--framework/src/audit/audisp/audispd-config.c507
-rw-r--r--framework/src/audit/audisp/audispd-config.h48
-rw-r--r--framework/src/audit/audisp/audispd-llist.c157
-rw-r--r--framework/src/audit/audisp/audispd-llist.h60
-rw-r--r--framework/src/audit/audisp/audispd-pconfig.c516
-rw-r--r--framework/src/audit/audisp/audispd-pconfig.h57
-rw-r--r--framework/src/audit/audisp/audispd.c854
-rw-r--r--framework/src/audit/audisp/plugins/Makefile.am32
-rw-r--r--framework/src/audit/audisp/plugins/builtins/Makefile.am39
-rw-r--r--framework/src/audit/audisp/plugins/builtins/af_unix.conf14
-rw-r--r--framework/src/audit/audisp/plugins/builtins/syslog.conf13
-rw-r--r--framework/src/audit/audisp/plugins/prelude/Makefile.am50
-rw-r--r--framework/src/audit/audisp/plugins/prelude/au-prelude.conf12
-rw-r--r--framework/src/audit/audisp/plugins/prelude/audisp-int.c114
-rw-r--r--framework/src/audit/audisp/plugins/prelude/audisp-int.h57
-rw-r--r--framework/src/audit/audisp/plugins/prelude/audisp-prelude.872
-rw-r--r--framework/src/audit/audisp/plugins/prelude/audisp-prelude.c2250
-rw-r--r--framework/src/audit/audisp/plugins/prelude/audisp-prelude.conf61
-rw-r--r--framework/src/audit/audisp/plugins/prelude/audisp-prelude.conf.5153
-rw-r--r--framework/src/audit/audisp/plugins/prelude/prelude-config.c844
-rw-r--r--framework/src/audit/audisp/plugins/prelude/prelude-config.h76
-rw-r--r--framework/src/audit/audisp/plugins/remote/Makefile.am51
-rw-r--r--framework/src/audit/audisp/plugins/remote/au-remote.conf12
-rw-r--r--framework/src/audit/audisp/plugins/remote/audisp-remote.834
-rw-r--r--framework/src/audit/audisp/plugins/remote/audisp-remote.c1486
-rw-r--r--framework/src/audit/audisp/plugins/remote/audisp-remote.conf31
-rw-r--r--framework/src/audit/audisp/plugins/remote/audisp-remote.conf.5211
-rw-r--r--framework/src/audit/audisp/plugins/remote/notes.txt31
-rw-r--r--framework/src/audit/audisp/plugins/remote/queue.c574
-rw-r--r--framework/src/audit/audisp/plugins/remote/queue.h66
-rw-r--r--framework/src/audit/audisp/plugins/remote/remote-config.c780
-rw-r--r--framework/src/audit/audisp/plugins/remote/remote-config.h78
-rw-r--r--framework/src/audit/audisp/plugins/remote/remote-fgets.c123
-rw-r--r--framework/src/audit/audisp/plugins/remote/remote-fgets.h33
-rw-r--r--framework/src/audit/audisp/plugins/remote/test-queue.c367
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/Makefile.am52
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/audispd-zos-remote.conf14
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/zos-remote-config.c443
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/zos-remote-config.h48
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/zos-remote-ldap.c608
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/zos-remote-ldap.h312
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/zos-remote-log.c109
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/zos-remote-log.h58
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/zos-remote-plugin.c580
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/zos-remote-queue.c144
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/zos-remote-queue.h38
-rw-r--r--framework/src/audit/audisp/plugins/zos-remote/zos-remote.conf10
-rw-r--r--framework/src/audit/audisp/queue.c226
-rw-r--r--framework/src/audit/audisp/queue.h45
52 files changed, 12963 insertions, 0 deletions
diff --git a/framework/src/audit/audisp/Makefile.am b/framework/src/audit/audisp/Makefile.am
new file mode 100644
index 00000000..f5134e52
--- /dev/null
+++ b/framework/src/audit/audisp/Makefile.am
@@ -0,0 +1,40 @@
+# Makefile.am--
+# Copyright 2007,2011,2015 Red Hat Inc., Durham, North Carolina.
+# All Rights Reserved.
+#
+# 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:
+# Steve Grubb <sgrubb@redhat.com>
+#
+
+SUBDIRS = plugins
+CONFIG_CLEAN_FILES = *.rej *.orig
+AUTOMAKE_OPTIONS = no-dependencies
+AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/lib
+sbin_PROGRAMS = audispd
+noinst_HEADERS = audispd-config.h audispd-pconfig.h audispd-llist.h \
+ queue.h audispd-builtins.h
+LIBS = -L${top_builddir}/src/mt -lauditmt
+LDADD = -lpthread
+AM_CFLAGS = -D_REENTRANT
+
+audispd_SOURCES = audispd.c audispd-config.c audispd-pconfig.c \
+ audispd-llist.c queue.c audispd-builtins.c
+audispd_CFLAGS = -fPIE -DPIE -g -D_GNU_SOURCE
+audispd_LDFLAGS = -pie -Wl,-z,relro -Wl,-z,now
+
+install-exec-hook:
+ chmod 0750 $(DESTDIR)$(sbindir)/audispd
diff --git a/framework/src/audit/audisp/audispd-builtins.c b/framework/src/audit/audisp/audispd-builtins.c
new file mode 100644
index 00000000..f0da6475
--- /dev/null
+++ b/framework/src/audit/audisp/audispd-builtins.c
@@ -0,0 +1,330 @@
+/*
+* audispd-builtins.c - some common builtin plugins
+* Copyright (c) 2007,2010,2013 Red Hat Inc., Durham, North Carolina.
+* All Rights Reserved.
+*
+* This software may be freely redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free
+* Software Foundation; either version 2, or (at your option) any
+* later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; see the file COPYING. If not, write to the
+* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+* Authors:
+* Steve Grubb <sgrubb@redhat.com>
+*/
+
+#include "config.h"
+#include <string.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "audispd-pconfig.h"
+#include "audispd-builtins.h"
+
+// Local data
+static volatile int sock = -1, conn = -1;
+static int syslog_started = 0, priority;
+static char *path = NULL;
+
+// Local prototypes
+static void init_af_unix(const plugin_conf_t *conf);
+static void init_syslog(const plugin_conf_t *conf);
+
+
+void start_builtin(plugin_conf_t *conf)
+{
+ if (strcasecmp("builtin_af_unix", conf->path) == 0) {
+ conf->type = S_AF_UNIX;
+ init_af_unix(conf);
+ } else if (strcasecmp("builtin_syslog", conf->path) == 0) {
+ conf->type = S_SYSLOG;
+ init_syslog(conf);
+ } else
+ syslog(LOG_ERR, "Unknown builtin %s", conf->path);
+}
+
+void stop_builtin(plugin_conf_t *conf)
+{
+ if (conf->type == S_AF_UNIX)
+ destroy_af_unix();
+ else if (conf->type == S_SYSLOG)
+ destroy_syslog();
+ else
+ syslog(LOG_ERR, "Unknown builtin %s", conf->path);
+}
+
+static void af_unix_accept(int fd)
+{
+ int cmd;
+
+ do {
+ conn = accept(fd, NULL, NULL);
+ } while (conn < 0 && errno == EINTR);
+
+ // De-register since this is intended to be one listener
+ if (conn >= 0)
+ remove_event(fd);
+ cmd = fcntl(conn, F_GETFD);
+ fcntl(conn, F_SETFD, cmd|FD_CLOEXEC);
+}
+
+static int create_af_unix_socket(const char *path, int mode)
+{
+ struct sockaddr_un addr;
+ socklen_t len;
+ int rc, cmd;
+
+ sock = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ syslog(LOG_ERR, "Couldn't open af_unix socket (%s)",
+ strerror(errno));
+ return -1;
+ }
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strcpy(&addr.sun_path[0], path);
+ len = sizeof(addr);
+ rc = bind(sock, (const struct sockaddr *)&addr, len);
+ if (rc < 0) {
+ syslog(LOG_ERR, "Couldn't bind af_unix socket (%s)",
+ strerror(errno));
+ destroy_af_unix();
+ return -1;
+ }
+ if (mode != -1) {
+ rc = chmod(path, mode);
+ if (rc < 0) {
+ syslog(LOG_ERR, "Couldn't chmod %s to %04o (%s)",
+ path, mode, strerror(errno));
+ destroy_af_unix();
+ return -1;
+ }
+ }
+
+ // Put socket in nonblock mode
+ cmd = fcntl(sock, F_GETFL);
+ fcntl(sock, F_SETFL, cmd|FNDELAY);
+
+ // don't leak the descriptor
+ cmd = fcntl(sock, F_GETFD);
+ fcntl(sock, F_SETFD, cmd|FD_CLOEXEC);
+
+ // Make socket listening...won't block
+ (void)listen(sock, 5);
+
+ // Register socket with poll
+ add_event(sock, af_unix_accept);
+ return 0;
+}
+
+static void init_af_unix(const plugin_conf_t *conf)
+{
+ int i = 1, mode = -1;
+ char *base = NULL;
+
+ // while args
+ while (conf->args[i]) {
+ int rc, bad = 0;
+
+ // is all nums - do mode
+ base = conf->args[i];
+ while (*base) {
+ if (!isdigit(*base)) {
+ bad = 1;
+ break;
+ }
+ base++;
+ }
+ if (!bad) {
+ errno = 0;
+ mode = strtoul(conf->args[i], NULL, 8);
+ if (errno) {
+ syslog(LOG_ERR, "Error converting %s (%s)",
+ conf->args[i], strerror(errno));
+ mode = -1;
+ bad = 1;
+ } else if (path) {
+ rc = chmod(path, mode);
+ if (rc < 0) {
+ syslog(LOG_ERR,
+ "Couldn't chmod %s to %04o (%s)",
+ conf->args[i], mode,
+ strerror(errno));
+ destroy_af_unix();
+ return;
+ }
+ }
+ } else {
+ // else check for '/'
+ base = strchr(conf->args[i], '/');
+ if (base) {
+ // get dirname
+ DIR *d;
+ char *dir = strdup(conf->args[i]);
+ base = dirname(dir);
+ d = opendir(base);
+ if (d) {
+ closedir(d);
+ unlink(conf->args[i]);
+ if (create_af_unix_socket(
+ conf->args[i], mode)<0) {
+ free(dir);
+ return;
+ }
+ path = strdup(conf->args[i]);
+ bad = 0;
+ } else
+ syslog(LOG_ERR, "Couldn't open %s (%s)",
+ base, strerror(errno));
+ free(dir);
+ } else
+ syslog(LOG_ERR, "Malformed path %s",
+ conf->args[i]);
+ }
+ if (bad) {
+ destroy_af_unix();
+ return;
+ }
+ i++;
+ }
+ syslog(LOG_INFO, "af_unix plugin initialized");
+}
+
+void send_af_unix_string(const char *s, unsigned int len)
+{
+ if (sock < 0)
+ return;
+
+ if (conn >= 0) {
+ int rc;
+ do {
+ rc = write(conn, s, len);
+ } while (rc < 0 && errno == EINTR);
+ if (rc < 0 && errno == EPIPE) {
+ close(conn);
+ conn = -1;
+ add_event(sock, af_unix_accept);
+ }
+ }
+}
+
+void send_af_unix_binary(event_t *e)
+{
+ if (sock < 0)
+ return;
+
+ if (conn >= 0) {
+ int rc;
+ struct iovec vec[2];
+
+ vec[0].iov_base = &e->hdr;
+ vec[0].iov_len = sizeof(struct audit_dispatcher_header);
+ vec[1].iov_base = e->data;
+ vec[1].iov_len = MAX_AUDIT_MESSAGE_LENGTH;
+ do {
+ rc = writev(conn, vec, 2);
+ } while (rc < 0 && errno == EINTR);
+ if (rc < 0 && errno == EPIPE) {
+ close(conn);
+ conn = -1;
+ add_event(sock, af_unix_accept);
+ }
+ }
+}
+
+void destroy_af_unix(void)
+{
+ if (conn >= 0) {
+ close(conn);
+ conn = -1;
+ }
+ if (sock >= 0) {
+ close(sock);
+ sock = -1;
+ }
+ if (path) {
+ unlink(path);
+ free(path);
+ path = NULL;
+ }
+}
+
+static void init_syslog(const plugin_conf_t *conf)
+{
+ int i, facility = LOG_USER;
+ priority = LOG_INFO;
+
+ for (i = 1; i<3; i++) {
+ if (conf->args[i]) {
+ if (strcasecmp(conf->args[i], "LOG_DEBUG") == 0)
+ priority = LOG_DEBUG;
+ else if (strcasecmp(conf->args[i], "LOG_INFO") == 0)
+ priority = LOG_INFO;
+ else if (strcasecmp(conf->args[i], "LOG_NOTICE") == 0)
+ priority = LOG_NOTICE;
+ else if (strcasecmp(conf->args[i], "LOG_WARNING") == 0)
+ priority = LOG_WARNING;
+ else if (strcasecmp(conf->args[i], "LOG_ERR") == 0)
+ priority = LOG_ERR;
+ else if (strcasecmp(conf->args[i], "LOG_CRIT") == 0)
+ priority = LOG_CRIT;
+ else if (strcasecmp(conf->args[i], "LOG_ALERT") == 0)
+ priority = LOG_ALERT;
+ else if (strcasecmp(conf->args[i], "LOG_EMERG") == 0)
+ priority = LOG_EMERG;
+ else if (strcasecmp(conf->args[i], "LOG_LOCAL0") == 0)
+ facility = LOG_LOCAL0;
+ else if (strcasecmp(conf->args[i], "LOG_LOCAL1") == 0)
+ facility = LOG_LOCAL1;
+ else if (strcasecmp(conf->args[i], "LOG_LOCAL2") == 0)
+ facility = LOG_LOCAL2;
+ else if (strcasecmp(conf->args[i], "LOG_LOCAL3") == 0)
+ facility = LOG_LOCAL3;
+ else if (strcasecmp(conf->args[i], "LOG_LOCAL4") == 0)
+ facility = LOG_LOCAL4;
+ else if (strcasecmp(conf->args[i], "LOG_LOCAL5") == 0)
+ facility = LOG_LOCAL5;
+ else if (strcasecmp(conf->args[i], "LOG_LOCAL6") == 0)
+ facility = LOG_LOCAL6;
+ else if (strcasecmp(conf->args[i], "LOG_LOCAL7") == 0)
+ facility = LOG_LOCAL7;
+ else {
+ syslog(LOG_ERR,
+ "Unknown log priority/facility %s",
+ conf->args[i]);
+ syslog_started = 0;
+ return;
+ }
+ }
+ }
+ syslog(LOG_INFO, "syslog plugin initialized");
+ if (facility != LOG_USER)
+ openlog("audispd", 0, facility);
+ syslog_started = 1;
+}
+
+void send_syslog(const char *s)
+{
+ if (syslog_started)
+ syslog(priority, "%s", s);
+}
+
+void destroy_syslog(void)
+{
+ syslog_started = 0;
+}
+
diff --git a/framework/src/audit/audisp/audispd-builtins.h b/framework/src/audit/audisp/audispd-builtins.h
new file mode 100644
index 00000000..79f43b81
--- /dev/null
+++ b/framework/src/audit/audisp/audispd-builtins.h
@@ -0,0 +1,43 @@
+/*
+* audispd-builtins.h - Minimal linked list library
+* Copyright (c) 2007,2013 Red Hat Inc., Durham, North Carolina.
+* All Rights Reserved.
+*
+* This software may be freely redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free
+* Software Foundation; either version 2, or (at your option) any
+* later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; see the file COPYING. If not, write to the
+* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+* Authors:
+* Steve Grubb <sgrubb@redhat.com>
+*/
+
+#ifndef AUDISPD_BUILTINS_HEADER
+#define AUDISPD_BUILTINS_HEADER
+
+#include "queue.h"
+
+void start_builtin(plugin_conf_t *conf);
+void stop_builtin(plugin_conf_t *conf);
+void send_af_unix_string(const char *s, unsigned int len);
+void send_af_unix_binary(event_t *e);
+void destroy_af_unix(void);
+void send_syslog(const char *s);
+void destroy_syslog(void);
+
+typedef void (*poll_callback_ptr)(int fd);
+int add_event(int fd, poll_callback_ptr cb);
+int remove_event(int fd);
+
+
+#endif
+
diff --git a/framework/src/audit/audisp/audispd-config.c b/framework/src/audit/audisp/audispd-config.c
new file mode 100644
index 00000000..5e1fd0ee
--- /dev/null
+++ b/framework/src/audit/audisp/audispd-config.c
@@ -0,0 +1,507 @@
+/* audispd-config.c --
+ * Copyright 2007-08,2010,2014-15 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#include "config.h"
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <limits.h>
+#include "audispd-config.h"
+#include "private.h"
+
+/* Local prototypes */
+struct nv_pair
+{
+ const char *name;
+ const char *value;
+ const char *option;
+};
+
+struct kw_pair
+{
+ const char *name;
+ int (*parser)(struct nv_pair *, int, daemon_conf_t *);
+ int max_options;
+};
+
+struct nv_list
+{
+ const char *name;
+ int option;
+};
+
+static char *get_line(FILE *f, char *buf, unsigned size, int *lineno,
+ const char *file);
+static int nv_split(char *buf, struct nv_pair *nv);
+static const struct kw_pair *kw_lookup(const char *val);
+static int q_depth_parser(struct nv_pair *nv, int line,
+ daemon_conf_t *config);
+static int name_format_parser(struct nv_pair *nv, int line,
+ daemon_conf_t *config);
+static int name_parser(struct nv_pair *nv, int line,
+ daemon_conf_t *config);
+static int overflow_action_parser(struct nv_pair *nv, int line,
+ daemon_conf_t *config);
+static int priority_boost_parser(struct nv_pair *nv, int line,
+ daemon_conf_t *config);
+static int max_restarts_parser(struct nv_pair *nv, int line,
+ daemon_conf_t *config);
+static int sanity_check(daemon_conf_t *config, const char *file);
+
+static const struct kw_pair keywords[] =
+{
+ {"q_depth", q_depth_parser, 0 },
+ {"name_format", name_format_parser, 0 },
+ {"name", name_parser, 0 },
+ {"overflow_action", overflow_action_parser, 0 },
+ {"priority_boost", priority_boost_parser, 0 },
+ {"max_restarts", max_restarts_parser, 0 },
+ { NULL, NULL }
+};
+
+static const struct nv_list node_name_formats[] =
+{
+ {"none", N_NONE },
+ {"hostname", N_HOSTNAME },
+ {"fqd", N_FQD },
+ {"numeric", N_NUMERIC },
+ {"user", N_USER },
+ { NULL, 0 }
+};
+
+static const struct nv_list overflow_actions[] =
+{
+ {"ignore", O_IGNORE },
+ {"syslog", O_SYSLOG },
+ {"suspend", O_SUSPEND },
+ {"single", O_SINGLE },
+ {"halt", O_HALT },
+ { NULL, 0 }
+};
+
+/*
+ * Set everything to its default value
+*/
+void clear_config(daemon_conf_t *config)
+{
+ config->q_depth = 80;
+ config->overflow_action = O_SYSLOG;
+ config->priority_boost = 4;
+ config->max_restarts = 10;
+ config->node_name_format = N_NONE;
+ config->name = NULL;
+}
+
+int load_config(daemon_conf_t *config, const char *file)
+{
+ int fd, rc, mode, lineno = 1;
+ struct stat st;
+ FILE *f;
+ char buf[160];
+
+ clear_config(config);
+
+ /* open the file */
+ mode = O_RDONLY;
+ rc = open(file, mode);
+ if (rc < 0) {
+ if (errno != ENOENT) {
+ audit_msg(LOG_ERR, "Error opening %s (%s)", file,
+ strerror(errno));
+ return 1;
+ }
+ audit_msg(LOG_WARNING,
+ "Config file %s doesn't exist, skipping", file);
+ return 0;
+ }
+ fd = rc;
+
+ /* check the file's permissions: owned by root, not world writable,
+ * not symlink.
+ */
+ if (fstat(fd, &st) < 0) {
+ audit_msg(LOG_ERR, "Error fstat'ing config file (%s)",
+ strerror(errno));
+ close(fd);
+ return 1;
+ }
+ if (st.st_uid != 0) {
+ audit_msg(LOG_ERR, "Error - %s isn't owned by root",
+ file);
+ close(fd);
+ return 1;
+ }
+ if ((st.st_mode & S_IWOTH) == S_IWOTH) {
+ audit_msg(LOG_ERR, "Error - %s is world writable",
+ file);
+ close(fd);
+ return 1;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ audit_msg(LOG_ERR, "Error - %s is not a regular file",
+ file);
+ close(fd);
+ return 1;
+ }
+
+ /* it's ok, read line by line */
+ f = fdopen(fd, "rm");
+ if (f == NULL) {
+ audit_msg(LOG_ERR, "Error - fdopen failed (%s)",
+ strerror(errno));
+ close(fd);
+ return 1;
+ }
+
+ while (get_line(f, buf, sizeof(buf), &lineno, file)) {
+ // convert line into name-value pair
+ const struct kw_pair *kw;
+ struct nv_pair nv;
+ rc = nv_split(buf, &nv);
+ switch (rc) {
+ case 0: // fine
+ break;
+ case 1: // not the right number of tokens.
+ audit_msg(LOG_ERR,
+ "Wrong number of arguments for line %d in %s",
+ lineno, file);
+ break;
+ case 2: // no '=' sign
+ audit_msg(LOG_ERR,
+ "Missing equal sign for line %d in %s",
+ lineno, file);
+ break;
+ default: // something else went wrong...
+ audit_msg(LOG_ERR,
+ "Unknown error for line %d in %s",
+ lineno, file);
+ break;
+ }
+ if (nv.name == NULL) {
+ lineno++;
+ continue;
+ }
+ if (nv.value == NULL) {
+ fclose(f);
+ audit_msg(LOG_ERR,
+ "Not processing any more lines in %s", file);
+ return 1;
+ }
+
+ /* identify keyword or error */
+ kw = kw_lookup(nv.name);
+ if (kw->name == NULL) {
+ audit_msg(LOG_ERR,
+ "Unknown keyword \"%s\" in line %d of %s",
+ nv.name, lineno, file);
+ fclose(f);
+ return 1;
+ }
+
+ /* Check number of options */
+ if (kw->max_options == 0 && nv.option != NULL) {
+ audit_msg(LOG_ERR,
+ "Keyword \"%s\" has invalid option "
+ "\"%s\" in line %d of %s",
+ nv.name, nv.option, lineno, file);
+ fclose(f);
+ return 1;
+ }
+
+ /* dispatch to keyword's local parser */
+ rc = kw->parser(&nv, lineno, config);
+ if (rc != 0) {
+ fclose(f);
+ return 1; // local parser puts message out
+ }
+
+ lineno++;
+ }
+
+ fclose(f);
+ if (lineno > 1)
+ return sanity_check(config, file);
+ return 0;
+}
+
+static char *get_line(FILE *f, char *buf, unsigned size, int *lineno,
+ const char *file)
+{
+ int too_long = 0;
+
+ while (fgets_unlocked(buf, size, f)) {
+ /* remove newline */
+ char *ptr = strchr(buf, 0x0a);
+ if (ptr) {
+ if (!too_long) {
+ *ptr = 0;
+ return buf;
+ }
+ // Reset and start with the next line
+ too_long = 0;
+ *lineno = *lineno + 1;
+ } else {
+ // If a line is too long skip it.
+ // Only output 1 warning
+ if (!too_long)
+ audit_msg(LOG_ERR,
+ "Skipping line %d in %s: too long",
+ *lineno, file);
+ too_long = 1;
+ }
+ }
+ return NULL;
+}
+
+static int nv_split(char *buf, struct nv_pair *nv)
+{
+ /* Get the name part */
+ char *ptr, *saved;
+
+ nv->name = NULL;
+ nv->value = NULL;
+ nv->option = NULL;
+ ptr = strtok_r(buf, " ", &saved);
+ if (ptr == NULL)
+ return 0; /* If there's nothing, go to next line */
+ if (ptr[0] == '#')
+ return 0; /* If there's a comment, go to next line */
+ nv->name = ptr;
+
+ /* Check for a '=' */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr == NULL)
+ return 1;
+ if (strcmp(ptr, "=") != 0)
+ return 2;
+
+ /* get the value */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr == NULL)
+ return 1;
+ nv->value = ptr;
+
+ /* See if there's an option */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr) {
+ nv->option = ptr;
+
+ /* Make sure there's nothing else */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr)
+ return 1;
+ }
+
+ /* Everything is OK */
+ return 0;
+}
+
+static const struct kw_pair *kw_lookup(const char *val)
+{
+ int i = 0;
+ while (keywords[i].name != NULL) {
+ if (strcasecmp(keywords[i].name, val) == 0)
+ break;
+ i++;
+ }
+ return &keywords[i];
+}
+
+static int q_depth_parser(struct nv_pair *nv, int line,
+ daemon_conf_t *config)
+{
+ const char *ptr = nv->value;
+ unsigned long i;
+
+ /* check that all chars are numbers */
+ for (i=0; ptr[i]; i++) {
+ if (!isdigit(ptr[i])) {
+ audit_msg(LOG_ERR,
+ "Value %s should only be numbers - line %d",
+ nv->value, line);
+ return 1;
+ }
+ }
+
+ /* convert to unsigned long */
+ errno = 0;
+ i = strtoul(nv->value, NULL, 10);
+ if (errno) {
+ audit_msg(LOG_ERR,
+ "Error converting string to a number (%s) - line %d",
+ strerror(errno), line);
+ return 1;
+ }
+ if (i > 99999) {
+ audit_msg(LOG_ERR, "q_depth must be 99999 or less");
+ return 1;
+ }
+ config->q_depth = i;
+ return 0;
+
+}
+
+static int name_format_parser(struct nv_pair *nv, int line,
+ daemon_conf_t *config)
+{
+ int i;
+
+ for (i=0; node_name_formats[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, node_name_formats[i].name) == 0) {
+ config->node_name_format = node_name_formats[i].option;
+ return 0;
+ }
+ }
+ audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int name_parser(struct nv_pair *nv, int line,
+ daemon_conf_t *config)
+{
+ if (nv->value == NULL)
+ config->name = NULL;
+ else
+ config->name = strdup(nv->value);
+ return 0;
+}
+
+static int overflow_action_parser(struct nv_pair *nv, int line,
+ daemon_conf_t *config)
+{
+ int i;
+
+ for (i=0; overflow_actions[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, overflow_actions[i].name) == 0) {
+ config->overflow_action = overflow_actions[i].option;
+ return 0;
+ }
+ }
+ audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int priority_boost_parser(struct nv_pair *nv, int line,
+ struct daemon_conf *config)
+{
+ const char *ptr = nv->value;
+ unsigned long i;
+
+ audit_msg(LOG_DEBUG, "priority_boost_parser called with: %s",
+ nv->value);
+
+ /* check that all chars are numbers */
+ for (i=0; ptr[i]; i++) {
+ if (!isdigit(ptr[i])) {
+ audit_msg(LOG_ERR,
+ "Value %s should only be numbers - line %d",
+ nv->value, line);
+ return 1;
+ }
+ }
+ /* convert to unsigned int */
+ errno = 0;
+ i = strtoul(nv->value, NULL, 10);
+ if (errno) {
+ audit_msg(LOG_ERR,
+ "Error converting string to a number (%s) - line %d",
+ strerror(errno), line);
+ return 1;
+ }
+ /* Check its range */
+ if (i > INT_MAX) {
+ audit_msg(LOG_ERR,
+ "Error - converted number (%s) is too large - line %d",
+ nv->value, line);
+ return 1;
+ }
+ config->priority_boost = (unsigned int)i;
+ return 0;
+}
+
+static int max_restarts_parser(struct nv_pair *nv, int line,
+ struct daemon_conf *config)
+{
+ const char *ptr = nv->value;
+ unsigned long i;
+
+ audit_msg(LOG_DEBUG, "max_restarts_parser called with: %s",
+ nv->value);
+
+ /* check that all chars are numbers */
+ for (i=0; ptr[i]; i++) {
+ if (!isdigit(ptr[i])) {
+ audit_msg(LOG_ERR,
+ "Value %s should only be numbers - line %d",
+ nv->value, line);
+ return 1;
+ }
+ }
+ /* convert to unsigned int */
+ errno = 0;
+ i = strtoul(nv->value, NULL, 10);
+ if (errno) {
+ audit_msg(LOG_ERR,
+ "Error converting string to a number (%s) - line %d",
+ strerror(errno), line);
+ return 1;
+ }
+ /* Check its range */
+ if (i > INT_MAX) {
+ audit_msg(LOG_ERR,
+ "Error - converted number (%s) is too large - line %d",
+ nv->value, line);
+ return 1;
+ }
+ config->max_restarts = (unsigned int)i;
+ return 0;
+}
+
+/*
+ * This function is where we do the integrated check of the audispd config
+ * options. At this point, all fields have been read. Returns 0 if no
+ * problems and 1 if problems detected.
+ */
+static int sanity_check(daemon_conf_t *config, const char *file)
+{
+ /* Error checking */
+ if (config->node_name_format == N_USER && config->name == NULL) {
+ audit_msg(LOG_ERR,
+ "Error - node_name_format is user supplied but none given (%s)",
+ file);
+ return 1;
+ }
+ return 0;
+}
+
+void free_config(daemon_conf_t *config)
+{
+ free((void *)config->name);
+}
+
diff --git a/framework/src/audit/audisp/audispd-config.h b/framework/src/audit/audisp/audispd-config.h
new file mode 100644
index 00000000..77585890
--- /dev/null
+++ b/framework/src/audit/audisp/audispd-config.h
@@ -0,0 +1,48 @@
+/* audispd-config.h --
+ * Copyright 2007-08 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#ifndef AUDISPD_CONFIG_H
+#define AUDISPD_CONFIG_H
+
+#include "libaudit.h"
+
+typedef enum { O_IGNORE, O_SYSLOG, O_SUSPEND, O_SINGLE,
+ O_HALT } overflow_action_t;
+typedef enum { N_NONE, N_HOSTNAME, N_FQD, N_NUMERIC, N_USER } node_t;
+
+typedef struct daemon_conf
+{
+ unsigned int q_depth;
+ overflow_action_t overflow_action;
+ unsigned int priority_boost;
+ unsigned int max_restarts;
+ node_t node_name_format;
+ const char *name;
+} daemon_conf_t;
+
+void clear_config(daemon_conf_t *config);
+int load_config(daemon_conf_t *config, const char *file);
+void free_config(daemon_conf_t *config);
+
+#endif
+
diff --git a/framework/src/audit/audisp/audispd-llist.c b/framework/src/audit/audisp/audispd-llist.c
new file mode 100644
index 00000000..c30d87b5
--- /dev/null
+++ b/framework/src/audit/audisp/audispd-llist.c
@@ -0,0 +1,157 @@
+/*
+* audispd-llist.c - Minimal linked list library
+* Copyright (c) 2007,2013 Red Hat Inc., Durham, North Carolina.
+* All Rights Reserved.
+*
+* This software may be freely redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free
+* Software Foundation; either version 2, or (at your option) any
+* later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; see the file COPYING. If not, write to the
+* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+* Authors:
+* Steve Grubb <sgrubb@redhat.com>
+*/
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include "audispd-llist.h"
+
+void plist_create(conf_llist *l)
+{
+ l->head = NULL;
+ l->cur = NULL;
+ l->cnt = 0;
+}
+
+void plist_last(conf_llist *l)
+{
+ register lnode* window;
+
+ if (l->head == NULL)
+ return;
+
+ window = l->head;
+ while (window->next)
+ window = window->next;
+ l->cur = window;
+}
+
+lnode *plist_next(conf_llist *l)
+{
+ if (l->cur == NULL)
+ return NULL;
+ l->cur = l->cur->next;
+ return l->cur;
+}
+
+unsigned int plist_count_active(const conf_llist *l)
+{
+ register lnode* current;
+ unsigned int cnt = 0;
+
+ current = l->head;
+ while (current) {
+ if (current->p && current->p->active == A_YES)
+ cnt++;
+ current=current->next;
+ }
+ return cnt;
+}
+
+void plist_append(conf_llist *l, plugin_conf_t *p)
+{
+ lnode* newnode;
+
+ newnode = malloc(sizeof(lnode));
+
+ if (p) {
+ void *pp = malloc(sizeof(struct plugin_conf));
+ if (pp)
+ memcpy(pp, p, sizeof(struct plugin_conf));
+ newnode->p = pp;
+ } else
+ newnode->p = NULL;
+
+ newnode->next = 0;
+
+ // if we are at top, fix this up
+ if (l->head == NULL)
+ l->head = newnode;
+ else // Otherwise add pointer to newnode
+ l->cur->next = newnode;
+
+ // make newnode current
+ l->cur = newnode;
+ l->cnt++;
+}
+
+void plist_clear(conf_llist* l)
+{
+ lnode* nextnode;
+ register lnode* current;
+
+ current = l->head;
+ while (current) {
+ nextnode=current->next;
+ free(current->p);
+ free(current);
+ current=nextnode;
+ }
+ l->head = NULL;
+ l->cur = NULL;
+ l->cnt = 0;
+}
+
+void plist_mark_all_unchecked(conf_llist* l)
+{
+ register lnode* current;
+
+ current = l->head;
+ while (current) {
+ if (current->p)
+ current->p->checked = 0;
+ current=current->next;
+ }
+}
+
+lnode *plist_find_unchecked(conf_llist* l)
+{
+ register lnode* current;
+
+ current = l->head;
+ while (current) {
+ if (current->p && current->p->checked == 0)
+ return current;
+ current=current->next;
+ }
+ return NULL;
+}
+
+lnode *plist_find_name(conf_llist* l, const char *name)
+{
+ register lnode* current;
+
+ if (name == NULL)
+ return NULL;
+
+ current = l->head;
+ while (current) {
+ if (current->p && current->p->name) {
+ if (strcmp(current->p->name, name) == 0)
+ return current;
+ }
+ current=current->next;
+ }
+ return NULL;
+}
+
diff --git a/framework/src/audit/audisp/audispd-llist.h b/framework/src/audit/audisp/audispd-llist.h
new file mode 100644
index 00000000..0ddd69a3
--- /dev/null
+++ b/framework/src/audit/audisp/audispd-llist.h
@@ -0,0 +1,60 @@
+/*
+* audispd-llist.h - Header file for ausearch-conf_llist.c
+* Copyright (c) 2007,2013 Red Hat Inc., Durham, North Carolina.
+* All Rights Reserved.
+*
+* This software may be freely redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free
+* Software Foundation; either version 2, or (at your option) any
+* later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; see the file COPYING. If not, write to the
+* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+* Authors:
+* Steve Grubb <sgrubb@redhat.com>
+*/
+
+#ifndef AUDISP_LIST_HEADER
+#define AUDISP_LIST_HEADER
+
+#include "config.h"
+#include <sys/types.h>
+#include "audispd-pconfig.h"
+
+/* This is the node of the linked list. message & item are the only elements
+ * at this time. Any data elements that are per item goes here. */
+typedef struct _lnode{
+ plugin_conf_t *p; // The rule from the kernel
+ struct _lnode *next; // Next node pointer
+} lnode;
+
+/* This is the linked list head. Only data elements that are 1 per
+ * event goes here. */
+typedef struct {
+ lnode *head; // List head
+ lnode *cur; // Pointer to current node
+ unsigned int cnt; // How many items in this list
+} conf_llist;
+
+void plist_create(conf_llist *l);
+static inline void plist_first(conf_llist *l) { l->cur = l->head; }
+static inline unsigned int plist_count(conf_llist *l) { return l->cnt; }
+unsigned int plist_count_active(const conf_llist *l);
+void plist_last(conf_llist *l);
+lnode *plist_next(conf_llist *l);
+static inline lnode *plist_get_cur(conf_llist *l) { return l->cur; }
+void plist_append(conf_llist *l, plugin_conf_t *p);
+void plist_clear(conf_llist* l);
+void plist_mark_all_unchecked(conf_llist* l);
+lnode *plist_find_unchecked(conf_llist* l);
+lnode *plist_find_name(conf_llist* l, const char *name);
+
+#endif
+
diff --git a/framework/src/audit/audisp/audispd-pconfig.c b/framework/src/audit/audisp/audispd-pconfig.c
new file mode 100644
index 00000000..4ae1b4d1
--- /dev/null
+++ b/framework/src/audit/audisp/audispd-pconfig.c
@@ -0,0 +1,516 @@
+/* audispd-pconfig.c --
+ * Copyright 2007,2010,2015 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#include "config.h"
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include "audispd-pconfig.h"
+#include "private.h"
+
+/* Local prototypes */
+struct nv_pair
+{
+ const char *name;
+ const char *value;
+ const char *option;
+};
+
+struct kw_pair
+{
+ const char *name;
+ int (*parser)(struct nv_pair *, int, plugin_conf_t *);
+ int max_options;
+};
+
+struct nv_list
+{
+ const char *name;
+ int option;
+};
+
+static char *get_line(FILE *f, char *buf, unsigned size, int *lineno,
+ const char *file);
+static int nv_split(char *buf, struct nv_pair *nv);
+static const struct kw_pair *kw_lookup(const char *val);
+static int active_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config);
+static int direction_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config);
+static int path_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config);
+static int service_type_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config);
+static int args_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config);
+static int format_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config);
+static int sanity_check(plugin_conf_t *config, const char *file);
+
+static const struct kw_pair keywords[] =
+{
+ {"active", active_parser, 0 },
+ {"direction", direction_parser, 0 },
+ {"path", path_parser, 0 },
+ {"type", service_type_parser, 0 },
+ {"args", args_parser, 2 },
+ {"format", format_parser, 0 },
+ { NULL, NULL }
+};
+
+static const struct nv_list active[] =
+{
+ {"yes", A_YES },
+ {"no", A_NO },
+ { NULL, 0 }
+};
+
+static const struct nv_list directions[] =
+{
+// {"in", D_IN }, FIXME: not supported yet
+ {"out", D_OUT },
+ { NULL, 0 }
+};
+
+static const struct nv_list service_type[] =
+{
+ {"builtin", S_BUILTIN },
+ {"always", S_ALWAYS },
+ { NULL, 0 }
+};
+
+static const struct nv_list formats[] =
+{
+ {"binary", F_BINARY },
+ {"string", F_STRING },
+ { NULL, 0 }
+};
+
+/*
+ * Set everything to its default value
+*/
+void clear_pconfig(plugin_conf_t *config)
+{
+ int i;
+
+ config->active = A_NO;
+ config->direction = D_UNSET;
+ config->path = NULL;
+ config->type = S_ALWAYS;
+ for (i=0; i< (MAX_PLUGIN_ARGS + 2); i++)
+ config->args[i] = NULL;
+ config->format = F_STRING;
+ config->plug_pipe[0] = -1;
+ config->plug_pipe[1] = -1;
+ config->pid = 0;
+ config->inode = 0;
+ config->checked = 0;
+ config->name = NULL;
+ config->restart_cnt = 0;
+}
+
+int load_pconfig(plugin_conf_t *config, char *file)
+{
+ int fd, rc, mode, lineno = 1;
+ struct stat st;
+ FILE *f;
+ char buf[160];
+
+ clear_pconfig(config);
+
+ /* open the file */
+ mode = O_RDONLY;
+ rc = open(file, mode);
+ if (rc < 0) {
+ if (errno != ENOENT) {
+ audit_msg(LOG_ERR, "Error opening %s (%s)", file,
+ strerror(errno));
+ return 1;
+ }
+ audit_msg(LOG_WARNING,
+ "Config file %s doesn't exist, skipping", file);
+ return 0;
+ }
+ fd = rc;
+
+ /* check the file's permissions: owned by root, not world writable,
+ * not symlink.
+ */
+ if (fstat(fd, &st) < 0) {
+ audit_msg(LOG_ERR, "Error fstat'ing config file (%s)",
+ strerror(errno));
+ close(fd);
+ return 1;
+ }
+ if (st.st_uid != 0) {
+ audit_msg(LOG_ERR, "Error - %s isn't owned by root",
+ file);
+ close(fd);
+ return 1;
+ }
+ if ((st.st_mode & S_IWOTH) == S_IWOTH) {
+ audit_msg(LOG_ERR, "Error - %s is world writable",
+ file);
+ close(fd);
+ return 1;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ audit_msg(LOG_ERR, "Error - %s is not a regular file",
+ file);
+ close(fd);
+ return 1;
+ }
+
+ /* it's ok, read line by line */
+ f = fdopen(fd, "rm");
+ if (f == NULL) {
+ audit_msg(LOG_ERR, "Error - fdopen failed (%s)",
+ strerror(errno));
+ close(fd);
+ return 1;
+ }
+
+ while (get_line(f, buf, sizeof(buf), &lineno, file)) {
+ // convert line into name-value pair
+ const struct kw_pair *kw;
+ struct nv_pair nv;
+ rc = nv_split(buf, &nv);
+ switch (rc) {
+ case 0: // fine
+ break;
+ case 1: // not the right number of tokens.
+ audit_msg(LOG_ERR,
+ "Wrong number of arguments for line %d in %s",
+ lineno, file);
+ break;
+ case 2: // no '=' sign
+ audit_msg(LOG_ERR,
+ "Missing equal sign for line %d in %s",
+ lineno, file);
+ break;
+ default: // something else went wrong...
+ audit_msg(LOG_ERR,
+ "Unknown error for line %d in %s",
+ lineno, file);
+ break;
+ }
+ if (nv.name == NULL) {
+ lineno++;
+ continue;
+ }
+ if (nv.value == NULL) {
+ fclose(f);
+ return 1;
+ }
+
+ /* identify keyword or error */
+ kw = kw_lookup(nv.name);
+ if (kw->name == NULL) {
+ audit_msg(LOG_ERR,
+ "Unknown keyword \"%s\" in line %d of %s",
+ nv.name, lineno, file);
+ fclose(f);
+ return 1;
+ }
+
+ /* Check number of options */
+ if (kw->max_options == 0 && nv.option != NULL) {
+ audit_msg(LOG_ERR,
+ "Keyword \"%s\" has invalid option "
+ "\"%s\" in line %d of %s",
+ nv.name, nv.option, lineno, file);
+ fclose(f);
+ return 1;
+ }
+
+ /* dispatch to keyword's local parser */
+ rc = kw->parser(&nv, lineno, config);
+ if (rc != 0) {
+ fclose(f);
+ return 1; // local parser puts message out
+ }
+
+ lineno++;
+ }
+
+ fclose(f);
+ config->name = strdup(basename(file));
+ if (lineno > 1)
+ return sanity_check(config, file);
+ return 0;
+}
+
+static char *get_line(FILE *f, char *buf, unsigned size, int *lineno,
+ const char *file)
+{
+ int too_long = 0;
+
+ while (fgets_unlocked(buf, size, f)) {
+ /* remove newline */
+ char *ptr = strchr(buf, 0x0a);
+ if (ptr) {
+ if (!too_long) {
+ *ptr = 0;
+ return buf;
+ }
+ // Reset and start with the next line
+ too_long = 0;
+ *lineno = *lineno + 1;
+ } else {
+ // If a line is too long skip it.
+ // Only output 1 warning
+ if (!too_long)
+ audit_msg(LOG_ERR,
+ "Skipping line %d in %s: too long",
+ *lineno, file);
+ too_long = 1;
+ }
+ }
+ return NULL;
+}
+
+static int nv_split(char *buf, struct nv_pair *nv)
+{
+ /* Get the name part */
+ char *ptr, *saved;
+
+ nv->name = NULL;
+ nv->value = NULL;
+ nv->option = NULL;
+ ptr = strtok_r(buf, " ", &saved);
+ if (ptr == NULL)
+ return 0; /* If there's nothing, go to next line */
+ if (ptr[0] == '#')
+ return 0; /* If there's a comment, go to next line */
+ nv->name = ptr;
+
+ /* Check for a '=' */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr == NULL)
+ return 1;
+ if (strcmp(ptr, "=") != 0)
+ return 2;
+
+ /* get the value */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr == NULL)
+ return 1;
+ nv->value = ptr;
+
+ /* See if there's an option */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr) {
+ nv->option = ptr;
+
+ /* Make sure there's nothing else */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr)
+ return 1;
+ }
+
+ /* Everything is OK */
+ return 0;
+}
+
+static const struct kw_pair *kw_lookup(const char *val)
+{
+ int i = 0;
+ while (keywords[i].name != NULL) {
+ if (strcasecmp(keywords[i].name, val) == 0)
+ break;
+ i++;
+ }
+ return &keywords[i];
+}
+
+static int active_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config)
+{
+ int i;
+
+ for (i=0; active[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, active[i].name) == 0) {
+ config->active = active[i].option;
+ return 0;
+ }
+ }
+ audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int direction_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config)
+{
+ int i;
+
+ for (i=0; directions[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, directions[i].name) == 0) {
+ config->direction = directions[i].option;
+ return 0;
+ }
+ }
+ audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int path_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config)
+{
+ char *dir = NULL, *tdir;
+ struct stat buf;
+
+ if (nv->value == NULL) {
+ config->path = NULL;
+ return 0;
+ }
+
+ if (strncasecmp(nv->value, "builtin_", 8) == 0) {
+ config->path = strdup(nv->value);
+ return 0;
+ }
+
+ /* get dir form name. */
+ tdir = strdup(nv->value);
+ if (tdir)
+ dir = dirname(tdir);
+ if (dir == NULL || strlen(dir) < 4) { // '/var' is shortest dirname
+ audit_msg(LOG_ERR,
+ "The directory name: %s is too short - line %d",
+ dir, line);
+ free(tdir);
+ return 1;
+ }
+
+ free((void *)tdir);
+ /* If the file exists, see that its regular, owned by root,
+ * and not world anything */
+ if (stat(nv->value, &buf) < 0) {
+ audit_msg(LOG_ERR, "Unable to stat %s (%s)", nv->value,
+ strerror(errno));
+ return 1;
+ }
+ if (!S_ISREG(buf.st_mode)) {
+ audit_msg(LOG_ERR, "%s is not a regular file", nv->value);
+ return 1;
+ }
+ if (buf.st_uid != 0) {
+ audit_msg(LOG_ERR, "%s is not owned by root", nv->value);
+ return 1;
+ }
+ if ((buf.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP)) !=
+ (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP)) {
+ audit_msg(LOG_ERR, "%s permissions should be 0750", nv->value);
+ return 1;
+ }
+ free((void *)config->path);
+ config->path = strdup(nv->value);
+ config->inode = buf.st_ino;
+ if (config->path == NULL)
+ return 1;
+ return 0;
+}
+
+static int service_type_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config)
+{
+ int i;
+
+ for (i=0; service_type[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, service_type[i].name) == 0) {
+ config->type = service_type[i].option;
+ return 0;
+ }
+ }
+ audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int args_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config)
+{
+ int i;
+
+ for (i=0; i < (MAX_PLUGIN_ARGS + 2); i++) {
+ free((void *)config->args[i]);
+ config->args[i] = NULL;
+ }
+
+ config->args[1] = strdup(nv->value);
+ if (nv->option)
+ config->args[2] = strdup(nv->option);
+ return 0;
+}
+
+static int format_parser(struct nv_pair *nv, int line,
+ plugin_conf_t *config)
+{
+ int i;
+
+ for (i=0; formats[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, formats[i].name) == 0) {
+ config->format = formats[i].option;
+ return 0;
+ }
+ }
+ audit_msg(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+/*
+ * This function is where we do the integrated check of the audispd config
+ * options. At this point, all fields have been read. Returns 0 if no
+ * problems and 1 if problems detected.
+ */
+static int sanity_check(plugin_conf_t *config, const char *file)
+{
+ /* Error checking */
+ if (config->active == A_YES && config->path == NULL) {
+ audit_msg(LOG_ERR,
+ "Error - plugin (%s) is active but no path given", file);
+ return 1;
+ }
+ return 0;
+}
+
+void free_pconfig(plugin_conf_t *config)
+{
+ int i;
+
+ if (config == NULL)
+ return;
+
+ for (i=0; i < (MAX_PLUGIN_ARGS + 2); i++)
+ free(config->args[i]);
+ if (config->plug_pipe[0] >= 0)
+ close(config->plug_pipe[0]);
+ if (config->plug_pipe[1] >= 0)
+ close(config->plug_pipe[1]);
+ free((void *)config->path);
+ free((void *)config->name);
+}
+
diff --git a/framework/src/audit/audisp/audispd-pconfig.h b/framework/src/audit/audisp/audispd-pconfig.h
new file mode 100644
index 00000000..05216352
--- /dev/null
+++ b/framework/src/audit/audisp/audispd-pconfig.h
@@ -0,0 +1,57 @@
+/* audispd-pconfig.h --
+ * Copyright 2007,2013 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#ifndef AUDISPD_PCONFIG_H
+#define AUDISPD_PCONFIG_H
+
+#include <sys/types.h>
+#include "libaudit.h"
+#define MAX_PLUGIN_ARGS 2
+
+typedef enum { A_NO, A_YES } active_t;
+typedef enum { D_UNSET, D_IN, D_OUT } direction_t;
+typedef enum { S_ALWAYS, S_BUILTIN, S_AF_UNIX, S_SYSLOG } service_t;
+typedef enum { F_BINARY, F_STRING } format_t;
+
+typedef struct plugin_conf
+{
+ active_t active; /* Current state - active or not */
+ direction_t direction; /* in or out kind of plugin */
+ const char *path; /* path to binary */
+ service_t type; /* builtin or always */
+ char *args[MAX_PLUGIN_ARGS+2]; /* args to be passed to plugin */
+ format_t format; /* Event format */
+ int plug_pipe[2]; /* Communication pipe for events */
+ pid_t pid; /* Used to signal children */
+ ino_t inode; /* Use to see if new binary was installed */
+ int checked; /* Used for internal housekeeping on HUP */
+ char *name; /* Used to distinguish plugins for HUP */
+ unsigned restart_cnt; /* Number of times its crashed */
+} plugin_conf_t;
+
+void clear_pconfig(plugin_conf_t *config);
+int load_pconfig(plugin_conf_t *config, char *file);
+void free_pconfig(plugin_conf_t *config);
+
+#endif
+
diff --git a/framework/src/audit/audisp/audispd.c b/framework/src/audit/audisp/audispd.c
new file mode 100644
index 00000000..06494b1e
--- /dev/null
+++ b/framework/src/audit/audisp/audispd.c
@@ -0,0 +1,854 @@
+/* audispd.c --
+ * Copyright 2007-08,2013 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <pthread.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/poll.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include "audispd-config.h"
+#include "audispd-pconfig.h"
+#include "audispd-llist.h"
+#include "audispd-builtins.h"
+#include "queue.h"
+#include "libaudit.h"
+
+/* Global Data */
+volatile int stop = 0;
+volatile int hup = 0;
+
+/* Local data */
+static daemon_conf_t daemon_config;
+static conf_llist plugin_conf;
+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/";
+
+/* Local function prototypes */
+static void signal_plugins(int sig);
+static int event_loop(void);
+static int safe_exec(plugin_conf_t *conf);
+static void *inbound_thread_main(void *arg);
+static void process_inbound_event(int fd);
+
+/*
+ * SIGTERM handler
+ */
+static void term_handler( int sig )
+{
+ stop = 1;
+}
+
+/*
+ * SIGCHLD handler
+ */
+static void child_handler( int sig )
+{
+ int status;
+ pid_t pid;
+
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid > 0) {
+ // Mark the child pid as 0 in the configs
+ lnode *tpconf;
+ plist_first(&plugin_conf);
+ tpconf = plist_get_cur(&plugin_conf);
+ while (tpconf) {
+ if (tpconf->p && tpconf->p->pid == pid) {
+ tpconf->p->pid = 0;
+ break;
+ }
+ tpconf = plist_next(&plugin_conf);
+ }
+ }
+}
+
+/*
+ * SIGHUP handler: re-read config
+ */
+static void hup_handler( int sig )
+{
+ hup = 1;
+}
+
+/*
+ * SIGALRM handler - help force exit when terminating daemon
+ */
+static void alarm_handler( int sig )
+{
+ pthread_cancel(inbound_thread);
+ raise(SIGTERM);
+}
+
+static int count_dots(const char *s)
+{
+ const char *ptr;
+ int cnt = 0;
+
+ while ((ptr = strchr(s, '.'))) {
+ cnt++;
+ s = ptr + 1;
+ }
+ return cnt;
+}
+
+static void load_plugin_conf(conf_llist *plugin)
+{
+ DIR *d;
+
+ /* init plugin list */
+ plist_create(plugin);
+
+ /* read configs */
+ d = opendir(plugin_dir);
+ if (d) {
+ struct dirent *e;
+
+ while ((e = readdir(d))) {
+ plugin_conf_t config;
+ char fname[PATH_MAX];
+
+ // Don't run backup files, hidden files, or dirs
+ if (e->d_name[0] == '.' || count_dots(e->d_name) > 1)
+ continue;
+
+ snprintf(fname, sizeof(fname), "%s%s",
+ plugin_dir, e->d_name);
+
+ clear_pconfig(&config);
+ if (load_pconfig(&config, fname) == 0) {
+ /* Push onto config list only if active */
+ if (config.active == A_YES)
+ plist_append(plugin, &config);
+ else
+ free_pconfig(&config);
+ } else
+ syslog(LOG_ERR,
+ "Skipping %s plugin due to errors",
+ e->d_name);
+ }
+ closedir(d);
+ }
+}
+
+static int start_one_plugin(lnode *conf)
+{
+ if (conf->p->restart_cnt > daemon_config.max_restarts)
+ return 1;
+
+ if (conf->p->type == S_BUILTIN)
+ start_builtin(conf->p);
+ else if (conf->p->type == S_ALWAYS) {
+ if (safe_exec(conf->p)) {
+ syslog(LOG_ERR,
+ "Error running %s (%s) continuing without it",
+ conf->p->path, strerror(errno));
+ conf->p->active = A_NO;
+ return 0;
+ }
+
+ /* Close the parent's read side */
+ close(conf->p->plug_pipe[0]);
+ conf->p->plug_pipe[0] = -1;
+ /* Avoid leaking descriptor */
+ fcntl(conf->p->plug_pipe[1], F_SETFD, FD_CLOEXEC);
+ }
+ return 1;
+}
+
+static int start_plugins(conf_llist *plugin)
+{
+ /* spawn children */
+ lnode *conf;
+ int active = 0;
+
+ plist_first(plugin);
+ conf = plist_get_cur(plugin);
+ if (conf == NULL || conf->p == NULL)
+ return active;
+
+ do {
+ if (conf->p && conf->p->active == A_YES) {
+ if (start_one_plugin(conf))
+ active++;
+ }
+ } while ((conf = plist_next(plugin)));
+ return active;
+}
+
+static int reconfigure(void)
+{
+ int rc;
+ daemon_conf_t tdc;
+ conf_llist tmp_plugin;
+ lnode *tpconf;
+
+ /* Read new daemon config */
+ rc = load_config(&tdc, config_file);
+ if (rc == 0) {
+ if (tdc.q_depth > daemon_config.q_depth) {
+ increase_queue_depth(tdc.q_depth);
+ daemon_config.q_depth = tdc.q_depth;
+ }
+ daemon_config.overflow_action = tdc.overflow_action;
+ reset_suspended();
+ /* We just fill these in because they are used by this
+ * same thread when we return
+ */
+ daemon_config.node_name_format = tdc.node_name_format;
+ free((char *)daemon_config.name);
+ daemon_config.name = tdc.name;
+ }
+
+ /* The idea for handling SIGHUP to children goes like this:
+ * 1) load the current config in temp list
+ * 2) mark all in real list unchecked
+ * 3) for each one in tmp list, scan old list
+ * 4) if new, start it, append to list, mark done
+ * 5) else check if there was a change to active state
+ * 6) if so, copy config over and start
+ * 7) If no change, send sighup to non-builtins and mark done
+ * 8) Finally, scan real list for unchecked, terminate and deactivate
+ */
+ syslog(LOG_INFO, "Starting reconfigure");
+ load_plugin_conf(&tmp_plugin);
+ plist_mark_all_unchecked(&plugin_conf);
+
+ plist_first(&tmp_plugin);
+ tpconf = plist_get_cur(&tmp_plugin);
+ while (tpconf && tpconf->p) {
+ lnode *opconf;
+
+ opconf = plist_find_name(&plugin_conf, tpconf->p->name);
+ if (opconf == NULL) {
+ /* We have a new service */
+ if (tpconf->p->active == A_YES) {
+ tpconf->p->checked = 1;
+ plist_last(&plugin_conf);
+ plist_append(&plugin_conf, tpconf->p);
+ free(tpconf->p);
+ tpconf->p = NULL;
+ start_one_plugin(plist_get_cur(&plugin_conf));
+ }
+ } else {
+ if (opconf->p->active == tpconf->p->active) {
+ /* If active and no state change, sighup it */
+ if (opconf->p->type == S_ALWAYS &&
+ opconf->p->active == A_YES) {
+ if (opconf->p->inode==tpconf->p->inode)
+ kill(opconf->p->pid, SIGHUP);
+ else {
+ /* Binary changed, restart */
+ syslog(LOG_INFO,
+ "Restarting %s since binary changed",
+ opconf->p->path);
+ kill(opconf->p->pid, SIGTERM);
+ usleep(50000); // 50 msecs
+ close(opconf->p->plug_pipe[1]);
+ opconf->p->plug_pipe[1] = -1;
+ opconf->p->pid = 0;
+ start_one_plugin(opconf);
+ opconf->p->inode =
+ tpconf->p->inode;
+ }
+ }
+ opconf->p->checked = 1;
+ } else {
+ /* A change in state */
+ if (tpconf->p->active == A_YES) {
+ /* starting - copy config and exec */
+ free_pconfig(opconf->p);
+ free(opconf->p);
+ opconf->p = tpconf->p;
+ opconf->p->checked = 1;
+ start_one_plugin(opconf);
+ tpconf->p = NULL;
+ }
+ }
+ }
+
+ tpconf = plist_next(&tmp_plugin);
+ }
+
+ /* Now see what's left over */
+ while ( (tpconf = plist_find_unchecked(&plugin_conf)) ) {
+ /* Anything not checked is something removed from the config */
+ tpconf->p->active = A_NO;
+ syslog(LOG_INFO, "Terminating %s because its now inactive",
+ tpconf->p->path);
+ if (tpconf->p->type == S_ALWAYS) {
+ kill(tpconf->p->pid, SIGTERM);
+ close(tpconf->p->plug_pipe[1]);
+ } else
+ stop_builtin(tpconf->p);
+ tpconf->p->plug_pipe[1] = -1;
+ tpconf->p->pid = 0;
+ tpconf->p->checked = 1;
+ }
+
+ /* Release memory from temp config */
+ plist_first(&tmp_plugin);
+ tpconf = plist_get_cur(&tmp_plugin);
+ while (tpconf) {
+ free_pconfig(tpconf->p);
+ tpconf = plist_next(&tmp_plugin);
+ }
+ plist_clear(&tmp_plugin);
+ return plist_count_active(&plugin_conf);
+}
+
+int main(int argc, char *argv[])
+{
+ lnode *conf;
+ struct sigaction sa;
+ int i;
+
+#ifndef DEBUG
+ /* Make sure we are root */
+ if (getuid() != 0) {
+ fprintf(stderr, "You must be root to run this program.\n");
+ return 4;
+ }
+#endif
+ set_aumessage_mode(MSG_SYSLOG, DBG_YES);
+
+ /* Clear any procmask set by libev */
+ sigfillset (&sa.sa_mask);
+ sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0);
+
+ /* Register sighandlers */
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ /* Ignore all signals by default */
+ sa.sa_handler = SIG_IGN;
+ for (i=1; i<NSIG; i++)
+ sigaction(i, &sa, NULL);
+ /* 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);
+ sa.sa_handler = alarm_handler;
+ sigaction(SIGALRM, &sa, NULL);
+ sa.sa_handler = child_handler;
+ sigaction(SIGCHLD, &sa, NULL);
+
+ /* move stdin to its own fd */
+ if (argc == 3 && strcmp(argv[1], "--input") == 0)
+ audit_fd = open(argv[2], O_RDONLY);
+ else
+ audit_fd = dup(0);
+ if (audit_fd < 0) {
+ syslog(LOG_ERR, "Failed setting up input, exiting");
+ return 1;
+ }
+
+ /* Make all descriptors point to dev null */
+ i = open("/dev/null", O_RDWR);
+ if (i >= 0) {
+ if (dup2(0, i) < 0 || dup2(1, i) < 0 || dup2(2, i) < 0) {
+ syslog(LOG_ERR, "Failed duping /dev/null %s, exiting",
+ strerror(errno));
+ return 1;
+ }
+ close(i);
+ } else {
+ syslog(LOG_ERR, "Failed opening /dev/null %s, exiting",
+ strerror(errno));
+ return 1;
+ }
+ if (fcntl(audit_fd, F_SETFD, FD_CLOEXEC) < 0) {
+ syslog(LOG_ERR, "Failed protecting input %s, exiting",
+ strerror(errno));
+ return 1;
+ }
+
+ /* init the daemon's config */
+ if (load_config(&daemon_config, config_file))
+ return 6;
+
+ load_plugin_conf(&plugin_conf);
+
+ /* if no plugins - exit */
+ if (plist_count(&plugin_conf) == 0) {
+ syslog(LOG_NOTICE, "No plugins found, exiting");
+ return 0;
+ }
+
+ /* Plugins are started with the auditd priority */
+ i = start_plugins(&plugin_conf);
+
+ /* Now boost priority to make sure we are getting time slices */
+ if (daemon_config.priority_boost != 0) {
+ int rc;
+
+ errno = 0;
+ rc = nice((int)-daemon_config.priority_boost);
+ if (rc == -1 && errno) {
+ syslog(LOG_ERR, "Cannot change priority (%s)",
+ strerror(errno));
+ /* Stay alive as this is better than stopping */
+ }
+ }
+
+ /* Let the queue initialize */
+ init_queue(daemon_config.q_depth);
+ syslog(LOG_INFO,
+ "audispd initialized with q_depth=%d and %d active plugins",
+ daemon_config.q_depth, i);
+
+ /* Tell it to poll the audit fd */
+ if (add_event(audit_fd, process_inbound_event) < 0) {
+ syslog(LOG_ERR, "Cannot add event, exiting");
+ return 1;
+ }
+
+ /* Create inbound thread */
+ pthread_create(&inbound_thread, NULL, inbound_thread_main, NULL);
+
+ /* Start event loop */
+ while (event_loop()) {
+ hup = 0;
+ if (reconfigure() == 0) {
+ syslog(LOG_INFO,
+ "After reconfigure, there are no active plugins, exiting");
+ break;
+ }
+ }
+
+ /* Tell plugins we are going down */
+ signal_plugins(SIGTERM);
+
+ /* Cleanup builtin plugins */
+ destroy_af_unix();
+ destroy_syslog();
+
+ /* Give it 5 seconds to clear the queue */
+ alarm(5);
+ pthread_join(inbound_thread, NULL);
+
+ /* Release configs */
+ plist_first(&plugin_conf);
+ conf = plist_get_cur(&plugin_conf);
+ while (conf) {
+ free_pconfig(conf->p);
+ conf = plist_next(&plugin_conf);
+ }
+ plist_clear(&plugin_conf);
+
+ /* Cleanup the queue */
+ destroy_queue();
+ free_config(&daemon_config);
+
+ return 0;
+}
+
+static int safe_exec(plugin_conf_t *conf)
+{
+ char *argv[MAX_PLUGIN_ARGS+2];
+ int pid, i;
+
+ /* Set up IPC with child */
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, conf->plug_pipe) != 0)
+ return -1;
+
+ pid = fork();
+ if (pid > 0) {
+ conf->pid = pid;
+ return 0; /* Parent...normal exit */
+ }
+ if (pid < 0) {
+ close(conf->plug_pipe[0]);
+ close(conf->plug_pipe[1]);
+ conf->pid = 0;
+ return -1; /* Failed to fork */
+ }
+
+ /* Set up comm with child */
+ dup2(conf->plug_pipe[0], 0);
+ for (i=3; i<24; i++) /* Arbitrary number */
+ close(i);
+
+ /* Child */
+ argv[0] = (char *)conf->path;
+ for (i=1; i<(MAX_PLUGIN_ARGS+1); i++)
+ argv[i] = conf->args[i];
+ argv[i] = NULL;
+ execve(conf->path, argv, NULL);
+ exit(1); /* Failed to exec */
+}
+
+static void signal_plugins(int sig)
+{
+ lnode *conf;
+
+ plist_first(&plugin_conf);
+ conf = plist_get_cur(&plugin_conf);
+ while (conf) {
+ if (conf->p && conf->p->pid && conf->p->type == S_ALWAYS)
+ kill(conf->p->pid, sig);
+ conf = plist_next(&plugin_conf);
+ }
+}
+
+static int write_to_plugin(event_t *e, const char *string, size_t string_len,
+ lnode *conf)
+{
+ int rc;
+
+ if (conf->p->format == F_STRING) {
+ do {
+ rc = write(conf->p->plug_pipe[1], string, string_len);
+ } while (rc < 0 && errno == EINTR);
+ } else {
+ struct iovec vec[2];
+
+ vec[0].iov_base = &e->hdr;
+ vec[0].iov_len = sizeof(struct audit_dispatcher_header);
+
+ vec[1].iov_base = e->data;
+ vec[1].iov_len = MAX_AUDIT_MESSAGE_LENGTH;
+ do {
+ rc = writev(conf->p->plug_pipe[1], vec, 2);
+ } while (rc < 0 && errno == EINTR);
+ }
+ return rc;
+}
+
+/* Returns 0 on stop, and 1 on HUP */
+static int event_loop(void)
+{
+ char *name = NULL, tmp_name[255];
+
+ /* Get the host name representation */
+ switch (daemon_config.node_name_format)
+ {
+ case N_NONE:
+ break;
+ case N_HOSTNAME:
+ if (gethostname(tmp_name, sizeof(tmp_name))) {
+ syslog(LOG_ERR, "Unable to get machine name");
+ name = strdup("?");
+ } else
+ name = strdup(tmp_name);
+ break;
+ case N_USER:
+ if (daemon_config.name)
+ name = strdup(daemon_config.name);
+ else {
+ syslog(LOG_ERR, "User defined name missing");
+ name = strdup("?");
+ }
+ break;
+ case N_FQD:
+ if (gethostname(tmp_name, sizeof(tmp_name))) {
+ syslog(LOG_ERR, "Unable to get machine name");
+ name = strdup("?");
+ } else {
+ int rc;
+ struct addrinfo *ai;
+ struct addrinfo hints;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_ADDRCONFIG | AI_CANONNAME;
+ hints.ai_socktype = SOCK_STREAM;
+
+ rc = getaddrinfo(tmp_name, NULL, &hints, &ai);
+ if (rc != 0) {
+ syslog(LOG_ERR,
+ "Cannot resolve hostname %s (%s)",
+ tmp_name, gai_strerror(rc));
+ name = strdup("?");
+ break;
+ }
+ name = strdup(ai->ai_canonname);
+ freeaddrinfo(ai);
+ }
+ break;
+ case N_NUMERIC:
+ if (gethostname(tmp_name, sizeof(tmp_name))) {
+ syslog(LOG_ERR, "Unable to get machine name");
+ name = strdup("?");
+ } else {
+ int rc;
+ struct addrinfo *ai;
+ struct addrinfo hints;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
+ hints.ai_socktype = SOCK_STREAM;
+
+ rc = getaddrinfo(tmp_name, NULL, &hints, &ai);
+ if (rc != 0) {
+ syslog(LOG_ERR,
+ "Cannot resolve hostname %s (%s)",
+ tmp_name, gai_strerror(rc));
+ name = strdup("?");
+ break;
+ }
+ inet_ntop(ai->ai_family,
+ ai->ai_family == AF_INET ?
+ (void *) &((struct sockaddr_in *)ai->ai_addr)->sin_addr :
+ (void *) &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr,
+ tmp_name, INET6_ADDRSTRLEN);
+ freeaddrinfo(ai);
+ name = strdup(tmp_name);
+ }
+ break;
+ }
+
+ /* Figure out the format for the af_unix socket */
+ while (stop == 0) {
+ event_t *e;
+ const char *type;
+ char *v, *ptr, unknown[32];
+ unsigned int len;
+ lnode *conf;
+
+ /* This is where we block until we have an event */
+ e = dequeue();
+ if (e == NULL) {
+ if (hup) {
+ free(name);
+ return 1;
+ }
+ continue;
+ }
+
+ /* Get the event formatted */
+ type = audit_msg_type_to_name(e->hdr.type);
+ if (type == NULL) {
+ snprintf(unknown, sizeof(unknown),
+ "UNKNOWN[%d]", e->hdr.type);
+ type = unknown;
+ }
+
+ if (daemon_config.node_name_format != N_NONE) {
+ len = asprintf(&v, "node=%s type=%s msg=%.*s\n",
+ name, type, e->hdr.size, e->data);
+ } else
+ len = asprintf(&v, "type=%s msg=%.*s\n",
+ type, e->hdr.size, e->data);
+ if (len <= 0) {
+ v = NULL;
+ free(e); /* Either corrupted event or no memory */
+ continue;
+ }
+
+ /* Strip newlines from event record */
+ ptr = v;
+ while ((ptr = strchr(ptr, 0x0A)) != NULL) {
+ if (ptr != &v[len-1])
+ *ptr = ' ';
+ else
+ break; /* Done - exit loop */
+ }
+
+ /* Distribute event to the plugins */
+ plist_first(&plugin_conf);
+ conf = plist_get_cur(&plugin_conf);
+ do {
+ if (conf == NULL || conf->p == NULL)
+ continue;
+ if (conf->p->active == A_NO || stop)
+ continue;
+
+ /* Now send the event to the right child */
+ if (conf->p->type == S_SYSLOG)
+ send_syslog(v);
+ else if (conf->p->type == S_AF_UNIX) {
+ if (conf->p->format == F_STRING)
+ send_af_unix_string(v, len);
+ else
+ send_af_unix_binary(e);
+ } else if (conf->p->type == S_ALWAYS && !stop) {
+ int rc;
+ rc = write_to_plugin(e, v, len, conf);
+ if (rc < 0 && errno == EPIPE) {
+ /* Child disappeared ? */
+ syslog(LOG_ERR,
+ "plugin %s terminated unexpectedly",
+ conf->p->path);
+ conf->p->pid = 0;
+ conf->p->restart_cnt++;
+ if (conf->p->restart_cnt >
+ daemon_config.max_restarts) {
+ syslog(LOG_ERR,
+ "plugin %s has exceeded max_restarts",
+ conf->p->path);
+ }
+ close(conf->p->plug_pipe[1]);
+ conf->p->plug_pipe[1] = -1;
+ conf->p->active = A_NO;
+ if (!stop && start_one_plugin(conf)) {
+ rc = write_to_plugin(e, v, len,
+ conf);
+ syslog(LOG_NOTICE,
+ "plugin %s was restarted",
+ conf->p->path);
+ conf->p->active = A_YES;
+ }
+ }
+ }
+ } while (!stop && (conf = plist_next(&plugin_conf)));
+
+ /* Done with the memory...release it */
+ free(v);
+ free(e);
+ if (hup)
+ break;
+ }
+ free(name);
+ if (stop)
+ return 0;
+ else
+ return 1;
+}
+
+static struct pollfd pfd[4];
+static poll_callback_ptr pfd_cb[4];
+static volatile int pfd_cnt=0;
+int add_event(int fd, poll_callback_ptr cb)
+{
+ if (pfd_cnt > 3)
+ return -1;
+
+ pfd[pfd_cnt].fd = fd;
+ pfd[pfd_cnt].events = POLLIN;
+ pfd[pfd_cnt].revents = 0;
+ pfd_cb[pfd_cnt] = cb;
+ pfd_cnt++;
+ return 0;
+}
+
+int remove_event(int fd)
+{
+ int start, i;
+ if (pfd_cnt == 0)
+ return -1;
+
+ for (start=0; start < pfd_cnt; start++) {
+ if (pfd[start].fd == fd)
+ break;
+ }
+ for (i=start; i<(pfd_cnt-1); i++) {
+ pfd[i].events = pfd[i+1].events;
+ pfd[i].revents = pfd[i+1].revents;
+ pfd[i].fd = pfd[i+1].fd;
+ pfd_cb[i] = pfd_cb[i+1];
+ }
+
+ pfd_cnt--;
+ return 0;
+}
+
+/* inbound thread - enqueue inbound data to intermediate table */
+static void *inbound_thread_main(void *arg)
+{
+ while (stop == 0) {
+ int rc;
+ if (hup)
+ nudge_queue();
+ do {
+ rc = poll(pfd, pfd_cnt, 20000); /* 20 sec */
+ } while (rc < 0 && errno == EAGAIN && stop == 0 && hup == 0);
+ if (rc == 0)
+ continue;
+
+ /* Event readable... */
+ if (rc > 0) {
+ /* Figure out the fd that is ready and call */
+ int i = 0;
+ while (i < pfd_cnt) {
+ if (pfd[i].revents & POLLIN)
+ pfd_cb[i](pfd[i].fd);
+ i++;
+ }
+ }
+ }
+ /* make sure event loop wakes up */
+ nudge_queue();
+ return NULL;
+}
+
+static void process_inbound_event(int fd)
+{
+ int rc;
+ struct iovec vec;
+ event_t *e = malloc(sizeof(event_t));
+ if (e == NULL)
+ return;
+ memset(e, 0, sizeof(event_t));
+
+ /* Get header first. It is fixed size */
+ vec.iov_base = &e->hdr;
+ vec.iov_len = sizeof(struct audit_dispatcher_header);
+ do {
+ rc = readv(fd, &vec, 1);
+ } while (rc < 0 && errno == EINTR);
+
+ if (rc <= 0) {
+ if (rc == 0)
+ stop = 1; // End of File
+ free(e);
+ return;
+ }
+
+ if (rc > 0) {
+ /* Sanity check */
+ if (e->hdr.ver != AUDISP_PROTOCOL_VER ||
+ e->hdr.hlen != sizeof(e->hdr) ||
+ e->hdr.size > MAX_AUDIT_MESSAGE_LENGTH) {
+ free(e);
+ syslog(LOG_ERR,
+ "Dispatcher protocol mismatch, exiting");
+ exit(1);
+ }
+
+ /* Next payload */
+ vec.iov_base = e->data;
+ vec.iov_len = e->hdr.size;
+ do {
+ rc = readv(fd, &vec, 1);
+ } while (rc < 0 && errno == EINTR);
+
+ if (rc > 0)
+ enqueue(e, &daemon_config);
+ else {
+ if (rc == 0)
+ stop = 1; // End of File
+ free(e);
+ }
+ }
+}
+
diff --git a/framework/src/audit/audisp/plugins/Makefile.am b/framework/src/audit/audisp/plugins/Makefile.am
new file mode 100644
index 00000000..2cba14b8
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/Makefile.am
@@ -0,0 +1,32 @@
+# Makefile.am --
+# Copyright 2007-08 Red Hat Inc., Durham, North Carolina.
+# All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Authors:
+# Steve Grubb <sgrubb@redhat.com>
+#
+
+CONFIG_CLEAN_FILES = *.loT *.rej *.orig
+
+SUBDIRS = builtins remote
+if ENABLE_ZOS_REMOTE
+SUBDIRS += zos-remote
+endif
+if HAVE_PRELUDE
+SUBDIRS += prelude
+endif
+
diff --git a/framework/src/audit/audisp/plugins/builtins/Makefile.am b/framework/src/audit/audisp/plugins/builtins/Makefile.am
new file mode 100644
index 00000000..713dee86
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/builtins/Makefile.am
@@ -0,0 +1,39 @@
+# Makefile.am--
+# Copyright 2007 Red Hat Inc., Durham, North Carolina.
+# All Rights Reserved.
+#
+# 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:
+# Steve Grubb <sgrubb@redhat.com>
+#
+
+CONFIG_CLEAN_FILES = *.rej *.orig
+CONF_FILES = af_unix.conf syslog.conf
+EXTRA_DIST = $(CONF_FILES)
+plugin_confdir=$(sysconfdir)/audisp/plugins.d
+
+install-data-hook:
+ mkdir -p -m 0750 ${DESTDIR}${plugin_confdir}
+ for i in $(CONF_FILES); do \
+ $(INSTALL_DATA) -D -m 640 ${srcdir}/"$$i" \
+ ${DESTDIR}${plugin_confdir}; \
+ done
+
+uninstall-hook:
+ for i in $(CONF_FILES); do \
+ rm ${DESTDIR}${plugin_confdir}/"$$i"; \
+ done
+
diff --git a/framework/src/audit/audisp/plugins/builtins/af_unix.conf b/framework/src/audit/audisp/plugins/builtins/af_unix.conf
new file mode 100644
index 00000000..a5ba8b1f
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/builtins/af_unix.conf
@@ -0,0 +1,14 @@
+
+# This file controls the configuration of the
+# af_unix socket plugin. It simply takes events
+# and writes them to a unix domain socket. This
+# plugin can take 2 arguments, the path for the
+# socket and the socket permissions in octal.
+
+active = no
+direction = out
+path = builtin_af_unix
+type = builtin
+args = 0640 /var/run/audispd_events
+format = string
+
diff --git a/framework/src/audit/audisp/plugins/builtins/syslog.conf b/framework/src/audit/audisp/plugins/builtins/syslog.conf
new file mode 100644
index 00000000..d603b2f2
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/builtins/syslog.conf
@@ -0,0 +1,13 @@
+# This file controls the configuration of the syslog plugin.
+# It simply takes events and writes them to syslog. The
+# arguments provided can be the default priority that you
+# want the events written with. And optionally, you can give
+# a second argument indicating the facility that you want events
+# logged to. Valid options are LOG_LOCAL0 through 7.
+
+active = no
+direction = out
+path = builtin_syslog
+type = builtin
+args = LOG_INFO
+format = string
diff --git a/framework/src/audit/audisp/plugins/prelude/Makefile.am b/framework/src/audit/audisp/plugins/prelude/Makefile.am
new file mode 100644
index 00000000..a70d7652
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/prelude/Makefile.am
@@ -0,0 +1,50 @@
+# Makefile.am --
+# Copyright 2008-09,2015 Red Hat Inc., Durham, North Carolina.
+# All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Authors:
+# Steve Grubb <sgrubb@redhat.com>
+#
+
+CONFIG_CLEAN_FILES = *.rej *.orig
+EXTRA_DIST = au-prelude.conf audisp-prelude.conf
+AUTOMAKE_OPTIONS = no-dependencies
+AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/lib -I${top_srcdir}/auparse
+LIBS = -L${top_builddir}/auparse/.libs -lauparse -lprelude
+LDADD = -lpthread $(CAPNG_LDADD)
+prog_confdir = $(sysconfdir)/audisp
+prog_conf = audisp-prelude.conf
+plugin_confdir=$(prog_confdir)/plugins.d
+plugin_conf = au-prelude.conf
+sbin_PROGRAMS = audisp-prelude
+noinst_HEADERS = prelude-config.h audisp-int.h
+dist_man_MANS = audisp-prelude.8 audisp-prelude.conf.5
+
+audisp_prelude_SOURCES = audisp-prelude.c prelude-config.c audisp-int.c
+audisp_prelude_CFLAGS = -fPIE -DPIE -g -D_REENTRANT -D_GNU_SOURCE -Wundef \
+ @LIBPRELUDE_CFLAGS@
+audisp_prelude_LDFLAGS = -pie -Wl,-z,relro -Wl,-z,now @LIBPRELUDE_LDFLAGS@
+
+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/framework/src/audit/audisp/plugins/prelude/au-prelude.conf b/framework/src/audit/audisp/plugins/prelude/au-prelude.conf
new file mode 100644
index 00000000..513fcf91
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/prelude/au-prelude.conf
@@ -0,0 +1,12 @@
+
+# This file controls the audispd data path to the audit
+# based prelude IDS (Intrusion Detection System) plugin. It
+# watches events and sends intersting ones to the prelude manager.
+
+active = no
+direction = out
+path = /sbin/audisp-prelude
+type = always
+#args =
+format = string
+
diff --git a/framework/src/audit/audisp/plugins/prelude/audisp-int.c b/framework/src/audit/audisp/plugins/prelude/audisp-int.c
new file mode 100644
index 00000000..54d7a3df
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/prelude/audisp-int.c
@@ -0,0 +1,114 @@
+/*
+* audisp-int.c - Minimal linked list library for integers
+* Copyright (c) 2008 Red Hat Inc., Durham, North Carolina.
+* All Rights Reserved.
+*
+* This software may be freely redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free
+* Software Foundation; either version 2, or (at your option) any
+* later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; see the file COPYING. If not, write to the
+* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+* Authors:
+* Steve Grubb <sgrubb@redhat.com>
+*/
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include "audisp-int.h"
+
+void ilist_create(ilist *l)
+{
+ l->head = NULL;
+ l->cur = NULL;
+ l->cnt = 0;
+}
+
+int_node *ilist_next(ilist *l)
+{
+ if (l->cur == NULL)
+ return NULL;
+ l->cur = l->cur->next;
+ return l->cur;
+}
+
+void ilist_append(ilist *l, int num)
+{
+ int_node* newnode;
+
+ newnode = malloc(sizeof(int_node));
+
+ newnode->num = num;
+ newnode->next = NULL;
+
+ // if we are at top, fix this up
+ if (l->head == NULL)
+ l->head = newnode;
+ else // Otherwise add pointer to newnode
+ l->cur->next = newnode;
+
+ // make newnode current
+ l->cur = newnode;
+ l->cnt++;
+}
+
+int ilist_find_num(ilist *l, unsigned int num)
+{
+ register int_node* window = l->head;
+
+ while (window) {
+ if (window->num == num) {
+ l->cur = window;
+ return 1;
+ }
+ else
+ window = window->next;
+ }
+ return 0;
+}
+
+void ilist_clear(ilist* l)
+{
+ int_node* nextnode;
+ register int_node* current;
+
+ if (l == NULL)
+ return;
+
+ current = l->head;
+ while (current) {
+ nextnode=current->next;
+ free(current);
+ current=nextnode;
+ }
+ l->head = NULL;
+ l->cur = NULL;
+ l->cnt = 0;
+}
+
+int ilist_add_if_uniq(ilist *l, int num)
+{
+ register int_node* cur;
+
+ cur = l->head;
+ while (cur) {
+ if (cur->num == num)
+ return 0;
+ else
+ cur = cur->next;
+ }
+
+ /* No matches, append to the end */
+ ilist_append(l, num);
+ return 1;
+}
+
diff --git a/framework/src/audit/audisp/plugins/prelude/audisp-int.h b/framework/src/audit/audisp/plugins/prelude/audisp-int.h
new file mode 100644
index 00000000..b0204753
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/prelude/audisp-int.h
@@ -0,0 +1,57 @@
+/*
+* audisp-int.h - Header file for audisp-int.c
+* Copyright (c) 2008 Red Hat Inc., Durham, North Carolina.
+* All Rights Reserved.
+*
+* This software may be freely redistributed and/or modified under the
+* terms of the GNU General Public License as published by the Free
+* Software Foundation; either version 2, or (at your option) any
+* later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; see the file COPYING. If not, write to the
+* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*
+* Authors:
+* Steve Grubb <sgrubb@redhat.com>
+*/
+
+#ifndef AUINT_HEADER
+#define AUINT_HEADER
+
+#include "config.h"
+#include <sys/types.h>
+
+/* This is the node of the linked list. Number & item are the only elements
+ * at this time. Any data elements that are per item goes here. */
+typedef struct _int_node{
+ int num; // The number
+ struct _int_node* next; // Next string node pointer
+} int_node;
+
+/* This is the linked list head. Only data elements that are 1 per
+ * event goes here. */
+typedef struct {
+ int_node *head; // List head
+ int_node *cur; // Pointer to current node
+ unsigned int cnt; // How many items in this list
+} ilist;
+
+void ilist_create(ilist *l);
+static inline void ilist_first(ilist *l) { l->cur = l->head; }
+int_node *ilist_next(ilist *l);
+static inline int_node *ilist_get_cur(ilist *l) { return l->cur; }
+void ilist_append(ilist *l, int num);
+void ilist_clear(ilist* l);
+int ilist_find_num(ilist *l, unsigned int num);
+
+/* append a number if its not already on the list */
+int ilist_add_if_uniq(ilist *l, int num);
+
+#endif
+
diff --git a/framework/src/audit/audisp/plugins/prelude/audisp-prelude.8 b/framework/src/audit/audisp/plugins/prelude/audisp-prelude.8
new file mode 100644
index 00000000..e457407e
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/prelude/audisp-prelude.8
@@ -0,0 +1,72 @@
+.TH AUDISP-PRELUDE: "8" "Dec 2008" "Red Hat" "System Administration Utilities"
+.SH NAME
+audisp\-prelude \- plugin for IDMEF alerts
+.SH SYNOPSIS
+.B audisp\-prelude [ \-\-test ]
+.SH DESCRIPTION
+\fBaudisp\-prelude\fP is a plugin for the audit event dispatcher daemon, audispd, that uses libprelude to send IDMEF alerts for possible Intrusion Detection events. This plugin requires connecting to a prelude\-manager to record the events it sends. This plugin will analyze audit events in realtime and send detected events to the prelude\-manager for correlation, recording, and display.
+
+Events that are currently supported are: Logins, Forbidden Login Location, Max Concurrent Sessions, Max Login Failures, Forbidden Login Time, SE Linux AVCs, SE Linux Enforcement Changes, Abnormal Program Termination, Promiscuous Socket Changes, and watched account logins.
+
+.SH OPTIONS
+.TP
+.B \-\-test
+Take input from stdin and write prelude events to stdout but does not send them to the prelude\-manager. This can be used for debugging or testing the system with suspicious log files when you do not want it to alert or react.
+
+.SH INSTALLATION
+This sensor has to be registered with the prelude\-manager before it will work properly. If the prelude\-manager is on the same host as the sensor, you will need to open two windows to register. If not, you will have to adjust this example to fit your environment.
+
+In one window, type:
+
+.B prelude\-admin register auditd "idmef:w" localhost \-\-uid 0 \-\-gid 0
+
+In another, type:
+
+.B prelude\-admin registration\-server prelude\-manager
+
+Follow the on\-screen instructions to complete the registration.
+
+.SH TIPS
+If you are aggregating multiple machines, you should enable node information in the audit event stream. You can do this in one of two places. If you want computer node names written to disk as well as sent in the realtime event stream, edit the name_format option in /etc/audit/auditd.conf. If you only want the node names in the realtime event stream, then edit the name_format option in /etc/audisp/audispd.conf. Do not enable both as it will put 2 node fields in the event stream.
+
+At this point, if you want have audit: forbidden login location, max concurrent sessions, max login failures, and forbidden login time anomalies being reported, you have to setup pam modules correctly. The pam modules are respectively: pam_access, pam_limits, pam_tally2, and pam_time. Please see the respective pam module man pages for any instructions.
+
+For performance reasons, some audit events will not produce syscall records which contain additional information about events unless there is at least one audit rule loaded. If you do not have any additional audit rules, edit \fI/etc/audit/audit.rules\fP and add something simple that won't impact performace like this: \fB\-w /etc/shadow \-p wa\fP. This rule will watch the shadow file for writes or changes to its attributes. The additional audit information provided by having at least one rule will allow the plugin to give a more complete view of the alert it is sending.
+
+If you are wanting to get alerts on watched syscalls, watched files, watched execution, or something becoming executable, you need to add some keys to your audit rules. For example, if you have the following audit watch in \fI/etc/audit/audit.rules\fP:
+
+.B \-w /etc/shadow \-p wa
+
+and you want idmef alerts on this, you need to add \fB\-k ids\-file\-med\fP or something appropriate to signal to the plugin that this message is for it. The format of the key has a fixed format of keywords separated by a dash. It follows the form of
+.IB ids \- type \- severity .
+The \fItype\fP can be either \fBsys\fP, \fBfile\fP, \fBexec\fP, or \fBmkexe\fP depending on whether you want the event to be considered a watched_syscall, watched_file, watched_exec, or watched_mk_exe respectively. The \fIseverity\fP can be either \fBinfo\fP, \fBlow\fP, \fBmed\fP, or \fBhi\fP depending on how urgent you would like it to be.
+
+.SH EXAMPLE RULES
+To alert on any use of the personality syscall:
+.br
+.B \-a always,exit \-S personality \-k ids\-sys\-med
+
+To alert on a user failing to access the shadow file:
+.br
+.B \-a always,exit \-F path=/etc/shadow \-F perms=wa \-F success=0 \-k ids\-file\-med
+
+To alert on the execution of a program:
+.br
+.B \-w /bin/ping \-p x \-k ids\-exe\-info
+
+To alert on users making exe's in their home dir (takes 2 rules):
+.br
+.B \-a always,exit \-S fchmodat \-F dir=/home \-F a2&0111 \-F filetype=file \-k ids\-mkexe\-hi
+.br
+.B \-a always,exit \-S fchmod,chmod \-F dir=/home \-F a1&0111 \-F filetype=file \-k ids\-mkexe\-hi
+
+.SH FILES
+/etc/audisp/plugins.d/au\-prelude.conf, /etc/audit/auditd.conf, /etc/audisp/audispd.conf, /etc/audisp/audisp\-prelude.conf
+.SH "SEE ALSO"
+.BR audispd (8),
+.BR prelude\-manager (1),
+.BR auditd.conf (8),
+.BR audispd.conf (8),
+.BR audisp\-prelude.conf (5).
+.SH AUTHOR
+Steve Grubb
diff --git a/framework/src/audit/audisp/plugins/prelude/audisp-prelude.c b/framework/src/audit/audisp/plugins/prelude/audisp-prelude.c
new file mode 100644
index 00000000..f3dc65a0
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/prelude/audisp-prelude.c
@@ -0,0 +1,2250 @@
+/* audisp-prelude.c --
+ * Copyright 2008-09,2011-12 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <syslog.h>
+#include <string.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <errno.h>
+#include <libprelude/prelude.h>
+#include <libprelude/idmef-message-print.h>
+#ifdef HAVE_LIBCAP_NG
+#include <cap-ng.h>
+#endif
+#include "libaudit.h"
+#include "auparse.h"
+#include "prelude-config.h"
+
+#define CONFIG_FILE "/etc/audisp/audisp-prelude.conf"
+#define ANALYZER_MODEL "auditd"
+#define ANALYZER_CLASS "HIDS"
+#define ANALYZER_MANUFACTURER "Red Hat, http://people.redhat.com/sgrubb/audit/"
+#define PRELUDE_FAIL_CHECK if (ret < 0) goto err;
+
+typedef enum { AS_LOGIN, AS_MAX_LOGIN_FAIL, AS_MAX_LOGIN_SESS, AS_ABEND,
+ AS_PROM, AS_MAC_STAT, AS_LOGIN_LOCATION, AS_LOGIN_TIME, AS_MAC,
+ AS_AUTH, AS_WATCHED_LOGIN, AS_WATCHED_FILE, AS_WATCHED_EXEC, AS_MK_EXE,
+ AS_MMAP0, AS_WATCHED_SYSCALL, AS_TTY, AS_TOTAL } as_description_t;
+const char *assessment_description[AS_TOTAL] = {
+ "A user has attempted to login",
+ "The maximum allowed login failures for this account has been reached. This could be an attempt to gain access to the account by someone other than the real account holder.",
+ "The maximum allowed concurrent logins for this account has been reached.",
+ "An application terminated abnormally. An attacker may be trying to exploit a weakness in the program.",
+ "A program has opened or closed a promiscuous socket. If this is not expected, it could be an attacker trying to sniff traffic.",
+ "A program has changed SE Linux policy enforcement. If this is not expected, it could be an attempt to subvert the system.",
+ "A user attempted to login from a location that is not allowed. This could be an attempt to gain access to the account by someone other than the real account holder.",
+ "A user attempted to login during a time that the user should not be logging into the system. This could be an attempt to gain access to the account by someone other than the real account holder.",
+ "A program has tried to access something that is not allowed in the MAC policy. This could indicate an attacker trying to exploit a weakness in the program.",
+ "A user has attempted to use an authentication mechanism and failed. This could be an attempt to gain privileges that they are not supposed to have.",
+ "A user has logged in to an account that is being watched.",
+ "A user has attempted to access a file that is being watched.",
+ "A user has attempted to execute a program that is being watched.",
+ "A user has attempted to create an executable program",
+ "A program has attempted mmap a fixed memory page at an address sometimes used as part of a kernel exploit",
+ "A user has run a command that issued a watched syscall",
+ "A user has typed keystrokes on a terminal"
+};
+typedef enum { M_NORMAL, M_TEST } output_t;
+typedef enum { W_NO, W_FILE, W_EXEC, W_MK_EXE } watched_t;
+
+/* Global Data */
+static volatile int stop = 0;
+static volatile int hup = 0;
+static prelude_client_t *client = NULL;
+static auparse_state_t *au = NULL;
+static prelude_conf_t config;
+static output_t mode = M_NORMAL;
+static char *myhostname=NULL;
+
+/* Local declarations */
+static void handle_event(auparse_state_t *au,
+ auparse_cb_event_t cb_event_type, void *user_data);
+
+/*
+ * SIGTERM handler
+ */
+static void term_handler( int sig )
+{
+ stop = 1;
+}
+
+/*
+ * SIGHUP handler: re-read config
+ */
+static void hup_handler( int sig )
+{
+ hup = 1;
+}
+
+static void reload_config(void)
+{
+ hup = 0;
+}
+
+static int setup_analyzer(idmef_analyzer_t *analyzer)
+{
+ int ret;
+ prelude_string_t *string;
+
+ ret = idmef_analyzer_new_model(analyzer, &string);
+ PRELUDE_FAIL_CHECK;
+ prelude_string_set_dup(string, ANALYZER_MODEL);
+
+ ret = idmef_analyzer_new_class(analyzer, &string);
+ PRELUDE_FAIL_CHECK;
+ prelude_string_set_dup(string, ANALYZER_CLASS);
+
+ ret = idmef_analyzer_new_manufacturer(analyzer, &string);
+ PRELUDE_FAIL_CHECK;
+ prelude_string_set_dup(string, ANALYZER_MANUFACTURER);
+
+ ret = idmef_analyzer_new_version(analyzer, &string);
+ PRELUDE_FAIL_CHECK;
+ prelude_string_set_dup(string, PACKAGE_VERSION);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "%s: IDMEF error: %s.\n",
+ prelude_strsource(ret), prelude_strerror(ret));
+
+ return -1;
+}
+
+static int init_prelude(int argc, char *argv[])
+{
+ int ret;
+ prelude_client_flags_t flags;
+
+ ret = prelude_thread_init(NULL);
+ ret = prelude_init(&argc, argv);
+ if (ret < 0) {
+ syslog(LOG_ERR,
+ "Unable to initialize the Prelude library: %s.\n",
+ prelude_strerror(ret));
+ return -1;
+ }
+ ret = prelude_client_new(&client,
+ config.profile ? config.profile : ANALYZER_MODEL);
+ if (! client) {
+ syslog(LOG_ERR,
+ "Unable to create a prelude client object: %s.\n",
+ prelude_strerror(ret));
+ return -1;
+ }
+ ret = setup_analyzer(prelude_client_get_analyzer(client));
+ if (ret < 0) {
+ syslog(LOG_ERR, "Unable to setup analyzer: %s\n",
+ prelude_strerror(ret));
+
+ prelude_client_destroy(client,
+ PRELUDE_CLIENT_EXIT_STATUS_FAILURE);
+ prelude_deinit();
+ return -1;
+ }
+ if (mode == M_NORMAL) {
+ flags = prelude_client_get_flags(client);
+ flags |= PRELUDE_CLIENT_FLAGS_ASYNC_TIMER;
+ } else
+ flags = 0; // Debug mode
+ ret = prelude_client_set_flags(client, flags);
+ if (ret < 0) {
+ syslog(LOG_ERR, "Unable to set prelude client flags: %s\n",
+ prelude_strerror(ret));
+
+ prelude_client_destroy(client,
+ PRELUDE_CLIENT_EXIT_STATUS_FAILURE);
+ prelude_deinit();
+ return -1;
+ }
+ ret = prelude_client_start(client);
+ if (ret < 0) {
+ syslog(LOG_ERR, "Unable to start prelude client: %s\n",
+ prelude_strerror(ret));
+
+ prelude_client_destroy(client,
+ PRELUDE_CLIENT_EXIT_STATUS_FAILURE);
+ prelude_deinit();
+ return -1;
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ char tmp[MAX_AUDIT_MESSAGE_LENGTH+1];
+ struct sigaction sa;
+
+ if (argc > 1) {
+ if (argc == 2 && strcmp(argv[1], "--test") == 0) {
+ mode = M_TEST;
+ } else {
+ fprintf(stderr, "Usage: audisp-prelude [--test]\n");
+ return 1;
+ }
+ }
+
+ /* 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);
+ if (load_config(&config, CONFIG_FILE)) {
+ if (mode == M_TEST)
+ puts("audisp-prelude is exiting on config load error");
+ return 6;
+ }
+
+ /* Initialize the auparse library */
+ au = auparse_init(AUSOURCE_FEED, 0);
+ if (au == NULL) {
+ syslog(LOG_ERR,
+ "audisp-prelude is exiting due to auparse init errors");
+ free_config(&config);
+ return -1;
+ }
+ auparse_add_callback(au, handle_event, NULL, NULL);
+ if (init_prelude(argc, argv)) {
+ if (mode == M_TEST)
+ puts("audisp-prelude is exiting due to init_prelude");
+ else
+ syslog(LOG_ERR,
+ "audisp-prelude is exiting due to init_prelude failure");
+ free_config(&config);
+ auparse_destroy(au);
+ return -1;
+ }
+#ifdef HAVE_LIBCAP_NG
+ // Drop all capabilities
+ capng_clear(CAPNG_SELECT_BOTH);
+ capng_apply(CAPNG_SELECT_BOTH);
+#endif
+ if (mode != M_TEST)
+ syslog(LOG_INFO, "audisp-prelude is ready for events");
+ do {
+ fd_set read_mask;
+ struct timeval tv;
+ int retval;
+
+ /* Load configuration */
+ if (hup) {
+ reload_config();
+ }
+ do {
+ tv.tv_sec = 5;
+ tv.tv_usec = 0;
+ FD_ZERO(&read_mask);
+ FD_SET(0, &read_mask);
+ if (auparse_feed_has_data(au))
+ retval= select(1, &read_mask, NULL, NULL, &tv);
+ else
+ retval= select(1, &read_mask, NULL, NULL, NULL); } while (retval == -1 && errno == EINTR && !hup && !stop);
+
+ /* Now the event loop */
+ if (!stop && !hup && retval > 0) {
+ if (fgets_unlocked(tmp, MAX_AUDIT_MESSAGE_LENGTH,
+ stdin)){
+ auparse_feed(au, tmp, strnlen(tmp,
+ MAX_AUDIT_MESSAGE_LENGTH));
+ }
+ } else if (retval == 0)
+ auparse_flush_feed(au);
+ if (feof(stdin))
+ break;
+ } while (stop == 0);
+
+ /* Flush any accumulated events from queue */
+ auparse_flush_feed(au);
+
+ if (stop) {
+ if (mode == M_TEST)
+ puts("audisp-prelude is exiting on stop request");
+ else
+ syslog(LOG_INFO,
+ "audisp-prelude is exiting on stop request");
+ } else {
+ if (mode == M_TEST)
+ puts("audisp-prelude is exiting due to end of file");
+ else
+ syslog(LOG_INFO,
+ "audisp-prelude is exiting due to losing input source");
+ }
+
+ /* Cleanup subsystems */
+ if (client)
+ prelude_client_destroy(client,
+ PRELUDE_CLIENT_EXIT_STATUS_SUCCESS);
+ prelude_deinit();
+ auparse_destroy(au);
+ free_config(&config);
+ free(myhostname);
+
+ return 0;
+}
+
+static void print_test_message(idmef_message_t *idmef)
+{
+ int ret;
+ prelude_io_t *fd;
+
+ ret = prelude_io_new(&fd);
+ if ( ret < 0 )
+ return;
+
+ prelude_io_set_file_io(fd, stdout);
+ idmef_message_print(idmef, fd);
+
+ prelude_io_destroy(fd);
+}
+
+static void send_idmef(prelude_client_t *client, idmef_message_t *idmef)
+{
+ if (mode == M_TEST)
+ print_test_message(idmef);
+ else
+ prelude_client_send_idmef(client, idmef);
+}
+
+static int new_alert_common(auparse_state_t *au, idmef_message_t **idmef,
+ idmef_alert_t **alert)
+{
+ int ret;
+ idmef_time_t *dtime, *ctime;
+ time_t au_time;
+
+ ret = idmef_message_new(idmef);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_message_new_alert(*idmef, alert);
+ PRELUDE_FAIL_CHECK;
+
+ idmef_alert_set_analyzer(*alert,
+ idmef_analyzer_ref(prelude_client_get_analyzer(client)),
+ IDMEF_LIST_PREPEND);
+
+ // Put the audit time and message ID in the event
+ au_time = auparse_get_time(au);
+ ret = idmef_time_new_from_time(&dtime, &au_time);
+ PRELUDE_FAIL_CHECK;
+ idmef_alert_set_detect_time(*alert, dtime);
+
+ // Set time this was created
+ ret = idmef_time_new_from_gettimeofday(&ctime);
+ PRELUDE_FAIL_CHECK;
+ idmef_alert_set_create_time(*alert, ctime);
+
+ return 0;
+ err:
+ syslog(LOG_ERR, "%s: IDMEF error: %s.\n",
+ prelude_strsource(ret), prelude_strerror(ret));
+ idmef_message_destroy(*idmef);
+ return -1;
+}
+
+static int get_loginuid(auparse_state_t *au)
+{
+ int uid;
+ const char *auid;
+
+ auparse_first_field(au);
+ auid = auparse_find_field(au, "auid");
+ if (auid)
+ uid = auparse_get_field_int(au);
+ else
+ uid = -1;
+ return uid;
+}
+
+static int get_new_gid(auparse_state_t *au)
+{
+ int gid;
+ const char *ngid;
+
+ auparse_first_field(au);
+ ngid = auparse_find_field(au, "new_gid");
+ if (ngid)
+ gid = auparse_get_field_int(au);
+ else
+ gid = -1;
+ return gid;
+}
+
+/*
+ * This function seeks to the specified record returning its type on succees
+ */
+static int goto_record_type(auparse_state_t *au, int type)
+{
+ int cur_type;
+
+ auparse_first_record(au);
+ do {
+ cur_type = auparse_get_type(au);
+ if (cur_type == type) {
+ auparse_first_field(au);
+ return type; // Normal exit
+ }
+ } while (auparse_next_record(au) > 0);
+
+ return -1;
+}
+
+static int get_loginuid_info(auparse_state_t *au, idmef_user_id_t *user_id)
+{
+ int ret, type, is_num = 0;
+ const char *auid;
+
+ type = auparse_get_type(au);
+ auparse_first_field(au);
+ auid = auparse_find_field(au, "acct");
+ if (auid == NULL) {
+ is_num = 1;
+ goto_record_type(au, type);
+ auid = auparse_find_field(au, "sauid");
+ if (auid == NULL) {
+ goto_record_type(au, type);
+ if (type == AUDIT_USER_LOGIN) {
+ // login programs write auid at second uid
+ auparse_find_field(au, "uid");
+ auparse_next_field(au);
+ auid = auparse_find_field(au, "uid");
+ } else {
+ auid = auparse_find_field(au, "auid");
+ }
+ }
+ }
+ if (auid) {
+ prelude_string_t *str;
+ ret = prelude_string_new(&str);
+ PRELUDE_FAIL_CHECK;
+
+ if (is_num) {
+ int uid = auparse_get_field_int(au);
+ idmef_user_id_set_number(user_id, uid);
+ } else {
+ struct passwd *pw;
+ pw = getpwnam(auid);
+ if (pw)
+ idmef_user_id_set_number(user_id, pw->pw_uid);
+ }
+
+ auid = auparse_interpret_field(au);
+ ret = prelude_string_set_ref(str, auid);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_name(user_id, str);
+ }
+ return 0;
+
+ err:
+ return -1;
+}
+
+static int get_tty_info(auparse_state_t *au, idmef_user_id_t *user_id)
+{
+ int ret, type;
+ const char *tty;
+
+ type = auparse_get_type(au);
+ auparse_first_field(au);
+ tty = auparse_find_field(au, "terminal");
+ if (tty == NULL) {
+ goto_record_type(au, type);
+ tty = auparse_find_field(au, "tty");
+ }
+ if (tty) {
+ prelude_string_t *str;
+
+ ret = prelude_string_new(&str);
+ PRELUDE_FAIL_CHECK;
+
+ ret = prelude_string_set_ref(str, tty);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_tty(user_id, str);
+ }
+ return 0;
+ err:
+ return -1;
+}
+
+static int is_ipv4(const char *addr)
+{
+ int i = 0;
+ while (addr[i]) {
+ if ((addr[i] != '.') && !isdigit(addr[i]))
+ return 0;
+ i++;
+ }
+ return 1;
+}
+
+static int is_ipv6(const char *addr)
+{
+ int i = 0;
+ while (addr[i]) {
+ if ((addr[i] != '.') && addr[i] != ':' && !isdigit(addr[i]))
+ return 0;
+ i++;
+ }
+ return 1;
+}
+
+static int fill_in_node(idmef_node_t *node, const char *addr)
+{
+ int ret;
+ prelude_string_t *str;
+
+ /* Setup the address string */
+ ret = prelude_string_new(&str);
+ PRELUDE_FAIL_CHECK;
+ ret = prelude_string_set_ref(str, addr);
+ PRELUDE_FAIL_CHECK;
+
+ /* Now figure out the kind of address */
+ if (is_ipv4(addr)) {
+ idmef_address_t *my_addr;
+ ret = idmef_address_new(&my_addr);
+ PRELUDE_FAIL_CHECK;
+ idmef_address_set_category(my_addr,
+ IDMEF_ADDRESS_CATEGORY_IPV4_ADDR);
+ idmef_address_set_address(my_addr, str);
+ idmef_node_set_address(node, my_addr, 0);
+ } else if (is_ipv6(addr)){
+ idmef_address_t *my_addr;
+ ret = idmef_address_new(&my_addr);
+ PRELUDE_FAIL_CHECK;
+ idmef_address_set_category(my_addr,
+ IDMEF_ADDRESS_CATEGORY_IPV6_ADDR);
+ idmef_address_set_address(my_addr, str);
+ idmef_node_set_address(node, my_addr, 0);
+ } else { /* Just a host name */
+ idmef_node_set_name(node, str);
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+static int get_rhost_info(auparse_state_t *au, idmef_source_t *source)
+{
+ int ret;
+ idmef_node_t *node;
+ const char *hostname;
+
+ auparse_first_field(au);
+ hostname = auparse_find_field(au, "hostname");
+ if (hostname) {
+ if (strcmp(hostname, "?") == 0) {
+ auparse_next_field(au);
+ hostname = auparse_get_field_str(au);
+ }
+ } else { /* Some AVCs have the remote addr */
+ auparse_first_field(au);
+ hostname = auparse_find_field(au, "laddr");
+ }
+
+ if (hostname) {
+ ret = idmef_source_new_node(source, &node);
+ PRELUDE_FAIL_CHECK;
+ idmef_node_set_category(node, IDMEF_NODE_CATEGORY_UNKNOWN);
+
+ ret = fill_in_node(node, hostname);
+ PRELUDE_FAIL_CHECK;
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+static int do_node_common(auparse_state_t *au, idmef_node_t *node)
+{
+ int ret;
+ const char *name;
+
+ auparse_first_field(au);
+ name = auparse_find_field(au, "node");
+ if (name == NULL) {
+ if (myhostname == NULL) {
+ char tmp_name[255];
+ if (gethostname(tmp_name, sizeof(tmp_name)) == 0)
+ myhostname = strdup(tmp_name);
+ }
+ name = myhostname;
+ idmef_node_set_category(node, IDMEF_NODE_CATEGORY_HOSTS);
+ } else
+ idmef_node_set_category(node, IDMEF_NODE_CATEGORY_UNKNOWN);
+
+ if (name) {
+ ret = fill_in_node(node, name);
+ PRELUDE_FAIL_CHECK;
+ } else
+ goto err;
+
+ return 0;
+ err:
+ return -1;
+}
+
+static int get_node_info(auparse_state_t *au, idmef_source_t *source,
+ idmef_target_t *target)
+{
+ int ret;
+ idmef_node_t *node;
+
+ if (source) {
+ ret = idmef_source_new_node(source, &node);
+ PRELUDE_FAIL_CHECK;
+
+ ret = do_node_common(au, node);
+ PRELUDE_FAIL_CHECK;
+ }
+
+ if (target) {
+ ret = idmef_target_new_node(target, &node);
+ PRELUDE_FAIL_CHECK;
+
+ ret = do_node_common(au, node);
+ PRELUDE_FAIL_CHECK;
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+static int get_login_exe_info(auparse_state_t *au, idmef_target_t *target)
+{
+ int ret, type;
+ idmef_process_t *process;
+ const char *exe, *pid;
+
+ ret = idmef_target_new_process(target, &process);
+ PRELUDE_FAIL_CHECK;
+
+ type = auparse_get_type(au);
+ auparse_first_field(au);
+ pid = auparse_find_field(au, "pid");
+ if (pid)
+ idmef_process_set_pid(process, auparse_get_field_int(au));
+
+ goto_record_type(au, type);
+ exe = auparse_find_field(au, "exe");
+ if (exe) {
+ char *base;
+ prelude_string_t *str, *name_str;
+ ret = prelude_string_new(&str);
+ PRELUDE_FAIL_CHECK;
+
+ exe = auparse_interpret_field(au);
+ ret = prelude_string_set_ref(str, exe);
+ PRELUDE_FAIL_CHECK;
+ idmef_process_set_path(process, str);
+
+ /* Set process name, login events do not have comm fields */
+ base = basename(exe);
+ ret = prelude_string_new(&name_str);
+ PRELUDE_FAIL_CHECK;
+ ret = prelude_string_set_dup(name_str, base);
+ PRELUDE_FAIL_CHECK;
+ idmef_process_set_name(process, name_str);
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+static int get_target_group_info(auparse_state_t *au, idmef_user_t *tuser)
+{
+ int ret;
+ const char *ngid;
+
+ auparse_first_field(au);
+ ngid = auparse_find_field(au, "new_gid");
+ if (ngid) {
+ int gid;
+ idmef_user_id_t *user_id;
+ prelude_string_t *str;
+
+ ret = idmef_user_new_user_id(tuser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id, IDMEF_USER_ID_TYPE_GROUP_PRIVS);
+
+ ret = prelude_string_new(&str);
+ PRELUDE_FAIL_CHECK;
+
+ gid = auparse_get_field_int(au);
+ if (gid >= 0)
+ idmef_user_id_set_number(user_id, gid);
+
+ ngid = auparse_interpret_field(au);
+ ret = prelude_string_set_ref(str, ngid);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_name(user_id, str);
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+static int get_comm_info(auparse_state_t *au, idmef_source_t *source,
+ idmef_target_t *target)
+{
+ int ret, type, need_comm = 1;
+ idmef_process_t *process;
+ const char *exe, *pid;
+
+ if (source)
+ ret = idmef_source_new_process(source, &process);
+ else if (target)
+ ret = idmef_target_new_process(target, &process);
+ else
+ return -1;
+ PRELUDE_FAIL_CHECK;
+
+ type = auparse_get_type(au);
+ auparse_first_field(au);
+ pid = auparse_find_field(au, "pid");
+ if (pid)
+ idmef_process_set_pid(process, auparse_get_field_int(au));
+
+ goto_record_type(au, type);
+ auparse_first_field(au);
+ exe = auparse_find_field(au, "comm");
+ if (exe) {
+ prelude_string_t *str;
+ ret = prelude_string_new(&str);
+ PRELUDE_FAIL_CHECK;
+
+ exe = auparse_interpret_field(au);
+ ret = prelude_string_set_ref(str, exe);
+ PRELUDE_FAIL_CHECK;
+ idmef_process_set_name(process, str);
+ need_comm = 0;
+ }
+
+ goto_record_type(au, type);
+ exe = auparse_find_field(au, "exe");
+ if (exe) {
+ prelude_string_t *str;
+ ret = prelude_string_new(&str);
+ PRELUDE_FAIL_CHECK;
+
+ exe = auparse_interpret_field(au);
+ ret = prelude_string_set_ref(str, exe);
+ PRELUDE_FAIL_CHECK;
+ idmef_process_set_path(process, str);
+
+ /* Set the process name if not set already */
+ if (need_comm) {
+ prelude_string_t *name_str;
+
+ char *base = basename(exe);
+ ret = prelude_string_new(&name_str);
+ PRELUDE_FAIL_CHECK;
+ ret = prelude_string_set_dup(name_str, base);
+ idmef_process_set_name(process, name_str);
+ }
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+/*
+ * Fill in a file record for idmef. Note that we always get the
+ * full path name unless we have an AVC.
+ */
+static int get_file_info(auparse_state_t *au, idmef_target_t *target, int full)
+{
+ int ret;
+ idmef_file_t *file;
+ const char *name;
+ char path[PATH_MAX+1];
+
+ ret = idmef_target_new_file(target, &file, 0);
+ PRELUDE_FAIL_CHECK;
+
+ *path = 0;
+ if (full) {
+ const char *cwd;
+ auparse_first_field(au);
+ cwd = auparse_find_field(au, "cwd");
+ if (cwd) {
+ if ((cwd = auparse_interpret_field(au)))
+ strcat(path, cwd);
+ }
+ // Loop across all PATH records in the event
+ goto_record_type(au, AUDIT_PATH);
+ name = NULL;
+ do { // Make sure that we have an actual file record
+ if (auparse_find_field(au, "mode")) {
+ int m = auparse_get_field_int(au);
+ if (S_ISREG(m)) {
+ // Now back up and get file name
+ auparse_first_field(au);
+ name = auparse_find_field(au, "name");
+ break;
+ }
+ }
+ } while (auparse_next_record(au) > 0 &&
+ auparse_get_type(au) == AUDIT_PATH);
+ } else {
+ // SE Linux AVC
+ int type = auparse_get_type(au);
+ auparse_first_field(au);
+ name = auparse_find_field(au, "path");
+ if (name == NULL) {
+ goto_record_type(au, type);
+ name = auparse_find_field(au, "name");
+ }
+ }
+ if (name)
+ name = auparse_interpret_field(au);
+ if (name) {
+ if (name[0] == '/')
+ strcpy(path, name);
+ else
+ strcat(path, name);
+ }
+ if (path[0] != 0) {
+ prelude_string_t *str;
+ ret = prelude_string_new(&str);
+ PRELUDE_FAIL_CHECK;
+
+ ret = prelude_string_set_dup(str, path);
+ PRELUDE_FAIL_CHECK;
+ if (path[0] == '/') {
+ char *base;
+ prelude_string_t *name_str;
+
+ idmef_file_set_path(file, str);
+ base = basename(path);
+ if (base[0] == 0)
+ base = "/";
+ ret = prelude_string_new(&name_str);
+ PRELUDE_FAIL_CHECK;
+ ret = prelude_string_set_dup(name_str, base);
+ PRELUDE_FAIL_CHECK;
+ idmef_file_set_name(file, name_str);
+ } else
+ idmef_file_set_name(file, str);
+ }
+ idmef_file_set_category(file, IDMEF_FILE_CATEGORY_CURRENT);
+
+ return 0;
+ err:
+ return -1;
+}
+
+static int add_additional_data(idmef_alert_t *alert, const char *title,
+ const char *text)
+{
+ int ret;
+ idmef_additional_data_t *data;
+ prelude_string_t *str;
+
+ ret = idmef_alert_new_additional_data(alert, &data, IDMEF_LIST_APPEND);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_additional_data_new_meaning(data, &str);
+ PRELUDE_FAIL_CHECK;
+
+ prelude_string_set_dup(str, title);
+ idmef_additional_data_set_type(data, IDMEF_ADDITIONAL_DATA_TYPE_STRING);
+ idmef_additional_data_set_string_ref(data, text);
+ return 0;
+ err:
+ return -1;
+}
+
+static int add_serial_number_data(auparse_state_t *au, idmef_alert_t *alert)
+{
+ int ret;
+ idmef_additional_data_t *data;
+ prelude_string_t *str;
+ unsigned long serial;
+ char eid[24];
+
+ serial = auparse_get_serial(au);
+ snprintf(eid, sizeof(eid), "%lu", serial);
+
+ ret = idmef_alert_new_additional_data(alert, &data, IDMEF_LIST_APPEND);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_additional_data_new_meaning(data, &str);
+ PRELUDE_FAIL_CHECK;
+
+ prelude_string_set_dup(str, "Audit event serial #");
+ idmef_additional_data_set_type(data, IDMEF_ADDITIONAL_DATA_TYPE_STRING);
+ idmef_additional_data_set_string_dup(data, eid);
+ return 0;
+ err:
+ return -1;
+}
+
+static int add_exit_data(auparse_state_t *au, idmef_alert_t *alert)
+{
+ const char *e_ptr;
+
+ if (goto_record_type(au, AUDIT_SYSCALL) == -1)
+ goto err;
+ e_ptr = auparse_find_field(au, "exit");
+ if (e_ptr) {
+ int ret;
+ idmef_additional_data_t *data;
+ prelude_string_t *str;
+ char exit_code[80];
+
+ snprintf(exit_code, sizeof(exit_code), "%d (%s)",
+ auparse_get_field_int(au),
+ auparse_interpret_field(au));
+
+ ret = idmef_alert_new_additional_data(alert,
+ &data, IDMEF_LIST_APPEND);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_additional_data_new_meaning(data, &str);
+ PRELUDE_FAIL_CHECK;
+
+ prelude_string_set_dup(str, "Audit syscall exit code:");
+ idmef_additional_data_set_type(data,
+ IDMEF_ADDITIONAL_DATA_TYPE_STRING);
+ idmef_additional_data_set_string_dup(data, exit_code);
+ }
+ return 0;
+ err:
+ return -1;
+}
+
+static int add_execve_data(auparse_state_t *au, idmef_alert_t *alert)
+{
+ int ret, i, len = 0;
+ idmef_additional_data_t *data;
+ prelude_string_t *str;
+ const char *msgptr;
+ char msg[256], var[16];
+
+ if (goto_record_type(au, AUDIT_EXECVE) != AUDIT_EXECVE)
+ return 0;
+
+ msg[0] = 0;
+ for (i=0; i<8; i++) {
+ snprintf(var, sizeof(var), "a%d", i);
+ msgptr = auparse_find_field(au, var);
+ if (msgptr) {
+ char *ptr;
+ int len2;
+ len2 = asprintf(&ptr, "%s=%s ", var,
+ auparse_interpret_field(au));
+ if (len2 < 0) {
+ ptr = NULL;
+ } else if (len2 > 0 && (len2 + len + 1) < sizeof(msg)) {
+ strcat(msg, ptr);
+ len += len2;
+ }
+ free(ptr);
+ } else
+ break;
+ }
+
+ ret = idmef_alert_new_additional_data(alert, &data, IDMEF_LIST_APPEND);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_additional_data_new_meaning(data, &str);
+ PRELUDE_FAIL_CHECK;
+
+ prelude_string_set_dup(str, "Execve args");
+ idmef_additional_data_set_type(data, IDMEF_ADDITIONAL_DATA_TYPE_STRING);
+ idmef_additional_data_set_string_dup(data, msg);
+ return 0;
+ err:
+ return -1;
+}
+
+static int set_classification(idmef_alert_t *alert, const char *text)
+{
+ int ret;
+ idmef_classification_t *classification;
+ prelude_string_t *str;
+
+ ret = idmef_alert_new_classification(alert, &classification);
+ PRELUDE_FAIL_CHECK;
+ ret = prelude_string_new(&str);
+ PRELUDE_FAIL_CHECK;
+ ret = prelude_string_set_ref(str, text);
+ PRELUDE_FAIL_CHECK;
+ idmef_classification_set_text(classification, str);
+
+ return 0;
+ err:
+ return -1;
+}
+
+static int do_assessment(idmef_alert_t *alert, auparse_state_t *au,
+ idmef_impact_severity_t severity, idmef_impact_type_t type,
+ const char *descr)
+{
+ int ret;
+ idmef_assessment_t *assessment;
+ idmef_impact_t *impact;
+ idmef_impact_completion_t completion = IDMEF_IMPACT_COMPLETION_ERROR;
+ const char *result;
+
+ auparse_first_record(au);
+ result = auparse_find_field(au, "res");
+ if (result == NULL) {
+ auparse_first_record(au);
+ result = auparse_find_field(au, "success");
+ }
+ if (result) {
+ if (strcmp(result, "yes") == 0)
+ completion = IDMEF_IMPACT_COMPLETION_SUCCEEDED;
+ else if (strcmp(result, "success") == 0)
+ completion = IDMEF_IMPACT_COMPLETION_SUCCEEDED;
+ else
+ completion = IDMEF_IMPACT_COMPLETION_FAILED;
+ }
+
+ // Adjust the rating on AVC's based on if they succeeded or not
+ if (goto_record_type(au, AUDIT_AVC) == AUDIT_AVC) {
+ if (completion == IDMEF_IMPACT_COMPLETION_FAILED)
+ severity = IDMEF_IMPACT_SEVERITY_LOW;
+ } else if (goto_record_type(au, AUDIT_USER_AVC) == AUDIT_USER_AVC) {
+ if (completion == IDMEF_IMPACT_COMPLETION_FAILED)
+ severity = IDMEF_IMPACT_SEVERITY_LOW;
+ }
+ // If this is a segfault, they failed
+ if (goto_record_type(au, AUDIT_ANOM_ABEND) == AUDIT_ANOM_ABEND)
+ completion = IDMEF_IMPACT_COMPLETION_FAILED;
+
+ ret = idmef_alert_new_assessment(alert, &assessment);
+ PRELUDE_FAIL_CHECK;
+ ret = idmef_assessment_new_impact(assessment, &impact);
+ PRELUDE_FAIL_CHECK;
+ idmef_impact_set_severity(impact, severity);
+ idmef_impact_set_type(impact, type);
+ if (descr) {
+ prelude_string_t *str;
+ ret = idmef_impact_new_description(impact, &str);
+ PRELUDE_FAIL_CHECK;
+ ret = prelude_string_set_ref(str, descr);
+ PRELUDE_FAIL_CHECK;
+ }
+
+ // FIXME: I think this is wrong. sb a way to express indeterminate
+ if (completion != IDMEF_IMPACT_COMPLETION_ERROR)
+ idmef_impact_set_completion(impact, completion);
+
+ return 0;
+ err:
+ return -1;
+}
+
+/*
+ * This is for login related alerts
+ */
+static int login_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert, const char *msg,
+ idmef_impact_severity_t severity, as_description_t num)
+{
+ int ret;
+ idmef_source_t *source;
+ idmef_target_t *target;
+ idmef_user_t *suser, *tuser;
+ idmef_user_id_t *user_id;
+ idmef_impact_type_t impact;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_UNKNOWN);
+
+ ret = get_rhost_info(au, source);
+ PRELUDE_FAIL_CHECK;
+
+ /* Fill in information about the target of the event */
+ ret = idmef_alert_new_target(alert, &target, -1);
+ PRELUDE_FAIL_CHECK;
+ ret = idmef_target_new_user(target, &tuser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(tuser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(tuser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id, IDMEF_USER_ID_TYPE_TARGET_USER);
+
+ auparse_first_record(au);
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_tty_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_login_exe_info(au, target);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_node_info(au, NULL, target);
+ PRELUDE_FAIL_CHECK;
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Describe event */
+ ret = set_classification(alert, msg);
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ if (get_loginuid(au) == 0)
+ impact = IDMEF_IMPACT_TYPE_ADMIN;
+ else
+ impact = IDMEF_IMPACT_TYPE_USER;
+ ret = do_assessment(alert, au, severity, impact,
+ assessment_description[num]);
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "login_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+
+/*
+ * This is for SE Linux AVC related alerts
+ */
+static int avc_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert)
+{
+ int ret, type;
+ idmef_source_t *source;
+ idmef_target_t *target;
+ idmef_user_t *suser;
+ idmef_user_id_t *user_id;
+ idmef_impact_type_t impact_type = IDMEF_IMPACT_TYPE_OTHER;
+ const char *seperm = NULL;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+
+ if ((type = goto_record_type(au, AUDIT_SYSCALL)) == AUDIT_SYSCALL ||
+ (type = goto_record_type(au, AUDIT_USER_AVC)) == AUDIT_USER_AVC) {
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(suser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id,
+ IDMEF_USER_ID_TYPE_ORIGINAL_USER);
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_tty_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_comm_info(au, source, NULL);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_rhost_info(au, source);
+ PRELUDE_FAIL_CHECK;
+ } else if ((type = goto_record_type(au, AUDIT_AVC)) == AUDIT_AVC) {
+ ret = get_comm_info(au, source, NULL);
+ PRELUDE_FAIL_CHECK;
+ }
+
+ /* Fill in information about the target of the event */
+ ret = idmef_alert_new_target(alert, &target, -1);
+ PRELUDE_FAIL_CHECK;
+
+ auparse_first_record(au);
+ ret = get_node_info(au, source, target);
+ PRELUDE_FAIL_CHECK;
+
+ type = goto_record_type(au, AUDIT_CWD);
+ if (type == AUDIT_CWD) {
+ ret = get_file_info(au, target, 1);
+ PRELUDE_FAIL_CHECK;
+ impact_type = IDMEF_IMPACT_TYPE_FILE;
+ } else if ((type = goto_record_type(au, AUDIT_AVC)) == AUDIT_AVC) {
+ seperm = auparse_find_field(au, "seperm");
+ if (auparse_find_field(au, "path")) {
+ ret = get_file_info(au, target, 0);
+ impact_type = IDMEF_IMPACT_TYPE_FILE;
+ } else {
+ goto_record_type(au, AUDIT_AVC);
+ if (auparse_find_field(au, "name")) {
+ ret = get_file_info(au, target, 0);
+ impact_type = IDMEF_IMPACT_TYPE_FILE;
+ }
+ }
+ }
+
+ /* Add AVC info for reference */
+ if ((goto_record_type(au, AUDIT_AVC) == AUDIT_AVC) ||
+ (goto_record_type(au, AUDIT_USER_AVC) == AUDIT_USER_AVC)) {
+ ret = add_additional_data(alert, "AVC Text",
+ auparse_get_record_text(au));
+ PRELUDE_FAIL_CHECK;
+ }
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Detect mmap 0 here */
+ type = AS_MAC;
+ if (seperm && strcmp(seperm, "mmap_zero") == 0) {
+ const char *tclass = auparse_find_field(au, "tclass");
+ if (tclass && strcmp(tclass, "memprotect"))
+ type = AS_MMAP0;
+ }
+
+ /* Describe event */
+ if (type == AS_MAC) {
+ ret = set_classification(alert, "MAC Violation");
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ ret = do_assessment(alert, au, IDMEF_IMPACT_SEVERITY_MEDIUM,
+ impact_type, assessment_description[AS_MAC]);
+ } else {
+ ret = set_classification(alert, "MMAP Page 0");
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ ret = do_assessment(alert, au, IDMEF_IMPACT_SEVERITY_HIGH,
+ impact_type, assessment_description[AS_MMAP0]);
+ }
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "avc_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+
+/*
+ * This is for Application Abnormal Termination related alerts
+ */
+static int app_term_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert)
+{
+ int ret;
+ idmef_source_t *source;
+ idmef_target_t *target;
+ idmef_user_t *suser, *tuser;
+ idmef_user_id_t *user_id;
+ const char *sig;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_APPLICATION);
+
+ /* Fill in information about the target of the event */
+ ret = idmef_alert_new_target(alert, &target, -1);
+ PRELUDE_FAIL_CHECK;
+ ret = idmef_target_new_user(target, &tuser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(tuser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(tuser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id, IDMEF_USER_ID_TYPE_ORIGINAL_USER);
+
+ auparse_first_record(au);
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_comm_info(au, NULL, target);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_node_info(au, source, target);
+ PRELUDE_FAIL_CHECK;
+
+ auparse_first_record(au);
+ sig = auparse_find_field(au, "sig");
+ if (sig) {
+ sig = auparse_interpret_field(au);
+ ret = add_additional_data(alert, "Signal", sig);
+ PRELUDE_FAIL_CHECK;
+ }
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Describe event */
+ ret = set_classification(alert, "App Abnormal Termination");
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ ret = do_assessment(alert, au, IDMEF_IMPACT_SEVERITY_MEDIUM,
+ IDMEF_IMPACT_TYPE_OTHER,
+ assessment_description[AS_ABEND]);
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "term_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+
+/*
+ * This is to alert that something has opened a promiscuous socket
+ */
+static int promiscuous_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert)
+{
+ int ret, type, old_prom=-1, new_prom=-1;
+ idmef_source_t *source;
+ idmef_target_t *target;
+ idmef_user_t *suser, *tuser;
+ idmef_user_id_t *user_id;
+ const char *dev;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+
+ type = goto_record_type(au, AUDIT_SYSCALL);
+ if (type == AUDIT_SYSCALL) {
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(suser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id,
+ IDMEF_USER_ID_TYPE_ORIGINAL_USER);
+
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_tty_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_comm_info(au, source, NULL);
+ PRELUDE_FAIL_CHECK;
+ }
+ dev = auparse_find_field(au, "dev");
+ if (dev) {
+ ret = add_additional_data(alert, "Device", dev);
+ PRELUDE_FAIL_CHECK;
+ }
+
+ /* Fill in information about the target of the event */
+ ret = idmef_alert_new_target(alert, &target, -1);
+ PRELUDE_FAIL_CHECK;
+ ret = idmef_target_new_user(target, &tuser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(tuser, IDMEF_USER_CATEGORY_OS_DEVICE);
+
+ ret = get_node_info(au, source, target);
+ PRELUDE_FAIL_CHECK;
+
+ type = goto_record_type(au, AUDIT_ANOM_PROMISCUOUS);
+ if (type == AUDIT_ANOM_PROMISCUOUS) {
+ const char *old_val, *new_val;
+
+ auparse_first_field(au);
+ new_val = auparse_find_field(au, "prom");
+ if (new_val)
+ new_prom = auparse_get_field_int(au);
+ old_val = auparse_find_field(au, "old_prom");
+ if (old_val)
+ old_prom = auparse_get_field_int(au);
+ }
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Describe event */
+ if (new_prom == 256 && old_prom == 0)
+ ret = set_classification(alert, "Promiscuous Socket Opened");
+ else if (new_prom == 0 && old_prom == 256)
+ ret = set_classification(alert, "Promiscuous Socket Closed");
+ else
+ ret = set_classification(alert, "Promiscuous Socket Changed");
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ ret = do_assessment(alert, au, IDMEF_IMPACT_SEVERITY_INFO,
+ IDMEF_IMPACT_TYPE_RECON,
+ assessment_description[AS_PROM]);
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "promiscuous_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+
+/*
+ * This is to alert that something has changed the selinux enforcement
+ */
+static int mac_status_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert)
+{
+ int ret, type, old_enforce=-1, new_enforce=-1;
+ idmef_source_t *source;
+ idmef_target_t *target;
+ idmef_user_t *suser, *tuser;
+ idmef_user_id_t *user_id;
+ idmef_impact_severity_t severity;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(suser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id, IDMEF_USER_ID_TYPE_ORIGINAL_USER);
+
+ type = goto_record_type(au, AUDIT_SYSCALL);
+ if (type == AUDIT_SYSCALL) {
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_tty_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_comm_info(au, source, NULL);
+ PRELUDE_FAIL_CHECK;
+ }
+
+ /* Fill in information about the target of the event */
+ ret = idmef_alert_new_target(alert, &target, -1);
+ PRELUDE_FAIL_CHECK;
+ ret = idmef_target_new_user(target, &tuser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(tuser, IDMEF_USER_CATEGORY_OS_DEVICE);
+
+ ret = get_node_info(au, source, target);
+ PRELUDE_FAIL_CHECK;
+
+ type = goto_record_type(au, AUDIT_MAC_STATUS);
+ if (type == AUDIT_MAC_STATUS) {
+ const char *old_val, *new_val;
+
+ auparse_first_field(au);
+ new_val = auparse_find_field(au, "enforcing");
+ if (new_val)
+ new_enforce = auparse_get_field_int(au);
+ old_val = auparse_find_field(au, "old_enforcing");
+ if (old_val)
+ old_enforce = auparse_get_field_int(au);
+ }
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Describe event */
+ if (new_enforce == 1 && old_enforce == 0) {
+ ret = set_classification(alert, "SE Linux Enforcement Enabled");
+ severity = IDMEF_IMPACT_SEVERITY_LOW;
+ } else if (new_enforce == 0 && old_enforce == 1) {
+ ret = set_classification(alert,"SE Linux Enforcement Disabled");
+ severity = IDMEF_IMPACT_SEVERITY_HIGH;
+ } else {
+ ret = set_classification(alert, "SE Linux Enforcement Changed");
+ severity = IDMEF_IMPACT_SEVERITY_LOW;
+ }
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ ret = do_assessment(alert, au, severity, IDMEF_IMPACT_TYPE_OTHER,
+ assessment_description[AS_MAC_STAT]);
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "mac_status_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+
+/*
+ * This is for authentication failure alerts
+ */
+static int auth_failure_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert, const char *msg,
+ idmef_impact_severity_t severity, as_description_t num)
+{
+ int ret, gid;
+ idmef_source_t *source;
+ idmef_target_t *target;
+ idmef_user_t *suser, *tuser;
+ idmef_user_id_t *user_id;
+ idmef_impact_type_t impact;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(suser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id, IDMEF_USER_ID_TYPE_ORIGINAL_USER);
+
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_tty_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_comm_info(au, source, NULL);
+ PRELUDE_FAIL_CHECK;
+
+ /* Fill in information about the target of the event */
+ ret = idmef_alert_new_target(alert, &target, -1);
+ PRELUDE_FAIL_CHECK;
+ ret = idmef_target_new_user(target, &tuser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(tuser, IDMEF_USER_CATEGORY_APPLICATION);
+
+ ret = get_target_group_info(au, tuser);
+ PRELUDE_FAIL_CHECK;
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Describe event */
+ ret = set_classification(alert, msg);
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ gid = get_new_gid(au);
+ if (gid == 0 || gid == 10) { // Root or wheel
+ impact = IDMEF_IMPACT_TYPE_ADMIN;
+ severity = IDMEF_IMPACT_SEVERITY_MEDIUM;
+ } else
+ impact = IDMEF_IMPACT_TYPE_USER;
+ ret = do_assessment(alert, au, severity, impact,
+ assessment_description[num]);
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "auth_failure_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+
+/*
+ * This is for watched syscall related alerts
+ */
+static int watched_syscall_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert, idmef_impact_severity_t severity)
+{
+ int ret, rtype;
+ idmef_source_t *source;
+ idmef_target_t *target;
+ idmef_user_t *suser;
+ idmef_user_id_t *user_id;
+ idmef_impact_type_t impact;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(suser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id, IDMEF_USER_ID_TYPE_ORIGINAL_USER);
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ /* We should only analyze the syscall */
+ rtype = goto_record_type(au, AUDIT_SYSCALL);
+ ret = get_tty_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_comm_info(au, source, NULL);
+ PRELUDE_FAIL_CHECK;
+
+ /* Fill in information about the target of the event */
+ ret = idmef_alert_new_target(alert, &target, -1);
+ PRELUDE_FAIL_CHECK;
+
+ auparse_first_record(au);
+ ret = get_node_info(au, source, target);
+ PRELUDE_FAIL_CHECK;
+
+ rtype = goto_record_type(au, AUDIT_CWD);
+ if (rtype == AUDIT_CWD) {
+ ret = get_file_info(au, target, 1);
+ PRELUDE_FAIL_CHECK;
+ }
+ impact = IDMEF_IMPACT_TYPE_OTHER;
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+ ret = add_exit_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Describe event */
+ ret = set_classification(alert, "Watched Syscall");
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ ret = do_assessment(alert, au, severity, impact,
+ assessment_description[AS_WATCHED_SYSCALL]);
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "watches_syscall_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+
+/*
+ * This is for watched file related alerts
+ */
+static int watched_file_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert, idmef_impact_severity_t severity)
+{
+ int ret, rtype;
+ idmef_source_t *source;
+ idmef_target_t *target;
+ idmef_user_t *suser;
+ idmef_user_id_t *user_id;
+ idmef_impact_type_t impact;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(suser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id, IDMEF_USER_ID_TYPE_ORIGINAL_USER);
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_tty_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_comm_info(au, source, NULL);
+ PRELUDE_FAIL_CHECK;
+
+ /* Fill in information about the target of the event */
+ ret = idmef_alert_new_target(alert, &target, -1);
+ PRELUDE_FAIL_CHECK;
+
+ auparse_first_record(au);
+ ret = get_node_info(au, source, target);
+ PRELUDE_FAIL_CHECK;
+
+ rtype = goto_record_type(au, AUDIT_CWD);
+ if (rtype == AUDIT_CWD) {
+ ret = get_file_info(au, target, 1);
+ PRELUDE_FAIL_CHECK;
+ }
+ impact = IDMEF_IMPACT_TYPE_FILE;
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Describe event */
+ ret = set_classification(alert, "Watched File");
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ ret = do_assessment(alert, au, severity, impact,
+ assessment_description[AS_WATCHED_FILE]);
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "watches_file_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+
+/*
+ * This is for watched executable related alerts
+ */
+static int watched_exec_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert, idmef_impact_severity_t severity)
+{
+ int ret, rtype;
+ idmef_source_t *source;
+ idmef_target_t *target;
+ idmef_user_t *suser;
+ idmef_user_id_t *user_id;
+ idmef_impact_type_t impact;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(suser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id, IDMEF_USER_ID_TYPE_ORIGINAL_USER);
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_tty_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_comm_info(au, source, NULL);
+ PRELUDE_FAIL_CHECK;
+
+ /* Fill in information about the target of the event */
+ ret = idmef_alert_new_target(alert, &target, -1);
+ PRELUDE_FAIL_CHECK;
+
+ auparse_first_record(au);
+ ret = get_node_info(au, source, target);
+ PRELUDE_FAIL_CHECK;
+
+ rtype = goto_record_type(au, AUDIT_CWD);
+ if (rtype == AUDIT_CWD) {
+ ret = get_file_info(au, target, 1);
+ PRELUDE_FAIL_CHECK;
+ }
+
+ ret = add_execve_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Describe event */
+ ret = set_classification(alert, "Watched Executable");
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ if (get_loginuid(au) == 0)
+ impact = IDMEF_IMPACT_TYPE_ADMIN;
+ else
+ impact = IDMEF_IMPACT_TYPE_USER;
+ ret = do_assessment(alert, au, severity, impact,
+ assessment_description[AS_WATCHED_EXEC]);
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "watched_exec_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+
+/*
+ * This is for watching exe's being made related alerts
+ */
+static int watched_mk_exe_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert, idmef_impact_severity_t severity)
+{
+ int ret, rtype;
+ idmef_source_t *source;
+ idmef_target_t *target;
+ idmef_user_t *suser;
+ idmef_user_id_t *user_id;
+ idmef_impact_type_t impact;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(suser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id, IDMEF_USER_ID_TYPE_ORIGINAL_USER);
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_tty_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_comm_info(au, source, NULL);
+ PRELUDE_FAIL_CHECK;
+
+ /* Fill in information about the target of the event */
+ ret = idmef_alert_new_target(alert, &target, -1);
+ PRELUDE_FAIL_CHECK;
+
+ auparse_first_record(au);
+ ret = get_node_info(au, source, target);
+ PRELUDE_FAIL_CHECK;
+
+ rtype = goto_record_type(au, AUDIT_CWD);
+ if (rtype == AUDIT_CWD) {
+ ret = get_file_info(au, target, 1);
+ PRELUDE_FAIL_CHECK;
+ }
+ impact = IDMEF_IMPACT_TYPE_FILE;
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Describe event */
+ ret = set_classification(alert, "Executable Created");
+ PRELUDE_FAIL_CHECK;
+
+ ret = do_assessment(alert, au, severity, impact,
+ assessment_description[AS_MK_EXE]);
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "watched_mk_exe_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+
+static int account_is_watched(auparse_state_t *au)
+{
+ const char *auid;
+
+ auparse_first_field(au);
+ auid = auparse_find_field(au, "auid");
+ if (auid) { // This is for successful logins
+ int uid = auparse_get_field_int(au);
+ if (ilist_find_num(&config.watched_accounts, uid))
+ return 1;
+ } else { // Now try failed logins to see if we know who they are
+ auparse_first_field(au);
+ if ((auid = auparse_find_field(au, "acct"))) {
+ struct passwd *pw = getpwnam(auid);
+ if (pw && ilist_find_num(
+ &config.watched_accounts, pw->pw_uid))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static idmef_impact_type_t lookup_itype(const char *kind)
+{
+ if (strcasecmp(kind, "sys") == 0)
+ return IDMEF_IMPACT_TYPE_OTHER;
+ if (strcasecmp(kind, "file") == 0)
+ return IDMEF_IMPACT_TYPE_FILE;
+ if (strcasecmp(kind, "exec") == 0)
+ return IDMEF_IMPACT_TYPE_USER;
+ if (strcasecmp(kind, "mkexe") == 0)
+ return IDMEF_IMPACT_TYPE_OTHER;
+ return IDMEF_IMPACT_TYPE_ERROR;
+}
+
+static idmef_impact_severity_t lookup_iseverity(const char *severity)
+{
+ if (strncmp(severity, "inf", 3) == 0)
+ return IDMEF_IMPACT_SEVERITY_INFO;
+ if (strncmp(severity, "low", 3) == 0)
+ return IDMEF_IMPACT_SEVERITY_LOW;
+ if (strncmp(severity, "med", 3) == 0)
+ return IDMEF_IMPACT_SEVERITY_MEDIUM;
+ if (strncmp(severity, "hi", 2) == 0)
+ return IDMEF_IMPACT_SEVERITY_HIGH;
+ return IDMEF_IMPACT_SEVERITY_ERROR;
+}
+
+static void handle_watched_syscalls(auparse_state_t *au,
+ idmef_message_t **idmef, idmef_alert_t **alert)
+{
+ if (config.watched_syscall == E_YES || config.watched_file == E_YES ||
+ config.watched_exec == E_YES ||
+ config.watched_mk_exe == E_YES) {
+ const char *keyptr;
+ char *ptr, *kindptr, *ratingptr;
+ char key[AUDIT_MAX_KEY_LEN+1];
+ idmef_impact_type_t type;
+ idmef_impact_severity_t severity;
+
+ /* If no key or key is not for the ids, return */
+ auparse_first_field(au);
+ keyptr = auparse_find_field(au, "key");
+ if (keyptr)
+ keyptr = auparse_interpret_field(au);
+ while (keyptr) {
+ if (strncmp(keyptr, "ids-", 4) == 0)
+ break;
+ keyptr = auparse_find_field_next(au);
+ if (keyptr)
+ keyptr = auparse_interpret_field(au);
+ }
+ if (keyptr == NULL)
+ return;
+
+ /* This key is for us, parse it up */
+ strncpy(key, keyptr, AUDIT_MAX_KEY_LEN);
+ key[AUDIT_MAX_KEY_LEN] = 0;
+
+ ptr = strchr(key, '-'); // There has to be a - because strncmp
+ kindptr = ptr + 1;
+ ptr = strchr(kindptr, '-');
+ if (ptr) {
+ *ptr = 0;
+ ratingptr = ptr +1;
+ } else // The rules are misconfigured
+ return;
+
+ type = lookup_itype(kindptr);
+ severity = lookup_iseverity(ratingptr);
+
+ if (type == IDMEF_IMPACT_TYPE_OTHER &&
+ strcasecmp(kindptr, "sys") == 0 &&
+ config.watched_syscall == E_YES &&
+ config.watched_syscall_act == A_IDMEF) {
+ if (new_alert_common(au, idmef, alert) >= 0)
+ watched_syscall_alert(au, *idmef, *alert,
+ severity);
+ } else if (type == IDMEF_IMPACT_TYPE_FILE &&
+ config.watched_file == E_YES &&
+ config.watched_file_act == A_IDMEF) {
+ if (new_alert_common(au, idmef, alert) >= 0)
+ watched_file_alert(au, *idmef, *alert,
+ severity);
+ } else if (type == IDMEF_IMPACT_TYPE_USER &&
+ config.watched_exec == E_YES &&
+ config.watched_exec_act == A_IDMEF) {
+ if (new_alert_common(au, idmef, alert) >= 0)
+ watched_exec_alert(au, *idmef, *alert,
+ severity);
+ } else if (type == IDMEF_IMPACT_TYPE_OTHER &&
+ strcasecmp(kindptr, "mkexe") == 0 &&
+ config.watched_mk_exe == E_YES &&
+ config.watched_mk_exe_act == A_IDMEF) {
+ if (new_alert_common(au, idmef, alert) >= 0)
+ watched_mk_exe_alert(au, *idmef, *alert,
+ severity);
+ }
+ }
+}
+
+static int tty_alert(auparse_state_t *au, idmef_message_t *idmef,
+ idmef_alert_t *alert)
+{
+ int ret;
+
+ idmef_source_t *source;
+ idmef_user_t *suser;
+ idmef_user_id_t *user_id;
+ idmef_impact_type_t impact_type;
+ idmef_assessment_t *assessment;
+ idmef_impact_t *impact;
+ idmef_impact_severity_t severity;
+ prelude_string_t *str;
+ idmef_impact_completion_t completion = IDMEF_IMPACT_COMPLETION_ERROR;
+
+ /* Fill in information about the event's source */
+ ret = idmef_alert_new_source(alert, &source, -1);
+ PRELUDE_FAIL_CHECK;
+
+ ret = idmef_source_new_user(source, &suser);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_set_category(suser, IDMEF_USER_CATEGORY_APPLICATION);
+ ret = idmef_user_new_user_id(suser, &user_id, 0);
+ PRELUDE_FAIL_CHECK;
+ idmef_user_id_set_type(user_id, IDMEF_USER_ID_TYPE_ORIGINAL_USER);
+ ret = get_loginuid_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_tty_info(au, user_id);
+ PRELUDE_FAIL_CHECK;
+
+ ret = get_comm_info(au, source, NULL);
+ PRELUDE_FAIL_CHECK;
+
+ ret = add_execve_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ ret = add_serial_number_data(au, alert);
+ PRELUDE_FAIL_CHECK;
+
+ /* Describe event */
+ ret = set_classification(alert, "Keylogger");
+ PRELUDE_FAIL_CHECK;
+
+ /* Assess impact */
+ if (get_loginuid(au) == 0)
+ impact_type = IDMEF_IMPACT_TYPE_ADMIN;
+ else
+ impact_type = IDMEF_IMPACT_TYPE_USER;
+ completion = IDMEF_IMPACT_COMPLETION_SUCCEEDED;
+ severity = IDMEF_IMPACT_SEVERITY_LOW;
+
+ ret = idmef_alert_new_assessment(alert, &assessment);
+ PRELUDE_FAIL_CHECK;
+ ret = idmef_assessment_new_impact(assessment, &impact);
+ PRELUDE_FAIL_CHECK;
+ idmef_impact_set_severity(impact, severity);
+ PRELUDE_FAIL_CHECK;
+ idmef_impact_set_type(impact, impact_type);
+ PRELUDE_FAIL_CHECK;
+ ret = idmef_impact_new_description(impact, &str);
+ PRELUDE_FAIL_CHECK;
+ ret = prelude_string_set_ref(str, assessment_description[AS_TTY]);
+ PRELUDE_FAIL_CHECK;
+
+ send_idmef(client, idmef);
+ idmef_message_destroy(idmef);
+
+ return 0;
+
+ err:
+ syslog(LOG_ERR, "tty_alert: IDMEF error: %s.\n",
+ prelude_strerror(ret));
+ idmef_message_destroy(idmef);
+ return -1;
+}
+static void handle_event(auparse_state_t *au,
+ auparse_cb_event_t cb_event_type, void *user_data)
+{
+ int type, num=0;
+ idmef_message_t *idmef;
+ idmef_alert_t *alert;
+
+ if (cb_event_type != AUPARSE_CB_EVENT_READY)
+ return;
+
+ // Loop through the records in the event looking for one to process.
+ // We use physical record number because we may search around and
+ // move the cursor accidentally skipping a record.
+ while (auparse_goto_record_num(au, num) > 0) {
+ type = auparse_get_type(au);
+ switch (type) {
+ case AUDIT_AVC:
+// case AUDIT_USER_AVC: ignore USER_AVC for now
+ if (config.avcs == E_NO)
+ break;
+ if (config.avcs_act != A_IDMEF)
+ break;
+ if (new_alert_common(au, &idmef, &alert) >= 0)
+ avc_alert(au, idmef, alert);
+ break;
+ case AUDIT_USER_LOGIN:
+ // Do normal login alert
+ if (config.logins == E_YES &&
+ config.logins_act == A_IDMEF) {
+ if (new_alert_common(au, &idmef, &alert) >= 0){
+ login_alert(au, idmef, alert, "Login",
+ IDMEF_IMPACT_SEVERITY_INFO, AS_LOGIN);
+ }}
+ // Next do watched account alerts
+ if (config.watched_acct == E_NO)
+ break;
+ if (config.watched_acct_act != A_IDMEF)
+ break;
+ else if (account_is_watched(au)) {
+ if (new_alert_common(au, &idmef, &alert) >= 0){
+ login_alert(au, idmef, alert,
+ "Watched Account Login",
+ IDMEF_IMPACT_SEVERITY_MEDIUM,
+ AS_WATCHED_LOGIN);
+ }}
+ break;
+ case AUDIT_ANOM_LOGIN_FAILURES:
+ if (config.login_failure_max == E_NO)
+ break;
+ if (config.login_failure_max_act != A_IDMEF)
+ break;
+ if (new_alert_common(au, &idmef, &alert) >= 0){
+ login_alert(au, idmef, alert,
+ "Max Failed Logins",
+ IDMEF_IMPACT_SEVERITY_LOW,
+ AS_MAX_LOGIN_FAIL);
+ }
+ break;
+ case AUDIT_ANOM_LOGIN_SESSIONS:
+ if (config.login_session_max == E_NO)
+ break;
+ if (config.login_session_max_act != A_IDMEF)
+ break;
+ if (new_alert_common(au, &idmef, &alert) >= 0){
+ login_alert(au, idmef, alert,
+ "Max Concurrent Sessions",
+ IDMEF_IMPACT_SEVERITY_INFO,
+ AS_MAX_LOGIN_SESS);
+ }
+ break;
+ case AUDIT_ANOM_LOGIN_LOCATION:
+ if (config.login_location == E_NO)
+ break;
+ if (config.login_location_act != A_IDMEF)
+ break;
+ if (new_alert_common(au, &idmef, &alert) >= 0){
+ login_alert(au, idmef, alert,
+ "Login From Forbidden Location",
+ IDMEF_IMPACT_SEVERITY_MEDIUM,
+ AS_LOGIN_LOCATION);
+ }
+ break;
+ case AUDIT_ANOM_LOGIN_TIME:
+ if (config.login_time == E_NO)
+ break;
+ if (config.login_time_act != A_IDMEF)
+ break;
+ if (new_alert_common(au, &idmef, &alert) >= 0){
+ login_alert(au, idmef, alert,
+ "Login During Forbidden Time",
+ IDMEF_IMPACT_SEVERITY_LOW,
+ AS_LOGIN_TIME);
+ }
+ break;
+ case AUDIT_ANOM_ABEND:
+ if (config.abends == E_NO)
+ break;
+ if (config.abends_act != A_IDMEF)
+ break;
+ if (new_alert_common(au, &idmef, &alert) >= 0)
+ app_term_alert(au, idmef, alert);
+ break;
+ case AUDIT_ANOM_PROMISCUOUS:
+ if (config.promiscuous == E_NO)
+ break;
+ if (config.promiscuous_act != A_IDMEF)
+ break;
+ if (new_alert_common(au, &idmef, &alert) >= 0)
+ promiscuous_alert(au, idmef, alert);
+ break;
+ case AUDIT_MAC_STATUS:
+ if (config.mac_status == E_NO)
+ break;
+ if (config.mac_status_act != A_IDMEF)
+ break;
+ if (new_alert_common(au, &idmef, &alert) >= 0)
+ mac_status_alert(au, idmef, alert);
+ break;
+ case AUDIT_GRP_AUTH:
+ if (config.group_auth == E_NO)
+ break;
+ if (config.group_auth_act != A_IDMEF)
+ break;
+ else {
+ const char *result;
+
+ // We only care about failures
+ auparse_first_field(au);
+ result = auparse_find_field(au, "res");
+ if (result && strcmp(result, "failed"))
+ break;
+ if (new_alert_common(au, &idmef, &alert) >= 0){
+ auth_failure_alert(au, idmef, alert,
+ "Group Authentication Failure",
+ IDMEF_IMPACT_SEVERITY_LOW,
+ AS_AUTH);
+ }}
+ break;
+ case AUDIT_SYSCALL:
+ handle_watched_syscalls(au, &idmef, &alert);
+ // The previous call moves the current record
+ auparse_goto_record_num(au, num);
+ break;
+ case AUDIT_TTY:
+ if (config.tty == E_NO)
+ break;
+ if (config.tty_act != A_IDMEF)
+ break;
+ if (new_alert_common(au, &idmef, &alert) >= 0)
+ tty_alert(au, idmef, alert);
+ break;
+ default:
+ break;
+ }
+ num++;
+ }
+}
+
diff --git a/framework/src/audit/audisp/plugins/prelude/audisp-prelude.conf b/framework/src/audit/audisp/plugins/prelude/audisp-prelude.conf
new file mode 100644
index 00000000..ae499a86
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/prelude/audisp-prelude.conf
@@ -0,0 +1,61 @@
+#
+# This file controls the configuration of the audit based
+# intrusion detection system, audisp-prelude.
+#
+
+profile = auditd
+
+detect_avc = yes
+avc_action = idmef
+
+detect_logins = yes
+login_action = idmef
+#login_acct_exceptions =
+
+detect_login_fail_max = yes
+login_fail_max_action = idmef
+#login_fail_max_acct_exceptions =
+
+detect_login_session_max = yes
+login_session_max_action = idmef
+#login_session_max_acct_exceptions =
+
+detect_login_location = yes
+login_location_action = idmef
+#login_location_acct_exceptions =
+
+detect_login_time = yes
+login_time_action = idmef
+#login_time_acct_exceptions =
+
+detect_abend = yes
+abend_action = idmef
+
+detect_promiscuous = yes
+promiscuous_action = idmef
+
+detect_mac_status = yes
+mac_status_action = idmef
+
+detect_group_auth = yes
+group_auth_action = idmef
+
+detect_watched_acct = yes
+watched_acct_action = idmef
+watched_accounts = 1-499
+
+detect_watched_syscall = yes
+watched_syscall_action = idmef
+
+detect_watched_file = yes
+watched_file_action = idmef
+
+detect_watched_exec = yes
+watched_exec_action = idmef
+
+detect_watched_mk_exe = yes
+watched_mk_exe_action = idmef
+
+detect_tty = no
+tty_action = idmef
+
diff --git a/framework/src/audit/audisp/plugins/prelude/audisp-prelude.conf.5 b/framework/src/audit/audisp/plugins/prelude/audisp-prelude.conf.5
new file mode 100644
index 00000000..b7228ed3
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/prelude/audisp-prelude.conf.5
@@ -0,0 +1,153 @@
+.TH AUDISP-PRELUDE.CONF: "5" "Mar 2008" "Red Hat" "System Administration Utilities"
+.SH NAME
+audisp-prelude.conf \- the audisp-prelude configuration file
+.SH DESCRIPTION
+\fBaudisp-prelude.conf\fP is the file that controls the configuration of the audit based intrusion detection system. There are 2 general kinds of configuration option types, enablers and actions. The enablers simply have
+.IR yes "/" no "
+as the only valid choices.
+
+The action options currently allow
+.IR ignore ", and "idmef "
+as its choices. The
+.IR ignore
+option means that the IDS still detects events, but only logs the detection in response. The
+.IR idmef
+option means that the IDS will send an IDMEF alert to the prelude manager upon detection.
+
+The configuration options that are available are as follows:
+
+.TP
+.I profile
+This is a one word character string that is used to identify the profile name in the prelude reporting tools. The default is auditd.
+.TP
+.I detect_avc
+This an enabler that determines if the IDS should be examining SE Linux AVC events. The default is
+.IR yes ".
+.TP
+.I avc_action
+This is an action that determines what response should be taken whenever a SE Linux AVC is detected. The default is
+.IR idmef ".
+.TP
+.I detect_login
+This is an enabler that determines if the IDS should be examining login events. The default is
+.IR yes ".
+.TP
+.I login_action
+This is an action that determines what response should be taken whenever a login event is detected. The default is
+.IR idmef ".
+.TP
+.I detect_login_fail_max
+This is an enabler that determines if the IDS should be looking for maximum number of failed logins for an account. The default is
+.IR yes ".
+.TP
+.I login_fail_max_action
+This is an action that determines what response should be taken whenever the maximum number of failed logins for an account is detected. The default is
+.IR idmef ".
+.TP
+.I detect_login_session_max
+This is an enabler that determines if the IDS should be looking for maximum concurrent sessions limit for an account. The default is
+.IR yes ".
+.TP
+.I login_session_max_action
+This is an action that determines what response should be taken whenever the maximum concurrent sessions limit for an account is detected. The default is
+.IR idmef ".
+.TP
+.I detect_login_location
+This is an enabler that determines if the IDS should be looking for logins being attempted from a forbidden location. The default is
+.IR yes ".
+.TP
+.I login_location_action
+This is an action that determines what response should be taken whenever logins are attempted from a forbidden location. The default is
+.IR idmef ".
+.TP
+.I detect_login_time_alerts
+This is an enabler that determines if the IDS should be looking for logins attempted during a forbidden time. The default is
+.IR yes ".
+.TP
+.I login_time_action
+This is an action that determines what response should be taken whenever logins are attempted during a forbidden time. The default is
+.IR idmef ".
+.TP
+.I detect_abend
+This is an enabler that determines if the IDS should be looking for programs terminating for an abnormal reason. The default is
+.IR yes ".
+.TP
+.I abend_action
+This is an action that determines what response should be taken whenever programs terminate for an abnormal reason. The default is
+.IR idmef ".
+.TP
+.I detect_promiscuous
+This is an enabler that determines if the IDS should be looking for promiscuous sockets being opened. The default is
+.IR yes ".
+.TP
+.I promiscuous_action
+This is an action that determines what response should be taken whenever promiscuous sockets are detected open. The default is
+.IR idmef ".
+.TP
+.I detect_mac_status
+This is an enabler that determines if the IDS should be detecting changes made to the SE Linux MAC enforcement. The default is
+.IR yes ".
+.TP
+.I mac_status_action
+This is an action that determines what response should be taken whenever changes are made to the SE Linux MAC enforcement. The default is
+.IR idmef ".
+.TP
+.I detect_group_auth
+This is an enabler that determines if the IDS should be detecting whenever a user fails in changing their default group. The default is
+.IR yes ".
+.TP
+.I group_auth_act
+This is an action that determines what response should be taken whenever a user fails in changing their default group. The default is
+.IR idmef ".
+.TP
+.I detect_watched_acct
+This is an enabler that determines if the IDS should be detecting a user attempting to login on an account that is being watched. The accounts to watch is set by the
+.IR watched_accounts
+option. The default is
+.IR yes ".
+.TP
+.I watched_acct_act
+This is an action that determines what response should be taken whenever a user attempts to login on an account that is being watched. The default is
+.IR idmef ".
+.TP
+.I watched_accounts
+This option is a whitespace and comma separated list of accounts to watch. The accounts may be numeric or alphanumeric. If you want to include a range of accounts, separate them with a dash but no spaces. For example, to watch logins from bin to lp, use "bin-lp". Only successful logins are recorded.
+.TP
+.I detect_watched_syscall
+This is an enabler that determines if the IDS should be detecting whenever a user runs a command that issues a syscall that is being watched. The default is
+.IR yes ".
+.TP
+.I watched_syscall_act
+This is an action that determines what response should be taken whenever a user runs a command that issues a syscall that is being watched. The default is
+.IR idmef ".
+.TP
+.I detect_watched_file
+This is an enabler that determines if the IDS should be detecting whenever a user accesses a file that is being watched. The default is
+.IR yes ".
+.TP
+.I watched_file_act
+This is an action that determines what response should be taken whenever a user accesses a file that is being watched. The default is
+.IR idmef ".
+.TP
+.I detect_watched_exec
+This is an enabler that determines if the IDS should be detecting whenever a user executes a program that is being watched. The default is
+.IR yes ".
+.TP
+.I watched_exec_act
+This is an action that determines what response should be taken whenever a user executes a program that is being watched. The default is
+.IR idmef ".
+.TP
+.I detect_watched_mk_exe
+This is an enabler that determines if the IDS should be detecting whenever a user creates a file that is executable. The default is
+.IR yes ".
+.TP
+.I watched_mk_exe_act
+This is an action that determines what response should be taken whenever a user creates a file that is executable. The default is
+.IR idmef ".
+.SH "SEE ALSO"
+.BR audispd (8),
+.BR audisp-prelude (8),
+.BR prelude-manager (1).
+.SH AUTHOR
+Steve Grubb
+
diff --git a/framework/src/audit/audisp/plugins/prelude/prelude-config.c b/framework/src/audit/audisp/plugins/prelude/prelude-config.c
new file mode 100644
index 00000000..3a360483
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/prelude/prelude-config.c
@@ -0,0 +1,844 @@
+/* prelude-config.c --
+ * Copyright 2008,2010-2011 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#include "config.h"
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <ctype.h>
+#include <pwd.h>
+#include "prelude-config.h"
+
+/* Local prototypes */
+struct nv_pair
+{
+ const char *name;
+ const char *value;
+ const char *option;
+};
+
+struct kw_pair
+{
+ const char *name;
+ int (*parser)(struct nv_pair *, int, prelude_conf_t *);
+ int max_options;
+};
+
+struct nv_list
+{
+ const char *name;
+ int option;
+};
+
+static char *get_line(FILE *f, char *buf);
+static int nv_split(char *buf, struct nv_pair *nv);
+static const struct kw_pair *kw_lookup(const char *val);
+static int profile_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int avc_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int avc_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int login_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int login_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int login_failure_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int login_failure_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int login_session_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int login_session_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int login_location_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int login_location_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int login_time_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int login_time_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int abends_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int abends_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int promiscuous_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int promiscuous_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int mac_status_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int mac_status_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int group_auth_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int group_auth_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_acct_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_acct_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_accounts_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_syscall_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_syscall_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_file_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_file_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_exec_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_exec_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_mk_exe_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int watched_mk_exe_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int tty_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int tty_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config);
+static int sanity_check(prelude_conf_t *config, const char *file);
+
+static const struct kw_pair keywords[] =
+{
+ {"profile", profile_parser, 0 },
+ {"detect_avc", avc_parser, 0 },
+ {"avc_action", avc_act_parser, 0 },
+ {"detect_logins", login_parser, 0 },
+ {"login_action", login_act_parser, 0 },
+ {"detect_login_fail_max", login_failure_parser, 0 },
+ {"login_fail_max_action", login_failure_act_parser, 0 },
+ {"detect_login_session_max", login_session_parser, 0 },
+ {"login_session_max_action", login_session_act_parser, 0 },
+ {"detect_login_location", login_location_parser, 0 },
+ {"login_location_action", login_location_act_parser, 0 },
+ {"detect_login_time", login_time_parser, 0 },
+ {"login_time_action", login_time_act_parser, 0 },
+ {"detect_abend", abends_parser, 0 },
+ {"abend_action", abends_act_parser, 0 },
+ {"detect_promiscuous", promiscuous_parser, 0 },
+ {"promiscuous_action", promiscuous_act_parser, 0 },
+ {"detect_mac_status", mac_status_parser, 0 },
+ {"mac_status_action", mac_status_act_parser, 0 },
+ {"detect_group_auth", group_auth_parser, 0 },
+ {"group_auth_action", group_auth_act_parser, 0 },
+ {"detect_watched_acct", watched_acct_parser, 0 },
+ {"watched_acct_action", watched_acct_act_parser, 0 },
+ {"watched_accounts", watched_accounts_parser, 1 },
+ {"detect_watched_syscall", watched_syscall_parser, 0 },
+ {"watched_syscall_action", watched_syscall_act_parser, 0 },
+ {"detect_watched_file", watched_file_parser, 0 },
+ {"watched_file_action", watched_file_act_parser, 0 },
+ {"detect_watched_exec", watched_exec_parser, 0 },
+ {"watched_exec_action", watched_exec_act_parser, 0 },
+ {"detect_watched_mk_exe", watched_mk_exe_parser, 0 },
+ {"watched_mk_exe_action", watched_mk_exe_act_parser, 0 },
+ {"detect_tty", tty_parser, 0 },
+ {"tty_action", tty_act_parser, 0 },
+ { NULL, NULL }
+};
+
+static const struct nv_list enabler_words[] =
+{
+ {"no", E_NO },
+ {"yes", E_YES },
+ { NULL, 0 }
+};
+
+static const struct nv_list action_words[] =
+{
+ {"ignore", A_IGNORE },
+ {"idmef", A_IDMEF },
+// {"kill", A_KILL },
+// {"session", A_SESSION },
+// {"single", A_SINGLE },
+// {"halt", A_HALT },
+ { NULL, 0 }
+};
+
+/*
+ * Set everything to its default value
+*/
+void clear_config(prelude_conf_t *config)
+{
+ config->profile = strdup("auditd");
+ config->avcs = E_YES;
+ config->avcs_act = A_IDMEF;
+ config->logins = E_YES;
+ config->logins_act = A_IDMEF;
+ config->login_failure_max = E_YES;
+ config->login_failure_max_act = A_IDMEF;
+ config->login_session_max = E_YES;
+ config->login_session_max_act = A_IDMEF;
+ config->login_location = E_YES;
+ config->login_location_act = A_IDMEF;
+ config->login_time = E_YES;
+ config->login_time_act = A_IDMEF;
+ config->abends = E_YES;
+ config->abends_act = A_IDMEF;
+ config->promiscuous = E_YES;
+ config->promiscuous_act = A_IDMEF;
+ config->mac_status = E_YES;
+ config->mac_status_act = A_IDMEF;
+ config->group_auth = E_YES;
+ config->group_auth_act = A_IDMEF;
+ config->watched_acct = E_YES;
+ config->watched_acct_act = A_IDMEF;
+ config->watched_syscall = E_YES;
+ config->watched_syscall_act = A_IDMEF;
+ config->watched_file = E_YES;
+ config->watched_file_act = A_IDMEF;
+ config->watched_exec = E_YES;
+ config->watched_exec_act = A_IDMEF;
+ config->watched_mk_exe = E_YES;
+ config->watched_mk_exe_act = A_IDMEF;
+ config->tty = E_NO;
+ config->tty_act = A_IDMEF;
+ ilist_create(&config->watched_accounts);
+}
+
+int load_config(prelude_conf_t *config, const char *file)
+{
+ int fd, rc, mode, lineno = 1;
+ struct stat st;
+ FILE *f;
+ char buf[128];
+
+ clear_config(config);
+
+ /* open the file */
+ mode = O_RDONLY;
+ rc = open(file, mode);
+ if (rc < 0) {
+ free_config(config);
+ if (errno != ENOENT) {
+ syslog(LOG_ERR, "Error opening %s (%s)", file,
+ strerror(errno));
+ return 1;
+ }
+ syslog(LOG_WARNING,
+ "Config file %s doesn't exist, skipping", file);
+ return 0;
+ }
+ fd = rc;
+
+ /* check the file's permissions: owned by root, not world writable,
+ * not symlink.
+ */
+ if (fstat(fd, &st) < 0) {
+ free_config(config);
+ syslog(LOG_ERR, "Error fstat'ing config file (%s)",
+ strerror(errno));
+ close(fd);
+ return 1;
+ }
+ if (st.st_uid != 0) {
+ free_config(config);
+ syslog(LOG_ERR, "Error - %s isn't owned by root",
+ file);
+ close(fd);
+ return 1;
+ }
+ if ((st.st_mode & S_IWOTH) == S_IWOTH) {
+ free_config(config);
+ syslog(LOG_ERR, "Error - %s is world writable",
+ file);
+ close(fd);
+ return 1;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ free_config(config);
+ syslog(LOG_ERR, "Error - %s is not a regular file",
+ file);
+ close(fd);
+ return 1;
+ }
+
+ /* it's ok, read line by line */
+ f = fdopen(fd, "rm");
+ if (f == NULL) {
+ free_config(config);
+ syslog(LOG_ERR, "Error - fdopen failed (%s)",
+ strerror(errno));
+ close(fd);
+ return 1;
+ }
+
+ while (get_line(f, buf)) {
+ // convert line into name-value pair
+ const struct kw_pair *kw;
+ struct nv_pair nv;
+ rc = nv_split(buf, &nv);
+ switch (rc) {
+ case 0: // fine
+ break;
+ case 1: // not the right number of tokens.
+ syslog(LOG_ERR,
+ "Wrong number of arguments for line %d in %s",
+ lineno, file);
+ break;
+ case 2: // no '=' sign
+ syslog(LOG_ERR,
+ "Missing equal sign for line %d in %s",
+ lineno, file);
+ break;
+ default: // something else went wrong...
+ syslog(LOG_ERR,
+ "Unknown error for line %d in %s",
+ lineno, file);
+ break;
+ }
+ if (nv.name == NULL) {
+ lineno++;
+ continue;
+ }
+ if (nv.value == NULL) {
+ free_config(config);
+ fclose(f);
+ return 1;
+ }
+
+ /* identify keyword or error */
+ kw = kw_lookup(nv.name);
+ if (kw->name == NULL) {
+ free_config(config);
+ syslog(LOG_ERR,
+ "Unknown keyword \"%s\" in line %d of %s",
+ nv.name, lineno, file);
+ fclose(f);
+ return 1;
+ }
+
+ /* Check number of options */
+ if (kw->max_options == 0 && nv.option != NULL) {
+ free_config(config);
+ syslog(LOG_ERR,
+ "Keyword \"%s\" has invalid option "
+ "\"%s\" in line %d of %s",
+ nv.name, nv.option, lineno, file);
+ fclose(f);
+ return 1;
+ }
+
+ /* dispatch to keyword's local parser */
+ rc = kw->parser(&nv, lineno, config);
+ if (rc != 0) {
+ free_config(config);
+ fclose(f);
+ return 1; // local parser puts message out
+ }
+
+ lineno++;
+ }
+
+ fclose(f);
+ if (lineno > 1)
+ return sanity_check(config, file);
+ return 0;
+}
+
+static char *get_line(FILE *f, char *buf)
+{
+ if (fgets_unlocked(buf, 128, f)) {
+ /* remove newline */
+ char *ptr = strchr(buf, 0x0a);
+ if (ptr)
+ *ptr = 0;
+ return buf;
+ }
+ return NULL;
+}
+
+static int nv_split(char *buf, struct nv_pair *nv)
+{
+ /* Get the name part */
+ char *ptr, *saved;
+
+ nv->name = NULL;
+ nv->value = NULL;
+ nv->option = NULL;
+ ptr = strtok_r(buf, " ", &saved);
+ if (ptr == NULL)
+ return 0; /* If there's nothing, go to next line */
+ if (ptr[0] == '#')
+ return 0; /* If there's a comment, go to next line */
+ nv->name = ptr;
+
+ /* Check for a '=' */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr == NULL)
+ return 1;
+ if (strcmp(ptr, "=") != 0)
+ return 2;
+
+ /* get the value */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr == NULL)
+ return 1;
+ nv->value = ptr;
+
+ /* Everything is OK */
+ return 0;
+}
+
+static const struct kw_pair *kw_lookup(const char *val)
+{
+ int i = 0;
+ while (keywords[i].name != NULL) {
+ if (strcasecmp(keywords[i].name, val) == 0)
+ break;
+ i++;
+ }
+ return &keywords[i];
+}
+
+static int profile_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (nv->value) {
+ free((char*)config->profile);
+ config->profile = strdup(nv->value);
+ }
+ return 0;
+}
+
+static int lookup_enabler(const char *value, enable_t *enabled)
+{
+ int i;
+ for (i=0; enabler_words[i].name != NULL; i++) {
+ if (strcasecmp(value, enabler_words[i].name) == 0) {
+ *enabled = enabler_words[i].option;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int lookup_action(const char *value, action_t *action)
+{
+ int i;
+ for (i=0; action_words[i].name != NULL; i++) {
+ if (strcasecmp(value, action_words[i].name) == 0) {
+ *action = action_words[i].option;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int avc_parser(struct nv_pair *nv, int line, prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->avcs) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int avc_act_parser(struct nv_pair *nv, int line, prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->avcs_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int login_parser(struct nv_pair *nv, int line, prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->logins) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int login_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->logins_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int login_failure_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->login_failure_max) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int login_failure_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->login_failure_max_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int login_session_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->login_session_max) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int login_session_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->login_session_max_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int login_location_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->login_location) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int login_location_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->login_location_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int login_time_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->login_time) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int login_time_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->login_time_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int abends_parser(struct nv_pair *nv, int line, prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->abends) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int abends_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->abends_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int promiscuous_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->promiscuous) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int promiscuous_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->promiscuous_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int mac_status_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->mac_status) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int mac_status_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->mac_status_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int group_auth_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->group_auth) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int group_auth_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->group_auth_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int watched_acct_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->watched_acct) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int watched_acct_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->watched_acct_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int string_is_numeric(const char *s)
+{
+ if (*s == 0)
+ return 0;
+ do {
+ if (!isdigit(*s))
+ return 0;
+ s++;
+ } while (*s);
+ return 1;
+}
+
+static int watched_accounts_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ char *str = (char *)nv->value;
+ do {
+ char *ptr = strchr(str, '-');
+ if (ptr) {
+ char *user1, *user2;
+ int start, end, i;
+
+ user1 = str;
+ *ptr = 0;
+ user2 = ptr+1;
+ if (string_is_numeric(user1)) {
+ start = strtoul(user1, NULL, 10);
+ } else {
+ struct passwd *pw;
+ pw = getpwnam(user1);
+ if (pw == NULL) {
+ syslog(LOG_ERR,
+ "user %s is invalid - line %d, skipping",
+ user1, line);
+ continue;
+ }
+ start = pw->pw_uid;
+ }
+ i = strlen(user2);
+ if (i>0 && user2[i-1] == ',')
+ user2[i-1] = 0;
+ if (string_is_numeric(user2)) {
+ end = strtoul(user2, NULL, 10);
+ } else {
+ struct passwd *pw;
+ pw = getpwnam(user2);
+ if (pw == NULL) {
+ syslog(LOG_ERR,
+ "user %s is invalid - line %d, skipping",
+ user2, line);
+ continue;
+ }
+ end = pw->pw_uid;
+ }
+ if (start >= end) {
+ syslog(LOG_ERR,
+ "%s is larger or equal to %s, please fix, skipping",
+ user1, user2);
+ continue;
+ }
+ for (i=start; i<=end; i++) {
+ ilist_add_if_uniq(
+ &config->watched_accounts, i);
+ }
+ } else {
+ int acct;
+ if (string_is_numeric(str))
+ acct = strtoul(str, NULL, 10);
+ else {
+ struct passwd *pw;
+ pw = getpwnam(str);
+ if (pw == NULL) {
+ syslog(LOG_ERR,
+ "user %s is invalid - line %d, skipping",
+ str, line);
+ continue;
+ }
+ acct = pw->pw_uid;
+ }
+ ilist_add_if_uniq(&config->watched_accounts, acct);
+ }
+ str = strtok(NULL, ", ");
+ } while(str);
+
+ return 0;
+}
+
+static int watched_syscall_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->watched_syscall) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int watched_syscall_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->watched_syscall_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int watched_file_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->watched_file) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int watched_file_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->watched_file_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int watched_exec_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->watched_exec) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int watched_exec_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->watched_exec_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int watched_mk_exe_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->watched_mk_exe) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int watched_mk_exe_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->watched_mk_exe_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int tty_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_enabler(nv->value, &config->tty) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int tty_act_parser(struct nv_pair *nv, int line,
+ prelude_conf_t *config)
+{
+ if (lookup_action(nv->value, &config->tty_act) == 0)
+ return 0;
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+/*
+ * This function is where we do the integrated check of the audispd config
+ * options. At this point, all fields have been read. Returns 0 if no
+ * problems and 1 if problems detected.
+ */
+static int sanity_check(prelude_conf_t *config, const char *file)
+{
+ /* Error checking */
+ return 0;
+}
+
+void free_config(prelude_conf_t *config)
+{
+ free((void *)config->profile);
+ ilist_clear(&config->watched_accounts);
+}
+
diff --git a/framework/src/audit/audisp/plugins/prelude/prelude-config.h b/framework/src/audit/audisp/plugins/prelude/prelude-config.h
new file mode 100644
index 00000000..f9d1c14a
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/prelude/prelude-config.h
@@ -0,0 +1,76 @@
+/* prelude-config.h --
+ * Copyright 2008 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#ifndef PRELUDE_CONFIG_H
+#define PRELUDE_CONFIG_H
+
+#include "audisp-int.h"
+
+typedef enum { E_NO, E_YES } enable_t;
+typedef enum { A_IGNORE, A_IDMEF=1, A_KILL=2, A_SESSION=4, A_SINGLE=8,
+ A_HALT=16 } action_t;
+
+typedef struct prelude_conf
+{
+ const char *profile;
+ enable_t avcs;
+ action_t avcs_act;
+ enable_t logins;
+ action_t logins_act;
+ enable_t login_failure_max;
+ action_t login_failure_max_act;
+ enable_t login_session_max;
+ action_t login_session_max_act;
+ enable_t login_location;
+ action_t login_location_act;
+ enable_t login_time;
+ action_t login_time_act;
+ enable_t abends;
+ action_t abends_act;
+ enable_t promiscuous;
+ action_t promiscuous_act;
+ enable_t mac_status;
+ action_t mac_status_act;
+ enable_t group_auth;
+ action_t group_auth_act;
+ enable_t watched_acct;
+ action_t watched_acct_act;
+ ilist watched_accounts;
+ enable_t watched_syscall;
+ action_t watched_syscall_act;
+ enable_t watched_file;
+ action_t watched_file_act;
+ enable_t watched_exec;
+ action_t watched_exec_act;
+ enable_t watched_mk_exe;
+ action_t watched_mk_exe_act;
+ enable_t tty;
+ action_t tty_act;
+} prelude_conf_t;
+
+void clear_config(prelude_conf_t *config);
+int load_config(prelude_conf_t *config, const char *file);
+void free_config(prelude_conf_t *config);
+
+#endif
+
diff --git a/framework/src/audit/audisp/plugins/remote/Makefile.am b/framework/src/audit/audisp/plugins/remote/Makefile.am
new file mode 100644
index 00000000..8440e908
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/Makefile.am
@@ -0,0 +1,51 @@
+# Makefile.am --
+# Copyright 2008-2009,2011,2015 Red Hat Inc., Durham, North Carolina.
+# All Rights Reserved.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Authors:
+# Steve Grubb <sgrubb@redhat.com>
+#
+
+CONFIG_CLEAN_FILES = *.loT *.rej *.orig
+EXTRA_DIST = au-remote.conf audisp-remote.conf notes.txt $(man_MANS)
+AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/lib
+prog_confdir = $(sysconfdir)/audisp
+prog_conf = audisp-remote.conf
+plugin_confdir=$(prog_confdir)/plugins.d
+plugin_conf = au-remote.conf
+sbin_PROGRAMS = audisp-remote
+noinst_HEADERS = remote-config.h queue.h remote-fgets.h
+man_MANS = audisp-remote.8 audisp-remote.conf.5
+check_PROGRAMS = test-queue
+TESTS = $(check_PROGRAMS)
+
+audisp_remote_SOURCES = audisp-remote.c remote-config.c queue.c remote-fgets.c
+audisp_remote_CFLAGS = -fPIE -DPIE -g -D_REENTRANT -D_GNU_SOURCE -Wundef
+audisp_remote_LDFLAGS = -pie -Wl,-z,relro -Wl,-z,now $(gss_libs)
+audisp_remote_LDADD = $(CAPNG_LDADD)
+
+test_queue_SOURCES = queue.c test-queue.c
+
+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/framework/src/audit/audisp/plugins/remote/au-remote.conf b/framework/src/audit/audisp/plugins/remote/au-remote.conf
new file mode 100644
index 00000000..e0adf96c
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/au-remote.conf
@@ -0,0 +1,12 @@
+
+# This file controls the audispd data path to the
+# remote event logger. This plugin will send events to
+# a remote machine (Central Logger).
+
+active = no
+direction = out
+path = /sbin/audisp-remote
+type = always
+#args =
+format = string
+
diff --git a/framework/src/audit/audisp/plugins/remote/audisp-remote.8 b/framework/src/audit/audisp/plugins/remote/audisp-remote.8
new file mode 100644
index 00000000..6f3b5fe6
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/audisp-remote.8
@@ -0,0 +1,34 @@
+.TH AUDISP-REMOTE: "8" "Apr 2011" "Red Hat" "System Administration Utilities"
+.SH NAME
+audisp-remote \- plugin for remote logging
+.SH SYNOPSIS
+.B audisp-remote
+.SH DESCRIPTION
+\fBaudisp-remote\fP is a plugin for the audit event dispatcher daemon, audispd, that preforms remote logging to an aggregate logging server.
+
+.SH TIPS
+If you are aggregating multiple machines, you should enable node information in the audit event stream. You can do this in one of two places. If you want computer node names written to disk as well as sent in the realtime event stream, edit the name_format option in /etc/audit/auditd.conf. If you only want the node names in the realtime event stream, then edit the name_format option in /etc/audisp/audispd.conf. Do not enable both as it will put 2 node fields in the event stream.
+
+.SH SIGNALS
+.TP
+SIGUSR1
+Causes the audisp-remote program to write the value of some of its internal flags to syslog. The
+.IR suspend
+flag tells whether or not logging has been suspended. The
+.IR transport_ok
+flag tells whether or not the connection to the remote server is healthy. The
+.IR queue_size
+tells how many records are enqueued to be sent to the remote server.
+.TP
+SIGUSR2
+Causes the audisp-remote program to resume logging if it were suspended due to an error.
+
+.SH FILES
+/etc/audisp/plugins.d/au-remote.conf, /etc/audit/auditd.conf, /etc/audisp/audispd.conf, /etc/audisp/audisp-remote.conf
+.SH "SEE ALSO"
+.BR audispd (8),
+.BR auditd.conf(8),
+.BR audispd.conf(8),
+.BR audisp-remote.conf(5).
+.SH AUTHOR
+Steve Grubb
diff --git a/framework/src/audit/audisp/plugins/remote/audisp-remote.c b/framework/src/audit/audisp/plugins/remote/audisp-remote.c
new file mode 100644
index 00000000..2585c78c
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/audisp-remote.c
@@ -0,0 +1,1486 @@
+/* audisp-remote.c --
+ * Copyright 2008-2012 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <signal.h>
+#include <syslog.h>
+#include <string.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <fcntl.h>
+#include <sys/select.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#ifdef USE_GSSAPI
+#include <gssapi/gssapi.h>
+#include <gssapi/gssapi_generic.h>
+#include <krb5.h>
+#endif
+#ifdef HAVE_LIBCAP_NG
+#include <cap-ng.h>
+#endif
+#include "libaudit.h"
+#include "private.h"
+#include "remote-config.h"
+#include "queue.h"
+#include "remote-fgets.h"
+
+#define CONFIG_FILE "/etc/audisp/audisp-remote.conf"
+#define BUF_SIZE 32
+
+/* MAX_AUDIT_MESSAGE_LENGTH, aligned to 4 KB so that an average q_append() only
+ writes to two disk disk blocks (1 aligned data block, 1 header block). */
+#define QUEUE_ENTRY_SIZE (3*4096)
+
+/* Error types */
+#define ET_SUCCESS 0
+#define ET_PERMANENT -1
+#define ET_TEMPORARY -2
+
+/* Global Data */
+static volatile int stop = 0;
+static volatile int hup = 0;
+static volatile int suspend = 0;
+static volatile int dump = 0;
+static volatile int transport_ok = 0;
+static volatile int sock=-1;
+static volatile int remote_ended = 0, quiet = 0;
+static int ifd;
+remote_conf_t config;
+
+/* Constants */
+static const char *SINGLE = "1";
+static const char *HALT = "0";
+static const char *INIT_PGM = "/sbin/init";
+static const char *SPOOL_FILE = "/var/spool/audit/remote.log";
+
+/* Local function declarations */
+static int check_message(void);
+static int relay_event(const char *s, size_t len);
+static int init_transport(void);
+static int stop_transport(void);
+static int ar_read (int, void *, int);
+static int ar_write (int, const void *, int);
+
+#ifdef USE_GSSAPI
+/* We only ever talk to one server, so we don't need per-connection
+ credentials. These are the ones we talk to the server with. */
+gss_ctx_id_t my_context;
+
+#define REQ_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG
+#define USE_GSS (config.enable_krb5)
+#endif
+
+/* Compile-time expression verification */
+#define verify(E) do { \
+ char verify__[(E) ? 1 : -1]; \
+ (void)verify__; \
+ } while (0)
+
+/*
+ * SIGTERM handler
+ */
+static void term_handler( int sig )
+{
+ stop = 1;
+}
+
+/*
+ * SIGHUP handler: re-read config
+ */
+static void hup_handler( int sig )
+{
+ hup = 1;
+}
+
+static void reload_config(void)
+{
+ stop_transport(); // FIXME: We should only stop transport if necessary
+ hup = 0;
+}
+
+/*
+ * SIGSUR1 handler: dump stats
+ */
+static void user1_handler( int sig )
+{
+ dump = 1;
+}
+
+static void dump_stats(struct queue *queue)
+{
+ syslog(LOG_INFO, "suspend=%s, transport_ok=%s, queue_size=%zu",
+ suspend ? "yes" : "no",
+ transport_ok ? "yes" : "no",
+ q_queue_length(queue));
+ dump = 0;
+}
+
+/*
+ * SIGSUR2 handler: resume logging
+ */
+static void user2_handler( int sig )
+{
+ suspend = 0;
+}
+
+/*
+ * SIGCHLD handler: reap exiting processes
+ */
+static void child_handler(int sig)
+{
+ while (waitpid(-1, NULL, WNOHANG) > 0)
+ ; /* empty */
+}
+
+/*
+ * Handlers for various events coming back from the remote server.
+ * Return -1 if the remote dispatcher should exit.
+ */
+
+/* Loss of sync - got an invalid response. */
+static int sync_error_handler (const char *why)
+{
+ /* "why" has human-readable details on why we've lost (or will
+ be losing) sync. Sync errors are transient - if a retry
+ doesn't fix it, we eventually call network_failure_handler
+ which has all the user-tweakable actions. */
+ syslog (LOG_ERR, "lost/losing sync, %s", why);
+ return 0;
+}
+
+static void change_runlevel(const char *level)
+{
+ char *argv[3];
+ int pid;
+
+ pid = fork();
+ if (pid < 0) {
+ syslog(LOG_ALERT,
+ "audisp-remote failed to fork switching runlevels");
+ return;
+ }
+ if (pid) /* Parent */
+ return;
+
+ /* Child */
+ argv[0] = (char *)INIT_PGM;
+ argv[1] = (char *)level;
+ argv[2] = NULL;
+ execve(INIT_PGM, argv, NULL);
+ syslog(LOG_ALERT, "audisp-remote failed to exec %s", INIT_PGM);
+ exit(1);
+}
+
+static void safe_exec(const char *exe, const char *message)
+{
+ char *argv[3];
+ int pid;
+
+ if (exe == NULL) {
+ syslog(LOG_ALERT,
+ "Safe_exec passed NULL for program to execute");
+ return;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ syslog(LOG_ALERT,
+ "audisp-remote failed to fork doing safe_exec");
+ return;
+ }
+ if (pid) /* Parent */
+ return;
+
+ /* Child */
+ argv[0] = (char *)exe;
+ argv[1] = (char *)message;
+ argv[2] = NULL;
+ execve(exe, argv, NULL);
+ syslog(LOG_ALERT, "audisp-remote failed to exec %s", exe);
+ exit(1);
+}
+
+static int do_action (const char *desc, const char *message,
+ int log_level,
+ failure_action_t action, const char *exe)
+{
+ switch (action)
+ {
+ case FA_IGNORE:
+ return 0;
+ case FA_SYSLOG:
+ syslog (log_level, "%s, %s", desc, message);
+ return 0;
+ case FA_EXEC:
+ safe_exec (exe, message);
+ return 0;
+ case FA_SUSPEND:
+ syslog (log_level,
+ "suspending remote logging due to %s", desc);
+ suspend = 1;
+ return 0;
+ case FA_RECONNECT:
+ syslog (log_level,
+ "remote logging disconnected due to %s, will attempt reconnection",
+ desc);
+ return 0;
+ case FA_SINGLE:
+ syslog (log_level,
+ "remote logging is switching system to single user mode due to %s",
+ desc);
+ change_runlevel(SINGLE);
+ return -1;
+ case FA_HALT:
+ syslog (log_level,
+ "remote logging halting system due to %s", desc);
+ change_runlevel(HALT);
+ return -1;
+ case FA_STOP:
+ syslog (log_level, "remote logging stopping due to %s, %s",
+ desc, message);
+ stop = 1;
+ return -1;
+ }
+ syslog (log_level, "unhandled action %d for %s", action, desc);
+ return -1;
+}
+
+static int network_failure_handler (const char *message)
+{
+ return do_action ("network failure", message,
+ LOG_WARNING,
+ config.network_failure_action,
+ config.network_failure_exe);
+}
+
+static int remote_disk_low_handler (const char *message)
+{
+ return do_action ("remote server is low on disk space", message,
+ LOG_WARNING,
+ config.disk_low_action, config.disk_low_exe);
+}
+
+static int remote_disk_full_handler (const char *message)
+{
+ return do_action ("remote server's disk is full", message,
+ LOG_ERR,
+ config.disk_full_action, config.disk_full_exe);
+}
+
+static int remote_disk_error_handler (const char *message)
+{
+ return do_action ("remote server has a disk error", message,
+ LOG_ERR,
+ config.disk_error_action, config.disk_error_exe);
+}
+
+static int remote_server_ending_handler (const char *message)
+{
+ stop_transport();
+ remote_ended = 1;
+ return do_action ("remote server is going down", message,
+ LOG_NOTICE,
+ config.remote_ending_action,
+ config.remote_ending_exe);
+}
+
+static int generic_remote_error_handler (const char *message)
+{
+ return do_action ("unrecognized remote error", message,
+ LOG_ERR, config.generic_error_action,
+ config.generic_error_exe);
+}
+
+static int generic_remote_warning_handler (const char *message)
+{
+ return do_action ("unrecognized remote warning", message,
+ LOG_WARNING,
+ config.generic_warning_action,
+ config.generic_warning_exe);
+}
+
+/* Report and handle a queue error, using errno. */
+static void queue_error(void)
+{
+ char *errno_str;
+
+ errno_str = strerror(errno);
+ do_action("queue error", errno_str, LOG_ERR, config.queue_error_action,
+ config.queue_error_exe);
+}
+
+static void send_heartbeat (void)
+{
+ relay_event (NULL, 0);
+}
+
+static void do_overflow_action(void)
+{
+ switch (config.overflow_action)
+ {
+ case OA_IGNORE:
+ break;
+ case OA_SYSLOG:
+ syslog(LOG_ERR, "queue is full - dropping event");
+ break;
+ case OA_SUSPEND:
+ syslog(LOG_ALERT,
+ "Audisp-remote is suspending event processing due to overflowing its queue.");
+ suspend = 1;
+ break;
+ case OA_SINGLE:
+ syslog(LOG_ALERT,
+ "Audisp-remote is now changing the system to single user mode due to overflowing its queue");
+ change_runlevel(SINGLE);
+ break;
+ case OA_HALT:
+ syslog(LOG_ALERT,
+ "Audisp-remote is now halting the system due to overflowing its queue");
+ change_runlevel(HALT);
+ break;
+ default:
+ syslog(LOG_ALERT, "Unknown overflow action requested");
+ break;
+ }
+}
+
+/* Initialize and return a queue depending on user's configuration.
+ On error return NULL and set errno. */
+static struct queue *init_queue(void)
+{
+ const char *path;
+ int q_flags;
+
+ if (config.queue_file != NULL)
+ path = config.queue_file;
+ else
+ path = SPOOL_FILE;
+ q_flags = Q_IN_MEMORY;
+ if (config.mode == M_STORE_AND_FORWARD)
+ /* FIXME: let user control Q_SYNC? */
+ q_flags |= Q_IN_FILE | Q_CREAT | Q_RESIZE;
+ verify(QUEUE_ENTRY_SIZE >= MAX_AUDIT_MESSAGE_LENGTH);
+ return q_open(q_flags, path, config.queue_depth, QUEUE_ENTRY_SIZE);
+}
+
+/* Send a record from QUEUE to the remote system */
+static void send_one(struct queue *queue)
+{
+ char event[MAX_AUDIT_MESSAGE_LENGTH];
+ int len;
+
+ if (suspend || !transport_ok)
+ return;
+
+ len = q_peek(queue, event, sizeof(event));
+ if (len == 0)
+ return;
+ if (len < 0) {
+ queue_error();
+ return;
+ }
+
+ /* We send len -1 to remove trailing \n */
+ if (relay_event(event, len-1) < 0)
+ return;
+
+ if (q_drop_head(queue) != 0)
+ queue_error();
+}
+
+int main(int argc, char *argv[])
+{
+ struct sigaction sa;
+ struct queue *queue;
+ int rc;
+ size_t q_len;
+
+ /* 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);
+ sa.sa_handler = user1_handler;
+ sigaction(SIGUSR1, &sa, NULL);
+ sa.sa_handler = user2_handler;
+ sigaction(SIGUSR2, &sa, NULL);
+ sa.sa_handler = child_handler;
+ sigaction(SIGCHLD, &sa, NULL);
+ if (load_config(&config, CONFIG_FILE))
+ return 6;
+
+ (void) umask( umask( 077 ) | 027 );
+ // ifd = open("test.log", O_RDONLY);
+ ifd = 0;
+ fcntl(ifd, F_SETFL, O_NONBLOCK);
+
+ /* We fail here if the transport can't be initialized because of some
+ * permanent (i.e. operator) problem, such as misspelled host name. */
+ rc = init_transport();
+ if (rc == ET_PERMANENT)
+ return 1;
+ queue = init_queue();
+ if (queue == NULL) {
+ syslog(LOG_ERR, "Error initializing audit record queue: %m");
+ return 1;
+ }
+
+#ifdef HAVE_LIBCAP_NG
+ // Drop capabilities
+ capng_clear(CAPNG_SELECT_BOTH);
+ if (config.local_port && config.local_port < 1024)
+ capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED,
+ CAP_NET_BIND_SERVICE);
+ capng_apply(CAPNG_SELECT_BOTH);
+#endif
+ syslog(LOG_NOTICE, "Audisp-remote started with queue_size: %zu",
+ q_queue_length(queue));
+
+ while (stop == 0) { //FIXME break out when socket is closed
+ fd_set rfd, wfd;
+ struct timeval tv;
+ char event[MAX_AUDIT_MESSAGE_LENGTH];
+ int n, fds = ifd + 1;
+
+ /* Load configuration */
+ if (hup)
+ reload_config();
+
+ if (dump)
+ dump_stats(queue);
+
+ /* Setup select flags */
+ FD_ZERO(&rfd);
+ FD_SET(ifd, &rfd); // input fd
+ FD_ZERO(&wfd);
+ if (sock > 0) {
+ // Setup socket to read acks from server
+ FD_SET(sock, &rfd); // remote socket
+ if (sock > ifd)
+ fds = sock + 1;
+ // If we have anything in the queue,
+ // find out if we can send it
+ if (q_queue_length(queue) && !suspend && transport_ok)
+ FD_SET(sock, &wfd);
+ }
+
+ if (config.heartbeat_timeout > 0) {
+ tv.tv_sec = config.heartbeat_timeout;
+ tv.tv_usec = 0;
+ n = select(fds, &rfd, &wfd, NULL, &tv);
+ } else
+ n = select(fds, &rfd, &wfd, NULL, NULL);
+ if (n < 0)
+ continue; // If here, we had some kind of problem
+
+ if ((config.heartbeat_timeout > 0) && n == 0 && !remote_ended) {
+ /* We attempt a hearbeat if select fails, which
+ * may give us more heartbeats than we need. This
+ * is safer than too few heartbeats. */
+ quiet = 1;
+ send_heartbeat();
+ quiet = 0;
+ continue;
+ }
+
+ // See if we got a shutdown message from the server
+ if (sock > 0 && FD_ISSET(sock, &rfd))
+ check_message();
+
+ // If we broke out due to one of these, cycle to start
+ if (hup != 0 || stop != 0)
+ continue;
+
+ // See if input fd is also set
+ if (FD_ISSET(ifd, &rfd)) {
+ do {
+ if (remote_fgets(event, sizeof(event), ifd)) {
+ if (!transport_ok && remote_ended &&
+ config.remote_ending_action ==
+ FA_RECONNECT) {
+ quiet = 1;
+ if (init_transport() ==
+ ET_SUCCESS)
+ remote_ended = 0;
+ quiet = 0;
+ }
+ /* Strip out EOE records */
+ if (*event == 't') {
+ if (strncmp(event,
+ "type=EOE", 8) == 0)
+ continue;
+ } else {
+ char *ptr = strchr(event, ' ');
+ if (ptr) {
+ ptr++;
+ if (strncmp(ptr,
+ "type=EOE",
+ 8) == 0)
+ continue;
+ } else
+ continue; //malformed
+ }
+ if (q_append(queue, event) != 0) {
+ if (errno == ENOSPC)
+ do_overflow_action();
+ else
+ queue_error();
+ }
+ } else if (remote_fgets_eof())
+ stop = 1;
+ } while (remote_fgets_more(sizeof(event)));
+ }
+ // See if output fd is also set
+ if (sock > 0 && FD_ISSET(sock, &wfd)) {
+ // If so, try to drain backlog
+ while (q_queue_length(queue) && !suspend &&
+ !stop && transport_ok)
+ send_one(queue);
+ }
+ }
+ if (sock >= 0) {
+ shutdown(sock, SHUT_RDWR);
+ close(sock);
+ }
+ free_config(&config);
+ q_len = q_queue_length(queue);
+ q_close(queue);
+ if (stop)
+ syslog(LOG_NOTICE, "audisp-remote is exiting on stop request, queue_size: %zu", q_len);
+
+ return q_len ? 1 : 0;
+}
+
+#ifdef USE_GSSAPI
+
+/* Communications under GSS is done by token exchanges. Each "token" may
+ contain a message, perhaps signed, perhaps encrypted. The messages within
+ are what we're interested in, but the network sees the tokens. The
+ protocol we use for transferring tokens is to send the length first,
+ four bytes MSB first, then the token data. We return nonzero on error. */
+static int recv_token(int s, gss_buffer_t tok)
+{
+ int ret;
+ unsigned char lenbuf[4];
+ unsigned int len;
+
+ ret = ar_read(s, (char *) lenbuf, 4);
+ if (ret < 0) {
+ syslog(LOG_ERR, "GSS-API error reading token length");
+ return -1;
+ } else if (!ret) {
+ return 0;
+ } else if (ret != 4) {
+ syslog(LOG_ERR, "GSS-API error reading token length");
+ return -1;
+ }
+
+ len = ( ((uint32_t)(lenbuf[0] & 0xFF) << 24)
+ | ((uint32_t)(lenbuf[1] & 0xFF) << 16)
+ | ((uint32_t)(lenbuf[2] & 0xFF) << 8)
+ | (uint32_t)(lenbuf[3] & 0xFF));
+
+ if (len > MAX_AUDIT_MESSAGE_LENGTH) {
+ syslog(LOG_ERR,
+ "GSS-API error: event length excedes MAX_AUDIT_LENGTH");
+ return -1;
+ }
+ tok->length = len;
+ tok->value = (char *) malloc(tok->length ? tok->length : 1);
+ if (tok->length && tok->value == NULL) {
+ syslog(LOG_ERR, "Out of memory allocating token data %zd %zx",
+ tok->length, tok->length);
+ return -1;
+ }
+
+ ret = ar_read(s, (char *) tok->value, tok->length);
+ if (ret < 0) {
+ syslog(LOG_ERR, "GSS-API error reading token data");
+ free(tok->value);
+ return -1;
+ } else if (ret != (int) tok->length) {
+ syslog(LOG_ERR, "GSS-API error reading token data");
+ free(tok->value);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Same here. */
+int send_token(int s, gss_buffer_t tok)
+{
+ int ret;
+ unsigned char lenbuf[4];
+ unsigned int len;
+
+ if (tok->length > 0xffffffffUL)
+ return -1;
+
+ len = tok->length;
+ lenbuf[0] = (len >> 24) & 0xff;
+ lenbuf[1] = (len >> 16) & 0xff;
+ lenbuf[2] = (len >> 8) & 0xff;
+ lenbuf[3] = len & 0xff;
+
+ ret = ar_write(s, (char *) lenbuf, 4);
+ if (ret < 0) {
+ syslog(LOG_ERR, "GSS-API error sending token length");
+ return -1;
+ } else if (ret != 4) {
+ syslog(LOG_ERR, "GSS-API error sending token length");
+ return -1;
+ }
+
+ ret = ar_write(s, tok->value, tok->length);
+ if (ret < 0) {
+ syslog(LOG_ERR, "GSS-API error sending token data");
+ return -1;
+ } else if (ret != (int) tok->length) {
+ syslog(LOG_ERR, "GSS-API error sending token data");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void gss_failure_2 (const char *msg, int status, int type)
+{
+ OM_uint32 message_context = 0;
+ OM_uint32 min_status = 0;
+ gss_buffer_desc status_string;
+
+ do {
+ gss_display_status (&min_status,
+ status,
+ type,
+ GSS_C_NO_OID,
+ &message_context,
+ &status_string);
+
+ syslog (LOG_ERR, "GSS error: %s: %s",
+ msg, (char *)status_string.value);
+
+ gss_release_buffer(&min_status, &status_string);
+ } while (message_context != 0);
+}
+
+static void gss_failure (const char *msg, int major_status, int minor_status)
+{
+ gss_failure_2 (msg, major_status, GSS_C_GSS_CODE);
+ if (minor_status)
+ gss_failure_2 (msg, minor_status, GSS_C_MECH_CODE);
+}
+
+#define KCHECK(x,f) if (x) { \
+ syslog (LOG_ERR, "krb5 error: %s in %s\n", krb5_get_error_message (kcontext, x), f); \
+ return -1; }
+
+#define KEYTAB_NAME "/etc/audisp/audisp-remote.key"
+#define CCACHE_NAME "MEMORY:audisp-remote"
+
+/* Each time we connect to the server, we negotiate a set of credentials and
+ a security context. To do this, we need our own credentials first. For
+ other Kerberos applications, the user will have called kinit (or otherwise
+ authenticated) first, but we don't have that luxury. So, we implement part
+ of kinit here. When our tickets expire, the usual close/open/retry logic
+ has us calling here again, where we re-init and get new tickets. */
+static int negotiate_credentials (void)
+{
+ gss_buffer_desc empty_token_buf = { 0, (void *) "" };
+ gss_buffer_t empty_token = &empty_token_buf;
+ gss_buffer_desc send_tok, recv_tok, *token_ptr;
+ gss_ctx_id_t *gss_context = &my_context;
+ gss_buffer_desc name_buf;
+ gss_name_t service_name_e;
+ OM_uint32 major_status, minor_status, init_sec_min_stat;
+ OM_uint32 ret_flags;
+
+ /* Getting an initial ticket is outside the scope of GSS, so
+ we use Kerberos calls here. */
+
+ int krberr;
+ krb5_context kcontext = NULL;
+ char *realm_name;
+ krb5_principal audit_princ;
+ krb5_ccache ccache = NULL;
+ krb5_creds my_creds;
+ krb5_get_init_creds_opt options;
+ krb5_keytab keytab = NULL;
+ const char *krb5_client_name;
+ char *slashptr;
+ char host_name[255];
+ struct stat st;
+ const char *key_file;
+
+ token_ptr = GSS_C_NO_BUFFER;
+ *gss_context = GSS_C_NO_CONTEXT;
+ recv_tok.value = NULL;
+
+ krberr = krb5_init_context (&kcontext);
+ KCHECK (krberr, "krb5_init_context");
+
+ if (config.krb5_key_file)
+ key_file = config.krb5_key_file;
+ else
+ key_file = KEYTAB_NAME;
+ unsetenv ("KRB5_KTNAME");
+ setenv ("KRB5_KTNAME", key_file, 1);
+
+ if (stat (key_file, &st) == 0) {
+ if ((st.st_mode & 07777) != 0400) {
+ if (!quiet)
+ syslog (LOG_ERR,
+ "%s is not mode 0400 (it's %#o) - compromised key?",
+ key_file, st.st_mode & 07777);
+ return -1;
+ }
+ if (st.st_uid != 0) {
+ if (!quiet)
+ syslog (LOG_ERR,
+ "%s is not owned by root (it's %d) - compromised key?",
+ key_file, st.st_uid);
+ return -1;
+ }
+ }
+
+ /* This looks up the default real (*our* realm) from
+ /etc/krb5.conf (or wherever) */
+ krberr = krb5_get_default_realm (kcontext, &realm_name);
+ KCHECK (krberr, "krb5_get_default_realm");
+
+ krb5_client_name = config.krb5_client_name ?
+ config.krb5_client_name : "auditd";
+ if (gethostname(host_name, sizeof(host_name)) != 0) {
+ if (!quiet)
+ syslog (LOG_ERR,
+ "gethostname: host name longer than %ld characters?",
+ sizeof (host_name));
+ return -1;
+ }
+
+ syslog (LOG_ERR, "kerberos principal: %s/%s@%s\n",
+ krb5_client_name, host_name, realm_name);
+ /* Encode our own "name" as auditd/remote@EXAMPLE.COM. */
+ krberr = krb5_build_principal (kcontext, &audit_princ,
+ strlen(realm_name), realm_name,
+ krb5_client_name, host_name, NULL);
+ KCHECK (krberr, "krb5_build_principal");
+
+ /* Locate our machine's key table, where our private key is
+ * held. */
+ krberr = krb5_kt_resolve (kcontext, key_file, &keytab);
+ KCHECK (krberr, "krb5_kt_resolve");
+
+ /* Identify a cache to hold the key in. The GSS wrappers look
+ up our credentials here. */
+ krberr = krb5_cc_resolve (kcontext, CCACHE_NAME, &ccache);
+ KCHECK (krberr, "krb5_cc_resolve");
+
+ setenv("KRB5CCNAME", CCACHE_NAME, 1);
+
+ memset(&my_creds, 0, sizeof(my_creds));
+ memset(&options, 0, sizeof(options));
+ krb5_get_init_creds_opt_set_address_list(&options, NULL);
+ krb5_get_init_creds_opt_set_forwardable(&options, 0);
+ krb5_get_init_creds_opt_set_proxiable(&options, 0);
+ krb5_get_init_creds_opt_set_tkt_life(&options, 24*60*60);
+
+ /* Load our credentials from the key table. */
+ krberr = krb5_get_init_creds_keytab(kcontext, &my_creds, audit_princ,
+ keytab, 0, NULL,
+ &options);
+ KCHECK (krberr, "krb5_get_init_creds_keytab");
+
+ /* Create the cache... */
+ krberr = krb5_cc_initialize(kcontext, ccache, audit_princ);
+ KCHECK (krberr, "krb5_cc_initialize");
+
+ /* ...and store our credentials in it. */
+ krberr = krb5_cc_store_cred(kcontext, ccache, &my_creds);
+ KCHECK (krberr, "krb5_cc_store_cred");
+
+ /* The GSS code now has a set of credentials for this program.
+ I.e. we know who "we" are. Now we talk to the server to
+ get its credentials and set up a security context for encryption. */
+ if (config.krb5_principal == NULL) {
+ const char *name = config.krb5_client_name ?
+ config.krb5_client_name : "auditd";
+ config.krb5_principal = (char *) malloc (strlen (name) + 1
+ + strlen (config.remote_server) + 1);
+ sprintf((char *)config.krb5_principal, "%s@%s",
+ name, config.remote_server);
+ }
+ slashptr = strchr (config.krb5_principal, '/');
+ if (slashptr)
+ *slashptr = '@';
+
+ name_buf.value = (char *)config.krb5_principal;
+ name_buf.length = strlen(name_buf.value) + 1;
+ major_status = gss_import_name(&minor_status, &name_buf,
+ (gss_OID) gss_nt_service_name, &service_name_e);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("importing name", major_status, minor_status);
+ return -1;
+ }
+
+ /* Someone has to go first. In this case, it's us. */
+ if (send_token(sock, empty_token) < 0) {
+ (void) gss_release_name(&minor_status, &service_name_e);
+ return -1;
+ }
+
+ /* The server starts this loop with the token we just sent
+ (the empty one). We start this loop with "no token". */
+ token_ptr = GSS_C_NO_BUFFER;
+ *gss_context = GSS_C_NO_CONTEXT;
+
+ do {
+ /* Give GSS a chance to digest what we have so far. */
+ major_status = gss_init_sec_context(&init_sec_min_stat,
+ GSS_C_NO_CREDENTIAL, gss_context,
+ service_name_e, NULL, REQ_FLAGS, 0,
+ NULL, /* no channel bindings */
+ token_ptr, NULL, /* ignore mech type */
+ &send_tok, &ret_flags, NULL); /* ignore time_rec */
+
+ if (token_ptr != GSS_C_NO_BUFFER)
+ free(recv_tok.value);
+
+ /* Send the server any tokens requested of us. */
+ if (send_tok.length != 0) {
+ if (send_token(sock, &send_tok) < 0) {
+ (void) gss_release_buffer(&minor_status,
+ &send_tok);
+ (void) gss_release_name(&minor_status,
+ &service_name_e);
+ return -1;
+ }
+ }
+ (void) gss_release_buffer(&minor_status, &send_tok);
+
+ if (major_status != GSS_S_COMPLETE
+ && major_status != GSS_S_CONTINUE_NEEDED) {
+ gss_failure("initializing context", major_status,
+ init_sec_min_stat);
+ (void) gss_release_name(&minor_status, &service_name_e);
+ if (*gss_context != GSS_C_NO_CONTEXT)
+ gss_delete_sec_context(&minor_status,
+ gss_context, GSS_C_NO_BUFFER);
+ return -1;
+ }
+
+ /* Now get any tokens the sever sends back. We use
+ these back at the top of the loop. */
+ if (major_status == GSS_S_CONTINUE_NEEDED) {
+ if (recv_token(sock, &recv_tok) < 0) {
+ (void) gss_release_name(&minor_status,
+ &service_name_e);
+ return -1;
+ }
+ token_ptr = &recv_tok;
+ }
+ } while (major_status == GSS_S_CONTINUE_NEEDED);
+
+ (void) gss_release_name(&minor_status, &service_name_e);
+
+#if 0
+ major_status = gss_inquire_context (&minor_status, &my_context, NULL,
+ &service_name_e, NULL, NULL,
+ NULL, NULL, NULL);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("inquiring target name", major_status, minor_status);
+ return -1;
+ }
+ major_status = gss_display_name(&minor_status, service_name_e,
+ &recv_tok, NULL);
+ gss_release_name(&minor_status, &service_name_e);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("displaying name", major_status, minor_status);
+ return -1;
+ }
+ syslog(LOG_INFO, "GSS-API Connected to: %s",
+ (char *)recv_tok.value);
+#endif
+ return 0;
+}
+#endif
+
+static int stop_sock(void)
+{
+ if (sock >= 0) {
+ shutdown(sock, SHUT_RDWR);
+ close(sock);
+ }
+ sock = -1;
+ transport_ok = 0;
+
+ return 0;
+}
+
+static int stop_transport(void)
+{
+ int rc;
+
+ switch (config.transport)
+ {
+ case T_TCP:
+ rc = stop_sock();
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+ return rc;
+}
+
+static int init_sock(void)
+{
+ int rc;
+ struct addrinfo *ai;
+ struct addrinfo hints;
+ char remote[BUF_SIZE];
+ int one=1;
+
+ if (sock >= 0) {
+ syslog(LOG_NOTICE, "socket already setup");
+ transport_ok = 1;
+ return ET_SUCCESS;
+ }
+ memset(&hints, '\0', sizeof(hints));
+ hints.ai_flags = AI_ADDRCONFIG|AI_NUMERICSERV;
+ hints.ai_socktype = SOCK_STREAM;
+ snprintf(remote, BUF_SIZE, "%u", config.port);
+ rc = getaddrinfo(config.remote_server, remote, &hints, &ai);
+ if (rc) {
+ if (!quiet)
+ syslog(LOG_ERR,
+ "Error looking up remote host: %s - exiting",
+ gai_strerror(rc));
+ if (rc == EAI_NONAME || rc == EAI_NODATA)
+ return ET_PERMANENT;
+ else
+ return ET_TEMPORARY;
+ }
+ sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (sock < 0) {
+ if (!quiet)
+ syslog(LOG_ERR, "Error creating socket: %s",
+ strerror(errno));
+ freeaddrinfo(ai);
+ return ET_TEMPORARY;
+ }
+
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof (int));
+
+ if (config.local_port != 0) {
+ struct sockaddr_in address;
+
+ memset (&address, 0, sizeof(address));
+ address.sin_family = AF_INET;
+ address.sin_port = htons(config.local_port);
+ address.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if (bind(sock, (struct sockaddr *)&address, sizeof(address))) {
+ if (!quiet)
+ syslog(LOG_ERR,
+ "Cannot bind local socket to port %d",
+ config.local_port);
+ stop_sock();
+ freeaddrinfo(ai);
+ return ET_TEMPORARY;
+ }
+
+ }
+ if (connect(sock, ai->ai_addr, ai->ai_addrlen)) {
+ if (!quiet)
+ syslog(LOG_ERR, "Error connecting to %s: %s",
+ config.remote_server, strerror(errno));
+ freeaddrinfo(ai);
+ stop_sock();
+ return ET_TEMPORARY;
+ }
+
+ freeaddrinfo(ai);
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof (int));
+
+ /* The idea here is to minimize the time between the message
+ and the ACK, assuming that individual messages are
+ infrequent enough that we can ignore the inefficiency of
+ sending the header and message in separate packets. */
+ if (config.format == F_MANAGED)
+ setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
+ (char *)&one, sizeof (int));
+
+#ifdef USE_GSSAPI
+ if (USE_GSS) {
+ if (negotiate_credentials ())
+ return ET_PERMANENT;
+ }
+#endif
+
+ transport_ok = 1;
+ syslog(LOG_NOTICE, "Connected to %s", config.remote_server);
+ return ET_SUCCESS;
+}
+
+static int init_transport(void)
+{
+ int rc;
+
+ switch (config.transport)
+ {
+ case T_TCP:
+ rc = init_sock();
+ // We set this so that it will retry the connection
+ if (rc == ET_TEMPORARY)
+ remote_ended = 1;
+ break;
+ default:
+ rc = ET_PERMANENT;
+ break;
+ }
+ return rc;
+}
+
+static int ar_write (int sk, const void *buf, int len)
+{
+ int rc = 0, r;
+ while (len > 0) {
+ do {
+ r = write(sk, buf, len);
+ } while (r < 0 && errno == EINTR);
+ if (r < 0) {
+ if (errno == EPIPE)
+ stop_sock();
+ return r;
+ }
+ if (r == 0)
+ break;
+ rc += r;
+ buf = (void *)((char *)buf + r);
+ len -= r;
+ }
+ return rc;
+}
+
+static int ar_read (int sk, void *buf, int len)
+{
+ int rc = 0, r, timeout = config.max_time_per_record * 1000;
+ struct pollfd pfd;
+
+ pfd.fd=sk;
+ pfd.events=POLLIN | POLLPRI | POLLHUP | POLLERR | POLLNVAL;
+ while (len > 0) {
+ do {
+ // reads can hang if cable is disconnected
+ int prc = poll(&pfd, (nfds_t) 1, timeout);
+ if (prc <= 0)
+ return -1;
+ r = read(sk, buf, len);
+ } while (r < 0 && errno == EINTR);
+ if (r < 0) {
+ if (errno == EPIPE)
+ stop_sock();
+ return r;
+ }
+ if (r == 0)
+ break;
+ rc += r;
+ buf = (void *)((char *)buf + r);
+ len -= r;
+ }
+ return rc;
+}
+
+static int relay_sock_ascii(const char *s, size_t len)
+{
+ int rc;
+
+ if (len == 0)
+ return 0;
+
+ if (!transport_ok) {
+ if (init_transport ())
+ return -1;
+ }
+
+ rc = ar_write(sock, s, len);
+ if (rc <= 0) {
+ stop = 1;
+ syslog(LOG_ERR,"Connection to %s closed unexpectedly - exiting",
+ config.remote_server);
+ return -1;
+ }
+
+ return 0;
+}
+
+#ifdef USE_GSSAPI
+
+/* Sending an encrypted message is pretty simple - wrap the message in
+ a token, and send the token. The server unwraps it to get the
+ original message. */
+static int send_msg_gss (unsigned char *header, const char *msg, uint32_t mlen)
+{
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc utok, etok;
+ int rc;
+
+ utok.length = AUDIT_RMW_HEADER_SIZE + mlen;
+ utok.value = malloc (utok.length);
+
+ memcpy (utok.value, header, AUDIT_RMW_HEADER_SIZE);
+
+ if (msg != NULL && mlen > 0)
+ memcpy (utok.value+AUDIT_RMW_HEADER_SIZE, msg, mlen);
+
+ major_status = gss_wrap (&minor_status,
+ my_context,
+ 1,
+ GSS_C_QOP_DEFAULT,
+ &utok,
+ NULL,
+ &etok);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("encrypting message", major_status, minor_status);
+ free (utok.value);
+ return -1;
+ }
+ rc = send_token (sock, &etok);
+ free (utok.value);
+ (void) gss_release_buffer(&minor_status, &etok);
+
+ return rc ? -1 : 0;
+}
+
+/* Likewise here. */
+static int recv_msg_gss (unsigned char *header, char *msg, uint32_t *mlen)
+{
+ OM_uint32 major_status, minor_status;
+ gss_buffer_desc utok, etok;
+ int hver, mver, rc;
+ uint32_t type, rlen, seq;
+
+ rc = recv_token (sock, &etok);
+ if (rc)
+ return -1;
+
+ major_status = gss_unwrap (&minor_status, my_context, &etok,
+ &utok, NULL, NULL);
+ if (major_status != GSS_S_COMPLETE) {
+ gss_failure("decrypting message", major_status, minor_status);
+ free (utok.value);
+ return -1;
+ }
+
+ if (utok.length < AUDIT_RMW_HEADER_SIZE) {
+ sync_error_handler ("message too short");
+ return -1;
+ }
+ memcpy (header, utok.value, AUDIT_RMW_HEADER_SIZE);
+
+ if (! AUDIT_RMW_IS_MAGIC (header, AUDIT_RMW_HEADER_SIZE)) {
+ sync_error_handler ("bad magic number");
+ return -1;
+ }
+
+ AUDIT_RMW_UNPACK_HEADER (header, hver, mver, type, rlen, seq);
+
+ if (rlen > MAX_AUDIT_MESSAGE_LENGTH) {
+ sync_error_handler ("message too long");
+ return -1;
+ }
+
+ memcpy (msg, utok.value+AUDIT_RMW_HEADER_SIZE, rlen);
+
+ *mlen = rlen;
+
+ return 0;
+}
+#endif
+
+static int send_msg_tcp (unsigned char *header, const char *msg, uint32_t mlen)
+{
+ int rc;
+
+ rc = ar_write(sock, header, AUDIT_RMW_HEADER_SIZE);
+ if (rc <= 0) {
+ syslog(LOG_ERR, "send to %s failed", config.remote_server);
+ return 1;
+ }
+
+ if (msg != NULL && mlen > 0) {
+ rc = ar_write(sock, msg, mlen);
+ if (rc <= 0) {
+ syslog(LOG_ERR, "send to %s failed",
+ config.remote_server);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int recv_msg_tcp (unsigned char *header, char *msg, uint32_t *mlen)
+{
+ int hver, mver, rc;
+ uint32_t type, rlen, seq;
+
+ rc = ar_read (sock, header, AUDIT_RMW_HEADER_SIZE);
+ if (rc < 16) {
+ syslog(LOG_ERR, "read from %s failed", config.remote_server);
+ return -1;
+ }
+
+ if (! AUDIT_RMW_IS_MAGIC (header, AUDIT_RMW_HEADER_SIZE)) {
+ /* FIXME: the right thing to do here is close the socket
+ * and start a new one. */
+ sync_error_handler ("bad magic number");
+ return -1;
+ }
+
+ AUDIT_RMW_UNPACK_HEADER (header, hver, mver, type, rlen, seq);
+
+ if (rlen > MAX_AUDIT_MESSAGE_LENGTH) {
+ sync_error_handler ("message too long");
+ return -1;
+ }
+
+ if (rlen > 0 && ar_read (sock, msg, rlen) < rlen) {
+ sync_error_handler ("ran out of data reading reply");
+ return -1;
+ }
+ return 0;
+}
+
+static int check_message_managed(void)
+{
+ unsigned char header[AUDIT_RMW_HEADER_SIZE];
+ int hver, mver;
+ uint32_t type, rlen, seq;
+ char msg[MAX_AUDIT_MESSAGE_LENGTH+1];
+
+#ifdef USE_GSSAPI
+ if (USE_GSS) {
+ if (recv_msg_gss (header, msg, &rlen)) {
+ stop_transport();
+ return -1;
+ }
+ } else
+#endif
+ if (recv_msg_tcp(header, msg, &rlen)) {
+ stop_transport();
+ return -1;
+ }
+
+ AUDIT_RMW_UNPACK_HEADER(header, hver, mver, type, rlen, seq);
+ msg[rlen] = 0;
+
+ if (type == AUDIT_RMW_TYPE_ENDING)
+ return remote_server_ending_handler(msg);
+ if (type == AUDIT_RMW_TYPE_DISKLOW)
+ return remote_disk_low_handler(msg);
+ if (type == AUDIT_RMW_TYPE_DISKFULL)
+ return remote_disk_full_handler(msg);
+ if (type == AUDIT_RMW_TYPE_DISKERROR)
+ return remote_disk_error_handler(msg);
+ return -1;
+}
+
+/* This is to check for async notification like server is shutting down */
+static int check_message(void)
+{
+ int rc;
+
+ switch (config.format)
+ {
+ case F_MANAGED:
+ rc = check_message_managed();
+ break;
+/* case F_ASCII:
+ rc = check_message_ascii();
+ break; */
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+static int relay_sock_managed(const char *s, size_t len)
+{
+ static int sequence_id = 1;
+ unsigned char header[AUDIT_RMW_HEADER_SIZE];
+ int hver, mver;
+ uint32_t type, rlen, seq;
+ char msg[MAX_AUDIT_MESSAGE_LENGTH+1];
+ int n_tries_this_message = 0;
+ time_t now, then = 0;
+
+ sequence_id ++;
+
+try_again:
+ time (&now);
+ if (then == 0)
+ then = now;
+
+ /* We want the first retry to be quick, in case the network
+ failed for some fail-once reason. In this case, it goes
+ "failure - reconnect - send". Only if this quick retry
+ fails do we start pausing between retries to prevent
+ swamping the local computer and the network. */
+ if (n_tries_this_message > 1)
+ sleep (config.network_retry_time);
+
+ if (n_tries_this_message > config.max_tries_per_record) {
+ network_failure_handler ("max retries exhausted");
+ return -1;
+ }
+ if ((now - then) > config.max_time_per_record) {
+ network_failure_handler ("max retry time exhausted");
+ return -1;
+ }
+
+ n_tries_this_message ++;
+
+ if (!transport_ok) {
+ if (init_transport ())
+ goto try_again;
+ }
+
+ type = (s != NULL) ? AUDIT_RMW_TYPE_MESSAGE : AUDIT_RMW_TYPE_HEARTBEAT;
+ AUDIT_RMW_PACK_HEADER (header, 0, type, len, sequence_id);
+
+#ifdef USE_GSSAPI
+ if (USE_GSS) {
+ if (send_msg_gss (header, s, len)) {
+ stop_transport ();
+ goto try_again;
+ }
+ } else
+#endif
+ if (send_msg_tcp (header, s, len)) {
+ stop_transport ();
+ goto try_again;
+ }
+
+#ifdef USE_GSSAPI
+ if (USE_GSS) {
+ if (recv_msg_gss (header, msg, &rlen)) {
+ stop_transport ();
+ goto try_again;
+ }
+ } else
+#endif
+ if (recv_msg_tcp (header, msg, &rlen)) {
+ stop_transport ();
+ goto try_again;
+ }
+
+ AUDIT_RMW_UNPACK_HEADER (header, hver, mver, type, rlen, seq);
+ msg[rlen] = 0;
+
+ /* Handle this first. It doesn't matter if seq compares or not
+ * since the other end is going down...deal with it. */
+ if (type == AUDIT_RMW_TYPE_ENDING)
+ return remote_server_ending_handler (msg);
+
+ if (seq != sequence_id) {
+ /* FIXME: should we read another header and
+ see if it matches? If so, we need to deal
+ with timeouts. */
+ if (sync_error_handler ("mismatched response"))
+ return -1;
+ stop_transport();
+ goto try_again;
+ }
+
+ /* Specific errors we know how to deal with. */
+ if (type == AUDIT_RMW_TYPE_DISKLOW)
+ return remote_disk_low_handler (msg);
+ if (type == AUDIT_RMW_TYPE_DISKFULL)
+ return remote_disk_full_handler (msg);
+ if (type == AUDIT_RMW_TYPE_DISKERROR)
+ return remote_disk_error_handler (msg);
+
+ /* Generic errors. */
+ if (type & AUDIT_RMW_TYPE_FATALMASK)
+ return generic_remote_error_handler (msg);
+ if (type & AUDIT_RMW_TYPE_WARNMASK)
+ return generic_remote_warning_handler (msg);
+
+ return 0;
+}
+
+static int relay_sock(const char *s, size_t len)
+{
+ int rc;
+
+ switch (config.format)
+ {
+ case F_MANAGED:
+ rc = relay_sock_managed (s, len);
+ break;
+ case F_ASCII:
+ rc = relay_sock_ascii (s, len);
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+/* Send audit event to remote system */
+static int relay_event(const char *s, size_t len)
+{
+ int rc;
+
+ switch (config.transport)
+ {
+ case T_TCP:
+ rc = relay_sock(s, len);
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
diff --git a/framework/src/audit/audisp/plugins/remote/audisp-remote.conf b/framework/src/audit/audisp/plugins/remote/audisp-remote.conf
new file mode 100644
index 00000000..70d8a992
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/audisp-remote.conf
@@ -0,0 +1,31 @@
+#
+# This file controls the configuration of the audit remote
+# logging subsystem, audisp-remote.
+#
+
+remote_server =
+port = 60
+##local_port =
+transport = tcp
+queue_file = /var/spool/audit/remote.log
+mode = immediate
+queue_depth = 2048
+format = managed
+network_retry_time = 1
+max_tries_per_record = 3
+max_time_per_record = 5
+heartbeat_timeout = 0
+
+network_failure_action = stop
+disk_low_action = ignore
+disk_full_action = ignore
+disk_error_action = syslog
+remote_ending_action = reconnect
+generic_error_action = syslog
+generic_warning_action = syslog
+overflow_action = syslog
+
+##enable_krb5 = no
+##krb5_principal =
+##krb5_client_name = auditd
+##krb5_key_file = /etc/audisp/audisp-remote.key
diff --git a/framework/src/audit/audisp/plugins/remote/audisp-remote.conf.5 b/framework/src/audit/audisp/plugins/remote/audisp-remote.conf.5
new file mode 100644
index 00000000..55efabfc
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/audisp-remote.conf.5
@@ -0,0 +1,211 @@
+.TH AUDISP-REMOTE.CONF: "5" "Mar 2011" "Red Hat" "System Administration Utilities"
+.SH NAME
+audisp-remote.conf \- the audisp-remote configuration file
+.SH DESCRIPTION
+\fBaudisp-remote.conf\fP is the file that controls the configuration of the audit remote logging subsystem. The options that are available are as follows:
+
+.TP
+.I remote_server
+This is a one word character string that is the remote server hostname or address that this plugin will send log information to. This can be the numeric address or a resolvable hostname.
+.TP
+.I port
+This option is an unsigned integer that indicates what port to connect to on the remote machine.
+.TP
+.I local_port
+This option is an unsigned integer that indicates what local port to
+connect from on the local machine. If unspecified (the default) or
+set to the word
+.I any
+then any available unpriviledged port is used. This is a security mechanism to prevent untrusted user space apps from injecting events into the audit daemon. You should set it to an unused port < 1024 to ensure that only privileged users can bind to that port. Then also set the tcp_client_ports in the aggregating auditd.conf file to match the ports that clients are sending from.
+.TP
+.I transport
+This parameter tells the remote logging app how to send events to the remote system. The only valid value right now is
+.IR tcp ".
+If set to
+.IR tcp ,
+the remote logging app will just make a normal clear text connection to the remote system. This is not used if kerberos is enabled.
+.TP
+.I mode
+This parameter tells the remote logging app what strategy to use getting records to the remote system. Valid values are
+.IR immediate ", and " forward " .
+If set to
+.IR immediate ,
+the remote logging app will attempt to send events immediately after getting them.
+.I forward
+means that it will store the events to disk and then attempt to send the records. If the connection cannot be made, it will queue records until it can connect to the remote system. The depth of the queue is controlled by the
+.I queue_depth
+option.
+.TP
+.I queue_file
+Path of a file used for the event queue if
+.I mode
+is set to \fIforward\fP. The default is \fB/var/spool/audit/remote.log\fP.
+.TP
+.I queue_depth
+This option is an unsigned integer that determines how many records can be buffered to disk or in memory before considering it to be a failure sending. This parameter affects the
+.I forward
+mode of the
+.I mode
+option and internal queueing for temporary network outtages. The default depth is 2048.
+.TP
+.I format
+This parameter tells the remote logging app what data format will be
+used for the messages sent over the network. The default is
+.I managed
+which adds some overhead to ensure each message is properly handled on
+the remote end, and to receive status messages from the remote server.
+If
+.I ascii
+is given instead, each message is a simple ASCII text line with no
+overhead at all. If
+.I mode
+is set to \fIforward\fP,
+.I format
+must be \fImanaged\fP.
+.TP
+.I network_retry_time
+The time, in seconds, between retries when a network error is
+detected. Note that this pause applies starting after the second
+attempt, so as to avoid unneeded delays if a reconnect is sufficient
+to fix the problem. The default is 1 second.
+.TP
+.I max_tries_per_record
+The maximum number of times an attempt is made to deliver each
+message. The minimum value is one, as even a completely successful
+delivery requires at least one try. If too many attempts are made,
+the network_failure_action action is performed. The default is 3.
+.TP
+.I max_time_per_record
+The maximum amount of time, in seconds, spent attempting to deliver
+each message. Note that both this and
+.I max_tries_per_record
+should be set, as each try may take a long time to time out. The
+default value is 5 seconds. If too much time is used on a message,
+the network_failure_action action is performed.
+.TP
+.I heartbeat_timeout
+This parameter determines how often in seconds the client should send a heartbeat event to the remote server. This is used to let both the client and server know that each end is alive and has not terminated in a way that it did not shutdown the connection uncleanly. This value must be coordinated with the server's
+.I tcp_client_max_idle
+setting. The default value is 0 which disables sending a heartbeat.
+.TP
+.I network_failure_action
+This parameter tells the system what action to take whenever there is an error
+detected when sending audit events to the remote system. Valid values are
+.IR ignore ", " syslog ", " exec ", " suspend ", " single ", " halt ", and " stop .
+If set to
+.IR ignore ,
+the remote logging app does nothing.
+.I Syslog
+means that it will issue a warning to syslog. This is the default.
+.I exec
+/path-to-script will execute the script. You cannot pass parameters to the script.
+.I Suspend
+will cause the remote logging app to stop sending records to the remote system. The logging app will still be alive. The
+.I single
+option will cause the remote logging app to put the computer system in single user mode. The
+.I stop
+option will cause the remote logging app to exit, but leave other plugins running. The
+.I halt
+option will cause the remote logging app to shutdown the computer system.
+.TP
+.I disk_low_action
+Likewise, this parameter tells the system what action to take if the
+remote end signals a disk low error. The default is to ignore it.
+.TP
+.I disk_full_action
+Likewise, this parameter tells the system what action to take if the
+remote end signals a disk full error. The default is to ignore it.
+.TP
+.I disk_error_action
+Likewise, this parameter tells the system what action to take if the
+remote end signals a disk error. The default is to log it to syslog.
+.TP
+.I remote_ending_action
+Likewise, this parameter tells the system what action to take if the
+remote end signals a disk error. This action has one additional option,
+.I reconnect
+which tells the remote plugin to attempt to reconnect to the server upon receipt of the next audit record. If it is unsuccessful, the audit record could be lost. The default is to reconnect.
+.TP
+.I generic_error_action
+Likewise, this parameter tells the system what action to take if the
+remote end signals an error we don't recognize. The default is to log
+it to syslog.
+.TP
+.I generic_warning_action
+Likewise, this parameter tells the system what action to take if the
+remote end signals a warning we don't recognize. The default is to
+log it to syslog.
+.TP
+.I queue_error_action
+Likewise, this parameter tells the system what action to take if there
+is a problem working with a local record queue. The default is to exit.
+.TP
+.I overflow_action
+This parameter tells the system what action to take if the
+internal event queue overflows. Valid values are
+.IR ignore ", " syslog ", " suspend ", " single ", and " halt " .
+If set to
+.IR ignore ,
+the remote logging app does nothing.
+.I Syslog
+means that it will issue a warning to syslog. This is the default.
+.I Suspend
+will cause the remote logging app to stop sending records to the remote system. The logging app will still be alive. The
+.I single
+option will cause the remote logging app to put the computer system in single user mode. The
+.I halt
+option will cause the remote logging app to shutdown the computer system.
+.TP
+.I enable_krb5
+If set to "yes", Kerberos 5 will be used for authentication and
+encryption. Default is "no". Note that encryption can only be used
+with managed connections, not plain ASCII.
+.TP
+.I krb5_principal
+If specified, This is the expected principal for the server. The
+client and server will use the specified principal to negotiate the
+encryption. The format for the
+.I krb5_principal
+is like somename/hostname, see the auditd.conf man page for
+details. If not specified, the krb5_client_name and remote_server values
+are used.
+.TP
+.I krb5_client_name
+This specifies the name portion of the client's own principal. If
+unspecified, the default is "auditd". The remainder of the principal
+will consist of the host's fully qualified domain name and the default
+Kerberos realm, like this:
+.I auditd/host14.example.com@EXAMPLE.COM
+(assuming you gave "auditd" as the krb_client_name). Note that the
+client and server must have the same principal name and realm.
+.TP
+.I krb5_key_file
+Location of the key for this client's principal.
+Note that the key file must be owned by root and mode 0400.
+The default is
+.I /etc/audisp/audisp-remote.key
+
+
+.SH "NOTES"
+Specifying a local port may make it difficult to restart the audit
+subsystem due to the previous connection being in a TIME_WAIT state,
+if you're reconnecting to and from the same hosts and ports as before.
+
+The network failure logic works as follows: The first attempt to
+deliver normally "just works". If it doesn't, a second attempt is
+immediately made, perhaps after reconnecting to the server. If
+the second attempt also fails,
+.I audispd-remote
+pauses for the configured time and tries again. It continues to pause
+and retry until either too many attempts have been made or the allowed
+time expires. Note that these times govern the maximum amount of time
+the remote server is allowed in order to reboot, if you want to
+maintain logging across a reboot.
+
+.SH "SEE ALSO"
+.BR audispd (8),
+.BR audisp-remote(8),
+.BR auditd.conf(5).
+.SH AUTHOR
+Steve Grubb
+
diff --git a/framework/src/audit/audisp/plugins/remote/notes.txt b/framework/src/audit/audisp/plugins/remote/notes.txt
new file mode 100644
index 00000000..1cd46193
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/notes.txt
@@ -0,0 +1,31 @@
+The queue data structure can keep data only in memory, only on disk
+(writing it to disk and reading from disk), or in both (writing everything
+to disk, but reading from disk only data stored in a previous run).
+audisp-remote will use the last option for performance.
+
+The queue file format starts with a fixed header, followed by an array
+of slots for strings. Due to the fixed size of each slot the file format
+is rather inefficient, but it is also very simple.
+
+The file is preallocated and the string slots will be aligned to a 4KB
+boundary, so it should be necessary to only write one block to disk
+when audisp-remote receives a (short) audit record.
+
+With the default queue size of 200 items the file will be about 2.4
+megabytes large, which is probably not really worth worrying about.
+
+If necessary, the space utilization could be improved by storing strings
+consecutively instead of using pre-arranged slots.
+
+The queue file format is intended to be resilient against unexpected
+termination of the process, and should be resilient against unexpected
+system crash as long as the OS does not reorder writes (the string data
+is written before the header that indicates that it is present) - but
+ultimately resiliency against such failures is limited by other
+links in the audit record transmission chain - if the record is lost
+within auditd or audispd, having a resilient queue file format does
+not help; audit records generated within the kernel are necessarily
+lost if the system crashes before they are read by auditd because
+the kernel will not be able to regenerate/retransmit them after the next
+boot.
+
diff --git a/framework/src/audit/audisp/plugins/remote/queue.c b/framework/src/audit/audisp/plugins/remote/queue.c
new file mode 100644
index 00000000..971e4e46
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/queue.c
@@ -0,0 +1,574 @@
+/* queue.c - a string queue implementation
+ * Copyright 2009, 2011 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Authors:
+ * Steve Grubb <sgrubb@redhat.com>
+ * Miloslav Trmač <mitr@redhat.com>
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "queue.h"
+
+struct queue
+{
+ int flags; /* Q_* */
+ int fd; /* -1 if !Q_IN_FILE */
+ /* NULL if !Q_IN_MEMORY. [i] contains a memory copy of the queue entry
+ "i", if known - it may be NULL even if entry exists. */
+ unsigned char **memory;
+ size_t num_entries;
+ size_t entry_size;
+ size_t queue_head;
+ size_t queue_length;
+ unsigned char buffer[]; /* Used only locally within q_peek() */
+};
+
+/* Infrastructure */
+
+/* Compile-time expression verification */
+#define verify(E) do { \
+ char verify__[(E) ? 1 : -1]; \
+ (void)verify__; \
+ } while (0)
+
+/* Like pread(), except that it handles partial reads, and returns 0 on
+ success. */
+static int full_pread(int fd, void *buf, size_t size, off_t offset)
+{
+ while (size != 0) {
+ ssize_t run, res;
+
+ if (size > SSIZE_MAX)
+ run = SSIZE_MAX;
+ else
+ run = size;
+ res = pread(fd, buf, run, offset);
+ if (res < 0)
+ return -1;
+ if (res == 0) {
+ errno = ENXIO; /* Any better value? */
+ return -1;
+ }
+ buf = (unsigned char *)buf + res;
+ size -= res;
+ offset += res;
+ }
+ return 0;
+}
+
+/* Like pwrite(), except that it handles partial writes, and returns 0 on
+ success. */
+static int full_pwrite(int fd, const void *buf, size_t size, off_t offset)
+{
+ while (size != 0) {
+ ssize_t run, res;
+
+ if (size > SSIZE_MAX)
+ run = SSIZE_MAX;
+ else
+ run = size;
+ res = pwrite(fd, buf, run, offset);
+ if (res < 0)
+ return -1;
+ if (res == 0) {
+ errno = ENXIO; /* Any better value? */
+ return -1;
+ }
+ buf = (const unsigned char *)buf + res;
+ size -= res;
+ offset += res;
+ }
+ return 0;
+}
+
+/* File format and utilities */
+
+/* The mutable part of struct file_header */
+struct fh_state {
+ uint32_t queue_head; /* 0-based index of the first non-empty entry */
+ uint32_t queue_length; /* [0, num_entries] */
+};
+
+/* All integer values are in network byte order (big endian) */
+struct file_header
+{
+ uint8_t magic[14]; /* See fh_magic below */
+ uint8_t version; /* File format version, see FH_VERSION* below */
+ uint8_t reserved; /* Must be 0 */
+ /* Total file size is (num_entries + 1) * entry_size. This must fit
+ into SIZE_MAX because the "len" parameter of posix_fallocate has
+ a size_t type. */
+ uint32_t num_entries; /* Total number of entries allocated */
+ uint32_t entry_size;
+ struct fh_state s;
+};
+
+/* Contains a '\0' byte to unambiguously mark the file as a binary file. */
+static const uint8_t fh_magic[14] = "\0audisp-remote";
+#define FH_VERSION_0 0x00
+
+/* Return file position for ENTRY in Q */
+static size_t entry_offset (const struct queue *q, size_t entry)
+{
+ return (entry + 1) * q->entry_size;
+}
+
+/* Synchronize Q if required and return 0.
+ On error, return -1 and set errno. */
+static int q_sync(struct queue *q)
+{
+ if ((q->flags & Q_SYNC) == 0)
+ return 0;
+ return fdatasync(q->fd);
+}
+
+/* Sync file's fh_state with Q, q_sync (Q), and return 0.
+ On error, return -1 and set errno. */
+static int sync_fh_state (struct queue *q)
+{
+ struct fh_state s;
+
+ if (q->fd == -1)
+ return 0;
+
+ s.queue_head = htonl(q->queue_head);
+ s.queue_length = htonl(q->queue_length);
+ if (full_pwrite(q->fd, &s, sizeof(s), offsetof(struct file_header, s))
+ != 0)
+ return -1;
+ return q_sync(q);
+}
+
+/* Queue implementation */
+
+/* Open PATH for Q, update Q from it, and return 0.
+ On error, return -1 and set errno; Q->fd may be set even on error. */
+static int q_open_file(struct queue *q, const char *path)
+{
+ int open_flags, fd_flags;
+ struct stat st;
+ struct file_header fh;
+
+ open_flags = O_RDWR;
+ if ((q->flags & Q_CREAT) != 0)
+ open_flags |= O_CREAT;
+ if ((q->flags & Q_EXCL) != 0)
+ open_flags |= O_EXCL;
+ q->fd = open(path, open_flags, S_IRUSR | S_IWUSR);
+ if (q->fd == -1)
+ return -1;
+
+ fd_flags = fcntl(q->fd, F_GETFD);
+ if (fd_flags < 0)
+ return -1;
+ if (fcntl(q->fd, F_SETFD, fd_flags | FD_CLOEXEC) == -1)
+ return -1;
+
+ /* File locking in POSIX is pretty much broken... let's hope nobody
+ attempts to open a single file twice within the same process.
+ open() above has initialized the file offset to 0, so the lockf()
+ below affects the whole file. */
+ if (lockf(q->fd, F_TLOCK, 0) != 0) {
+ if (errno == EACCES || errno == EAGAIN)
+ errno = EBUSY; /* This makes more sense... */
+ return -1;
+ }
+
+ if (fstat(q->fd, &st) != 0)
+ return -1;
+ if (st.st_size == 0) {
+ verify(sizeof(fh.magic) == sizeof(fh_magic));
+ memcpy(fh.magic, fh_magic, sizeof(fh.magic));
+ fh.version = FH_VERSION_0;
+ fh.reserved = 0;
+ fh.num_entries = htonl(q->num_entries);
+ fh.entry_size = htonl(q->entry_size);
+ fh.s.queue_head = htonl(0);
+ fh.s.queue_length = htonl(0);
+ if (full_pwrite(q->fd, &fh, sizeof(fh), 0) != 0)
+ return -1;
+ if (q_sync(q) != 0)
+ return -1;
+#ifdef HAVE_POSIX_FALLOCATE
+ if (posix_fallocate(q->fd, 0,
+ (q->num_entries + 1) * q->entry_size) != 0)
+ return -1;
+#endif
+ } else {
+ uint32_t file_entries;
+ if (full_pread(q->fd, &fh, sizeof(fh), 0) != 0)
+ return -1;
+ if (memcmp(fh.magic, fh_magic, sizeof(fh.magic)) != 0
+ || fh.version != FH_VERSION_0 || fh.reserved != 0
+ || fh.entry_size != htonl(q->entry_size)) {
+ errno = EINVAL;
+ return -1;
+ }
+ file_entries = ntohl(fh.num_entries);
+ if (file_entries > SIZE_MAX / q->entry_size - 1
+ || ((uintmax_t)st.st_size
+ != (file_entries + 1) * q->entry_size)) {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+ /* Note that this may change q->num_entries! */
+ q->num_entries = ntohl(fh.num_entries);
+ q->queue_head = ntohl(fh.s.queue_head);
+ q->queue_length = ntohl(fh.s.queue_length);
+ if (q->queue_head >= q->num_entries
+ || q->queue_length > q->num_entries) {
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+}
+
+/* Like q_open(), but does not handle Q_RESIZE, and NUM_ENTRIES is only used
+ when creating a new file. */
+static struct queue *q_open_no_resize(int q_flags, const char *path,
+ size_t num_entries, size_t entry_size)
+{
+ struct queue *q;
+ int saved_errno;
+
+ if ((q_flags & (Q_IN_MEMORY | Q_IN_FILE)) == 0) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (num_entries == 0 || num_entries > UINT32_MAX
+ || entry_size < 1 /* for trailing NUL */
+ || entry_size < sizeof(struct file_header) /* for Q_IN_FILE */
+ /* to allocate "struct queue" including its buffer*/
+ || entry_size > UINT32_MAX - sizeof(struct queue)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (entry_size > SIZE_MAX
+ || num_entries > SIZE_MAX / entry_size - 1 /* for Q_IN_FILE */
+ || num_entries > SIZE_MAX / sizeof(*q->memory)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ q = malloc(sizeof(*q) + entry_size);
+ if (q == NULL)
+ return NULL;
+ q->flags = q_flags;
+ q->fd = -1;
+ q->memory = NULL;
+ q->num_entries = num_entries;
+ q->entry_size = entry_size;
+ q->queue_head = 0;
+ q->queue_length = 0;
+
+ if ((q_flags & Q_IN_MEMORY) != 0) {
+ size_t sz = num_entries * sizeof(*q->memory);
+
+ q->memory = malloc(sz);
+ if (q->memory == NULL)
+ goto err;
+ memset(q->memory, 0, sz);
+ }
+
+ if ((q_flags & Q_IN_FILE) != 0 && q_open_file(q, path) != 0)
+ goto err;
+
+ return q;
+
+err:
+ saved_errno = errno;
+ if (q->fd != -1)
+ close(q->fd);
+ free(q->memory);
+ free(q);
+ errno = saved_errno;
+ return NULL;
+}
+
+void q_close(struct queue *q)
+{
+ if (q->fd != -1)
+ close(q->fd); /* Also releases the file lock */
+ if (q->memory != NULL) {
+ size_t i;
+
+ for (i = 0; i < q->num_entries; i++)
+ free(q->memory[i]);
+ free(q->memory);
+ }
+ free(q);
+}
+
+/* Internal use only: add DATA to Q, but don't update fh_state. */
+static int q_append_no_sync_fh_state(struct queue *q, const char *data)
+{
+ size_t data_size, entry_index;
+ unsigned char *copy;
+
+ if (q->queue_length == q->num_entries) {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ data_size = strlen(data) + 1;
+ if (data_size > q->entry_size) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ entry_index = (q->queue_head + q->queue_length) % q->num_entries;
+ if (q->memory != NULL) {
+ if (q->memory[entry_index] != NULL) {
+ errno = EIO; /* This is _really_ unexpected. */
+ return -1;
+ }
+ copy = malloc(data_size);
+ if (copy == NULL)
+ return -1;
+ memcpy(copy, data, data_size);
+ } else
+ copy = NULL;
+
+ if (q->fd != -1) {
+ size_t offset;
+
+ offset = entry_offset(q, entry_index);
+ if (full_pwrite(q->fd, data, data_size, offset) != 0) {
+ int saved_errno;
+
+ saved_errno = errno;
+ if (copy != NULL)
+ free(copy);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ if (copy != NULL)
+ q->memory[entry_index] = copy;
+
+ q->queue_length++;
+
+ return 0;
+}
+
+int q_append(struct queue *q, const char *data)
+{
+ int r;
+
+ r = q_append_no_sync_fh_state(q, data);
+ if (r != 0)
+ return r;
+
+ return sync_fh_state(q); /* Calls q_sync() */
+}
+
+int q_peek(struct queue *q, char *buf, size_t size)
+{
+ const unsigned char *data;
+ size_t data_size;
+
+ if (q->queue_length == 0)
+ return 0;
+
+ if (q->memory != NULL && q->memory[q->queue_head] != NULL) {
+ data = q->memory[q->queue_head];
+ data_size = strlen((char *)data) + 1;
+ } else if (q->fd != -1) {
+ const unsigned char *end;
+
+ if (full_pread(q->fd, q->buffer, q->entry_size,
+ entry_offset(q, q->queue_head)) != 0)
+ return -1;
+ data = q->buffer;
+ end = memchr(q->buffer, '\0', q->entry_size);
+ if (end == NULL) {
+ /* FIXME: silently drop this entry? */
+ errno = EBADMSG;
+ return -1;
+ }
+ data_size = (end - data) + 1;
+
+ if (q->memory != NULL) {
+ unsigned char *copy;
+
+ copy = malloc(data_size);
+ if (copy != NULL) { /* Silently ignore failures. */
+ memcpy(copy, data, data_size);
+ q->memory[q->queue_head] = copy;
+ }
+ }
+ } else {
+ errno = EIO; /* This is _really_ unexpected. */
+ return -1;
+ }
+
+ if (size < data_size) {
+ errno = ERANGE;
+ return -1;
+ }
+ memcpy(buf, data, data_size);
+ return data_size;
+}
+
+/* Internal use only: drop head of Q, but don't write this into the file */
+static int q_drop_head_memory_only(struct queue *q)
+{
+ if (q->queue_length == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (q->memory != NULL) {
+ free(q->memory[q->queue_head]);
+ q->memory[q->queue_head] = NULL;
+ }
+
+ q->queue_head++;
+ if (q->queue_head == q->num_entries)
+ q->queue_head = 0;
+ q->queue_length--;
+ return 0;
+}
+
+int q_drop_head(struct queue *q)
+{
+ int r;
+
+ r = q_drop_head_memory_only(q);
+ if (r != 0)
+ return r;
+
+ return sync_fh_state(q); /* Calls q_sync() */
+}
+
+size_t q_queue_length(const struct queue *q)
+{
+ return q->queue_length;
+}
+
+struct queue *q_open(int q_flags, const char *path, size_t num_entries,
+ size_t entry_size)
+{
+ struct queue *q, *q2;
+ char *tmp_path, *buf;
+ size_t path_len;
+ int saved_errno, fd;
+
+ q = q_open_no_resize(q_flags, path, num_entries, entry_size);
+ if (q == NULL || q->num_entries == num_entries)
+ return q;
+
+ if ((q->flags & Q_RESIZE) == 0) {
+ saved_errno = EINVAL;
+ goto err_errno_q;
+ }
+
+ if (q->queue_length > num_entries) {
+ saved_errno = ENOSPC;
+ goto err_errno_q;
+ }
+
+ buf = malloc(entry_size);
+ if (buf == NULL) {
+ saved_errno = errno;
+ goto err_errno_q;
+ }
+
+ path_len = strlen(path);
+ tmp_path = malloc(path_len + 7);
+ if (tmp_path == NULL) {
+ saved_errno = errno;
+ goto err_errno_buf;
+ }
+ memcpy(tmp_path, path, path_len);
+ memcpy(tmp_path + path_len, "XXXXXX", 7);
+ /* We really want tmpnam() here (safe due to the Q_EXCL below), but gcc
+ warns on any use of tmpnam(). */
+ fd = mkstemp(tmp_path);
+ if (fd == -1) {
+ saved_errno = errno;
+ goto err_errno_tmp_path;
+ }
+ if (close(fd) != 0 || unlink(tmp_path) != 0) {
+ saved_errno = errno;
+ goto err_errno_tmp_file;
+ }
+
+ q2 = q_open_no_resize(q_flags | Q_CREAT | Q_EXCL, tmp_path, num_entries,
+ entry_size);
+ if (q2 == NULL) {
+ saved_errno = errno;
+ goto err_errno_tmp_file;
+ }
+ if (q2->num_entries != num_entries) {
+ errno = EIO; /* This is _really_ unexpected. */
+ goto err_q2;
+ }
+
+ for (;;) {
+ int r;
+
+ r = q_peek(q, buf, entry_size);
+ if (r == 0)
+ break;
+ if (r < 0)
+ goto err_q2;
+
+ if (q_append_no_sync_fh_state(q2, buf) != 0)
+ goto err_q2;
+ if (q_drop_head_memory_only(q) != 0)
+ goto err_q2;
+ }
+ if (sync_fh_state(q2) != 0)
+ goto err_q2;
+
+ if (rename(tmp_path, path) != 0)
+ goto err_q2;
+
+ q_close(q);
+ free(buf);
+ free(tmp_path);
+ return q2;
+
+err_q2:
+ saved_errno = errno;
+ q_close(q2);
+err_errno_tmp_file:
+ unlink(tmp_path);
+err_errno_tmp_path:
+ free(tmp_path);
+err_errno_buf:
+ free(buf);
+err_errno_q:
+ q_close(q);
+ errno = saved_errno;
+ return NULL;
+}
diff --git a/framework/src/audit/audisp/plugins/remote/queue.h b/framework/src/audit/audisp/plugins/remote/queue.h
new file mode 100644
index 00000000..5f5ef647
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/queue.h
@@ -0,0 +1,66 @@
+/* queue.h -- a queue abstraction
+ * Copyright 2009, 2011 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Authors:
+ * Steve Grubb <sgrubb@redhat.com>
+ * Miloslav Trmač <mitr@redhat.com>
+ */
+
+#ifndef QUEUE_HEADER
+#define QUEUE_HEADER
+
+#include <sys/types.h>
+
+struct queue;
+
+enum {
+ // Queue storage. Both options can be set at the same time.
+ Q_IN_MEMORY = 1 << 0, // Keep a copy of the queue in memory
+ Q_IN_FILE = 1 << 1, // Store the queue in a file
+ // Other flags for use with Q_IN_FILE
+ Q_CREAT = 1 << 2, // Create the queue if it does not exist
+ Q_EXCL = 1 << 3, // With Q_CREAT, don't open an existing queue
+ Q_SYNC = 1 << 4, // fdatasync() after each operation
+ Q_RESIZE = 1 << 5, // resize the queue if needed
+};
+
+/* Open a queue using Q_FLAGS and return it. If Q_IN_FILE: use PATH for the
+ * file, NUM_ENTRIES must be the same for all users of the file unless Q_RESIZE
+ * is set. ENTRY_SIZE is the maximum length of a stored string, including the
+ * trailing NUL. If Q_IN_FILE, it must be the same for all users of the file.
+ * On error, return NULL and set errno. */
+struct queue *q_open(int q_flags, const char *path, size_t num_entries,
+ size_t entry_size);
+/* Close Q. */
+void q_close(struct queue *q);
+
+/* Add DATA to tail of Q. Return 0 on success, -1 on error and set errno. */
+int q_append(struct queue *q, const char *data);
+
+/* Peek at head of Q, storing it into BUF of SIZE. Return 1 if an entry
+ * exists, 0 if queue is empty. On error, return -1 and set errno. */
+int q_peek(struct queue *q, char *buf, size_t size);
+
+/* Drop head of Q and return 0. On error, return -1 and set errno. */
+int q_drop_head(struct queue *q);
+
+/* Return the number of entries in Q. */
+size_t q_queue_length(const struct queue *q);
+
+#endif
+
diff --git a/framework/src/audit/audisp/plugins/remote/remote-config.c b/framework/src/audit/audisp/plugins/remote/remote-config.c
new file mode 100644
index 00000000..841a1ed3
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/remote-config.c
@@ -0,0 +1,780 @@
+/* remote-config.c --
+ * Copyright 2008,2009,2011,2015 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#include "config.h"
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <ctype.h>
+#include <limits.h>
+#include "remote-config.h"
+
+/* Local prototypes */
+struct nv_pair
+{
+ const char *name;
+ const char *value;
+ const char *option;
+};
+
+struct kw_pair
+{
+ const char *name;
+ int (*parser)(struct nv_pair *, int, remote_conf_t *);
+ int max_options;
+};
+
+struct nv_list
+{
+ const char *name;
+ int option;
+};
+
+static char *get_line(FILE *f, char *buf);
+static int nv_split(char *buf, struct nv_pair *nv);
+static const struct kw_pair *kw_lookup(const char *val);
+static int server_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int port_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int local_port_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int transport_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int mode_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int queue_file_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int depth_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int format_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int heartbeat_timeout_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int enable_krb5_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int krb5_principal_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int krb5_client_name_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int krb5_key_file_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int network_retry_time_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int max_tries_per_record_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int max_time_per_record_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+#define AP(x) static int x##_action_parser(struct nv_pair *nv, int line, \
+ remote_conf_t *config);
+AP(network_failure)
+AP(disk_low)
+AP(disk_full)
+AP(disk_error)
+AP(generic_error)
+AP(generic_warning)
+AP(queue_error)
+#undef AP
+static int remote_ending_action_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int overflow_action_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config);
+static int sanity_check(remote_conf_t *config, const char *file);
+
+static const struct kw_pair keywords[] =
+{
+ {"remote_server", server_parser, 0 },
+ {"port", port_parser, 0 },
+ {"local_port", local_port_parser, 0 },
+ {"transport", transport_parser, 0 },
+ {"mode", mode_parser, 0 },
+ {"queue_file", queue_file_parser, 0 },
+ {"queue_depth", depth_parser, 0 },
+ {"format", format_parser, 0 },
+ {"network_retry_time", network_retry_time_parser, 0 },
+ {"max_tries_per_record", max_tries_per_record_parser, 0 },
+ {"max_time_per_record", max_time_per_record_parser, 0 },
+ {"heartbeat_timeout", heartbeat_timeout_parser, 0 },
+ {"enable_krb5", enable_krb5_parser, 0 },
+ {"krb5_principal", krb5_principal_parser, 0 },
+ {"krb5_client_name", krb5_client_name_parser, 0 },
+ {"krb5_key_file", krb5_key_file_parser, 0 },
+ {"network_failure_action", network_failure_action_parser, 1 },
+ {"disk_low_action", disk_low_action_parser, 1 },
+ {"disk_full_action", disk_full_action_parser, 1 },
+ {"disk_error_action", disk_error_action_parser, 1 },
+ {"remote_ending_action", remote_ending_action_parser, 1 },
+ {"generic_error_action", generic_error_action_parser, 1 },
+ {"generic_warning_action", generic_warning_action_parser, 1 },
+ {"queue_error_action", queue_error_action_parser, 1 },
+ {"overflow_action", overflow_action_parser, 1 },
+ { NULL, NULL, 0 }
+};
+
+static const struct nv_list transport_words[] =
+{
+ {"tcp", T_TCP },
+ { NULL, 0 }
+};
+
+static const struct nv_list mode_words[] =
+{
+ {"immediate", M_IMMEDIATE },
+ {"forward", M_STORE_AND_FORWARD },
+ { NULL, 0 }
+};
+
+static const struct nv_list fail_action_words[] =
+{
+ {"ignore", FA_IGNORE },
+ {"syslog", FA_SYSLOG },
+ {"exec", FA_EXEC },
+ {"suspend", FA_SUSPEND },
+ {"single", FA_SINGLE },
+ {"halt", FA_HALT },
+ {"stop", FA_STOP },
+ { NULL, 0 }
+};
+
+static const struct nv_list overflow_action_words[] =
+{
+ {"ignore", OA_IGNORE },
+ {"syslog", OA_SYSLOG },
+ {"suspend", OA_SUSPEND },
+ {"single", OA_SINGLE },
+ {"halt", OA_HALT },
+ { NULL, 0 }
+};
+
+static const struct nv_list format_words[] =
+{
+ {"ascii", F_ASCII },
+ {"managed", F_MANAGED },
+ { NULL, 0 }
+};
+
+#ifdef USE_GSSAPI
+static const struct nv_list enable_krb5_values[] =
+{
+ {"yes", 1 },
+ {"no", 0 },
+ { NULL, 0 }
+};
+#endif
+
+/*
+ * Set everything to its default value
+*/
+void clear_config(remote_conf_t *config)
+{
+ config->remote_server = NULL;
+ config->port = 60;
+ config->local_port = 0;
+ config->transport = T_TCP;
+ config->mode = M_IMMEDIATE;
+ config->queue_file = NULL;
+ config->queue_depth = 2048;
+ config->format = F_MANAGED;
+
+ config->network_retry_time = 1;
+ config->max_tries_per_record = 3;
+ config->max_time_per_record = 5;
+ config->heartbeat_timeout = 0;
+
+#define IA(x,f) config->x##_action = f; config->x##_exe = NULL
+ IA(network_failure, FA_STOP);
+ IA(disk_low, FA_IGNORE);
+ IA(disk_full, FA_IGNORE);
+ IA(disk_error, FA_SYSLOG);
+ IA(remote_ending, FA_RECONNECT);
+ IA(generic_error, FA_SYSLOG);
+ IA(generic_warning, FA_SYSLOG);
+ IA(queue_error, FA_STOP);
+#undef IA
+ config->overflow_action = OA_SYSLOG;
+
+ config->enable_krb5 = 0;
+ config->krb5_principal = NULL;
+ config->krb5_client_name = NULL;
+ config->krb5_key_file = NULL;
+}
+
+int load_config(remote_conf_t *config, const char *file)
+{
+ int fd, rc, mode, lineno = 1;
+ struct stat st;
+ FILE *f;
+ char buf[128];
+
+ clear_config(config);
+
+ /* open the file */
+ mode = O_RDONLY;
+ rc = open(file, mode);
+ if (rc < 0) {
+ if (errno != ENOENT) {
+ syslog(LOG_ERR, "Error opening %s (%s)", file,
+ strerror(errno));
+ return 1;
+ }
+ syslog(LOG_WARNING,
+ "Config file %s doesn't exist, skipping", file);
+ return 0;
+ }
+ fd = rc;
+
+ /* check the file's permissions: owned by root, not world writable,
+ * not symlink.
+ */
+ if (fstat(fd, &st) < 0) {
+ syslog(LOG_ERR, "Error fstat'ing config file (%s)",
+ strerror(errno));
+ close(fd);
+ return 1;
+ }
+ if (st.st_uid != 0) {
+ syslog(LOG_ERR, "Error - %s isn't owned by root",
+ file);
+ close(fd);
+ return 1;
+ }
+ if ((st.st_mode & S_IWOTH) == S_IWOTH) {
+ syslog(LOG_ERR, "Error - %s is world writable",
+ file);
+ close(fd);
+ return 1;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ syslog(LOG_ERR, "Error - %s is not a regular file",
+ file);
+ close(fd);
+ return 1;
+ }
+
+ /* it's ok, read line by line */
+ f = fdopen(fd, "rm");
+ if (f == NULL) {
+ syslog(LOG_ERR, "Error - fdopen failed (%s)",
+ strerror(errno));
+ close(fd);
+ return 1;
+ }
+
+ while (get_line(f, buf)) {
+ // convert line into name-value pair
+ const struct kw_pair *kw;
+ struct nv_pair nv;
+ rc = nv_split(buf, &nv);
+ switch (rc) {
+ case 0: // fine
+ break;
+ case 1: // not the right number of tokens.
+ syslog(LOG_ERR,
+ "Wrong number of arguments for line %d in %s",
+ lineno, file);
+ break;
+ case 2: // no '=' sign
+ syslog(LOG_ERR,
+ "Missing equal sign for line %d in %s",
+ lineno, file);
+ break;
+ default: // something else went wrong...
+ syslog(LOG_ERR,
+ "Unknown error for line %d in %s",
+ lineno, file);
+ break;
+ }
+ if (nv.name == NULL) {
+ lineno++;
+ continue;
+ }
+ if (nv.value == NULL) {
+ fclose(f);
+ return 1;
+ }
+
+ /* identify keyword or error */
+ kw = kw_lookup(nv.name);
+ if (kw->name == NULL) {
+ syslog(LOG_ERR,
+ "Unknown keyword \"%s\" in line %d of %s",
+ nv.name, lineno, file);
+ fclose(f);
+ return 1;
+ }
+
+ /* Check number of options */
+ if (kw->max_options == 0 && nv.option != NULL) {
+ syslog(LOG_ERR,
+ "Keyword \"%s\" has invalid option "
+ "\"%s\" in line %d of %s",
+ nv.name, nv.option, lineno, file);
+ fclose(f);
+ return 1;
+ }
+
+ /* dispatch to keyword's local parser */
+ rc = kw->parser(&nv, lineno, config);
+ if (rc != 0) {
+ fclose(f);
+ return 1; // local parser puts message out
+ }
+
+ lineno++;
+ }
+
+ fclose(f);
+ if (lineno > 1)
+ return sanity_check(config, file);
+ return 0;
+}
+
+static char *get_line(FILE *f, char *buf)
+{
+ if (fgets_unlocked(buf, 128, f)) {
+ /* remove newline */
+ char *ptr = strchr(buf, 0x0a);
+ if (ptr)
+ *ptr = 0;
+ return buf;
+ }
+ return NULL;
+}
+
+static int nv_split(char *buf, struct nv_pair *nv)
+{
+ /* Get the name part */
+ char *ptr, *saved;
+
+ nv->name = NULL;
+ nv->value = NULL;
+ nv->option = NULL;
+ ptr = strtok_r(buf, " ", &saved);
+ if (ptr == NULL)
+ return 0; /* If there's nothing, go to next line */
+ if (ptr[0] == '#')
+ return 0; /* If there's a comment, go to next line */
+ nv->name = ptr;
+
+ /* Check for a '=' */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr == NULL)
+ return 1;
+ if (strcmp(ptr, "=") != 0)
+ return 2;
+
+ /* get the value */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr == NULL)
+ return 1;
+ nv->value = ptr;
+
+ /* See if there's an option */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr) {
+ nv->option = ptr;
+
+ /* Make sure there's nothing else */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr)
+ return 1;
+ }
+
+ /* Everything is OK */
+ return 0;
+}
+
+static const struct kw_pair *kw_lookup(const char *val)
+{
+ int i = 0;
+ while (keywords[i].name != NULL) {
+ if (strcasecmp(keywords[i].name, val) == 0)
+ break;
+ i++;
+ }
+ return &keywords[i];
+}
+
+static int check_exe_name(const char *val, int line)
+{
+ struct stat buf;
+
+ if (val == NULL) {
+ syslog(LOG_ERR, "Executable path needed for line %d", line);
+ return -1;
+ }
+
+ if (*val != '/') {
+ syslog(LOG_ERR, "Absolute path needed for %s - line %d",
+ val, line);
+ return -1;
+ }
+
+ if (stat(val, &buf) < 0) {
+ syslog(LOG_ERR, "Unable to stat %s (%s) - line %d", val,
+ strerror(errno), line);
+ return -1;
+ }
+ if (!S_ISREG(buf.st_mode)) {
+ syslog(LOG_ERR, "%s is not a regular file - line %d", val,
+ line);
+ return -1;
+ }
+ if (buf.st_uid != 0) {
+ syslog(LOG_ERR, "%s is not owned by root - line %d", val,
+ line);
+ return -1;
+ }
+ if ((buf.st_mode & (S_IRWXU|S_IRWXG|S_IWOTH)) !=
+ (S_IRWXU|S_IRGRP|S_IXGRP)) {
+ syslog(LOG_ERR, "%s permissions should be 0750 - line %d", val,
+ line);
+ return -1;
+ }
+ return 0;
+}
+
+static int server_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ if (nv->value)
+ config->remote_server = strdup(nv->value);
+ else
+ config->remote_server = NULL;
+ return 0;
+}
+
+static int parse_uint (const struct nv_pair *nv, int line, unsigned int *valp,
+ unsigned int min, unsigned int max)
+{
+ const char *ptr = nv->value;
+ unsigned int i;
+
+ /* check that all chars are numbers */
+ for (i=0; ptr[i]; i++) {
+ if (!isdigit(ptr[i])) {
+ syslog(LOG_ERR,
+ "Value %s should only be numbers - line %d",
+ nv->value, line);
+ return 1;
+ }
+ }
+
+ /* convert to unsigned int */
+ errno = 0;
+ i = strtoul(nv->value, NULL, 10);
+ if (errno) {
+ syslog(LOG_ERR,
+ "Error converting string to a number (%s) - line %d",
+ strerror(errno), line);
+ return 1;
+ }
+ /* Check its range */
+ if (min != 0 && i < (int)min) {
+ syslog(LOG_ERR,
+ "Error - converted number (%s) is too small - line %d",
+ nv->value, line);
+ return 1;
+ }
+ if (max != 0 && i > max) {
+ syslog(LOG_ERR,
+ "Error - converted number (%s) is too large - line %d",
+ nv->value, line);
+ return 1;
+ }
+ *valp = (unsigned int)i;
+ return 0;
+}
+
+static int port_parser(struct nv_pair *nv, int line, remote_conf_t *config)
+{
+ return parse_uint (nv, line, &(config->port), 0, 65535);
+}
+
+static int local_port_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ if ((strcasecmp(nv->value, "any") == 0))
+ return 0; // The default is 0, which means any port
+ return parse_uint (nv, line, &(config->local_port), 0, 65535);
+}
+
+static int transport_parser(struct nv_pair *nv, int line, remote_conf_t *config)
+{
+ int i;
+ for (i=0; transport_words[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, transport_words[i].name) == 0) {
+ config->transport = transport_words[i].option;
+ return 0;
+ }
+ }
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int mode_parser(struct nv_pair *nv, int line, remote_conf_t *config)
+{
+ int i;
+ for (i=0; mode_words[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, mode_words[i].name) == 0) {
+ config->mode = mode_words[i].option;
+ return 0;
+ }
+ }
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int queue_file_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ if (nv->value) {
+ if (*nv->value != '/') {
+ syslog(LOG_ERR, "Absolute path needed for %s - line %d",
+ nv->value, line);
+ return 1;
+ }
+ config->queue_file = strdup(nv->value);
+ } else
+ config->queue_file = NULL;
+ return 0;
+}
+
+static int depth_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ return parse_uint (nv, line, &(config->queue_depth), 1, INT_MAX);
+}
+
+static int action_parser(struct nv_pair *nv, int line,
+ failure_action_t *actp, const char **exep)
+{
+ int i;
+ for (i=0; fail_action_words[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, fail_action_words[i].name) == 0) {
+ if (fail_action_words[i].option == FA_EXEC) {
+ if (check_exe_name(nv->option, line))
+ return 1;
+ *exep = strdup(nv->option);
+ }
+ *actp = fail_action_words[i].option;
+ return 0;
+ }
+ }
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+#define AP(x) \
+static int x##_action_parser(struct nv_pair *nv, int line, \
+ remote_conf_t *config) \
+{ \
+ return action_parser(nv,line,&(config->x##_action),&(config->x##_exe));\
+} \
+
+AP(network_failure)
+AP(disk_low)
+AP(disk_full)
+AP(disk_error)
+AP(generic_error)
+AP(generic_warning)
+AP(queue_error)
+#undef AP
+
+static int overflow_action_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ int i;
+
+ for (i=0; overflow_action_words[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, overflow_action_words[i].name) == 0) {
+ config->overflow_action = overflow_action_words[i].option;
+ return 0;
+ }
+ }
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int remote_ending_action_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ if (strcasecmp(nv->value, "reconnect") == 0) {
+ config->remote_ending_action = FA_RECONNECT;
+ return 0;
+ }
+ return action_parser(nv, line, &config->remote_ending_action,
+ &config->remote_ending_exe);
+}
+
+static int format_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ int i;
+ for (i=0; format_words[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, format_words[i].name) == 0) {
+ config->format = format_words[i].option;
+ return 0;
+ }
+ }
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+}
+
+static int network_retry_time_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ return parse_uint(nv, line, &config->network_retry_time, 1, INT_MAX);
+}
+
+static int max_tries_per_record_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ return parse_uint(nv, line, &config->max_tries_per_record, 1, INT_MAX);
+}
+
+static int max_time_per_record_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ return parse_uint(nv, line, &(config->max_time_per_record), 1, INT_MAX);
+}
+
+static int heartbeat_timeout_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+ return parse_uint (nv, line, &(config->heartbeat_timeout), 0, INT_MAX);
+}
+
+static int enable_krb5_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+#ifndef USE_GSSAPI
+ syslog(LOG_INFO,
+ "GSSAPI support is not enabled, ignoring value at line %d",
+ line);
+ return 0;
+#else
+ unsigned long i;
+
+ for (i=0; enable_krb5_values[i].name != NULL; i++) {
+ if (strcasecmp(nv->value, enable_krb5_values[i].name) == 0) {
+ config->enable_krb5 = enable_krb5_values[i].option;
+ return 0;
+ }
+ }
+ syslog(LOG_ERR, "Option %s not found - line %d", nv->value, line);
+ return 1;
+#endif
+}
+
+static int krb5_principal_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+#ifndef USE_GSSAPI
+ syslog(LOG_INFO,
+ "GSSAPI support is not enabled, ignoring value at line %d",
+ line);
+#else
+ if (config->krb5_principal)
+ free ((char *)config->krb5_principal);
+
+ config->krb5_principal = strdup(nv->value);
+#endif
+ return 0;
+}
+
+static int krb5_client_name_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+#ifndef USE_GSSAPI
+ syslog(LOG_INFO,
+ "GSSAPI support is not enabled, ignoring value at line %d",
+ line);
+#else
+ if (config->krb5_client_name)
+ free ((char *)config->krb5_client_name);
+
+ config->krb5_client_name = strdup(nv->value);
+#endif
+ return 0;
+}
+
+static int krb5_key_file_parser(struct nv_pair *nv, int line,
+ remote_conf_t *config)
+{
+#ifndef USE_GSSAPI
+ syslog(LOG_INFO,
+ "GSSAPI support is not enabled, ignoring value at line %d",
+ line);
+#else
+ if (config->krb5_key_file)
+ free ((char *)config->krb5_key_file);
+
+ config->krb5_key_file = strdup(nv->value);
+#endif
+ return 0;
+}
+
+/*
+ * This function is where we do the integrated check of the audispd config
+ * options. At this point, all fields have been read. Returns 0 if no
+ * problems and 1 if problems detected.
+ */
+static int sanity_check(remote_conf_t *config, const char *file)
+{
+ /* Error checking */
+// server should have string
+// port should be less that 32k
+// queue_depth should be less than 100k
+// If fail_action is F_EXEC, fail_exec must exist
+ if (config->mode == M_STORE_AND_FORWARD
+ && config->format != F_MANAGED) {
+ syslog(LOG_ERR, "\"mode=forward\" is valid only with "
+ "\"format=managed\"");
+ return 1;
+ }
+ return 0;
+}
+
+void free_config(remote_conf_t *config)
+{
+ free((void *)config->remote_server);
+ free((void *)config->queue_file);
+ free((void *)config->network_failure_exe);
+ free((void *)config->disk_low_exe);
+ free((void *)config->disk_full_exe);
+ free((void *)config->disk_error_exe);
+ free((void *)config->remote_ending_exe);
+ free((void *)config->generic_error_exe);
+ free((void *)config->generic_warning_exe);
+ free((void *)config->queue_error_exe);
+ free((void *)config->krb5_principal);
+ free((void *)config->krb5_client_name);
+ free((void *)config->krb5_key_file);
+}
+
diff --git a/framework/src/audit/audisp/plugins/remote/remote-config.h b/framework/src/audit/audisp/plugins/remote/remote-config.h
new file mode 100644
index 00000000..20c1f0b2
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/remote-config.h
@@ -0,0 +1,78 @@
+/* remote-config.h --
+ * Copyright 2008, 2009, 2011 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ *
+ */
+
+#ifndef REMOTE_CONFIG_H
+#define REMOTE_CONFIG_H
+
+typedef enum { M_IMMEDIATE, M_STORE_AND_FORWARD } rmode_t;
+typedef enum { T_TCP, T_SSL, T_GSSAPI, T_LABELED } transport_t;
+typedef enum { F_ASCII, F_MANAGED } format_t;
+typedef enum { FA_IGNORE, FA_SYSLOG, FA_EXEC, FA_RECONNECT, FA_SUSPEND,
+ FA_SINGLE, FA_HALT, FA_STOP } failure_action_t;
+typedef enum { OA_IGNORE, OA_SYSLOG, OA_SUSPEND, OA_SINGLE,
+ OA_HALT } overflow_action_t;
+
+typedef struct remote_conf
+{
+ const char *remote_server;
+ unsigned int port;
+ unsigned int local_port;
+ transport_t transport;
+ rmode_t mode;
+ const char *queue_file;
+ unsigned int queue_depth;
+ format_t format;
+ unsigned int network_retry_time;
+ unsigned int max_tries_per_record;
+ unsigned int max_time_per_record;
+ unsigned int heartbeat_timeout;
+ int enable_krb5;
+ const char *krb5_principal;
+ const char *krb5_client_name;
+ const char *krb5_key_file;
+
+ failure_action_t network_failure_action;
+ const char *network_failure_exe;
+ failure_action_t disk_low_action;
+ const char *disk_low_exe;
+ failure_action_t disk_full_action;
+ const char *disk_full_exe;
+ failure_action_t disk_error_action;
+ const char *disk_error_exe;
+ failure_action_t remote_ending_action;
+ const char *remote_ending_exe;
+ failure_action_t generic_error_action;
+ const char *generic_error_exe;
+ failure_action_t generic_warning_action;
+ const char *generic_warning_exe;
+ failure_action_t queue_error_action;
+ const char *queue_error_exe;
+ overflow_action_t overflow_action;
+} remote_conf_t;
+
+void clear_config(remote_conf_t *config);
+int load_config(remote_conf_t *config, const char *file);
+void free_config(remote_conf_t *config);
+
+#endif
+
diff --git a/framework/src/audit/audisp/plugins/remote/remote-fgets.c b/framework/src/audit/audisp/plugins/remote/remote-fgets.c
new file mode 100644
index 00000000..41e9c0c2
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/remote-fgets.c
@@ -0,0 +1,123 @@
+/* remote-fgets.c --
+ * Copyright 2011 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Steve Grubb <sgrubb@redhat.com>
+ */
+
+#include "config.h"
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include "remote-fgets.h"
+
+#define BUF_SIZE 8192
+static char buffer[2*BUF_SIZE+1] = { 0 };
+static char *current = buffer;
+static char *const eptr = buffer+(2*BUF_SIZE);
+static int eof = 0;
+
+int remote_fgets_eof(void)
+{
+ return eof;
+}
+
+/* Function to check if we have more data stored
+ * and ready to process. If we have a newline or enough
+ * bytes we return 1 for success. Otherwise 0 meaning that
+ * there is not enough to process without blocking. */
+int remote_fgets_more(size_t blen)
+{
+ char *ptr = strchr(buffer, '\n');
+ assert(blen != 0);
+ if (ptr || (size_t)(current-buffer) >= blen-1)
+ return 1;
+ return 0;
+}
+
+int remote_fgets(char *buf, size_t blen, int fd)
+{
+ int complete = 0;
+ size_t line_len;
+ char *line_end = NULL;
+
+ assert(blen != 0);
+ /* See if we have more in the buffer first */
+ if (current != buffer) {
+ line_end = strchr(buffer, '\n');
+ if (line_end == NULL && (size_t)(current - buffer) >= blen-1)
+ line_end = current-1; // have enough to fill blen, so point to end
+ }
+
+ /* Otherwise get some new bytes */
+ if (line_end == NULL && current != eptr && !eof) {
+ ssize_t len;
+
+ /* Use current since we may be adding more */
+ do {
+ len = read(fd, current, eptr - current);
+ } while (len < 0 && errno == EINTR);
+ if (len < 0)
+ return -1;
+ if (len == 0)
+ eof = 1;
+ else
+ current[len] = 0;
+ current += len;
+
+ /* Start from beginning to see if we have one */
+ line_end = strchr(buffer, '\n');
+ }
+
+ /* See what we have */
+ if (line_end) {
+ /* Include the last character (usually newline) */
+ line_len = (line_end+1) - buffer;
+ /* Make sure we are within the right size */
+ if (line_len > blen-1)
+ line_len = blen-1;
+ complete = 1;
+ } else if (current == eptr) {
+ /* We are full but no newline */
+ line_len = blen-1;
+ complete = 1;
+ } else if (current >= buffer+blen-1) {
+ /* Not completely full, no newline, but enough to fill buf */
+ line_len = blen-1;
+ complete = 1;
+ }
+ if (complete) {
+ size_t remainder_len;
+
+ /* Move to external buf and terminate it */
+ memcpy(buf, buffer, line_len);
+ buf[line_len] = 0;
+ remainder_len = current - (buffer + line_len);
+ if (remainder_len > 0) {
+ /* We have a few leftover bytes to move */
+ memmove(buffer, buffer+line_len, remainder_len);
+ current = buffer+remainder_len;
+ } else {
+ /* Got the whole thing, just reset */
+ current = buffer;
+ }
+ *current = 0;
+ }
+ return complete;
+}
diff --git a/framework/src/audit/audisp/plugins/remote/remote-fgets.h b/framework/src/audit/audisp/plugins/remote/remote-fgets.h
new file mode 100644
index 00000000..cb6b2d51
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/remote-fgets.h
@@ -0,0 +1,33 @@
+/* remote-fgtes.h -- a replacement for glibc's fgets
+ * Copyright 2011 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Authors:
+ * Steve Grubb <sgrubb@redhat.com>
+ */
+
+#ifndef REMOTE_FGETS_HEADER
+#define REMOTE_FGETS_HEADER
+
+#include <sys/types.h>
+
+int remote_fgets_eof(void);
+int remote_fgets_more(size_t blen);
+int remote_fgets(char *buf, size_t blen, int fd);
+
+#endif
+
diff --git a/framework/src/audit/audisp/plugins/remote/test-queue.c b/framework/src/audit/audisp/plugins/remote/test-queue.c
new file mode 100644
index 00000000..cbf815e8
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/remote/test-queue.c
@@ -0,0 +1,367 @@
+/* test-queue.c -- test suite for persistent-queue.c
+ * Copyright 2011 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * 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:
+ * Miloslav Trmač <mitr@redhat.com>
+ */
+
+#include "config.h"
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include "queue.h"
+
+#define NUM_ENTRIES 7
+/* 3*4096, larger than MAX_AUDIT_MESSAGE_LENGTH. The same value is used in the
+ main audisp-remote code. */
+#define ENTRY_SIZE 12288
+
+static char filename[] = "/tmp/tqXXXXXX";
+static struct queue *q;
+
+static char *sample_entries[NUM_ENTRIES - 1];
+#define NUM_SAMPLE_ENTRIES (sizeof(sample_entries) / sizeof(*sample_entries))
+
+#define die(...) die__(__LINE__, __VA_ARGS__)
+static void __attribute__((format (printf, 2, 3)))
+die__(int line, const char *message, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "test-queue: %d: ", line);
+ va_start(ap, message);
+ vfprintf(stderr, message, ap);
+ va_end(ap);
+ putc('\n', stderr);
+ abort();
+}
+
+#define err(...) err__(__LINE__, __VA_ARGS__)
+static void __attribute__((format (printf, 2, 3)))
+err__(int line, const char *message, ...)
+{
+ char *errno_str;
+ va_list ap;
+
+ errno_str = strerror(errno);
+ fprintf(stderr, "test-queue: %d: ", line);
+ va_start(ap, message);
+ vfprintf(stderr, message, ap);
+ va_end(ap);
+ fprintf(stderr, ": %s\n", errno_str);
+ abort();
+}
+
+static void
+init_sample_entries(void)
+{
+ size_t i;
+
+ for (i = 0; i < NUM_SAMPLE_ENTRIES; i++) {
+ char *e;
+ size_t j, len;
+
+ len = rand() % ENTRY_SIZE;
+ e = malloc(len + 1);
+ if (e == NULL)
+ err("malloc");
+ for (j = 0; j < len; j++)
+ e[j] = rand() % CHAR_MAX + 1;
+ e[j] = '\0';
+ sample_entries[i] = e;
+ }
+}
+
+static void
+free_sample_entries(void)
+{
+ size_t i;
+
+ for (i = 0; i < NUM_SAMPLE_ENTRIES; i++)
+ free(sample_entries[i]);
+}
+
+static void
+test_q_open(void)
+{
+ struct queue *q2;
+
+ /* Test that flags are honored */
+ q2 = q_open(Q_IN_FILE | Q_CREAT | Q_EXCL, filename, NUM_ENTRIES,
+ ENTRY_SIZE);
+ if (q2 != NULL)
+ die("q_open didn't fail");
+ if (errno != EEXIST)
+ err("q_open");
+
+ /* Test that locking is enforced. Use a separate process because
+ fcntl()/lockf() locking is attached to processes, not file
+ descriptors. */
+ fflush(NULL);
+ switch (fork()) {
+ case -1:
+ err("fork");
+ case 0:
+ q2 = q_open(Q_IN_FILE, filename, NUM_ENTRIES, ENTRY_SIZE);
+ if (q2 != NULL)
+ die("q_open didn't fail");
+ if (errno != EBUSY)
+ err("q_open");
+ _exit(0);
+ default: {
+ int status;
+
+ if (wait(&status) == (pid_t)-1)
+ err("wait");
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ die("wait status %d", status);
+ }
+ }
+}
+
+static void
+test_empty_q (void)
+{
+ char buf[ENTRY_SIZE];
+
+ if (q_peek(q, buf, sizeof(buf)) != 0)
+ die("q_peek reports non-empty");
+
+ if (q_drop_head(q) != -1)
+ die("q_drop_head didn't fail");
+ if (errno != EINVAL)
+ err("q_drop_head");
+
+ if (q_queue_length(q) != 0)
+ die("Unexpected q_queue_length");
+}
+
+static void
+test_basic_data (void)
+{
+ char buf[ENTRY_SIZE + 1];
+ int i;
+
+ if (q_append(q, " ") != 0)
+ die("q_append");
+
+ memset (buf, 'A', ENTRY_SIZE);
+ buf[ENTRY_SIZE] = '\0';
+ if (q_append(q, buf) != -1)
+ die("q_append didn't fail");
+ if (errno != EINVAL)
+ err("q_append");
+
+ buf[ENTRY_SIZE - 1] = '\0';
+ if (q_append(q, buf) != 0)
+ die("q_append");
+
+ if (q_queue_length(q) != 2)
+ die("Unexpected q_queue_length");
+
+ if (q_peek(q, buf, sizeof(buf)) < 1)
+ err("q_peek");
+ if (strcmp(buf, " ") != 0)
+ die("invalid data returned");
+ if (q_drop_head(q) != 0)
+ err("q_drop_head");
+
+ if (q_peek(q, buf, ENTRY_SIZE - 1) != -1)
+ err("q_peek didn't fail");
+ if (errno != ERANGE)
+ err("q_peek");
+ for (i = 0; i < 2; i++) {
+ size_t j;
+
+ if (q_peek(q, buf, sizeof(buf)) < 1)
+ err("q_peek");
+ for (j = 0; j < ENTRY_SIZE - 1; j++) {
+ if (buf[j] != 'A')
+ die("invalid data at %zu", j);
+ }
+ if (buf[j] != '\0')
+ die("invalid data at %zu", j);
+ }
+ if (q_drop_head(q) != 0)
+ err("q_drop_head");
+
+ if (q_queue_length(q) != 0)
+ die("Unexpected q_queue_length");
+}
+
+static void
+append_sample_entries(size_t count)
+{
+ size_t i;
+
+ for (i = 0; i < count; i++) {
+ if (q_append(q, sample_entries[i % NUM_SAMPLE_ENTRIES]) != 0)
+ die("q_append %zu", i);
+ }
+}
+
+static void
+verify_sample_entries(size_t count)
+{
+ char buf[ENTRY_SIZE + 1];
+ size_t i;
+
+ if (q_queue_length(q) != count)
+ die("Unexpected q_queue_length");
+ for (i = 0; i < count; i++) {
+ if (q_peek(q, buf, sizeof(buf)) < 1)
+ err("q_peek %zu", i);
+ if (strcmp(buf, sample_entries[i % NUM_SAMPLE_ENTRIES]) != 0)
+ die("invalid data %zu", i);
+ if (q_drop_head(q) != 0)
+ err("q_drop_head");
+ }
+ if (q_peek(q, buf, sizeof(buf)) != 0)
+ die("q_peek reports non-empty");
+}
+
+static void
+test_run(int flags)
+{
+ size_t j;
+
+ q = q_open(flags | Q_CREAT | Q_EXCL, filename, NUM_ENTRIES, ENTRY_SIZE);
+ if (q == NULL)
+ err("q_open");
+
+ if ((flags & Q_IN_FILE) != 0)
+ test_q_open();
+
+ /* Do this enough times to get a wraparound */
+ for (j = 0; j < NUM_ENTRIES; j++) {
+ test_empty_q();
+ test_basic_data();
+ }
+
+ append_sample_entries(NUM_ENTRIES - 1);
+ if (q_queue_length(q) != NUM_ENTRIES - 1)
+ die("Unexpected q_queue_length");
+
+ q_close(q);
+
+ q = q_open(flags, filename, NUM_ENTRIES, ENTRY_SIZE);
+ if (q == NULL)
+ err("q_open");
+ if ((flags & Q_IN_FILE) != 0)
+ /* Test that the queue can be reopened and data has been
+ preserved. */
+ verify_sample_entries(NUM_ENTRIES - 1);
+ else
+ /* Test that a new in-memory queue is empty. */
+ verify_sample_entries(0);
+ q_close(q);
+
+ if ((flags & Q_IN_FILE) != 0 && unlink(filename) != 0)
+ err("unlink");
+}
+
+static void
+test_resizing(void)
+{
+ q = q_open(Q_IN_FILE | Q_CREAT | Q_EXCL, filename, NUM_ENTRIES,
+ ENTRY_SIZE);
+ if (q == NULL)
+ err("q_open");
+
+ append_sample_entries(NUM_ENTRIES);
+ if (q_queue_length(q) != NUM_ENTRIES)
+ die("Unexpected q_queue_length");
+
+ q_close(q);
+
+ /* Verify num_entries is validated */
+ q = q_open(Q_IN_FILE, filename, NUM_ENTRIES + 1, ENTRY_SIZE);
+ if (q != NULL)
+ die("q_open didn't fail");
+ if (errno != EINVAL)
+ err("q_open");
+ q = q_open(Q_IN_FILE, filename, NUM_ENTRIES - 1, ENTRY_SIZE);
+ if (q != NULL)
+ die("q_open didn't fail");
+ if (errno != EINVAL)
+ err("q_open");
+
+ /* Test increasing size */
+ q = q_open(Q_IN_FILE | Q_RESIZE, filename, 2 * NUM_ENTRIES, ENTRY_SIZE);
+ if (q == NULL)
+ err("q_open");
+ verify_sample_entries(NUM_ENTRIES);
+
+ append_sample_entries(NUM_ENTRIES);
+ q_close(q);
+
+ /* Test decreasing size */
+ q = q_open(Q_IN_FILE | Q_RESIZE, filename, NUM_ENTRIES / 2, ENTRY_SIZE);
+ if (q != NULL)
+ die("q_open didn't fail");
+ if (errno != ENOSPC)
+ err("q_open");
+ q = q_open(Q_IN_FILE | Q_RESIZE, filename, NUM_ENTRIES, ENTRY_SIZE);
+ if (q == NULL)
+ err("q_open");
+ verify_sample_entries(NUM_ENTRIES);
+ q_close(q);
+
+ if (unlink(filename) != 0)
+ err("unlink");
+}
+
+int
+main(void)
+{
+ static const int flags[] = {
+ Q_IN_MEMORY,
+ Q_IN_FILE,
+ Q_IN_FILE | Q_SYNC,
+ Q_IN_MEMORY | Q_IN_FILE
+ };
+
+ int fd;
+ size_t i;
+
+ init_sample_entries();
+
+ /* We really want tmpnam() here (safe due to the Q_EXCL below), but
+ gcc warns on any use of tmpnam(). */
+ fd = mkstemp(filename);
+ if (fd == -1)
+ err("tmpnam");
+ if (close(fd) != 0)
+ err("close");
+ if (unlink(filename) != 0)
+ err("unlink");
+
+ for (i = 0; i < sizeof(flags) / sizeof(*flags); i++)
+ test_run(flags[i]);
+
+ test_resizing();
+
+ free_sample_entries();
+
+ return EXIT_SUCCESS;
+}
diff --git a/framework/src/audit/audisp/plugins/zos-remote/Makefile.am b/framework/src/audit/audisp/plugins/zos-remote/Makefile.am
new file mode 100644
index 00000000..ac83a74d
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/Makefile.am
@@ -0,0 +1,52 @@
+# Makefile.am--
+# Copyright (C) 2007,2008 International Business Machines Corp.
+# Copyright (C) 2011, 2015 Red Hat., Durham, North Carolina.
+# All Rights Reserved.
+#
+# 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:
+# Klaus Heinrich Kiwi <klausk@br.ibm.com>
+#
+
+AM_CPPFLAGS = -I${top_srcdir} -I${top_srcdir}/lib -I${top_srcdir}/auparse
+CONFIG_CLEAN_FILES = *.rej *.orig
+AUTOMAKE_OPTIONS = no-dependencies
+EXTRA_DIST = zos-remote.conf audispd-zos-remote.conf
+LIBS = -L${top_builddir}/auparse -lauparse
+LDADD = -lpthread -lldap -llber $(CAPNG_LDADD)
+dispatcher_confdir = $(sysconfdir)/audisp
+plugin_confdir=$(dispatcher_confdir)/plugins.d
+plugin_conf = zos-remote.conf
+dispatcher_conf = audispd-zos-remote.conf
+sbin_PROGRAMS = audispd-zos-remote
+
+noinst_HEADERS = zos-remote-log.h zos-remote-ldap.h zos-remote-config.h \
+ zos-remote-queue.h
+audispd_zos_remote_SOURCES = zos-remote-plugin.c zos-remote-log.c \
+ zos-remote-ldap.c zos-remote-config.c zos-remote-queue.c
+audispd_zos_remote_CFLAGS = -W -Wall -Wundef -D_GNU_SOURCE -fPIE -DPIE
+audispd_zos_remote_LDFLAGS = -pie -Wl,-z,relro -Wl,-z,now
+
+install-data-hook:
+ mkdir -p -m 0750 ${DESTDIR}${plugin_confdir}
+ $(INSTALL_DATA) -D -m 640 ${srcdir}/$(plugin_conf) \
+ ${DESTDIR}${dispatcher_confdir}
+ $(INSTALL_DATA) -D -m 640 ${srcdir}/$(dispatcher_conf) \
+ ${DESTDIR}${plugin_confdir}
+
+uninstall-hook:
+ rm ${DESTDIR}${plugin_confdir}/$(dispatcher_conf)
+ rm ${DESTDIR}${dispatcher_confdir}/$(plugin_conf)
diff --git a/framework/src/audit/audisp/plugins/zos-remote/audispd-zos-remote.conf b/framework/src/audit/audisp/plugins/zos-remote/audispd-zos-remote.conf
new file mode 100644
index 00000000..13aef2ce
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/audispd-zos-remote.conf
@@ -0,0 +1,14 @@
+# This is the configuration for the audispd-zos-remote
+# audit dispatcher plugin - See audispd(8)
+#
+# Note that this specific plugin has a configuration file of
+# its own. The complete path for this file must be entered as
+# the argument for the plugin in the 'args' field below
+# See audispd-zos-remote(8)
+
+active = no
+direction = out
+path = /sbin/audispd-zos-remote
+type = always
+args = /etc/audisp/zos-remote.conf
+format = string
diff --git a/framework/src/audit/audisp/plugins/zos-remote/zos-remote-config.c b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-config.c
new file mode 100644
index 00000000..b92dc778
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-config.c
@@ -0,0 +1,443 @@
+/***************************************************************************
+ * Copyright (C) 2007 International Business Machines Corp. *
+ * All Rights Reserved. *
+ * *
+ * 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: *
+ * Klaus Heinrich Kiwi <klausk@br.ibm.com> *
+ * based on code by Steve Grubb <sgrubb@redhat.com> *
+ ***************************************************************************/
+
+#include "zos-remote-config.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include "zos-remote-log.h"
+
+/* Local prototypes */
+struct nv_pair
+{
+ const char *name;
+ const char *value;
+ const char *option;
+};
+
+struct kw_pair
+{
+ const char *name;
+ int (*parser) (struct nv_pair *, int, plugin_conf_t *);
+ int max_options;
+};
+
+struct nv_list
+{
+ const char *name;
+ int option;
+};
+
+static char *get_line(FILE *, char *);
+static int nv_split(char *, struct nv_pair *);
+static const struct kw_pair *kw_lookup(const char *);
+static int server_parser(struct nv_pair *, int, plugin_conf_t *);
+static int port_parser(struct nv_pair *, int, plugin_conf_t *);
+static int timeout_parser(struct nv_pair *, int, plugin_conf_t *);
+static int user_parser(struct nv_pair *, int, plugin_conf_t *);
+static int password_parser(struct nv_pair *, int, plugin_conf_t *);
+static int q_depth_parser(struct nv_pair *, int, plugin_conf_t *);
+static int sanity_check(plugin_conf_t *);
+
+static const struct kw_pair keywords[] = {
+ {"server", server_parser, 0},
+ {"port", port_parser, 0},
+ {"timeout", timeout_parser, 0},
+ {"user", user_parser, 0},
+ {"password", password_parser, 0},
+ {"q_depth", q_depth_parser, 0},
+ {NULL, NULL, 0}
+};
+
+#define UNUSED(x) (void)(x)
+
+/*
+ * Set everything to its default value
+*/
+void plugin_clear_config(plugin_conf_t * c)
+{
+ c->server = NULL;
+ c->port = 0;
+ c->user = NULL;
+ c->password = NULL;
+ c->timeout = 15;
+ c->q_depth = 64;
+ /* not re-setting counter */
+}
+
+int plugin_load_config(plugin_conf_t * c, const char *file)
+{
+ int fd, rc, mode, lineno = 1;
+ struct stat st;
+ FILE *f;
+ char buf[128];
+
+ plugin_clear_config(c);
+
+ /* open the file */
+ mode = O_RDONLY;
+ rc = open(file, mode);
+ if (rc < 0) {
+ if (errno != ENOENT) {
+ log_err("Error opening %s (%s)", file,
+ strerror(errno));
+ return 1;
+ }
+ log_warn("Config file %s doesn't exist, skipping", file);
+ return 1;
+ }
+ fd = rc;
+
+ /* check the file's permissions: owned by root, not world anything,
+ * not symlink.
+ */
+ if (fstat(fd, &st) < 0) {
+ log_err("Error fstat'ing config file (%s)",
+ strerror(errno));
+ close(fd);
+ return 1;
+ }
+ if (st.st_uid != 0) {
+ log_err("Error - %s isn't owned by root", file);
+ close(fd);
+ return 1;
+ }
+ if ((st.st_mode & (S_IRUSR | S_IWUSR | S_IRGRP)) !=
+ (S_IRUSR | S_IWUSR | S_IRGRP)) {
+ log_err("%s permissions should be 0640", file);
+ close(fd);
+ return 1;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ log_err("Error - %s is not a regular file", file);
+ close(fd);
+ return 1;
+ }
+
+ /* it's ok, read line by line */
+ f = fdopen(fd, "r");
+ if (f == NULL) {
+ log_err("Error - fdopen failed (%s)", strerror(errno));
+ close(fd);
+ return 1;
+ }
+
+ while (get_line(f, buf)) {
+ /* convert line into name-value pair */
+ const struct kw_pair *kw;
+ struct nv_pair nv;
+
+ rc = nv_split(buf, &nv);
+ switch (rc) {
+ case 0: /* fine */
+ break;
+ case 1: /* not the right number of tokens. */
+ log_err("Wrong number of arguments for line %d in %s", lineno, file);
+ break;
+ case 2: /* no '=' sign */
+ log_err("Missing equal sign for line %d in %s",
+ lineno, file);
+ break;
+ default: /* something else went wrong... */
+ log_err("Unknown error for line %d in %s",
+ lineno, file);
+ break;
+ }
+ if (nv.name == NULL) {
+ lineno++;
+ continue;
+ }
+ if (nv.value == NULL) {
+ fclose(f);
+ return 1;
+ }
+
+ /* identify keyword or error */
+ kw = kw_lookup(nv.name);
+ if (kw->name == NULL) {
+ log_err("Unknown keyword \"%s\" in line %d of %s",
+ nv.name, lineno, file);
+ fclose(f);
+ return 1;
+ }
+
+ /* Check number of options */
+ if (kw->max_options == 0 && nv.option != NULL) {
+ log_err("Keyword \"%s\" has invalid option "
+ "\"%s\" in line %d of %s",
+ nv.name, nv.option, lineno, file);
+ fclose(f);
+ return 1;
+ }
+
+ /* dispatch to keyword's local parser */
+ rc = kw->parser(&nv, lineno, c);
+ if (rc != 0) {
+ fclose(f);
+ return 1; /* local parser puts message out */
+ }
+
+ lineno++;
+ }
+
+ fclose(f);
+ c->name = strdup(basename(file));
+ if (lineno > 1)
+ return sanity_check(c);
+ return 0;
+}
+
+static char *get_line(FILE * f, char *buf)
+{
+ if (fgets_unlocked(buf, 128, f)) {
+ /* remove newline */
+ char *ptr = strchr(buf, 0x0a);
+
+ if (ptr)
+ *ptr = 0;
+ return buf;
+ }
+ return NULL;
+}
+
+static int nv_split(char *buf, struct nv_pair *nv)
+{
+ /* Get the name part */
+ char *ptr, *saved;
+
+ nv->name = NULL;
+ nv->value = NULL;
+ nv->option = NULL;
+ ptr = strtok_r(buf, " ", &saved);
+ if (ptr == NULL)
+ return 0; /* If there's nothing, go to next line */
+ if (ptr[0] == '#')
+ return 0; /* If there's a comment, go to next line */
+ nv->name = ptr;
+
+ /* Check for a '=' */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr == NULL)
+ return 1;
+ if (strcmp(ptr, "=") != 0)
+ return 2;
+
+ /* get the value */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr == NULL)
+ return 1;
+ nv->value = ptr;
+
+ /* See if there's an option */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr) {
+ nv->option = ptr;
+
+ /* Make sure there's nothing else */
+ ptr = strtok_r(NULL, " ", &saved);
+ if (ptr)
+ return 1;
+ }
+
+ /* Everything is OK */
+ return 0;
+}
+
+static const struct kw_pair *kw_lookup(const char *val)
+{
+ int i = 0;
+
+ while (keywords[i].name != NULL) {
+ if (strcasecmp(keywords[i].name, val) == 0)
+ break;
+ i++;
+ }
+ return &keywords[i];
+}
+
+
+static int server_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
+{
+ UNUSED(line);
+ if (nv->value == NULL)
+ c->server = NULL;
+ else
+ c->server = strdup(nv->value);
+
+ return 0;
+}
+
+static int port_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
+{
+ const char *ptr = nv->value;
+ unsigned long i;
+
+ /* check that all chars are numbers */
+ for (i = 0; ptr[i]; i++) {
+ if (!isdigit(ptr[i])) {
+ log_err("Value %s should only be numbers - line %d", nv->value, line);
+ return 1;
+ }
+ }
+
+ /* convert to unsigned long */
+ errno = 0;
+ i = strtoul(nv->value, NULL, 10);
+ if (errno) {
+ log_err("Error converting string to a number (%s) - line %d", strerror(errno), line);
+ return 1;
+ }
+
+ c->port = i;
+ return 0;
+
+}
+
+static int timeout_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
+{
+ const char *ptr = nv->value;
+ unsigned long i;
+
+ /* check that all chars are numbers */
+ for (i = 0; ptr[i]; i++) {
+ if (!isdigit(ptr[i])) {
+ log_err("Value %s should only be numbers - line %d", nv->value, line);
+ return 1;
+ }
+ }
+
+ /* convert to unsigned long */
+ errno = 0;
+ i = strtoul(nv->value, NULL, 10);
+ if (errno) {
+ log_err("Error converting string to a number (%s) - line %d", strerror(errno), line);
+ return 1;
+ }
+
+ c->timeout = i;
+ return 0;
+
+}
+
+
+static int user_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
+{
+ UNUSED(line);
+ if (nv->value == NULL)
+ c->user = NULL;
+ else
+ c->user = strdup(nv->value);
+
+ return 0;
+}
+
+static int password_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
+{
+ UNUSED(line);
+ if (nv->value == NULL)
+ c->password = NULL;
+ else
+ c->password = strdup(nv->value);
+
+ return 0;
+}
+
+static int q_depth_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
+{
+ const char *ptr = nv->value;
+ unsigned long i;
+
+ /* check that all chars are numbers */
+ for (i = 0; ptr[i]; i++) {
+ if (!isdigit(ptr[i])) {
+ log_err("Value %s should only be numbers - line %d", nv->value, line);
+ return 1;
+ }
+ }
+
+ /* convert to unsigned long */
+ errno = 0;
+ i = strtoul(nv->value, NULL, 10);
+ if (errno) {
+ log_err("Error converting string to a number (%s) - line %d", strerror(errno), line);
+ return 1;
+ }
+
+ if (i < 16 || i > 99999) {
+ log_err("q_depth must be between 16 and 99999");
+ return 1;
+ }
+
+ c->q_depth = i;
+ return 0;
+
+}
+
+
+/*
+ * Check configuration.At this point, all fields have been read.
+ * Returns 0 if no problems and 1 if problems detected.
+ */
+static int sanity_check(plugin_conf_t * c)
+{
+ /* Error checking */
+ if (!c->server) {
+ log_err("Error - no server hostname given");
+ return 1;
+ }
+
+ if (!c->user) {
+ log_err("Error - no bind user given");
+ return 1;
+ }
+
+ if (!c->password) {
+ log_err("Error - no password given");
+ return 1;
+ }
+
+ if (!c->timeout) {
+ log_err("Error - timeout can't be zero");
+ return 1;
+ }
+ return 0;
+}
+
+void plugin_free_config(plugin_conf_t * c)
+{
+
+ if (c == NULL)
+ return;
+
+ free((void *) c->server);
+ free((void *) c->user);
+ free((void *) c->password);
+ free((void *) c->name);
+}
diff --git a/framework/src/audit/audisp/plugins/zos-remote/zos-remote-config.h b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-config.h
new file mode 100644
index 00000000..82bf365f
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-config.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ * Copyright (C) 2007 International Business Machines Corp. *
+ * All Rights Reserved. *
+ * *
+ * 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: *
+ * Klaus Heinrich Kiwi <klausk@br.ibm.com> *
+ * based on code by Steve Grubb <sgrubb@redhat.com> *
+ ***************************************************************************/
+
+#ifndef _ZOS_REMOTE_CONFIG_H
+#define _ZOS_REMOTE_CONFIG_H
+
+
+/***************************************************************************
+ * z/OS Remote-services Plugin configuration *
+ ***************************************************************************/
+typedef struct plugin_conf
+{
+ char *name;
+ char *server;
+ unsigned int port;
+ char *user;
+ char *password;
+ long timeout;
+ unsigned int q_depth;
+ unsigned int counter;
+} plugin_conf_t;
+
+void plugin_clear_config(plugin_conf_t *);
+int plugin_load_config(plugin_conf_t *, const char *);
+void plugin_free_config(plugin_conf_t *);
+
+#endif /* _ZOS_REMOTE_CONFIG_H */
diff --git a/framework/src/audit/audisp/plugins/zos-remote/zos-remote-ldap.c b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-ldap.c
new file mode 100644
index 00000000..209743f3
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-ldap.c
@@ -0,0 +1,608 @@
+/***************************************************************************
+ * Copyright (C) 2007 International Business Machines Corp. *
+ * All Rights Reserved. *
+ * *
+ * 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: *
+ * Klaus Heinrich Kiwi <klausk@br.ibm.com> *
+ ***************************************************************************/
+
+#include "zos-remote-ldap.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "zos-remote-log.h"
+
+/***************************************************************************
+ * Audit response struct *
+ ***************************************************************************/
+typedef struct audit_resp_item
+{
+ ber_int_t version; /* Version of Response data itself */
+ ber_int_t itemTag; /* Copy of itemTag from Operation */
+ ber_int_t majorCode; /* Majorcode. Main return code of this Outcome */
+ ber_int_t minorCode1; /* minorCode1. SAFRc or other Rc */
+ ber_int_t minorCode2; /* minorCode2. RacfRc or other Rc */
+ ber_int_t minorCode3; /* minorCode3. RacfRsn or other Rc */
+} audit_resp_item_t;
+
+typedef struct audit_response
+{
+ ber_int_t respVersion; /* Overall version */
+ ber_int_t respMajor; /* Overall major code */
+ unsigned int numItems; /* Number of response items */
+ audit_resp_item_t **itemList; /* response ItemList */
+} audit_response_t;
+
+
+/***************************************************************************
+ * z/OS Remote-services Major return code handling *
+ ***************************************************************************/
+struct zos_remote_error
+{
+ int code;
+ char *str;
+};
+
+static struct zos_remote_error zos_remote_errlist[] = {
+ {ZOS_REMOTE_MAJOR_SUCCESS, "Success"},
+ {ZOS_REMOTE_MAJOR_WARNINGMODE, "WARNINGMODE - Event was logged, with warnings"},
+ {ZOS_REMOTE_MAJOR_NOTREQ, "NOTREQ - No logging required"},
+ {ZOS_REMOTE_MAJOR_UNDETERMINED, "UNDETERMINED - Undetermined result"},
+ {ZOS_REMOTE_MAJOR_UNAUTHORIZED, "UNAUTHORIZED - The user does not have authority the R_auditx service"},
+ {ZOS_REMOTE_MAJOR_RACROUTE, "RACROUTE - The R_auditx service returned an unexpected error"},
+ {ZOS_REMOTE_MAJOR_VAL_ERR, "VAL_ERR - Value error in request"},
+ {ZOS_REMOTE_MAJOR_ENC_ERR, "ENC_ERR - DER decoding error in request"},
+ {ZOS_REMOTE_MAJOR_UNSUF_AUTH, "UNSUF_AUTH - The user has unsuficient authority for the requested function"},
+ {ZOS_REMOTE_MAJOR_EMPTY, "EMPTY - Empty request received - No items found within the ItemList"},
+ {ZOS_REMOTE_MAJOR_INVALID_VER, "INVALID_VER - Invalid RequestVersion"},
+ {ZOS_REMOTE_MAJOR_INTERNAL_ERR, "INTERNAL_ERR - An internal error was encountered within the ICTX component"},
+ {-1, NULL}
+};
+
+/***************************************************************************
+ * Internal functions prototypes *
+ ***************************************************************************/
+static int _zos_remote_init(ZOS_REMOTE *);
+static void _zos_remote_destroy(ZOS_REMOTE *);
+static int zos_remote_connect(ZOS_REMOTE *);
+static void zos_remote_disconnect(ZOS_REMOTE *);
+static int submit_xop_s(ZOS_REMOTE *, struct berval *);
+static int decode_response(audit_response_t *, struct berval *);
+
+/***************************************************************************
+ * Exported functions *
+ ***************************************************************************/
+int submit_request_s(ZOS_REMOTE *zos_remote, BerElement *ber)
+{
+ int rc, retry = 1; /* retry once and give up */
+ struct berval bv;
+
+ rc = ber_flatten2(ber, &bv, 0); /* 0 = Use ber's buffer */
+ if (rc == -1) {
+ log_err("Error flattening BER element");
+ return ICTX_E_ABORT;
+ }
+
+retry:
+ rc = submit_xop_s(zos_remote, &bv);
+ switch (rc) {
+ case ICTX_SUCCESS:
+ break;
+ case ICTX_E_TRYAGAIN:
+ /*
+ * Usually means that the server connection timed-out
+ * So we flush the LDAP connection by unsetting the
+ * 'connected' flag and trying again.
+ */
+ if (retry > 0) {
+ log_debug("Connection seems down - retrying");
+ retry--;
+ _zos_remote_destroy(zos_remote);
+ rc = _zos_remote_init(zos_remote);
+ if (rc != ICTX_SUCCESS)
+ log_err("Error - failed to re-initialize LDAP session");
+ else
+ goto retry; /* go to submit_xop_s once more */
+ }
+ log_err("Can't establish connection");
+ break;
+ case ICTX_E_ABORT:
+ break;
+ default:
+ log_err("Event resulted failure, code: 0x%x", rc);
+ }
+
+ return rc;
+}
+
+int zos_remote_init(ZOS_REMOTE *zos_remote, const char *server, int port,
+ const char *user, const char *password, int timeout)
+{
+ zos_remote->server = strdup(server);
+ zos_remote->port = port;
+ zos_remote->user = strdup(user);
+ zos_remote->password = strdup(password);
+ zos_remote->timeout = timeout;
+ zos_remote->connected = 0;
+
+ if (!zos_remote->server || !zos_remote->user || !zos_remote->password) {
+ log_err("Error allocating memory for session members");
+ return ICTX_E_FATAL;
+ }
+
+ return _zos_remote_init(zos_remote);
+}
+
+void zos_remote_destroy(ZOS_REMOTE *zos_remote)
+{
+ _zos_remote_destroy(zos_remote);
+
+ free(zos_remote->server);
+ free(zos_remote->user);
+ free(zos_remote->password);
+}
+
+char *zos_remote_err2string(int err)
+{
+ int i;
+
+ for (i = 0; zos_remote_errlist[i].str != NULL; i++) {
+ if (err == zos_remote_errlist[i].code)
+ return zos_remote_errlist[i].str;
+ }
+ return "Unknown error";
+}
+
+/***************************************************************************
+ * Internal Functions *
+ ***************************************************************************/
+static int _zos_remote_init(ZOS_REMOTE *zos_remote)
+{
+ int version, rc;
+ char *uri = NULL;
+
+#ifdef LDAP_DEPRECATED
+
+ log_debug("Initializing z/OS Remote-services LDAP connection at ldap://%s:%d",
+ zos_remote->server, zos_remote->port);
+ zos_remote->ld = ldap_init(zos_remote->server
+ zos_remote->port ? zos_remote->port : LDAP_PORT);
+ if (zos_remote->ld == NULL) {
+ log_err("Error initializing LDAP session: %s",
+ strerror(errno));
+ rc = ICTX_E_FATAL;
+ goto end;
+ }
+#else
+ /* build ldap URI */
+ if (zos_remote->port == 0 || zos_remote->port == LDAP_PORT)
+ rc = asprintf(&uri, "ldap://%s", zos_remote->server);
+ else
+ rc = asprintf(&uri, "ldap://%s:%d", zos_remote->server,
+ zos_remote->port);
+
+ if (rc == -1) {
+ log_err("Out of memory building LDAP server URI");
+ rc = ICTX_E_FATAL;
+ uri = NULL;
+ goto end;
+ }
+
+ log_debug("Initializing z/OS Remote-services LDAP connection at %s", uri);
+ /* Get a handle to an LDAP connection */
+ rc = ldap_initialize(&zos_remote->ld, uri);
+ if (rc != LDAP_SUCCESS) {
+ log_err("Error initializing LDAP session: %s",
+ ldap_err2string(rc));
+ rc = ICTX_E_FATAL;
+ goto free_uri;
+ }
+#endif
+
+ /*
+ * Ensure the LDAP protocol version supported by the client
+ * to 3. (Extended operations are part of version 3).
+ */
+ rc = ldap_get_option(zos_remote->ld, LDAP_OPT_PROTOCOL_VERSION,
+ &version);
+ if (rc != LDAP_OPT_SUCCESS) {
+ log_err("Error getting LDAP session options");
+ rc = ICTX_E_FATAL;
+ goto unbind;
+ }
+
+ if (version < LDAP_VERSION3) {
+ log_debug("Setting LDAP session version to %d",
+ LDAP_VERSION3);
+ version = LDAP_VERSION3;
+ rc = ldap_set_option(zos_remote->ld, LDAP_OPT_PROTOCOL_VERSION,
+ &version);
+ if (rc != LDAP_OPT_SUCCESS) {
+ log_err("Error setting LDAP session version");
+ rc = ICTX_E_FATAL;
+ goto unbind;
+ }
+ }
+
+ goto free_uri;
+
+unbind:
+ ldap_unbind_ext_s(zos_remote->ld, NULL, NULL);
+ zos_remote->ld = NULL;
+
+free_uri:
+ free(uri);
+
+end:
+ return rc;
+}
+
+static void _zos_remote_destroy(ZOS_REMOTE *zos_remote)
+{
+ zos_remote_disconnect(zos_remote);
+ zos_remote->ld = NULL;
+}
+
+static int zos_remote_connect(ZOS_REMOTE *zos_remote)
+{
+ struct berval cred;
+ int rc;
+ char bindusr[255];
+
+ snprintf(bindusr, 255, "racfid=%s,cn=ictx", zos_remote->user);
+
+ log_debug("Attempting BIND. User '%s', password '<not shown>'",
+ bindusr);
+
+ cred.bv_val = (char *) zos_remote->password;
+ cred.bv_len = strlen(zos_remote->password);
+
+ rc = ldap_sasl_bind_s(zos_remote->ld, bindusr,
+ LDAP_SASL_SIMPLE, &cred,
+ NULL, NULL, NULL);
+
+
+ switch (rc) {
+ case LDAP_SUCCESS:
+ log_debug("LDAP BIND succeeded");
+ zos_remote->connected = 1;
+ rc = ICTX_SUCCESS;
+ break;
+ case LDAP_SERVER_DOWN:
+ case LDAP_BUSY:
+ case LDAP_UNAVAILABLE:
+ case LDAP_TIMEOUT:
+ case LDAP_CONNECT_ERROR:
+ log_warn("z/OS Remote-services connection failed: %s",
+ ldap_err2string(rc));
+ rc = ICTX_E_TRYAGAIN;
+ break;
+ default:
+ log_err("Error - z/OS Remote-services initialization failed: %s",
+ ldap_err2string(rc));
+ rc = ICTX_E_FATAL;
+ }
+
+ return rc;
+}
+
+
+static void zos_remote_disconnect(ZOS_REMOTE *zos_remote)
+{
+ if (zos_remote->ld) {
+ log_debug("Unbinding LDAP session");
+
+#ifdef LDAP_DEPRECATED
+ ldap_unbind(zos_remote->ld);
+#else
+ ldap_unbind_ext_s(zos_remote->ld, NULL, NULL);
+#endif
+ }
+ zos_remote->connected = 0;
+
+}
+
+/*
+ * Sync-submit extended operation given in *bv
+ * return ICTX_SUCCESS if submission (and response)
+ * succeeded.
+ * Log errors using log_err() functions
+ */
+int submit_xop_s(ZOS_REMOTE *zos_remote, struct berval *bv)
+{
+ LDAPMessage *result;
+ audit_response_t response;
+ int rc, errcode, msgId;
+ unsigned int i;
+ char *errmsg, *oid;
+ struct berval *bv_response;
+ struct timeval t;
+
+ if (zos_remote->connected == 0) {
+ rc = zos_remote_connect(zos_remote);
+ if (rc != ICTX_SUCCESS)
+ return rc;
+ }
+
+ /* call LDAP - won't block */
+ rc = ldap_extended_operation(zos_remote->ld, ICTX_OIDAUDITREQUEST,
+ bv, NULL, NULL, &msgId);
+ if (rc == LDAP_SERVER_DOWN) {
+ zos_remote->connected = 0;
+ return ICTX_E_TRYAGAIN;
+ } else if (rc != LDAP_SUCCESS) {
+ log_err("LDAP extended operation submission failure: %s",
+ ldap_err2string(rc));
+ return ICTX_E_ABORT;
+ } else {
+ log_debug("Sent LDAP extended operation request, msgId=0x%x",
+ msgId);
+ }
+
+ /* call blocking ldap_result with specified timeout */
+ t.tv_sec = zos_remote->timeout;
+ t.tv_usec = 0;
+ rc = ldap_result(zos_remote->ld, msgId, 1, &t, &result);
+
+ if (rc == -1) {
+ /* error in ldap operation */
+ ldap_get_option(zos_remote->ld, LDAP_OPT_ERROR_NUMBER, &errcode);
+ switch (errcode) {
+ case LDAP_SERVER_DOWN:
+ /* Connection may have timed out, let's retry */
+ zos_remote->connected = 0;
+ rc = ICTX_E_TRYAGAIN;
+ break;
+ default:
+ log_err("ldap_result unexpected failure: %s (0x%x)",
+ ldap_err2string(rc), rc);
+ rc = ICTX_E_ABORT;
+ }
+ goto end;
+ } else if (rc == 0) {
+ /* timeout reached */
+ log_warn("LDAP extended operation timed out");
+ rc = ICTX_E_ABORT;
+ goto end;
+ } else if (rc != LDAP_RES_EXTENDED) {
+ /* not an extended operation response! */
+ log_err("LDAP extended operation resulted in unexpected answer: 0x%x", rc);
+ rc = ICTX_E_ABORT;
+ goto free_result;
+ }
+
+ log_debug("Got LDAP Extended result");
+ /*
+ * we have an extended operation result
+ * first parse_result will check for errcode, later
+ * parse_extended_result will give us the oid and the BER value
+ */
+ rc = ldap_parse_result(zos_remote->ld, result, &errcode, NULL,
+ &errmsg, NULL, NULL, 0);
+ if (rc != LDAP_SUCCESS) {
+ log_err("LDAP parse result internal failure (code 0x%x)",
+ rc);
+ rc = ICTX_E_ABORT;
+ goto free_result;
+ }
+
+ if (errcode != LDAP_SUCCESS) {
+ log_err("LDAP extended operation failed: %s", errmsg);
+ rc = ICTX_E_ABORT;
+ goto free_errmsg;
+ }
+
+ rc = ldap_parse_extended_result(zos_remote->ld, result, &oid,
+ &bv_response, 0);
+ if (rc != LDAP_SUCCESS) {
+ log_err("Failed to parse ldap extended result (code 0x%x)",
+ rc);
+ rc = ICTX_E_ABORT;
+ goto free_errmsg;
+ }
+
+ if (oid && strcmp(oid, ICTX_OIDAUDITRESPONSE) != 0) {
+ /* oid == null shouldn't be a problem to log_err */
+ log_err("LDAP extended operation returned an invalid oid: %s", oid);
+ rc = ICTX_E_ABORT;
+ goto free_bv;
+ }
+
+ rc = decode_response(&response, bv_response);
+ if (rc != ICTX_SUCCESS) {
+ log_err("Error decoding extended operation response");
+ goto free_bv;
+ }
+
+ if (response.respMajor == ZOS_REMOTE_MAJOR_SUCCESS) {
+ /* submission was successful, no further processing needed */
+ log_debug("Successfully submited Remote audit Request");
+ rc = ICTX_SUCCESS;
+ goto free_response;
+ } else if (response.respMajor == ZOS_REMOTE_MAJOR_EMPTY) {
+ /* something is going on. Set error and stop processing */
+ log_warn("Warning - LDAP extended operation returned empty result");
+ rc = ICTX_E_ABORT;
+ goto free_response;
+ } else if (response.respMajor == ZOS_REMOTE_MAJOR_WARNINGMODE ||
+ response.respMajor == ZOS_REMOTE_MAJOR_NOTREQ)
+ rc = ICTX_SUCCESS; /* don't fail, but continue processing */
+ else
+ rc = ICTX_E_ABORT; /* set return code and continue processing */
+
+ /* If it's not success nor empty, let's check for errors in the response */
+ for (i = 0; i < response.numItems; i++) {
+ switch ((response.itemList[i])->majorCode) {
+ /* 0 <= Major Code <= 14 */
+ case ZOS_REMOTE_MAJOR_SUCCESS:
+ break;
+ case ZOS_REMOTE_MAJOR_WARNINGMODE:
+ case ZOS_REMOTE_MAJOR_NOTREQ:
+ log_debug("Warning - LDAP extended operation returned '%s' for item %d",
+ zos_remote_err2string((response.itemList[i])->majorCode),
+ (response.itemList[i])->itemTag);
+ log_debug("SAF code: 0x%x, RACF code: 0x%x, RACF reason: 0x%x",
+ (response.itemList[i])->minorCode1,
+ (response.itemList[i])->minorCode2,
+ (response.itemList[i])->minorCode3);
+ break;
+ case ZOS_REMOTE_MAJOR_UNDETERMINED:
+ case ZOS_REMOTE_MAJOR_UNAUTHORIZED:
+ case ZOS_REMOTE_MAJOR_RACROUTE:
+ log_err("Error - LDAP extended operation returned '%s' for item %d",
+ zos_remote_err2string((response.itemList[i])->majorCode),
+ (response.itemList[i])->itemTag);
+ log_err("SAF code: 0x%x, RACF code: 0x%x, RACF reason: 0x%x",
+ (response.itemList[i])->minorCode1,
+ (response.itemList[i])->minorCode2,
+ (response.itemList[i])->minorCode3);
+ break;
+ /* 16 <= Major Code <= 20 */
+ case ZOS_REMOTE_MAJOR_VAL_ERR:
+ case ZOS_REMOTE_MAJOR_ENC_ERR:
+ log_err("Error - LDAP extended operation returned '%s' for item %d",
+ zos_remote_err2string((response.itemList[i])->majorCode),
+ (response.itemList[i])->itemTag);
+ log_err("Item field: %d, reson %d",
+ (response.itemList[i])->
+ minorCode1,
+ (response.itemList[i])->minorCode2);
+ break;
+ /* 24 <= Major code <= 100 */
+ case ZOS_REMOTE_MAJOR_UNSUF_AUTH:
+ case ZOS_REMOTE_MAJOR_EMPTY:
+ case ZOS_REMOTE_MAJOR_INVALID_VER:
+ case ZOS_REMOTE_MAJOR_INTERNAL_ERR:
+ log_err("Error - LDAP extended operation returned '%s' for item %d",
+ zos_remote_err2string((response.itemList[i])->majorCode),
+ (response.itemList[i])->itemTag);
+ break;
+ default:
+ log_err("Error - LDAP extended operation returned an unknown Major code for item %d",
+ (response.itemList[i])->majorCode);
+ }
+ }
+
+free_response:
+ for (; response.numItems > 0; response.numItems--)
+ free(response.itemList[response.numItems - 1]);
+ free(response.itemList);
+
+free_bv:
+ if (bv_response)
+ ber_bvfree(bv_response);
+ if (oid)
+ ldap_memfree(oid);
+
+free_errmsg:
+ ldap_memfree(errmsg);
+
+free_result:
+ ldap_msgfree(result);
+
+end:
+ return rc;
+}
+
+static int decode_response(audit_response_t * r, struct berval *bv)
+{
+ BerElement *ber;
+ ber_len_t len;
+ int rc;
+
+ if (!bv) {
+ log_err("LDAP extended operation returned NULL message");
+ return ICTX_E_ABORT;
+ } else if ((ber = ber_init(bv)) == NULL) {
+ log_err("Error initializing BER response data");
+ return ICTX_E_ABORT;
+ }
+
+ log_debug("---Got an encoded request response:");
+ debug_bv(bv);
+
+ r->respVersion = 0;
+ r->respMajor = 0;
+ r->numItems = 0;
+ r->itemList = NULL;
+
+ rc = ber_scanf(ber, "{ii", &r->respVersion, &r->respMajor);
+ if (r->respVersion != ICTX_REQUESTVER) {
+ log_err("Invalid version returned by z/OS Remote-services server");
+ log_err("Should be %d, got %d", ICTX_REQUESTVER,
+ r->respVersion);
+ rc = ICTX_E_ABORT;
+ goto free_ber;
+ }
+
+ if (r->respMajor == ZOS_REMOTE_MAJOR_SUCCESS ||
+ r->respMajor == ZOS_REMOTE_MAJOR_EMPTY) {
+ rc = ICTX_SUCCESS;
+ /* No further processing required */
+ goto free_ber;
+ }
+
+ /* Inspect ber response otherwise */
+ while (ber_peek_tag(ber, &len) == LBER_SEQUENCE) {
+ r->numItems++;
+ r->itemList = (audit_resp_item_t **) realloc(r->itemList,
+ r->numItems *
+ sizeof
+ (audit_resp_item_t
+ *));
+ if (errno == ENOMEM) {
+ if (r->itemList)
+ free(r->itemList);
+ rc = ICTX_E_FATAL;
+ goto free_ber;
+ }
+
+ audit_resp_item_t *item = (audit_resp_item_t *)
+ malloc(sizeof(audit_resp_item_t));
+
+ if (!item) {
+ rc = ICTX_E_FATAL;
+ goto free_ber;
+ }
+
+ rc |= ber_scanf(ber, "{{iiiiii}}",
+ &item->version,
+ &item->itemTag,
+ &item->majorCode,
+ &item->minorCode1, &item->minorCode2,
+ &item->minorCode3);
+ r->itemList[r->numItems - 1] = item;
+ }
+ rc |= ber_scanf(ber, "}");
+
+ if (rc == -1) {
+ for (; r->numItems > 0; r->numItems--)
+ free(r->itemList[r->numItems - 1]);
+ free(r->itemList);
+ rc = ICTX_E_ABORT;
+ }
+ else
+ rc = ICTX_SUCCESS;
+
+free_ber:
+ ber_free(ber, 1);
+
+ return rc;
+}
diff --git a/framework/src/audit/audisp/plugins/zos-remote/zos-remote-ldap.h b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-ldap.h
new file mode 100644
index 00000000..5767b96e
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-ldap.h
@@ -0,0 +1,312 @@
+/***************************************************************************
+ * Copyright (C) 2007 International Business Machines Corp. *
+ * All Rights Reserved. *
+ * *
+ * 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: *
+ * Klaus Heinrich Kiwi <klausk@br.ibm.com> *
+ ***************************************************************************/
+
+#ifndef _ZOS_REMOTE_LDAP_H
+#define _ZOS_REMOTE_LDAP_H
+
+#include <lber.h>
+#include <ldap.h>
+
+
+/***************************************************************************
+ * LDAP Extended Op OID for ICTX Audit *
+ ***************************************************************************/
+/* ICTX EIM component AUDIT Request OID */
+#define ICTX_OIDAUDITREQUEST "1.3.18.0.2.12.68"
+
+/* The AUDIT Response OID */
+#define ICTX_OIDAUDITRESPONSE "1.3.18.0.2.12.69"
+
+/* This implementation version
+ Request and response must match this */
+#define ICTX_REQUESTVER 0x1
+
+/* Needed for BER-encoding */
+#define ASN1_IA5STRING_TAG 0x16
+
+/***************************************************************************
+ * the ASN.1 struct for the remote audit request and response: *
+ * *
+ * RequestValue ::= SEQUENCE { *
+ * RequestVersion INTEGER, *
+ * ItemList SEQUENCE OF *
+ * Item SEQUENCE { *
+ * ItemVersion INTEGER, *
+ * ItemTag INTEGER, *
+ * LinkValue OCTET STRING SIZE(8), *
+ * Violation BOOLEAN, *
+ * Event INTEGER, *
+ * Qualifier INTEGER, *
+ * Class IA5String, *
+ * Resource IA5String, *
+ * LogString IA5String, *
+ * DatafieldList SEQUENCE OF *
+ * DataField SEQUENCE { *
+ * TYPE INTEGER, *
+ * VALUE IA5STRING *
+ * } *
+ * } *
+ * } *
+ * *
+ * Response ::= SEQUENCE { *
+ * Version INTEGER, *
+ * ResponseCode INTEGER, *
+ * ItemList SEQUENCE OF *
+ * Item SEQUENCE { *
+ * ItemVersion INTEGER, *
+ * ItemTag INTEGER, *
+ * MajorCode INTEGER, *
+ * MinorCode1 INTEGER, *
+ * MinorCode2 INTEGER, *
+ * MinorCode3 INTEGER *
+ * } *
+ * } *
+ ***************************************************************************/
+
+/***************************************************************************
+ * z/OS Remote-services Audit Minor return codes meaning
+
+Major Code Meaning
+---------- ---------------------------------------------------------
+0-14 - MinorCode1 is the SAF return code
+ - MinorCode2 is the RACF return code
+ - MinorCode3 is the RACF reason code
+
+16-20 - MinorCode1 identifies the extended operation request
+ parameter number (see audit request ASN.1 definition):
+ 0 - Item
+ 1 - ItemVersion
+ 2 - ItemTag
+ 3 - LinkValue
+ 4 - Violation
+ 5 - Event
+ 6 - Qualifier
+ 7 - Class
+ 8 - Resource
+ 9 - LogString
+ 10 - DataFieldList
+ 11 - DataField *
+ 12 - TYPE *
+ 13 - VALUE *
+ - MinorCode2 indicates one of the Following:
+ 32 - incorrect length
+ 36 - incorrect value
+ 40 - encoding error
+ - MinorCode3 has no defined meaning
+
+24-100 - MinorCode1 has no defined meaning
+ - MinorCode2 has no defined meaning
+ - MinorCode3 has no defined meaning
+
+* There can be multiple DataField, TYPEs and VALUEs in a request. If any of them is bad
+ you get the same 11, 12 or 13 MinorCode1. There is no further breakdown of which one
+ is bad.
+
+ ***************************************************************************/
+
+/***************************************************************************
+ * Audit Request 'event' field meaning *
+ ***************************************************************************/
+#define ZOS_REMOTE_EVENT_AUTHENTICATION 0x1
+#define ZOS_REMOTE_EVENT_AUTHORIZATION 0x2
+#define ZOS_REMOTE_EVENT_AUTHORIZATION_MAPPING 0x3
+#define ZOS_REMOTE_EVENT_KEY_MGMT 0x4
+#define ZOS_REMOTE_EVENT_POLICY_MGMT 0x5
+#define ZOS_REMOTE_EVENT_ADMIN_CONFIG 0x6
+#define ZOS_REMOTE_EVENT_ADMIN_ACTION 0x7
+
+/***************************************************************************
+ * Audit Request 'qualifier' field meaning *
+ ***************************************************************************/
+#define ZOS_REMOTE_QUALIF_SUCCESS 0x0
+#define ZOS_REMOTE_QUALIF_INFO 0x1
+#define ZOS_REMOTE_QUALIF_WARN 0x2
+#define ZOS_REMOTE_QUALIF_FAIL 0x3
+
+/***************************************************************************
+ * Relocate types for Audit Request *
+ ***************************************************************************/
+/* SAF identifier for bind user */
+#define ZOS_REMOTE_RELOC_SAF_BIND_USER 100
+
+/* Reguestor's bind user identifier */
+#define ZOS_REMOTE_RELOC_REQ_BIND_USER 101
+
+/* Originating security domain */
+#define ZOS_REMOTE_RELOC_ORIG_SECURITY 102
+
+/* Originating registry / realm */
+#define ZOS_REMOTE_RELOC_ORIG_REALM 103
+
+/* Originating user name */
+#define ZOS_REMOTE_RELOC_ORIG_USER 104
+
+/* Mapped security domain */
+#define ZOS_REMOTE_RELOC_MAPPED_SECURITY 105
+
+/* Mapped registry / realm */
+#define ZOS_REMOTE_RELOC_MAPPED_REALM 106
+
+/* Mapped user name */
+#define ZOS_REMOTE_RELOC_MAPPED_USER 107
+
+/* Operation performed */
+#define ZOS_REMOTE_RELOC_OPERATION 108
+
+/* Mechanism / object name */
+#define ZOS_REMOTE_RELOC_OBJECT 109
+
+/* Method / function used */
+#define ZOS_REMOTE_RELOC_FUNCTION 110
+
+/* Key / certificate name */
+#define ZOS_REMOTE_RELOC_CERTIFICATE 111
+
+/* Caller subject initiating security event */
+#define ZOS_REMOTE_RELOC_INITIATING_EVENT 112
+
+/* Date and time security event occurred */
+#define ZOS_REMOTE_RELOC_TIMESTAMP 113
+
+/* Application specific data. (i.e. Other) */
+#define ZOS_REMOTE_RELOC_OTHER 114
+
+/***************************************************************************
+ * z/OS Remote-services Audit Major return codes *
+ ***************************************************************************/
+#define ZOS_REMOTE_MAJOR_SUCCESS 0
+
+/* Event was logged, with warnings */
+#define ZOS_REMOTE_MAJOR_WARNINGMODE 2
+
+/* No logging required
+ No audit controls are set to require it */
+#define ZOS_REMOTE_MAJOR_NOTREQ 3
+
+/* Class not active/ractlisted,
+ covering profile not found or
+ RACF is not installed */
+#define ZOS_REMOTE_MAJOR_UNDETERMINED 4
+
+/* The user does not have authority the R_auditx service.
+ The userid associated with the LDAP server must have
+ at least READ access to the FACILITY class profile IRR.RAUDITX. */
+#define ZOS_REMOTE_MAJOR_UNAUTHORIZED 8
+
+
+/* The R_auditx service returned an unexpected error.
+ Compare the returned minor codes with the SAF RACF codes
+ documented in Security Server Callable Services */
+#define ZOS_REMOTE_MAJOR_RACROUTE 12
+
+/* A value specified in the extended operation request is
+ incorrect or unsupported. Check the returned minor codes
+ to narrow the reason */
+#define ZOS_REMOTE_MAJOR_VAL_ERR 16
+
+/* A DER decoding error was encountered in an item.
+ Processing Terminated. Partial results may be returned */
+#define ZOS_REMOTE_MAJOR_ENC_ERR 20
+
+/* The requestor does not have sufficient authority for the
+ requested function. The userid associated with the LDAP bind
+ user must have at least READ access to the FACILITY class
+ profile IRR.LDAP.REMOTE.AUDIT. */
+#define ZOS_REMOTE_MAJOR_UNSUF_AUTH 24
+
+/* No items are found within the ItemList sequence of the extended
+ operation request, so no response items are returned */
+#define ZOS_REMOTE_MAJOR_EMPTY 28
+
+/* Invalid RequestVersion */
+#define ZOS_REMOTE_MAJOR_INVALID_VER 61
+
+/* An internal error was encountered within the ICTX component */
+#define ZOS_REMOTE_MAJOR_INTERNAL_ERR 100
+
+/***************************************************************************
+ * Some standard sizes for remote audit request items *
+ ***************************************************************************/
+#define ZOS_REMOTE_LINK_VALUE_SIZE 8
+#define ZOS_REMOTE_CLASS_SIZE 8
+#define ZOS_REMOTE_RESOURCE_SIZE 240
+#define ZOS_REMOTE_LOGSTRING_SIZE 200
+
+
+/***************************************************************************
+ * Some standard Error defines *
+ ***************************************************************************/
+#define ICTX_SUCCESS 0x00
+
+/* maybe a temporary failure? */
+#define ICTX_E_TRYAGAIN 0x01
+
+/* permanent failure - abort event submission */
+#define ICTX_E_ABORT 0x02
+
+/* Fatal failure - abort program */
+#define ICTX_E_FATAL 0x03
+
+/* generic error */
+#define ICTX_E_ERROR 0x10
+
+/***************************************************************************
+ * structure representing an z/OS Remote-services session *
+ ***************************************************************************/
+typedef struct opaque
+{
+ char *server;
+ unsigned int port;
+ char *user;
+ char *password;
+ unsigned int timeout;
+ LDAP *ld;
+ int connected;
+} ZOS_REMOTE;
+
+/***************************************************************************
+ * LDAP XOP operations *
+ ***************************************************************************/
+/*
+ * Initializes z/OS Remote-services (LDAP to ITDS) connection,
+ * binds to ITDS Server using configured RACF ID
+ * Args are:
+ * server, bind user, bind password, server port, timeout
+ * Caller must call zos_remote_destroy() to free memory allocation
+ */
+int zos_remote_init(ZOS_REMOTE *, const char *, int, const char *,
+ const char *, int);
+
+/*
+ * Uninitializes z/OS Remote-services (LDAP) connection
+ */
+void zos_remote_destroy(ZOS_REMOTE *);
+
+/*
+ * sync submit request - possibly reconnect to server
+ * if the connection if found to be dead
+ */
+int submit_request_s(ZOS_REMOTE *, BerElement *);
+
+
+#endif /* _ZOS_REMOTE_LDAP_H */
diff --git a/framework/src/audit/audisp/plugins/zos-remote/zos-remote-log.c b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-log.c
new file mode 100644
index 00000000..a272078e
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-log.c
@@ -0,0 +1,109 @@
+/***************************************************************************
+ * Copyright (C) 2007 International Business Machines Corp. *
+ * All Rights Reserved. *
+ * *
+ * 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: *
+ * Klaus Heinrich Kiwi <klausk@br.ibm.com> *
+ ***************************************************************************/
+#include "zos-remote-log.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "auparse.h"
+
+
+static void vlog_prio(int prio, const char *fmt, va_list ap)
+{
+ char *str;
+
+ if (asprintf(&str, "pid=%d: %s", mypid, fmt) != -1) {
+ vsyslog(LOG_DAEMON | prio, str, ap);
+ free(str);
+ }
+}
+
+void log_err(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog_prio(LOG_ERR, fmt, ap);
+ va_end(ap);
+}
+
+void log_warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog_prio(LOG_WARNING, fmt, ap);
+ va_end(ap);
+}
+
+void log_info(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog_prio(LOG_INFO, fmt, ap);
+ va_end(ap);
+}
+
+void _log_debug(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog_prio(LOG_INFO, fmt, ap);
+ va_end(ap);
+}
+
+void _debug_ber(BerElement * ber)
+{
+ struct berval bv;
+
+ if (ber_flatten2(ber, &bv, 0) != -1) {
+ debug_bv(&bv);
+ }
+}
+
+void _debug_bv(struct berval *bv)
+{
+ char *out;
+ char octet[4];
+ ber_len_t i;
+
+ log_debug("---BER value HEX dump (size %u bytes)",
+ (unsigned int) bv->bv_len);
+
+ if (bv->bv_len > 0) {
+ out = (char *) calloc((3 * (bv->bv_len)) + 1, sizeof(char));
+ if (!out) return;
+
+ for (i = 1; i <= bv->bv_len; i++) {
+ snprintf(octet, 4, "%02x ",
+ (unsigned char) bv->bv_val[i - 1]);
+ strcat(out, octet);
+ }
+ log_debug(out);
+ free(out);
+ }
+}
+
+
diff --git a/framework/src/audit/audisp/plugins/zos-remote/zos-remote-log.h b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-log.h
new file mode 100644
index 00000000..c5722cbe
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-log.h
@@ -0,0 +1,58 @@
+/***************************************************************************
+ * Copyright (C) 2007 International Business Machines Corp. *
+ * All Rights Reserved. *
+ * *
+ * 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: *
+ * Klaus Heinrich Kiwi <klausk@br.ibm.com> *
+ ***************************************************************************/
+
+#ifndef _ZOS_REMOTE_LOG_H
+#define _ZOS_REMOTE_LOG_H
+
+#include "zos-remote-ldap.h"
+
+#include <syslog.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <lber.h>
+
+extern pid_t mypid;
+
+void log_err(const char *, ...);
+void log_warn(const char *, ...);
+void log_info(const char *, ...);
+void _log_debug(const char *, ...);
+void _debug_bv(struct berval *);
+void _debug_ber(BerElement *);
+
+#ifdef DEBUG
+
+#define log_debug(fmt, ...) _log_debug(fmt, ## __VA_ARGS__)
+#define debug_bv(bv) _debug_bv(bv)
+#define debug_ber(ber) _debug_ber(ber)
+
+#else
+
+#define log_debug(fmt, ...)
+#define debug_bv(bv)
+#define debug_ber(ber)
+
+#endif /* DEBUG */
+
+
+#endif /* _ZOS_REMOTE_LOG_H */
diff --git a/framework/src/audit/audisp/plugins/zos-remote/zos-remote-plugin.c b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-plugin.c
new file mode 100644
index 00000000..8234a273
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-plugin.c
@@ -0,0 +1,580 @@
+/***************************************************************************
+* Copyright (C) 2007 International Business Machines Corp. *
+* All Rights Reserved. *
+* *
+* 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: *
+* Klaus Heinrich Kiwi <klausk@br.ibm.com> *
+***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <limits.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <pthread.h>
+#include <lber.h>
+#include <netinet/in.h>
+#ifdef HAVE_LIBCAP_NG
+#include <cap-ng.h>
+#endif
+#include "auparse.h"
+#include "zos-remote-log.h"
+#include "zos-remote-ldap.h"
+#include "zos-remote-config.h"
+#include "zos-remote-queue.h"
+
+#define UNUSED(x) (void)(x)
+
+/*
+ * Global vars
+ */
+volatile int stop = 0;
+volatile int hup = 0;
+volatile ZOS_REMOTE zos_remote_inst;
+static plugin_conf_t conf;
+static const char *def_config_file = "/etc/audisp/zos-remote.conf";
+static pthread_t submission_thread;
+pid_t mypid = 0;
+
+/*
+ * SIGTERM handler
+ */
+static void term_handler(int sig)
+{
+ UNUSED(sig);
+ log_info("Got Termination signal - shutting down plugin");
+ stop = 1;
+ nudge_queue();
+}
+
+/*
+ * SIGHUP handler - re-read config, reconnect to ITDS
+ */
+static void hup_handler(int sig)
+{
+ UNUSED(sig);
+ log_info("Got Hangup signal - flushing plugin configuration");
+ hup = 1;
+ nudge_queue();
+}
+
+/*
+ * SIGALRM handler - help force exit when terminating daemon
+ */
+static void alarm_handler(int sig)
+{
+ UNUSED(sig);
+ log_err("Timeout waiting for submission thread - Aborting (some events may have been dropped)");
+ pthread_cancel(submission_thread);
+}
+
+/*
+ * The submission thread
+ * It's job is to dequeue the events from the queue
+ * and sync submit them to ITDS
+ */
+static void *submission_thread_main(void *arg)
+{
+ int rc;
+
+ UNUSED(arg);
+ log_debug("Starting event submission thread");
+
+ rc = zos_remote_init(&zos_remote_inst, conf.server,
+ conf.port, conf.user,
+ conf.password,
+ conf.timeout);
+
+ if (rc != ICTX_SUCCESS) {
+ log_err("Error - Failed to initialize session to z/OS ITDS Server");
+ stop = 1;
+ return 0;
+ }
+
+ while (stop == 0) {
+ /* block until we have an event */
+ BerElement *ber = dequeue();
+
+ if (ber == NULL) {
+ if (hup) {
+ break;
+ }
+ continue;
+ }
+ debug_ber(ber);
+ rc = submit_request_s(&zos_remote_inst, ber);
+ if (rc == ICTX_E_FATAL) {
+ log_err("Error - Fatal error in event submission. Aborting");
+ stop = 1;
+ } else if (rc != ICTX_SUCCESS) {
+ log_warn("Warning - Event submission failure - event dropped");
+ }
+ else {
+ log_debug("Event submission success");
+ }
+ ber_free(ber, 1); /* also free BER buffer */
+ }
+ log_debug("Stopping event submission thread");
+ zos_remote_destroy(&zos_remote_inst);
+
+ return 0;
+}
+
+
+/*
+ * auparse library callback that's called when an event is ready
+ */
+void
+push_event(auparse_state_t * au, auparse_cb_event_t cb_event_type,
+ void *user_data)
+{
+ int rc;
+ BerElement *ber;
+ int qualifier;
+ char timestamp[26];
+ char linkValue[ZOS_REMOTE_LINK_VALUE_SIZE];
+ char logString[ZOS_REMOTE_LOGSTRING_SIZE];
+ unsigned long linkValue_tmp;
+
+ UNUSED(user_data);
+ if (cb_event_type != AUPARSE_CB_EVENT_READY)
+ return;
+
+ const au_event_t *e = auparse_get_timestamp(au);
+ if (e == NULL)
+ return;
+ /*
+ * we have an event. Each record will result in a different 'Item'
+ * (refer ASN.1 definition in zos-remote-ldap.h)
+ */
+
+ /*
+ * Create a new BER element to encode the request
+ */
+ ber = ber_alloc_t(LBER_USE_DER);
+ if (ber == NULL) {
+ log_err("Error allocating memory for BER element");
+ goto fatal;
+ }
+
+ /*
+ * Collect some information to fill in every item
+ */
+ const char *node = auparse_get_node(au);
+ const char *orig_type = auparse_find_field(au, "type");
+ /* roll back event to get 'success' */
+ auparse_first_record(au);
+ const char *success = auparse_find_field(au, "success");
+ /* roll back event to get 'res' */
+ auparse_first_record(au);
+ const char *res = auparse_find_field(au, "res");
+
+ /* check if this event is a success or failure one */
+ if (success) {
+ if (strncmp(success, "0", 1) == 0 ||
+ strncmp(success, "no", 2) == 0)
+ qualifier = ZOS_REMOTE_QUALIF_FAIL;
+ else
+ qualifier = ZOS_REMOTE_QUALIF_SUCCESS;
+ } else if (res) {
+ if (strncmp(res, "0", 1) == 0
+ || strncmp(res, "failed", 6) == 0)
+ qualifier = ZOS_REMOTE_QUALIF_FAIL;
+ else
+ qualifier = ZOS_REMOTE_QUALIF_SUCCESS;
+ } else
+ qualifier = ZOS_REMOTE_QUALIF_INFO;
+
+ /* get timestamp text */
+ ctime_r(&e->sec, timestamp);
+ timestamp[24] = '\0'; /* strip \n' */
+
+ /* prepare linkValue which will be used for every item */
+ linkValue_tmp = htonl(e->serial); /* padronize to use network
+ * byte order
+ */
+ memset(&linkValue, 0, ZOS_REMOTE_LINK_VALUE_SIZE);
+ memcpy(&linkValue, &linkValue_tmp, sizeof(unsigned long));
+
+ /*
+ * Prepare the logString with some meaningful text
+ * We assume the first record type found is the
+ * 'originating' audit record
+ */
+ sprintf(logString, "Linux (%s): type: %s", node, orig_type);
+ free((void *)node);
+
+ /*
+ * Start writing to BER element.
+ * There's only one field (version) out of the item sequence.
+ * Also open item sequence
+ */
+ rc = ber_printf(ber, "{i{", ICTX_REQUESTVER);
+ if (rc < 0)
+ goto skip_event;
+
+ /*
+ * Roll back to first record and iterate through all records
+ */
+ auparse_first_record(au);
+ do {
+ const char *type = auparse_find_field(au, "type");
+ if (type == NULL)
+ goto skip_event;
+
+ log_debug("got record: %s", auparse_get_record_text(au));
+
+ /*
+ * First field is item Version, same as global version
+ */
+ rc = ber_printf(ber, "{i", ICTX_REQUESTVER);
+
+ /*
+ * Second field is the itemTag
+ * use our internal event counter, increasing it
+ */
+ rc |= ber_printf(ber, "i", conf.counter++);
+
+ /*
+ * Third field is the linkValue
+ * using ber_put_ostring since it is not null-terminated
+ */
+ rc |= ber_put_ostring(ber, linkValue,
+ ZOS_REMOTE_LINK_VALUE_SIZE,
+ LBER_OCTETSTRING);
+ /*
+ * Fourth field is the violation
+ * Don't have anything better yet to put here
+ */
+ rc |= ber_printf(ber, "b", 0);
+
+ /*
+ * Fifth field is the event.
+ * FIXME: this might be the place to switch on the
+ * audit record type and map to a more meaningful
+ * SMF type 83, subtype 4 event here
+ */
+ rc |= ber_printf(ber, "i", ZOS_REMOTE_EVENT_AUTHORIZATION);
+
+ /*
+ * Sixth field is the qualifier. We map 'success' or
+ * 'res' to this field
+ */
+ rc |= ber_printf(ber, "i", qualifier);
+
+ /*
+ * Seventh field is the Class
+ * always use '@LINUX' for this version
+ * max size ZOS_REMOTE_CLASS_SIZE
+ */
+ rc |= ber_printf(ber, "t", ASN1_IA5STRING_TAG);
+ rc |= ber_printf(ber, "s", "@LINUX");
+
+ /*
+ * Eighth field is the resource
+ * use the record type (name) as the resource
+ * max size ZOS_REMOTE_RESOURCE_SIZE
+ */
+ rc |= ber_printf(ber, "t", ASN1_IA5STRING_TAG);
+ rc |= ber_printf(ber, "s", type);
+
+ /*
+ * Nineth field is the LogString
+ * we try to put something meaningful here
+ * we also start the relocations sequence
+ */
+ rc |= ber_printf(ber, "t", ASN1_IA5STRING_TAG);
+ rc |= ber_printf(ber, "s{", logString);
+
+ /*
+ * Now we start adding the relocations.
+ * Let's add the timestamp as the first one
+ * so it's out of the field loop
+ */
+ rc |= ber_printf(ber, "{i", ZOS_REMOTE_RELOC_TIMESTAMP);
+ rc |= ber_printf(ber, "t", ASN1_IA5STRING_TAG);
+ rc |= ber_printf(ber, "s}", timestamp);
+
+ /*
+ * Check that encoding is going OK until now
+ */
+ if (rc < 0)
+ goto skip_event;
+
+ /*
+ * Now go to first field,
+ * and iterate through all fields
+ */
+ auparse_first_field(au);
+ do {
+ /*
+ * we set a maximum of 1024 chars for
+ * relocation data (field=value pairs)
+ * Hopefuly this wont overflow too often
+ */
+ char data[1024];
+ const char *name = auparse_get_field_name(au);
+ const char *value = auparse_interpret_field(au);
+ if (name == NULL || value == NULL)
+ goto skip_event;
+
+ /*
+ * First reloc field is the Relocation type
+ * We use 'OTHER' here since we don't have
+ * anything better
+ */
+ rc |= ber_printf(ber, "{i", ZOS_REMOTE_RELOC_OTHER);
+
+ /*
+ * Second field is the relocation data
+ * We use a 'name=value' pair here
+ * Use up to 1023 chars (one char left for '\0')
+ */
+ snprintf(data, 1023, "%s=%s", name, value);
+ rc |= ber_printf(ber, "t", ASN1_IA5STRING_TAG);
+ rc |= ber_printf(ber, "s}", data);
+
+ /*
+ * Check encoding status
+ */
+ if (rc < 0)
+ goto skip_event;
+ } while (auparse_next_field(au) > 0);
+
+ /*
+ * After adding all relocations we are done with
+ * this item - finalize relocs and item
+ */
+ rc |= ber_printf(ber, "}}");
+
+ /*
+ * Check if we are doing well with encoding
+ */
+ if (rc < 0)
+ goto skip_event;
+
+ } while (auparse_next_record(au) > 0);
+
+ /*
+ * We have all items in - finalize item sequence & request
+ */
+ rc |= ber_printf(ber, "}}");
+
+ /*
+ * Check if everything went alright with encoding
+ */
+ if (rc < 0)
+ goto skip_event;
+
+ /*
+ * finally, enqueue request and let the other
+ * thread process it
+ */
+ log_debug("Encoding done, enqueuing event");
+ enqueue(ber);
+
+ return;
+
+skip_event:
+ log_warn("Warning - error encoding request, skipping event");
+ ber_free(ber, 1); /* free it since we're not enqueuing it */
+ return;
+
+fatal:
+ log_err("Error - Fatal error while encoding request. Aborting");
+ stop = 1;
+}
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ const char *cpath;
+ char buf[1024];
+ struct sigaction sa;
+ sigset_t ss;
+ auparse_state_t *au;
+ ssize_t len;
+
+ mypid = getpid();
+
+ log_info("starting with pid=%d", mypid);
+
+ /*
+ * install signal handlers
+ */
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = term_handler;
+ sigaction(SIGTERM, &sa, NULL);
+ sa.sa_handler = hup_handler;
+ sigaction(SIGHUP, &sa, NULL);
+ sa.sa_handler = alarm_handler;
+ sigaction(SIGALRM, &sa, NULL);
+
+ /*
+ * the main program accepts a single (optional) argument:
+ * it's configuration file (this is NOT the plugin configuration
+ * usually located at /etc/audisp/plugin.d)
+ * We use the default (def_config_file) if no arguments are given
+ */
+ if (argc == 1) {
+ cpath = def_config_file;
+ log_warn("No configuration file specified - using default (%s)", cpath);
+ } else if (argc == 2) {
+ cpath = argv[1];
+ log_info("Using configuration file: %s", cpath);
+ } else {
+ log_err("Error - invalid number of parameters passed. Aborting");
+ return 1;
+ }
+
+ /* initialize record counter */
+ conf.counter = 1;
+
+ /* initialize configuration with default values */
+ plugin_clear_config(&conf);
+
+ /* initialize the submission queue */
+ if (init_queue(conf.q_depth) != 0) {
+ log_err("Error - Can't initialize event queue. Aborting");
+ return -1;
+ }
+
+#ifdef HAVE_LIBCAP_NG
+ // Drop all capabilities
+ capng_clear(CAPNG_SELECT_BOTH);
+ capng_apply(CAPNG_SELECT_BOTH);
+#endif
+
+ /* set stdin to O_NONBLOCK */
+ if (fcntl(0, F_SETFL, O_NONBLOCK) == -1) {
+ log_err("Error - Can't set input to Non-blocking mode: %s. Aborting",
+ strerror(errno));
+ return -1;
+ }
+
+ do {
+
+ hup = 0; /* don't flush unless hup == 1 */
+
+ /*
+ * initialization is done in 4 steps:
+ */
+
+ /*
+ * load configuration and
+ * increase queue depth if needed
+ */
+ rc = plugin_load_config(&conf, cpath);
+ if (rc != 0) {
+ log_err("Error - Can't load configuration. Aborting");
+ return -1;
+ }
+ increase_queue_depth(conf.q_depth); /* 1 */
+
+ /* initialize auparse */
+ au = auparse_init(AUSOURCE_FEED, 0); /* 2 */
+ if (au == NULL) {
+ log_err("Error - exiting due to auparse init errors");
+ return -1;
+ }
+
+ /*
+ * Block signals for everyone,
+ * Initialize submission thread, and
+ * Unblock signals for this thread
+ */
+ sigfillset(&ss);
+ pthread_sigmask(SIG_BLOCK, &ss, NULL);
+ pthread_create(&submission_thread, NULL,
+ submission_thread_main, NULL);
+ pthread_sigmask(SIG_UNBLOCK, &ss, NULL); /* 3 */
+
+ /* add our event consumer callback */
+ auparse_add_callback(au, push_event, NULL, NULL); /* 4 */
+
+ /* main loop */
+ while (hup == 0 && stop == 0) {
+ fd_set rfds;
+ struct timeval tv;
+
+ FD_ZERO(&rfds);
+ FD_SET(0, &rfds);
+ tv.tv_sec = 5;
+ tv.tv_usec = 0;
+ rc = select(1, &rfds, NULL, NULL, &tv);
+ if (rc == -1) {
+ if (errno == EINTR) {
+ log_debug("Select call interrupted");
+ continue;
+ }
+ else {
+ log_err("Error - Fatal error while monitoring input: %s. Aborting",
+ strerror(errno));
+ stop = 1;
+ }
+ }
+ else if (rc) {
+ len = read(0, buf, 1024);
+ if (len > 0)
+ /* let our callback know of the new data */
+ auparse_feed(au, buf, len);
+ else if (len == 0) {
+ log_debug("End of input - Exiting");
+ stop = 1;
+ }
+ else {
+ /* ignore interrupted call or empty pipe */
+ if (errno != EINTR && errno != EAGAIN) {
+ log_err("Error - Fatal error while reading input: %s. Aborting",
+ strerror(errno));
+ stop = 1;
+ }
+ else {
+ log_debug("Ignoring read interruption: %s",
+ strerror(errno));
+ }
+ }
+ }
+ }
+ /* flush everything, in order */
+ auparse_flush_feed(au); /* 4 */
+ alarm(10); /* 10 seconds to clear the queue */
+ pthread_join(submission_thread, NULL); /* 3 */
+ alarm(0); /* cancel any pending alarm */
+ auparse_destroy(au); /* 2 */
+ plugin_free_config(&conf); /* 1 */
+ }
+ while (hup && stop == 0);
+
+ /* destroy queue before leaving */
+ destroy_queue();
+
+ log_info("Exiting");
+
+ return 0;
+}
diff --git a/framework/src/audit/audisp/plugins/zos-remote/zos-remote-queue.c b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-queue.c
new file mode 100644
index 00000000..8071dca4
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-queue.c
@@ -0,0 +1,144 @@
+/***************************************************************************
+ * Copyright (C) 2007 International Business Machines Corp. *
+ * All Rights Reserved. *
+ * *
+ * 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: *
+ * Klaus Heinrich Kiwi <klausk@br.ibm.com> *
+ * based on code by Steve Grubb <sgrubb@redhat.com> *
+ ***************************************************************************/
+
+#include "zos-remote-queue.h"
+
+#include <stdlib.h>
+#include <pthread.h>
+#include <syslog.h>
+#include "zos-remote-log.h"
+
+static volatile BerElement **q;
+static pthread_mutex_t queue_lock;
+static pthread_cond_t queue_nonempty;
+static unsigned int q_next, q_last, q_depth;
+
+
+int init_queue(unsigned int size)
+{
+ unsigned int i;
+
+ q_next = 0;
+ q_last = 0;
+ q_depth = size;
+ q = malloc(q_depth * sizeof(BerElement *));
+ if (q == NULL)
+ return -1;
+
+ for (i=0; i<q_depth; i++)
+ q[i] = NULL;
+
+ /* Setup IPC mechanisms */
+ pthread_mutex_init(&queue_lock, NULL);
+ pthread_cond_init(&queue_nonempty, NULL);
+
+ return 0;
+}
+
+void enqueue(BerElement *ber)
+{
+ unsigned int n, retry_cnt = 0;
+
+retry:
+ /* We allow 3 retries and then its over */
+ if (retry_cnt > 3) {
+ log_err("queue is full - dropping event");
+ return;
+ }
+ pthread_mutex_lock(&queue_lock);
+
+ /* OK, have lock add event */
+ n = q_next%q_depth;
+ if (q[n] == NULL) {
+ q[n] = ber;
+ q_next = (n+1) % q_depth;
+ pthread_cond_signal(&queue_nonempty);
+ pthread_mutex_unlock(&queue_lock);
+ } else {
+ pthread_mutex_unlock(&queue_lock);
+ pthread_yield(); /* Let dequeue thread run to clear queue */
+ retry_cnt++;
+ goto retry;
+ }
+}
+
+BerElement *dequeue(void)
+{
+ BerElement *ber;
+ unsigned int n;
+
+ /* Wait until its got something in it */
+ pthread_mutex_lock(&queue_lock);
+ n = q_last%q_depth;
+ if (q[n] == NULL) {
+ pthread_cond_wait(&queue_nonempty, &queue_lock);
+ n = q_last%q_depth;
+ }
+
+ /* OK, grab the next event */
+ if (q[n] != NULL) {
+ ber = (BerElement *) q[n];
+ q[n] = NULL;
+ q_last = (n+1) % q_depth;
+ } else
+ ber = NULL;
+
+ pthread_mutex_unlock(&queue_lock);
+
+ /* Process the event */
+ return ber;
+}
+
+void nudge_queue(void)
+{
+ pthread_cond_signal(&queue_nonempty);
+}
+
+void increase_queue_depth(unsigned int size)
+{
+ pthread_mutex_lock(&queue_lock);
+ if (size > q_depth) {
+ unsigned int i;
+ void *tmp_q;
+
+ tmp_q = realloc(q, size * sizeof(BerElement *));
+ q = tmp_q;
+ for (i=q_depth; i<size; i++)
+ q[i] = NULL;
+ q_depth = size;
+ }
+ pthread_mutex_unlock(&queue_lock);
+}
+
+void destroy_queue(void)
+{
+ unsigned int i;
+
+ for (i=0; i<q_depth; i++) {
+ ber_free(q[i], 1);
+ }
+
+ free(q);
+}
+
diff --git a/framework/src/audit/audisp/plugins/zos-remote/zos-remote-queue.h b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-queue.h
new file mode 100644
index 00000000..c653747a
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/zos-remote-queue.h
@@ -0,0 +1,38 @@
+/***************************************************************************
+ * Copyright (C) 2007 International Business Machines Corp. *
+ * All Rights Reserved. *
+ * *
+ * 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: *
+ * Klaus Heinrich Kiwi <klausk@br.ibm.com> *
+ * based on code by Steve Grubb <sgrubb@redhat.com> *
+ ***************************************************************************/
+
+#ifndef _ZOS_REMOTE_QUEUE_H
+#define _ZOS_REMOTE_QUEUE_H
+
+#include <lber.h>
+
+int init_queue(unsigned int size);
+void enqueue(BerElement *);
+BerElement *dequeue(void);
+void nudge_queue(void);
+void increase_queue_depth(unsigned int size);
+void destroy_queue(void);
+
+#endif /* _ZOS_REMOTE_QUEUE_H */
+
diff --git a/framework/src/audit/audisp/plugins/zos-remote/zos-remote.conf b/framework/src/audit/audisp/plugins/zos-remote/zos-remote.conf
new file mode 100644
index 00000000..8cf85f71
--- /dev/null
+++ b/framework/src/audit/audisp/plugins/zos-remote/zos-remote.conf
@@ -0,0 +1,10 @@
+## This is the configuration file for the audispd-zos-remote
+## Audit dispatcher plugin.
+## See zos-remote.conf(5) for more information
+
+server = zos_server.localdomain
+port = 389
+user = RACF_ID
+password = racf_password
+timeout = 15
+q_depth = 64
diff --git a/framework/src/audit/audisp/queue.c b/framework/src/audit/audisp/queue.c
new file mode 100644
index 00000000..477b9d1b
--- /dev/null
+++ b/framework/src/audit/audisp/queue.c
@@ -0,0 +1,226 @@
+/* queue.c --
+ * Copyright 2007,2013,2015 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Authors:
+ * Steve Grubb <sgrubb@redhat.com>
+ */
+
+#include "config.h"
+#include <pthread.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include "queue.h"
+
+static volatile event_t **q;
+static pthread_mutex_t queue_lock;
+static pthread_cond_t queue_nonempty;
+static unsigned int q_next, q_last, q_depth, processing_suspended;
+static const char *SINGLE = "1";
+static const char *HALT = "0";
+static int queue_full_warning = 0;
+extern volatile int hup;
+#define QUEUE_FULL_LIMIT 5
+
+void reset_suspended(void)
+{
+ processing_suspended = 0;
+ queue_full_warning = 0;
+}
+
+int init_queue(unsigned int size)
+{
+ unsigned int i;
+
+ processing_suspended = 0;
+ q_next = 0;
+ q_last = 0;
+ q_depth = size;
+ q = malloc(q_depth * sizeof(event_t *));
+ if (q == NULL)
+ return -1;
+
+ for (i=0; i<q_depth; i++)
+ q[i] = NULL;
+
+ /* Setup IPC mechanisms */
+ pthread_mutex_init(&queue_lock, NULL);
+ pthread_cond_init(&queue_nonempty, NULL);
+
+ return 0;
+}
+
+static void change_runlevel(const char *level)
+{
+ char *argv[3];
+ int pid;
+ static const char *init_pgm = "/sbin/init";
+
+ pid = fork();
+ if (pid < 0) {
+ syslog(LOG_ALERT, "Audispd failed to fork switching runlevels");
+ return;
+ }
+ if (pid) /* Parent */
+ return;
+ /* Child */
+ argv[0] = (char *)init_pgm;
+ argv[1] = (char *)level;
+ argv[2] = NULL;
+ execve(init_pgm, argv, NULL);
+ syslog(LOG_ALERT, "Audispd failed to exec %s", init_pgm);
+ exit(1);
+}
+
+static void do_overflow_action(struct daemon_conf *config)
+{
+ switch (config->overflow_action)
+ {
+ case O_IGNORE:
+ break;
+ case O_SYSLOG:
+ if (queue_full_warning < QUEUE_FULL_LIMIT) {
+ syslog(LOG_ERR,
+ "queue is full - dropping event");
+ queue_full_warning++;
+ if (queue_full_warning == QUEUE_FULL_LIMIT)
+ syslog(LOG_ERR,
+ "audispd queue full reporting "
+ "limit reached - ending "
+ "dropped event notifications");
+ }
+ break;
+ case O_SUSPEND:
+ syslog(LOG_ALERT,
+ "Audispd is suspending event processing due to overflowing its queue.");
+ processing_suspended = 1;
+ break;
+ case O_SINGLE:
+ syslog(LOG_ALERT,
+ "Audisp is now changing the system to single user mode due to overflowing its queue");
+ change_runlevel(SINGLE);
+ break;
+ case O_HALT:
+ syslog(LOG_ALERT,
+ "Audispd is now halting the system due to overflowing its queue");
+ change_runlevel(HALT);
+ break;
+ default:
+ syslog(LOG_ALERT, "Unknown overflow action requested");
+ break;
+ }
+}
+
+void enqueue(event_t *e, struct daemon_conf *config)
+{
+ unsigned int n, retry_cnt = 0;
+
+ if (processing_suspended) {
+ free(e);
+ return;
+ }
+
+retry:
+ // We allow 3 retries and then its over
+ if (retry_cnt > 3) {
+ do_overflow_action(config);
+ free(e);
+ return;
+ }
+ pthread_mutex_lock(&queue_lock);
+
+ // OK, have lock add event
+ n = q_next%q_depth;
+ if (q[n] == NULL) {
+ q[n] = e;
+ q_next = (n+1) % q_depth;
+ pthread_cond_signal(&queue_nonempty);
+ pthread_mutex_unlock(&queue_lock);
+ } else {
+ pthread_mutex_unlock(&queue_lock);
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 2 * 1000 * 1000; // 2 milliseconds
+ nanosleep(&ts, NULL); /* Let other thread try to log it. */
+ retry_cnt++;
+ goto retry;
+ }
+}
+
+event_t *dequeue(void)
+{
+ event_t *e;
+ unsigned int n;
+
+ // Wait until its got something in it
+ pthread_mutex_lock(&queue_lock);
+ if (hup) {
+ pthread_mutex_unlock(&queue_lock);
+ return NULL;
+ }
+ n = q_last%q_depth;
+ if (q[n] == NULL) {
+ pthread_cond_wait(&queue_nonempty, &queue_lock);
+ n = q_last%q_depth;
+ }
+
+ // OK, grab the next event
+ if (q[n] != NULL) {
+ e = (event_t *)q[n];
+ q[n] = NULL;
+ q_last = (n+1) % q_depth;
+ } else
+ e = NULL;
+
+ pthread_mutex_unlock(&queue_lock);
+
+ // Process the event
+ return e;
+}
+
+void nudge_queue(void)
+{
+ pthread_cond_signal(&queue_nonempty);
+}
+
+void increase_queue_depth(unsigned int size)
+{
+ pthread_mutex_lock(&queue_lock);
+ if (size > q_depth) {
+ int i;
+ void *tmp_q;
+
+ tmp_q = realloc(q, size * sizeof(event_t *));
+ q = tmp_q;
+ for (i=q_depth; i<size; i++)
+ q[i] = NULL;
+ q_depth = size;
+ }
+ pthread_mutex_unlock(&queue_lock);
+}
+
+void destroy_queue(void)
+{
+ unsigned int i;
+
+ for (i=0; i<q_depth; i++)
+ free((void *)q[i]);
+
+ free(q);
+}
+
diff --git a/framework/src/audit/audisp/queue.h b/framework/src/audit/audisp/queue.h
new file mode 100644
index 00000000..b209150f
--- /dev/null
+++ b/framework/src/audit/audisp/queue.h
@@ -0,0 +1,45 @@
+/* queue.h --
+ * Copyright 2007 Red Hat Inc., Durham, North Carolina.
+ * All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Authors:
+ * Steve Grubb <sgrubb@redhat.com>
+ */
+
+#ifndef QUEUE_HEADER
+#define QUEUE_HEADER
+
+#include "libaudit.h"
+#include "audispd-config.h"
+
+typedef struct event
+{
+ struct audit_dispatcher_header hdr;
+ char data[MAX_AUDIT_MESSAGE_LENGTH];
+} event_t;
+
+
+void reset_suspended(void);
+int init_queue(unsigned int size);
+void enqueue(event_t *e, struct daemon_conf *config);
+event_t *dequeue(void);
+void nudge_queue(void);
+void increase_queue_depth(unsigned int size);
+void destroy_queue(void);
+
+#endif
+