/* auparse.c --
* Copyright 2006-08,2012-19 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 "expression.h"
#include "internal.h"
#include "auparse.h"
#include "interpret.h"
#include "auparse-idata.h"
#include "libaudit.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio_ext.h>
#include "common.h"
//#define LOL_EVENTS_DEBUG01 1 // add debug for list of list event
// processing
#ifdef LOL_EVENTS_DEBUG01
static int debug = 0;
#endif
static void init_lib(void) __attribute__ ((constructor));
static void init_lib(void)
{
init_interpretation_list();
}
/* like strchr except string is delimited by length, not null byte */
static char *strnchr(const char *s, int c, size_t n)
{
char *p_char;
const char *p_end = s + n;
for (p_char = (char *)s; p_char < p_end && *p_char != c; p_char++);
if (p_char == p_end) return NULL;
return p_char;
}
static int setup_log_file_array(auparse_state_t *au)
{
struct daemon_conf config;
char *filename, **tmp;
int len, num = 0, i = 0;
/* Load config so we know where logs are */
set_aumessage_mode(au, MSG_STDERR, DBG_NO);
aup_load_config(au, &config, TEST_SEARCH);
/* for each file */
len = strlen(config.log_file) + 16;
filename = malloc(len);
if (!filename) {
fprintf(stderr, "No memory\n");
free_config(&config);
return 1;
}
/* Find oldest log file */
snprintf(filename, len, "%s", config.log_file);
do {
if (access(filename, R_OK) != 0)
break;
num++;
snprintf(filename, len, "%s.%d", config.log_file, num);
} while (1);
if (num == 0) {
fprintf(stderr, "No log file\n");
free_config(&config);
free(filename);
return 1;
}
num--;
tmp = malloc((num+2)*sizeof(char *));
/* Got it, now process logs from last to first */
if (num > 0)
snprintf(filename, len, "%s.%d", config.log_file, num);
else
snprintf(filename, len, "%s", config.log_file);
do {
tmp[i++] = strdup(filename);
/* Get next log file */
num--;
if (num > 0)
snprintf(filename, len, "%s.%d", config.log_file, num);
else if (num == 0)
snprintf(filename, len, "%s", config.log_file);
else
break;
} while (1);
free_config(&config);
free(filename);
// Terminate the list
tmp[i] = NULL;
au->source_list = tmp;
return 0;
}
/*
* au_lol_create - Create and initialise the base List of List event structure
* Args:
* lol - pointer to memory holding structure (eg the static au_lo variable)
* Rtns:
* NULL - no memory
* ptr - pointer to array of event nodes (au_lolnode)
*/
static au_lolnode *au_lol_create(au_lol *lol)
{
int sz = ARRAY_LIMIT * sizeof(au_lolnode);
lol->maxi = -1;
lol->limit = ARRAY_LIMIT;
if ((lol->array = (au_lolnode *)malloc(sz)) == NULL) {
lol->maxi = -1;
return NULL;
}
memset(lol->array, 0x00, sz);
return lol->array;
}
/*
* au_lol_clear - Free or rest the base List of List event structure
*
* Args:
* lol - pointer to memory holding structure (eg the static au_lo variable)
* reset - flag to indicate a reset of the structure, or the complete
* freeing of memory
* Rtns:
* void
*/
static void au_lol_clear(au_lol *lol, int reset)
{
int i;
if (lol->array) {
for (i = 0; i <= lol->maxi; i++) {
if (lol->array[i].l) {
aup_list_clear(lol->array[i].l);
free(lol->array[i].l);
}
}
}
if (reset) {
/* If resetting, we just zero fields */
if (lol->array)
memset(lol->array, 0x00,
lol->limit * sizeof(au_lolnode));
lol->maxi = -1;
} else {
/* If not resetting, we free everything */
if (lol->array) free(lol->array);
lol->array = NULL;
lol->maxi = -1;
}
}
/*
* au_lol_append - Add a new event to our base List of List structure
*
* Args:
* lol - pointer to memory holding structure (eg the static au_lo variable)
* l - event list structure (which contains an event's constituent records)
* Rtns:
* ptr - pointer to au_lolnode which holds the event list structure
* NULL - failed to reallocate memory
*/
static au_lolnode *au_lol_append(au_lol *lol, event_list_t *l)
{
int i;
size_t new_size;
au_lolnode *ptr;
for (i = 0; i < lol->limit; i++) {
au_lolnode *cur = &lol->array[i];
if (cur->status == EBS_EMPTY) {
cur->l = l;
cur->status = EBS_BUILDING;
if (i > lol->maxi)
lol->maxi = i;
return cur;
}
}
/* Over ran the array, make it bigger */
new_size = sizeof(au_lolnode) * (lol->limit + ARRAY_LIMIT);
ptr = realloc(lol->array, new_size);
if (ptr) {
lol->array = ptr;
memset(&lol->array[lol->limit], 0x00,
sizeof(au_lolnode) * ARRAY_LIMIT);
lol->array[i].l = l;
lol->array[i].status = EBS_BUILDING;
lol->maxi = i;
lol->limit += ARRAY_LIMIT;
}
return ptr;
}
/*
* au_get_ready_event - Find the next COMPLETE event in our list and mark EMPTY
*
* Args:
* lol - pointer to memory holding structure (eg the static au_lo variable)
* is_test - do not mark the node EMPTY
* Rtns:
* ptr - pointer to complete node (possibly just marked empty)
* NULL - no complete nodes exist
*/
static event_list_t *au_get_ready_event(auparse_state_t *au, int is_test)
{
int i;
au_lol *lol = au->au_lo;
au_lolnode *lowest = NULL;
if (au->au_ready == 0) {
//if (debug) printf("No events ready\n");
return NULL;
}
for (i=0; i<=lol->maxi; i++) {
// Look for the event with the lowest timestamp
au_lolnode *cur = &(lol->array[i]);
if (cur->status == EBS_EMPTY)
continue;
// If we are just testing for a complete event, return
if (is_test && cur->status == EBS_COMPLETE)
return cur->l;
if (lowest == NULL)
lowest = cur;
else if (auparse_timestamp_compare(&(lowest->l->e),
&(cur->l->e)) == 1)
lowest = cur;
}
if (lowest && lowest->status == EBS_COMPLETE) {
lowest->status = EBS_EMPTY;
au->au_ready--;
return lowest->l;
}
return NULL;
}
/*
* au_check_events - Run though all events marking those we can mark COMPLETE
*
* Args:
* lol - pointer to memory holding structure (eg the static au_lo variable)
* sec - time of current event from stream being processed. We use this to see
* how old the events are we have in our list
* Rtns:
* void
*/
static void au_check_events(auparse_state_t *au, time_t sec)
{
rnode *r;
int i;
au_lol *lol = au->au_lo;
for(i=0; i<=lol->maxi; i++) {
au_lolnode *cur = &lol->array[i];
if (cur->status == EBS_BUILDING) {
if ((r = aup_list_get_cur(cur->l)) == NULL)
continue;
// If 2 seconds have elapsed, we are done
if (cur->l->e.sec + 2 <= sec) {
cur->status = EBS_COMPLETE;
au->au_ready++;
} else if ( // FIXME: Check this v remains true
r->type == AUDIT_PROCTITLE ||
r->type == AUDIT_EOE ||
r->type < AUDIT_FIRST_EVENT ||
r->type >= AUDIT_FIRST_ANOM_MSG ||
r->type == AUDIT_KERNEL ||
(r->type >= AUDIT_MAC_UNLBL_ALLOW &&
r->type <= AUDIT_MAC_CALIPSO_DEL)) {
// If known to be 1 record event, we are done
cur->status = EBS_COMPLETE;
au->au_ready++;
}
}
}
}
/*
* au_terminate_all_events - Mark all events in 'BUILD' state to be COMPLETE
*
* Args:
* lol - pointer to memory holding structure (eg the static au_lo variable)
* Rtns:
* void
*/
static void au_terminate_all_events(auparse_state_t *au)
{
int i;
au_lol *lol = au->au_lo;
for (i=0; i<=lol->maxi; i++) {
au_lolnode *cur = &lol->array[i];
if (cur->status == EBS_BUILDING) {
cur->status = EBS_COMPLETE;
au->au_ready++;
//if (debug) printf("%d events complete\n", au->au_ready);
}
}
}
#ifdef LOL_EVENTS_DEBUG01
/*
* print_list_t - Print summary of event's records
* Args:
* l - event_list to print
* Rtns:
* void
*/
void print_list_t(event_list_t *l)
{
rnode *r;
if (l == NULL) {
printf("\n");
return;
}
printf("0x%X: %ld.%3.3u:%lu %s", l, l->e.sec, l->e.milli,
l->e.serial, l->e.host ? l->e.host : "");
printf(" cnt=%u", l->cnt);
for (r = l->head; r != NULL; r = r->next) {
printf(" {%d %d %u}", r->type, r->list_idx, r->line_number);
}
printf("\n");
}
/*
* lol_status - return type of event state as a character
* Args:
* s - event state
* Rtns:
* char - E, B or C for EMPTY, BUILDING or COMPLETE, or '*' for unknown
*/
static char lol_status(au_lol_t s)
{
switch(s) {
case EBS_EMPTY: return 'E'; break;
case EBS_BUILDING: return 'B'; break;
case EBS_COMPLETE: return 'C'; break;
}
return '*';
}
/*
* print_lol - Print a list of list events and their records
* Args:
* label - String to act as label when printing
* lol - pointer to memory holding structure (eg the static au_lo variable)
* Rtns:
* void
*/
void print_lol(char *label, au_lol *lol)
{
int i;
printf("%s 0x%X: a: 0x%X, %d, %d\n", label, lol, lol->array,
lol->maxi, lol->limit);
if (debug > 1) for (i = 0; i <= lol->maxi; i++) {
printf("{%2d 0x%X %c } ", i, (&lol->array[i]),
lol_status(lol->array[i].status));
print_list_t(lol->array[i].l);
}
if (lol->maxi >= 0)
printf("\n");
}
#endif /* LOL_EVENTS_DEBUG01 */
/* General functions that affect operation of the library */
auparse_state_t *auparse_init(ausource_t source, const void *b)
{
char **tmp, **bb = (char **)b, *buf = (char *)b;
int n, i;
size_t size, len;
auparse_state_t *au = malloc(sizeof(auparse_state_t));
if (au == NULL) {
errno = ENOMEM;
return NULL;
}
au->le = NULL;
/*
* Set up the List of List events base structure
*/
au->au_lo = calloc(sizeof(au_lol), 1);
if (au->au_lo == NULL) {
free(au);
errno = ENOMEM;
return NULL;
}
au_lol_clear(au->au_lo, 0); // python doesn't call auparse_destroy
if (au_lol_create(au->au_lo) == NULL) {
free(au->au_lo);
free(au);
errno = ENOMEM;
return NULL;
}
au->au_ready = 0;
au->in = NULL;
au->source_list = NULL;
databuf_init(&au->databuf, 0, 0);
au->callback = NULL;
au->callback_user_data = NULL;
au->callback_user_data_destroy = NULL;
switch (source)
{
case AUSOURCE_LOGS:
if (setup_log_file_array(au))
goto bad_exit;
break;
case AUSOURCE_FILE:
if (b == NULL)
goto bad_exit;
if (access(b, R_OK))
goto bad_exit;
tmp = malloc(2*sizeof(char *));
tmp[0] = strdup(b);
tmp[1] = NULL;
au->source_list = tmp;
break;
case AUSOURCE_FILE_ARRAY:
if (bb == NULL)
goto bad_exit;
n = 0;
while (bb[n]) {
if (access(bb[n], R_OK))
goto bad_exit;
n++;
}
tmp = malloc((n+1)*sizeof(char *));
for (i=0; i<n; i++)
tmp[i] = strdup(bb[i]);
tmp[n] = NULL;
au->source_list = tmp;
break;
case AUSOURCE_BUFFER:
if (buf == NULL)
goto bad_exit;
len = strlen(buf);
if (databuf_init(&au->databuf, len,
DATABUF_FLAG_PRESERVE_HEAD) < 0)
goto bad_exit;
if (databuf_append(&au->databuf, buf, len) < 0)
goto bad_exit;
break;
case AUSOURCE_BUFFER_ARRAY:
if (bb == NULL)
goto bad_exit;
size = 0;
for (n = 0; (buf = bb[n]); n++) {
len = strlen(bb[n]);
if (bb[n][len-1] != '\n') {
size += len + 1;
} else {
size += len;
}
}
if (databuf_init(&au->databuf, size,
DATABUF_FLAG_PRESERVE_HEAD) < 0)
goto bad_exit;
for (n = 0; (buf = bb[n]); n++) {
len = strlen(buf);
if (databuf_append(&au->databuf, buf, len) < 0)
goto bad_exit;
}
break;
case AUSOURCE_DESCRIPTOR:
n = (long)b;
au->in = fdopen(n, "rm");
break;
case AUSOURCE_FILE_POINTER:
au->in = (FILE *)b;
break;
case AUSOURCE_FEED:
if (databuf_init(&au->databuf, 0, 0) < 0) goto bad_exit;
break;
default:
errno = EINVAL;
goto bad_exit;
break;
}
au->source = source;
au->list_idx = 0;
au->line_number = 0;
au->next_buf = NULL;
au->off = 0;
au->cur_buf = NULL;
au->line_pushed = 0;
au->parse_state = EVENT_EMPTY;
au->expr = NULL;
au->find_field = NULL;
au->search_where = AUSEARCH_STOP_EVENT;
au->escape_mode = AUPARSE_ESC_TTY;
au->message_mode = MSG_QUIET;
au->debug_message = DBG_NO;
au->tmp_translation = NULL;
init_normalizer(&au->norm_data);
return au;
bad_exit:
databuf_free(&au->databuf);
/* Free list of events list (au_lo) structure */
au_lol_clear(au->au_lo, 0);
free(au->au_lo);
free(au);
return NULL;
}
void auparse_add_callback(auparse_state_t *au, auparse_callback_ptr callback,
void *user_data, user_destroy user_destroy_func)
{
if (au == NULL) {
errno = EINVAL;
return;
}
if (au->callback_user_data_destroy) {
(*au->callback_user_data_destroy)(au->callback_user_data);
au->callback_user_data = NULL;
}
au->callback = callback;
au->callback_user_data = user_data;
au->callback_user_data_destroy = user_destroy_func;
}
static void consume_feed(auparse_state_t *au, int flush)
{
//if (debug) printf("consume feed, flush %d\n", flush);
while (auparse_next_event(au) > 0) {
if (au->callback) {
(*au->callback)(au, AUPARSE_CB_EVENT_READY,
au->callback_user_data);
}
}
if (flush) {
// FIXME: might need a call here to force auparse_next_event()
// to consume any partial data not fully consumed.
/* Terminate all outstanding events, as we are at end of input
* (ie mark BUILDING events as COMPLETE events) then if we
* have a callback execute the callback on each event
* FIXME: Should we implement a 'checkpoint' concept as per
* ausearch or accept these 'partial' events?
*/
event_list_t *l;
//if (debug) printf("terminate all events in flush\n");
au_terminate_all_events(au);
while ((l = au_get_ready_event(au, 0)) != NULL) {
rnode *r;
au->le = l; // make this current the event of interest
aup_list_first(l);
r = aup_list_get_cur(l);
free_interpretation_list();
load_interpretation_list(r->interp);
aup_list_first_field(l);
if (au->callback) {
(*au->callback)(au, AUPARSE_CB_EVENT_READY,
au->callback_user_data);
}
}
}
}
int auparse_feed(auparse_state_t *au, const char *data, size_t data_len)
{
if (databuf_append(&au->databuf, data, data_len) < 0)
return -1;
consume_feed(au, 0);
return 0;
}
int auparse_flush_feed(auparse_state_t *au)
{
consume_feed(au, 1);
return 0;
}
// If there is data in the state machine, return 1
// Otherwise return 0 to indicate its empty
int auparse_feed_has_data(auparse_state_t *au)
{
if (au_get_ready_event(au, 1) != NULL)
return 1;
return 0;
}
void auparse_feed_age_events(auparse_state_t *au)
{
time_t t = time(NULL);
au_check_events(au, t);
consume_feed(au, 0);
}
void auparse_set_escape_mode(auparse_state_t *au, auparse_esc_t mode)
{
if (au == NULL)
return;
au->escape_mode = mode;
}
/*
* Non-public function. Subject to change.
* buf is a string of name value pairs to be used for interpreting.
* Calling this function automatically releases the previous list.
*/
void _auparse_load_interpretations(const char *buf)
{
free_interpretation_list();
if (buf == NULL)
return;
load_interpretation_list(buf);
}
/*
* Non-public function. Subject to change.
*/
void _auparse_free_interpretations(void)
{
free_interpretation_list();
}
int auparse_reset(auparse_state_t *au)
{
if (au == NULL) {
errno = EINVAL;
return -1;
}
/* Create or Free list of events list (au_lo) structure */
if (au->au_lo->array == NULL)
au_lol_create(au->au_lo);
else
au_lol_clear(au->au_lo, 1);
au->parse_state = EVENT_EMPTY;
au->au_ready = 0;
au->le = NULL;
switch (au->source)
{
case AUSOURCE_LOGS:
case AUSOURCE_FILE:
case AUSOURCE_FILE_ARRAY:
if (au->in) {
fclose(au->in);
au->in = NULL;
}
/* Fall through */
case AUSOURCE_DESCRIPTOR:
case AUSOURCE_FILE_POINTER:
if (au->in)
rewind(au->in);
/* Fall through */
case AUSOURCE_BUFFER:
case AUSOURCE_BUFFER_ARRAY:
au->list_idx = 0;
au->line_number = 0;
au->off = 0;
databuf_reset(&au->databuf);
break;
default:
return -1;
}
free_interpretation_list();
return 0;
}
/* Add EXPR to AU, using HOW to select the combining operator.
On success, return 0.
On error, free EXPR set errno and return -1.
NOTE: EXPR is freed on error! */
static int add_expr(auparse_state_t *au, struct expr *expr, ausearch_rule_t how)
{
if (au->expr == NULL)
au->expr = expr;
else if (how == AUSEARCH_RULE_CLEAR) {
expr_free(au->expr);
au->expr = expr;
} else {
struct expr *e;
e = expr_create_binary(how == AUSEARCH_RULE_OR ? EO_OR : EO_AND,
au->expr, expr);
if (e == NULL) {
int err;
err = errno;
expr_free(expr);
errno = err;
return -1;
}
au->expr = e;
}
au->expr->started = 0;
return 0;
}
static int ausearch_add_item_internal(auparse_state_t *au, const char *field,
const char *op, const char *value, ausearch_rule_t how, unsigned op_eq,
unsigned op_ne)
{
struct expr *expr;
// Make sure there's a field
if (field == NULL)
goto err_out;
// Make sure how is within range
if (how < AUSEARCH_RULE_CLEAR || how > AUSEARCH_RULE_AND)
goto err_out;
// All pre-checks are done, build a rule
if (strcmp(op, "exists") == 0)
expr = expr_create_field_exists(field);
else {
unsigned t_op;
if (strcmp(op, "=") == 0)
t_op = op_eq;
else if (strcmp(op, "!=") == 0)
t_op = op_ne;
else
goto err_out;
if (value == NULL)
goto err_out;
expr = expr_create_comparison(field, t_op, value);
}
if (expr == NULL)
return -1;
if (add_expr(au, expr, how) != 0)
return -1; /* expr is freed by add_expr() */
return 0;
err_out:
errno = EINVAL;
return -1;
}
int ausearch_add_item(auparse_state_t *au, const char *field, const char *op,
const char *value, ausearch_rule_t how)
{
return ausearch_add_item_internal(au, field, op, value, how, EO_RAW_EQ,
EO_RAW_NE);
}
int ausearch_add_interpreted_item(auparse_state_t *au, const char *field,
const char *op, const char *value, ausearch_rule_t how)
{
return ausearch_add_item_internal(au, field, op, value, how,
EO_INTERPRETED_EQ, EO_INTERPRETED_NE);
}
int ausearch_add_timestamp_item_ex(auparse_state_t *au, const char *op,
time_t sec, unsigned milli, unsigned serial, ausearch_rule_t how)
{
static const struct {
unsigned value;
const char name[3];
} ts_tab[] = {
{EO_VALUE_LT, "<"},
{EO_VALUE_LE, "<="},
{EO_VALUE_GE, ">="},
{EO_VALUE_GT, ">"},
{EO_VALUE_EQ, "="},
};
struct expr *expr;
size_t i;
unsigned t_op;
for (i = 0; i < sizeof(ts_tab) / sizeof(*ts_tab); i++) {
if (strcmp(ts_tab[i].name, op) == 0)
goto found_op;
}
goto err_out;
found_op:
t_op = ts_tab[i].value;
if (milli >= 1000)
goto err_out;
// Make sure how is within range
if (how < AUSEARCH_RULE_CLEAR || how > AUSEARCH_RULE_AND)
goto err_out;
// All pre-checks are done, build a rule
expr = expr_create_timestamp_comparison_ex(t_op, sec, milli, serial);
if (expr == NULL)
return -1;
if (add_expr(au, expr, how) != 0)
return -1; /* expr is freed by add_expr() */
return 0;
err_out:
errno = EINVAL;
return -1;
}
int ausearch_add_timestamp_item(auparse_state_t *au, const char *op, time_t sec,
unsigned milli, ausearch_rule_t how)
{
return ausearch_add_timestamp_item_ex(au, op, sec, milli, 0, how);
}
int ausearch_add_expression(auparse_state_t *au, const char *expression,
char **error, ausearch_rule_t how)
{
struct expr *expr;
if (how < AUSEARCH_RULE_CLEAR || how > AUSEARCH_RULE_AND)
goto err_einval;
expr = expr_parse(expression, error);
if (expr == NULL) {
errno = EINVAL;
return -1;
}
if (add_expr(au, expr, how) != 0)
goto err; /* expr is freed by add_expr() */
return 0;
err_einval:
errno = EINVAL;
err:
*error = NULL;
return -1;
}
int ausearch_add_regex(auparse_state_t *au, const char *regexp)
{
struct expr *expr;
// Make sure there's an expression
if (regexp == NULL)
goto err_out;
expr = expr_create_regexp_expression(regexp);
if (expr == NULL)
return -1;
if (add_expr(au, expr, AUSEARCH_RULE_AND) != 0)
return -1; /* expr is freed by add_expr() */
return 0;
err_out:
errno = EINVAL;
return -1;
}
int ausearch_set_stop(auparse_state_t *au, austop_t where)
{
if (where < AUSEARCH_STOP_EVENT || where > AUSEARCH_STOP_FIELD) {
errno = EINVAL;
return -1;
}
au->search_where = where;
return 0;
}
void ausearch_clear(auparse_state_t *au)
{
if (au->expr != NULL) {
expr_free(au->expr);
au->expr = NULL;
}
au->search_where = AUSEARCH_STOP_EVENT;
}
static void auparse_destroy_common(auparse_state_t *au)
{
if (au == NULL)
return;
if (au->source_list) {
int n = 0;
while (au->source_list[n])
free(au->source_list[n++]);
free(au->source_list);
au->source_list = NULL;
}
au->next_buf = NULL;
free(au->cur_buf);
au->cur_buf = NULL;
au->le = NULL;
au->parse_state = EVENT_EMPTY;
free(au->find_field);
au->find_field = NULL;
ausearch_clear(au);
databuf_free(&au->databuf);
if (au->callback_user_data_destroy) {
(*au->callback_user_data_destroy)(au->callback_user_data);
au->callback_user_data = NULL;
}
if (au->in) {
fclose(au->in);
au->in = NULL;
}
free_interpretation_list();
clear_normalizer(&au->norm_data);
au_lol_clear(au->au_lo, 0);
free((void *)au->tmp_translation);
free(au->au_lo);
free(au);
}
void auparse_destroy(auparse_state_t *au)
{
aulookup_destroy_uid_list();
aulookup_destroy_gid_list();
auparse_destroy_common(au);
}
void auparse_destroy_ext(auparse_state_t *au, auparse_destroy_what_t what)
{
if (what == AUPARSE_DESTROY_COMMON)
auparse_destroy_common(au);
else if (what == AUPARSE_DESTROY_ALL)
auparse_destroy(au);
return;
}
/* alloc a new buffer, cur_buf which contains a null terminated line
* without a newline (note, this implies the line may be empty (strlen == 0)) if
* successfully read a blank line (e.g. containing only a single newline).
* cur_buf will have been newly allocated with malloc.
*
* Note: cur_buf will be freed the next time this routine is called if
* cur_buf is not NULL, callers who retain a reference to the cur_buf
* pointer will need to set cur_buf to NULL to cause the previous cur_buf
* allocation to persist.
*
* Returns:
* 1 if successful (errno == 0)
* 0 if non-blocking input unavailable (errno == 0)
* -1 if error (errno contains non-zero error code)
* -2 if EOF (errno == 0)
*/
static int readline_file(auparse_state_t *au)
{
ssize_t rc;
char *p_last_char;
size_t n = 0;
if (au->cur_buf != NULL) {
free(au->cur_buf);
au->cur_buf = NULL;
}
if (au->in == NULL) {
errno = EBADF;
return -1;
}
if ((rc = getline(&au->cur_buf, &n, au->in)) <= 0) {
// Note: getline always malloc's if lineptr==NULL or n==0,
// on failure malloc'ed memory is left uninitialized,
// caller must free it.
free(au->cur_buf);
au->cur_buf = NULL;
// Note: feof() does not set errno
if (feof(au->in)) {
// return EOF condition
errno = 0;
return -2;
}
// return error condition, error code in errno
return -1;
}
p_last_char = au->cur_buf + (rc-1);
if (*p_last_char == '\n') { /* nuke newline */
*p_last_char = 0;
}
// return success
errno = 0;
return 1;
}
/* malloc & copy a line into cur_buf from the internal buffer,
* next_buf. cur_buf will contain a null terminated line without a
* newline (note, this implies the line may be empty (strlen == 0)) if
* successfully read a blank line (e.g. containing only a single
* newline).
*
* Note: cur_buf will be freed the next time this routine is called if
* cur_buf is not NULL, callers who retain a reference to the cur_buf
* pointer will need to set cur_buf to NULL to cause the previous cur_buf
* allocation to persist.
*
* Returns:
* 1 if successful (errno == 0)
* 0 if non-blocking input unavailable (errno == 0)
* -1 if error (errno contains non-zero error code)
* -2 if EOF (errno == 0)
*/
static int readline_buf(auparse_state_t *au)
{
char *p_newline=NULL;
size_t line_len;
if (au->cur_buf != NULL) {
free(au->cur_buf);
au->cur_buf = NULL;
}
//if (debug) databuf_print(&au->databuf, 1, "readline_buf");
if (au->databuf.len == 0) {
// return EOF condition
errno = 0;
return -2;
}
if ((p_newline = strnchr(databuf_beg(&au->databuf), '\n',
au->databuf.len)) != NULL) {
line_len = p_newline - databuf_beg(&au->databuf);
/* dup the line */
au->cur_buf = malloc(line_len+1); // +1 for null terminator
if (au->cur_buf == NULL)
return -1; // return error condition, errno set
strncpy(au->cur_buf, databuf_beg(&au->databuf), line_len);
au->cur_buf[line_len] = 0;
if (databuf_advance(&au->databuf, line_len+1) < 0)
return -1;
// return success
errno = 0;
return 1;
} else {
// return no data available
errno = 0;
return 0;
}
}
static int str2event(char *s, au_event_t *e)
{
char *ptr;
errno = 0;
e->sec = strtoul(s, NULL, 10);
if (errno)
return -1;
ptr = strchr(s, '.');
if (ptr) {
ptr++;
e->milli = strtoul(ptr, NULL, 10);
if (errno)
return -1;
s = ptr;
} else
e->milli = 0;
ptr = strchr(s, ':');
if (ptr) {
ptr++;
e->serial = strtoul(ptr, NULL, 10);
if (errno)
return -1;
} else
e->serial = 0;
return 0;
}
#ifndef HAVE_STRNDUPA
static inline char *strndupa(const char *old, size_t n)
{
size_t len = strnlen(old, n);
char *tmp = alloca(len + 1);
tmp[len] = 0;
return memcpy(tmp, old, len);
}
#endif
/* Returns 0 on success and 1 on error */
static int extract_timestamp(const char *b, au_event_t *e)
{
char *ptr, *tmp;
int rc = 1;
e->host = NULL;
if (*b == 'n')
tmp = strndupa(b, 340);
else
tmp = strndupa(b, 80);
ptr = audit_strsplit(tmp);
if (ptr) {
// Optionally grab the node - may or may not be included
if (*ptr == 'n') {
e->host = strdup(ptr+5);
(void)audit_strsplit(NULL);// Bump along to next one
}
// at this point we have type=
ptr = audit_strsplit(NULL);
if (ptr) {
if (*(ptr+9) == '(')
ptr+=9;
else
ptr = strchr(ptr, '(');
if (ptr) {
// now we should be pointed at the timestamp
char *eptr;
ptr++;
eptr = strchr(ptr, ')');
if (eptr)
*eptr = 0;
if (str2event(ptr, e) == 0)
rc = 0;
}
// else we have a bad line
}
// else we have a bad line
}
if (rc)
free((void *)e->host);
// else we have a bad line
return rc;
}
static int events_are_equal(au_event_t *e1, au_event_t *e2)
{
// Check time & serial first since its most likely way
// to spot 2 different events
if (!(e1->serial == e2->serial && e1->milli == e2->milli &&
e1->sec == e2->sec))
return 0;
// Hmm...same so far, check if both have a host, only a string
// compare can tell if they are the same. Otherwise, if only one
// of them have a host, they are definitely not the same. Its
// a boundary on daemon config.
if (e1->host && e2->host) {
if (strcmp(e1->host, e2->host))
return 0;
} else if (e1->host || e2->host)
return 0;
return 1;
}
/* This function will figure out how to get the next line of input.
* storing it cur_buf. cur_buf will be NULL terminated but will not
* contain a trailing newline. This implies a successful read
* (result == 1) may result in a zero length cur_buf if a blank line
* was read.
*
* cur_buf will have been allocated with malloc. The next time this
* routine is called if cur_buf is non-NULL cur_buf will be freed,
* thus if the caller wishes to retain a reference to malloc'ed
* cur_buf data it should copy the cur_buf pointer and set cur_buf to
* NULL.
*
* Returns:
* 1 if successful (errno == 0)
* 0 if non-blocking input unavailable (errno == 0)
* -1 if error (errno contains non-zero error code)
* -2 if EOF (errno == 0)
*/
static int retrieve_next_line(auparse_state_t *au)
{
int rc;
// If line was pushed back for re-reading return that
if (au->line_pushed) {
// Starting new event, clear previous event data,
// previous line is returned again for new parsing
au->line_pushed = 0;
au->line_number++;
return 1;
}
switch (au->source)
{
case AUSOURCE_DESCRIPTOR:
case AUSOURCE_FILE_POINTER:
rc = readline_file(au);
if (rc > 0) au->line_number++;
return rc;
case AUSOURCE_LOGS:
case AUSOURCE_FILE:
case AUSOURCE_FILE_ARRAY:
// if the first time through, open file
if (au->list_idx == 0 && au->in == NULL &&
au->source_list != NULL) {
if (au->source_list[au->list_idx] == NULL) {
errno = 0;
return -2;
}
au->line_number = 0;
au->in = fopen(au->source_list[au->list_idx],
"rm");
if (au->in == NULL)
return -1;
__fsetlocking(au->in, FSETLOCKING_BYCALLER);
}
// loop reading lines from a file
while (au->in) {
if ((rc = readline_file(au)) == -2) {
// end of file, open next file,
// try readline again
fclose(au->in);
au->in = NULL;
au->list_idx++;
au->line_number = 0;
if (au->source_list[au->list_idx]) {
au->in = fopen(
au->source_list[au->list_idx],
"rm");
if (au->in == NULL)
return -1;
__fsetlocking(au->in,
FSETLOCKING_BYCALLER);
}
} else {
if (rc > 0)
au->line_number++;
return rc;
}
}
return -2; // return EOF
case AUSOURCE_BUFFER:
case AUSOURCE_BUFFER_ARRAY:
rc = readline_buf(au);
if (rc > 0)
au->line_number++;
return rc;
case AUSOURCE_FEED:
rc = readline_buf(au);
// No such thing as EOF for feed, translate EOF
// to data not available
if (rc == -2)
return 0;
else
if (rc > 0)
au->line_number++;
return rc;
default:
return -1;
}
return -1; /* should never reach here */
}
/*******
* Functions that traverse events.
********/
static int ausearch_reposition_cursors(auparse_state_t *au)
{
int rc = 0;
switch (au->search_where)
{
case AUSEARCH_STOP_EVENT:
aup_list_first(au->le);
aup_list_first_field(au->le);
break;
case AUSEARCH_STOP_RECORD:
aup_list_first_field(au->le);
break;
case AUSEARCH_STOP_FIELD:
// do nothing - this is the normal stopping point
break;
default:
rc = -1;
break;
}
return rc;
}
/* This is called during search once per each record. It walks the list
* of nvpairs and decides if a field matches. */
static int ausearch_compare(auparse_state_t *au)
{
rnode *r;
if (au->le == NULL)
return 0;
r = aup_list_get_cur(au->le);
if (r) {
int res = expr_eval(au, r, au->expr);
return res;
}
return 0;
}
// Returns < 0 on error, 0 no data, > 0 success
int ausearch_next_event(auparse_state_t *au)
{
int rc;
if (au->expr == NULL) {
errno = EINVAL;
return -1;
}
if (au->expr->started == 0) {
if ((rc = auparse_first_record(au)) <= 0)
return rc;
au->expr->started = 1;
} else {
if ((rc = auparse_next_event(au)) <= 0)
return rc;
}
do {
do {
if ((rc = ausearch_compare(au)) > 0) {
ausearch_reposition_cursors(au);
return 1;
} else if (rc < 0)
return rc;
} while ((rc = auparse_next_record(au)) > 0);
if (rc < 0)
return rc;
} while ((rc = auparse_next_event(au)) > 0);
if (rc < 0)
return rc;
return 0;
}
/*
* au_auparse_next_event - Get the next complete event
* Args:
* au - the parser state machine
* Rtns:
* < 0 - error
* == 0 - no data
* > 0 - we have an event and it's set to the 'current event' au->le
*/
static int au_auparse_next_event(auparse_state_t *au)
{
int rc, i, built;
event_list_t *l;
au_event_t e;
/*
* Deal with Python memory management issues where it issues a
* auparse_destroy() call after an auparse_init() call but then wants
* to still work with auparse data. Basically, we assume if the user
* wants to parse for events (calling auparse_next_event()) we accept
* that they expect the memory structures to exist. This is a bit
* 'disconcerting' but the au_lol capability is a patch trying to
* redress a singleton approach to event processing.
*/
if (au->au_lo->array == NULL && au->au_lo->maxi == -1) {
#ifdef LOL_EVENTS_DEBUG01
if (debug) printf("Creating lol array\n");
#endif /* LOL_EVENTS_DEBUG01 */
au_lol_create(au->au_lo);
}
/*
* First see if we have any empty events but with an allocated event
* list. These would have just been processed, so we can free them
*/
for (i = 0; i <= au->au_lo->maxi; i++) {
au_lolnode *cur = &au->au_lo->array[i];
if (cur->status == EBS_EMPTY && cur->l) {
#ifdef LOL_EVENTS_DEBUG01
if (debug) {printf("Freeing at start "); print_list_t(cur->l);}
#endif /* LOL_EVENTS_DEBUG01 */
aup_list_clear(cur->l);
free(cur->l);
au->le = NULL; // this should crash any usage
// of au->le until reset
cur->l = NULL;
}
}
/*
* Now see if we have completed events queued, and if so grab the
* first one and set it to be the 'current' event of interest
*/
if ((l = au_get_ready_event(au, 0)) != NULL) {
rnode *r;
aup_list_first(l);
r = aup_list_get_cur(l);
free_interpretation_list();
load_interpretation_list(r->interp);
aup_list_first_field(l);
au->le = l;
#ifdef LOL_EVENTS_DEBUG01
if (debug) print_lol("upfront", au->au_lo);
#endif /* LOL_EVENTS_DEBUG01 */
return 1;
}
/*
* If no complete events are available, lets ingest
*/
while (1) {
for (i = 0; i <= au->au_lo->maxi; i++) {
au_lolnode *cur = &au->au_lo->array[i];
if (cur->status == EBS_EMPTY && cur->l) {
#ifdef LOL_EVENTS_DEBUG01
if (debug) {printf("Freeing at loop"); print_list_t(cur->l);}
#endif /* LOL_EVENTS_DEBUG01 */
aup_list_clear(cur->l);
free(cur->l);
au->le = NULL; /* this should crash any usage of au->le until reset */
cur->l = NULL;
}
}
rc = retrieve_next_line(au);
#ifdef LOL_EVENTS_DEBUG01
if (debug) printf("next_line(%d) '%s'\n", rc, au->cur_buf);
#endif /* LOL_EVENTS_DEBUG01 */
if (rc == 0) {
#ifdef LOL_EVENTS_DEBUG01
if (debug) printf("Empty line\n");
#endif /* LOL_EVENTS_DEBUG01 */
return 0; /* NO data now */
}
if (rc == -2) {
/*
* We are at EOF, so see if we have any accumulated
* events.
*/
#ifdef LOL_EVENTS_DEBUG01
if (debug) printf("EOF\n");
#endif /* LOL_EVENTS_DEBUG01 */
au_terminate_all_events(au);
if ((l = au_get_ready_event(au, 0)) != NULL) {
rnode *r;
aup_list_first(l);
r = aup_list_get_cur(l);
free_interpretation_list();
load_interpretation_list(r->interp);
aup_list_first_field(l);
au->le = l;
#ifdef LOL_EVENTS_DEBUG01
if (debug) print_lol("eof termination",au->au_lo);
#endif /* LOL_EVENTS_DEBUG01 */
return 1;
}
return 0;
} else if (rc < 0) {
#ifdef LOL_EVENTS_DEBUG01
/* Straight error */
if (debug) printf("Error %d\n", rc);
#endif /* LOL_EVENTS_DEBUG01 */
return -1;
}
/* So we got a successful read ie rc > 0 */
if (extract_timestamp(au->cur_buf, &e)) {
#ifdef LOL_EVENTS_DEBUG01
if (debug) printf("Malformed line:%s\n", au->cur_buf);
#endif /* LOL_EVENTS_DEBUG01 */
continue;
}
/*
* Is this an event we have already been building?
*/
built = 0;
for (i = 0; i <= au->au_lo->maxi; i++) {
au_lolnode *cur = &au->au_lo->array[i];
if (cur->status == EBS_BUILDING) {
if (events_are_equal(&cur->l->e, &e)) {
#ifdef LOL_EVENTS_DEBUG01
if (debug) printf("Adding event to building event\n");
#endif /* LOL_EVENTS_DEBUG01 */
aup_list_append(cur->l, au->cur_buf,
au->list_idx, au->line_number);
au->cur_buf = NULL;
free((char *)e.host);
au_check_events(au, e.sec);
#ifdef LOL_EVENTS_DEBUG01
if (debug) print_lol("building",au->au_lo);
#endif /* LOL_EVENTS_DEBUG01 */
/* we built something, so break out */
built++;
break;
}
}
}
if (built)
continue;
/* So create one */
#ifdef LOL_EVENTS_DEBUG01
if (debug) printf("First record in new event, initialize event\n");
#endif /* LOL_EVENTS_DEBUG01 */
if ((l=(event_list_t *)malloc(sizeof(event_list_t))) == NULL) {
free((char *)e.host);
return -1;
}
aup_list_create(l);
aup_list_set_event(l, &e);
aup_list_append(l, au->cur_buf, au->list_idx, au->line_number);
// Eat standalone EOE - main event was already marked complete
if (l->head->type == AUDIT_EOE) {
au->cur_buf = NULL;
aup_list_clear(l);
free(l);
continue;
}
if (au_lol_append(au->au_lo, l) == NULL) {
free((char *)e.host);
#ifdef LOL_EVENTS_DEBUG01
if (debug) printf("error appending to lol\n");
#endif /* LOL_EVENTS_DEBUG01 */
return -1;
}
au->cur_buf = NULL;
free((char *)e.host);
au_check_events(au, e.sec);
if ((l = au_get_ready_event(au, 0)) != NULL) {
rnode *r;
aup_list_first(l);
r = aup_list_get_cur(l);
free_interpretation_list();
load_interpretation_list(r->interp);
aup_list_first_field(l);
au->le = l;
#ifdef LOL_EVENTS_DEBUG01
if (debug) print_lol("basic", au->au_lo);
#endif /* LOL_EVENTS_DEBUG01 */
return 1;
}
}
}
// Brute force go to next event. Returns < 0 on error, 0 no data, > 0 success
int auparse_next_event(auparse_state_t *au)
{
clear_normalizer(&au->norm_data);
return au_auparse_next_event(au);
}
/* Accessors to event data */
const au_event_t *auparse_get_timestamp(auparse_state_t *au)
{
if (au && au->le && au->le->e.sec != 0)
return &au->le->e;
else
return NULL;
}
time_t auparse_get_time(auparse_state_t *au)
{
if (au && au->le)
return au->le->e.sec;
else
return 0;
}
unsigned int auparse_get_milli(auparse_state_t *au)
{
if (au && au->le)
return au->le->e.milli;
else
return 0;
}
unsigned long auparse_get_serial(auparse_state_t *au)
{
if (au && au->le)
return au->le->e.serial;
else
return 0;
}
// Gets the machine node name
const char *auparse_get_node(auparse_state_t *au)
{
if (au && au->le && au->le->e.host != NULL)
return strdup(au->le->e.host);
else
return NULL;
}
int auparse_node_compare(au_event_t *e1, au_event_t *e2)
{
// If both have a host, only a string compare can tell if they
// are the same. Otherwise, if only one of them have a host, they
// are definitely not the same. Its a boundary on daemon config.
if (e1->host && e2->host)
return strcmp(e1->host, e2->host);
else if (e1->host)
return 1;
else if (e2->host)
return -1;
return 0;
}
int auparse_timestamp_compare(au_event_t *e1, au_event_t *e2)
{
if (e1->sec > e2->sec)
return 1;
if (e1->sec < e2->sec)
return -1;
if (e1->milli > e2->milli)
return 1;
if (e1->milli < e2->milli)
return -1;
if (e1->serial > e2->serial)
return 1;
if (e1->serial < e2->serial)
return -1;
return 0;
}
unsigned int auparse_get_num_records(auparse_state_t *au)
{
// Its OK if au->le == NULL because get_cnt handles it
return aup_list_get_cnt(au->le);
}
unsigned int auparse_get_record_num(auparse_state_t *au)
{
if (au->le == NULL)
return 0;
rnode *r = aup_list_get_cur(au->le);
if (r)
return r->item;
return 0;
}
/* Functions that traverse records in the same event */
int auparse_first_record(auparse_state_t *au)
{
int rc;
rnode *r;
// Its OK if au->le == NULL because get_cnt handles it
if (aup_list_get_cnt(au->le) == 0) {
// This function loads interpretations
rc = auparse_next_event(au);
if (rc <= 0)
return rc;
}
aup_list_first(au->le);
r = aup_list_get_cur(au->le);
free_interpretation_list();
load_interpretation_list(r->interp);
aup_list_first_field(au->le);
return 1;
}
/*
* Returns: -1 if an error occurs,
* 0 if no more records in current event,
* 1 for success.
*/
int auparse_next_record(auparse_state_t *au)
{
rnode *r;
free_interpretation_list();
// Its OK if au->le == NULL because get_cnt handles it
if (aup_list_get_cnt(au->le) == 0) {
int rc = auparse_first_record(au);
if (rc <= 0)
return rc;
}
r = aup_list_next(au->le);
if (r) {
load_interpretation_list(r->interp);
return 1;
} else
return 0;
}
int auparse_goto_record_num(auparse_state_t *au, unsigned int num)
{
rnode *r;
/* Check if a request is out of range */
free_interpretation_list();
// Its OK if au->le == NULL because get_cnt handles it
if (num >= aup_list_get_cnt(au->le))
return 0;
r = aup_list_goto_rec(au->le, num);
if (r != NULL) {
load_interpretation_list(r->interp);
aup_list_first_field(au->le);
return 1;
} else
return 0;
}
/* Accessors to record data */
int auparse_get_type(auparse_state_t *au)
{
if (au->le == NULL)
return 0;
rnode *r = aup_list_get_cur(au->le);
if (r)
return r->type;
else
return 0;
}
const char *auparse_get_type_name(auparse_state_t *au)
{
if (au->le == NULL)
return NULL;
rnode *r = aup_list_get_cur(au->le);
if (r)
return audit_msg_type_to_name(r->type);
else
return NULL;
}
unsigned int auparse_get_line_number(auparse_state_t *au)
{
if (au->le == NULL)
return 0;
rnode *r = aup_list_get_cur(au->le);
if (r)
return r->line_number;
else
return 0;
}
const char *auparse_get_filename(auparse_state_t *au)
{
switch (au->source)
{
case AUSOURCE_FILE:
case AUSOURCE_FILE_ARRAY:
break;
default:
return NULL;
}
if (au->le == NULL)
return NULL;
rnode *r = aup_list_get_cur(au->le);
if (r) {
if (r->list_idx < 0) return NULL;
return au->source_list[r->list_idx];
} else {
return NULL;
}
}
int auparse_first_field(auparse_state_t *au)
{
if (au->le == NULL)
return 0;
return aup_list_first_field(au->le);
}
int auparse_next_field(auparse_state_t *au)
{
if (au->le == NULL)
return 0;
rnode *r = aup_list_get_cur(au->le);
if (r) {
if (nvlist_next(&r->nv))
return 1;
else
return 0;
}
return 0;
}
unsigned int auparse_get_num_fields(auparse_state_t *au)
{
if (au->le == NULL)
return 0;
rnode *r = aup_list_get_cur(au->le);
if (r)
return nvlist_get_cnt(&r->nv);
else
return 0;
}
const char *auparse_get_record_text(auparse_state_t *au)
{
if (au->le == NULL)
return NULL;
rnode *r = aup_list_get_cur(au->le);
if (r)
return r->record;
else
return NULL;
}
const char *auparse_get_record_interpretations(auparse_state_t *au)
{
if (au->le == NULL)
return NULL;
rnode *r = aup_list_get_cur(au->le);
if (r)
return r->interp;
else
return NULL;
}
/* scan from current location to end of event */
const char *auparse_find_field(auparse_state_t *au, const char *name)
{
if (au->le == NULL)
return NULL;
free(au->find_field);
au->find_field = strdup(name);
if (au->le->e.sec) {
const char *cur_name;
rnode *r;
// look at current record before moving
r = aup_list_get_cur(au->le);
if (r == NULL)
return NULL;
cur_name = nvlist_get_cur_name(&r->nv);
if (cur_name && strcmp(cur_name, name) == 0)
return nvlist_get_cur_val(&r->nv);
return auparse_find_field_next(au);
}
return NULL;
}
/* Increment 1 location and then scan for next field */
const char *auparse_find_field_next(auparse_state_t *au)
{
if (au->le == NULL)
return NULL;
if (au->find_field == NULL) {
errno = EINVAL;
return NULL;
}
if (au->le->e.sec) {
int moved = 0;
rnode *r = aup_list_get_cur(au->le);
while (r) { // For each record in the event...
if (!moved) {
nvlist_next(&r->nv);
moved=1;
}
if (nvlist_find_name(&r->nv, au->find_field))
return nvlist_get_cur_val(&r->nv);
r = aup_list_next(au->le);
if (r) {
aup_list_first_field(au->le);
load_interpretation_list(r->interp);
}
}
}
return NULL;
}
/* Accessors to field data */
unsigned int auparse_get_field_num(auparse_state_t *au)
{
if (au->le == NULL)
return 0;
rnode *r = aup_list_get_cur(au->le);
if (r) {
nvnode *n = nvlist_get_cur(&r->nv);
if (n)
return n->item;
}
return 0;
}
int auparse_goto_field_num(auparse_state_t *au, unsigned int num)
{
if (au->le == NULL)
return 0;
rnode *r = aup_list_get_cur(au->le);
if (r) {
if (num >= r->nv.cnt)
return 0;
if ((nvlist_goto_rec(&r->nv, num)))
return 1;
}
return 0;
}
const char *auparse_get_field_name(auparse_state_t *au)
{
if (au->le == NULL)
return NULL;
if (au->le->e.sec) {
rnode *r = aup_list_get_cur(au->le);
if (r)
return nvlist_get_cur_name(&r->nv);
}
return NULL;
}
const char *auparse_get_field_str(auparse_state_t *au)
{
if (au->le == NULL)
return NULL;
if (au->le->e.sec) {
rnode *r = aup_list_get_cur(au->le);
if (r)
return nvlist_get_cur_val(&r->nv);
}
return NULL;
}
int auparse_get_field_type(auparse_state_t *au)
{
if (au->le == NULL)
return AUPARSE_TYPE_UNCLASSIFIED;
if (au->le->e.sec) {
rnode *r = aup_list_get_cur(au->le);
if (r)
return nvlist_get_cur_type(r);
}
return AUPARSE_TYPE_UNCLASSIFIED;
}
int auparse_get_field_int(auparse_state_t *au)
{
const char *v = auparse_get_field_str(au);
if (v) {
int val;
errno = 0;
val = strtol(v, NULL, 10);
if (errno == 0)
return val;
} else
errno = ENODATA;
return -1;
}
const char *auparse_interpret_field(auparse_state_t *au)
{
if (au->le == NULL)
return NULL;
if (au->le->e.sec) {
rnode *r = aup_list_get_cur(au->le);
if (r) {
r->cwd = NULL;
return nvlist_interp_cur_val(r, au->escape_mode);
}
}
return NULL;
}
const char *auparse_interpret_realpath(auparse_state_t *au)
{
if (au->le == NULL)
return NULL;
if (au->le->e.sec) {
rnode *r = aup_list_get_cur(au->le);
if (r) {
if (nvlist_get_cur_type(r) != AUPARSE_TYPE_ESCAPED_FILE)
return NULL;
// Tell it to make a realpath
r->cwd = au->le->cwd;
return nvlist_interp_cur_val(r, au->escape_mode);
}
}
return NULL;
}
static const char *auparse_interpret_sock_parts(auparse_state_t *au,
const char *field)
{
if (au->le == NULL)
return NULL;
if (au->le->e.sec) {
rnode *r = aup_list_get_cur(au->le);
if (r == NULL)
return NULL;
// This is limited to socket address fields
if (nvlist_get_cur_type(r) != AUPARSE_TYPE_SOCKADDR)
return NULL;
// Get interpretation
const char *val = nvlist_interp_cur_val(r, au->escape_mode);
if (val == NULL)
return NULL;
// make a copy since we modify it
char *tmp = strdup(val);
if (tmp == NULL)
return NULL;
// Locate the address part
val = strstr(tmp, field);
if (val) {
// Get past the =
val += strlen(field);
// find other side
char *ptr = strchr(val, ' ');
if (ptr) {
// terminate, copy, and return it
*ptr = 0;
const char *final = strdup(val);
free(tmp);
free((void *)au->tmp_translation);
au->tmp_translation = final;
return final;
}
}
free(tmp);
}
return NULL;
}
const char *auparse_interpret_sock_family(auparse_state_t *au)
{
return auparse_interpret_sock_parts(au, "fam=");
}
const char *auparse_interpret_sock_port(auparse_state_t *au)
{
return auparse_interpret_sock_parts(au, "lport=");
}
const char *auparse_interpret_sock_address(auparse_state_t *au)
{
return auparse_interpret_sock_parts(au, "laddr=");
}