From 8840a1f7dfd199d422c598102c3e7d4481150c99 Mon Sep 17 00:00:00 2001 From: Samaresh Kumar Singh Date: Sat, 6 Jun 2026 15:13:55 -0500 Subject: [PATCH] conf-yaml: support glob patterns in include directive SCConfYamlHandleInclude only accepted explicit filenames. The rule-files directive has supported shell-style glob expansion via glob(3) for years, and asymmetry with include has been blocking clean drop-in conf.d/ style configuration directories. Refactor SCConfYamlHandleInclude into a thin wrapper that resolves the path against conf_dirname and, if the input contains glob metacharacters (*, ?, [), expands it with glob(3) and includes each match in lexicographic order. A pattern that matches no files is logged as a warning and not treated as an error, so a drop-in directory can be empty without breaking startup. Literal paths skip glob() and call the existing per-file inclusion logic directly, preserving current behaviour. The behaviour is verified by the config-includes-glob-order suricata-verify test, which checks that every file matching a pattern is loaded and that matches are included in deterministic lexicographic order. Document the new behaviour in doc/userguide/configuration/ includes.rst. Feature: #8427. --- doc/userguide/configuration/includes.rst | 18 ++++++ src/conf-yaml-loader.c | 79 +++++++++++++++++++----- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/doc/userguide/configuration/includes.rst b/doc/userguide/configuration/includes.rst index 2fc62908aab5..e8793f7f14a7 100644 --- a/doc/userguide/configuration/includes.rst +++ b/doc/userguide/configuration/includes.rst @@ -49,6 +49,24 @@ is the equivalent of:: address-groups: HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]" +Glob Patterns +------------- + +Filenames in ``include`` may contain shell-style glob metacharacters +(``*``, ``?``, ``[...]``). Patterns are expanded at startup via +``glob(3)`` and each matching file is loaded in lexicographic order. A +pattern that matches no files is logged as a warning and is not treated +as an error, which allows drop-in ``conf.d/``-style directories to be +empty. + +:: + + include: + - /etc/suricata/conf.d/*.yaml + +Relative patterns are resolved against the directory of the top-level +configuration file, the same as literal includes. + .. note:: Suricata versions less than 7 required multiple ``include`` statements to be specified to include more than one file. While Suricata 7.0 still supports this it will issue a deprecation diff --git a/src/conf-yaml-loader.c b/src/conf-yaml-loader.c index a011ba697d14..c5e88c9a384b 100644 --- a/src/conf-yaml-loader.c +++ b/src/conf-yaml-loader.c @@ -27,6 +27,9 @@ #include "conf.h" #include "conf-yaml-loader.h" #include +#ifdef HAVE_GLOB_H +#include +#endif #include "util-path.h" #include "util-debug.h" #include "util-unittest.h" @@ -104,18 +107,17 @@ ConfYamlSetConfDirname(const char *filename) } /** - * \brief Include a file in the configuration. + * \brief Include a single resolved file in the configuration. * * \param parent The configuration node the included configuration will be * placed at. - * \param filename The filename to include. + * \param filename The fully resolved filename to include. * * \retval 0 on success, -1 on failure. */ -int SCConfYamlHandleInclude(SCConfNode *parent, const char *filename) +static int ConfYamlHandleIncludeOne(SCConfNode *parent, const char *filename) { yaml_parser_t parser; - char include_filename[PATH_MAX]; FILE *file = NULL; int ret = -1; @@ -124,18 +126,9 @@ int SCConfYamlHandleInclude(SCConfNode *parent, const char *filename) return -1; } - if (PathIsAbsolute(filename)) { - strlcpy(include_filename, filename, sizeof(include_filename)); - } - else { - snprintf(include_filename, sizeof(include_filename), "%s/%s", - conf_dirname, filename); - } - - file = fopen(include_filename, "r"); + file = fopen(filename, "r"); if (file == NULL) { - SCLogError("Failed to open configuration include file %s: %s", include_filename, - strerror(errno)); + SCLogError("Failed to open configuration include file %s: %s", filename, strerror(errno)); goto done; } @@ -157,6 +150,62 @@ int SCConfYamlHandleInclude(SCConfNode *parent, const char *filename) return ret; } +/** + * \brief Include a file or glob pattern in the configuration. + * + * Relative paths are resolved against the directory of the top-level config + * file. If the input contains glob metacharacters (\c *, \c ?, \c [) the + * pattern is expanded via glob(3) and each match is included in lexicographic + * order. A pattern that matches no files is logged as a warning and not + * treated as an error, to support drop-in `conf.d/` directories. + * + * \param parent The configuration node the included configuration will be + * placed at. + * \param filename The filename or glob pattern to include. + * + * \retval 0 on success, -1 on failure. + */ +int SCConfYamlHandleInclude(SCConfNode *parent, const char *filename) +{ + char include_filename[PATH_MAX]; + + if (PathIsAbsolute(filename)) { + strlcpy(include_filename, filename, sizeof(include_filename)); + } else { + snprintf(include_filename, sizeof(include_filename), "%s/%s", conf_dirname, filename); + } + +#ifdef HAVE_GLOB_H + if (strpbrk(filename, "*?[") != NULL) { + glob_t globbuf; + int gret = glob(include_filename, 0, NULL, &globbuf); + + if (gret == GLOB_NOMATCH) { + SCLogWarning("No files match include pattern %s", include_filename); + return 0; + } else if (gret != 0) { + SCLogError( + "Failed to expand include pattern %s: %s", include_filename, strerror(errno)); + return -1; + } + + int ret = 0; + for (size_t i = 0; i < (size_t)globbuf.gl_pathc; i++) { + const char *path = globbuf.gl_pathv[i]; + SCLogInfo("Including configuration file %s (matched %s).", path, filename); + if (ConfYamlHandleIncludeOne(parent, path) != 0) { + ret = -1; + break; + } + } + globfree(&globbuf); + return ret; + } +#endif + + return ConfYamlHandleIncludeOne(parent, include_filename); +} + /** * \brief Parse a YAML layer. *