]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/journal/journald-audit.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / journal / journald-audit.c
index 787ec34bb830af9836263863c3c98a1a0a2cb61b..19f53751cb8874dd4c89477fabc285d1052cc25c 100644 (file)
@@ -1,5 +1,4 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
+/* SPDX-License-Identifier: LGPL-2.1+ */
 /***
   This file is part of systemd.
 
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include "missing.h"
+#include "alloc-util.h"
+#include "audit-type.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "io-util.h"
 #include "journald-audit.h"
+#include "missing.h"
+#include "string-util.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);
+        int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *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) {
+static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
         _cleanup_free_ char *c = NULL;
         size_t l = 0, allocated = 0;
         const char *e;
@@ -45,7 +50,7 @@ static int map_simple_field(const char *field, const char **p, struct iovec **io
                 return -ENOMEM;
 
         memcpy(c, field, l);
-        for (e = *p; *e != ' ' && *e != 0; e++) {
+        for (e = *p; !IN_SET(*e, 0, ' '); e++) {
                 if (!GREEDY_REALLOC(c, allocated, l+2))
                         return -ENOMEM;
 
@@ -57,9 +62,7 @@ static int map_simple_field(const char *field, const char **p, struct iovec **io
         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) ++;
+        (*iov)[(*n_iov)++] = IOVEC_MAKE(c, l);
 
         *p = e;
         c = NULL;
@@ -67,7 +70,7 @@ static int map_simple_field(const char *field, const char **p, struct iovec **io
         return 1;
 }
 
-static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+static int map_string_field_internal(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov, bool filter_printable) {
         _cleanup_free_ char *c = NULL;
         const char *s, *e;
         size_t l;
@@ -106,8 +109,9 @@ static int map_string_field(const char *field, const char **p, struct iovec **io
                         return -ENOMEM;
 
                 memcpy(c, field, l);
-                for (e = *p; *e != ' ' && *e != 0; e += 2) {
+                for (e = *p; !IN_SET(*e, 0, ' '); e += 2) {
                         int a, b;
+                        uint8_t x;
 
                         a = unhexchar(e[0]);
                         if (a < 0)
@@ -117,10 +121,15 @@ static int map_string_field(const char *field, const char **p, struct iovec **io
                         if (b < 0)
                                 return 0;
 
+                        x = ((uint8_t) a << 4 | (uint8_t) b);
+
+                        if (filter_printable && x < (uint8_t) ' ')
+                                x = (uint8_t) ' ';
+
                         if (!GREEDY_REALLOC(c, allocated, l+2))
                                 return -ENOMEM;
 
-                        c[l++] = (char) ((uint8_t) a << 4 | (uint8_t) b);
+                        c[l++] = (char) x;
                 }
 
                 c[l] = 0;
@@ -130,9 +139,7 @@ static int map_string_field(const char *field, const char **p, struct iovec **io
         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) ++;
+        (*iov)[(*n_iov)++] = IOVEC_MAKE(c, l);
 
         *p = e;
         c = NULL;
@@ -140,7 +147,15 @@ static int map_string_field(const char *field, const char **p, struct iovec **io
         return 1;
 }
 
-static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
+        return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, false);
+}
+
+static int map_string_field_printable(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
+        return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, true);
+}
+
+static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
         const char *e, *f;
         char *c, *t;
         int r;
@@ -149,7 +164,7 @@ static int map_generic_field(const char *prefix, const char **p, struct iovec **
 
         for (e = *p; e < *p + 16; e++) {
 
-                if (*e == 0 || *e == ' ')
+                if (IN_SET(*e, 0, ' '))
                         return 0;
 
                 if (*e == '=')
@@ -158,7 +173,7 @@ static int map_generic_field(const char *prefix, const char **p, struct iovec **
                 if (!((*e >= 'a' && *e <= 'z') ||
                       (*e >= 'A' && *e <= 'Z') ||
                       (*e >= '0' && *e <= '9') ||
-                      (*e == '_')))
+                      IN_SET(*e, '_', '-')))
                         return 0;
         }
 
@@ -168,11 +183,21 @@ static int map_generic_field(const char *prefix, const char **p, struct iovec **
         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;
+        for (f = *p; f < e; f++) {
+                char x;
+
+                if (*f >= 'a' && *f <= 'z')
+                        x = (*f - 'a') + 'A'; /* uppercase */
+                else if (*f == '-')
+                        x = '_'; /* dashes → underscores */
+                else
+                        x = *f;
+
+                *(t++) = x;
+        }
         strcpy(t, "=");
 
-        e ++;
+        e++;
 
         r = map_simple_field(c, &e, iov, n_iov_allocated, n_iov);
         if (r < 0)
@@ -182,7 +207,7 @@ static int map_generic_field(const char *prefix, const char **p, struct iovec **
         return r;
 }
 
-/* Kernel fields are those occuring in the audit string before
+/* Kernel fields are those occurring 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= */
@@ -190,41 +215,41 @@ 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 },
+        { "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_printable },
 
         /* 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 },
+        { "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
+/* Userspace fields are those occurring 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 },
+        { "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 },
         {}
 };
 
@@ -235,7 +260,7 @@ static int map_all_fields(
                 bool handle_msg,
                 struct iovec **iov,
                 size_t *n_iov_allocated,
-                unsigned *n_iov) {
+                size_t *n_iov) {
 
         int r;
 
@@ -284,10 +309,8 @@ static int map_all_fields(
                                 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)
+                                return log_debug_errno(r, "Failed to parse audit array: %m");
 
                         if (r > 0) {
                                 mapped = true;
@@ -298,30 +321,26 @@ static int map_all_fields(
 
                 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)
+                                return log_debug_errno(r, "Failed to parse audit array: %m");
 
-                        if (r == 0) {
+                        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) {
+static void process_audit_string(Server *s, int type, const char *data, size_t size) {
+        size_t n_iov_allocated = 0, n_iov = 0, z;
         _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;
+        const char *p, *type_name;
         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;
+        char *m;
+        int k;
 
         assert(s);
 
@@ -340,7 +359,7 @@ static void process_audit_string(Server *s, int type, const char *data, size_t s
         if (!p)
                 return;
 
-        if (sscanf(p, "(%" PRIi64 ".%" PRIi64 ":%" PRIi64 "): %n",
+        if (sscanf(p, "(%" PRIu64 ".%" PRIu64 ":%" PRIu64 "):%n",
                    &seconds,
                    &msec,
                    &id,
@@ -348,28 +367,38 @@ static void process_audit_string(Server *s, int type, const char *data, size_t s
                 return;
 
         p += k;
+        p += strspn(p, WHITESPACE);
+
+        if (isempty(p))
+                return;
 
-        n_iov_allocated = N_IOVEC_META_FIELDS + 5;
+        n_iov_allocated = N_IOVEC_META_FIELDS + 7;
         iov = new(struct iovec, n_iov_allocated);
         if (!iov) {
                 log_oom();
                 return;
         }
 
-        IOVEC_SET_STRING(iov[n_iov++], "_TRANSPORT=audit");
+        iov[n_iov++] = IOVEC_MAKE_STRING("_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);
+        iov[n_iov++] = IOVEC_MAKE_STRING(source_time_field);
 
         sprintf(type_field, "_AUDIT_TYPE=%i", type);
-        IOVEC_SET_STRING(iov[n_iov++], type_field);
+        iov[n_iov++] = IOVEC_MAKE_STRING(type_field);
 
         sprintf(id_field, "_AUDIT_ID=%" PRIu64, id);
-        IOVEC_SET_STRING(iov[n_iov++], id_field);
+        iov[n_iov++] = IOVEC_MAKE_STRING(id_field);
+
+        assert_cc(4 == LOG_FAC(LOG_AUTH));
+        iov[n_iov++] = IOVEC_MAKE_STRING("SYSLOG_FACILITY=4");
+        iov[n_iov++] = IOVEC_MAKE_STRING("SYSLOG_IDENTIFIER=audit");
 
-        m = strappenda("MESSAGE=", data);
-        IOVEC_SET_STRING(iov[n_iov++], m);
+        type_name = audit_type_name_alloca(type);
+
+        m = strjoina("MESSAGE=", type_name, " ", p);
+        iov[n_iov++] = IOVEC_MAKE_STRING(m);
 
         z = n_iov;
 
@@ -380,7 +409,7 @@ static void process_audit_string(Server *s, int type, const char *data, size_t s
                 goto finish;
         }
 
-        server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, tv, NULL, 0, NULL, LOG_NOTICE, 0);
+        server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, NULL, LOG_NOTICE, 0);
 
 finish:
         /* free() all entries that map_all_fields() added. All others
@@ -395,7 +424,6 @@ void server_process_audit_message(
                 const void *buffer,
                 size_t buffer_size,
                 const struct ucred *ucred,
-                const struct timeval *tv,
                 const union sockaddr_union *sa,
                 socklen_t salen) {
 
@@ -435,7 +463,52 @@ void server_process_audit_message(
         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);
+        process_audit_string(s, nl->nlmsg_type, NLMSG_DATA(nl), nl->nlmsg_len - ALIGN(sizeof(struct nlmsghdr)));
+}
+
+static int enable_audit(int fd, bool b) {
+        struct {
+                union {
+                        struct nlmsghdr header;
+                        uint8_t header_space[NLMSG_HDRLEN];
+                };
+                struct audit_status body;
+        } _packed_ request = {
+                .header.nlmsg_len = NLMSG_LENGTH(sizeof(struct audit_status)),
+                .header.nlmsg_type = AUDIT_SET,
+                .header.nlmsg_flags = NLM_F_REQUEST,
+                .header.nlmsg_seq = 1,
+                .header.nlmsg_pid = 0,
+                .body.mask = AUDIT_STATUS_ENABLED,
+                .body.enabled = b,
+        };
+        union sockaddr_union sa = {
+                .nl.nl_family = AF_NETLINK,
+                .nl.nl_pid = 0,
+        };
+        struct iovec iovec = {
+                .iov_base = &request,
+                .iov_len = NLMSG_LENGTH(sizeof(struct audit_status)),
+        };
+        struct msghdr mh = {
+                .msg_iov = &iovec,
+                .msg_iovlen = 1,
+                .msg_name = &sa.sa,
+                .msg_namelen = sizeof(sa.nl),
+        };
+
+        ssize_t n;
+
+        n = sendmsg(fd, &mh, MSG_NOSIGNAL);
+        if (n < 0)
+                return -errno;
+        if (n != NLMSG_LENGTH(sizeof(struct audit_status)))
+                return -EIO;
+
+        /* We don't wait for the result here, we can't do anything
+         * about it anyway */
+
+        return 0;
 }
 
 int server_open_audit(Server *s) {
@@ -451,33 +524,37 @@ int server_open_audit(Server *s) {
 
                 s->audit_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT);
                 if (s->audit_fd < 0) {
-                        if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT)
+                        if (IN_SET(errno, EAFNOSUPPORT, EPROTONOSUPPORT))
                                 log_debug("Audit not supported in the kernel.");
                         else
-                                log_warning("Failed to create audit socket, ignoring: %m");
+                                log_warning_errno(errno, "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;
+                if (bind(s->audit_fd, &sa.sa, sizeof(sa.nl)) < 0) {
+                        log_warning_errno(errno,
+                                          "Failed to join audit multicast group. "
+                                          "The kernel is probably too old or multicast reading is not supported. "
+                                          "Ignoring: %m");
+                        s->audit_fd = safe_close(s->audit_fd);
+                        return 0;
                 }
         } 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;
-        }
+        if (r < 0)
+                return log_error_errno(errno, "Failed to set SO_PASSCRED on audit socket: %m");
 
-        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;
-        }
+        r = sd_event_add_io(s->event, &s->audit_event_source, s->audit_fd, EPOLLIN, server_process_datagram, s);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add audit fd to event loop: %m");
+
+        /* We are listening now, try to enable audit */
+        r = enable_audit(s->audit_fd, true);
+        if (r < 0)
+                log_warning_errno(r, "Failed to issue audit enable call: %m");
 
         return 0;
 }