]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
plugins: initial support for a filetype plugin
authorJason Ish <jason.ish@oisf.net>
Fri, 3 Jul 2020 22:33:12 +0000 (16:33 -0600)
committerVictor Julien <victor@inliniac.net>
Thu, 6 Aug 2020 15:15:10 +0000 (17:15 +0200)
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

src/Makefile.am
src/output-json.c
src/output-json.h
src/suricata-common.h
src/suricata-plugin.h [new file with mode: 0644]
src/suricata.c
src/util-logopenfile.c
src/util-logopenfile.h
src/util-plugin.c [new file with mode: 0644]
src/util-plugin.h [new file with mode: 0644]

index 70e11faf25bc9e43153ec68226052e7ccee53480..a9ec84e269b97a8f41848ef040d3a7689a624a06 100755 (executable)
@@ -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
index 09db2494d4d7602d8ffdd83618209108336582fb..83538777559dafd4c5842e458b959145b5554eff 100644 (file)
@@ -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) {
index c34efdf719fd4cbbaae6879b99d8a3af9021cd8f..cca913a0a97760572d51de5d890068ae47c2fda1 100644 (file)
@@ -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_ {
index a5468a096e6870b0ebbf2d67f1dce7bcb6bf88c1..4a6873cdf4355f5e161cecb4bcc4c482dbecb55b 100644 (file)
@@ -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"
 
diff --git a/src/suricata-plugin.h b/src/suricata-plugin.h
new file mode 100644 (file)
index 0000000..233c5d8
--- /dev/null
@@ -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 */
index 335710cacbb9103cbfde221d85152533dd88cc84..6c96302e03febd201630cd9a0d924c39faf31b26 100644 (file)
 
 #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();
index 58834bc193dba1530ed138022ed6acacb89a088e..feee704a2ccaff339e2d67ae82820276d7d5fc16 100644 (file)
@@ -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;
 }
index 3b32a02d752975bd113427e84c2323b41af16932..992c64f8d7e26332ea5598219691acd315a815d4 100644 (file)
@@ -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;
diff --git a/src/util-plugin.c b/src/util-plugin.c
new file mode 100644 (file)
index 0000000..a4c6501
--- /dev/null
@@ -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
diff --git a/src/util-plugin.h b/src/util-plugin.h
new file mode 100644 (file)
index 0000000..55c21e9
--- /dev/null
@@ -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__ */