* Copyright 2006-08,2012-19,21 Red Hat Inc.
* 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 <fcntl.h>
#include <stdio_ext.h>
#include <limits.h>
#include "common.h"
#ifdef LOL_EVENTS_DEBUG01
static int debug = 0;
#endif
static time_t eoe_timeout = EOE_TIMEOUT;
static void init_lib(void) __attribute__ ((constructor));
static void init_lib(void)
{
init_interpretation_list();
}
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 access_ok(const char *filename)
{
int rc = access(filename, R_OK);
if (rc == 0)
return rc;
#ifdef HAVE_FACCESSAT
return faccessat(AT_FDCWD, filename, R_OK, AT_EACCESS);
#else
return rc;
#endif
}
static int setup_log_file_array(auparse_state_t *au)
{
struct daemon_conf config;
char *filename, **tmp;
int len, num = 0, i = 0;
if (secure_getenv("AUPARSE_DEBUG"))
set_aumessage_mode(au, MSG_STDERR, DBG_NO);
aup_load_config(au, &config, TEST_SEARCH);
len = strlen(config.log_file) + 16;
filename = malloc(len);
if (!filename) {
fprintf(stderr, "No memory\n");
aup_free_config(&config);
return 1;
}
snprintf(filename, len, "%s", config.log_file);
do {
if (access_ok(filename) != 0)
break;
num++;
snprintf(filename, len, "%s.%d", config.log_file, num);
} while (1);
if (num == 0) {
fprintf(stderr, "No log file\n");
aup_free_config(&config);
free(filename);
return 1;
}
num--;
tmp = malloc((num+2)*sizeof(char *));
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);
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);
aup_free_config(&config);
free(filename);
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;
if ((lol->array = (au_lolnode *)malloc(sz)) == NULL)
return NULL;
lol->limit = ARRAY_LIMIT;
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 (lol->array)
memset(lol->array, 0x00,
lol->limit * sizeof(au_lolnode));
lol->maxi = -1;
} else {
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;
}
}
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) {
return NULL;
}
for (i=0; i<=lol->maxi; i++) {
au_lolnode *cur = &(lol->array[i]);
if (cur->status == EBS_EMPTY)
continue;
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 (cur->l->e.sec + eoe_timeout <= sec) {
cur->status = EBS_COMPLETE;
au->au_ready++;
} else if (
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)) {
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++;
}
}
}
#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%p: %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%p: a: 0x%p, %d, %d\n", label, lol, lol->array,
lol->maxi, lol->limit);
if (debug > 1) for (i = 0; i <= lol->maxi; i++) {
printf("{%2d 0x%p %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
* au_setup_userspace_configitems - load userspace configuration items from auditd.conf
*
* Args:
* au - pointer to auparseing state structure
*
* Rtns:
* void
*/
static void au_setup_userspace_configitems(auparse_state_t *au)
{
struct daemon_conf config;
if (secure_getenv("AUPARSE_DEBUG"))
set_aumessage_mode(au, MSG_STDERR, DBG_NO);
aup_load_config(au, &config, TEST_SEARCH);
eoe_timeout = (time_t)config.end_of_event_timeout;
aup_free_config(&config);
}
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);
if (au_lol_create(au->au_lo) == NULL) {
free(au->au_lo);
free(au);
errno = ENOMEM;
return NULL;
}
au->au_ready = 0;
au->escape_mode = AUPARSE_ESC_TTY;
au->message_mode = MSG_QUIET;
au->debug_message = DBG_NO;
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;
au_setup_userspace_configitems(au);
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_ok(b))
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_ok(bb[n]))
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->tmp_translation = NULL;
init_normalizer(&au->norm_data);
return au;
bad_exit:
databuf_free(&au->databuf);
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)
{
while (auparse_next_event(au) > 0) {
if (au->callback) {
(*au->callback)(au, AUPARSE_CB_EVENT_READY,
au->callback_user_data);
}
}
if (flush) {
* (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;
au_terminate_all_events(au);
while ((l = au_get_ready_event(au, 0)) != NULL) {
rnode *r;
au->le = l;
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_new_buffer(auparse_state_t *au, const char *data, size_t data_len)
{
if (au->source != AUSOURCE_BUFFER)
return 1;
auparse_reset(au);
if (databuf_replace(&au->databuf, data, data_len) < 0)
return 1;
return 0;
}
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;
}
int auparse_feed_has_data(auparse_state_t *au)
{
if (!au)
return 0;
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_EMPTY)
return 1;
}
return 0;
}
int auparse_feed_has_ready_event(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;
}
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;
}
case AUSOURCE_DESCRIPTOR:
case AUSOURCE_FILE_POINTER:
if (au->in)
rewind(au->in);
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;
}
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;
if (field == NULL)
goto err_out;
if (how < AUSEARCH_RULE_CLEAR || how > AUSEARCH_RULE_AND)
goto err_out;
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;
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;
if (how < AUSEARCH_RULE_CLEAR || how > AUSEARCH_RULE_AND)
goto err_out;
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;
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;
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;
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;
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)
{
lookup_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;
}
* 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) {
free(au->cur_buf);
au->cur_buf = NULL;
if (feof(au->in)) {
errno = 0;
return -2;
}
return -1;
}
p_last_char = au->cur_buf + (rc-1);
if (*p_last_char == '\n') {
*p_last_char = 0;
}
errno = 0;
return 1;
}
* 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 (au->databuf.len == 0) {
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);
au->cur_buf = malloc(line_len+1);
if (au->cur_buf == NULL)
return -1;
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;
errno = 0;
return 1;
} else {
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 || e->sec > (LONG_MAX - eoe_timeout -1))
return -1;
ptr = strchr(s, '.');
if (ptr) {
ptr++;
e->milli = strtoul(ptr, NULL, 10);
if (errno || e->milli > 999)
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
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) {
if (*ptr == 'n' && strnlen(ptr, 8) > 5) {
e->host = strdup(ptr+5);
(void)audit_strsplit(NULL);
}
ptr = audit_strsplit(NULL);
if (ptr && strnlen(ptr, 20) > 18) {
if (*(ptr+9) == '(')
ptr+=9;
else
ptr = strchr(ptr, '(');
if (ptr) {
char *eptr;
ptr++;
eptr = strchr(ptr, ')');
if (eptr)
*eptr = 0;
if (str2event(ptr, e) == 0)
rc = 0;
}
}
}
if (rc)
free((void *)e->host);
return rc;
}
static int events_are_equal(au_event_t *e1, au_event_t *e2)
{
if (!(e1->serial == e2->serial && e1->milli == e2->milli &&
e1->sec == e2->sec))
return 0;
if (e1->host && e2->host) {
if (strcmp(e1->host, e2->host))
return 0;
} else if (e1->host || e2->host)
return 0;
return 1;
}
* 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 (au->line_pushed) {
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 (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);
}
while (au->in) {
if ((rc = readline_file(au)) == -2) {
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;
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);
if (rc == -2)
return 0;
else
if (rc > 0)
au->line_number++;
return rc;
default:
return -1;
}
return -1;
}
* 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:
break;
default:
rc = -1;
break;
}
return rc;
}
* 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;
}
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
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
aup_list_clear(cur->l);
free(cur->l);
au->le = NULL;
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
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
aup_list_clear(cur->l);
free(cur->l);
au->le = NULL;
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
if (rc == 0) {
#ifdef LOL_EVENTS_DEBUG01
if (debug) printf("Empty line\n");
#endif
return 0;
}
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
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
return 1;
}
return 0;
} else if (rc < 0) {
#ifdef LOL_EVENTS_DEBUG01
if (debug)
printf("Error %d\n", rc);
#endif
return -1;
}
if (extract_timestamp(au->cur_buf, &e)) {
#ifdef LOL_EVENTS_DEBUG01
if (debug)
printf("Malformed line:%s\n", au->cur_buf);
#endif
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
if (aup_list_append(cur->l, au->cur_buf,
au->list_idx, au->line_number) < 0) {
au->cur_buf = NULL;
continue;
}
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
built++;
break;
}
}
}
if (built)
continue;
#ifdef LOL_EVENTS_DEBUG01
if (debug)
printf("First record in new event, initialize event\n");
#endif
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);
if (aup_list_append(l, au->cur_buf, au->list_idx,
au->line_number) < 0) {
au->cur_buf = NULL;
aup_list_clear(l);
free(l);
continue;
}
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
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
return 1;
}
}
}
int auparse_next_event(auparse_state_t *au)
{
clear_normalizer(&au->norm_data);
return au_auparse_next_event(au);
}
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;
}
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 (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)
{
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;
}
int auparse_first_record(auparse_state_t *au)
{
int rc;
rnode *r;
if (aup_list_get_cnt(au->le) == 0) {
rc = auparse_next_event(au);
if (rc <= 0)
return rc;
}
r = aup_list_get_cur(au->le);
if (r && r->item == 0 && interpretation_list_cnt()) {
aup_list_first_field(au->le);
return 1;
}
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();
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;
r = aup_list_get_cur(au->le);
if (r && r->item == num && interpretation_list_cnt()) {
aup_list_first_field(au->le);
return 1;
}
free_interpretation_list();
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;
}
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;
}
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;
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;
}
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) {
if (!moved) {
if (nvlist_next(&r->nv) == NULL)
return NULL;
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);
free_interpretation_list();
load_interpretation_list(r->interp);
}
}
}
return NULL;
}
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;
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;
if (nvlist_get_cur_type(r) != AUPARSE_TYPE_SOCKADDR)
return NULL;
const char *val = nvlist_interp_cur_val(r, au->escape_mode);
if (val == NULL)
return NULL;
char *tmp = strdup(val);
if (tmp == NULL)
return NULL;
val = strstr(tmp, field);
if (val) {
val += strlen(field);
char *ptr = strchr(val, ' ');
if (ptr) {
*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=");
}
* auparse_set_eoe_timeout - set the end of event timeout value
*
* Args
* new_tmo - new timeout value
* Rtns
* 0 - correctly set
* 1 - failed to set
*/
int auparse_set_eoe_timeout (time_t new_tmo) {
if (new_tmo == 0)
return 1;
eoe_timeout = new_tmo;
return 0;
}