]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
src: add string preprocessor and use it for log prefix string
authorPablo Neira Ayuso <pablo@netfilter.org>
Tue, 18 Jun 2024 12:26:31 +0000 (14:26 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Tue, 25 Jun 2024 15:20:12 +0000 (17:20 +0200)
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>
14 files changed:
Makefile.am
include/expression.h
include/parser.h
include/statement.h
src/evaluate.c
src/expression.c
src/json.c
src/netlink_delinearize.c
src/netlink_linearize.c
src/optimize.c
src/parser_bison.y
src/parser_json.c
src/preprocess.c [new file with mode: 0644]
src/statement.c

index fef1d8d1632171e3fe2c852cb569ba9d7c87deee..9088170bfc683ea22a710ee1cc46181e9a6f6aa6 100644 (file)
@@ -232,6 +232,7 @@ src_libnftables_la_SOURCES = \
        src/osf.c \
        src/owner.c \
        src/payload.c \
+       src/preprocess.c \
        src/print.c \
        src/proto.c \
        src/rt.c \
index 01b45b7c83b9c6c0a3474770b93dcdeeda4071f4..8982110cce95232177c4b2f579ac8d997dca743b 100644 (file)
@@ -415,8 +415,6 @@ extern const struct datatype *expr_basetype(const struct expr *expr);
 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,
index f79a22f306af0db6dd20b8341844810ee626b1ac..576e5e43e6881a8f07345107c6af3adaafdd665d 100644 (file)
@@ -112,4 +112,8 @@ extern void scanner_push_buffer(void *scanner,
 
 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 */
index 662f99ddef796af16baee84fb530e192358db0b4..9376911bb377938e9631383b39fd80f6517d0478 100644 (file)
@@ -90,7 +90,7 @@ enum {
 };
 
 struct log_stmt {
-       struct expr             *prefix;
+       const char              *prefix;
        unsigned int            snaplen;
        uint16_t                group;
        uint16_t                qthreshold;
index 227f5da8638260a127cc420f03e779c2859fe1dc..aa9293a8785689acdcaa762fd61f42ff41af3704 100644 (file)
@@ -4440,49 +4440,12 @@ static int stmt_evaluate_queue(struct eval_ctx *ctx, struct stmt *stmt)
 
 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;
 }
index cb2573fec4576ab267c872f85e99b1842134d758..992f5106405129721443e224b75e4558c69d3ef4 100644 (file)
@@ -183,15 +183,6 @@ void expr_describe(const struct expr *expr, struct output_ctx *octx)
        }
 }
 
-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)
 {
index b4fad0abd4b35feb5a34002128b16a9bc681de6d..b1531ff3f4c9e7b42a4aceaf51308a60342aef85 100644 (file)
@@ -1343,12 +1343,9 @@ json_t *log_stmt_json(const struct stmt *stmt, struct output_ctx *octx)
 {
        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));
index da9f7a91e4b3bb529cbb13cb268597081a917e63..82e68999a432c367d7f1b8d489f233aef5302e04 100644 (file)
@@ -1090,11 +1090,7 @@ static void netlink_parse_log(struct netlink_parse_ctx *ctx,
        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)) {
index de9e975ffcf19e446e21aaa786c1b0a7bd292e7e..abda903bc59cff545e1ec47c88aa5d9db131ce39 100644 (file)
@@ -1146,12 +1146,9 @@ static void netlink_gen_log_stmt(struct netlink_linearize_ctx *ctx,
        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)
index b90dd995b13e18500d5a93da14eebbe0119ac816..1dd08586f32687cd0a0cc56e2e9eacdc369b6265 100644 (file)
@@ -215,9 +215,7 @@ static bool __stmt_type_eq(const struct stmt *stmt_a, const struct stmt *stmt_b,
                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:
@@ -406,7 +404,7 @@ static int rule_collect_stmts(struct optimize_ctx *ctx, struct rule *rule)
                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 &&
index 53f45315ef469e079fa0ddd90d9242f0a34f4a6e..f3f71801643d85d6c3dc891e38b2886eab378214 100644 (file)
@@ -3372,127 +3372,19 @@ log_args               :       log_arg
 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
                        {
index 8b7efaf226627f5853b0fe440da51954f9fad048..ee4657ee8044b0ad4f74fab0466b9683fc4247a7 100644 (file)
@@ -2565,9 +2565,7 @@ static struct stmt *json_parse_log_stmt(struct json_ctx *ctx,
        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)) {
diff --git a/src/preprocess.c b/src/preprocess.c
new file mode 100644 (file)
index 0000000..619f67a
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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;
+}
index ab144d6393189aa9ae08763c6c23df4250c92366..551cd13fa04ebd4ee03694cb3b29f7ba464c8d10 100644 (file)
@@ -377,12 +377,8 @@ int log_level_parse(const char *level)
 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)
@@ -419,7 +415,7 @@ static void log_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
 
 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 = {