# Copyright (C) 2017 Open Information Security Foundation # Copyright (c) 2015-2017 Jason Ish # # 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 import yaml import suricata.update.engine from suricata.update.exceptions import ApplicationError try: from suricata.config import defaults has_defaults = True except: has_defaults = False logger = logging.getLogger() DEFAULT_DATA_DIRECTORY = "/var/lib/suricata" # Cache directory - relative to the data directory. CACHE_DIRECTORY = os.path.join("update", "cache") # Source directory - relative to the data directory. SOURCE_DIRECTORY = os.path.join("update", "sources") # Configuration keys. DATA_DIRECTORY_KEY = "data-directory" CACHE_DIRECTORY_KEY = "cache-directory" IGNORE_KEY = "ignore" DISABLE_CONF_KEY = "disable-conf" ENABLE_CONF_KEY = "enable-conf" MODIFY_CONF_KEY = "modify-conf" DROP_CONF_KEY = "drop-conf" LOCAL_CONF_KEY = "local" OUTPUT_KEY = "output" DIST_RULE_DIRECTORY_KEY = "dist-rule-directory" if has_defaults: DEFAULT_UPDATE_YAML_PATH = os.path.join(defaults.sysconfdir, "update.yaml") else: DEFAULT_UPDATE_YAML_PATH = "/etc/suricata/update.yaml" DEFAULT_SURICATA_YAML_PATH = [ "/etc/suricata/suricata.yaml", "/usr/local/etc/suricata/suricata.yaml", "/etc/suricata/suricata-debian.yaml" ] if has_defaults: DEFAULT_DIST_RULE_PATH = [ defaults.datarulesdir, "/etc/suricata/rules", ] else: DEFAULT_DIST_RULE_PATH = [ "/etc/suricata/rules", ] DEFAULT_CONFIG = { "sources": [], LOCAL_CONF_KEY: [], # The default file patterns to ignore. "ignore": [ "*deleted.rules", ], } _args = None _config = {} # The filename the config was read from, if any. filename = None def has(key): """Return true if a configuration key exists.""" return key in _config def set(key, value): """Set a configuration value.""" _config[key] = value def get(key): """Get a configuration value.""" if key in _config: return _config[key] return None def set_state_dir(directory): _config[DATA_DIRECTORY_KEY] = directory def get_state_dir(): """Get the data directory. This is more of the Suricata state directory than a specific Suricata-Update directory, and is used as the root directory for Suricata-Update data. """ if os.getenv("DATA_DIRECTORY"): return os.getenv("DATA_DIRECTORY") if DATA_DIRECTORY_KEY in _config: return _config[DATA_DIRECTORY_KEY] return DEFAULT_DATA_DIRECTORY def set_cache_dir(directory): """Set an alternate cache directory.""" _config[CACHE_DIRECTORY_KEY] = directory def get_cache_dir(): """Get the cache directory.""" if CACHE_DIRECTORY_KEY in _config: return _config[CACHE_DIRECTORY_KEY] return os.path.join(get_state_dir(), CACHE_DIRECTORY) def get_output_dir(): """Get the rule output directory.""" if OUTPUT_KEY in _config: return _config[OUTPUT_KEY] return os.path.join(get_state_dir(), "rules") def args(): """Return sthe parsed argument object.""" return _args def get_arg(key): key = key.replace("-", "_") if hasattr(_args, key): val = getattr(_args, key) if val not in [[], None]: return val return None def init(args): global _args global filename _args = args _config.update(DEFAULT_CONFIG) if args.config: logger.info("Loading %s", args.config) with open(args.config, "rb") as fileobj: config = yaml.safe_load(fileobj) if config: _config.update(config) filename = args.config elif os.path.exists(DEFAULT_UPDATE_YAML_PATH): logger.info("Loading %s", DEFAULT_UPDATE_YAML_PATH) with open(DEFAULT_UPDATE_YAML_PATH, "rb") as fileobj: config = yaml.safe_load(fileobj) if config: _config.update(config) filename = DEFAULT_UPDATE_YAML_PATH # Apply command line arguments to the config. for arg in vars(args): if arg == "local": for local in args.local: logger.debug("Adding local ruleset to config: %s", local) _config[LOCAL_CONF_KEY].append(local) elif arg == "data_dir" and args.data_dir: logger.debug("Setting data directory to %s", args.data_dir) _config[DATA_DIRECTORY_KEY] = args.data_dir elif getattr(args, arg) is not None: key = arg.replace("_", "-") val = getattr(args, arg) logger.debug("Setting configuration value %s -> %s", key, val) _config[key] = val # Find and set the path to suricata if not provided. if "suricata" in _config: if not os.path.exists(_config["suricata"]): raise ApplicationError( "Configured path to suricata does not exist: %s" % ( _config["suricata"])) else: suricata_path = suricata.update.engine.get_path() if not suricata_path: logger.warning("No suricata application binary found on path.") else: _config["suricata"] = suricata_path if "suricata" in _config: build_info = suricata.update.engine.get_build_info(_config["suricata"]) # Set the first suricata.yaml to check for to the one in the # --sysconfdir provided by build-info. if not "suricata_conf" in _config and "sysconfdir" in build_info: DEFAULT_SURICATA_YAML_PATH.insert( 0, os.path.join( build_info["sysconfdir"], "suricata/suricata.yaml")) # Amend the path to look for Suricata provided rules based on # the build info. As we are inserting at the front, put the # highest priority path last. if "sysconfdir" in build_info: DEFAULT_DIST_RULE_PATH.insert( 0, os.path.join(build_info["sysconfdir"], "suricata/rules")) if "datarootdir" in build_info: DEFAULT_DIST_RULE_PATH.insert( 0, os.path.join(build_info["datarootdir"], "suricata/rules")) # Set the data-directory prefix to that of the --localstatedir # found in the build-info. if not DATA_DIRECTORY_KEY in _config and "localstatedir" in build_info: data_directory = os.path.join( build_info["localstatedir"], "lib/suricata") logger.info("Using data-directory %s.", data_directory) _config[DATA_DIRECTORY_KEY] = data_directory # Fixup the default locations for Suricata-Update configuration files, but only if # they exist, otherwise keep the defaults. conf_search_path = ["/etc"] if "sysconfdir" in build_info: sysconfdir = build_info["sysconfdir"] if not sysconfdir in conf_search_path: conf_search_path.insert(0, sysconfdir) configs = ( ("disable-conf", "disable.conf"), ("enable-conf", "enable.conf"), ("drop-conf", "drop.conf"), ("modify-conf", "modify.conf"), ) for key, filename in configs: if getattr(args, key.replace("-", "_"), None) is not None: continue if _config.get(key) is not None: continue for conf_dir in conf_search_path: config_path = os.path.join(conf_dir, "suricata", filename) logger.debug("Looking for {}".format(config_path)) if os.path.exists(config_path): logger.debug("Found {}".format(config_path)) logger.debug("Using {} for {}".format(config_path, key)) _config[key] = config_path break # If suricata-conf not provided on the command line or in the # configuration file, look for it. if not "suricata-conf" in _config: for conf in DEFAULT_SURICATA_YAML_PATH: if os.path.exists(conf): logger.info("Using Suricata configuration %s" % (conf)) _config["suricata-conf"] = conf break if not DIST_RULE_DIRECTORY_KEY in _config: for path in DEFAULT_DIST_RULE_PATH: if os.path.exists(path): logger.info("Using %s for Suricata provided rules.", path) _config[DIST_RULE_DIRECTORY_KEY] = path break