Add a string preprocessor to identify and replace variables in a string.
Rework existing support to variables in log prefix strings to use it.
Fixes: e76bb3794018 ("src: allow for variables in the log prefix string")
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
src/osf.c \
src/owner.c \
src/payload.c \
+ src/preprocess.c \
src/print.c \
src/proto.c \
src/rt.c \
extern void expr_set_type(struct expr *expr, const struct datatype *dtype,
enum byteorder byteorder);
-void expr_to_string(const struct expr *expr, char *string);
-
struct eval_ctx;
extern int expr_binary_error(struct list_head *msgs,
const struct expr *e1, const struct expr *e2,
extern void scanner_pop_start_cond(void *scanner, enum startcond_type sc);
+const char *str_preprocess(struct parser_state *state, struct location *loc,
+ struct scope *scope, const char *x,
+ struct error_record **rec);
+
#endif /* NFTABLES_PARSER_H */
};
struct log_stmt {
- struct expr *prefix;
+ const char *prefix;
unsigned int snaplen;
uint16_t group;
uint16_t qthreshold;
static int stmt_evaluate_log_prefix(struct eval_ctx *ctx, struct stmt *stmt)
{
- char tmp[NF_LOG_PREFIXLEN] = {};
- char prefix[NF_LOG_PREFIXLEN];
- size_t len = sizeof(prefix);
- size_t offset = 0;
- struct expr *expr;
-
- if (stmt->log.prefix->etype != EXPR_LIST) {
- if (stmt->log.prefix &&
- div_round_up(stmt->log.prefix->len, BITS_PER_BYTE) >= NF_LOG_PREFIXLEN)
- return expr_error(ctx->msgs, stmt->log.prefix, "log prefix is too long");
-
- return 0;
- }
-
- prefix[0] = '\0';
-
- list_for_each_entry(expr, &stmt->log.prefix->expressions, list) {
- int ret;
-
- switch (expr->etype) {
- case EXPR_VALUE:
- expr_to_string(expr, tmp);
- ret = snprintf(prefix + offset, len, "%s", tmp);
- break;
- case EXPR_VARIABLE:
- ret = snprintf(prefix + offset, len, "%s",
- expr->sym->expr->identifier);
- break;
- default:
- BUG("unknown expression type %s\n", expr_name(expr));
- break;
- }
- SNPRINTF_BUFFER_SIZE(ret, &len, &offset);
- }
+ unsigned int len = strlen(stmt->log.prefix);
- if (len == 0)
+ if (len >= NF_LOG_PREFIXLEN)
return stmt_error(ctx, stmt, "log prefix is too long");
-
- expr = constant_expr_alloc(&stmt->log.prefix->location, &string_type,
- BYTEORDER_HOST_ENDIAN,
- strlen(prefix) * BITS_PER_BYTE, prefix);
- expr_free(stmt->log.prefix);
- stmt->log.prefix = expr;
+ else if (len == 0)
+ return stmt_error(ctx, stmt, "log prefix must have a minimum length of 1 character");
return 0;
}
}
}
-void expr_to_string(const struct expr *expr, char *string)
-{
- int len = expr->len / BITS_PER_BYTE;
-
- assert(expr->dtype == &string_type);
-
- mpz_export_data(string, expr->value, BYTEORDER_HOST_ENDIAN, len);
-}
-
void expr_set_type(struct expr *expr, const struct datatype *dtype,
enum byteorder byteorder)
{
{
json_t *root = json_object(), *flags;
- if (stmt->log.flags & STMT_LOG_PREFIX) {
- char prefix[NF_LOG_PREFIXLEN] = {};
+ if (stmt->log.flags & STMT_LOG_PREFIX)
+ json_object_set_new(root, "prefix", json_string(stmt->log.prefix));
- expr_to_string(stmt->log.prefix, prefix);
- json_object_set_new(root, "prefix", json_string(prefix));
- }
if (stmt->log.flags & STMT_LOG_GROUP)
json_object_set_new(root, "group",
json_integer(stmt->log.group));
stmt = log_stmt_alloc(loc);
prefix = nftnl_expr_get_str(nle, NFTNL_EXPR_LOG_PREFIX);
if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_PREFIX)) {
- stmt->log.prefix = constant_expr_alloc(&internal_location,
- &string_type,
- BYTEORDER_HOST_ENDIAN,
- (strlen(prefix) + 1) * BITS_PER_BYTE,
- prefix);
+ stmt->log.prefix = xstrdup(prefix);
stmt->log.flags |= STMT_LOG_PREFIX;
}
if (nftnl_expr_is_set(nle, NFTNL_EXPR_LOG_GROUP)) {
struct nftnl_expr *nle;
nle = alloc_nft_expr("log");
- if (stmt->log.prefix != NULL) {
- char prefix[NF_LOG_PREFIXLEN] = {};
+ if (stmt->log.prefix != NULL)
+ nftnl_expr_set_str(nle, NFTNL_EXPR_LOG_PREFIX, stmt->log.prefix);
- expr_to_string(stmt->log.prefix, prefix);
- nftnl_expr_set_str(nle, NFTNL_EXPR_LOG_PREFIX, prefix);
- }
if (stmt->log.flags & STMT_LOG_GROUP) {
nftnl_expr_set_u16(nle, NFTNL_EXPR_LOG_GROUP, stmt->log.group);
if (stmt->log.flags & STMT_LOG_SNAPLEN)
if (!stmt_a->log.prefix)
return true;
- if (stmt_a->log.prefix->etype != EXPR_VALUE ||
- stmt_b->log.prefix->etype != EXPR_VALUE ||
- mpz_cmp(stmt_a->log.prefix->value, stmt_b->log.prefix->value))
+ if (strcmp(stmt_a->log.prefix, stmt_b->log.prefix))
return false;
break;
case STMT_REJECT:
case STMT_LOG:
memcpy(&clone->log, &stmt->log, sizeof(clone->log));
if (stmt->log.prefix)
- clone->log.prefix = expr_get(stmt->log.prefix);
+ clone->log.prefix = xstrdup(stmt->log.prefix);
break;
case STMT_NAT:
if ((stmt->nat.addr &&
log_arg : PREFIX string
{
struct scope *scope = current_scope(state);
- bool done = false, another_var = false;
- char *start, *end, scratch = '\0';
- struct expr *expr, *item;
- struct symbol *sym;
- enum {
- PARSE_TEXT,
- PARSE_VAR,
- } prefix_state;
-
- /* No variables in log prefix, skip. */
- if (!strchr($2, '$')) {
- expr = constant_expr_alloc(&@$, &string_type,
- BYTEORDER_HOST_ENDIAN,
- (strlen($2) + 1) * BITS_PER_BYTE, $2);
- free_const($2);
- $<stmt>0->log.prefix = expr;
- $<stmt>0->log.flags |= STMT_LOG_PREFIX;
- break;
- }
-
- /* Parse variables in log prefix string using a
- * state machine parser with two states. This
- * parser creates list of expressions composed
- * of constant and variable expressions.
- */
- expr = compound_expr_alloc(&@$, EXPR_LIST);
-
- start = (char *)$2;
+ struct error_record *erec;
+ const char *prefix;
- if (*start != '$') {
- prefix_state = PARSE_TEXT;
- } else {
- prefix_state = PARSE_VAR;
- start++;
- }
- end = start;
-
- /* Not nice, but works. */
- while (!done) {
- switch (prefix_state) {
- case PARSE_TEXT:
- while (*end != '\0' && *end != '$')
- end++;
-
- if (*end == '\0')
- done = true;
-
- *end = '\0';
- item = constant_expr_alloc(&@$, &string_type,
- BYTEORDER_HOST_ENDIAN,
- (strlen(start) + 1) * BITS_PER_BYTE,
- start);
- compound_expr_add(expr, item);
-
- if (done)
- break;
-
- start = end + 1;
- end = start;
-
- /* fall through */
- case PARSE_VAR:
- while (isalnum(*end) || *end == '_')
- end++;
-
- if (*end == '\0')
- done = true;
- else if (*end == '$')
- another_var = true;
- else
- scratch = *end;
-
- *end = '\0';
-
- sym = symbol_get(scope, start);
- if (!sym) {
- sym = symbol_lookup_fuzzy(scope, start);
- if (sym) {
- erec_queue(error(&@2, "unknown identifier '%s'; "
- "did you mean identifier ā%sā?",
- start, sym->identifier),
- state->msgs);
- } else {
- erec_queue(error(&@2, "unknown identifier '%s'",
- start),
- state->msgs);
- }
- expr_free(expr);
- free_const($2);
- YYERROR;
- }
- item = variable_expr_alloc(&@$, scope, sym);
- compound_expr_add(expr, item);
-
- if (done)
- break;
-
- /* Restore original byte after
- * symbol lookup.
- */
- if (scratch) {
- *end = scratch;
- scratch = '\0';
- }
-
- start = end;
- if (another_var) {
- another_var = false;
- start++;
- prefix_state = PARSE_VAR;
- } else {
- prefix_state = PARSE_TEXT;
- }
- end = start;
- break;
- }
+ prefix = str_preprocess(state, &@2, scope, $2, &erec);
+ if (!prefix) {
+ erec_queue(erec, state->msgs);
+ free_const($2);
+ YYERROR;
}
free_const($2);
- $<stmt>0->log.prefix = expr;
- $<stmt>0->log.flags |= STMT_LOG_PREFIX;
+ $<stmt>0->log.prefix = prefix;
+ $<stmt>0->log.flags |= STMT_LOG_PREFIX;
}
| GROUP NUM
{
stmt = log_stmt_alloc(int_loc);
if (!json_unpack(value, "{s:s}", "prefix", &tmpstr)) {
- stmt->log.prefix = constant_expr_alloc(int_loc, &string_type,
- BYTEORDER_HOST_ENDIAN,
- (strlen(tmpstr) + 1) * BITS_PER_BYTE, tmpstr);
+ stmt->log.prefix = xstrdup(tmpstr);
stmt->log.flags |= STMT_LOG_PREFIX;
}
if (!json_unpack(value, "{s:i}", "group", &tmp)) {
--- /dev/null
+/*
+ * Copyright (c) 2013-2024 Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 (or any
+ * later) as published by the Free Software Foundation.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <utils.h>
+
+#include "list.h"
+#include "parser.h"
+#include "erec.h"
+
+struct str_buf {
+ uint8_t *str;
+ uint32_t len;
+ uint32_t size;
+};
+
+#define STR_BUF_LEN 128
+
+static struct str_buf *str_buf_alloc(void)
+{
+ struct str_buf *buf;
+
+ buf = xzalloc(sizeof(*buf));
+ buf->str = xzalloc_array(1, STR_BUF_LEN);
+ buf->size = STR_BUF_LEN;
+
+ return buf;
+}
+
+static int str_buf_add(struct str_buf *buf, const char *str, uint32_t len)
+{
+ uint8_t *tmp;
+
+ if (len + buf->len > buf->size) {
+ buf->size = (len + buf->len) * 2;
+ tmp = xrealloc(buf->str, buf->size);
+ buf->str = tmp;
+ }
+
+ memcpy(&buf->str[buf->len], str, len);
+ buf->len += len;
+
+ return 0;
+}
+
+struct str_chunk {
+ struct list_head list;
+ char *str;
+ uint32_t len;
+ bool is_sym;
+};
+
+static void add_str_chunk(const char *x, int from, int to, struct list_head *list, bool is_sym)
+{
+ struct str_chunk *chunk;
+ int len = to - from;
+
+ chunk = xzalloc_array(1, sizeof(*chunk));
+ chunk->str = xzalloc_array(1, len + 1);
+ chunk->is_sym = is_sym;
+ chunk->len = len;
+ memcpy(chunk->str, &x[from], len);
+
+ list_add_tail(&chunk->list, list);
+}
+
+static void free_str_chunk(struct str_chunk *chunk)
+{
+ free(chunk->str);
+ free(chunk);
+}
+
+const char *str_preprocess(struct parser_state *state, struct location *loc,
+ struct scope *scope, const char *x,
+ struct error_record **erec)
+{
+ struct str_chunk *chunk, *next;
+ struct str_buf *buf;
+ const char *str;
+ int i, j, start;
+ LIST_HEAD(list);
+
+ start = 0;
+ i = 0;
+ while (1) {
+ if (x[i] == '\0') {
+ i++;
+ break;
+ }
+
+ if (x[i] != '$') {
+ i++;
+ continue;
+ }
+
+ if (isdigit(x[++i]))
+ continue;
+
+ j = i;
+ while (1) {
+ if (isalpha(x[i]) ||
+ isdigit(x[i]) ||
+ x[i] == '_') {
+ i++;
+ continue;
+ }
+ break;
+ }
+ add_str_chunk(x, start, j-1, &list, false);
+ add_str_chunk(x, j, i, &list, true);
+ start = i;
+ }
+ if (start != i)
+ add_str_chunk(x, start, i, &list, false);
+
+ buf = str_buf_alloc();
+
+ list_for_each_entry_safe(chunk, next, &list, list) {
+ if (chunk->is_sym) {
+ struct symbol *sym;
+
+ sym = symbol_lookup(scope, chunk->str);
+ if (!sym) {
+ sym = symbol_lookup_fuzzy(scope, chunk->str);
+ if (sym) {
+ *erec = error(loc, "unknown identifier '%s'; "
+ "did you mean identifier '%s'?",
+ chunk->str, sym->identifier);
+ } else {
+ *erec = error(loc, "unknown identifier '%s'",
+ chunk->str);
+ }
+ goto err;
+ }
+ str_buf_add(buf, sym->expr->identifier,
+ strlen(sym->expr->identifier));
+ } else {
+ str_buf_add(buf, chunk->str, chunk->len);
+ }
+ list_del(&chunk->list);
+ free_str_chunk(chunk);
+ }
+
+ str = (char *)buf->str;
+
+ free(buf);
+
+ return (char *)str;
+err:
+ list_for_each_entry_safe(chunk, next, &list, list) {
+ list_del(&chunk->list);
+ free_str_chunk(chunk);
+ }
+ free(buf->str);
+ free(buf);
+
+ return NULL;
+}
static void log_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
{
nft_print(octx, "log");
- if (stmt->log.flags & STMT_LOG_PREFIX) {
- char prefix[NF_LOG_PREFIXLEN] = {};
-
- expr_to_string(stmt->log.prefix, prefix);
- nft_print(octx, " prefix \"%s\"", prefix);
- }
+ if (stmt->log.flags & STMT_LOG_PREFIX)
+ nft_print(octx, " prefix \"%s\"", stmt->log.prefix);
if (stmt->log.flags & STMT_LOG_GROUP)
nft_print(octx, " group %u", stmt->log.group);
if (stmt->log.flags & STMT_LOG_SNAPLEN)
static void log_stmt_destroy(struct stmt *stmt)
{
- expr_free(stmt->log.prefix);
+ free_const(stmt->log.prefix);
}
static const struct stmt_ops log_stmt_ops = {