diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 6212220aed..d2a21b5eb9 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -187,7 +187,7 @@ jobs: working-directory: examples/plugins/ci-capture run: | make - ../../../src/suricata -S /dev/null --set plugins.0=./capture.so --capture-plugin=ci-capture --runmode=single -l . -c ../../../suricata.yaml + ../../../src/suricata -S /dev/null --plugin ./capture.so --capture-plugin=ci-capture --runmode=single -l . -c ../../../suricata.yaml cat eve.json | jq -c 'select(.dns)' test $(cat eve.json | jq -c 'select(.dns)' | wc -l) = "1" @@ -195,7 +195,7 @@ jobs: working-directory: examples/plugins/altemplate run: | cargo build - ../../../src/suricata -S altemplate.rules --set plugins.0=./target/debug/libsuricata_altemplate.so --runmode=single -l . -c altemplate.yaml -k none -r ../../../rust/src/applayertemplate/template.pcap + ../../../src/suricata -S altemplate.rules --plugin ./target/debug/libsuricata_altemplate.so --runmode=single -l . -c altemplate.yaml -k none -r ../../../rust/src/applayertemplate/template.pcap cat eve.json | jq -c 'select(.altemplate)' test $(cat eve.json | jq -c 'select(.altemplate)' | wc -l) = "3" # we get 2 alerts and 1 altemplate events @@ -419,7 +419,7 @@ jobs: working-directory: examples/plugins/ci-capture run: | make - ../../../src/suricata -S /dev/null --set plugins.0=./capture.so --capture-plugin=ci-capture --runmode=single -l . -c ../../../suricata.yaml + ../../../src/suricata -S /dev/null --plugin ./capture.so --capture-plugin=ci-capture --runmode=single -l . -c ../../../suricata.yaml cat eve.json | jq -c 'select(.dns)' test $(cat eve.json | jq -c 'select(.dns)' | wc -l) = "1" @@ -427,7 +427,7 @@ jobs: working-directory: examples/plugins/altemplate run: | cargo build - ../../../src/suricata -S altemplate.rules --set plugins.0=./target/debug/libsuricata_altemplate.so --runmode=single -l . -c altemplate.yaml -k none -r ../../../rust/src/applayertemplate/template.pcap + ../../../src/suricata -S altemplate.rules --plugin ./target/debug/libsuricata_altemplate.so --runmode=single -l . -c altemplate.yaml -k none -r ../../../rust/src/applayertemplate/template.pcap cat eve.json | jq -c 'select(.altemplate)' test $(cat eve.json | jq -c 'select(.altemplate)' | wc -l) = "3" # we get 2 alerts and 1 altemplate events diff --git a/doc/userguide/partials/options.rst b/doc/userguide/partials/options.rst index c11992e516..5264db8133 100644 --- a/doc/userguide/partials/options.rst +++ b/doc/userguide/partials/options.rst @@ -272,6 +272,14 @@ .. Advanced options. +.. option:: --plugin + + Load a plugin from *path* in addition to the plugins listed in the + configuration file. This option can be specified multiple times. + + If *path* is a directory, Suricata will attempt to load each + ``.so`` file in that directory. + .. option:: --set = Set a configuration value. Useful for overriding basic diff --git a/plugins/napatech/README.md b/plugins/napatech/README.md index bfc783d0b7..2598428daf 100644 --- a/plugins/napatech/README.md +++ b/plugins/napatech/README.md @@ -9,14 +9,15 @@ optionally the `--with-napatech-includes` and ## Running ``` /usr/local/suricata/bin/suricata \ - --set plugins.0=/usr/local/lib/suricata/napatech.so \ + --plugin /usr/local/lib/suricata/napatech.so \ --capture-plugin=napatech ``` -### --set plugins.0=/usr/local/lib/suricata/napatech.so +### --plugin /usr/local/lib/suricata/napatech.so -This command line option tells Suricata about this plugin. This could also -be done in `suricata.yaml` with the following section: +This command line option tells Suricata about this plugin in addition to any +plugins listed in `suricata.yaml`. This could also be done in `suricata.yaml` +with the following section: ``` plugins: - /usr/local/lib/suricata/napatech.so diff --git a/plugins/pfring/README.md b/plugins/pfring/README.md index d7888f4161..79a9d5da45 100644 --- a/plugins/pfring/README.md +++ b/plugins/pfring/README.md @@ -9,15 +9,16 @@ optionally the `--with-libpfring-includes` and ## Running ``` /usr/local/suricata/bin/suricata \ - --set plugins.0=/usr/local/lib/suricata/pfring.so \ - --capture-plugin=pfring-plugin \ + --plugin /usr/local/lib/suricata/pfring.so \ + --capture-plugin=pfring \ --set pfring.0.interface=eno1 ``` -### --set plugins.0=/usr/local/lib/suricata/pfring.so +### --plugin /usr/local/lib/suricata/pfring.so -This command line option tells Suricata about this plugin. This could also -be done in `suricata.yaml` with the following section: +This command line option tells Suricata about this plugin in addition to any +plugins listed in `suricata.yaml`. This could also be done in `suricata.yaml` +with the following section: ``` plugins: - /usr/local/lib/suricata/pfring.so diff --git a/src/suricata.c b/src/suricata.c index 394ebb3b25..5fba29f47f 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -644,6 +644,7 @@ static void PrintUsage(const char *progname) printf("\t--runmode : specific runmode modification the engine should run. The argument\n" "\t supplied should be the id for the runmode obtained by running\n" "\t --list-runmodes\n"); + printf("\t--plugin : load plugin in addition to config\n"); printf("\n Capture and IPS:\n"); @@ -1382,6 +1383,38 @@ static bool IsLogDirectoryWritable(const char* str) return access(str, W_OK) == 0; } +/** + * Helper functions to append option values to an array where the + * option is allowed multiple times. For example: + * - --include + * - --plugin + */ +static void AddCommandLineOptionValue( + const char ***values, const char *value, const char *description) +{ + if (*values == NULL) { + *values = SCCalloc(2, sizeof(char *)); + if (*values == NULL) { + FatalError("Failed to allocate memory for %s: %s", description, strerror(errno)); + } + (*values)[0] = value; + } else { + for (int i = 0;; i++) { + if ((*values)[i] == NULL) { + const char **new_values = SCRealloc(*values, (i + 2) * sizeof(char *)); + if (new_values == NULL) { + FatalError( + "Failed to allocate memory for %s: %s", description, strerror(errno)); + } + *values = new_values; + (*values)[i] = value; + (*values)[i + 1] = NULL; + break; + } + } + } +} + extern int g_skip_prefilter; TmEcode SCParseCommandLine(int argc, char **argv) @@ -1434,6 +1467,7 @@ TmEcode SCParseCommandLine(int argc, char **argv) {"no-random", 0, &g_disable_randomness, 1}, {"strict-rule-keywords", optional_argument, 0, 0}, + {"plugin", required_argument, 0, 0}, {"capture-plugin", required_argument, 0, 0}, {"capture-plugin-args", required_argument, 0, 0}, @@ -1550,6 +1584,8 @@ TmEcode SCParseCommandLine(int argc, char **argv) "to pass --enable-pfring to configure when building."); return TM_ECODE_FAILED; #endif /* HAVE_PFRING */ + } else if (strcmp((long_opts[option_index]).name, "plugin") == 0) { + AddCommandLineOptionValue(&suri->additional_plugins, optarg, "additional plugins"); } else if (strcmp((long_opts[option_index]).name, "capture-plugin") == 0) { suri->run_mode = RUNMODE_PLUGIN; suri->capture_plugin_name = optarg; @@ -1870,32 +1906,8 @@ TmEcode SCParseCommandLine(int argc, char **argv) FatalError("failed to duplicate 'strict' string"); } } else if (strcmp((long_opts[option_index]).name, "include") == 0) { - if (suri->additional_configs == NULL) { - suri->additional_configs = SCCalloc(2, sizeof(char *)); - if (suri->additional_configs == NULL) { - FatalError( - "Failed to allocate memory for additional configuration files: %s", - strerror(errno)); - } - suri->additional_configs[0] = optarg; - } else { - for (int i = 0;; i++) { - if (suri->additional_configs[i] == NULL) { - const char **additional_configs = - SCRealloc(suri->additional_configs, (i + 2) * sizeof(char *)); - if (additional_configs == NULL) { - FatalError("Failed to allocate memory for additional configuration " - "files: %s", - strerror(errno)); - } else { - suri->additional_configs = additional_configs; - } - suri->additional_configs[i] = optarg; - suri->additional_configs[i + 1] = NULL; - break; - } - } - } + AddCommandLineOptionValue( + &suri->additional_configs, optarg, "additional configuration files"); } else if (strcmp((long_opts[option_index]).name, "firewall-rules-exclusive") == 0) { if (suri->firewall_rule_file != NULL) { SCLogError("can't have multiple --firewall-rules-exclusive options"); @@ -2828,7 +2840,7 @@ int PostConfLoadedSetup(SCInstance *suri) SigTableInit(); #ifdef HAVE_PLUGINS - SCPluginsLoad(suri->capture_plugin_name, suri->capture_plugin_args); + SCPluginsLoad(suri->capture_plugin_name, suri->capture_plugin_args, suri->additional_plugins); #endif LiveDeviceFinalize(); // must be after EBPF extension registration diff --git a/src/suricata.h b/src/suricata.h index 6344e4ee71..ca5afce918 100644 --- a/src/suricata.h +++ b/src/suricata.h @@ -176,6 +176,7 @@ typedef struct SCInstance_ { const char *progname; /**< pointer to argv[0] */ const char *conf_filename; const char **additional_configs; + const char **additional_plugins; char *strict_rule_parsing_string; const char *capture_plugin_name; diff --git a/src/util-plugin.c b/src/util-plugin.c index ac06ea8051..de55357d73 100644 --- a/src/util-plugin.c +++ b/src/util-plugin.c @@ -76,7 +76,7 @@ bool RegisterPlugin(SCPlugin *plugin, void *lib) return true; } -static void InitPlugin(char *path) +static void InitPlugin(const char *path) { void *lib = dlopen(path, RTLD_NOW); if (lib == NULL) { @@ -99,37 +99,48 @@ static void InitPlugin(char *path) } } -void SCPluginsLoad(const char *capture_plugin_name, const char *capture_plugin_args) +static void LoadPluginsFromPath(const char *plugin_path) { - SCConfNode *conf = SCConfGetNode("plugins"); - if (conf == NULL) { + struct stat statbuf; + if (stat(plugin_path, &statbuf) == -1) { + SCLogError("Bad plugin path: %s: %s", plugin_path, strerror(errno)); return; } - SCConfNode *plugin = NULL; - TAILQ_FOREACH(plugin, &conf->head, next) { - struct stat statbuf; - if (stat(plugin->val, &statbuf) == -1) { - SCLogError("Bad plugin path: %s: %s", plugin->val, strerror(errno)); - continue; + if (S_ISDIR(statbuf.st_mode)) { + // coverity[toctou : FALSE] + DIR *dir = opendir(plugin_path); + if (dir == NULL) { + SCLogError("Failed to open plugin directory %s: %s", plugin_path, strerror(errno)); + return; } - if (S_ISDIR(statbuf.st_mode)) { - // coverity[toctou : FALSE] - DIR *dir = opendir(plugin->val); - if (dir == NULL) { - SCLogError("Failed to open plugin directory %s: %s", plugin->val, strerror(errno)); - continue; + struct dirent *entry = NULL; + char path[PATH_MAX]; + while ((entry = readdir(dir)) != NULL) { + if (strstr(entry->d_name, ".so") != NULL) { + snprintf(path, sizeof(path), "%s/%s", plugin_path, entry->d_name); + InitPlugin(path); } - struct dirent *entry = NULL; - char path[PATH_MAX]; - while ((entry = readdir(dir)) != NULL) { - if (strstr(entry->d_name, ".so") != NULL) { - snprintf(path, sizeof(path), "%s/%s", plugin->val, entry->d_name); - InitPlugin(path); - } - } - closedir(dir); - } else { - InitPlugin(plugin->val); + } + closedir(dir); + } else { + InitPlugin(plugin_path); + } +} + +void SCPluginsLoad(const char *capture_plugin_name, const char *capture_plugin_args, + const char **additional_plugins) +{ + SCConfNode *conf = SCConfGetNode("plugins"); + if (conf != NULL) { + SCConfNode *plugin = NULL; + TAILQ_FOREACH (plugin, &conf->head, next) { + LoadPluginsFromPath(plugin->val); + } + } + + if (additional_plugins != NULL) { + for (int i = 0; additional_plugins[i] != NULL; i++) { + LoadPluginsFromPath(additional_plugins[i]); } } diff --git a/src/util-plugin.h b/src/util-plugin.h index f749e287a3..84f17fe190 100644 --- a/src/util-plugin.h +++ b/src/util-plugin.h @@ -20,7 +20,8 @@ #include "suricata-plugin.h" -void SCPluginsLoad(const char *capture_plugin_name, const char *capture_plugin_args); +void SCPluginsLoad(const char *capture_plugin_name, const char *capture_plugin_args, + const char **additional_plugins); SCCapturePlugin *SCPluginFindCaptureByName(const char *name); bool RegisterPlugin(SCPlugin *, void *);