chiark / gitweb /
journald: if available pull audit messages from the kernel into journal logs
authorLennart Poettering <lennart@poettering.net>
Mon, 3 Nov 2014 19:58:24 +0000 (20:58 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 3 Nov 2014 20:51:28 +0000 (21:51 +0100)
Makefile.am
src/journal/journald-audit.c [new file with mode: 0644]
src/journal/journald-audit.h [new file with mode: 0644]
src/journal/journald-server.c
src/journal/journald-server.h
src/shared/missing.h
units/systemd-journald-audit.socket [new file with mode: 0644]
units/systemd-journald.service.in

index 3800a2235307e22c26f71b486ca00a8606bb5dab..ff5f61b5856749afbbe8b1923c4c21f84c9e9ee8 100644 (file)
@@ -4073,6 +4073,8 @@ libsystemd_journal_core_la_SOURCES = \
        src/journal/journald-wall.h \
        src/journal/journald-native.c \
        src/journal/journald-native.h \
        src/journal/journald-wall.h \
        src/journal/journald-native.c \
        src/journal/journald-native.h \
+       src/journal/journald-audit.c \
+       src/journal/journald-audit.h \
        src/journal/journald-rate-limit.c \
        src/journal/journald-rate-limit.h \
        src/journal/journal-internal.h
        src/journal/journald-rate-limit.c \
        src/journal/journald-rate-limit.h \
        src/journal/journal-internal.h
@@ -4224,7 +4226,8 @@ bin_PROGRAMS += \
 
 dist_systemunit_DATA += \
        units/systemd-journald.socket \
 
 dist_systemunit_DATA += \
        units/systemd-journald.socket \
-       units/systemd-journald-dev-log.socket
+       units/systemd-journald-dev-log.socket \
+       units/systemd-journald-audit.socket
 
 nodist_systemunit_DATA += \
        units/systemd-journald.service \
 
 nodist_systemunit_DATA += \
        units/systemd-journald.service \
@@ -4243,7 +4246,8 @@ dist_catalog_DATA = \
 
 SOCKETS_TARGET_WANTS += \
        systemd-journald.socket \
 
 SOCKETS_TARGET_WANTS += \
        systemd-journald.socket \
-       systemd-journald-dev-log.socket
+       systemd-journald-dev-log.socket \
+       systemd-journald-audit.socket
 
 SYSINIT_TARGET_WANTS += \
        systemd-journald.service \
 
 SYSINIT_TARGET_WANTS += \
        systemd-journald.service \
diff --git a/src/journal/journald-audit.c b/src/journal/journald-audit.c
new file mode 100644 (file)
index 0000000..fe0eec8
--- /dev/null
@@ -0,0 +1,489 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  systemd 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.
+
+  systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "missing.h"
+#include "journald-audit.h"
+
+typedef struct MapField {
+        const char *audit_field;
+        const char *journal_field;
+        int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov);
+} MapField;
+
+static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+        _cleanup_free_ char *c = NULL;
+        size_t l = 0, allocated = 0;
+        const char *e;
+
+        assert(field);
+        assert(p);
+        assert(iov);
+        assert(n_iov);
+
+        l = strlen(field);
+        allocated = l + 1;
+        c = malloc(allocated);
+        if (!c)
+                return -ENOMEM;
+
+        memcpy(c, field, l);
+        for (e = *p; *e != ' ' && *e != 0; e++) {
+                if (!GREEDY_REALLOC(c, allocated, l+2))
+                        return -ENOMEM;
+
+                c[l++] = *e;
+        }
+
+        c[l] = 0;
+
+        if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
+                return -ENOMEM;
+
+        (*iov)[*n_iov].iov_base = c;
+        (*iov)[*n_iov].iov_len = l;
+        (*n_iov) ++;
+
+        *p = e;
+        c = NULL;
+
+        return 1;
+}
+
+static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+        _cleanup_free_ char *c = NULL;
+        const char *s, *e;
+        size_t l;
+
+        assert(field);
+        assert(p);
+        assert(iov);
+        assert(n_iov);
+
+        /* The kernel formats string fields in one of two formats. */
+
+        if (**p == '"') {
+                /* Normal quoted syntax */
+                s = *p + 1;
+                e = strchr(s, '"');
+                if (!e)
+                        return 0;
+
+                l = strlen(field) + (e - s);
+                c = malloc(l+1);
+                if (!c)
+                        return -ENOMEM;
+
+                *((char*) mempcpy(stpcpy(c, field), s, e - s)) = 0;
+
+                e += 1;
+
+        } else if (unhexchar(**p) >= 0) {
+                /* Hexadecimal escaping */
+                size_t allocated = 0;
+
+                l = strlen(field);
+                allocated = l + 2;
+                c = malloc(allocated);
+                if (!c)
+                        return -ENOMEM;
+
+                memcpy(c, field, l);
+                for (e = *p; *e != ' ' && *e != 0; e += 2) {
+                        int a, b;
+
+                        a = unhexchar(e[0]);
+                        if (a < 0)
+                                return 0;
+
+                        b = unhexchar(e[1]);
+                        if (b < 0)
+                                return 0;
+
+                        if (!GREEDY_REALLOC(c, allocated, l+2))
+                                return -ENOMEM;
+
+                        c[l++] = (char) ((uint8_t) a << 4 | (uint8_t) b);
+                }
+
+                c[l] = 0;
+        } else
+                return 0;
+
+        if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
+                return -ENOMEM;
+
+        (*iov)[*n_iov].iov_base = c;
+        (*iov)[*n_iov].iov_len = l;
+        (*n_iov) ++;
+
+        *p = e;
+        c = NULL;
+
+        return 1;
+}
+
+static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+        const char *e, *f;
+        char *c, *t;
+        int r;
+
+        /* Implements fallback mappings for all fields we don't know */
+
+        for (e = *p; e < *p + 16; e++) {
+
+                if (*e == 0 || *e == ' ')
+                        return 0;
+
+                if (*e == '=')
+                        break;
+
+                if (!((*e >= 'a' && *e <= 'z') ||
+                      (*e >= 'A' && *e <= 'Z') ||
+                      (*e >= '0' && *e <= '9') ||
+                      (*e == '_')))
+                        return 0;
+        }
+
+        if (e <= *p || e >= *p + 16)
+                return 0;
+
+        c = alloca(strlen(prefix) + (e - *p) + 2);
+
+        t = stpcpy(c, prefix);
+        for (f = *p; f < e; f++)
+                *(t++) = *f >= 'a' && *f <= 'z' ? ((*f - 'a') + 'A') : *f;
+        strcpy(t, "=");
+
+        e ++;
+
+        r = map_simple_field(c, &e, iov, n_iov_allocated, n_iov);
+        if (r < 0)
+                return r;
+
+        *p = e;
+        return r;
+}
+
+/* Kernel fields are those occuring in the audit string before
+ * msg='. All of these fields are trusted, hence carry the "_" prefix.
+ * We try to translate the fields we know into our native names. The
+ * other's are generically mapped to _AUDIT_FIELD_XYZ= */
+static const MapField map_fields_kernel[] = {
+
+        /* First, we map certain well-known audit fields into native
+         * well-known fields */
+        { "pid=",       "_PID=",                   map_simple_field },
+        { "ppid=",      "_PPID=",                  map_simple_field },
+        { "uid=",       "_UID=",                   map_simple_field },
+        { "euid=",      "_EUID=",                  map_simple_field },
+        { "fsuid=",     "_FSUID=",                 map_simple_field },
+        { "gid=",       "_GID=",                   map_simple_field },
+        { "egid=",      "_EGID=",                  map_simple_field },
+        { "fsgid=",     "_FSGID=",                 map_simple_field },
+        { "tty=",       "_TTY=",                   map_simple_field },
+        { "ses=",       "_AUDIT_SESSION=",         map_simple_field },
+        { "auid=",      "_AUDIT_LOGINUID=",        map_simple_field },
+        { "subj=",      "_SELINUX_CONTEXT=",       map_simple_field },
+        { "comm=",      "_COMM=",                  map_string_field },
+        { "exe=",       "_EXE=",                   map_string_field },
+        { "proctitle=", "_CMDLINE=",               map_string_field },
+
+        /* Some fields don't map to native well-known fields. However,
+         * we know that they are string fields, hence let's undo
+         * string field escaping for them, though we stick to the
+         * generic field names. */
+        { "path=",      "_AUDIT_FIELD_PATH=",      map_string_field },
+        { "dev=",       "_AUDIT_FIELD_DEV=",       map_string_field },
+        { "name=",      "_AUDIT_FIELD_NAME=",      map_string_field },
+        {}
+};
+
+/* Userspace fields are thos occuring in the audit string after
+ * msg='. All of these fields are untrusted, hence carry no "_"
+ * prefix. We map the fields we don't know to AUDIT_FIELD_XYZ= */
+static const MapField map_fields_userspace[] = {
+        { "cwd=",       "AUDIT_FIELD_CWD=",  map_string_field },
+        { "cmd=",       "AUDIT_FIELD_CMD=",  map_string_field },
+        { "acct=",      "AUDIT_FIELD_ACCT=", map_string_field },
+        { "exe=",       "AUDIT_FIELD_EXE=",  map_string_field },
+        { "comm=",      "AUDIT_FIELD_COMM=", map_string_field },
+        {}
+};
+
+static int map_all_fields(
+                const char *p,
+                const MapField map_fields[],
+                const char *prefix,
+                bool handle_msg,
+                struct iovec **iov,
+                size_t *n_iov_allocated,
+                unsigned *n_iov) {
+
+        int r;
+
+        assert(p);
+        assert(iov);
+        assert(n_iov_allocated);
+        assert(n_iov);
+
+        for (;;) {
+                bool mapped = false;
+                const MapField *m;
+                const char *v;
+
+                p += strspn(p, WHITESPACE);
+
+                if (*p == 0)
+                        return 0;
+
+                if (handle_msg) {
+                        v = startswith(p, "msg='");
+                        if (v) {
+                                const char *e;
+                                char *c;
+
+                                /* Userspace message. It's enclosed in
+                                   simple quotation marks, is not
+                                   escaped, but the last field in the
+                                   line, hence let's remove the
+                                   quotation mark, and apply the
+                                   userspace mapping instead of the
+                                   kernel mapping. */
+
+                                e = endswith(v, "'");
+                                if (!e)
+                                        return 0; /* don't continue splitting up if the final quotation mark is missing */
+
+                                c = strndupa(v, e - v);
+                                return map_all_fields(c, map_fields_userspace, "AUDIT_FIELD_", false, iov, n_iov_allocated, n_iov);
+                        }
+                }
+
+                /* Try to map the kernel fields to our own names */
+                for (m = map_fields; m->audit_field; m++) {
+                        v = startswith(p, m->audit_field);
+                        if (!v)
+                                continue;
+
+                        r = m->map(m->journal_field, &v, iov, n_iov_allocated, n_iov);
+                        if (r < 0) {
+                                log_debug("Failed to parse audit array: %s", strerror(-r));
+                                return r;
+                        }
+
+                        if (r > 0) {
+                                mapped = true;
+                                p = v;
+                                break;
+                        }
+                }
+
+                if (!mapped) {
+                        r = map_generic_field(prefix, &p, iov, n_iov_allocated, n_iov);
+                        if (r < 0) {
+                                log_debug("Failed to parse audit array: %s", strerror(-r));
+                                return r;
+                        }
+
+                        if (r == 0) {
+                                /* Couldn't process as generic field, let's just skip over it */
+                                p += strcspn(p, WHITESPACE);
+                        }
+                }
+        }
+}
+
+static void process_audit_string(Server *s, int type, const char *data, size_t size, const struct timeval *tv) {
+        _cleanup_free_ struct iovec *iov = NULL;
+        size_t n_iov_allocated = 0;
+        unsigned n_iov = 0, k;
+        uint64_t seconds, msec, id;
+        const char *p;
+        unsigned z;
+        char id_field[sizeof("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)],
+             type_field[sizeof("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)],
+             source_time_field[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)];
+        const char *m;
+
+        assert(s);
+
+        if (size <= 0)
+                return;
+
+        if (!data)
+                return;
+
+        /* Note that the input buffer is NUL terminated, but let's
+         * check whether there is a spurious NUL byte */
+        if (memchr(data, 0, size))
+                return;
+
+        p = startswith(data, "audit");
+        if (!p)
+                return;
+
+        if (sscanf(p, "(%" PRIi64 ".%" PRIi64 ":%" PRIi64 "): %n",
+                   &seconds,
+                   &msec,
+                   &id,
+                   &k) != 3)
+                return;
+
+        p += k;
+
+        n_iov_allocated = N_IOVEC_META_FIELDS + 5;
+        iov = new(struct iovec, n_iov_allocated);
+        if (!iov) {
+                log_oom();
+                return;
+        }
+
+        IOVEC_SET_STRING(iov[n_iov++], "_TRANSPORT=audit");
+
+        sprintf(source_time_field, "_SOURCE_REALTIME_TIMESTAMP=%" PRIu64,
+                (usec_t) seconds * USEC_PER_SEC + (usec_t) msec * USEC_PER_MSEC);
+        IOVEC_SET_STRING(iov[n_iov++], source_time_field);
+
+        sprintf(type_field, "_AUDIT_TYPE=%i", type);
+        IOVEC_SET_STRING(iov[n_iov++], type_field);
+
+        sprintf(id_field, "_AUDIT_ID=%" PRIu64, id);
+        IOVEC_SET_STRING(iov[n_iov++], id_field);
+
+        m = strappenda("MESSAGE=", data);
+        IOVEC_SET_STRING(iov[n_iov++], m);
+
+        z = n_iov;
+
+        map_all_fields(p, map_fields_kernel, "_AUDIT_FIELD_", true, &iov, &n_iov_allocated, &n_iov);
+
+        if (!GREEDY_REALLOC(iov, n_iov_allocated, n_iov + N_IOVEC_META_FIELDS)) {
+                log_oom();
+                goto finish;
+        }
+
+        server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, tv, NULL, 0, NULL, LOG_NOTICE, 0);
+
+finish:
+        /* free() all entries that map_all_fields() added. All others
+         * are allocated on the stack or are constant. */
+
+        for (; z < n_iov; z++)
+                free(iov[z].iov_base);
+}
+
+void server_process_audit_message(
+                Server *s,
+                const void *buffer,
+                size_t buffer_size,
+                const struct ucred *ucred,
+                const struct timeval *tv,
+                const union sockaddr_union *sa,
+                socklen_t salen) {
+
+        const struct nlmsghdr *nl = buffer;
+
+        assert(s);
+
+        if (buffer_size < ALIGN(sizeof(struct nlmsghdr)))
+                return;
+
+        assert(buffer);
+
+        /* Filter out fake data */
+        if (!sa ||
+            salen != sizeof(struct sockaddr_nl) ||
+            sa->nl.nl_family != AF_NETLINK ||
+            sa->nl.nl_pid != 0) {
+                log_debug("Audit netlink message from invalid sender.");
+                return;
+        }
+
+        if (!ucred || ucred->pid != 0) {
+                log_debug("Audit netlink message with invalid credentials.");
+                return;
+        }
+
+        if (!NLMSG_OK(nl, buffer_size)) {
+                log_error("Audit netlink message truncated.");
+                return;
+        }
+
+        /* Ignore special Netlink messages */
+        if (IN_SET(nl->nlmsg_type, NLMSG_NOOP, NLMSG_ERROR))
+                return;
+
+        /* Below AUDIT_FIRST_USER_MSG theer are only control messages, let's ignore those */
+        if (nl->nlmsg_type < AUDIT_FIRST_USER_MSG)
+                return;
+
+        process_audit_string(s, nl->nlmsg_type, NLMSG_DATA(nl), nl->nlmsg_len - ALIGN(sizeof(struct nlmsghdr)), tv);
+}
+
+int server_open_audit(Server *s) {
+        static const int one = 1;
+        int r;
+
+        if (s->audit_fd < 0) {
+                static const union sockaddr_union sa = {
+                        .nl.nl_family = AF_NETLINK,
+                        .nl.nl_pid    = 0,
+                        .nl.nl_groups = AUDIT_NLGRP_READLOG,
+                };
+
+                s->audit_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT);
+                if (s->audit_fd < 0) {
+                        if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT)
+                                log_debug("Audit not supported in the kernel.");
+                        else
+                                log_warning("Failed to create audit socket, ignoring: %m");
+
+                        return 0;
+                }
+
+                r = bind(s->audit_fd, &sa.sa, sizeof(sa.nl));
+                if (r < 0) {
+                        log_error("Failed to join audit multicast group: %m");
+                        return -errno;
+                }
+        } else
+                fd_nonblock(s->audit_fd, 1);
+
+        r = setsockopt(s->audit_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+        if (r < 0) {
+                log_error("Failed to set SO_PASSCRED on audit socket: %m");
+                return -errno;
+        }
+
+        r = setsockopt(s->audit_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
+        if (r < 0) {
+                log_error("Failed to set SO_TIMESTAMP on audit socket: %m");
+                return -errno;
+        }
+
+        r = sd_event_add_io(s->event, &s->audit_event_source, s->audit_fd, EPOLLIN, process_datagram, s);
+        if (r < 0) {
+                log_error("Failed to add audit fd to event loop: %s", strerror(-r));
+                return r;
+        }
+
+        return 0;
+}
diff --git a/src/journal/journald-audit.h b/src/journal/journald-audit.h
new file mode 100644 (file)
index 0000000..29bff07
--- /dev/null
@@ -0,0 +1,29 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  systemd 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.
+
+  systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "socket-util.h"
+#include "journald-server.h"
+
+void server_process_audit_message(Server *s, const void *buffer, size_t buffer_size, const struct ucred *ucred, const struct timeval *tv, const union sockaddr_union *sa, socklen_t salen);
+
+int server_open_audit(Server*s);
index ac6dc3660f3021d38c9cea9f5ff17cb51cfa3837..e06242769e7acc2142b40cad4f7b068d336deac9 100644 (file)
@@ -50,6 +50,7 @@
 #include "journald-stream.h"
 #include "journald-console.h"
 #include "journald-native.h"
 #include "journald-stream.h"
 #include "journald-console.h"
 #include "journald-native.h"
+#include "journald-audit.h"
 #include "journald-server.h"
 
 #ifdef HAVE_ACL
 #include "journald-server.h"
 
 #ifdef HAVE_ACL
@@ -1112,7 +1113,7 @@ int process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userda
         Server *s = userdata;
 
         assert(s);
         Server *s = userdata;
 
         assert(s);
-        assert(fd == s->native_fd || fd == s->syslog_fd);
+        assert(fd == s->native_fd || fd == s->syslog_fd || fd == s->audit_fd);
 
         if (revents != EPOLLIN) {
                 log_error("Got invalid event from epoll for datagram fd: %"PRIx32, revents);
 
         if (revents != EPOLLIN) {
                 log_error("Got invalid event from epoll for datagram fd: %"PRIx32, revents);
@@ -1142,28 +1143,37 @@ int process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userda
                                     CMSG_SPACE(sizeof(int)) + /* fd */
                                     CMSG_SPACE(NAME_MAX)]; /* selinux label */
                 } control = {};
                                     CMSG_SPACE(sizeof(int)) + /* fd */
                                     CMSG_SPACE(NAME_MAX)]; /* selinux label */
                 } control = {};
+                union sockaddr_union sa = {};
                 struct msghdr msghdr = {
                         .msg_iov = &iovec,
                         .msg_iovlen = 1,
                         .msg_control = &control,
                         .msg_controllen = sizeof(control),
                 struct msghdr msghdr = {
                         .msg_iov = &iovec,
                         .msg_iovlen = 1,
                         .msg_control = &control,
                         .msg_controllen = sizeof(control),
+                        .msg_name = &sa,
+                        .msg_namelen = sizeof(sa),
                 };
 
                 ssize_t n;
                 };
 
                 ssize_t n;
-                int v;
                 int *fds = NULL;
                 unsigned n_fds = 0;
                 int *fds = NULL;
                 unsigned n_fds = 0;
+                int v = 0;
+                size_t m;
 
 
-                if (ioctl(fd, SIOCINQ, &v) < 0) {
-                        log_error("SIOCINQ failed: %m");
-                        return -errno;
-                }
+                /* Try to get the right size, if we can. (Not all
+                 * sockets support SIOCINQ, hence we just try, but
+                 * don't rely on it. */
+                (void) ioctl(fd, SIOCINQ, &v);
+
+                /* Fix it up, if it is too small. We use the same fixed value as auditd here. Awful!*/
+                m = PAGE_ALIGN(MAX3((size_t) v + 1,
+                                    (size_t) LINE_MAX,
+                                    ALIGN(sizeof(struct nlmsghdr)) + ALIGN((size_t) MAX_AUDIT_MESSAGE_LENGTH)) + 1);
 
 
-                if (!GREEDY_REALLOC(s->buffer, s->buffer_size, LINE_MAX + (size_t) v))
+                if (!GREEDY_REALLOC(s->buffer, s->buffer_size, m))
                         return log_oom();
 
                 iovec.iov_base = s->buffer;
                         return log_oom();
 
                 iovec.iov_base = s->buffer;
-                iovec.iov_len = s->buffer_size;
+                iovec.iov_len = s->buffer_size - 1; /* Leave room for trailing NUL we add later */
 
                 n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
                 if (n < 0) {
 
                 n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
                 if (n < 0) {
@@ -1195,20 +1205,30 @@ int process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userda
                         }
                 }
 
                         }
                 }
 
+                /* And a trailing NUL, just in case */
+                s->buffer[n] = 0;
+
                 if (fd == s->syslog_fd) {
                 if (fd == s->syslog_fd) {
-                        if (n > 0 && n_fds == 0) {
-                                s->buffer[n] = 0;
+                        if (n > 0 && n_fds == 0)
                                 server_process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len);
                                 server_process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len);
-                        else if (n_fds > 0)
+                        else if (n_fds > 0)
                                 log_warning("Got file descriptors via syslog socket. Ignoring.");
 
                                 log_warning("Got file descriptors via syslog socket. Ignoring.");
 
-                } else {
+                } else if (fd == s->native_fd) {
                         if (n > 0 && n_fds == 0)
                                 server_process_native_message(s, s->buffer, n, ucred, tv, label, label_len);
                         else if (n == 0 && n_fds == 1)
                                 server_process_native_file(s, fds[0], ucred, tv, label, label_len);
                         else if (n_fds > 0)
                                 log_warning("Got too many file descriptors via native socket. Ignoring.");
                         if (n > 0 && n_fds == 0)
                                 server_process_native_message(s, s->buffer, n, ucred, tv, label, label_len);
                         else if (n == 0 && n_fds == 1)
                                 server_process_native_file(s, fds[0], ucred, tv, label, label_len);
                         else if (n_fds > 0)
                                 log_warning("Got too many file descriptors via native socket. Ignoring.");
+
+                } else {
+                        assert(fd == s->audit_fd);
+
+                        if (n > 0 && n_fds == 0)
+                                server_process_audit_message(s, s->buffer, n, ucred, tv, &sa, msghdr.msg_namelen);
+                        else if (n_fds > 0)
+                                log_warning("Got file descriptors via audit socket. Ignoring.");
                 }
 
                 close_many(fds, n_fds);
                 }
 
                 close_many(fds, n_fds);
@@ -1452,7 +1472,7 @@ int server_init(Server *s) {
         assert(s);
 
         zero(*s);
         assert(s);
 
         zero(*s);
-        s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->hostname_fd = -1;
+        s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->audit_fd = s->hostname_fd = -1;
         s->compress = true;
         s->seal = true;
 
         s->compress = true;
         s->seal = true;
 
@@ -1537,6 +1557,15 @@ int server_init(Server *s) {
 
                         s->syslog_fd = fd;
 
 
                         s->syslog_fd = fd;
 
+                } else if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) {
+
+                        if (s->audit_fd >= 0) {
+                                log_error("Too many audit sockets passed.");
+                                return -EINVAL;
+                        }
+
+                        s->audit_fd = fd;
+
                 } else {
                         log_error("Unknown socket passed.");
                         return -EINVAL;
                 } else {
                         log_error("Unknown socket passed.");
                         return -EINVAL;
@@ -1559,6 +1588,10 @@ int server_init(Server *s) {
         if (r < 0)
                 return r;
 
         if (r < 0)
                 return r;
 
+        r = server_open_audit(s);
+        if (r < 0)
+                return r;
+
         r = server_open_kernel_seqnum(s);
         if (r < 0)
                 return r;
         r = server_open_kernel_seqnum(s);
         if (r < 0)
                 return r;
@@ -1632,6 +1665,7 @@ void server_done(Server *s) {
         sd_event_source_unref(s->native_event_source);
         sd_event_source_unref(s->stdout_event_source);
         sd_event_source_unref(s->dev_kmsg_event_source);
         sd_event_source_unref(s->native_event_source);
         sd_event_source_unref(s->stdout_event_source);
         sd_event_source_unref(s->dev_kmsg_event_source);
+        sd_event_source_unref(s->audit_event_source);
         sd_event_source_unref(s->sync_event_source);
         sd_event_source_unref(s->sigusr1_event_source);
         sd_event_source_unref(s->sigusr2_event_source);
         sd_event_source_unref(s->sync_event_source);
         sd_event_source_unref(s->sigusr1_event_source);
         sd_event_source_unref(s->sigusr2_event_source);
@@ -1644,6 +1678,7 @@ void server_done(Server *s) {
         safe_close(s->native_fd);
         safe_close(s->stdout_fd);
         safe_close(s->dev_kmsg_fd);
         safe_close(s->native_fd);
         safe_close(s->stdout_fd);
         safe_close(s->dev_kmsg_fd);
+        safe_close(s->audit_fd);
         safe_close(s->hostname_fd);
 
         if (s->rate_limit)
         safe_close(s->hostname_fd);
 
         if (s->rate_limit)
index 65a2b71a7f15bdc679336a2683cb840a4acbff47..9c7fa50a96e8976f484f03d7c75c8be1660f1b89 100644 (file)
@@ -59,6 +59,7 @@ typedef struct Server {
         int native_fd;
         int stdout_fd;
         int dev_kmsg_fd;
         int native_fd;
         int stdout_fd;
         int dev_kmsg_fd;
+        int audit_fd;
         int hostname_fd;
 
         sd_event *event;
         int hostname_fd;
 
         sd_event *event;
@@ -67,6 +68,7 @@ typedef struct Server {
         sd_event_source *native_event_source;
         sd_event_source *stdout_event_source;
         sd_event_source *dev_kmsg_event_source;
         sd_event_source *native_event_source;
         sd_event_source *stdout_event_source;
         sd_event_source *dev_kmsg_event_source;
+        sd_event_source *audit_event_source;
         sd_event_source *sync_event_source;
         sd_event_source *sigusr1_event_source;
         sd_event_source *sigusr2_event_source;
         sd_event_source *sync_event_source;
         sd_event_source *sigusr1_event_source;
         sd_event_source *sigusr2_event_source;
index 0d7c55952ee3f885ecf8c0cd3121c1dfd40ce374..cc81c98829c8b2a75182231bda12c4661361ff7a 100644 (file)
@@ -33,6 +33,7 @@
 #include <linux/input.h>
 #include <linux/if_link.h>
 #include <linux/loop.h>
 #include <linux/input.h>
 #include <linux/if_link.h>
 #include <linux/loop.h>
+#include <linux/audit.h>
 
 #ifdef HAVE_AUDIT
 #include <libaudit.h>
 
 #ifdef HAVE_AUDIT
 #include <libaudit.h>
@@ -557,3 +558,11 @@ static inline int setns(int fd, int nstype) {
 #ifndef LOOPBACK_IFINDEX
 #define LOOPBACK_IFINDEX 1
 #endif
 #ifndef LOOPBACK_IFINDEX
 #define LOOPBACK_IFINDEX 1
 #endif
+
+#ifndef MAX_AUDIT_MESSAGE_LENGTH
+#define MAX_AUDIT_MESSAGE_LENGTH 8970
+#endif
+
+#ifndef AUDIT_NLGRP_MAX
+#define AUDIT_NLGRP_READLOG 1
+#endif
diff --git a/units/systemd-journald-audit.socket b/units/systemd-journald-audit.socket
new file mode 100644 (file)
index 0000000..ce849da
--- /dev/null
@@ -0,0 +1,18 @@
+#  This file is part of systemd.
+#
+#  systemd 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.
+
+[Unit]
+Description=Journal Audit Socket
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+DefaultDependencies=no
+Before=sockets.target
+
+[Socket]
+Service=systemd-journald.service
+ReceiveBuffer=128M
+ListenNetlink=audit 1
+PassCredentials=yes
index 4de38fad59cb71cc6df066cf15892b8e94cbc89f..7ee67fd000fe400526fa01a5dd4c528ad6c96ab0 100644 (file)
@@ -10,17 +10,17 @@ Description=Journal Service
 Documentation=man:systemd-journald.service(8) man:journald.conf(5)
 DefaultDependencies=no
 Requires=systemd-journald.socket
 Documentation=man:systemd-journald.service(8) man:journald.conf(5)
 DefaultDependencies=no
 Requires=systemd-journald.socket
-After=systemd-journald.socket systemd-journald-dev-log.socket syslog.socket
+After=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket syslog.socket
 Before=sysinit.target
 
 [Service]
 Before=sysinit.target
 
 [Service]
-Sockets=systemd-journald.socket systemd-journald-dev-log.socket
+Sockets=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket
 ExecStart=@rootlibexecdir@/systemd-journald
 Restart=always
 RestartSec=0
 NotifyAccess=all
 StandardOutput=null
 ExecStart=@rootlibexecdir@/systemd-journald
 Restart=always
 RestartSec=0
 NotifyAccess=all
 StandardOutput=null
-CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
+CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
 WatchdogSec=1min
 
 # Increase the default a bit in order to allow many simultaneous
 WatchdogSec=1min
 
 # Increase the default a bit in order to allow many simultaneous