* ausearch-report.c - Format and output events
* Copyright (c) 2005-09,2011-13,2016-17,2021-23 Red Hat
* 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., 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1335, USA.
*
* Authors:
* Steve Grubb <sgrubb@redhat.com>
*/
#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include "libaudit.h"
#include "ausearch-options.h"
#include "ausearch-parse.h"
#include "ausearch-lookup.h"
#include "auparse.h"
#include "auparse-idata.h"
#include "auditd-config.h"
static void output_raw(llist *l);
static void output_default(llist *l);
static void output_interpreted(llist *l);
static void output_interpreted_record(const lnode *n, const event *e);
static void feed_auparse(llist *l, auparse_callback_ptr callback);
static void report_interpret(char *name, char *val, int comma, int rtype);
static void csv_event(auparse_state_t *au,
auparse_cb_event_t cb_event_type, void *user_data);
static void text_event(auparse_state_t *au,
auparse_cb_event_t cb_event_type, void *user_data);
extern time_t lol_get_eoe_timeout(void);
static unsigned long machine = -1;
static int cur_syscall = -1;
static unsigned long long a0, a1;
static int loaded = 0;
void ausearch_load_interpretations(const lnode *n)
{
if (loaded == 0) {
_auparse_load_interpretations(n->interp);
loaded = 1;
}
}
void ausearch_free_interpretations(void)
{
if (loaded) {
_auparse_free_interpretations();
loaded = 0;
}
}
void output_event(llist *l)
{
switch (report_format) {
case RPT_RAW:
output_raw(l);
break;
case RPT_DEFAULT:
output_default(l);
break;
case RPT_INTERP:
output_interpreted(l);
break;
case RPT_PRETTY:
break;
case RPT_CSV:
feed_auparse(l, csv_event);
break;
case RPT_TEXT:
feed_auparse(l, text_event);
break;
default:
fprintf(stderr, "Report format error");
exit(1);
}
}
static void output_raw(llist *l)
{
const lnode *n;
list_first(l);
n = list_get_cur(l);
if (!n) {
fprintf(stderr, "Error - no elements in record.");
return;
}
do {
if (l->fmt == LF_ENRICHED)
n->message[n->mlen] = AUDIT_INTERP_SEPARATOR;
puts(n->message);
} while ((n=list_next(l)));
}
* This function will take the linked list and format it for output. No
* interpretation is performed. The output order is lifo for everything.
*/
static void output_default(llist *l)
{
const lnode *n;
list_last(l);
n = list_get_cur(l);
printf("----\ntime->%s", ctime(&l->e.sec));
if (!n) {
fprintf(stderr, "Error - no elements in record.");
return;
}
if (n->type >= AUDIT_DAEMON_START && n->type < AUDIT_SYSCALL)
puts(n->message);
else {
do {
safe_print_string_n(n->message, n->mlen, 1);
} while ((n=list_prev(l)));
}
}
* This function will take the linked list and format it for output.
* Interpretation is performed to aid understanding of records. The output
* order is lifo for everything.
*/
static void output_interpreted(llist *l)
{
const lnode *n;
list_last(l);
n = list_get_cur(l);
printf("----\n");
if (!n) {
fprintf(stderr, "Error - no elements in record.");
return;
}
if (n->type >= AUDIT_DAEMON_START && n->type < AUDIT_SYSCALL)
output_interpreted_record(n, &(l->e));
else {
do {
output_interpreted_record(n, &(l->e));
} while ((n=list_prev(l)));
}
}
* This function will cycle through a single record and lookup each field's
* value that it finds.
*/
static void output_interpreted_record(const lnode *n, const event *e)
{
char *ptr, *str = n->message;
int found, comma = 0;
int num = n->type;
struct tm *btm;
char tmp[32];
machine = -1;
cur_syscall = -1;
* If we do, and there is a space in the line
* move the pointer to the first character past
* the space
*/
if (e->node) {
if ((ptr=strchr(str, ' ')) != NULL) {
str = ptr+1;
}
}
ptr = strchr(str, '(');
if (ptr == NULL) {
fprintf(stderr, "can't find time stamp\n");
return;
}
*ptr++ = 0;
if (num >= 0) {
const char * bptr;
bptr = audit_msg_type_to_name(num);
if (bptr) {
if (e->node)
printf("node=%s ", e->node);
printf("type=%s msg=audit(", bptr);
goto no_print;
}
}
if (e->node)
printf("node=%s ", e->node);
printf("%s(", str);
no_print:
str = strchr(ptr, ')');
if(str == NULL)
return;
*str++ = 0;
btm = localtime(&e->sec);
if (btm)
strftime(tmp, sizeof(tmp), "%x %T", btm);
else
strcpy(tmp, "?");
printf("%s", tmp);
printf(".%03u:%lu) ", e->milli, e->serial);
if (n->type == AUDIT_SYSCALL) {
a0 = n->a0;
a1 = n->a1;
}
ausearch_load_interpretations(n);
found = 0;
while (str && *str && (ptr = strchr(str, '='))) {
char *name, *val;
comma = 0;
found = 1;
name = ptr;
while (*name != ' ' && name > str)
--name;
*ptr++ = 0;
printf("%s=", str);
if (strcmp(name, "msg") == 0) {
str = ptr;
continue;
}
if (*name == '\'')
name++;
if (*ptr == '\'' || *ptr == '"') {
str = strchr(ptr+1, *ptr);
if (str) {
str++;
if (*str)
*str++ = 0;
}
} else {
str = strchr(ptr, ',');
val = strchr(ptr, ' ');
if (str && val && (str < val)) {
int ftype = auparse_interp_adjust_type(n->type,
name, val);
if (ftype == AUPARSE_TYPE_MAC_LABEL) {
str = val;
*str++ = 0;
} else {
*str++ = 0;
comma = 1;
}
} else if (str && (val == NULL)) {
int ftype = auparse_interp_adjust_type(n->type,
name, ptr);
if (ftype == AUPARSE_TYPE_MAC_LABEL)
str = NULL;
else {
*str++ = 0;
comma = 1;
}
} else if (val) {
str = val;
*str++ = 0;
}
}
val = ptr;
report_interpret(name, val, comma, n->type);
}
ausearch_free_interpretations();
if (!found && ptr == NULL && str)
safe_print_string(str, 1);
else if (comma)
safe_print_string(str, 1);
printf("\n");
}
static void report_interpret(char *name, char *val, int comma, int rtype)
{
int type;
idata id;
while (*name == ' '||*name == '(')
name++;
if (*name == 'a' && strcmp(name, "acct") == 0) {
int len = strlen(val);
if (val[len-1] == ':')
val[len-1] = 0;
}
type = auparse_interp_adjust_type(rtype, name, val);
if (rtype == AUDIT_SYSCALL || rtype == AUDIT_SECCOMP ||
rtype == AUDIT_URINGOP) {
if (rtype == AUDIT_URINGOP)
machine = MACH_IO_URING;
else if (machine == (unsigned long)-1)
machine = audit_detect_machine();
if (*name == 'a' && strcmp(name, "arch") == 0) {
unsigned long ival;
errno = 0;
ival = strtoul(val, NULL, 16);
if (errno) {
printf("arch conversion error(%s) ", val);
return;
}
machine = audit_elf_to_machine(ival);
}
if (cur_syscall < 0 && ((*name == 's' &&
strcmp(name, "syscall") == 0) ||
(*name == 'u' && strcmp(name, "uring_op") == 0))) {
unsigned long ival;
errno = 0;
ival = strtoul(val, NULL, 10);
if (errno) {
printf("syscall conversion error(%s) ", val);
return;
}
cur_syscall = ival;
}
id.syscall = cur_syscall;
} else
id.syscall = 0;
id.machine = machine;
id.a0 = a0;
id.a1 = a1;
id.name = name;
id.val = val;
id.cwd = NULL;
char *out = auparse_do_interpretation(type, &id, escape_mode);
if (type == AUPARSE_TYPE_UNCLASSIFIED)
printf("%s%c", val, comma ? ',' : ' ');
else if (name[0] == 'k' && strcmp(name, "key") == 0) {
char *str, *ptr = out;
int count = 0;
while ((str = strchr(ptr, AUDIT_KEY_SEPARATOR))) {
*str = 0;
if (count == 0) {
printf("%s", ptr);
count++;
} else
printf(" key=%s", ptr);
ptr = str+1;
}
if (count == 0)
printf("%s ", out);
else
printf(" key=%s ", ptr);
} else if (type == AUPARSE_TYPE_TTY_DATA)
printf("%s", out);
else
printf("%s ", out);
free(out);
}
* fields one line per event in csv format */
static int csv_header_done = 0;
extern int extra_keys, extra_labels, extra_obj2, extra_time;
static void csv_event(auparse_state_t *au,
auparse_cb_event_t cb_event_type, void *user_data)
{
if (cb_event_type != AUPARSE_CB_EVENT_READY)
return;
if (csv_header_done == 0) {
csv_header_done = 1;
printf( "NODE,EVENT,DATE,TIME,%sSERIAL_NUM,EVENT_KIND,"
"SESSION,SUBJ_PRIME,SUBJ_SEC,SUBJ_KIND,%sACTION,"
"RESULT,OBJ_PRIME,OBJ_SEC,%s%sOBJ_KIND,HOW%s\n",
extra_time ? "YEAR,MONTH,DAY,WEEKDAY,HOUR,MILLI,GMT_OFFSET," : "",
extra_labels ? "SUBJ_LABEL," : "",
extra_obj2 ? "OBJ2," : "",
extra_labels ? "OBJ_LABEL," : "",
extra_keys ? ",KEY" : "");
}
char tmp[20];
const char *item, *type, *evkind, *subj_kind, *action, *str, *how;
int rc;
time_t t = auparse_get_time(au);
struct tm *tv = localtime(&t);
item = auparse_get_node(au);
if (item) {
printf("%s", auparse_interpret_field(au));
free((void *)item);
}
putchar(',');
type = auparse_get_type_name(au);
if (type)
printf("%s", type);
putchar(',');
rc = auparse_normalize(au,
extra_labels ? NORM_OPT_ALL : NORM_OPT_NO_ATTRS);
if (tv) {
strftime(tmp, sizeof(tmp), "%x", tv);
printf("%s", tmp);
}
putchar(',');
if (tv) {
strftime(tmp, sizeof(tmp), "%T", tv);
printf("%s", tmp);
}
putchar(',');
if (extra_time) {
if (tv) {
strftime(tmp, sizeof(tmp), "%Y", tv);
printf("%s", tmp);
}
putchar(',');
if (tv) {
strftime(tmp, sizeof(tmp), "%m", tv);
printf("%s", tmp);
}
putchar(',');
if (tv) {
strftime(tmp, sizeof(tmp), "%d", tv);
printf("%s", tmp);
}
putchar(',');
if (tv) {
strftime(tmp, sizeof(tmp), "%u", tv);
printf("%s", tmp);
}
putchar(',');
if (tv) {
strftime(tmp, sizeof(tmp), "%k", tv);
printf("%s", tmp);
}
putchar(',');
printf("%u", auparse_get_milli(au));
putchar(',');
if (tv) {
char sign = tv->tm_gmtoff >= 0 ? '+' : '-';
unsigned long total = labs(tv->tm_gmtoff);
unsigned long hour = total/3600;
unsigned long min = (total - (hour * 3600))%60;
printf("%c%02lu:%02lu", sign, hour, min);
}
putchar(',');
}
printf("%lu,", auparse_get_serial(au));
if (rc) {
fprintf(stderr, "error normalizing %s\n", type);
printf(",,,,,,,,,%s%s\n", extra_labels ? ",," : "",
extra_keys ? "," : "");
return;
}
evkind = auparse_normalize_get_event_kind(au);
printf("%s", evkind ? evkind : "unknown");
putchar(',');
rc = auparse_normalize_session(au);
if (rc == 1)
printf("%s", auparse_interpret_field(au));
putchar(',');
rc = auparse_normalize_subject_primary(au);
if (rc == 1) {
const char *subj = auparse_interpret_field(au);
if (strcmp(subj, "unset") == 0)
subj = "system";
printf("%s", subj);
}
putchar(',');
rc = auparse_normalize_subject_secondary(au);
if (rc == 1)
printf("%s", auparse_interpret_field(au));
putchar(',');
subj_kind = auparse_normalize_subject_kind(au);
if (subj_kind)
printf("%s", subj_kind);
putchar(',');
if (extra_labels) {
rc = auparse_normalize_subject_first_attribute(au);
do {
if (rc == 1) {
const char *name = auparse_get_field_name(au);
if (strcmp(name, "subj") == 0) {
printf("%s",
auparse_interpret_field(au));
break;
}
}
} while (auparse_normalize_subject_next_attribute(au) == 1);
putchar(',');
}
action = auparse_normalize_get_action(au);
printf("%s", action ? action : "did-unknown");
putchar(',');
rc = auparse_normalize_get_results(au);
if (rc == 1) {
int i = 0;
const char *res[] = { "failed", "success" };
item = auparse_interpret_field(au);
if (strcmp(item, "yes") == 0)
i = 1;
else if (strncmp(item, "suc", 3) == 0)
i = 1;
else if (auparse_get_field_type(au) == AUPARSE_TYPE_SECCOMP &&
strcmp(item, "allow") == 0)
i = 1;
printf("%s", res[i]);
}
putchar(',');
rc = auparse_normalize_object_primary(au);
if (rc == 1) {
const char *val;
if (auparse_get_field_type(au) == AUPARSE_TYPE_ESCAPED_FILE)
val = auparse_interpret_realpath(au);
else if (auparse_get_type(au) == AUDIT_CONFIG_CHANGE) {
if (action && ((strcmp(action, "set") == 0) ||
(strcmp(action, "seccomp-logging") == 0)))
val = auparse_get_field_name(au);
else
val = auparse_interpret_field(au);
} else
val = auparse_interpret_field(au);
printf("%s", val);
}
putchar(',');
rc = auparse_normalize_object_secondary(au);
if (rc == 1)
printf("%s", auparse_interpret_field(au));
putchar(',');
if (extra_obj2) {
rc = auparse_normalize_object_primary2(au);
if (rc == 1) {
const char *val;
if (auparse_get_field_type(au) ==
AUPARSE_TYPE_ESCAPED_FILE)
val = auparse_interpret_realpath(au);
else
val = auparse_interpret_field(au);
printf("%s", val);
}
putchar(',');
}
if (extra_labels) {
rc = auparse_normalize_object_first_attribute(au);
do {
if (rc == 1) {
const char *name = auparse_get_field_name(au);
if (strcmp(name, "obj") == 0) {
printf("%s",
auparse_interpret_field(au));
break;
}
}
} while (auparse_normalize_object_next_attribute(au) == 1);
putchar(',');
}
str = auparse_normalize_object_kind(au);
printf("%s,", str);
how = auparse_normalize_how(au);
if (how)
printf("%s", how);
if (extra_keys) {
putchar(',');
rc = auparse_normalize_key(au);
if (rc == 1)
printf("%s", auparse_interpret_field(au));
}
printf("\n");
}
* fields one line per event as an english sentence */
static void text_event(auparse_state_t *au,
auparse_cb_event_t cb_event_type, void *user_data)
{
if (cb_event_type != AUPARSE_CB_EVENT_READY)
return;
char tmp[20];
const char *item, *action, *how;
int rc, type, id = -2;
time_t t = auparse_get_time(au);
struct tm *tv = localtime(&t);
if (tv)
strftime(tmp, sizeof(tmp), "%T %x", tv);
else
strcpy(tmp, "?");
type = auparse_get_type(au);
auparse_normalize(au, NORM_OPT_NO_ATTRS);
item = auparse_get_node(au);
if (item) {
printf("On %s at %s ", auparse_interpret_field(au), tmp);
free((void *)item);
} else
printf("At %s ", tmp);
rc = auparse_normalize_subject_primary(au);
if (rc == 1) {
const char *subj = auparse_interpret_field(au);
id = auparse_get_field_int(au);
if (strcmp(subj, "unset") == 0)
subj = "system";
printf("%s", subj);
}
rc = auparse_normalize_subject_secondary(au);
if (rc == 1) {
int uid = auparse_get_field_int(au);
if (uid != id && id != -2 && uid != -1)
printf(", acting as %s,", auparse_interpret_field(au));
}
rc = auparse_normalize_get_results(au);
if (rc == 1) {
int i = 0;
const char *res[] = { "unsuccessfully", "successfully" };
item = auparse_interpret_field(au);
if (strcmp(item, "yes") == 0)
i = 1;
else if (strncmp(item, "suc", 3) == 0)
i = 1;
else if (auparse_get_field_type(au) == AUPARSE_TYPE_SECCOMP &&
strcmp(item, "allow") == 0)
i = 1;
printf(" %s ", res[i]);
} else
putchar(' ');
action = auparse_normalize_get_action(au);
if (event_debug) {
if (action == NULL)
printf("error on type:%d\n", type);
}
printf("%s ", action ? action : "did-unknown");
rc = auparse_normalize_object_primary(au);
if (rc == 1) {
const char *val = NULL;
int ftype;
if (action && strstr(action, "violated"))
val = "accessing ";
ftype = auparse_get_field_type(au);
if (ftype == AUPARSE_TYPE_ESCAPED_FILE)
val = auparse_interpret_realpath(au);
else if (ftype == AUPARSE_TYPE_SOCKADDR) {
val = auparse_interpret_sock_address(au);
if (val == NULL)
val = auparse_interpret_sock_family(au);
} else if (type == AUDIT_CONFIG_CHANGE) {
if (action &&
((strcmp(action, "set") == 0) ||
strcmp(action, "seccomp-logging") == 0))
val = auparse_get_field_name(au);
else
val = auparse_interpret_field(au);
}
if (val == NULL)
val = auparse_interpret_field(au);
printf("%s ", val);
}
rc = auparse_normalize_object_primary2(au);
if (rc == 1) {
const char *val;
if (auparse_get_field_type(au) == AUPARSE_TYPE_ESCAPED_FILE)
val = auparse_interpret_realpath(au);
else
val = auparse_interpret_field(au);
printf("to %s ", val);
}
how = auparse_normalize_how(au);
if (how && action && *action != 'e')
printf("using %s", how);
printf("\n");
}
* perform all formatting for the intended report option. */
static auparse_state_t *au = NULL;
static void feed_auparse(llist *l, auparse_callback_ptr callback)
{
const lnode *n;
list_first(l);
n = list_get_cur(l);
if (!n) {
fprintf(stderr, "Error - no elements in record.");
return;
}
if (au == NULL) {
au = auparse_init(AUSOURCE_FEED, 0);
auparse_set_escape_mode(au, escape_mode);
auparse_set_eoe_timeout(lol_get_eoe_timeout());
auparse_add_callback(au, callback, NULL, NULL);
}
do {
if (l->fmt == LF_ENRICHED)
n->message[n->mlen] = AUDIT_INTERP_SEPARATOR;
n->message[n->tlen] = 0x0a;
auparse_feed(au, n->message, n->tlen+1);
if (l->fmt == LF_ENRICHED)
n->message[n->mlen] = 0;
n->message[n->tlen] = 0;
} while ((n=list_next(l)));
auparse_flush_feed(au);
}
void output_auparse_finish(void)
{
if (au)
auparse_destroy(au);
au = NULL;
}