1# Copyright (C) 2017 Open Information Security Foundation
2# Copyright (c) 2015-2017 Jason Ish
3#
4# You can copy, redistribute or modify this Program under the terms of
5# the GNU General Public License version 2 as published by the Free
6# Software Foundation.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# version 2 along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16# 02110-1301, USA.
17
18import os.path
19import logging
20
21import yaml
22
23import suricata.update.engine
24from suricata.update.exceptions import ApplicationError
25
26try:
27    from suricata.config import defaults
28    has_defaults = True
29except:
30    has_defaults = False
31
32logger = logging.getLogger()
33
34DEFAULT_DATA_DIRECTORY = "/var/lib/suricata"
35
36# Cache directory - relative to the data directory.
37CACHE_DIRECTORY = os.path.join("update", "cache")
38
39# Source directory - relative to the data directory.
40SOURCE_DIRECTORY = os.path.join("update", "sources")
41
42# Configuration keys.
43DATA_DIRECTORY_KEY = "data-directory"
44CACHE_DIRECTORY_KEY = "cache-directory"
45IGNORE_KEY = "ignore"
46DISABLE_CONF_KEY = "disable-conf"
47ENABLE_CONF_KEY = "enable-conf"
48MODIFY_CONF_KEY = "modify-conf"
49DROP_CONF_KEY = "drop-conf"
50LOCAL_CONF_KEY = "local"
51OUTPUT_KEY = "output"
52DIST_RULE_DIRECTORY_KEY = "dist-rule-directory"
53
54if has_defaults:
55    DEFAULT_UPDATE_YAML_PATH = os.path.join(defaults.sysconfdir, "update.yaml")
56else:
57    DEFAULT_UPDATE_YAML_PATH = "/etc/suricata/update.yaml"
58
59DEFAULT_SURICATA_YAML_PATH = [
60    "/etc/suricata/suricata.yaml",
61    "/usr/local/etc/suricata/suricata.yaml",
62    "/etc/suricata/suricata-debian.yaml"
63]
64
65if has_defaults:
66    DEFAULT_DIST_RULE_PATH = [
67        defaults.datarulesdir,
68        "/etc/suricata/rules",
69    ]
70else:
71    DEFAULT_DIST_RULE_PATH = [
72        "/etc/suricata/rules",
73    ]
74
75DEFAULT_CONFIG = {
76    "disable-conf": "/etc/suricata/disable.conf",
77    "enable-conf": "/etc/suricata/enable.conf",
78    "drop-conf": "/etc/suricata/drop.conf",
79    "modify-conf": "/etc/suricata/modify.conf",
80    "sources": [],
81    LOCAL_CONF_KEY: [],
82
83    # The default file patterns to ignore.
84    "ignore": [
85        "*deleted.rules",
86    ],
87}
88
89_args = None
90_config = {}
91
92# The filename the config was read from, if any.
93filename = None
94
95def has(key):
96    """Return true if a configuration key exists."""
97    return key in _config
98
99def set(key, value):
100    """Set a configuration value."""
101    _config[key] = value
102
103def get(key):
104    """Get a configuration value."""
105    if key in _config:
106        return _config[key]
107    return None
108
109def set_state_dir(directory):
110    _config[DATA_DIRECTORY_KEY] = directory
111
112def get_state_dir():
113    """Get the data directory. This is more of the Suricata state
114    directory than a specific Suricata-Update directory, and is used
115    as the root directory for Suricata-Update data.
116    """
117    if os.getenv("DATA_DIRECTORY"):
118        return os.getenv("DATA_DIRECTORY")
119    if DATA_DIRECTORY_KEY in _config:
120        return _config[DATA_DIRECTORY_KEY]
121    return DEFAULT_DATA_DIRECTORY
122
123def set_cache_dir(directory):
124    """Set an alternate cache directory."""
125    _config[CACHE_DIRECTORY_KEY] = directory
126
127def get_cache_dir():
128    """Get the cache directory."""
129    if CACHE_DIRECTORY_KEY in _config:
130        return _config[CACHE_DIRECTORY_KEY]
131    return os.path.join(get_state_dir(), CACHE_DIRECTORY)
132
133def get_output_dir():
134    """Get the rule output directory."""
135    if OUTPUT_KEY in _config:
136        return _config[OUTPUT_KEY]
137    return os.path.join(get_state_dir(), "rules")
138
139def args():
140    """Return sthe parsed argument object."""
141    return _args
142
143def get_arg(key):
144    key = key.replace("-", "_")
145    if hasattr(_args, key):
146        val = getattr(_args, key)
147        if val not in [[], None]:
148            return val
149    return None
150
151def init(args):
152    global _args
153    global filename
154
155    _args = args
156    _config.update(DEFAULT_CONFIG)
157
158    if args.config:
159        logger.info("Loading %s", args.config)
160        with open(args.config, "rb") as fileobj:
161            config = yaml.safe_load(fileobj)
162            if config:
163                _config.update(config)
164                filename = args.config
165    elif os.path.exists(DEFAULT_UPDATE_YAML_PATH):
166        logger.info("Loading %s", DEFAULT_UPDATE_YAML_PATH)
167        with open(DEFAULT_UPDATE_YAML_PATH, "rb") as fileobj:
168            config = yaml.safe_load(fileobj)
169            if config:
170                _config.update(config)
171                filename = DEFAULT_UPDATE_YAML_PATH
172
173    # Apply command line arguments to the config.
174    for arg in vars(args):
175        if arg == "local":
176            for local in args.local:
177                logger.debug("Adding local ruleset to config: %s", local)
178                _config[LOCAL_CONF_KEY].append(local)
179        elif arg == "data_dir" and args.data_dir:
180            logger.debug("Setting data directory to %s", args.data_dir)
181            _config[DATA_DIRECTORY_KEY] = args.data_dir
182        elif getattr(args, arg) is not None:
183            key = arg.replace("_", "-")
184            val = getattr(args, arg)
185            logger.debug("Setting configuration value %s -> %s", key, val)
186            _config[key] = val
187
188    # Find and set the path to suricata if not provided.
189    if "suricata" in _config:
190        if not os.path.exists(_config["suricata"]):
191            raise ApplicationError(
192                "Configured path to suricata does not exist: %s" % (
193                    _config["suricata"]))
194    else:
195        suricata_path = suricata.update.engine.get_path()
196        if not suricata_path:
197            logger.warning("No suricata application binary found on path.")
198        else:
199            _config["suricata"] = suricata_path
200
201    if "suricata" in _config:
202        build_info = suricata.update.engine.get_build_info(_config["suricata"])
203
204        # Set the first suricata.yaml to check for to the one in the
205        # --sysconfdir provided by build-info.
206        if not "suricata_conf" in _config and "sysconfdir" in build_info:
207            DEFAULT_SURICATA_YAML_PATH.insert(
208                0, os.path.join(
209                    build_info["sysconfdir"], "suricata/suricata.yaml"))
210
211        # Amend the path to look for Suricata provided rules based on
212        # the build info. As we are inserting at the front, put the
213        # highest priority path last.
214        if "sysconfdir" in build_info:
215            DEFAULT_DIST_RULE_PATH.insert(
216                0, os.path.join(build_info["sysconfdir"], "suricata/rules"))
217        if "datarootdir" in build_info:
218            DEFAULT_DIST_RULE_PATH.insert(
219                0, os.path.join(build_info["datarootdir"], "suricata/rules"))
220
221        # Set the data-directory prefix to that of the --localstatedir
222        # found in the build-info.
223        if not DATA_DIRECTORY_KEY in _config and "localstatedir" in build_info:
224            data_directory = os.path.join(
225                build_info["localstatedir"], "lib/suricata")
226            logger.info("Using data-directory %s.", data_directory)
227            _config[DATA_DIRECTORY_KEY] = data_directory
228
229    # If suricata-conf not provided on the command line or in the
230    # configuration file, look for it.
231    if not "suricata-conf" in _config:
232        for conf in DEFAULT_SURICATA_YAML_PATH:
233            if os.path.exists(conf):
234                logger.info("Using Suricata configuration %s" % (conf))
235                _config["suricata-conf"] = conf
236                break
237
238    if not DIST_RULE_DIRECTORY_KEY in _config:
239        for path in DEFAULT_DIST_RULE_PATH:
240            if os.path.exists(path):
241                logger.info("Using %s for Suricata provided rules.", path)
242                _config[DIST_RULE_DIRECTORY_KEY] = path
243                break
244