Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include "alloc-util.h"
4 : : #include "audit-type.h"
5 : : #include "fd-util.h"
6 : : #include "hexdecoct.h"
7 : : #include "io-util.h"
8 : : #include "journald-audit.h"
9 : : #include "missing.h"
10 : : #include "string-util.h"
11 : :
12 : : typedef struct MapField {
13 : : const char *audit_field;
14 : : const char *journal_field;
15 : : int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov);
16 : : } MapField;
17 : :
18 : 0 : static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
19 : 0 : _cleanup_free_ char *c = NULL;
20 : 0 : size_t l = 0, allocated = 0;
21 : : const char *e;
22 : :
23 [ # # ]: 0 : assert(field);
24 [ # # ]: 0 : assert(p);
25 [ # # ]: 0 : assert(iov);
26 [ # # ]: 0 : assert(n_iov);
27 : :
28 : 0 : l = strlen(field);
29 : 0 : allocated = l + 1;
30 : 0 : c = malloc(allocated);
31 [ # # ]: 0 : if (!c)
32 : 0 : return -ENOMEM;
33 : :
34 : 0 : memcpy(c, field, l);
35 [ # # # # ]: 0 : for (e = *p; !IN_SET(*e, 0, ' '); e++) {
36 [ # # ]: 0 : if (!GREEDY_REALLOC(c, allocated, l+2))
37 : 0 : return -ENOMEM;
38 : :
39 : 0 : c[l++] = *e;
40 : : }
41 : :
42 : 0 : c[l] = 0;
43 : :
44 [ # # ]: 0 : if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
45 : 0 : return -ENOMEM;
46 : :
47 : 0 : (*iov)[(*n_iov)++] = IOVEC_MAKE(c, l);
48 : :
49 : 0 : *p = e;
50 : 0 : c = NULL;
51 : :
52 : 0 : return 1;
53 : : }
54 : :
55 : 0 : 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) {
56 : 0 : _cleanup_free_ char *c = NULL;
57 : : const char *s, *e;
58 : : size_t l;
59 : :
60 [ # # ]: 0 : assert(field);
61 [ # # ]: 0 : assert(p);
62 [ # # ]: 0 : assert(iov);
63 [ # # ]: 0 : assert(n_iov);
64 : :
65 : : /* The kernel formats string fields in one of two formats. */
66 : :
67 [ # # ]: 0 : if (**p == '"') {
68 : : /* Normal quoted syntax */
69 : 0 : s = *p + 1;
70 : 0 : e = strchr(s, '"');
71 [ # # ]: 0 : if (!e)
72 : 0 : return 0;
73 : :
74 : 0 : l = strlen(field) + (e - s);
75 : 0 : c = malloc(l+1);
76 [ # # ]: 0 : if (!c)
77 : 0 : return -ENOMEM;
78 : :
79 : 0 : *((char*) mempcpy(stpcpy(c, field), s, e - s)) = 0;
80 : :
81 : 0 : e += 1;
82 : :
83 [ # # ]: 0 : } else if (unhexchar(**p) >= 0) {
84 : : /* Hexadecimal escaping */
85 : 0 : size_t allocated = 0;
86 : :
87 : 0 : l = strlen(field);
88 : 0 : allocated = l + 2;
89 : 0 : c = malloc(allocated);
90 [ # # ]: 0 : if (!c)
91 : 0 : return -ENOMEM;
92 : :
93 : 0 : memcpy(c, field, l);
94 [ # # # # ]: 0 : for (e = *p; !IN_SET(*e, 0, ' '); e += 2) {
95 : : int a, b;
96 : : uint8_t x;
97 : :
98 : 0 : a = unhexchar(e[0]);
99 [ # # ]: 0 : if (a < 0)
100 : 0 : return 0;
101 : :
102 : 0 : b = unhexchar(e[1]);
103 [ # # ]: 0 : if (b < 0)
104 : 0 : return 0;
105 : :
106 : 0 : x = ((uint8_t) a << 4 | (uint8_t) b);
107 : :
108 [ # # # # ]: 0 : if (filter_printable && x < (uint8_t) ' ')
109 : 0 : x = (uint8_t) ' ';
110 : :
111 [ # # ]: 0 : if (!GREEDY_REALLOC(c, allocated, l+2))
112 : 0 : return -ENOMEM;
113 : :
114 : 0 : c[l++] = (char) x;
115 : : }
116 : :
117 : 0 : c[l] = 0;
118 : : } else
119 : 0 : return 0;
120 : :
121 [ # # ]: 0 : if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
122 : 0 : return -ENOMEM;
123 : :
124 : 0 : (*iov)[(*n_iov)++] = IOVEC_MAKE(c, l);
125 : :
126 : 0 : *p = e;
127 : 0 : c = NULL;
128 : :
129 : 0 : return 1;
130 : : }
131 : :
132 : 0 : static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
133 : 0 : return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, false);
134 : : }
135 : :
136 : 0 : static int map_string_field_printable(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
137 : 0 : return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, true);
138 : : }
139 : :
140 : 0 : static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
141 : : const char *e, *f;
142 : : char *c, *t;
143 : : int r;
144 : :
145 : : /* Implements fallback mappings for all fields we don't know */
146 : :
147 [ # # ]: 0 : for (e = *p; e < *p + 16; e++) {
148 : :
149 [ # # # # ]: 0 : if (IN_SET(*e, 0, ' '))
150 : 0 : return 0;
151 : :
152 [ # # ]: 0 : if (*e == '=')
153 : 0 : break;
154 : :
155 [ # # # # ]: 0 : if (!((*e >= 'a' && *e <= 'z') ||
156 [ # # # # ]: 0 : (*e >= 'A' && *e <= 'Z') ||
157 [ # # # # ]: 0 : (*e >= '0' && *e <= '9') ||
158 [ # # # # ]: 0 : IN_SET(*e, '_', '-')))
159 : 0 : return 0;
160 : : }
161 : :
162 [ # # # # ]: 0 : if (e <= *p || e >= *p + 16)
163 : 0 : return 0;
164 : :
165 [ # # # # ]: 0 : c = newa(char, strlen(prefix) + (e - *p) + 2);
166 : :
167 : 0 : t = stpcpy(c, prefix);
168 [ # # ]: 0 : for (f = *p; f < e; f++) {
169 : : char x;
170 : :
171 [ # # # # ]: 0 : if (*f >= 'a' && *f <= 'z')
172 : 0 : x = (*f - 'a') + 'A'; /* uppercase */
173 [ # # ]: 0 : else if (*f == '-')
174 : 0 : x = '_'; /* dashes → underscores */
175 : : else
176 : 0 : x = *f;
177 : :
178 : 0 : *(t++) = x;
179 : : }
180 : 0 : strcpy(t, "=");
181 : :
182 : 0 : e++;
183 : :
184 : 0 : r = map_simple_field(c, &e, iov, n_iov_allocated, n_iov);
185 [ # # ]: 0 : if (r < 0)
186 : 0 : return r;
187 : :
188 : 0 : *p = e;
189 : 0 : return r;
190 : : }
191 : :
192 : : /* Kernel fields are those occurring in the audit string before
193 : : * msg='. All of these fields are trusted, hence carry the "_" prefix.
194 : : * We try to translate the fields we know into our native names. The
195 : : * other's are generically mapped to _AUDIT_FIELD_XYZ= */
196 : : static const MapField map_fields_kernel[] = {
197 : :
198 : : /* First, we map certain well-known audit fields into native
199 : : * well-known fields */
200 : : { "pid=", "_PID=", map_simple_field },
201 : : { "ppid=", "_PPID=", map_simple_field },
202 : : { "uid=", "_UID=", map_simple_field },
203 : : { "euid=", "_EUID=", map_simple_field },
204 : : { "fsuid=", "_FSUID=", map_simple_field },
205 : : { "gid=", "_GID=", map_simple_field },
206 : : { "egid=", "_EGID=", map_simple_field },
207 : : { "fsgid=", "_FSGID=", map_simple_field },
208 : : { "tty=", "_TTY=", map_simple_field },
209 : : { "ses=", "_AUDIT_SESSION=", map_simple_field },
210 : : { "auid=", "_AUDIT_LOGINUID=", map_simple_field },
211 : : { "subj=", "_SELINUX_CONTEXT=", map_simple_field },
212 : : { "comm=", "_COMM=", map_string_field },
213 : : { "exe=", "_EXE=", map_string_field },
214 : : { "proctitle=", "_CMDLINE=", map_string_field_printable },
215 : :
216 : : /* Some fields don't map to native well-known fields. However,
217 : : * we know that they are string fields, hence let's undo
218 : : * string field escaping for them, though we stick to the
219 : : * generic field names. */
220 : : { "path=", "_AUDIT_FIELD_PATH=", map_string_field },
221 : : { "dev=", "_AUDIT_FIELD_DEV=", map_string_field },
222 : : { "name=", "_AUDIT_FIELD_NAME=", map_string_field },
223 : : {}
224 : : };
225 : :
226 : : /* Userspace fields are those occurring in the audit string after
227 : : * msg='. All of these fields are untrusted, hence carry no "_"
228 : : * prefix. We map the fields we don't know to AUDIT_FIELD_XYZ= */
229 : : static const MapField map_fields_userspace[] = {
230 : : { "cwd=", "AUDIT_FIELD_CWD=", map_string_field },
231 : : { "cmd=", "AUDIT_FIELD_CMD=", map_string_field },
232 : : { "acct=", "AUDIT_FIELD_ACCT=", map_string_field },
233 : : { "exe=", "AUDIT_FIELD_EXE=", map_string_field },
234 : : { "comm=", "AUDIT_FIELD_COMM=", map_string_field },
235 : : {}
236 : : };
237 : :
238 : 0 : static int map_all_fields(
239 : : const char *p,
240 : : const MapField map_fields[],
241 : : const char *prefix,
242 : : bool handle_msg,
243 : : struct iovec **iov,
244 : : size_t *n_iov_allocated,
245 : : size_t *n_iov) {
246 : :
247 : : int r;
248 : :
249 [ # # ]: 0 : assert(p);
250 [ # # ]: 0 : assert(iov);
251 [ # # ]: 0 : assert(n_iov_allocated);
252 [ # # ]: 0 : assert(n_iov);
253 : :
254 : 0 : for (;;) {
255 : 0 : bool mapped = false;
256 : : const MapField *m;
257 : : const char *v;
258 : :
259 : 0 : p += strspn(p, WHITESPACE);
260 : :
261 [ # # ]: 0 : if (*p == 0)
262 : 0 : return 0;
263 : :
264 [ # # ]: 0 : if (handle_msg) {
265 : 0 : v = startswith(p, "msg='");
266 [ # # ]: 0 : if (v) {
267 : 0 : _cleanup_free_ char *c = NULL;
268 : : const char *e;
269 : :
270 : : /* Userspace message. It's enclosed in
271 : : simple quotation marks, is not
272 : : escaped, but the last field in the
273 : : line, hence let's remove the
274 : : quotation mark, and apply the
275 : : userspace mapping instead of the
276 : : kernel mapping. */
277 : :
278 : 0 : e = endswith(v, "'");
279 [ # # ]: 0 : if (!e)
280 : 0 : return 0; /* don't continue splitting up if the final quotation mark is missing */
281 : :
282 : 0 : c = strndup(v, e - v);
283 [ # # ]: 0 : if (!c)
284 : 0 : return -ENOMEM;
285 : :
286 : 0 : return map_all_fields(c, map_fields_userspace, "AUDIT_FIELD_", false, iov, n_iov_allocated, n_iov);
287 : : }
288 : : }
289 : :
290 : : /* Try to map the kernel fields to our own names */
291 [ # # ]: 0 : for (m = map_fields; m->audit_field; m++) {
292 : 0 : v = startswith(p, m->audit_field);
293 [ # # ]: 0 : if (!v)
294 : 0 : continue;
295 : :
296 : 0 : r = m->map(m->journal_field, &v, iov, n_iov_allocated, n_iov);
297 [ # # ]: 0 : if (r < 0)
298 [ # # ]: 0 : return log_debug_errno(r, "Failed to parse audit array: %m");
299 : :
300 [ # # ]: 0 : if (r > 0) {
301 : 0 : mapped = true;
302 : 0 : p = v;
303 : 0 : break;
304 : : }
305 : : }
306 : :
307 [ # # ]: 0 : if (!mapped) {
308 : 0 : r = map_generic_field(prefix, &p, iov, n_iov_allocated, n_iov);
309 [ # # ]: 0 : if (r < 0)
310 [ # # ]: 0 : return log_debug_errno(r, "Failed to parse audit array: %m");
311 : :
312 [ # # ]: 0 : if (r == 0)
313 : : /* Couldn't process as generic field, let's just skip over it */
314 : 0 : p += strcspn(p, WHITESPACE);
315 : : }
316 : : }
317 : : }
318 : :
319 : 0 : void process_audit_string(Server *s, int type, const char *data, size_t size) {
320 : 0 : size_t n_iov_allocated = 0, n_iov = 0, z;
321 [ # # ]: 0 : _cleanup_free_ struct iovec *iov = NULL;
322 : : uint64_t seconds, msec, id;
323 : : const char *p, *type_name;
324 : : char id_field[sizeof("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)],
325 : : type_field[sizeof("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)],
326 : : source_time_field[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)];
327 : : char *m, *type_field_name;
328 : : int k;
329 : :
330 [ # # ]: 0 : assert(s);
331 : :
332 [ # # ]: 0 : if (size <= 0)
333 : 0 : return;
334 : :
335 [ # # ]: 0 : if (!data)
336 : 0 : return;
337 : :
338 : : /* Note that the input buffer is NUL terminated, but let's
339 : : * check whether there is a spurious NUL byte */
340 [ # # ]: 0 : if (memchr(data, 0, size))
341 : 0 : return;
342 : :
343 : 0 : p = startswith(data, "audit");
344 [ # # ]: 0 : if (!p)
345 : 0 : return;
346 : :
347 : 0 : k = 0;
348 [ # # ]: 0 : if (sscanf(p, "(%" PRIu64 ".%" PRIu64 ":%" PRIu64 "):%n",
349 : : &seconds,
350 : : &msec,
351 : : &id,
352 [ # # ]: 0 : &k) != 3 || k == 0)
353 : 0 : return;
354 : :
355 : 0 : p += k;
356 : 0 : p += strspn(p, WHITESPACE);
357 : :
358 [ # # ]: 0 : if (isempty(p))
359 : 0 : return;
360 : :
361 : 0 : n_iov_allocated = N_IOVEC_META_FIELDS + 8;
362 : 0 : iov = new(struct iovec, n_iov_allocated);
363 [ # # ]: 0 : if (!iov) {
364 : 0 : log_oom();
365 : 0 : return;
366 : : }
367 : :
368 : 0 : iov[n_iov++] = IOVEC_MAKE_STRING("_TRANSPORT=audit");
369 : :
370 : 0 : sprintf(source_time_field, "_SOURCE_REALTIME_TIMESTAMP=%" PRIu64,
371 : 0 : (usec_t) seconds * USEC_PER_SEC + (usec_t) msec * USEC_PER_MSEC);
372 : 0 : iov[n_iov++] = IOVEC_MAKE_STRING(source_time_field);
373 : :
374 : 0 : sprintf(type_field, "_AUDIT_TYPE=%i", type);
375 : 0 : iov[n_iov++] = IOVEC_MAKE_STRING(type_field);
376 : :
377 : 0 : sprintf(id_field, "_AUDIT_ID=%" PRIu64, id);
378 : 0 : iov[n_iov++] = IOVEC_MAKE_STRING(id_field);
379 : :
380 : : assert_cc(4 == LOG_FAC(LOG_AUTH));
381 : 0 : iov[n_iov++] = IOVEC_MAKE_STRING("SYSLOG_FACILITY=4");
382 : 0 : iov[n_iov++] = IOVEC_MAKE_STRING("SYSLOG_IDENTIFIER=audit");
383 : :
384 [ # # # # : 0 : type_name = audit_type_name_alloca(type);
# # ]
385 : :
386 [ # # # # : 0 : type_field_name = strjoina("_AUDIT_TYPE_NAME=", type_name);
# # # # #
# # # ]
387 : 0 : iov[n_iov++] = IOVEC_MAKE_STRING(type_field_name);
388 : :
389 [ # # # # : 0 : m = strjoina("MESSAGE=", type_name, " ", p);
# # # # #
# # # ]
390 : 0 : iov[n_iov++] = IOVEC_MAKE_STRING(m);
391 : :
392 : 0 : z = n_iov;
393 : :
394 : 0 : map_all_fields(p, map_fields_kernel, "_AUDIT_FIELD_", true, &iov, &n_iov_allocated, &n_iov);
395 : :
396 [ # # ]: 0 : if (!GREEDY_REALLOC(iov, n_iov_allocated, n_iov + N_IOVEC_META_FIELDS)) {
397 : 0 : log_oom();
398 : 0 : goto finish;
399 : : }
400 : :
401 : 0 : server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, NULL, LOG_NOTICE, 0);
402 : :
403 : 0 : finish:
404 : : /* free() all entries that map_all_fields() added. All others
405 : : * are allocated on the stack or are constant. */
406 : :
407 [ # # ]: 0 : for (; z < n_iov; z++)
408 : 0 : free(iov[z].iov_base);
409 : : }
410 : :
411 : 0 : void server_process_audit_message(
412 : : Server *s,
413 : : const void *buffer,
414 : : size_t buffer_size,
415 : : const struct ucred *ucred,
416 : : const union sockaddr_union *sa,
417 : : socklen_t salen) {
418 : :
419 : 0 : const struct nlmsghdr *nl = buffer;
420 : :
421 [ # # ]: 0 : assert(s);
422 : :
423 [ # # ]: 0 : if (buffer_size < ALIGN(sizeof(struct nlmsghdr)))
424 : 0 : return;
425 : :
426 [ # # ]: 0 : assert(buffer);
427 : :
428 : : /* Filter out fake data */
429 [ # # # # ]: 0 : if (!sa ||
430 : 0 : salen != sizeof(struct sockaddr_nl) ||
431 [ # # ]: 0 : sa->nl.nl_family != AF_NETLINK ||
432 [ # # ]: 0 : sa->nl.nl_pid != 0) {
433 [ # # ]: 0 : log_debug("Audit netlink message from invalid sender.");
434 : 0 : return;
435 : : }
436 : :
437 [ # # # # ]: 0 : if (!ucred || ucred->pid != 0) {
438 [ # # ]: 0 : log_debug("Audit netlink message with invalid credentials.");
439 : 0 : return;
440 : : }
441 : :
442 [ # # # # : 0 : if (!NLMSG_OK(nl, buffer_size)) {
# # ]
443 [ # # ]: 0 : log_error("Audit netlink message truncated.");
444 : 0 : return;
445 : : }
446 : :
447 : : /* Ignore special Netlink messages */
448 [ # # # # ]: 0 : if (IN_SET(nl->nlmsg_type, NLMSG_NOOP, NLMSG_ERROR))
449 : 0 : return;
450 : :
451 : : /* Except AUDIT_USER, all messages below AUDIT_FIRST_USER_MSG are control messages, let's ignore those */
452 [ # # # # ]: 0 : if (nl->nlmsg_type < AUDIT_FIRST_USER_MSG && nl->nlmsg_type != AUDIT_USER)
453 : 0 : return;
454 : :
455 : 0 : process_audit_string(s, nl->nlmsg_type, NLMSG_DATA(nl), nl->nlmsg_len - ALIGN(sizeof(struct nlmsghdr)));
456 : : }
457 : :
458 : 0 : static int enable_audit(int fd, bool b) {
459 : : struct {
460 : : union {
461 : : struct nlmsghdr header;
462 : : uint8_t header_space[NLMSG_HDRLEN];
463 : : };
464 : : struct audit_status body;
465 : 0 : } _packed_ request = {
466 : : .header.nlmsg_len = NLMSG_LENGTH(sizeof(struct audit_status)),
467 : : .header.nlmsg_type = AUDIT_SET,
468 : : .header.nlmsg_flags = NLM_F_REQUEST,
469 : : .header.nlmsg_seq = 1,
470 : : .header.nlmsg_pid = 0,
471 : : .body.mask = AUDIT_STATUS_ENABLED,
472 : : .body.enabled = b,
473 : : };
474 : 0 : union sockaddr_union sa = {
475 : : .nl.nl_family = AF_NETLINK,
476 : : .nl.nl_pid = 0,
477 : : };
478 : 0 : struct iovec iovec = {
479 : : .iov_base = &request,
480 : : .iov_len = NLMSG_LENGTH(sizeof(struct audit_status)),
481 : : };
482 : 0 : struct msghdr mh = {
483 : : .msg_iov = &iovec,
484 : : .msg_iovlen = 1,
485 : : .msg_name = &sa.sa,
486 : : .msg_namelen = sizeof(sa.nl),
487 : : };
488 : :
489 : : ssize_t n;
490 : :
491 : 0 : n = sendmsg(fd, &mh, MSG_NOSIGNAL);
492 [ # # ]: 0 : if (n < 0)
493 : 0 : return -errno;
494 [ # # ]: 0 : if (n != NLMSG_LENGTH(sizeof(struct audit_status)))
495 : 0 : return -EIO;
496 : :
497 : : /* We don't wait for the result here, we can't do anything
498 : : * about it anyway */
499 : :
500 : 0 : return 0;
501 : : }
502 : :
503 : 0 : int server_open_audit(Server *s) {
504 : : int r;
505 : :
506 [ # # ]: 0 : if (s->audit_fd < 0) {
507 : : static const union sockaddr_union sa = {
508 : : .nl.nl_family = AF_NETLINK,
509 : : .nl.nl_pid = 0,
510 : : .nl.nl_groups = AUDIT_NLGRP_READLOG,
511 : : };
512 : :
513 : 0 : s->audit_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT);
514 [ # # ]: 0 : if (s->audit_fd < 0) {
515 [ # # # # ]: 0 : if (IN_SET(errno, EAFNOSUPPORT, EPROTONOSUPPORT))
516 [ # # ]: 0 : log_debug("Audit not supported in the kernel.");
517 : : else
518 [ # # ]: 0 : log_warning_errno(errno, "Failed to create audit socket, ignoring: %m");
519 : :
520 : 0 : return 0;
521 : : }
522 : :
523 [ # # ]: 0 : if (bind(s->audit_fd, &sa.sa, sizeof(sa.nl)) < 0) {
524 [ # # ]: 0 : log_warning_errno(errno,
525 : : "Failed to join audit multicast group. "
526 : : "The kernel is probably too old or multicast reading is not supported. "
527 : : "Ignoring: %m");
528 : 0 : s->audit_fd = safe_close(s->audit_fd);
529 : 0 : return 0;
530 : : }
531 : : } else
532 : 0 : (void) fd_nonblock(s->audit_fd, true);
533 : :
534 : 0 : r = setsockopt_int(s->audit_fd, SOL_SOCKET, SO_PASSCRED, true);
535 [ # # ]: 0 : if (r < 0)
536 [ # # ]: 0 : return log_error_errno(r, "Failed to set SO_PASSCRED on audit socket: %m");
537 : :
538 : 0 : r = sd_event_add_io(s->event, &s->audit_event_source, s->audit_fd, EPOLLIN, server_process_datagram, s);
539 [ # # ]: 0 : if (r < 0)
540 [ # # ]: 0 : return log_error_errno(r, "Failed to add audit fd to event loop: %m");
541 : :
542 : : /* We are listening now, try to enable audit */
543 : 0 : r = enable_audit(s->audit_fd, true);
544 [ # # ]: 0 : if (r < 0)
545 [ # # ]: 0 : log_warning_errno(r, "Failed to issue audit enable call: %m");
546 : :
547 : 0 : return 0;
548 : : }
|