plugins: initial support for a filetype plugin

A filetype plugin is a plugin that implements an eve filetype. Most
of the current filetypes could likely be implemented as such a plugin.
Such a plugin must implement Open, Close and Write, where Write
is provided the formatted JSON to be logged.

This commit also includes the plumbing for plugin loading. Example
plugin to come.

Plugins are loaded by the "plugin" section in the configuration
file:

  plugins:
    - /path/to/directory/plugins
    - /path/to/plugin_file.so

This can also be done on the command line with:

  --set plugins.0=/path/plugin_file.so
pull/5282/head
Jason Ish 5 years ago committed by Victor Julien
parent f35c25cef2
commit 8fb35236e6

@ -527,6 +527,7 @@ util-optimize.h \
util-pages.c util-pages.h \
util-path.c util-path.h \
util-pidfile.c util-pidfile.h \
util-plugin.c util-plugin.h \
util-pool.c util-pool.h \
util-pool-thread.c util-pool-thread.h \
util-prefilter.c util-prefilter.h \
@ -572,6 +573,9 @@ win32-misc.c win32-misc.h \
win32-service.c win32-service.h \
win32-syslog.h
include_HEADERS = \
suricata-plugin.h
EXTRA_DIST = tests
# set the include path found by configure

@ -60,6 +60,7 @@
#include "util-device.h"
#include "util-validate.h"
#include "util-crypt.h"
#include "util-plugin.h"
#include "flow-var.h"
#include "flow-bit.h"
@ -67,6 +68,8 @@
#include "source-pcap-file.h"
#include "suricata-plugin.h"
#define DEFAULT_LOG_FILENAME "eve.json"
#define DEFAULT_ALERT_SYSLOG_FACILITY_STR "local0"
#define DEFAULT_ALERT_SYSLOG_FACILITY LOG_LOCAL0
@ -1081,9 +1084,19 @@ OutputInitResult OutputJsonInitCtx(ConfNode *conf)
"redis JSON output option is not compiled");
#endif
} else {
SCLogError(SC_ERR_INVALID_ARGUMENT,
"Invalid JSON output option: %s", output_s);
exit(EXIT_FAILURE);
#ifdef HAVE_PLUGINS
SCPluginFileType *plugin = SCPluginFindFileType(output_s);
if (plugin == NULL) {
FatalError(SC_ERR_INVALID_ARGUMENT,
"Invalid JSON output option: %s", output_s);
} else {
json_ctx->json_out = LOGFILE_TYPE_PLUGIN;
json_ctx->plugin = plugin;
}
#else
FatalError(SC_ERR_INVALID_ARGUMENT,
"Invalid JSON output option: %s", output_s);
#endif
}
}
@ -1177,6 +1190,19 @@ OutputInitResult OutputJsonInitCtx(ConfNode *conf)
}
}
#endif
else if (json_ctx->json_out == LOGFILE_TYPE_PLUGIN) {
ConfNode *plugin_conf = ConfNodeLookupChild(conf,
json_ctx->plugin->name);
void *plugin_data = NULL;
if (json_ctx->plugin->Open(plugin_conf, &plugin_data) < 0) {
LogFileFreeCtx(json_ctx->file_ctx);
SCFree(json_ctx);
SCFree(output_ctx);
} else {
json_ctx->file_ctx->plugin = json_ctx->plugin;
json_ctx->file_ctx->plugin_data = plugin_data;
}
}
const char *sensor_id_s = ConfNodeLookupChildValue(conf, "sensor-id");
if (sensor_id_s != NULL) {

@ -31,6 +31,7 @@
#include "rust.h"
#include "app-layer-htp-xff.h"
#include "suricata-plugin.h"
void OutputJsonRegister(void);
@ -102,6 +103,7 @@ typedef struct OutputJsonCtx_ {
enum LogFileType json_out;
OutputJsonCommonSettings cfg;
HttpXFFCfg *xff_cfg;
SCPluginFileType *plugin;
} OutputJsonCtx;
typedef struct OutputJsonThreadCtx_ {

@ -492,13 +492,17 @@ typedef enum {
} LoggerId;
#include "util-optimize.h"
#ifndef SURICATA_PLUGIN
#include <htp/htp.h>
#endif
#include "threads.h"
#include "tm-threads-common.h"
#include "util-debug.h"
#include "util-error.h"
#include "util-mem.h"
#ifndef SURICATA_PLUGIN
#include "detect-engine-alert.h"
#endif
#include "util-path.h"
#include "util-conf.h"

@ -0,0 +1,53 @@
/* Copyright (C) 2020 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#ifndef __SURICATA_PLUGIN_H__
#define __SURICATA_PLUGIN_H__
#include "autoconf.h"
#include <stdint.h>
#include <stdbool.h>
#include "conf.h"
/**
* Structure to define a Suricata plugin.
*/
typedef struct SCPlugin_ {
const char *name;
const char *license;
const char *author;
void (*Init)(void);
} SCPlugin;
/**
* Structure used to define a file type plugin.
*
* Currently only used by the Eve output type.
*/
typedef struct SCPluginFileType_ {
char *name;
int (*Open)(ConfNode *conf, void **data);
int (*Write)(const char *buffer, int buffer_len, void *ctx);
void (*Close)(void *ctx);
TAILQ_ENTRY(SCPluginFileType_) entries;
} SCPluginFileType;
bool SCPluginRegisterFileType(SCPluginFileType *);
#endif /* __SURICATA_PLUGIN_H */

@ -172,6 +172,8 @@
#include "util-lua.h"
#include "util-plugin.h"
#include "rust.h"
/*
@ -2548,6 +2550,8 @@ int PostConfLoadedSetup(SCInstance *suri)
FeatureTrackingRegister(); /* must occur prior to output mod registration */
RegisterAllModules();
SCPluginsLoad();
AppLayerHtpNeedFileInspection();
StorageFinalize();

@ -752,7 +752,11 @@ int LogFileFreeCtx(LogFileCtx *lf_ctx)
SCFree(lf_ctx->threads->append);
SCFree(lf_ctx->threads);
} else {
if (lf_ctx->fp != NULL) {
if (lf_ctx->type == LOGFILE_TYPE_PLUGIN) {
if (lf_ctx->plugin->Close != NULL) {
lf_ctx->plugin->Close(lf_ctx->plugin_data);
}
} else if (lf_ctx->fp != NULL) {
lf_ctx->Close(lf_ctx);
}
if (lf_ctx->parent) {
@ -803,6 +807,10 @@ int LogFileWrite(LogFileCtx *file_ctx, MemBuffer *buffer)
SCMutexUnlock(&file_ctx->fp_mutex);
}
#endif
else if (file_ctx->type == LOGFILE_TYPE_PLUGIN) {
file_ctx->plugin->Write((const char *)MEMBUFFER_BUFFER(buffer),
MEMBUFFER_OFFSET(buffer), file_ctx->plugin_data);
}
return 0;
}

@ -31,6 +31,7 @@
#include "util-log-redis.h"
#endif /* HAVE_LIBHIREDIS */
#include "suricata-plugin.h"
typedef struct {
uint16_t fileno;
@ -40,7 +41,8 @@ enum LogFileType { LOGFILE_TYPE_FILE,
LOGFILE_TYPE_SYSLOG,
LOGFILE_TYPE_UNIX_DGRAM,
LOGFILE_TYPE_UNIX_STREAM,
LOGFILE_TYPE_REDIS };
LOGFILE_TYPE_REDIS,
LOGFILE_TYPE_PLUGIN };
typedef struct SyslogSetup_ {
int alert_syslog_level;
@ -60,6 +62,7 @@ typedef struct LogFileCtx_ {
FILE *fp;
PcieFile *pcie_fp;
LogThreadedFileCtx *threads;
void *plugin_data;
#ifdef HAVE_LIBHIREDIS
void *redis;
#endif
@ -75,6 +78,8 @@ typedef struct LogFileCtx_ {
int (*Write)(const char *buffer, int buffer_len, struct LogFileCtx_ *fp);
void (*Close)(struct LogFileCtx_ *fp);
SCPluginFileType *plugin;
/** It will be locked if the log/alert
* record cannot be written to the file in one call */
SCMutex fp_mutex;

@ -0,0 +1,148 @@
/* Copyright (C) 2020 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "suricata-common.h"
#include "suricata-plugin.h"
#include "util-plugin.h"
#ifdef HAVE_PLUGINS
#include <dlfcn.h>
static TAILQ_HEAD(, SCPluginFileType_) output_types =
TAILQ_HEAD_INITIALIZER(output_types);
static void InitPlugin(char *path)
{
void *lib = dlopen(path, RTLD_NOW);
if (lib == NULL) {
SCLogNotice("Failed to open %s as a plugin: %s", path, dlerror());
} else {
SCLogNotice("Loading plugin %s", path);
SCPlugin *plugin = dlsym(lib, "PluginSpec");
if (plugin == NULL) {
SCLogError(SC_ERR_PLUGIN, "Plugin does not export a plugin specification: %s", path);
} else {
BUG_ON(plugin->name == NULL);
BUG_ON(plugin->author == NULL);
BUG_ON(plugin->license == NULL);
BUG_ON(plugin->Init == NULL);
SCLogNotice("Initializing plugin %s; author=%s; license=%s",
plugin->name, plugin->author, plugin->license);
(*plugin->Init)();
}
}
}
void SCPluginsLoad(void)
{
ConfNode *conf = ConfGetNode("plugins");
if (conf == NULL) {
return;
}
ConfNode *plugin = NULL;
TAILQ_FOREACH(plugin, &conf->head, next) {
struct stat statbuf;
if (stat(plugin->val, &statbuf) == -1) {
SCLogError(SC_ERR_STAT, "Bad plugin path: %s: %s",
plugin->val, strerror(errno));
continue;
}
if (S_ISDIR(statbuf.st_mode)) {
DIR *dir = opendir(plugin->val);
if (dir == NULL) {
SCLogError(SC_ERR_DIR_OPEN, "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->val, entry->d_name);
InitPlugin(path);
}
}
free(dir);
} else {
InitPlugin(plugin->val);
}
}
}
/**
* \brief Register an Eve/JSON file type plugin.
*
* \retval true if registered successfully, false if the plugin name
* conflicts with a built-in or previously registered
* plugin file type.
*
* TODO: As this is Eve specific, perhaps Eve should be in the filename.
*/
bool SCPluginRegisterFileType(SCPluginFileType *plugin)
{
const char *builtin[] = {
"regular",
"syslog",
"unix_dgram",
"unix_stream",
"redis",
NULL,
};
for (int i = 0;; i++) {
if (builtin[i] == NULL) {
break;
}
if (strcmp(builtin[i], plugin->name) == 0) {
SCLogNotice("Eve filetype plugin name \"%s\" conflicts "
"with built-in name", plugin->name);
return false;
}
}
SCPluginFileType *existing = NULL;
TAILQ_FOREACH(existing, &output_types, entries) {
if (strcmp(existing->name, plugin->name) == 0) {
SCLogNotice("Eve filetype plugin name conflicts with previously "
"registered plugin: %s", plugin->name);
return false;
}
}
SCLogNotice("Registering JSON file type plugin %s", plugin->name);
TAILQ_INSERT_TAIL(&output_types, plugin, entries);
return true;
}
SCPluginFileType *SCPluginFindFileType(const char *name)
{
SCPluginFileType *plugin = NULL;
TAILQ_FOREACH(plugin, &output_types, entries) {
if (strcmp(name, plugin->name) == 0) {
return plugin;
}
}
return NULL;
}
#else
void PluginsLoad(void)
{
}
#endif

@ -0,0 +1,26 @@
/* Copyright (C) 2020 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#ifndef __UTIL_PLUGIN_H__
#define __UTIL_PLUGIN_H__
#include "suricata-plugin.h"
void SCPluginsLoad(void);
SCPluginFileType *SCPluginFindFileType(const char *name);
#endif /* __UTIL_PLUGIN_H__ */
Loading…
Cancel
Save