aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/audit/auparse/expression.c
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/audit/auparse/expression.c')
-rw-r--r--framework/src/audit/auparse/expression.c1111
1 files changed, 1111 insertions, 0 deletions
diff --git a/framework/src/audit/auparse/expression.c b/framework/src/audit/auparse/expression.c
new file mode 100644
index 00000000..6bed45ba
--- /dev/null
+++ b/framework/src/audit/auparse/expression.c
@@ -0,0 +1,1111 @@
+/*
+* expression.c - Expression parsing and handling
+* Copyright (C) 2008,2014 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:
+* Miloslav Trmač <mitr@redhat.com>
+* Steve Grubb <sgrubb@redhat.com> extended timestamp
+*/
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "expression.h"
+
+ /* Utilities */
+
+/* Free EXPR and all its subexpressions. */
+void
+expr_free(struct expr *expr)
+{
+ switch (expr->op) {
+ case EO_NOT:
+ expr_free(expr->v.sub[0]);
+ break;
+
+ case EO_AND: case EO_OR:
+ expr_free(expr->v.sub[0]);
+ expr_free(expr->v.sub[1]);
+ break;
+
+ case EO_RAW_EQ: case EO_RAW_NE: case EO_INTERPRETED_EQ:
+ case EO_INTERPRETED_NE: case EO_VALUE_EQ: case EO_VALUE_NE:
+ case EO_VALUE_LT: case EO_VALUE_LE: case EO_VALUE_GT: case EO_VALUE_GE:
+ if (expr->virtual_field == 0)
+ free(expr->v.p.field.name);
+ if (expr->precomputed_value == 0)
+ free(expr->v.p.value.string);
+ break;
+
+ case EO_FIELD_EXISTS:
+ assert(expr->virtual_field == 0);
+ free(expr->v.p.field.name);
+ break;
+
+ case EO_REGEXP_MATCHES:
+ regfree(expr->v.regexp);
+ free(expr->v.regexp);
+ break;
+
+ default:
+ abort();
+ }
+ free(expr);
+}
+
+ /* Expression parsing. */
+
+/* The formal grammar:
+
+ start: or-expression
+
+ or-expression: and-expression
+ or-expression: or-expression || and-expression
+
+ and-expression: primary-expression
+ and-expression: and-expression && primary-expression
+
+ primary-expression: ! primary-expression
+ primary-expression: ( or-expression )
+ primary-expression: comparison-expression
+
+ comparison-expression: field op value
+ comparison-expression: field-escape "regexp" regexp-value
+ field: string
+ field: field-escape string
+ value: string
+ regexp-value: string
+ regexp-value: regexp */
+
+/* Token types */
+enum token_type {
+ /* EO_* */
+ T_LEFT_PAREN = NUM_EO_VALUES, T_RIGHT_PAREN, T_STRING, T_REGEXP,
+ T_FIELD_ESCAPE, T_UNKNOWN, T_EOF
+};
+
+/* Expression parsing status */
+struct parsing {
+ char **error; /* Error message destination. */
+ enum token_type token;
+ const char *token_start; /* Original "src" value */
+ int token_len; /* int because it must be usable in %.*s */
+ char *token_value; /* Non-NULL only for T_STRING, until used */
+ const char *src; /* Expression source, after the current token */
+};
+
+static struct expr *parse_or(struct parsing *p);
+
+/* Allocate SIZE bytes.
+ On error, return NULL and try to set *P->ERROR. */
+static void *
+parser_malloc(struct parsing *p, size_t size)
+{
+ void *res;
+
+ res = malloc(size);
+ if (res != NULL || size == 0)
+ return res;
+ *p->error = strdup("Out of memory");
+ return NULL;
+}
+
+/* Reallocate PTR to SIZE bytes.
+ On error, free(PTR), return NULL and try to set *P->ERROR.
+ NOTE: realloc() does not free(PTR), this function does. */
+static void *
+parser_realloc(struct parsing *p, void *ptr, size_t size)
+{
+ void *res;
+
+ res = realloc(ptr, size);
+ if (res != NULL || size == 0)
+ return res;
+ free(ptr);
+ *p->error = strdup("Out of memory");
+ return NULL;
+}
+
+/* Discard P->token_value, if any, and parse the next token in P->src.
+ On success, return 0.
+ On error, set *P->ERROR to an error string (for free()) or NULL, and return
+ -1. */
+static int
+lex(struct parsing *p)
+{
+ free(p->token_value);
+ p->token_value = NULL;
+ while (*p->src == ' ' || *p->src == '\t' || *p->src == '\n')
+ p->src++;
+ p->token_start = p->src;
+ switch (*p->src) {
+ case '\0':
+ p->token = T_EOF;
+ break;
+
+ case '!':
+ p->src++;
+ if (*p->src == '=' && p->src[1] == '=') {
+ p->src += 2;
+ p->token = EO_VALUE_NE;
+ break;
+ }
+ p->token = EO_NOT;
+ break;
+
+ case '"': case '/': {
+ char *buf, delimiter;
+ size_t dest, buf_size;
+
+ delimiter = *p->src;
+ buf_size = 8;
+ buf = parser_malloc(p, buf_size);
+ if (buf == NULL)
+ return -1;
+ p->src++;
+ dest = 0;
+ while (*p->src != delimiter) {
+ if (*p->src == '\0') {
+ *p->error = strdup("Terminating delimiter "
+ "missing");
+ free(buf);
+ return -1;
+ }
+ if (*p->src == '\\') {
+ p->src++;
+ if (*p->src != '\\' && *p->src != delimiter) {
+ if (asprintf(p->error, "Unknown escape "
+ "sequence ``\\%c''",
+ *p->src) < 0)
+ *p->error = NULL;
+ free(buf);
+ return -1;
+ }
+ }
+ /* +1: make sure there is space for the terminating
+ NUL. */
+ if (dest + 1 >= buf_size) {
+ if (buf_size > SIZE_MAX / 2) {
+ *p->error = strdup("Delimited string "
+ "too long");
+ free(buf);
+ return -1;
+ }
+ buf_size *= 2;
+ buf = parser_realloc(p, buf, buf_size);
+ if (buf == NULL) {
+ *p->error = strdup("Out of memory");
+ return -1;
+ }
+ }
+ buf[dest] = *p->src;
+ dest++;
+ p->src++;
+ }
+ p->src++;
+ buf[dest] = '\0';
+ p->token_value = parser_realloc(p, buf, dest + 1);
+ if (p->token_value == NULL)
+ return -1;
+ p->token = delimiter == '/' ? T_REGEXP : T_STRING;
+ break;
+ }
+
+ case '&':
+ p->src++;
+ if (*p->src == '&') {
+ p->src++;
+ p->token = EO_AND;
+ break;
+ }
+ p->token = T_UNKNOWN;
+ break;
+
+ case '(':
+ p->src++;
+ p->token = T_LEFT_PAREN;
+ break;
+
+ case ')':
+ p->src++;
+ p->token = T_RIGHT_PAREN;
+ break;
+
+ case '<':
+ p->src++;
+ if (*p->src == '=') {
+ p->src++;
+ p->token = EO_VALUE_LE;
+ break;
+ }
+ p->token = EO_VALUE_LT;
+ break;
+
+ case '=':
+ p->src++;
+ if (*p->src == '=') {
+ p->src++;
+ p->token = EO_VALUE_EQ;
+ break;
+ }
+ p->token = T_UNKNOWN;
+ break;
+
+ case '>':
+ p->src++;
+ if (*p->src == '=') {
+ p->src++;
+ p->token = EO_VALUE_GE;
+ break;
+ }
+ p->token = EO_VALUE_GT;
+ break;
+
+ case '\\':
+ p->src++;
+ p->token = T_FIELD_ESCAPE;
+ break;
+
+ case '|':
+ p->src++;
+ if (*p->src == '|') {
+ p->src++;
+ p->token = EO_OR;
+ break;
+ }
+ p->token = T_UNKNOWN;
+ break;
+
+ case 'i':
+ if (p->src[1] == '=') {
+ p->src += 2;
+ p->token = EO_INTERPRETED_EQ;
+ break;
+ } else if (p->src[1] == '!' && p->src[2] == '=') {
+ p->src += 3;
+ p->token = EO_INTERPRETED_NE;
+ break;
+ }
+ goto unquoted_string;
+
+ case 'r':
+ if (p->src[1] == '=') {
+ p->src += 2;
+ p->token = EO_RAW_EQ;
+ break;
+ } else if (p->src[1] == '!' && p->src[2] == '=') {
+ p->src += 3;
+ p->token = EO_RAW_NE;
+ break;
+ }
+ goto unquoted_string;
+
+ default:
+ /* This assumes ASCII */
+ assert ('Z' == 'A' + 25 && 'z' == 'a' + 25);
+#define IS_UNQUOTED_STRING_CHAR(C) \
+ (((C) >= 'a' && (C) <= 'z') \
+ || ((C) >= 'A' && (C) <= 'Z') \
+ || ((C) >= '0' && (C) <= '9') \
+ || (C) == '_')
+ if (IS_UNQUOTED_STRING_CHAR(*p->src)) {
+ size_t len;
+
+ unquoted_string:
+ do
+ p->src++;
+ while (IS_UNQUOTED_STRING_CHAR(*p->src));
+ len = p->src - p->token_start;
+ p->token_value = parser_malloc(p, len + 1);
+ if (p->token_value == NULL)
+ return -1;
+ memcpy(p->token_value, p->token_start, len);
+ p->token_value[len] = '\0';
+ p->token = T_STRING;
+ break;
+ }
+ p->src++;
+ p->token = T_UNKNOWN;
+ break;
+ }
+ if (p->src - p->token_start > INT_MAX) {
+ *p->error = strdup("Token too long");
+ return -1;
+ }
+ p->token_len = p->src - p->token_start;
+ return 0;
+}
+
+/* Parse an escaped field NAME to DEST.
+ Return 0 on success, -1 if NAME is unknown. */
+static int
+parse_escaped_field_name(enum field_id *dest, const char *name)
+{
+ if (strcmp(name, "timestamp") == 0)
+ *dest = EF_TIMESTAMP;
+ else if (strcmp(name, "record_type") == 0)
+ *dest = EF_RECORD_TYPE;
+ else if (strcmp(name, "timestamp_ex") == 0)
+ *dest = EF_TIMESTAMP_EX;
+ else
+ return -1;
+ return 0;
+}
+
+/* Parse a \timestamp field value in P->token_value to DEST.
+ On success, return 0.
+ On error, set *P->ERROR to an error string (for free()) or NULL, and return
+ -1. */
+static int
+parse_timestamp_value(struct expr *dest, struct parsing *p)
+{
+ intmax_t sec;
+
+ assert(p->token == T_STRING);
+ /* FIXME: other formats? */
+ if (sscanf(p->token_value, "ts:%jd.%u:%u", &sec,
+ &dest->v.p.value.timestamp_ex.milli,
+ &dest->v.p.value.timestamp_ex.serial) != 3) {
+ if (sscanf(p->token_value, "ts:%jd.%u", &sec,
+ &dest->v.p.value.timestamp.milli) != 2) {
+ if (asprintf(p->error, "Invalid timestamp value `%.*s'",
+ p->token_len, p->token_start) < 0)
+ *p->error = NULL;
+ return -1;
+ }
+ }
+ /* FIXME: validate milli */
+ dest->v.p.value.timestamp.sec = sec;
+ if (dest->v.p.value.timestamp.sec != sec) {
+ if (asprintf(p->error, "Timestamp overflow in `%.*s'",
+ p->token_len, p->token_start) < 0)
+ *p->error = NULL;
+ return -1;
+ }
+ dest->precomputed_value = 1;
+ return 0;
+}
+
+/* Parse a \record_type field value in P->token_value to DEST.
+ On success, return 0.
+ On error, set *P->ERROR to an error string (for free()) or NULL, and return
+ -1. */
+static int
+parse_record_type_value(struct expr *dest, struct parsing *p)
+{
+ int type;
+
+ assert(p->token == T_STRING);
+ type = audit_name_to_msg_type(p->token_value);
+ if (type < 0) {
+ if (asprintf(p->error, "Invalid record type `%.*s'",
+ p->token_len, p->token_start) < 0)
+ *p->error = NULL;
+ return -1;
+ }
+ dest->v.p.value.int_value = type;
+ dest->precomputed_value = 1;
+ return 0;
+}
+
+/* Parse a virtual field value in P->token_value to DEST.
+ On success, return 0.
+ On error, set *P->ERROR to an error string (for free()) or NULL, and return
+ NULL. */
+static int
+parse_virtual_field_value(struct expr *dest, struct parsing *p)
+{
+ switch (dest->v.p.field.id) {
+ case EF_TIMESTAMP:
+ return parse_timestamp_value(dest, p);
+
+ case EF_RECORD_TYPE:
+ return parse_record_type_value(dest, p);
+
+ case EF_TIMESTAMP_EX:
+ return parse_timestamp_value(dest, p);
+
+ default:
+ abort();
+ }
+}
+
+/* Parse a \regexp comparison-expression string in *P, with \regexp parsed.
+ Use or free EXPR.
+ On success, return the parsed comparison-expression.
+ On error, set *P->ERROR to an error string (for free()) or NULL, and return
+ NULL. */
+static struct expr *
+parse_comparison_regexp(struct parsing *p, struct expr *res)
+{
+ int err;
+
+ if (lex(p) != 0)
+ goto err_res;
+ if (p->token != T_STRING && p->token != T_REGEXP) {
+ if (asprintf(p->error, "Regexp expected, got `%.*s'",
+ p->token_len, p->token_start) < 0)
+ *p->error = NULL;
+ goto err_res;
+ }
+ res->v.regexp = parser_malloc(p, sizeof(*res->v.regexp));
+ if (res->v.regexp == NULL)
+ goto err_res;
+ err = regcomp(res->v.regexp, p->token_value, REG_EXTENDED | REG_NOSUB);
+ if (err != 0) {
+ size_t err_size;
+ char *err_msg;
+
+ err_size = regerror(err, res->v.regexp, NULL, 0);
+ err_msg = parser_malloc(p, err_size);
+ if (err_msg == NULL)
+ goto err_res_regexp;
+ regerror(err, res->v.regexp, err_msg, err_size);
+ if (asprintf(p->error, "Invalid regexp: %s", err_msg) < 0)
+ *p->error = NULL;
+ free(err_msg);
+ goto err_res_regexp;
+ }
+ res->op = EO_REGEXP_MATCHES;
+ if (lex(p) != 0) {
+ expr_free(res);
+ return NULL;
+ }
+ return res;
+
+err_res_regexp:
+ free(res->v.regexp);
+err_res:
+ free(res);
+ return NULL;
+}
+
+/* Parse a comparison-expression string in *P.
+ On success, return the parsed comparison-expression.
+ On error, set *P->ERROR to an error string (for free()) or NULL, and return
+ NULL. */
+static struct expr *
+parse_comparison(struct parsing *p)
+{
+ struct expr *res;
+
+ res = parser_malloc(p, sizeof(*res));
+ if (res == NULL)
+ return NULL;
+ if (p->token == T_FIELD_ESCAPE) {
+ if (lex(p) != 0)
+ goto err_res;
+ if (p->token != T_STRING) {
+ *p->error = strdup("Field name expected after field "
+ "escape");
+ goto err_res;
+ }
+ if (strcmp(p->token_value, "regexp") == 0)
+ return parse_comparison_regexp(p, res);
+ res->virtual_field = 1;
+ if (parse_escaped_field_name(&res->v.p.field.id, p->token_value)
+ != 0) {
+ if (asprintf(p->error,
+ "Unknown escaped field name `%.*s'",
+ p->token_len, p->token_start) < 0)
+ *p->error = NULL;
+ goto err_res;
+ }
+ } else {
+ assert(p->token == T_STRING);
+ res->virtual_field = 0;
+ res->v.p.field.name = p->token_value;
+ p->token_value = NULL;
+ }
+ if (lex(p) != 0)
+ goto err_field;
+ switch (p->token) {
+ case EO_RAW_EQ: case EO_RAW_NE: case EO_INTERPRETED_EQ:
+ case EO_INTERPRETED_NE:
+ res->op = p->token;
+ if (lex(p) != 0)
+ goto err_field;
+ if (p->token != T_STRING) {
+ if (asprintf(p->error, "Value expected, got `%.*s'",
+ p->token_len, p->token_start) < 0)
+ *p->error = NULL;
+ goto err_field;
+ }
+ res->precomputed_value = 0;
+ res->v.p.value.string = p->token_value;
+ p->token_value = NULL;
+ if (lex(p) != 0) {
+ expr_free(res);
+ return NULL;
+ }
+ break;
+
+ case EO_VALUE_EQ: case EO_VALUE_NE: case EO_VALUE_LT: case EO_VALUE_LE:
+ case EO_VALUE_GT: case EO_VALUE_GE:
+ res->op = p->token;
+ if (lex(p) != 0)
+ goto err_field;
+ if (p->token != T_STRING) {
+ if (asprintf(p->error, "Value expected, got `%.*s'",
+ p->token_len, p->token_start) < 0)
+ *p->error = NULL;
+ goto err_field;
+ }
+ if (res->virtual_field == 0) {
+ if (asprintf(p->error, "Field `%s' does not support "
+ "value comparison",
+ res->v.p.field.name) < 0)
+ *p->error = NULL;
+ goto err_field;
+ } else {
+ if (parse_virtual_field_value(res, p) != 0)
+ goto err_field;
+ }
+ if (lex(p) != 0) {
+ expr_free(res);
+ return NULL;
+ }
+ break;
+
+ default:
+ if (asprintf(p->error, "Operator expected, got `%.*s'",
+ p->token_len, p->token_start) < 0)
+ *p->error = NULL;
+ goto err_field;
+ }
+ return res;
+
+err_field:
+ if (res->virtual_field == 0)
+ free(res->v.p.field.name);
+err_res:
+ free(res);
+ return NULL;
+}
+
+/* Parse a primary-expression string in *P.
+ On success, return the parsed primary-expression.
+ On error, set *P->ERROR to an error string (for free()) or NULL, and return
+ NULL. */
+static struct expr *
+parse_primary(struct parsing *p)
+{
+ struct expr *e;
+
+ switch (p->token) {
+ case EO_NOT: {
+ struct expr *res;
+
+ if (lex(p) != 0)
+ return NULL;
+ e = parse_primary(p);
+ if (e == NULL)
+ return NULL;
+ res = parser_malloc(p, sizeof(*res));
+ if (res == NULL)
+ goto err_e;
+ res->op = EO_NOT;
+ res->v.sub[0] = e;
+ return res;
+ }
+
+ case T_LEFT_PAREN: {
+ if (lex(p) != 0)
+ return NULL;
+ e = parse_or(p);
+ if (e == NULL)
+ return NULL;
+ if (p->token != T_RIGHT_PAREN) {
+ if (asprintf(p->error,
+ "Right paren expected, got `%.*s'",
+ p->token_len, p->token_start) < 0)
+ *p->error = NULL;
+ goto err_e;
+ }
+ if (lex(p) != 0)
+ goto err_e;
+ return e;
+ }
+
+ case T_FIELD_ESCAPE: case T_STRING:
+ return parse_comparison(p);
+
+ default:
+ if (asprintf(p->error, "Unexpected token `%.*s'", p->token_len,
+ p->token_start) < 0)
+ *p->error = NULL;
+ return NULL;
+ }
+err_e:
+ expr_free(e);
+ return NULL;
+}
+
+/* Parse an and-expression string in *P.
+ On success, return the parsed and-expression.
+ On error, set *P->ERROR to an error string (for free()) or NULL, and return
+ NULL. */
+static struct expr *
+parse_and(struct parsing *p)
+{
+ struct expr *res;
+
+ res = parse_primary(p);
+ if (res == NULL)
+ return NULL;
+ while (p->token == EO_AND) {
+ struct expr *e2, *e;
+
+ if (lex(p) != 0)
+ goto err_res;
+ e2 = parse_primary(p);
+ if (e2 == NULL)
+ goto err_res;
+ e = parser_malloc(p, sizeof(*e));
+ if (e == NULL) {
+ expr_free(e2);
+ goto err_res;
+ }
+ e->op = EO_AND;
+ e->v.sub[0] = res;
+ e->v.sub[1] = e2;
+ res = e;
+ }
+ return res;
+
+err_res:
+ expr_free(res);
+ return NULL;
+}
+
+/* Parse an or-expression string in *P.
+ On success, return the parsed or-expression.
+ On error, set *P->ERROR to an error string (for free()) or NULL, and return
+ NULL. */
+static struct expr *
+parse_or(struct parsing *p)
+{
+ struct expr *res;
+
+ res = parse_and(p);
+ if (res == NULL)
+ return NULL;
+ while (p->token == EO_OR) {
+ struct expr *e2, *e;
+
+ if (lex(p) != 0)
+ goto err_res;
+ e2 = parse_and(p);
+ if (e2 == NULL)
+ goto err_res;
+ e = parser_malloc(p, sizeof(*e));
+ if (e == NULL) {
+ expr_free(e2);
+ goto err_res;
+ }
+ e->op = EO_OR;
+ e->v.sub[0] = res;
+ e->v.sub[1] = e2;
+ res = e;
+ }
+ return res;
+
+err_res:
+ expr_free(res);
+ return NULL;
+}
+
+/* Parse STRING.
+ On success, return the parsed expression tree.
+ On error, set *ERROR to an error string (for free()) or NULL, and return
+ NULL. (*ERROR == NULL is allowed to handle out-of-memory errors) */
+struct expr *
+expr_parse(const char *string, char **error)
+{
+ struct parsing p;
+ struct expr *res;
+
+ p.error = error;
+ p.token_value = NULL;
+ p.src = string;
+ if (lex(&p) != 0)
+ goto err;
+ if (p.token == T_EOF) {
+ *error = strdup("Empty expression");
+ goto err;
+ }
+ res = parse_or(&p);
+ if (res != NULL && p.token != T_EOF) {
+ expr_free(res);
+ if (asprintf(error, "Unexpected trailing token `%.*s'",
+ p.token_len, p.token_start) < 0)
+ *error = NULL;
+ goto err;
+ }
+ free(p.token_value);
+ return res;
+
+err:
+ free(p.token_value);
+ return NULL;
+}
+
+ /* Manual expression creation */
+
+/* Create a comparison-expression for FIELD, OP and VALUE.
+ On success, return the created expression.
+ On error, set errno and return NULL. */
+struct expr *
+expr_create_comparison(const char *field, unsigned op, const char *value)
+{
+ struct expr *res;
+
+ res = malloc(sizeof(*res));
+ if (res == NULL)
+ goto err;
+ assert(op == EO_RAW_EQ || op == EO_RAW_NE || op == EO_INTERPRETED_EQ
+ || op == EO_INTERPRETED_NE);
+ res->op = op;
+ res->virtual_field = 0;
+ res->precomputed_value = 0;
+ res->v.p.field.name = strdup(field);
+ if (res->v.p.field.name == NULL)
+ goto err_res;
+ res->v.p.value.string = strdup(value);
+ if (res->v.p.value.string == NULL)
+ goto err_field;
+ return res;
+
+err_field:
+ free(res->v.p.field.name);
+err_res:
+ free(res);
+err:
+ return NULL;
+}
+
+/* Create an extended timestamp comparison-expression for with OP, SEC,
+ MILLI, and SERIAL.
+ On success, return the created expression.
+ On error, set errno and return NULL. */
+struct expr *
+expr_create_timestamp_comparison_ex(unsigned op, time_t sec, unsigned milli,
+ unsigned serial)
+{
+ struct expr *res;
+
+ res = malloc(sizeof(*res));
+ if (res == NULL)
+ return NULL;
+ assert(op == EO_VALUE_EQ || op == EO_VALUE_NE || op == EO_VALUE_LT
+ || op == EO_VALUE_LE || op == EO_VALUE_GT || op == EO_VALUE_GE);
+ res->op = op;
+ res->virtual_field = 1;
+ res->v.p.field.id = EF_TIMESTAMP_EX;
+ res->precomputed_value = 1;
+ res->v.p.value.timestamp_ex.sec = sec;
+ assert(milli < 1000);
+ res->v.p.value.timestamp_ex.milli = milli;
+ res->v.p.value.timestamp_ex.serial = serial;
+ return res;
+}
+
+/* Create a timestamp comparison-expression for with OP, SEC, MILLI.
+ On success, return the created expression.
+ On error, set errno and return NULL. */
+struct expr *
+expr_create_timestamp_comparison(unsigned op, time_t sec, unsigned milli)
+{
+ return expr_create_timestamp_comparison_ex(op, sec, milli, 0);
+}
+
+/* Create an EO_FIELD_EXISTS-expression for FIELD.
+ On success, return the created expression.
+ On error, set errno and return NULL. */
+struct expr *
+expr_create_field_exists(const char *field)
+{
+ struct expr *res;
+
+ res = malloc(sizeof(*res));
+ if (res == NULL)
+ goto err;
+ res->op = EO_FIELD_EXISTS;
+ res->virtual_field = 0;
+ res->v.p.field.name = strdup(field);
+ if (res->v.p.field.name == NULL)
+ goto err_res;
+ return res;
+
+err_res:
+ free(res);
+err:
+ return NULL;
+}
+
+/* Create a \regexp expression for regexp comparison.
+ On success, return the created expression.
+ On error, set errno and return NULL. */
+struct expr *
+expr_create_regexp_expression(const char *regexp)
+{
+ struct expr *res;
+
+ res = malloc(sizeof(*res));
+ if (res == NULL)
+ goto err;
+ res->v.regexp = malloc(sizeof(*res->v.regexp));
+ if (res->v.regexp == NULL)
+ goto err_res;
+ if (regcomp(res->v.regexp, regexp, REG_EXTENDED | REG_NOSUB) != 0) {
+ errno = EINVAL;
+ goto err_res_regexp;
+ }
+ res->op = EO_REGEXP_MATCHES;
+ return res;
+
+err_res_regexp:
+ free(res->v.regexp);
+err_res:
+ free(res);
+err:
+ return NULL;
+}
+
+/* Create a binary expresion for OP and subexpressions E1 and E2.
+ On success, return the created expresion.
+ On error, set errno and return NULL. */
+struct expr *
+expr_create_binary(unsigned op, struct expr *e1, struct expr *e2)
+{
+ struct expr *res;
+
+ res = malloc(sizeof(*res));
+ if (res == NULL)
+ return NULL;
+ assert(op == EO_AND || op ==EO_OR);
+ res->op = op;
+ res->v.sub[0] = e1;
+ res->v.sub[1] = e2;
+ return res;
+}
+
+ /* Expression evaluation */
+
+/* Return the "raw" value of the field in EXPR for RECORD in AU->le. Set
+ *FREE_IT to 1 if the return value should free()'d.
+ Return NULL on error. */
+static char *
+eval_raw_value(auparse_state_t *au, rnode *record, const struct expr *expr,
+ int *free_it)
+{
+ if (expr->virtual_field == 0) {
+ nvlist_first(&record->nv);
+ if (nvlist_find_name(&record->nv, expr->v.p.field.name) == 0)
+ return NULL;
+ *free_it = 0;
+ return (char *)nvlist_get_cur_val(&record->nv);
+ }
+ switch (expr->v.p.field.id) {
+ case EF_TIMESTAMP: case EF_RECORD_TYPE: case EF_TIMESTAMP_EX:
+ return NULL;
+
+ default:
+ abort();
+ }
+}
+
+/* Return the "interpreted" value of the field in EXPR for RECORD in AU->le.
+ Set *FREE_IT to 1 if the return value should free()'d.
+ Return NULL on *error. */
+static char *
+eval_interpreted_value(auparse_state_t *au, rnode *record,
+ const struct expr *expr, int *free_it)
+{
+ if (expr->virtual_field == 0) {
+ const char *res;
+
+ nvlist_first(&record->nv);
+ if (nvlist_find_name(&record->nv, expr->v.p.field.name) == 0)
+ return NULL;
+ *free_it = 0;
+ res = nvlist_interp_cur_val(record);
+ if (res == NULL)
+ res = nvlist_get_cur_val(&record->nv);
+ return (char *)res;
+ }
+ switch (expr->v.p.field.id) {
+ case EF_TIMESTAMP: case EF_RECORD_TYPE: case EF_TIMESTAMP_EX:
+ return NULL;
+
+ default:
+ abort();
+ }
+}
+
+/* Return -1, 0, 1 depending on comparing the field in EXPR with RECORD in AU.
+ Set *ERROR to 0 if OK, non-zero otherwise. */
+static int
+compare_values(auparse_state_t *au, rnode *record, const struct expr *expr,
+ int *error)
+{
+ int res;
+ if (expr->virtual_field == 0) {
+ *error = 1;
+ return 0;
+ }
+ switch (expr->v.p.field.id) {
+ case EF_TIMESTAMP:
+ if (au->le.e.sec < expr->v.p.value.timestamp.sec)
+ res = -1;
+ else if (au->le.e.sec > expr->v.p.value.timestamp.sec)
+ res = 1;
+ else if (au->le.e.milli < expr->v.p.value.timestamp.milli)
+ res = -1;
+ else if (au->le.e.milli > expr->v.p.value.timestamp.milli)
+ res = 1;
+ else
+ res = 0;
+ break;
+
+ case EF_RECORD_TYPE:
+ if (record->type < expr->v.p.value.int_value)
+ res = -1;
+ else if (record->type > expr->v.p.value.int_value)
+ res = 1;
+ else
+ res = 0;
+ break;
+
+ case EF_TIMESTAMP_EX:
+ if (au->le.e.sec < expr->v.p.value.timestamp.sec)
+ res = -1;
+ else if (au->le.e.sec > expr->v.p.value.timestamp.sec)
+ res = 1;
+ else if (au->le.e.milli < expr->v.p.value.timestamp.milli)
+ res = -1;
+ else if (au->le.e.milli > expr->v.p.value.timestamp.milli)
+ res = 1;
+ else if (au->le.e.serial < expr->v.p.value.timestamp_ex.serial)
+ res = -1;
+ else if (au->le.e.serial > expr->v.p.value.timestamp_ex.serial)
+ res = 1;
+ else
+ res = 0;
+ break;
+
+ default:
+ abort();
+ }
+ *error = 0;
+ return res;
+}
+
+/* Evaluate EXPR on RECORD in AU->le.
+ Return 1 if EXPR is true, 0 if it false or if it fails.
+ (No error reporting facility is provided; an invalid term is considered to
+ be false; e.g. !invalid is true.) */
+int
+expr_eval(auparse_state_t *au, rnode *record, const struct expr *expr)
+{
+ switch (expr->op) {
+ case EO_NOT:
+ return !expr_eval(au, record, expr->v.sub[0]);
+
+ case EO_AND:
+ return (expr_eval(au, record, expr->v.sub[0])
+ && expr_eval(au, record, expr->v.sub[1]));
+
+ case EO_OR:
+ return (expr_eval(au, record, expr->v.sub[0])
+ || expr_eval(au, record, expr->v.sub[1]));
+
+ case EO_RAW_EQ: case EO_RAW_NE: {
+ int free_it, ne;
+ char *value;
+
+ value = eval_raw_value(au, record, expr, &free_it);
+ if (value == NULL)
+ return 0;
+ assert(expr->precomputed_value == 0);
+ ne = strcmp(expr->v.p.value.string, value);
+ if (free_it != 0)
+ free(value);
+ return expr->op == EO_RAW_EQ ? ne == 0 : ne != 0;
+ }
+
+ case EO_INTERPRETED_EQ: case EO_INTERPRETED_NE: {
+ int free_it, ne;
+ char *value;
+
+ value = eval_interpreted_value(au, record, expr, &free_it);
+ if (value == NULL)
+ return 0;
+ assert(expr->precomputed_value == 0);
+ ne = strcmp(expr->v.p.value.string, value);
+ if (free_it != 0)
+ free(value);
+ return expr->op == EO_INTERPRETED_EQ ? ne == 0 : ne != 0;
+ }
+
+ case EO_VALUE_EQ: case EO_VALUE_NE: case EO_VALUE_LT: case EO_VALUE_LE:
+ case EO_VALUE_GT: case EO_VALUE_GE: {
+ int err, cmp;
+
+ cmp = compare_values(au, record, expr, &err);
+ if (err != 0)
+ return 0;
+ switch (expr->op) {
+ case EO_VALUE_EQ:
+ return cmp == 0;
+
+ case EO_VALUE_NE:
+ return cmp != 0;
+
+ case EO_VALUE_LT:
+ return cmp < 0;
+
+ case EO_VALUE_LE:
+ return cmp <= 0;
+
+ case EO_VALUE_GT:
+ return cmp > 0;
+
+ case EO_VALUE_GE:
+ return cmp >= 0;
+
+ default:
+ abort();
+ }
+ }
+
+ case EO_FIELD_EXISTS:
+ assert(expr->virtual_field == 0);
+ nvlist_first(&record->nv);
+ return nvlist_find_name(&record->nv, expr->v.p.field.name) != 0;
+
+ case EO_REGEXP_MATCHES:
+ return regexec(expr->v.regexp, record->record, 0, NULL, 0) == 0;
+
+ default:
+ abort();
+ }
+}