instructions
This commit is contained in:
316
fail2ban-master/fail2ban/client/jailreader.py
Normal file
316
fail2ban-master/fail2ban/client/jailreader.py
Normal file
@@ -0,0 +1,316 @@
|
||||
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
|
||||
# vi: set ft=python sts=4 ts=4 sw=4 noet :
|
||||
|
||||
# This file is part of Fail2Ban.
|
||||
#
|
||||
# Fail2Ban is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fail2Ban 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
|
||||
# along with Fail2Ban; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
# Author: Cyril Jaquier
|
||||
#
|
||||
|
||||
__author__ = "Cyril Jaquier"
|
||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||
__license__ = "GPL"
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os.path
|
||||
import re
|
||||
|
||||
from .configreader import ConfigReaderUnshared, ConfigReader, NoSectionError
|
||||
from .filterreader import FilterReader
|
||||
from .actionreader import ActionReader
|
||||
from ..version import version
|
||||
from ..helpers import _merge_dicts, getLogger, extractOptions, splitWithOptions, splitwords
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
|
||||
|
||||
class NoJailError(ValueError):
|
||||
pass
|
||||
|
||||
class JailReader(ConfigReader):
|
||||
|
||||
def __init__(self, name, force_enable=False, **kwargs):
|
||||
ConfigReader.__init__(self, **kwargs)
|
||||
self.__name = name
|
||||
self.__filter = None
|
||||
self.__force_enable = force_enable
|
||||
self.__actions = list()
|
||||
self.__opts = None
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
return self.__opts
|
||||
|
||||
def setName(self, value):
|
||||
self.__name = value
|
||||
|
||||
def getName(self):
|
||||
return self.__name
|
||||
|
||||
def read(self):
|
||||
out = ConfigReader.read(self, "jail")
|
||||
# Before returning -- verify that requested section
|
||||
# exists at all
|
||||
if not (self.__name in self.sections()):
|
||||
raise NoJailError("Jail %r was not found among available"
|
||||
% self.__name)
|
||||
return out
|
||||
|
||||
def isEnabled(self):
|
||||
return self.__force_enable or (
|
||||
self.__opts and self.__opts.get("enabled", False))
|
||||
|
||||
@staticmethod
|
||||
def _glob(path):
|
||||
"""Given a path for glob return list of files to be passed to server.
|
||||
|
||||
Dangling symlinks are warned about and not returned
|
||||
"""
|
||||
pathList = []
|
||||
for p in glob.glob(path):
|
||||
if os.path.exists(p):
|
||||
pathList.append(p)
|
||||
else:
|
||||
logSys.warning("File %s is a dangling link, thus cannot be monitored" % p)
|
||||
return pathList
|
||||
|
||||
_configOpts1st = {
|
||||
"enabled": ["bool", False],
|
||||
"backend": ["string", "auto"],
|
||||
"filter": ["string", ""]
|
||||
}
|
||||
_configOpts = {
|
||||
"enabled": ["bool", False],
|
||||
"backend": ["string", "auto"],
|
||||
"maxretry": ["int", None],
|
||||
"maxmatches": ["int", None],
|
||||
"findtime": ["string", None],
|
||||
"bantime": ["string", None],
|
||||
"bantime.increment": ["bool", None],
|
||||
"bantime.factor": ["string", None],
|
||||
"bantime.formula": ["string", None],
|
||||
"bantime.multipliers": ["string", None],
|
||||
"bantime.maxtime": ["string", None],
|
||||
"bantime.rndtime": ["string", None],
|
||||
"bantime.overalljails": ["bool", None],
|
||||
"ignorecommand": ["string", None],
|
||||
"ignoreself": ["bool", None],
|
||||
"ignoreip": ["string", None],
|
||||
"ignorecache": ["string", None],
|
||||
"filter": ["string", ""],
|
||||
"logtimezone": ["string", None],
|
||||
"logencoding": ["string", None],
|
||||
"logpath": ["string", None],
|
||||
"skip_if_nologs": ["bool", False],
|
||||
"systemd_if_nologs": ["bool", True],
|
||||
"action": ["string", ""]
|
||||
}
|
||||
_configOpts.update(FilterReader._configOpts)
|
||||
|
||||
_ignoreOpts = set(
|
||||
['action', 'filter', 'enabled', 'backend', 'skip_if_nologs', 'systemd_if_nologs'] +
|
||||
list(FilterReader._configOpts.keys())
|
||||
)
|
||||
|
||||
def getOptions(self, addOpts=None):
|
||||
|
||||
basedir = self.getBaseDir()
|
||||
|
||||
# Before interpolation (substitution) add static options always available as default:
|
||||
self.merge_defaults({
|
||||
"fail2ban_version": version,
|
||||
"fail2ban_confpath": basedir
|
||||
})
|
||||
|
||||
try:
|
||||
|
||||
# Read first options only needed for merge defaults ('known/...' from filter):
|
||||
self.__opts = ConfigReader.getOptions(self, self.__name, self._configOpts1st,
|
||||
shouldExist=True)
|
||||
if not self.__opts: # pragma: no cover
|
||||
raise JailDefError("Init jail options failed")
|
||||
if addOpts:
|
||||
self.__opts = _merge_dicts(self.__opts, addOpts)
|
||||
|
||||
if not self.isEnabled():
|
||||
return True
|
||||
|
||||
# Read filter
|
||||
flt = self.__opts["filter"]
|
||||
if flt:
|
||||
try:
|
||||
filterName, filterOpt = extractOptions(flt)
|
||||
except ValueError as e:
|
||||
raise JailDefError("Invalid filter definition %r: %s" % (flt, e))
|
||||
if addOpts:
|
||||
filterOpt = _merge_dicts(filterOpt, addOpts)
|
||||
self.__filter = FilterReader(
|
||||
filterName, self.__name, filterOpt,
|
||||
share_config=self.share_config, basedir=basedir)
|
||||
ret = self.__filter.read()
|
||||
if not ret:
|
||||
raise JailDefError("Unable to read the filter %r" % filterName)
|
||||
# set backend-related options (logtype):
|
||||
self.__filter.applyAutoOptions(self.__opts.get('backend', ''))
|
||||
# merge options from filter as 'known/...' (all options unfiltered):
|
||||
self.__filter.getOptions(self.__opts, all=True)
|
||||
ConfigReader.merge_section(self, self.__name, self.__filter.getCombined(), 'known/')
|
||||
else:
|
||||
self.__filter = None
|
||||
logSys.warning("No filter set for jail %s" % self.__name)
|
||||
|
||||
# Read second all options (so variables like %(known/param) can be interpolated):
|
||||
self.__opts = ConfigReader.getOptions(self, self.__name, self._configOpts)
|
||||
if not self.__opts: # pragma: no cover
|
||||
raise JailDefError("Read jail options failed")
|
||||
|
||||
# cumulate filter options again (ignore given in jail):
|
||||
if self.__filter:
|
||||
self.__filter.getOptions(self.__opts)
|
||||
|
||||
# Read action
|
||||
for act in splitWithOptions(self.__opts["action"]):
|
||||
try:
|
||||
act = act.strip()
|
||||
if not act: # skip empty actions
|
||||
continue
|
||||
# join with previous line if needed (consider possible new-line):
|
||||
try:
|
||||
actName, actOpt = extractOptions(act)
|
||||
except ValueError as e:
|
||||
raise JailDefError("Invalid action definition %r: %s" % (act, e))
|
||||
if actName.endswith(".py"):
|
||||
self.__actions.append([
|
||||
"set",
|
||||
self.__name,
|
||||
"addaction",
|
||||
actOpt.pop("actname", os.path.splitext(actName)[0]),
|
||||
os.path.join(
|
||||
basedir, "action.d", actName),
|
||||
json.dumps(actOpt),
|
||||
])
|
||||
else:
|
||||
action = ActionReader(
|
||||
actName, self.__name, actOpt,
|
||||
share_config=self.share_config, basedir=basedir)
|
||||
ret = action.read()
|
||||
if ret:
|
||||
action.getOptions(self.__opts)
|
||||
self.__actions.append(action)
|
||||
else:
|
||||
raise JailDefError("Unable to read action %r" % actName)
|
||||
except JailDefError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logSys.debug("Caught exception: %s", e, exc_info=True)
|
||||
raise ValueError("Error in action definition %r: %r" % (act, e))
|
||||
if not len(self.__actions):
|
||||
logSys.warning("No actions were defined for %s" % self.__name)
|
||||
|
||||
except JailDefError as e:
|
||||
e = str(e)
|
||||
logSys.error(e)
|
||||
if not self.__opts:
|
||||
self.__opts = dict()
|
||||
self.__opts['config-error'] = e
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def filter(self):
|
||||
return self.__filter
|
||||
|
||||
def getCombined(self):
|
||||
if not self.__filter:
|
||||
return self.__opts
|
||||
return _merge_dicts(self.__opts, self.__filter.getCombined())
|
||||
|
||||
def convert(self, allow_no_files=False, systemd_if_nologs=True):
|
||||
"""Convert read before __opts to the commands stream
|
||||
|
||||
Parameters
|
||||
----------
|
||||
allow_missing : bool
|
||||
Either to allow log files to be missing entirely. Primarily is
|
||||
used for testing
|
||||
"""
|
||||
|
||||
stream = []
|
||||
stream2 = []
|
||||
e = self.__opts.get('config-error')
|
||||
if e:
|
||||
stream.extend([['config-error', "Jail '%s' skipped, because of wrong configuration: %s" % (self.__name, e)]])
|
||||
return stream
|
||||
# fill jail with filter options, using filter (only not overridden in jail):
|
||||
if self.__filter:
|
||||
stream.extend(self.__filter.convert())
|
||||
# and using options from jail:
|
||||
FilterReader._fillStream(stream, self.__opts, self.__name)
|
||||
backend = self.__opts.get('backend', 'auto')
|
||||
for opt, value in self.__opts.items():
|
||||
if opt == "logpath":
|
||||
if backend.startswith("systemd"): continue
|
||||
found_files = 0
|
||||
for path in value.split("\n"):
|
||||
path = path.rsplit(" ", 1)
|
||||
path, tail = path if len(path) > 1 else (path[0], "head")
|
||||
pathList = JailReader._glob(path)
|
||||
if len(pathList) == 0:
|
||||
logSys.notice("No file(s) found for glob %s" % path)
|
||||
for p in pathList:
|
||||
found_files += 1
|
||||
# logpath after all log-related data (backend, date-pattern, etc)
|
||||
stream2.append(
|
||||
["set", self.__name, "addlogpath", p, tail])
|
||||
if not found_files:
|
||||
msg = "Have not found any log file for '%s' jail." % self.__name
|
||||
skip_if_nologs = self.__opts.get('skip_if_nologs', False)
|
||||
# if auto and we can switch to systemd backend (only possible if jail have journalmatch):
|
||||
if backend.startswith("auto") and systemd_if_nologs and (
|
||||
self.__opts.get('systemd_if_nologs', True) and
|
||||
self.__opts.get('journalmatch', None) is not None
|
||||
):
|
||||
# switch backend to systemd:
|
||||
backend = 'systemd'
|
||||
msg += " Jail will monitor systemd journal."
|
||||
skip_if_nologs = False
|
||||
elif not allow_no_files and not skip_if_nologs:
|
||||
raise ValueError(msg)
|
||||
logSys.warning(msg)
|
||||
if skip_if_nologs:
|
||||
self.__opts['runtime-error'] = msg
|
||||
msg = "Jail '%s' skipped, because of missing log files." % (self.__name,)
|
||||
logSys.warning(msg)
|
||||
stream = [['config-error', msg]]
|
||||
return stream
|
||||
elif opt == "ignoreip":
|
||||
stream.append(["set", self.__name, "addignoreip"] + splitwords(value))
|
||||
elif opt not in JailReader._ignoreOpts:
|
||||
stream.append(["set", self.__name, opt, value])
|
||||
# consider options order (after other options):
|
||||
if stream2: stream += stream2
|
||||
for action in self.__actions:
|
||||
if isinstance(action, (ConfigReaderUnshared, ConfigReader)):
|
||||
stream.extend(action.convert())
|
||||
else:
|
||||
stream.append(action)
|
||||
stream.insert(0, ["add", self.__name, backend])
|
||||
return stream
|
||||
|
||||
class JailDefError(Exception):
|
||||
pass
|
||||
Reference in New Issue
Block a user