summaryrefslogtreecommitdiffstats
path: root/suricata/update/commands
diff options
context:
space:
mode:
Diffstat (limited to 'suricata/update/commands')
-rw-r--r--suricata/update/commands/__init__.py23
-rw-r--r--suricata/update/commands/addsource.py72
-rw-r--r--suricata/update/commands/checkversions.py83
-rw-r--r--suricata/update/commands/disablesource.py40
-rw-r--r--suricata/update/commands/enablesource.py162
-rw-r--r--suricata/update/commands/listsources.py116
-rw-r--r--suricata/update/commands/removesource.py49
-rw-r--r--suricata/update/commands/updatesources.py105
8 files changed, 650 insertions, 0 deletions
diff --git a/suricata/update/commands/__init__.py b/suricata/update/commands/__init__.py
new file mode 100644
index 0000000..e75c80a
--- /dev/null
+++ b/suricata/update/commands/__init__.py
@@ -0,0 +1,23 @@
+# Copyright (C) 2017 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.
+
+from suricata.update.commands import addsource
+from suricata.update.commands import listsources
+from suricata.update.commands import updatesources
+from suricata.update.commands import enablesource
+from suricata.update.commands import disablesource
+from suricata.update.commands import removesource
+from suricata.update.commands import checkversions
diff --git a/suricata/update/commands/addsource.py b/suricata/update/commands/addsource.py
new file mode 100644
index 0000000..a87095c
--- /dev/null
+++ b/suricata/update/commands/addsource.py
@@ -0,0 +1,72 @@
+# Copyright (C) 2017 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.
+
+from __future__ import print_function
+
+import logging
+
+from suricata.update import config
+from suricata.update import sources
+
+try:
+ input = raw_input
+except:
+ pass
+
+logger = logging.getLogger()
+
+
+def register(parser):
+ parser.add_argument("name", metavar="<name>", nargs="?",
+ help="Name of source")
+ parser.add_argument("url", metavar="<url>", nargs="?", help="Source URL")
+ parser.add_argument("--http-header", metavar="<http-header>",
+ help="Additional HTTP header to add to requests")
+ parser.add_argument("--no-checksum", action="store_false",
+ help="Skips downloading the checksum URL")
+ parser.set_defaults(func=add_source)
+
+
+def add_source():
+ args = config.args()
+
+ if args.name:
+ name = args.name
+ else:
+ while True:
+ name = input("Name of source: ").strip()
+ if name:
+ break
+
+ if sources.source_name_exists(name):
+ logger.error("A source with name %s already exists.", name)
+ return 1
+
+ if args.url:
+ url = args.url
+ else:
+ while True:
+ url = input("URL: ").strip()
+ if url:
+ break
+
+ checksum = args.no_checksum
+
+ header = args.http_header if args.http_header else None
+
+ source_config = sources.SourceConfiguration(
+ name, header=header, url=url, checksum=checksum)
+ sources.save_source_config(source_config)
diff --git a/suricata/update/commands/checkversions.py b/suricata/update/commands/checkversions.py
new file mode 100644
index 0000000..3492317
--- /dev/null
+++ b/suricata/update/commands/checkversions.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2019 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.
+
+import os.path
+import logging
+from suricata.update import sources, engine
+
+logger = logging.getLogger()
+
+
+def is_gt(v1, v2):
+ if v1.full == v2.full:
+ return False
+
+ if v1.major < v2.major:
+ return False
+ elif v1.major > v2.major:
+ return True
+
+ if v1.minor < v2.minor:
+ return False
+ elif v1.minor > v2.minor:
+ return True
+
+ if v1.patch < v2.patch:
+ return False
+
+ return True
+
+
+def register(parser):
+ parser.set_defaults(func=check_version)
+
+
+def check_version(suricata_version):
+ if "dev" in suricata_version.full:
+ logger.warning("Development version of Suricata found: %s. "
+ "Skipping version check.", suricata_version.full)
+ return
+
+ index_filename = sources.get_index_filename()
+ if not os.path.exists(index_filename):
+ logger.warning("No index exists, will use bundled index.")
+ logger.warning("Please run suricata-update update-sources.")
+ index = sources.Index(index_filename)
+ version = index.get_versions()
+ recommended = engine.parse_version(version["suricata"]["recommended"])
+ if not recommended:
+ logger.error("Recommended version was not parsed properly")
+ sys.exit(1)
+ # In case index is out of date
+ if is_gt(suricata_version, recommended):
+ return
+ # Evaluate if the installed version is present in index
+ upgrade_version = version["suricata"].get(suricata_version.short)
+ if not upgrade_version:
+ logger.warning("Suricata version %s has reached EOL. Please upgrade to %s.",
+ suricata_version.full, recommended.full)
+ return
+ if suricata_version.full == upgrade_version:
+ logger.info("Suricata version %s is up to date", suricata_version.full)
+ elif upgrade_version == recommended.full:
+ logger.warning(
+ "Suricata version %s is outdated. Please upgrade to %s.",
+ suricata_version.full, recommended.full)
+ else:
+ logger.warning(
+ "Suricata version %s is outdated. Please upgrade to %s or %s.",
+ suricata_version.full, upgrade_version, recommended.full)
+
diff --git a/suricata/update/commands/disablesource.py b/suricata/update/commands/disablesource.py
new file mode 100644
index 0000000..6a64a7b
--- /dev/null
+++ b/suricata/update/commands/disablesource.py
@@ -0,0 +1,40 @@
+# Copyright (C) 2017 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.
+
+from __future__ import print_function
+
+import os
+import logging
+
+from suricata.update import config
+from suricata.update import sources
+
+logger = logging.getLogger()
+
+def register(parser):
+ parser.add_argument("name")
+ parser.set_defaults(func=disable_source)
+
+def disable_source():
+ name = config.args().name
+ filename = sources.get_enabled_source_filename(name)
+ if not os.path.exists(filename):
+ logger.debug("Filename %s does not exist.", filename)
+ logger.warning("Source %s is not enabled.", name)
+ return 0
+ logger.debug("Renaming %s to %s.disabled.", filename, filename)
+ os.rename(filename, "%s.disabled" % (filename))
+ logger.info("Source %s has been disabled", name)
diff --git a/suricata/update/commands/enablesource.py b/suricata/update/commands/enablesource.py
new file mode 100644
index 0000000..53bb68a
--- /dev/null
+++ b/suricata/update/commands/enablesource.py
@@ -0,0 +1,162 @@
+# Copyright (C) 2017 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.
+
+from __future__ import print_function
+
+import os
+import logging
+
+import yaml
+
+from suricata.update import config
+from suricata.update import sources
+
+try:
+ input = raw_input
+except:
+ pass
+
+logger = logging.getLogger()
+
+default_source = "et/open"
+
+def register(parser):
+ parser.add_argument("name")
+ parser.add_argument("params", nargs="*", metavar="param=val")
+ parser.set_defaults(func=enable_source)
+
+def enable_source():
+ name = config.args().name
+ update_params = False
+
+ # Check if source is already enabled.
+ enabled_source_filename = sources.get_enabled_source_filename(name)
+ if os.path.exists(enabled_source_filename):
+ logger.warning("The source %s is already enabled.", name)
+ update_params = True
+
+ # First check if this source was previous disabled and then just
+ # re-enable it.
+ disabled_source_filename = sources.get_disabled_source_filename(name)
+ if os.path.exists(disabled_source_filename):
+ logger.info("Re-enabling previously disabled source for %s.", name)
+ os.rename(disabled_source_filename, enabled_source_filename)
+ update_params = True
+
+ if not os.path.exists(sources.get_index_filename()):
+ logger.warning("Source index does not exist, will use bundled one.")
+ logger.warning("Please run suricata-update update-sources.")
+
+ source_index = sources.load_source_index(config)
+
+ if not name in source_index.get_sources() and not name in sources.get_sources_from_dir():
+ logger.error("Unknown source: %s", name)
+ return 1
+
+ # Parse key=val options.
+ opts = {}
+ for param in config.args().params:
+ key, val = param.split("=", 1)
+ opts[key] = val
+
+ params = {}
+ if update_params:
+ source = yaml.safe_load(open(sources.get_enabled_source_filename(name), "rb"))
+ else:
+ source = source_index.get_sources()[name]
+
+ if "params" in source:
+ params = source["params"]
+ for old_param in source["params"]:
+ if old_param in opts and source["params"][old_param] != opts[old_param]:
+ logger.info("Updating source parameter '%s': '%s' -> '%s'." % (
+ old_param, source["params"][old_param], opts[old_param]))
+ params[old_param] = opts[old_param]
+
+ if "subscribe-url" in source:
+ print("The source %s requires a subscription. Subscribe here:" % (name))
+ print(" %s" % source["subscribe-url"])
+
+ if "parameters" in source:
+ for param in source["parameters"]:
+ if param in opts:
+ params[param] = opts[param]
+ else:
+ prompt = source["parameters"][param]["prompt"]
+ while True:
+ r = input("%s (%s): " % (prompt, param))
+ r = r.strip()
+ if r:
+ break
+ params[param] = r.strip()
+
+ if "checksum" in source:
+ checksum = source["checksum"]
+ else:
+ checksum = source.get("checksum", True)
+
+ new_source = sources.SourceConfiguration(
+ name, params=params, checksum=checksum)
+
+ # If the source directory does not exist, create it. Also create
+ # the default rule-source of et/open, unless the source being
+ # enabled replaces it.
+ source_directory = sources.get_source_directory()
+ if not os.path.exists(source_directory):
+ try:
+ logger.info("Creating directory %s", source_directory)
+ os.makedirs(source_directory)
+ except Exception as err:
+ logger.error(
+ "Failed to create directory %s: %s", source_directory, err)
+ return 1
+
+ if "replaces" in source and default_source in source["replaces"]:
+ logger.debug(
+ "Not enabling default source as selected source replaces it")
+ elif new_source.name == default_source:
+ logger.debug(
+ "Not enabling default source as selected source is the default")
+ else:
+ logger.info("Enabling default source %s", default_source)
+ if not source_index.get_source_by_name(default_source):
+ logger.error("Default source %s not in index", default_source)
+ else:
+ default_source_config = sources.SourceConfiguration(
+ default_source)
+ write_source_config(default_source_config, True)
+
+ write_source_config(new_source, True)
+ logger.info("Source %s enabled", new_source.name)
+
+ if "replaces" in source:
+ for replaces in source["replaces"]:
+ filename = sources.get_enabled_source_filename(replaces)
+ if os.path.exists(filename):
+ logger.info(
+ "Removing source %s as its replaced by %s", replaces,
+ new_source.name)
+ logger.debug("Deleting %s", filename)
+ os.unlink(filename)
+
+def write_source_config(config, enabled):
+ if enabled:
+ filename = sources.get_enabled_source_filename(config.name)
+ else:
+ filename = sources.get_disabled_source_filename(config.name)
+ with open(filename, "w") as fileobj:
+ logger.debug("Writing %s", filename)
+ fileobj.write(yaml.safe_dump(config.dict(), default_flow_style=False))
diff --git a/suricata/update/commands/listsources.py b/suricata/update/commands/listsources.py
new file mode 100644
index 0000000..d35c3cd
--- /dev/null
+++ b/suricata/update/commands/listsources.py
@@ -0,0 +1,116 @@
+# Copyright (C) 2017 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.
+
+from __future__ import print_function
+
+import logging
+
+from suricata.update import config
+from suricata.update import sources
+from suricata.update import util
+from suricata.update import exceptions
+
+logger = logging.getLogger()
+
+def register(parser):
+ parser.add_argument("--free", action="store_true",
+ default=False, help="List all freely available sources")
+ parser.add_argument("--enabled", action="store_true",
+ help="List all enabled sources")
+ parser.add_argument("--all", action="store_true",
+ help="List all sources (including deprecated and obsolete)")
+ parser.set_defaults(func=list_sources)
+
+def list_sources():
+ enabled = config.args().enabled or \
+ config.args().subcommand == "list-enabled-sources"
+
+ if enabled:
+ found = False
+
+ # First list sources from the main config.
+ config_sources = config.get("sources")
+ if config_sources:
+ found = True
+ print("From %s:" % (config.filename))
+ for source in config_sources:
+ print(" - %s" % (source))
+
+ # And local files.
+ local = config.get("local")
+ if local:
+ found = True
+ print("Local files/directories:")
+ for filename in local:
+ print(" - %s" % (filename))
+
+ enabled_sources = sources.get_enabled_sources()
+ if enabled_sources:
+ found = True
+ print("Enabled sources:")
+ for source in enabled_sources.values():
+ print(" - %s" % (source["source"]))
+
+ # If no enabled sources were found, log it.
+ if not found:
+ logger.warning("No enabled sources.")
+ return 0
+
+ free_only = config.args().free
+ if not sources.source_index_exists(config):
+ logger.warning("Source index does not exist, will use bundled one.")
+ logger.warning("Please run suricata-update update-sources.")
+
+ index = sources.load_source_index(config)
+ for name, source in index.get_sources().items():
+ is_not_free = source.get("subscribe-url")
+ if free_only and is_not_free:
+ continue
+ if not config.args().all:
+ if source.get("deprecated") is not None or \
+ source.get("obsolete") is not None:
+ continue
+ print("%s: %s" % (util.bright_cyan("Name"), util.bright_magenta(name)))
+ print(" %s: %s" % (
+ util.bright_cyan("Vendor"), util.bright_magenta(source["vendor"])))
+ print(" %s: %s" % (
+ util.bright_cyan("Summary"), util.bright_magenta(source["summary"])))
+ print(" %s: %s" % (
+ util.bright_cyan("License"), util.bright_magenta(source["license"])))
+ if "tags" in source:
+ print(" %s: %s" % (
+ util.bright_cyan("Tags"),
+ util.bright_magenta(", ".join(source["tags"]))))
+ if "replaces" in source:
+ print(" %s: %s" % (
+ util.bright_cyan("Replaces"),
+ util.bright_magenta(", ".join(source["replaces"]))))
+ if "parameters" in source:
+ print(" %s: %s" % (
+ util.bright_cyan("Parameters"),
+ util.bright_magenta(", ".join(source["parameters"]))))
+ if "subscribe-url" in source:
+ print(" %s: %s" % (
+ util.bright_cyan("Subscription"),
+ util.bright_magenta(source["subscribe-url"])))
+ if "deprecated" in source:
+ print(" %s: %s" % (
+ util.orange("Deprecated"),
+ util.bright_magenta(source["deprecated"])))
+ if "obsolete" in source:
+ print(" %s: %s" % (
+ util.orange("Obsolete"),
+ util.bright_magenta(source["obsolete"])))
diff --git a/suricata/update/commands/removesource.py b/suricata/update/commands/removesource.py
new file mode 100644
index 0000000..f75d5ca
--- /dev/null
+++ b/suricata/update/commands/removesource.py
@@ -0,0 +1,49 @@
+# Copyright (C) 2017 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.
+
+from __future__ import print_function
+
+import os
+import logging
+
+from suricata.update import config
+from suricata.update import sources
+
+logger = logging.getLogger()
+
+def register(parser):
+ parser.add_argument("name")
+ parser.set_defaults(func=remove_source)
+
+def remove_source():
+ name = config.args().name
+
+ enabled_source_filename = sources.get_enabled_source_filename(name)
+ if os.path.exists(enabled_source_filename):
+ logger.debug("Deleting file %s.", enabled_source_filename)
+ os.remove(enabled_source_filename)
+ logger.info("Source %s removed, previously enabled.", name)
+ return 0
+
+ disabled_source_filename = sources.get_disabled_source_filename(name)
+ if os.path.exists(disabled_source_filename):
+ logger.debug("Deleting file %s.", disabled_source_filename)
+ os.remove(disabled_source_filename)
+ logger.info("Source %s removed, previously disabled.", name)
+ return 0
+
+ logger.warning("Source %s does not exist.", name)
+ return 1
diff --git a/suricata/update/commands/updatesources.py b/suricata/update/commands/updatesources.py
new file mode 100644
index 0000000..06a0d11
--- /dev/null
+++ b/suricata/update/commands/updatesources.py
@@ -0,0 +1,105 @@
+# Copyright (C) 2017 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.
+
+from __future__ import print_function
+
+import io
+import logging
+import os
+
+import yaml
+from suricata.update import config, exceptions, net, sources
+
+logger = logging.getLogger()
+
+
+def register(parser):
+ parser.set_defaults(func=update_sources)
+
+
+def get_initial_content():
+ initial_content = None
+ if os.path.exists(local_index_filename):
+ with open(local_index_filename, "r") as stream:
+ initial_content = yaml.safe_load(stream)
+ return initial_content
+
+
+def get_sources(before, after):
+ all_sources = {source: after[source]
+ for source in after if source not in before}
+ return all_sources
+
+
+def log_sources(sources_map):
+ for name, all_sources in sources_map.items():
+ if not all_sources:
+ continue
+ for source in all_sources:
+ logger.info("Source %s was %s", source, name)
+
+
+def compare_sources(initial_content, final_content):
+ if not initial_content:
+ logger.info("Adding all sources")
+ return
+ if initial_content == final_content:
+ logger.info("No change in sources")
+ return
+ initial_sources = initial_content.get("sources")
+ final_sources = final_content.get("sources")
+ added_sources = get_sources(before=initial_sources, after=final_sources)
+ removed_sources = get_sources(before=final_sources, after=initial_sources)
+ log_sources(sources_map={"added": added_sources,
+ "removed": removed_sources})
+ for source in set(initial_sources) & set(final_sources):
+ if initial_sources[source] != final_sources[source]:
+ logger.info("Source %s was changed", source)
+
+
+def write_and_compare(initial_content, fileobj):
+ try:
+ with open(local_index_filename, "wb") as outobj:
+ outobj.write(fileobj.getvalue())
+ except IOError as ioe:
+ logger.error("Failed to open directory: %s", ioe)
+ return 1
+ with open(local_index_filename, "rb") as stream:
+ final_content = yaml.safe_load(stream)
+ compare_sources(initial_content, final_content)
+ logger.info("Saved %s", local_index_filename)
+
+
+def update_sources():
+ global local_index_filename
+ local_index_filename = sources.get_index_filename()
+ initial_content = get_initial_content()
+ with io.BytesIO() as fileobj:
+ url = sources.get_source_index_url()
+ logger.info("Downloading %s", url)
+ try:
+ net.get(url, fileobj)
+ except Exception as err:
+ raise exceptions.ApplicationError(
+ "Failed to download index: %s: %s" % (url, err))
+ if not os.path.exists(config.get_cache_dir()):
+ try:
+ os.makedirs(config.get_cache_dir())
+ except Exception as err:
+ logger.error("Failed to create directory %s: %s",
+ config.get_cache_dir(), err)
+ return 1
+ write_and_compare(initial_content=initial_content, fileobj=fileobj)