]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
src: allow for variables in the log prefix string
authorPablo Neira Ayuso <pablo@netfilter.org>
Tue, 7 Jul 2020 15:42:37 +0000 (17:42 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Wed, 8 Jul 2020 09:25:25 +0000 (11:25 +0200)
For example:

 define test = "state"
 define foo = "match"

 table x {
        chain y {
                ct state invalid log prefix "invalid $test $foo:"
        }
 }

This patch scans for variables in the log prefix string. The log prefix
expression is a list of constant and variable expression that are
converted into a constant expression from the evaluation phase.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
src/evaluate.c
src/parser_bison.y
tests/shell/testcases/optionals/dumps/log_prefix_0.nft [new file with mode: 0644]
tests/shell/testcases/optionals/log_prefix_0 [new file with mode: 0755]

index 640a7d465bae08fa83f33d4f04cf668c8b9df2f6..d3368bacc6afe70400826f92d415acab016f63e9 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/netfilter/nf_tables.h>
 #include <linux/netfilter/nf_synproxy.h>
 #include <linux/netfilter/nf_nat.h>
+#include <linux/netfilter/nf_log.h>
 #include <linux/netfilter_ipv4.h>
 #include <netinet/ip_icmp.h>
 #include <netinet/icmp6.h>
@@ -3203,8 +3204,50 @@ static int stmt_evaluate_queue(struct eval_ctx *ctx, struct stmt *stmt)
        return 0;
 }
 
+static int stmt_evaluate_log_prefix(struct eval_ctx *ctx, struct stmt *stmt)
+{
+       char prefix[NF_LOG_PREFIXLEN] = {}, tmp[NF_LOG_PREFIXLEN] = {};
+       int len = sizeof(prefix), offset = 0, ret;
+       struct expr *expr;
+       size_t size = 0;
+
+       if (stmt->log.prefix->etype != EXPR_LIST)
+               return 0;
+
+       list_for_each_entry(expr, &stmt->log.prefix->expressions, list) {
+               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 expresion type %s\n", expr_name(expr));
+                       break;
+               }
+               SNPRINTF_BUFFER_SIZE(ret, size, len, offset);
+       }
+
+       if (len == NF_LOG_PREFIXLEN)
+               return stmt_error(ctx, stmt, "log prefix is too long");
+
+       expr_free(stmt->log.prefix);
+
+       stmt->log.prefix =
+               constant_expr_alloc(&stmt->log.prefix->location, &string_type,
+                                   BYTEORDER_HOST_ENDIAN,
+                                   strlen(prefix) * BITS_PER_BYTE,
+                                   prefix);
+       return 0;
+}
+
 static int stmt_evaluate_log(struct eval_ctx *ctx, struct stmt *stmt)
 {
+       int ret = 0;
+
        if (stmt->log.flags & (STMT_LOG_GROUP | STMT_LOG_SNAPLEN |
                               STMT_LOG_QTHRESHOLD)) {
                if (stmt->log.flags & STMT_LOG_LEVEL)
@@ -3218,7 +3261,11 @@ static int stmt_evaluate_log(struct eval_ctx *ctx, struct stmt *stmt)
            (stmt->log.flags & ~STMT_LOG_LEVEL || stmt->log.logflags))
                return stmt_error(ctx, stmt,
                                  "log level audit doesn't support any further options");
-       return 0;
+
+       if (stmt->log.prefix)
+               ret = stmt_evaluate_log_prefix(ctx, stmt);
+
+       return ret;
 }
 
 static int stmt_evaluate_set(struct eval_ctx *ctx, struct stmt *stmt)
index 2fecc3472fbaacb12903ab7c3eaf1cd9efbf4aa7..face99507b828e5d1634076241097dfbe34e8674 100644 (file)
@@ -2636,11 +2636,125 @@ log_args               :       log_arg
 
 log_arg                        :       PREFIX                  string
                        {
-                               struct expr *expr;
+                               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);
+                                       $<stmt>0->log.prefix = expr;
+                                       $<stmt>0->log.flags |= STMT_LOG_PREFIX;
+                                       break;
+                               }
 
-                               expr = constant_expr_alloc(&@$, &string_type,
-                                                          BYTEORDER_HOST_ENDIAN,
-                                                          strlen($2) * BITS_PER_BYTE, $2);
+                               /* 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;
+
+                               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);
+                                                       xfree($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;
+                                       }
+                               }
+
+                               xfree($2);
                                $<stmt>0->log.prefix     = expr;
                                $<stmt>0->log.flags     |= STMT_LOG_PREFIX;
                        }
diff --git a/tests/shell/testcases/optionals/dumps/log_prefix_0.nft b/tests/shell/testcases/optionals/dumps/log_prefix_0.nft
new file mode 100644 (file)
index 0000000..8c11d69
--- /dev/null
@@ -0,0 +1,5 @@
+table ip x {
+       chain y {
+               ct state invalid log prefix "invalid state match, logging:"
+       }
+}
diff --git a/tests/shell/testcases/optionals/log_prefix_0 b/tests/shell/testcases/optionals/log_prefix_0
new file mode 100755 (executable)
index 0000000..513a9e7
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+set -e
+
+TMP=$(mktemp)
+
+RULESET='define test = "state"
+define foo = "match, logging"
+
+table x {
+        chain y {
+                ct state invalid log prefix "invalid $test $foo:"
+        }
+}'
+
+$NFT -f - <<< "$RULESET"