mirror of https://github.com/fail2ban/fail2ban
Merge pull request #1557 from sebres/_0.10/fix-reload-bug
0.10/reload-and-more: reload without restart, stability and performance fixespull/1563/head
commit
a0d8581a2c
85
ChangeLog
85
ChangeLog
|
@ -12,14 +12,22 @@ ver. 0.10.0 (2016/XX/XXX) - gonna-be-released-some-time-shining
|
||||||
TODO: implementing of options resp. other tasks from PR #1346
|
TODO: implementing of options resp. other tasks from PR #1346
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
* [grave] memory leak's fixed (gh-1277, gh-1234)
|
* [Grave] memory leak's fixed (gh-1277, gh-1234)
|
||||||
* tricky bug fix: last position of log file will be never retrieved (gh-795),
|
* Tricky bug fix: last position of log file will be never retrieved (gh-795),
|
||||||
because of CASCADE all log entries will be deleted from logs table together with jail,
|
because of CASCADE all log entries will be deleted from logs table together with jail,
|
||||||
if used "INSERT OR REPLACE" statement
|
if used "INSERT OR REPLACE" statement
|
||||||
* asyncserver (asyncore) code fixed and test cases repaired (again gh-161)
|
* Asyncserver (asyncore) code fixed and test cases repaired (again gh-161)
|
||||||
* testSocket: sporadical bug repaired - wait for server thread starts a socket (listener)
|
* testSocket: sporadical bug repaired - wait for server thread starts a socket (listener)
|
||||||
* testExecuteTimeoutWithNastyChildren: sporadical bug repaired - wait for pid file inside bash,
|
* testExecuteTimeoutWithNastyChildren: sporadical bug repaired - wait for pid file inside bash,
|
||||||
kill tree in any case (gh-1155)
|
kill tree in any case (gh-1155)
|
||||||
|
* Fixed high-load of pyinotify-backend,
|
||||||
|
see https://github.com/fail2ban/fail2ban/issues/885#issuecomment-248964591
|
||||||
|
* Database: stability fix - repack cursor iterator as long as locked
|
||||||
|
* File filter backends: stability fix for sporadically errors - always close file
|
||||||
|
handle, otherwise may be locked (prevent log-rotate, etc.)
|
||||||
|
* Pyinotify-backend: stability fix for sporadically errors in multi-threaded
|
||||||
|
environment (without lock)
|
||||||
|
* Fixed sporadically error in testCymruInfoNxdomain, because of unsorted values
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
* IPv6 support:
|
* IPv6 support:
|
||||||
|
@ -33,18 +41,33 @@ TODO: implementing of options resp. other tasks from PR #1346
|
||||||
- new conditional section functionality used in config resp. includes:
|
- new conditional section functionality used in config resp. includes:
|
||||||
- [Init?family=inet4] - IPv4 qualified hosts only
|
- [Init?family=inet4] - IPv4 qualified hosts only
|
||||||
- [Init?family=inet6] - IPv6 qualified hosts only
|
- [Init?family=inet6] - IPv6 qualified hosts only
|
||||||
|
* New reload functionality (now totally without restart, unbanning/rebanning, etc.),
|
||||||
|
see gh-1557
|
||||||
|
* Several commands extended and new commands introduced:
|
||||||
|
- `restart [--unban] [--if-exists] <JAIL>` - restarts the jail \<JAIL\>
|
||||||
|
(alias for `reload --restart ... <JAIL>`)
|
||||||
|
- `reload [--restart] [--unban] [--all]` - reloads the configuration without restarting
|
||||||
|
of the server, the option `--restart` activates completely restarting of affected jails,
|
||||||
|
thereby can unban IP addresses (if option `--unban` specified)
|
||||||
|
- `reload [--restart] [--unban] [--if-exists] <JAIL>` - reloads the jail \<JAIL\>,
|
||||||
|
or restarts it (if option `--restart` specified), at the same time unbans all IP addresses
|
||||||
|
banned in this jail, if option `--unban` specified
|
||||||
|
- `unban --all` - unbans all IP addresses (in all jails and database)
|
||||||
|
- `unban <IP> ... <IP>` - unbans \<IP\> (in all jails and database) (see gh-1388)
|
||||||
|
* New command action parameter `actionrepair` - command executed in order to restore
|
||||||
|
sane environment in error case of `actioncheck`.
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
* huge increasing of fail2ban performance and especially test-cases performance (see gh-1109)
|
* Huge increasing of fail2ban performance and especially test-cases performance (see gh-1109)
|
||||||
* datedetector: in-place reordering using hits and last used time:
|
* Datedetector: in-place reordering using hits and last used time:
|
||||||
matchTime, template list etc. rewritten because of performance degradation
|
matchTime, template list etc. rewritten because of performance degradation
|
||||||
* prevent out of memory situation if many IP's makes extremely many failures (maxEntries)
|
* Prevent out of memory situation if many IP's makes extremely many failures (maxEntries)
|
||||||
* introduced string to seconds (str2seconds) for configuration entries with time,
|
* Introduced string to seconds (str2seconds) for configuration entries with time,
|
||||||
use `1h` instead of `3600`, `1d` instead of `86400`, etc
|
use `1h` instead of `3600`, `1d` instead of `86400`, etc
|
||||||
* seekToTime - prevent completely read of big files first time (after start of service),
|
* seekToTime - prevent completely read of big files first time (after start of service),
|
||||||
initial seek to start time using half-interval search algorithm (see issue gh-795)
|
initial seek to start time using half-interval search algorithm (see issue gh-795)
|
||||||
* ticket and some other modules prepared to easy merge with newest version of 'ban-time-incr'
|
* Ticket and some other modules prepared to easy merge with newest version of 'ban-time-incr'
|
||||||
* cache dnsToIp, ipToName to prevent long wait during retrieving of ip/name,
|
* Cache dnsToIp, ipToName to prevent long wait during retrieving of ip/name,
|
||||||
especially for wrong dns or lazy dns-system
|
especially for wrong dns or lazy dns-system
|
||||||
* FailManager memory-optimization: increases performance,
|
* FailManager memory-optimization: increases performance,
|
||||||
prevents memory leakage, because don't copy failures list on some operations
|
prevents memory leakage, because don't copy failures list on some operations
|
||||||
|
@ -54,14 +77,52 @@ TODO: implementing of options resp. other tasks from PR #1346
|
||||||
- `-g`, `--no-gamin` to prevent running of tests that require the gamin (slow)
|
- `-g`, `--no-gamin` to prevent running of tests that require the gamin (slow)
|
||||||
- `-m`, `--memory-db` - run database tests using memory instead of file
|
- `-m`, `--memory-db` - run database tests using memory instead of file
|
||||||
- `-i`, `--ignore` - negate [regexps] filter to ignore tests matched specified regexps
|
- `-i`, `--ignore` - negate [regexps] filter to ignore tests matched specified regexps
|
||||||
* background servicing: prevents memory leak on some platforms/python versions, using forced GC
|
* Background servicing: prevents memory leak on some platforms/python versions, using forced GC
|
||||||
in periodic intervals (latency and threshold)
|
in periodic intervals (latency and threshold)
|
||||||
* executeCmd partially moved from action to new module utils
|
* executeCmd partially moved from action to new module utils
|
||||||
* several functionality of class `DNSUtils` moved to new class `IPAddr`,
|
* Several functionality of class `DNSUtils` moved to new class `IPAddr`,
|
||||||
both classes moved to new module `ipdns`
|
both classes moved to new module `ipdns`
|
||||||
* pseudo-conditional section introduced, for conditional substitution resp.
|
* Pseudo-conditional section introduced, for conditional substitution resp.
|
||||||
evaluation of parameters for different family qualified hosts,
|
evaluation of parameters for different family qualified hosts,
|
||||||
syntax `[Section?family=inet6]` (currently use for IPv6-support only).
|
syntax `[Section?family=inet6]` (currently use for IPv6-support only).
|
||||||
|
* All the backends were rewritten to get reload-possibility, performance increased,
|
||||||
|
so fewer greedy regarding cpu- resp. system-load now
|
||||||
|
* Numeric log-level allowed now in server (resp. fail2ban.conf);
|
||||||
|
* Implemented better error handling in some multi-threaded routines; shutdown of jails
|
||||||
|
rewritten (faster and safer, does not breaks shutdown process if some error occurred)
|
||||||
|
* Possibility for overwriting some configuration options (read with config-readers)
|
||||||
|
with command line option, e. g.:
|
||||||
|
```bash
|
||||||
|
## start server with DEBUG log-level (ignore level read from fail2ban.conf):
|
||||||
|
fail2ban-client --loglevel DEBUG start
|
||||||
|
## or
|
||||||
|
fail2ban-server -c /cfg/path --loglevel DEBUG start
|
||||||
|
## keep server log-level by reload (without restart it)
|
||||||
|
fail2ban-client --loglevel DEBUG reload
|
||||||
|
## switch log-level back to INFO:
|
||||||
|
fail2ban-client set loglevel INFO
|
||||||
|
```
|
||||||
|
* Optimized BanManager: increase performance, fewer system load, try to prevent
|
||||||
|
memory leakage:
|
||||||
|
- better ban/unban handling within actions (e.g. used dict instead of list)
|
||||||
|
- don't copy bans resp. its list on some operations;
|
||||||
|
- added new unbantime handling to relieve unBanList (prevent permanent
|
||||||
|
searching for tickets to unban)
|
||||||
|
- prefer failure-ID as identifier of the ticket to its IP (most of the time
|
||||||
|
the same, but it can be something else e.g. user name in some complex jails,
|
||||||
|
as introduced in 0.10)
|
||||||
|
* Regexp enhancements:
|
||||||
|
- build replacement of `<HOST>` substitution corresponding parameter
|
||||||
|
`usedns` - dns-part will be added only if `usedns` is not `no`,
|
||||||
|
also using fail2ban-regex
|
||||||
|
- new replacement for `<ADDR>` in opposition to `<HOST>`, for separate
|
||||||
|
usage of 2 address groups only (regardless of `usedns`), `ip4` and `ip6`
|
||||||
|
together, without host (dns)
|
||||||
|
* fail2ban-testcases:
|
||||||
|
- `assertLogged` extended with parameter wait (to wait up to specified timeout,
|
||||||
|
before we throw assert exception) + test cases rewritten using that
|
||||||
|
- added `assertDictEqual` for compatibility to early python versions (< 2.7);
|
||||||
|
- new `with_foreground_server_thread` decorator to test several client/server commands
|
||||||
|
|
||||||
|
|
||||||
ver. 0.9.6 (2016/XX/XX) - wanna-be-released
|
ver. 0.9.6 (2016/XX/XX) - wanna-be-released
|
||||||
|
|
|
@ -26,8 +26,11 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
|
|
||||||
# Custom debug level
|
# Custom debug levels
|
||||||
|
logging.TRACEDEBUG = 7
|
||||||
logging.HEAVYDEBUG = 5
|
logging.HEAVYDEBUG = 5
|
||||||
|
logging.addLevelName(logging.TRACEDEBUG, 'TRACE')
|
||||||
|
logging.addLevelName(logging.HEAVYDEBUG, 'HEAVY')
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Below derived from:
|
Below derived from:
|
||||||
|
|
|
@ -38,7 +38,9 @@ class ActionReader(DefinitionInitConfigReader):
|
||||||
_configOpts = {
|
_configOpts = {
|
||||||
"actionstart": ["string", None],
|
"actionstart": ["string", None],
|
||||||
"actionstop": ["string", None],
|
"actionstop": ["string", None],
|
||||||
|
"actionreload": ["string", None],
|
||||||
"actioncheck": ["string", None],
|
"actioncheck": ["string", None],
|
||||||
|
"actionrepair": ["string", None],
|
||||||
"actionban": ["string", None],
|
"actionban": ["string", None],
|
||||||
"actionunban": ["string", None],
|
"actionunban": ["string", None],
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ class Beautifier:
|
||||||
return self.__inputCmd
|
return self.__inputCmd
|
||||||
|
|
||||||
def beautify(self, response):
|
def beautify(self, response):
|
||||||
logSys.debug(
|
logSys.log(5,
|
||||||
"Beautify " + repr(response) + " with " + repr(self.__inputCmd))
|
"Beautify " + repr(response) + " with " + repr(self.__inputCmd))
|
||||||
inC = self.__inputCmd
|
inC = self.__inputCmd
|
||||||
msg = response
|
msg = response
|
||||||
|
|
|
@ -72,8 +72,8 @@ class Configurator:
|
||||||
def getEarlyOptions(self):
|
def getEarlyOptions(self):
|
||||||
return self.__fail2ban.getEarlyOptions()
|
return self.__fail2ban.getEarlyOptions()
|
||||||
|
|
||||||
def getOptions(self, jail = None):
|
def getOptions(self, jail=None, updateMainOpt=None):
|
||||||
self.__fail2ban.getOptions()
|
self.__fail2ban.getOptions(updateMainOpt)
|
||||||
return self.__jails.getOptions(jail)
|
return self.__jails.getOptions(jail)
|
||||||
|
|
||||||
def convertToProtocol(self):
|
def convertToProtocol(self):
|
||||||
|
|
|
@ -91,7 +91,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
||||||
client = CSocket(self._conf["socket"])
|
client = CSocket(self._conf["socket"])
|
||||||
ret = client.send(c)
|
ret = client.send(c)
|
||||||
if ret[0] == 0:
|
if ret[0] == 0:
|
||||||
logSys.debug("OK : %r", ret[1])
|
logSys.log(5, "OK : %r", ret[1])
|
||||||
if showRet or c[0] == 'echo':
|
if showRet or c[0] == 'echo':
|
||||||
output(beautifier.beautify(ret[1]))
|
output(beautifier.beautify(ret[1]))
|
||||||
else:
|
else:
|
||||||
|
@ -104,7 +104,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
||||||
if showRet or c != ["ping"]:
|
if showRet or c != ["ping"]:
|
||||||
self.__logSocketError()
|
self.__logSocketError()
|
||||||
else:
|
else:
|
||||||
logSys.debug(" -- ping failed -- %r", e)
|
logSys.log(5, " -- ping failed -- %r", e)
|
||||||
return False
|
return False
|
||||||
except Exception as e: # pragma: no cover
|
except Exception as e: # pragma: no cover
|
||||||
if showRet or self._conf["verbose"] > 1:
|
if showRet or self._conf["verbose"] > 1:
|
||||||
|
@ -226,11 +226,11 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
||||||
# prepare: read config, check configuration is valid, etc.:
|
# prepare: read config, check configuration is valid, etc.:
|
||||||
if phase is not None:
|
if phase is not None:
|
||||||
phase['start'] = True
|
phase['start'] = True
|
||||||
logSys.debug(' client phase %s', phase)
|
logSys.log(5, ' client phase %s', phase)
|
||||||
stream = self.__prepareStartServer()
|
stream = self.__prepareStartServer()
|
||||||
if phase is not None:
|
if phase is not None:
|
||||||
phase['ready'] = phase['start'] = (True if stream else False)
|
phase['ready'] = phase['start'] = (True if stream else False)
|
||||||
logSys.debug(' client phase %s', phase)
|
logSys.log(5, ' client phase %s', phase)
|
||||||
if not stream:
|
if not stream:
|
||||||
return False
|
return False
|
||||||
# configure server with config stream:
|
# configure server with config stream:
|
||||||
|
@ -246,6 +246,10 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
||||||
# @param cmd the command line
|
# @param cmd the command line
|
||||||
|
|
||||||
def __processCommand(self, cmd):
|
def __processCommand(self, cmd):
|
||||||
|
# wrap tuple to list (because could be modified here):
|
||||||
|
if not isinstance(cmd, list):
|
||||||
|
cmd = list(cmd)
|
||||||
|
# process:
|
||||||
if len(cmd) == 1 and cmd[0] == "start":
|
if len(cmd) == 1 and cmd[0] == "start":
|
||||||
|
|
||||||
ret = self.__startServer(self._conf["background"])
|
ret = self.__startServer(self._conf["background"])
|
||||||
|
@ -253,8 +257,12 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
||||||
return False
|
return False
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
elif len(cmd) == 1 and cmd[0] == "restart":
|
elif len(cmd) >= 1 and cmd[0] == "restart":
|
||||||
|
# if restart jail - re-operate via "reload --restart ...":
|
||||||
|
if len(cmd) > 1:
|
||||||
|
cmd[0:1] = ["reload", "--restart"]
|
||||||
|
return self.__processCommand(cmd)
|
||||||
|
# restart server:
|
||||||
if self._conf.get("interactive", False):
|
if self._conf.get("interactive", False):
|
||||||
output(' ## stop ... ')
|
output(' ## stop ... ')
|
||||||
self.__processCommand(['stop'])
|
self.__processCommand(['stop'])
|
||||||
|
@ -273,9 +281,21 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
||||||
return self.__processCommand(['start'])
|
return self.__processCommand(['start'])
|
||||||
|
|
||||||
elif len(cmd) >= 1 and cmd[0] == "reload":
|
elif len(cmd) >= 1 and cmd[0] == "reload":
|
||||||
|
# reload options:
|
||||||
|
opts = []
|
||||||
|
while len(cmd) >= 2:
|
||||||
|
if cmd[1] in ('--restart', "--unban", "--if-exists"):
|
||||||
|
opts.append(cmd[1])
|
||||||
|
del cmd[1]
|
||||||
|
else:
|
||||||
|
if len(cmd) > 2:
|
||||||
|
logSys.error("Unexpected argument(s) for reload: %r", cmd[1:])
|
||||||
|
return False
|
||||||
|
# stop options - jail name or --all
|
||||||
|
break
|
||||||
if self.__ping():
|
if self.__ping():
|
||||||
if len(cmd) == 1:
|
if len(cmd) == 1:
|
||||||
jail = 'all'
|
jail = '--all'
|
||||||
ret, stream = self.readConfig()
|
ret, stream = self.readConfig()
|
||||||
else:
|
else:
|
||||||
jail = cmd[1]
|
jail = cmd[1]
|
||||||
|
@ -283,9 +303,10 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
||||||
# Do not continue if configuration is not 100% valid
|
# Do not continue if configuration is not 100% valid
|
||||||
if not ret:
|
if not ret:
|
||||||
return False
|
return False
|
||||||
self.__processCmd([['stop', jail]], False)
|
if self._conf.get("interactive", False):
|
||||||
# Configure the server
|
output(' ## reload ... ')
|
||||||
return self.__processCmd(stream, True)
|
# Reconfigure the server
|
||||||
|
return self.__processCmd([['reload', jail, opts, stream]], True)
|
||||||
else:
|
else:
|
||||||
logSys.error("Could not find server")
|
logSys.error("Could not find server")
|
||||||
return False
|
return False
|
||||||
|
@ -320,7 +341,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
||||||
maxtime = self._conf["timeout"]
|
maxtime = self._conf["timeout"]
|
||||||
# Wait for the server to start (the server has 30 seconds to answer ping)
|
# Wait for the server to start (the server has 30 seconds to answer ping)
|
||||||
starttime = time.time()
|
starttime = time.time()
|
||||||
logSys.debug("__waitOnServer: %r", (alive, maxtime))
|
logSys.log(5, "__waitOnServer: %r", (alive, maxtime))
|
||||||
test = lambda: os.path.exists(self._conf["socket"]) and self.__ping()
|
test = lambda: os.path.exists(self._conf["socket"]) and self.__ping()
|
||||||
with VisualWait(self._conf["verbose"]) as vis:
|
with VisualWait(self._conf["verbose"]) as vis:
|
||||||
sltime = 0.0125 / 2
|
sltime = 0.0125 / 2
|
||||||
|
|
|
@ -242,7 +242,7 @@ class Fail2banCmdLine():
|
||||||
try:
|
try:
|
||||||
self.configurator.Reload()
|
self.configurator.Reload()
|
||||||
self.configurator.readAll()
|
self.configurator.readAll()
|
||||||
ret = self.configurator.getOptions(jail)
|
ret = self.configurator.getOptions(jail, self._conf)
|
||||||
self.configurator.convertToProtocol()
|
self.configurator.convertToProtocol()
|
||||||
stream = self.configurator.getConfigStream()
|
stream = self.configurator.getConfigStream()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -25,7 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from .configreader import ConfigReader
|
from .configreader import ConfigReader
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger, str2LogLevel
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
@ -49,13 +49,17 @@ class Fail2banReader(ConfigReader):
|
||||||
]
|
]
|
||||||
return ConfigReader.getOptions(self, "Definition", opts)
|
return ConfigReader.getOptions(self, "Definition", opts)
|
||||||
|
|
||||||
def getOptions(self):
|
def getOptions(self, updateMainOpt=None):
|
||||||
opts = [["string", "loglevel", "INFO" ],
|
opts = [["string", "loglevel", "INFO" ],
|
||||||
["string", "logtarget", "STDERR"],
|
["string", "logtarget", "STDERR"],
|
||||||
["string", "syslogsocket", "auto"],
|
["string", "syslogsocket", "auto"],
|
||||||
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
|
["string", "dbfile", "/var/lib/fail2ban/fail2ban.sqlite3"],
|
||||||
["string", "dbpurgeage", "1d"]]
|
["string", "dbpurgeage", "1d"]]
|
||||||
self.__opts = ConfigReader.getOptions(self, "Definition", opts)
|
self.__opts = ConfigReader.getOptions(self, "Definition", opts)
|
||||||
|
if updateMainOpt:
|
||||||
|
self.__opts.update(updateMainOpt)
|
||||||
|
# check given log-level:
|
||||||
|
str2LogLevel(self.__opts.get('loglevel', 0))
|
||||||
|
|
||||||
def convert(self):
|
def convert(self):
|
||||||
# Ensure logtarget/level set first so any db errors are captured
|
# Ensure logtarget/level set first so any db errors are captured
|
||||||
|
|
|
@ -29,7 +29,6 @@ __copyright__ = "Copyright (c) 2004-2008 Cyril Jaquier, 2012-2014 Yaroslav Halch
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import getopt
|
import getopt
|
||||||
import locale
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
@ -52,7 +51,7 @@ from .filterreader import FilterReader
|
||||||
from ..server.filter import Filter, FileContainer
|
from ..server.filter import Filter, FileContainer
|
||||||
from ..server.failregex import RegexException
|
from ..server.failregex import RegexException
|
||||||
|
|
||||||
from ..helpers import FormatterWithTraceBack, getLogger
|
from ..helpers import FormatterWithTraceBack, getLogger, PREFER_ENC
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger("fail2ban")
|
logSys = getLogger("fail2ban")
|
||||||
|
|
||||||
|
@ -127,6 +126,9 @@ Report bugs to https://github.com/fail2ban/fail2ban/issues
|
||||||
help="File encoding. Default: system locale"),
|
help="File encoding. Default: system locale"),
|
||||||
Option("-r", "--raw", action='store_true',
|
Option("-r", "--raw", action='store_true',
|
||||||
help="Raw hosts, don't resolve dns"),
|
help="Raw hosts, don't resolve dns"),
|
||||||
|
Option("--usedns", action='store', default=None,
|
||||||
|
help="DNS specified replacement of tags <HOST> in regexp "
|
||||||
|
"('yes' - matches all form of hosts, 'no' - IP addresses only)"),
|
||||||
Option("-L", "--maxlines", type=int, default=0,
|
Option("-L", "--maxlines", type=int, default=0,
|
||||||
help="maxlines for multi-line regex"),
|
help="maxlines for multi-line regex"),
|
||||||
Option("-m", "--journalmatch",
|
Option("-m", "--journalmatch",
|
||||||
|
@ -239,8 +241,10 @@ class Fail2banRegex(object):
|
||||||
if opts.encoding:
|
if opts.encoding:
|
||||||
self.encoding = opts.encoding
|
self.encoding = opts.encoding
|
||||||
else:
|
else:
|
||||||
self.encoding = locale.getpreferredencoding()
|
self.encoding = PREFER_ENC
|
||||||
self.raw = True if opts.raw else False
|
self.raw = True if opts.raw else False
|
||||||
|
if opts.usedns:
|
||||||
|
self._filter.setUseDns(opts.usedns)
|
||||||
|
|
||||||
def decode_line(self, line):
|
def decode_line(self, line):
|
||||||
return FileContainer.decode_line('<LOG>', self.encoding, line)
|
return FileContainer.decode_line('<LOG>', self.encoding, line)
|
||||||
|
|
|
@ -106,7 +106,7 @@ class JailReader(ConfigReader):
|
||||||
["int", "maxretry", None],
|
["int", "maxretry", None],
|
||||||
["string", "findtime", None],
|
["string", "findtime", None],
|
||||||
["string", "bantime", None],
|
["string", "bantime", None],
|
||||||
["string", "usedns", None],
|
["string", "usedns", None], # be sure usedns is before all regex(s) in stream
|
||||||
["string", "failregex", None],
|
["string", "failregex", None],
|
||||||
["string", "ignoreregex", None],
|
["string", "ignoreregex", None],
|
||||||
["string", "ignorecommand", None],
|
["string", "ignorecommand", None],
|
||||||
|
|
|
@ -21,6 +21,7 @@ __author__ = "Cyril Jaquier, Arturo 'Buanzo' Busleiman, Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import gc
|
import gc
|
||||||
|
import locale
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -32,6 +33,9 @@ from threading import Lock
|
||||||
from .server.mytime import MyTime
|
from .server.mytime import MyTime
|
||||||
|
|
||||||
|
|
||||||
|
PREFER_ENC = locale.getpreferredencoding()
|
||||||
|
|
||||||
|
|
||||||
def formatExceptionInfo():
|
def formatExceptionInfo():
|
||||||
""" Consistently format exception information """
|
""" Consistently format exception information """
|
||||||
cla, exc = sys.exc_info()[:2]
|
cla, exc = sys.exc_info()[:2]
|
||||||
|
@ -125,6 +129,16 @@ def getLogger(name):
|
||||||
name = "fail2ban.%s" % name.rpartition(".")[-1]
|
name = "fail2ban.%s" % name.rpartition(".")[-1]
|
||||||
return logging.getLogger(name)
|
return logging.getLogger(name)
|
||||||
|
|
||||||
|
def str2LogLevel(value):
|
||||||
|
try:
|
||||||
|
if isinstance(value, int) or value.isdigit():
|
||||||
|
ll = int(value)
|
||||||
|
else:
|
||||||
|
ll = getattr(logging, value)
|
||||||
|
except AttributeError:
|
||||||
|
raise ValueError("Invalid log level %r" % value)
|
||||||
|
return ll
|
||||||
|
|
||||||
|
|
||||||
def excepthook(exctype, value, traceback):
|
def excepthook(exctype, value, traceback):
|
||||||
"""Except hook used to log unhandled exceptions to Fail2Ban log
|
"""Except hook used to log unhandled exceptions to Fail2Ban log
|
||||||
|
@ -144,6 +158,36 @@ def splitwords(s):
|
||||||
return filter(bool, map(str.strip, re.split('[ ,\n]+', s)))
|
return filter(bool, map(str.strip, re.split('[ ,\n]+', s)))
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Following "uni_decode" function unified python independent any to string converting
|
||||||
|
#
|
||||||
|
# Typical example resp. work-case for understanding the coding/decoding issues:
|
||||||
|
#
|
||||||
|
# [isinstance('', str), isinstance(b'', str), isinstance(u'', str)]
|
||||||
|
# [True, True, False]; # -- python2
|
||||||
|
# [True, False, True]; # -- python3
|
||||||
|
#
|
||||||
|
if sys.version_info >= (3,):
|
||||||
|
def uni_decode(x, enc=PREFER_ENC, errors='strict'):
|
||||||
|
try:
|
||||||
|
if isinstance(x, bytes):
|
||||||
|
return x.decode(enc, errors)
|
||||||
|
return x
|
||||||
|
except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - unsure if reachable
|
||||||
|
if errors != 'strict':
|
||||||
|
raise
|
||||||
|
return uni_decode(x, enc, 'replace')
|
||||||
|
else:
|
||||||
|
def uni_decode(x, enc=PREFER_ENC, errors='strict'):
|
||||||
|
try:
|
||||||
|
if isinstance(x, unicode):
|
||||||
|
return x.encode(enc, errors)
|
||||||
|
return x
|
||||||
|
except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - unsure if reachable
|
||||||
|
if errors != 'strict':
|
||||||
|
raise
|
||||||
|
return uni_decode(x, enc, 'replace')
|
||||||
|
|
||||||
class BgService(object):
|
class BgService(object):
|
||||||
"""Background servicing
|
"""Background servicing
|
||||||
|
|
||||||
|
|
|
@ -49,16 +49,20 @@ protocol = [
|
||||||
['', "BASIC", ""],
|
['', "BASIC", ""],
|
||||||
["start", "starts the server and the jails"],
|
["start", "starts the server and the jails"],
|
||||||
["restart", "restarts the server"],
|
["restart", "restarts the server"],
|
||||||
["reload", "reloads the configuration without restart"],
|
["restart [--unban] [--if-exists] <JAIL>", "restarts the jail <JAIL> (alias for 'reload --restart ... <JAIL>')"],
|
||||||
["reload <JAIL>", "reloads the jail <JAIL>"],
|
["reload [--restart] [--unban] [--all]", "reloads the configuration without restarting of the server, the option '--restart' activates completely restarting of affected jails, thereby can unban IP addresses (if option '--unban' specified)"],
|
||||||
|
["reload [--restart] [--unban] [--if-exists] <JAIL>", "reloads the jail <JAIL>, or restarts it (if option '--restart' specified)"],
|
||||||
["stop", "stops all jails and terminate the server"],
|
["stop", "stops all jails and terminate the server"],
|
||||||
|
["unban --all", "unbans all IP addresses (in all jails and database)"],
|
||||||
|
["unban <IP> ... <IP>", "unbans <IP> (in all jails and database)"],
|
||||||
["status", "gets the current status of the server"],
|
["status", "gets the current status of the server"],
|
||||||
["ping", "tests if the server is alive"],
|
["ping", "tests if the server is alive"],
|
||||||
["echo", "for internal usage, returns back and outputs a given string"],
|
["echo", "for internal usage, returns back and outputs a given string"],
|
||||||
["help", "return this output"],
|
["help", "return this output"],
|
||||||
["version", "return the server version"],
|
["version", "return the server version"],
|
||||||
['', "LOGGING", ""],
|
['', "LOGGING", ""],
|
||||||
["set loglevel <LEVEL>", "sets logging level to <LEVEL>. Levels: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG"],
|
["set loglevel <LEVEL>", "sets logging level to <LEVEL>. Levels: CRITICAL, ERROR, WARNING, NOTICE, INFO, "
|
||||||
|
"DEBUG, TRACEDEBUG, HEAVYDEBUG or corresponding numeric value (50-5)"],
|
||||||
["get loglevel", "gets the logging level"],
|
["get loglevel", "gets the logging level"],
|
||||||
["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"],
|
["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"],
|
||||||
["get logtarget", "gets logging target"],
|
["get logtarget", "gets logging target"],
|
||||||
|
|
|
@ -200,6 +200,9 @@ class CommandAction(ActionBase):
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
actionban
|
actionban
|
||||||
|
actioncheck
|
||||||
|
actionreload
|
||||||
|
actionrepair
|
||||||
actionstart
|
actionstart
|
||||||
actionstop
|
actionstop
|
||||||
actionunban
|
actionunban
|
||||||
|
@ -208,22 +211,35 @@ class CommandAction(ActionBase):
|
||||||
|
|
||||||
_escapedTags = set(('matches', 'ipmatches', 'ipjailmatches'))
|
_escapedTags = set(('matches', 'ipmatches', 'ipjailmatches'))
|
||||||
|
|
||||||
timeout = 60
|
def clearAllParams(self):
|
||||||
## Command executed in order to initialize the system.
|
""" Clear all lists/dicts parameters (used by reloading)
|
||||||
actionstart = ''
|
"""
|
||||||
## Command executed when an IP address gets banned.
|
self.__init = 1
|
||||||
actionban = ''
|
try:
|
||||||
## Command executed when an IP address gets removed.
|
self.timeout = 60
|
||||||
actionunban = ''
|
## Command executed in order to initialize the system.
|
||||||
## Command executed in order to check requirements.
|
self.actionstart = ''
|
||||||
actioncheck = ''
|
## Command executed when an IP address gets banned.
|
||||||
## Command executed in order to stop the system.
|
self.actionban = ''
|
||||||
actionstop = ''
|
## Command executed when an IP address gets removed.
|
||||||
|
self.actionunban = ''
|
||||||
|
## Command executed in order to check requirements.
|
||||||
|
self.actioncheck = ''
|
||||||
|
## Command executed in order to restore sane environment in error case.
|
||||||
|
self.actionrepair = ''
|
||||||
|
## Command executed in order to stop the system.
|
||||||
|
self.actionstop = ''
|
||||||
|
## Command executed in case of reloading action.
|
||||||
|
self.actionreload = ''
|
||||||
|
finally:
|
||||||
|
self.__init = 0
|
||||||
|
|
||||||
def __init__(self, jail, name):
|
def __init__(self, jail, name):
|
||||||
super(CommandAction, self).__init__(jail, name)
|
super(CommandAction, self).__init__(jail, name)
|
||||||
|
self.__init = 1
|
||||||
self.__properties = None
|
self.__properties = None
|
||||||
self.__substCache = {}
|
self.__substCache = {}
|
||||||
|
self.clearAllParams()
|
||||||
self._logSys.debug("Created %s" % self.__class__)
|
self._logSys.debug("Created %s" % self.__class__)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -231,7 +247,7 @@ class CommandAction(ActionBase):
|
||||||
return NotImplemented # Standard checks
|
return NotImplemented # Standard checks
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
if not name.startswith('_') and not callable(value):
|
if not name.startswith('_') and not self.__init and not callable(value):
|
||||||
# special case for some pasrameters:
|
# special case for some pasrameters:
|
||||||
if name in ('timeout', 'bantime'):
|
if name in ('timeout', 'bantime'):
|
||||||
value = str(MyTime.str2seconds(value))
|
value = str(MyTime.str2seconds(value))
|
||||||
|
@ -264,28 +280,38 @@ class CommandAction(ActionBase):
|
||||||
def _substCache(self):
|
def _substCache(self):
|
||||||
return self.__substCache
|
return self.__substCache
|
||||||
|
|
||||||
|
def _executeOperation(self, tag, operation):
|
||||||
|
"""Executes the operation commands (like "actionstart", "actionstop", etc).
|
||||||
|
|
||||||
|
Replace the tags in the action command with actions properties
|
||||||
|
and executes the resulting command.
|
||||||
|
"""
|
||||||
|
# check valid tags in properties (raises ValueError if self recursion, etc.):
|
||||||
|
res = True
|
||||||
|
try:
|
||||||
|
# common (resp. ipv4):
|
||||||
|
startCmd = self.replaceTag(tag, self._properties,
|
||||||
|
conditional='family=inet4', cache=self.__substCache)
|
||||||
|
if startCmd:
|
||||||
|
res &= self.executeCmd(startCmd, self.timeout)
|
||||||
|
# start ipv6 actions if available:
|
||||||
|
if allowed_ipv6:
|
||||||
|
startCmd6 = self.replaceTag(tag, self._properties,
|
||||||
|
conditional='family=inet6', cache=self.__substCache)
|
||||||
|
if startCmd6 and startCmd6 != startCmd:
|
||||||
|
res &= self.executeCmd(startCmd6, self.timeout)
|
||||||
|
if not res:
|
||||||
|
raise RuntimeError("Error %s action %s/%s" % (operation, self._jail, self._name,))
|
||||||
|
except ValueError as e:
|
||||||
|
raise RuntimeError("Error %s action %s/%s: %r" % (operation, self._jail, self._name, e))
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Executes the "actionstart" command.
|
"""Executes the "actionstart" command.
|
||||||
|
|
||||||
Replace the tags in the action command with actions properties
|
Replace the tags in the action command with actions properties
|
||||||
and executes the resulting command.
|
and executes the resulting command.
|
||||||
"""
|
"""
|
||||||
# check valid tags in properties (raises ValueError if self recursion, etc.):
|
return self._executeOperation('<actionstart>', 'starting')
|
||||||
try:
|
|
||||||
# common (resp. ipv4):
|
|
||||||
startCmd = self.replaceTag('<actionstart>', self._properties,
|
|
||||||
conditional='family=inet4', cache=self.__substCache)
|
|
||||||
res = self.executeCmd(startCmd, self.timeout)
|
|
||||||
# start ipv6 actions if available:
|
|
||||||
if allowed_ipv6:
|
|
||||||
startCmd6 = self.replaceTag('<actionstart>', self._properties,
|
|
||||||
conditional='family=inet6', cache=self.__substCache)
|
|
||||||
if startCmd6 != startCmd:
|
|
||||||
res &= self.executeCmd(startCmd6, self.timeout)
|
|
||||||
if not res:
|
|
||||||
raise RuntimeError("Error starting action %s/%s" % (self._jail, self._name,))
|
|
||||||
except ValueError as e:
|
|
||||||
raise RuntimeError("Error starting action %s/%s: %r" % (self._jail, self._name, e))
|
|
||||||
|
|
||||||
def ban(self, aInfo):
|
def ban(self, aInfo):
|
||||||
"""Executes the "actionban" command.
|
"""Executes the "actionban" command.
|
||||||
|
@ -323,18 +349,20 @@ class CommandAction(ActionBase):
|
||||||
Replaces the tags in the action command with actions properties
|
Replaces the tags in the action command with actions properties
|
||||||
and executes the resulting command.
|
and executes the resulting command.
|
||||||
"""
|
"""
|
||||||
# common (resp. ipv4):
|
return self._executeOperation('<actionstop>', 'stopping')
|
||||||
stopCmd = self.replaceTag('<actionstop>', self._properties,
|
|
||||||
conditional='family=inet4', cache=self.__substCache)
|
def reload(self, **kwargs):
|
||||||
res = self.executeCmd(stopCmd, self.timeout)
|
"""Executes the "actionreload" command.
|
||||||
# ipv6 actions if available:
|
|
||||||
if allowed_ipv6:
|
Parameters
|
||||||
stopCmd6 = self.replaceTag('<actionstop>', self._properties,
|
----------
|
||||||
conditional='family=inet6', cache=self.__substCache)
|
kwargs : dict
|
||||||
if stopCmd6 != stopCmd:
|
Currently unused, because CommandAction do not support initOpts
|
||||||
res &= self.executeCmd(stopCmd6, self.timeout)
|
|
||||||
if not res:
|
Replaces the tags in the action command with actions properties
|
||||||
raise RuntimeError("Error stopping action")
|
and executes the resulting command.
|
||||||
|
"""
|
||||||
|
return self._executeOperation('<actionreload>', 'reloading')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def substituteRecursiveTags(cls, inptags, conditional=''):
|
def substituteRecursiveTags(cls, inptags, conditional=''):
|
||||||
|
@ -520,14 +548,28 @@ class CommandAction(ActionBase):
|
||||||
|
|
||||||
checkCmd = self.replaceTag('<actioncheck>', self._properties,
|
checkCmd = self.replaceTag('<actioncheck>', self._properties,
|
||||||
conditional=conditional, cache=self.__substCache)
|
conditional=conditional, cache=self.__substCache)
|
||||||
if not self.executeCmd(checkCmd, self.timeout):
|
if checkCmd:
|
||||||
self._logSys.error(
|
|
||||||
"Invariant check failed. Trying to restore a sane environment")
|
|
||||||
self.stop()
|
|
||||||
self.start()
|
|
||||||
if not self.executeCmd(checkCmd, self.timeout):
|
if not self.executeCmd(checkCmd, self.timeout):
|
||||||
self._logSys.critical("Unable to restore environment")
|
self._logSys.error(
|
||||||
return False
|
"Invariant check failed. Trying to restore a sane environment")
|
||||||
|
# try to find repair command, if exists - exec it:
|
||||||
|
repairCmd = self.replaceTag('<actionrepair>', self._properties,
|
||||||
|
conditional=conditional, cache=self.__substCache)
|
||||||
|
if repairCmd:
|
||||||
|
if not self.executeCmd(repairCmd, self.timeout):
|
||||||
|
self._logSys.critical("Unable to restore environment")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# no repair command, try to restart action...
|
||||||
|
# [WARNING] TODO: be sure all banactions get a repair command, because
|
||||||
|
# otherwise stop/start will theoretically remove all the bans,
|
||||||
|
# but the tickets are still in BanManager, so in case of new failures
|
||||||
|
# it will not be banned, because "already banned" will happen.
|
||||||
|
self.stop()
|
||||||
|
self.start()
|
||||||
|
if not self.executeCmd(checkCmd, self.timeout):
|
||||||
|
self._logSys.critical("Unable to restore environment")
|
||||||
|
return False
|
||||||
|
|
||||||
# Replace static fields
|
# Replace static fields
|
||||||
realCmd = self.replaceTag(cmd, self._properties,
|
realCmd = self.replaceTag(cmd, self._properties,
|
||||||
|
|
|
@ -36,7 +36,7 @@ from collections import Mapping
|
||||||
try:
|
try:
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
except ImportError:
|
except ImportError:
|
||||||
OrderedDict = None
|
OrderedDict = dict
|
||||||
|
|
||||||
from .banmanager import BanManager
|
from .banmanager import BanManager
|
||||||
from .jailthread import JailThread
|
from .jailthread import JailThread
|
||||||
|
@ -81,14 +81,11 @@ class Actions(JailThread, Mapping):
|
||||||
JailThread.__init__(self)
|
JailThread.__init__(self)
|
||||||
## The jail which contains this action.
|
## The jail which contains this action.
|
||||||
self._jail = jail
|
self._jail = jail
|
||||||
if OrderedDict is not None:
|
self._actions = OrderedDict()
|
||||||
self._actions = OrderedDict()
|
|
||||||
else:
|
|
||||||
self._actions = dict()
|
|
||||||
## The ban manager.
|
## The ban manager.
|
||||||
self.__banManager = BanManager()
|
self.__banManager = BanManager()
|
||||||
|
|
||||||
def add(self, name, pythonModule=None, initOpts=None):
|
def add(self, name, pythonModule=None, initOpts=None, reload=False):
|
||||||
"""Adds a new action.
|
"""Adds a new action.
|
||||||
|
|
||||||
Add a new action if not already present, defaulting to standard
|
Add a new action if not already present, defaulting to standard
|
||||||
|
@ -116,7 +113,17 @@ class Actions(JailThread, Mapping):
|
||||||
"""
|
"""
|
||||||
# Check is action name already exists
|
# Check is action name already exists
|
||||||
if name in self._actions:
|
if name in self._actions:
|
||||||
raise ValueError("Action %s already exists" % name)
|
if not reload:
|
||||||
|
raise ValueError("Action %s already exists" % name)
|
||||||
|
# don't create new action if reload supported:
|
||||||
|
action = self._actions[name]
|
||||||
|
if hasattr(action, 'reload'):
|
||||||
|
# don't execute reload right now, reload after all parameters are actualized
|
||||||
|
if hasattr(action, 'clearAllParams'):
|
||||||
|
action.clearAllParams()
|
||||||
|
self._reload_actions[name] = initOpts
|
||||||
|
return
|
||||||
|
## Create new action:
|
||||||
if pythonModule is None:
|
if pythonModule is None:
|
||||||
action = CommandAction(self._jail, name)
|
action = CommandAction(self._jail, name)
|
||||||
else:
|
else:
|
||||||
|
@ -138,6 +145,27 @@ class Actions(JailThread, Mapping):
|
||||||
action = customActionModule.Action(self._jail, name, **initOpts)
|
action = customActionModule.Action(self._jail, name, **initOpts)
|
||||||
self._actions[name] = action
|
self._actions[name] = action
|
||||||
|
|
||||||
|
def reload(self, begin=True):
|
||||||
|
""" Begin or end of reloading resp. refreshing of all parameters
|
||||||
|
"""
|
||||||
|
if begin:
|
||||||
|
self._reload_actions = dict()
|
||||||
|
else:
|
||||||
|
if hasattr(self, '_reload_actions'):
|
||||||
|
# reload actions after all parameters set via stream:
|
||||||
|
for name, initOpts in self._reload_actions.iteritems():
|
||||||
|
if name in self._actions:
|
||||||
|
self._actions[name].reload(**initOpts if initOpts else {})
|
||||||
|
# remove obsolete actions (untouched by reload process):
|
||||||
|
delacts = OrderedDict((name, action) for name, action in self._actions.iteritems()
|
||||||
|
if name not in self._reload_actions)
|
||||||
|
if len(delacts):
|
||||||
|
# unban all tickets using remove action only:
|
||||||
|
self.__flushBan(db=False, actions=delacts)
|
||||||
|
# stop and remove it:
|
||||||
|
self.stopActions(actions=delacts)
|
||||||
|
delattr(self, '_reload_actions')
|
||||||
|
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
try:
|
try:
|
||||||
return self._actions[name]
|
return self._actions[name]
|
||||||
|
@ -180,7 +208,7 @@ class Actions(JailThread, Mapping):
|
||||||
def getBanTime(self):
|
def getBanTime(self):
|
||||||
return self.__banManager.getBanTime()
|
return self.__banManager.getBanTime()
|
||||||
|
|
||||||
def removeBannedIP(self, ip):
|
def removeBannedIP(self, ip=None, db=True, ifexists=False):
|
||||||
"""Removes banned IP calling actions' unban method
|
"""Removes banned IP calling actions' unban method
|
||||||
|
|
||||||
Remove a banned IP now, rather than waiting for it to expire,
|
Remove a banned IP now, rather than waiting for it to expire,
|
||||||
|
@ -188,24 +216,50 @@ class Actions(JailThread, Mapping):
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
ip : str or IPAddr
|
ip : str or IPAddr or None
|
||||||
The IP address to unban
|
The IP address to unban or all IPs if None
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
ValueError
|
ValueError
|
||||||
If `ip` is not banned
|
If `ip` is not banned
|
||||||
"""
|
"""
|
||||||
|
# Unban all?
|
||||||
|
if ip is None:
|
||||||
|
return self.__flushBan(db)
|
||||||
|
# Single IP:
|
||||||
# Always delete ip from database (also if currently not banned)
|
# Always delete ip from database (also if currently not banned)
|
||||||
if self._jail.database is not None:
|
if db and self._jail.database is not None:
|
||||||
self._jail.database.delBan(self._jail, ip)
|
self._jail.database.delBan(self._jail, ip)
|
||||||
# Find the ticket with the IP.
|
# Find the ticket with the IP.
|
||||||
ticket = self.__banManager.getTicketByIP(ip)
|
ticket = self.__banManager.getTicketByID(ip)
|
||||||
if ticket is not None:
|
if ticket is not None:
|
||||||
# Unban the IP.
|
# Unban the IP.
|
||||||
self.__unBan(ticket)
|
self.__unBan(ticket)
|
||||||
else:
|
else:
|
||||||
raise ValueError("IP %s is not banned" % ip)
|
if ifexists:
|
||||||
|
return 0
|
||||||
|
raise ValueError("%s is not banned" % ip)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def stopActions(self, actions=None):
|
||||||
|
"""Stops the actions in reverse sequence (optionally filtered)
|
||||||
|
"""
|
||||||
|
if actions is None:
|
||||||
|
actions = self._actions
|
||||||
|
revactions = actions.items()
|
||||||
|
revactions.reverse()
|
||||||
|
for name, action in revactions:
|
||||||
|
try:
|
||||||
|
action.stop()
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error("Failed to stop jail '%s' action '%s': %s",
|
||||||
|
self._jail.name, name, e,
|
||||||
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
|
del self._actions[name]
|
||||||
|
logSys.debug("%s: action %s terminated", self._jail.name, name)
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Main loop for Threading.
|
"""Main loop for Threading.
|
||||||
|
@ -227,22 +281,14 @@ class Actions(JailThread, Mapping):
|
||||||
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
while self.active:
|
while self.active:
|
||||||
if self.idle:
|
if self.idle:
|
||||||
time.sleep(self.sleeptime)
|
Utils.wait_for(lambda: not self.active or not self.idle,
|
||||||
|
self.sleeptime * 10, self.sleeptime)
|
||||||
continue
|
continue
|
||||||
if not Utils.wait_for(self.__checkBan, self.sleeptime):
|
if not Utils.wait_for(lambda: not self.active or self.__checkBan(), self.sleeptime):
|
||||||
self.__checkUnBan()
|
self.__checkUnBan()
|
||||||
|
|
||||||
self.__flushBan()
|
self.__flushBan()
|
||||||
|
self.stopActions()
|
||||||
actions = self._actions.items()
|
|
||||||
actions.reverse()
|
|
||||||
for name, action in actions:
|
|
||||||
try:
|
|
||||||
action.stop()
|
|
||||||
except Exception as e:
|
|
||||||
logSys.error("Failed to stop jail '%s' action '%s': %s",
|
|
||||||
self._jail.name, name, e,
|
|
||||||
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
|
||||||
logSys.debug(self._jail.name + ": action terminated")
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __getBansMerged(self, mi, overalljails=False):
|
def __getBansMerged(self, mi, overalljails=False):
|
||||||
|
@ -295,8 +341,11 @@ class Actions(JailThread, Mapping):
|
||||||
bool
|
bool
|
||||||
True if an IP address get banned.
|
True if an IP address get banned.
|
||||||
"""
|
"""
|
||||||
ticket = self._jail.getFailTicket()
|
cnt = 0
|
||||||
if ticket:
|
while cnt < 100:
|
||||||
|
ticket = self._jail.getFailTicket()
|
||||||
|
if not ticket:
|
||||||
|
break
|
||||||
aInfo = CallingMap()
|
aInfo = CallingMap()
|
||||||
bTicket = BanManager.createBanTicket(ticket)
|
bTicket = BanManager.createBanTicket(ticket)
|
||||||
ip = bTicket.getIP()
|
ip = bTicket.getIP()
|
||||||
|
@ -311,8 +360,10 @@ class Actions(JailThread, Mapping):
|
||||||
aInfo["ipjailmatches"] = lambda: "\n".join(mi4ip().getMatches())
|
aInfo["ipjailmatches"] = lambda: "\n".join(mi4ip().getMatches())
|
||||||
aInfo["ipfailures"] = lambda: mi4ip(True).getAttempt()
|
aInfo["ipfailures"] = lambda: mi4ip(True).getAttempt()
|
||||||
aInfo["ipjailfailures"] = lambda: mi4ip().getAttempt()
|
aInfo["ipjailfailures"] = lambda: mi4ip().getAttempt()
|
||||||
if self.__banManager.addBanTicket(bTicket):
|
reason = {}
|
||||||
logSys.notice("[%s] Ban %s" % (self._jail.name, aInfo["ip"]))
|
if self.__banManager.addBanTicket(bTicket, reason=reason):
|
||||||
|
cnt += 1
|
||||||
|
logSys.notice("[%s] %sBan %s", self._jail.name, ('' if not bTicket.restored else 'Restore '), ip)
|
||||||
for name, action in self._actions.iteritems():
|
for name, action in self._actions.iteritems():
|
||||||
try:
|
try:
|
||||||
action.ban(aInfo.copy())
|
action.ban(aInfo.copy())
|
||||||
|
@ -322,30 +373,68 @@ class Actions(JailThread, Mapping):
|
||||||
"info '%r': %s",
|
"info '%r': %s",
|
||||||
self._jail.name, name, aInfo, e,
|
self._jail.name, name, aInfo, e,
|
||||||
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
return True
|
# after all actions are processed set banned flag:
|
||||||
|
bTicket.banned = True
|
||||||
else:
|
else:
|
||||||
logSys.notice("[%s] %s already banned" % (self._jail.name,
|
bTicket = reason['ticket']
|
||||||
aInfo["ip"]))
|
# if already banned (otherwise still process some action)
|
||||||
return False
|
if bTicket.banned:
|
||||||
|
# compare time of failure occurrence with time ticket was really banned:
|
||||||
|
diftm = ticket.getTime() - bTicket.getTime()
|
||||||
|
# log already banned with following level:
|
||||||
|
# DEBUG - before 3 seconds - certain interval for it, because of possible latency by recognizing in backends, etc.
|
||||||
|
# NOTICE - before 60 seconds - may still occurre if action are slow, or very high load in backend,
|
||||||
|
# WARNING - after 60 seconds - very long time, something may be wrong
|
||||||
|
ll = logging.DEBUG if diftm < 3 \
|
||||||
|
else logging.NOTICE if diftm < 60 \
|
||||||
|
else logging.WARNING
|
||||||
|
logSys.log(ll, "[%s] %s already banned", self._jail.name, ip)
|
||||||
|
if cnt:
|
||||||
|
logSys.debug("Banned %s / %s, %s ticket(s) in %r", cnt,
|
||||||
|
self.__banManager.getBanTotal(), self.__banManager.size(), self._jail.name)
|
||||||
|
return cnt
|
||||||
|
|
||||||
def __checkUnBan(self):
|
def __checkUnBan(self):
|
||||||
"""Check for IP address to unban.
|
"""Check for IP address to unban.
|
||||||
|
|
||||||
Unban IP addresses which are outdated.
|
Unban IP addresses which are outdated.
|
||||||
"""
|
"""
|
||||||
for ticket in self.__banManager.unBanList(MyTime.time()):
|
lst = self.__banManager.unBanList(MyTime.time())
|
||||||
|
for ticket in lst:
|
||||||
self.__unBan(ticket)
|
self.__unBan(ticket)
|
||||||
|
cnt = len(lst)
|
||||||
|
if cnt:
|
||||||
|
logSys.debug("Unbanned %s, %s ticket(s) in %r",
|
||||||
|
cnt, self.__banManager.size(), self._jail.name)
|
||||||
|
return cnt
|
||||||
|
|
||||||
def __flushBan(self):
|
def __flushBan(self, db=False, actions=None):
|
||||||
"""Flush the ban list.
|
"""Flush the ban list.
|
||||||
|
|
||||||
Unban all IP address which are still in the banning list.
|
Unban all IP address which are still in the banning list.
|
||||||
"""
|
|
||||||
logSys.debug("Flush ban list")
|
|
||||||
for ticket in self.__banManager.flushBanList():
|
|
||||||
self.__unBan(ticket)
|
|
||||||
|
|
||||||
def __unBan(self, ticket):
|
If actions specified, don't flush list - just execute unban for
|
||||||
|
given actions (reload, obsolete resp. removed actions).
|
||||||
|
"""
|
||||||
|
if actions is None:
|
||||||
|
logSys.debug("Flush ban list")
|
||||||
|
lst = self.__banManager.flushBanList()
|
||||||
|
else:
|
||||||
|
lst = iter(self.__banManager)
|
||||||
|
cnt = 0
|
||||||
|
for ticket in lst:
|
||||||
|
# delete ip from database also:
|
||||||
|
if db and self._jail.database is not None:
|
||||||
|
ip = str(ticket.getIP())
|
||||||
|
self._jail.database.delBan(self._jail, ip)
|
||||||
|
# unban ip:
|
||||||
|
self.__unBan(ticket, actions=actions)
|
||||||
|
cnt += 1
|
||||||
|
logSys.debug("Unbanned %s, %s ticket(s) in %r",
|
||||||
|
cnt, self.__banManager.size(), self._jail.name)
|
||||||
|
return cnt
|
||||||
|
|
||||||
|
def __unBan(self, ticket, actions=None):
|
||||||
"""Unbans host corresponding to the ticket.
|
"""Unbans host corresponding to the ticket.
|
||||||
|
|
||||||
Executes the actions in order to unban the host given in the
|
Executes the actions in order to unban the host given in the
|
||||||
|
@ -356,14 +445,20 @@ class Actions(JailThread, Mapping):
|
||||||
ticket : FailTicket
|
ticket : FailTicket
|
||||||
Ticket of failures of which to unban
|
Ticket of failures of which to unban
|
||||||
"""
|
"""
|
||||||
|
if actions is None:
|
||||||
|
unbactions = self._actions
|
||||||
|
else:
|
||||||
|
unbactions = actions
|
||||||
aInfo = dict()
|
aInfo = dict()
|
||||||
aInfo["ip"] = ticket.getIP()
|
aInfo["ip"] = ticket.getIP()
|
||||||
aInfo["failures"] = ticket.getAttempt()
|
aInfo["failures"] = ticket.getAttempt()
|
||||||
aInfo["time"] = ticket.getTime()
|
aInfo["time"] = ticket.getTime()
|
||||||
aInfo["matches"] = "".join(ticket.getMatches())
|
aInfo["matches"] = "".join(ticket.getMatches())
|
||||||
logSys.notice("[%s] Unban %s" % (self._jail.name, aInfo["ip"]))
|
if actions is None:
|
||||||
for name, action in self._actions.iteritems():
|
logSys.notice("[%s] Unban %s", self._jail.name, aInfo["ip"])
|
||||||
|
for name, action in unbactions.iteritems():
|
||||||
try:
|
try:
|
||||||
|
logSys.debug("[%s] action %r: unban %s", self._jail.name, name, aInfo["ip"])
|
||||||
action.unban(aInfo.copy())
|
action.unban(aInfo.copy())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logSys.error(
|
logSys.error(
|
||||||
|
|
|
@ -67,23 +67,28 @@ class RequestHandler(asynchat.async_chat):
|
||||||
# This method is called once we have a complete request.
|
# This method is called once we have a complete request.
|
||||||
|
|
||||||
def found_terminator(self):
|
def found_terminator(self):
|
||||||
# Pop whole buffer
|
try:
|
||||||
message = self.__buffer
|
# Pop whole buffer
|
||||||
self.__buffer = []
|
message = self.__buffer
|
||||||
# Joins the buffer items.
|
self.__buffer = []
|
||||||
message = CSPROTO.EMPTY.join(message)
|
# Joins the buffer items.
|
||||||
# Closes the channel if close was received
|
message = CSPROTO.EMPTY.join(message)
|
||||||
if message == CSPROTO.CLOSE:
|
# Closes the channel if close was received
|
||||||
self.close_when_done()
|
if message == CSPROTO.CLOSE:
|
||||||
return
|
self.close_when_done()
|
||||||
# Deserialize
|
return
|
||||||
message = loads(message)
|
# Deserialize
|
||||||
# Gives the message to the transmitter.
|
message = loads(message)
|
||||||
message = self.__transmitter.proceed(message)
|
# Gives the message to the transmitter.
|
||||||
# Serializes the response.
|
message = self.__transmitter.proceed(message)
|
||||||
message = dumps(message, HIGHEST_PROTOCOL)
|
# Serializes the response.
|
||||||
# Sends the response to the client.
|
message = dumps(message, HIGHEST_PROTOCOL)
|
||||||
self.push(message + CSPROTO.END)
|
# Sends the response to the client.
|
||||||
|
self.push(message + CSPROTO.END)
|
||||||
|
except Exception as e: # pragma: no cover
|
||||||
|
logSys.error("Caught unhandled exception: %r", e,
|
||||||
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
def handle_error(self):
|
def handle_error(self):
|
||||||
e1, e2 = formatExceptionInfo()
|
e1, e2 = formatExceptionInfo()
|
||||||
|
@ -199,6 +204,7 @@ class AsyncServer(asyncore.dispatcher):
|
||||||
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
stopflg = False
|
||||||
if self.__active:
|
if self.__active:
|
||||||
self.__loop = False
|
self.__loop = False
|
||||||
asyncore.dispatcher.close(self)
|
asyncore.dispatcher.close(self)
|
||||||
|
@ -206,11 +212,13 @@ class AsyncServer(asyncore.dispatcher):
|
||||||
# for the server leaves loop, before remove socket
|
# for the server leaves loop, before remove socket
|
||||||
if threading.current_thread() != self.__worker:
|
if threading.current_thread() != self.__worker:
|
||||||
Utils.wait_for(lambda: not self.__active, 1)
|
Utils.wait_for(lambda: not self.__active, 1)
|
||||||
|
stopflg = True
|
||||||
# Remove socket (file) only if it was created:
|
# Remove socket (file) only if it was created:
|
||||||
if self.__init and os.path.exists(self.__sock):
|
if self.__init and os.path.exists(self.__sock):
|
||||||
self._remove_sock()
|
self._remove_sock()
|
||||||
logSys.debug("Removed socket file " + self.__sock)
|
logSys.debug("Removed socket file " + self.__sock)
|
||||||
logSys.debug("Socket shutdown")
|
if stopflg:
|
||||||
|
logSys.debug("Socket shutdown")
|
||||||
self.__active = False
|
self.__active = False
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -51,11 +51,13 @@ class BanManager:
|
||||||
## Mutex used to protect the ban list.
|
## Mutex used to protect the ban list.
|
||||||
self.__lock = Lock()
|
self.__lock = Lock()
|
||||||
## The ban list.
|
## The ban list.
|
||||||
self.__banList = list()
|
self.__banList = dict()
|
||||||
## The amount of time an IP address gets banned.
|
## The amount of time an IP address gets banned.
|
||||||
self.__banTime = 600
|
self.__banTime = 600
|
||||||
## Total number of banned IP address
|
## Total number of banned IP address
|
||||||
self.__banTotal = 0
|
self.__banTotal = 0
|
||||||
|
## The time for next unban process (for performance and load reasons):
|
||||||
|
self.__nextUnbanTime = BanTicket.MAX_TIME
|
||||||
|
|
||||||
##
|
##
|
||||||
# Set the ban time.
|
# Set the ban time.
|
||||||
|
@ -64,11 +66,8 @@ class BanManager:
|
||||||
# @param value the time
|
# @param value the time
|
||||||
|
|
||||||
def setBanTime(self, value):
|
def setBanTime(self, value):
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
|
||||||
self.__banTime = int(value)
|
self.__banTime = int(value)
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get the ban time.
|
# Get the ban time.
|
||||||
|
@ -77,11 +76,8 @@ class BanManager:
|
||||||
# @return the time
|
# @return the time
|
||||||
|
|
||||||
def getBanTime(self):
|
def getBanTime(self):
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
|
||||||
return self.__banTime
|
return self.__banTime
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Set the total number of banned address.
|
# Set the total number of banned address.
|
||||||
|
@ -89,11 +85,8 @@ class BanManager:
|
||||||
# @param value total number
|
# @param value total number
|
||||||
|
|
||||||
def setBanTotal(self, value):
|
def setBanTotal(self, value):
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
|
||||||
self.__banTotal = value
|
self.__banTotal = value
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get the total number of banned address.
|
# Get the total number of banned address.
|
||||||
|
@ -101,11 +94,8 @@ class BanManager:
|
||||||
# @return the total number
|
# @return the total number
|
||||||
|
|
||||||
def getBanTotal(self):
|
def getBanTotal(self):
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
|
||||||
return self.__banTotal
|
return self.__banTotal
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Returns a copy of the IP list.
|
# Returns a copy of the IP list.
|
||||||
|
@ -113,11 +103,17 @@ class BanManager:
|
||||||
# @return IP list
|
# @return IP list
|
||||||
|
|
||||||
def getBanList(self):
|
def getBanList(self):
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
return self.__banList.keys()
|
||||||
return [m.getIP() for m in self.__banList]
|
|
||||||
finally:
|
##
|
||||||
self.__lock.release()
|
# Returns a iterator to ban list (used in reload, so idle).
|
||||||
|
#
|
||||||
|
# @return ban list iterator
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
with self.__lock:
|
||||||
|
return self.__banList.itervalues()
|
||||||
|
|
||||||
##
|
##
|
||||||
# Returns normalized value
|
# Returns normalized value
|
||||||
|
@ -149,7 +145,7 @@ class BanManager:
|
||||||
return return_dict
|
return return_dict
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
try:
|
try:
|
||||||
for banData in self.__banList:
|
for banData in self.__banList.values():
|
||||||
ip = banData.getIP()
|
ip = banData.getIP()
|
||||||
# Reference: http://www.team-cymru.org/Services/ip-to-asn.html#dns
|
# Reference: http://www.team-cymru.org/Services/ip-to-asn.html#dns
|
||||||
question = ip.getPTR(
|
question = ip.getPTR(
|
||||||
|
@ -260,30 +256,33 @@ class BanManager:
|
||||||
# @param ticket the ticket
|
# @param ticket the ticket
|
||||||
# @return True if the IP address is not in the ban list
|
# @return True if the IP address is not in the ban list
|
||||||
|
|
||||||
def addBanTicket(self, ticket):
|
def addBanTicket(self, ticket, reason={}):
|
||||||
try:
|
eob = ticket.getEndOfBanTime(self.__banTime)
|
||||||
self.__lock.acquire()
|
with self.__lock:
|
||||||
# check already banned
|
# check already banned
|
||||||
for oldticket in self.__banList:
|
fid = ticket.getID()
|
||||||
if ticket.getIP() == oldticket.getIP():
|
oldticket = self.__banList.get(fid)
|
||||||
# if already permanent
|
if oldticket:
|
||||||
btold, told = oldticket.getBanTime(self.__banTime), oldticket.getTime()
|
reason['ticket'] = oldticket
|
||||||
if btold == -1:
|
# if new time for end of ban is larger than already banned end-time:
|
||||||
return False
|
if eob > oldticket.getEndOfBanTime(self.__banTime):
|
||||||
# if given time is less than already banned time
|
|
||||||
btnew, tnew = ticket.getBanTime(self.__banTime), ticket.getTime()
|
|
||||||
if btnew != -1 and tnew + btnew <= told + btold:
|
|
||||||
return False
|
|
||||||
# we have longest ban - set new (increment) ban time
|
# we have longest ban - set new (increment) ban time
|
||||||
oldticket.setTime(tnew)
|
reason['prolong'] = 1
|
||||||
oldticket.setBanTime(btnew)
|
btm = ticket.getBanTime(self.__banTime)
|
||||||
return False
|
# if not permanent:
|
||||||
# not yet banned - add new
|
if btm != -1:
|
||||||
self.__banList.append(ticket)
|
diftm = ticket.getTime() - oldticket.getTime()
|
||||||
|
if diftm > 0:
|
||||||
|
btm += diftm
|
||||||
|
oldticket.setBanTime(btm)
|
||||||
|
return False
|
||||||
|
# not yet banned - add new one:
|
||||||
|
self.__banList[fid] = ticket
|
||||||
self.__banTotal += 1
|
self.__banTotal += 1
|
||||||
|
# correct next unban time:
|
||||||
|
if self.__nextUnbanTime > eob:
|
||||||
|
self.__nextUnbanTime = eob
|
||||||
return True
|
return True
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get the size of the ban list.
|
# Get the size of the ban list.
|
||||||
|
@ -291,11 +290,7 @@ class BanManager:
|
||||||
# @return the size
|
# @return the size
|
||||||
|
|
||||||
def size(self):
|
def size(self):
|
||||||
try:
|
return len(self.__banList)
|
||||||
self.__lock.acquire()
|
|
||||||
return len(self.__banList)
|
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Check if a ticket is in the list.
|
# Check if a ticket is in the list.
|
||||||
|
@ -306,10 +301,7 @@ class BanManager:
|
||||||
# @return True if a ticket already exists
|
# @return True if a ticket already exists
|
||||||
|
|
||||||
def _inBanList(self, ticket):
|
def _inBanList(self, ticket):
|
||||||
for i in self.__banList:
|
return ticket.getID() in self.__banList
|
||||||
if ticket.getIP() == i.getIP():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get the list of IP address to unban.
|
# Get the list of IP address to unban.
|
||||||
|
@ -319,22 +311,39 @@ class BanManager:
|
||||||
# @return the list of ticket to unban
|
# @return the list of ticket to unban
|
||||||
|
|
||||||
def unBanList(self, time):
|
def unBanList(self, time):
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
|
||||||
# Permanent banning
|
# Permanent banning
|
||||||
if self.__banTime < 0:
|
if self.__banTime < 0:
|
||||||
return list()
|
return list()
|
||||||
|
|
||||||
# Gets the list of ticket to remove.
|
# Check next unban time:
|
||||||
unBanList = [ticket for ticket in self.__banList if ticket.isTimedOut(time, self.__banTime)]
|
if self.__nextUnbanTime > time:
|
||||||
|
return list()
|
||||||
|
|
||||||
|
# Gets the list of ticket to remove (thereby correct next unban time).
|
||||||
|
unBanList = {}
|
||||||
|
self.__nextUnbanTime = BanTicket.MAX_TIME
|
||||||
|
for fid,ticket in self.__banList.iteritems():
|
||||||
|
# current time greater as end of ban - timed out:
|
||||||
|
eob = ticket.getEndOfBanTime(self.__banTime)
|
||||||
|
if time > eob:
|
||||||
|
unBanList[fid] = ticket
|
||||||
|
elif self.__nextUnbanTime > eob:
|
||||||
|
self.__nextUnbanTime = eob
|
||||||
|
|
||||||
# Removes tickets.
|
# Removes tickets.
|
||||||
self.__banList = [ticket for ticket in self.__banList
|
if len(unBanList):
|
||||||
if ticket not in unBanList]
|
if len(unBanList) / 2.0 <= len(self.__banList) / 3.0:
|
||||||
|
# few as 2/3 should be removed - remove particular items:
|
||||||
|
for fid in unBanList.iterkeys():
|
||||||
|
del self.__banList[fid]
|
||||||
|
else:
|
||||||
|
# create new dictionary without items to be deleted:
|
||||||
|
self.__banList = dict((fid,ticket) for fid,ticket in self.__banList.iteritems() \
|
||||||
|
if fid not in unBanList)
|
||||||
|
|
||||||
return unBanList
|
# return list of tickets:
|
||||||
finally:
|
return unBanList.values()
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Flush the ban list.
|
# Flush the ban list.
|
||||||
|
@ -343,28 +352,21 @@ class BanManager:
|
||||||
# @return the complete ban list
|
# @return the complete ban list
|
||||||
|
|
||||||
def flushBanList(self):
|
def flushBanList(self):
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
uBList = self.__banList.values()
|
||||||
uBList = self.__banList
|
self.__banList = dict()
|
||||||
self.__banList = list()
|
|
||||||
return uBList
|
return uBList
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Gets the ticket for the specified IP.
|
# Gets the ticket for the specified ID (most of the time it is IP-address).
|
||||||
#
|
#
|
||||||
# @return the ticket for the IP or False.
|
# @return the ticket or False.
|
||||||
def getTicketByIP(self, ip):
|
def getTicketByID(self, fid):
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
try:
|
||||||
|
# Return the ticket after removing (popping)
|
||||||
# Find the ticket the IP goes with and return it
|
# if from the ban list.
|
||||||
for i, ticket in enumerate(self.__banList):
|
return self.__banList.pop(fid)
|
||||||
if ticket.getIP() == ip:
|
except KeyError:
|
||||||
# Return the ticket after removing (popping)
|
pass
|
||||||
# if from the ban list.
|
|
||||||
return self.__banList.pop(i)
|
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
return None # if none found
|
return None # if none found
|
||||||
|
|
|
@ -22,7 +22,6 @@ __copyright__ = "Copyright (c) 2013 Steven Hiscocks"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import locale
|
|
||||||
import shutil
|
import shutil
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import sys
|
import sys
|
||||||
|
@ -32,7 +31,7 @@ from threading import RLock
|
||||||
|
|
||||||
from .mytime import MyTime
|
from .mytime import MyTime
|
||||||
from .ticket import FailTicket
|
from .ticket import FailTicket
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger, PREFER_ENC
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
@ -41,7 +40,7 @@ if sys.version_info >= (3,):
|
||||||
def _json_dumps_safe(x):
|
def _json_dumps_safe(x):
|
||||||
try:
|
try:
|
||||||
x = json.dumps(x, ensure_ascii=False).encode(
|
x = json.dumps(x, ensure_ascii=False).encode(
|
||||||
locale.getpreferredencoding(), 'replace')
|
PREFER_ENC, 'replace')
|
||||||
except Exception as e: # pragma: no cover
|
except Exception as e: # pragma: no cover
|
||||||
logSys.error('json dumps failed: %s', e)
|
logSys.error('json dumps failed: %s', e)
|
||||||
x = '{}'
|
x = '{}'
|
||||||
|
@ -50,7 +49,7 @@ if sys.version_info >= (3,):
|
||||||
def _json_loads_safe(x):
|
def _json_loads_safe(x):
|
||||||
try:
|
try:
|
||||||
x = json.loads(x.decode(
|
x = json.loads(x.decode(
|
||||||
locale.getpreferredencoding(), 'replace'))
|
PREFER_ENC, 'replace'))
|
||||||
except Exception as e: # pragma: no cover
|
except Exception as e: # pragma: no cover
|
||||||
logSys.error('json loads failed: %s', e)
|
logSys.error('json loads failed: %s', e)
|
||||||
x = {}
|
x = {}
|
||||||
|
@ -62,14 +61,14 @@ else:
|
||||||
elif isinstance(x, list):
|
elif isinstance(x, list):
|
||||||
return [_normalize(element) for element in x]
|
return [_normalize(element) for element in x]
|
||||||
elif isinstance(x, unicode):
|
elif isinstance(x, unicode):
|
||||||
return x.encode(locale.getpreferredencoding())
|
return x.encode(PREFER_ENC)
|
||||||
else:
|
else:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
def _json_dumps_safe(x):
|
def _json_dumps_safe(x):
|
||||||
try:
|
try:
|
||||||
x = json.dumps(_normalize(x), ensure_ascii=False).decode(
|
x = json.dumps(_normalize(x), ensure_ascii=False).decode(
|
||||||
locale.getpreferredencoding(), 'replace')
|
PREFER_ENC, 'replace')
|
||||||
except Exception as e: # pragma: no cover
|
except Exception as e: # pragma: no cover
|
||||||
logSys.error('json dumps failed: %s', e)
|
logSys.error('json dumps failed: %s', e)
|
||||||
x = '{}'
|
x = '{}'
|
||||||
|
@ -78,7 +77,7 @@ else:
|
||||||
def _json_loads_safe(x):
|
def _json_loads_safe(x):
|
||||||
try:
|
try:
|
||||||
x = _normalize(json.loads(x.decode(
|
x = _normalize(json.loads(x.decode(
|
||||||
locale.getpreferredencoding(), 'replace')))
|
PREFER_ENC, 'replace')))
|
||||||
except Exception as e: # pragma: no cover
|
except Exception as e: # pragma: no cover
|
||||||
logSys.error('json loads failed: %s', e)
|
logSys.error('json loads failed: %s', e)
|
||||||
x = {}
|
x = {}
|
||||||
|
@ -212,7 +211,7 @@ class Fail2BanDb(object):
|
||||||
if newversion == Fail2BanDb.__version__:
|
if newversion == Fail2BanDb.__version__:
|
||||||
logSys.warning( "Database updated from '%i' to '%i'",
|
logSys.warning( "Database updated from '%i' to '%i'",
|
||||||
version, newversion)
|
version, newversion)
|
||||||
else:
|
else: # pragma: no cover
|
||||||
logSys.error( "Database update failed to achieve version '%i'"
|
logSys.error( "Database update failed to achieve version '%i'"
|
||||||
": updated from '%i' to '%i'",
|
": updated from '%i' to '%i'",
|
||||||
Fail2BanDb.__version__, version, newversion)
|
Fail2BanDb.__version__, version, newversion)
|
||||||
|
@ -223,6 +222,11 @@ class Fail2BanDb(object):
|
||||||
cur.execute("PRAGMA journal_mode = MEMORY")
|
cur.execute("PRAGMA journal_mode = MEMORY")
|
||||||
cur.close()
|
cur.close()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
logSys.debug("Close connection to database ...")
|
||||||
|
self._db.close()
|
||||||
|
logSys.info("Connection to database closed.")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filename(self):
|
def filename(self):
|
||||||
"""File name of SQLite3 database file.
|
"""File name of SQLite3 database file.
|
||||||
|
@ -477,7 +481,8 @@ class Fail2BanDb(object):
|
||||||
queryArgs.append(str(ip))
|
queryArgs.append(str(ip))
|
||||||
query += " ORDER BY ip, timeofban desc"
|
query += " ORDER BY ip, timeofban desc"
|
||||||
|
|
||||||
return cur.execute(query, queryArgs)
|
# repack iterator as long as in lock:
|
||||||
|
return list(cur.execute(query, queryArgs))
|
||||||
|
|
||||||
def getBans(self, **kwargs):
|
def getBans(self, **kwargs):
|
||||||
"""Get bans from the database.
|
"""Get bans from the database.
|
||||||
|
@ -576,6 +581,43 @@ class Fail2BanDb(object):
|
||||||
self._bansMergedCache[cacheKey] = tickets if ip is None else ticket
|
self._bansMergedCache[cacheKey] = tickets if ip is None else ticket
|
||||||
return tickets if ip is None else ticket
|
return tickets if ip is None else ticket
|
||||||
|
|
||||||
|
def _getCurrentBans(self, cur, jail = None, ip = None, forbantime=None, fromtime=None):
|
||||||
|
if fromtime is None:
|
||||||
|
fromtime = MyTime.time()
|
||||||
|
queryArgs = []
|
||||||
|
if jail is not None:
|
||||||
|
query = "SELECT ip, timeofban, data FROM bans WHERE jail=?"
|
||||||
|
queryArgs.append(jail.name)
|
||||||
|
else:
|
||||||
|
query = "SELECT ip, max(timeofban), data FROM bans WHERE 1"
|
||||||
|
if ip is not None:
|
||||||
|
query += " AND ip=?"
|
||||||
|
queryArgs.append(ip)
|
||||||
|
if forbantime is not None:
|
||||||
|
query += " AND timeofban > ?"
|
||||||
|
queryArgs.append(fromtime - forbantime)
|
||||||
|
if ip is None:
|
||||||
|
query += " GROUP BY ip ORDER BY ip, timeofban DESC"
|
||||||
|
cur = self._db.cursor()
|
||||||
|
return cur.execute(query, queryArgs)
|
||||||
|
|
||||||
|
def getCurrentBans(self, jail = None, ip = None, forbantime=None, fromtime=None):
|
||||||
|
tickets = []
|
||||||
|
ticket = None
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
results = list(self._getCurrentBans(self._db.cursor(),
|
||||||
|
jail=jail, ip=ip, forbantime=forbantime, fromtime=fromtime))
|
||||||
|
|
||||||
|
if results:
|
||||||
|
for banip, timeofban, data in results:
|
||||||
|
# logSys.debug('restore ticket %r, %r, %r', banip, timeofban, data)
|
||||||
|
ticket = FailTicket(banip, timeofban, data=data)
|
||||||
|
# logSys.debug('restored ticket: %r', ticket)
|
||||||
|
tickets.append(ticket)
|
||||||
|
|
||||||
|
return tickets if ip is None else ticket
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def purge(self, cur):
|
def purge(self, cur):
|
||||||
"""Purge old bans, jails and log files from database.
|
"""Purge old bans, jails and log files from database.
|
||||||
|
|
|
@ -40,11 +40,11 @@ class Regex:
|
||||||
# avoid construction of invalid object.
|
# avoid construction of invalid object.
|
||||||
# @param value the regular expression
|
# @param value the regular expression
|
||||||
|
|
||||||
def __init__(self, regex):
|
def __init__(self, regex, **kwargs):
|
||||||
self._matchCache = None
|
self._matchCache = None
|
||||||
# Perform shortcuts expansions.
|
# Perform shortcuts expansions.
|
||||||
# Resolve "<HOST>" tag using default regular expression for host:
|
# Resolve "<HOST>" tag using default regular expression for host:
|
||||||
regex = Regex._resolveHostTag(regex)
|
regex = Regex._resolveHostTag(regex, **kwargs)
|
||||||
# Replace "<SKIPLINES>" with regular expression for multiple lines.
|
# Replace "<SKIPLINES>" with regular expression for multiple lines.
|
||||||
regexSplit = regex.split("<SKIPLINES>")
|
regexSplit = regex.split("<SKIPLINES>")
|
||||||
regex = regexSplit[0]
|
regex = regexSplit[0]
|
||||||
|
@ -69,22 +69,29 @@ class Regex:
|
||||||
# @return the replaced regular expression as string
|
# @return the replaced regular expression as string
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _resolveHostTag(regex):
|
def _resolveHostTag(regex, useDns="yes"):
|
||||||
# 3 groups instead of <HOST> - separated ipv4, ipv6 and host
|
|
||||||
regex = regex.replace("<HOST>",
|
|
||||||
r"""(?:(?:::f{4,6}:)?(?P<ip4>(?:\d{1,3}\.){3}\d{1,3})|\[?(?P<ip6>(?:[0-9a-fA-F]{1,4}::?|::){1,7}(?:[0-9a-fA-F]{1,4}|(?<=:):))\]?|(?P<dns>[\w\-.^_]*\w))""")
|
|
||||||
# separated ipv4:
|
# separated ipv4:
|
||||||
|
r_host = []
|
||||||
r = r"""(?:::f{4,6}:)?(?P<ip4>(?:\d{1,3}\.){3}\d{1,3})"""
|
r = r"""(?:::f{4,6}:)?(?P<ip4>(?:\d{1,3}\.){3}\d{1,3})"""
|
||||||
regex = regex.replace("<IP4>", r); # self closed
|
regex = regex.replace("<IP4>", r); # self closed
|
||||||
regex = regex.replace("<F-IP4/>", r); # closed
|
regex = regex.replace("<F-IP4/>", r); # closed
|
||||||
|
r_host.append(r)
|
||||||
# separated ipv6:
|
# separated ipv6:
|
||||||
r = r"""(?P<ip6>(?:[0-9a-fA-F]{1,4}::?|::){1,7}(?:[0-9a-fA-F]{1,4}?|(?<=:):))"""
|
r = r"""(?P<ip6>(?:[0-9a-fA-F]{1,4}::?|::){1,7}(?:[0-9a-fA-F]{1,4}?|(?<=:):))"""
|
||||||
regex = regex.replace("<IP6>", r); # self closed
|
regex = regex.replace("<IP6>", r); # self closed
|
||||||
regex = regex.replace("<F-IP6/>", r); # closed
|
regex = regex.replace("<F-IP6/>", r); # closed
|
||||||
|
r_host.append(r"""\[?%s\]?""" % (r,)); # enclose ipv6 in optional [] in host-regex
|
||||||
|
# 2 address groups instead of <ADDR> - in opposition to `<HOST>`,
|
||||||
|
# for separate usage of 2 address groups only (regardless of `usedns`), `ip4` and `ip6` together
|
||||||
|
regex = regex.replace("<ADDR>", "(?:%s)" % ("|".join(r_host),))
|
||||||
# separated dns:
|
# separated dns:
|
||||||
r = r"""(?P<dns>[\w\-.^_]*\w)"""
|
r = r"""(?P<dns>[\w\-.^_]*\w)"""
|
||||||
regex = regex.replace("<DNS>", r); # self closed
|
regex = regex.replace("<DNS>", r); # self closed
|
||||||
regex = regex.replace("<F-DNS/>", r); # closed
|
regex = regex.replace("<F-DNS/>", r); # closed
|
||||||
|
if useDns not in ("no",):
|
||||||
|
r_host.append(r)
|
||||||
|
# 3 groups instead of <HOST> - separated ipv4, ipv6 and host (dns)
|
||||||
|
regex = regex.replace("<HOST>", "(?:%s)" % ("|".join(r_host),))
|
||||||
# default failure-id as no space tag:
|
# default failure-id as no space tag:
|
||||||
regex = regex.replace("<F-ID/>", r"""(?P<fid>\S+)"""); # closed
|
regex = regex.replace("<F-ID/>", r"""(?P<fid>\S+)"""); # closed
|
||||||
# default failure port, like 80 or http :
|
# default failure port, like 80 or http :
|
||||||
|
@ -249,9 +256,9 @@ class FailRegex(Regex):
|
||||||
# avoid construction of invalid object.
|
# avoid construction of invalid object.
|
||||||
# @param value the regular expression
|
# @param value the regular expression
|
||||||
|
|
||||||
def __init__(self, regex):
|
def __init__(self, regex, **kwargs):
|
||||||
# Initializes the parent.
|
# Initializes the parent.
|
||||||
Regex.__init__(self, regex)
|
Regex.__init__(self, regex, **kwargs)
|
||||||
# Check for group "dns", "ip4", "ip6", "fid"
|
# Check for group "dns", "ip4", "ip6", "fid"
|
||||||
if not [grp for grp in FAILURE_ID_GROPS if grp in self._regexObj.groupindex]:
|
if not [grp for grp in FAILURE_ID_GROPS if grp in self._regexObj.groupindex]:
|
||||||
raise RegexException("No failure-id group in '%s'" % self._regex)
|
raise RegexException("No failure-id group in '%s'" % self._regex)
|
||||||
|
|
|
@ -24,7 +24,6 @@ __license__ = "GPL"
|
||||||
import codecs
|
import codecs
|
||||||
import datetime
|
import datetime
|
||||||
import fcntl
|
import fcntl
|
||||||
import locale
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -40,7 +39,7 @@ from .datetemplate import DatePatternRegex, DateEpoch, DateTai64n
|
||||||
from .mytime import MyTime
|
from .mytime import MyTime
|
||||||
from .failregex import FailRegex, Regex, RegexException
|
from .failregex import FailRegex, Regex, RegexException
|
||||||
from .action import CommandAction
|
from .action import CommandAction
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger, PREFER_ENC
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
@ -87,9 +86,10 @@ class Filter(JailThread):
|
||||||
## External command
|
## External command
|
||||||
self.__ignoreCommand = False
|
self.__ignoreCommand = False
|
||||||
## Default or preferred encoding (to decode bytes from file or journal):
|
## Default or preferred encoding (to decode bytes from file or journal):
|
||||||
self.__encoding = locale.getpreferredencoding()
|
self.__encoding = PREFER_ENC
|
||||||
## Error counter
|
## Error counter (protected, so can be used in filter implementations)
|
||||||
self.__errors = 0
|
## if it reached 100 (at once), run-cycle will go idle
|
||||||
|
self._errors = 0
|
||||||
## Ticks counter
|
## Ticks counter
|
||||||
self.ticks = 0
|
self.ticks = 0
|
||||||
|
|
||||||
|
@ -100,6 +100,31 @@ class Filter(JailThread):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "%s(%r)" % (self.__class__.__name__, self.jail)
|
return "%s(%r)" % (self.__class__.__name__, self.jail)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def jailName(self):
|
||||||
|
return (self.jail is not None and self.jail.name or "~jailless~")
|
||||||
|
|
||||||
|
def clearAllParams(self):
|
||||||
|
""" Clear all lists/dicts parameters (used by reloading)
|
||||||
|
"""
|
||||||
|
self.delFailRegex()
|
||||||
|
self.delIgnoreRegex()
|
||||||
|
self.delIgnoreIP()
|
||||||
|
|
||||||
|
def reload(self, begin=True):
|
||||||
|
""" Begin or end of reloading resp. refreshing of all parameters
|
||||||
|
"""
|
||||||
|
if begin:
|
||||||
|
self.clearAllParams()
|
||||||
|
if hasattr(self, 'getLogPaths'):
|
||||||
|
self._reload_logs = dict((k, 1) for k in self.getLogPaths())
|
||||||
|
else:
|
||||||
|
if hasattr(self, '_reload_logs'):
|
||||||
|
# if it was not reloaded - remove obsolete log file:
|
||||||
|
for path in self._reload_logs:
|
||||||
|
self.delLogPath(path)
|
||||||
|
delattr(self, '_reload_logs')
|
||||||
|
|
||||||
##
|
##
|
||||||
# Add a regular expression which matches the failure.
|
# Add a regular expression which matches the failure.
|
||||||
#
|
#
|
||||||
|
@ -109,18 +134,23 @@ class Filter(JailThread):
|
||||||
|
|
||||||
def addFailRegex(self, value):
|
def addFailRegex(self, value):
|
||||||
try:
|
try:
|
||||||
regex = FailRegex(value)
|
regex = FailRegex(value, useDns=self.__useDns)
|
||||||
self.__failRegex.append(regex)
|
self.__failRegex.append(regex)
|
||||||
if "\n" in regex.getRegex() and not self.getMaxLines() > 1:
|
if "\n" in regex.getRegex() and not self.getMaxLines() > 1:
|
||||||
logSys.warning(
|
logSys.warning(
|
||||||
"Mutliline regex set for jail '%s' "
|
"Mutliline regex set for jail %r "
|
||||||
"but maxlines not greater than 1")
|
"but maxlines not greater than 1", self.jailName)
|
||||||
except RegexException as e:
|
except RegexException as e:
|
||||||
logSys.error(e)
|
logSys.error(e)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def delFailRegex(self, index):
|
def delFailRegex(self, index=None):
|
||||||
try:
|
try:
|
||||||
|
# clear all:
|
||||||
|
if index is None:
|
||||||
|
del self.__failRegex[:]
|
||||||
|
return
|
||||||
|
# delete by index:
|
||||||
del self.__failRegex[index]
|
del self.__failRegex[index]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logSys.error("Cannot remove regular expression. Index %d is not "
|
logSys.error("Cannot remove regular expression. Index %d is not "
|
||||||
|
@ -146,14 +176,19 @@ class Filter(JailThread):
|
||||||
|
|
||||||
def addIgnoreRegex(self, value):
|
def addIgnoreRegex(self, value):
|
||||||
try:
|
try:
|
||||||
regex = Regex(value)
|
regex = Regex(value, useDns=self.__useDns)
|
||||||
self.__ignoreRegex.append(regex)
|
self.__ignoreRegex.append(regex)
|
||||||
except RegexException as e:
|
except RegexException as e:
|
||||||
logSys.error(e)
|
logSys.error(e)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def delIgnoreRegex(self, index):
|
def delIgnoreRegex(self, index=None):
|
||||||
try:
|
try:
|
||||||
|
# clear all:
|
||||||
|
if index is None:
|
||||||
|
del self.__ignoreRegex[:]
|
||||||
|
return
|
||||||
|
# delete by index:
|
||||||
del self.__ignoreRegex[index]
|
del self.__ignoreRegex[index]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logSys.error("Cannot remove regular expression. Index %d is not "
|
logSys.error("Cannot remove regular expression. Index %d is not "
|
||||||
|
@ -203,7 +238,7 @@ class Filter(JailThread):
|
||||||
value = MyTime.str2seconds(value)
|
value = MyTime.str2seconds(value)
|
||||||
self.__findTime = value
|
self.__findTime = value
|
||||||
self.failManager.setMaxTime(value)
|
self.failManager.setMaxTime(value)
|
||||||
logSys.info("Set findtime = %s" % value)
|
logSys.info(" findtime: %s", value)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get the time needed to find a failure.
|
# Get the time needed to find a failure.
|
||||||
|
@ -232,10 +267,10 @@ class Filter(JailThread):
|
||||||
template = DatePatternRegex(pattern)
|
template = DatePatternRegex(pattern)
|
||||||
self.dateDetector = DateDetector()
|
self.dateDetector = DateDetector()
|
||||||
self.dateDetector.appendTemplate(template)
|
self.dateDetector.appendTemplate(template)
|
||||||
logSys.info("Date pattern set to `%r`: `%s`" %
|
logSys.info(" date pattern `%r`: `%s`",
|
||||||
(pattern, template.name))
|
pattern, template.name)
|
||||||
logSys.debug("Date pattern regex for %r: %s" %
|
logSys.debug(" date pattern regex for %r: %s",
|
||||||
(pattern, template.regex))
|
pattern, template.regex)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get the date detector pattern, or Default Detectors if not changed
|
# Get the date detector pattern, or Default Detectors if not changed
|
||||||
|
@ -261,7 +296,7 @@ class Filter(JailThread):
|
||||||
|
|
||||||
def setMaxRetry(self, value):
|
def setMaxRetry(self, value):
|
||||||
self.failManager.setMaxRetry(value)
|
self.failManager.setMaxRetry(value)
|
||||||
logSys.info("Set maxRetry = %s" % value)
|
logSys.info(" maxRetry: %s", value)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get the maximum retry value.
|
# Get the maximum retry value.
|
||||||
|
@ -280,7 +315,7 @@ class Filter(JailThread):
|
||||||
if int(value) <= 0:
|
if int(value) <= 0:
|
||||||
raise ValueError("maxlines must be integer greater than zero")
|
raise ValueError("maxlines must be integer greater than zero")
|
||||||
self.__lineBufferSize = int(value)
|
self.__lineBufferSize = int(value)
|
||||||
logSys.info("Set maxlines = %i" % self.__lineBufferSize)
|
logSys.info(" maxLines: %i", self.__lineBufferSize)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get the maximum line buffer size.
|
# Get the maximum line buffer size.
|
||||||
|
@ -297,10 +332,10 @@ class Filter(JailThread):
|
||||||
|
|
||||||
def setLogEncoding(self, encoding):
|
def setLogEncoding(self, encoding):
|
||||||
if encoding.lower() == "auto":
|
if encoding.lower() == "auto":
|
||||||
encoding = locale.getpreferredencoding()
|
encoding = PREFER_ENC
|
||||||
codecs.lookup(encoding) # Raise LookupError if invalid codec
|
codecs.lookup(encoding) # Raise LookupError if invalid codec
|
||||||
self.__encoding = encoding
|
self.__encoding = encoding
|
||||||
logSys.info("Set jail log file encoding to %s" % encoding)
|
logSys.info(" encoding: %s" % encoding)
|
||||||
return encoding
|
return encoding
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -375,16 +410,21 @@ class Filter(JailThread):
|
||||||
ip = IPAddr(ipstr)
|
ip = IPAddr(ipstr)
|
||||||
|
|
||||||
# log and append to ignore list
|
# log and append to ignore list
|
||||||
logSys.debug("Add %r to ignore list (%r)", ip, ipstr)
|
logSys.debug(" Add %r to ignore list (%r)", ip, ipstr)
|
||||||
self.__ignoreIpList.append(ip)
|
self.__ignoreIpList.append(ip)
|
||||||
|
|
||||||
def delIgnoreIP(self, ip):
|
def delIgnoreIP(self, ip=None):
|
||||||
logSys.debug("Remove %r from ignore list", ip)
|
# clear all:
|
||||||
|
if ip is None:
|
||||||
|
del self.__ignoreIpList[:]
|
||||||
|
return
|
||||||
|
# delete by ip:
|
||||||
|
logSys.debug(" Remove %r from ignore list", ip)
|
||||||
self.__ignoreIpList.remove(ip)
|
self.__ignoreIpList.remove(ip)
|
||||||
|
|
||||||
def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"):
|
def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"):
|
||||||
if log_ignore:
|
if log_ignore:
|
||||||
logSys.info("[%s] Ignore %s by %s" % (self.jail.name, ip, ignore_source))
|
logSys.info("[%s] Ignore %s by %s" % (self.jailName, ip, ignore_source))
|
||||||
|
|
||||||
def getIgnoreIP(self):
|
def getIgnoreIP(self):
|
||||||
return self.__ignoreIpList
|
return self.__ignoreIpList
|
||||||
|
@ -415,29 +455,6 @@ class Filter(JailThread):
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if sys.version_info >= (3,):
|
|
||||||
@staticmethod
|
|
||||||
def uni_decode(x, enc, errors='strict'):
|
|
||||||
try:
|
|
||||||
if isinstance(x, bytes):
|
|
||||||
return x.decode(enc, errors)
|
|
||||||
return x
|
|
||||||
except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - unsure if reachable
|
|
||||||
if errors != 'strict':
|
|
||||||
raise
|
|
||||||
return uni_decode(x, enc, 'replace')
|
|
||||||
else:
|
|
||||||
@staticmethod
|
|
||||||
def uni_decode(x, enc, errors='strict'):
|
|
||||||
try:
|
|
||||||
if isinstance(x, unicode):
|
|
||||||
return x.encode(enc, errors)
|
|
||||||
return x
|
|
||||||
except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - unsure if reachable
|
|
||||||
if errors != 'strict':
|
|
||||||
raise
|
|
||||||
return uni_decode(x, enc, 'replace')
|
|
||||||
|
|
||||||
def processLine(self, line, date=None, returnRawHost=False,
|
def processLine(self, line, date=None, returnRawHost=False,
|
||||||
checkAllRegex=False, checkFindTime=False):
|
checkAllRegex=False, checkFindTime=False):
|
||||||
"""Split the time portion from log msg and return findFailures on them
|
"""Split the time portion from log msg and return findFailures on them
|
||||||
|
@ -478,24 +495,28 @@ class Filter(JailThread):
|
||||||
if self.inIgnoreIPList(ip, log_ignore=True):
|
if self.inIgnoreIPList(ip, log_ignore=True):
|
||||||
continue
|
continue
|
||||||
logSys.info(
|
logSys.info(
|
||||||
"[%s] Found %s - %s", self.jail.name, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S")
|
"[%s] Found %s - %s", self.jailName, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
)
|
)
|
||||||
tick = FailTicket(ip, unixTime, lines, data=fail)
|
tick = FailTicket(ip, unixTime, lines, data=fail)
|
||||||
self.failManager.addFailure(tick)
|
self.failManager.addFailure(tick)
|
||||||
# reset (halve) error counter (successfully processed line):
|
# reset (halve) error counter (successfully processed line):
|
||||||
if self.__errors:
|
if self._errors:
|
||||||
self.__errors //= 2
|
self._errors //= 2
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logSys.error("Failed to process line: %r, caught exception: %r", line, e,
|
logSys.error("Failed to process line: %r, caught exception: %r", line, e,
|
||||||
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
# incr error counter, stop processing (going idle) after 100th error :
|
# incr common error counter:
|
||||||
self.__errors += 1
|
self.commonError()
|
||||||
# sleep a little bit (to get around time-related errors):
|
|
||||||
time.sleep(self.sleeptime)
|
def commonError(self):
|
||||||
if self.__errors >= 100:
|
# incr error counter, stop processing (going idle) after 100th error :
|
||||||
logSys.error("Too many errors at once (%s), going idle", self.__errors)
|
self._errors += 1
|
||||||
self.__errors //= 2
|
# sleep a little bit (to get around time-related errors):
|
||||||
self.idle = True
|
time.sleep(self.sleeptime)
|
||||||
|
if self._errors >= 100:
|
||||||
|
logSys.error("Too many errors at once (%s), going idle", self._errors)
|
||||||
|
self._errors //= 2
|
||||||
|
self.idle = True
|
||||||
|
|
||||||
##
|
##
|
||||||
# Returns true if the line should be ignored.
|
# Returns true if the line should be ignored.
|
||||||
|
@ -657,7 +678,10 @@ class FileFilter(Filter):
|
||||||
|
|
||||||
def addLogPath(self, path, tail=False, autoSeek=True):
|
def addLogPath(self, path, tail=False, autoSeek=True):
|
||||||
if path in self.__logs:
|
if path in self.__logs:
|
||||||
logSys.error(path + " already exists")
|
if hasattr(self, '_reload_logs') and path in self._reload_logs:
|
||||||
|
del self._reload_logs[path]
|
||||||
|
else:
|
||||||
|
logSys.error(path + " already exists")
|
||||||
else:
|
else:
|
||||||
log = FileContainer(path, self.getLogEncoding(), tail)
|
log = FileContainer(path, self.getLogEncoding(), tail)
|
||||||
db = self.jail.database
|
db = self.jail.database
|
||||||
|
@ -666,7 +690,7 @@ class FileFilter(Filter):
|
||||||
if lastpos and not tail:
|
if lastpos and not tail:
|
||||||
log.setPos(lastpos)
|
log.setPos(lastpos)
|
||||||
self.__logs[path] = log
|
self.__logs[path] = log
|
||||||
logSys.info("Added logfile = %s (pos = %s, hash = %s)" , path, log.getPos(), log.getHash())
|
logSys.info("Added logfile: %r (pos = %s, hash = %s)" , path, log.getPos(), log.getHash())
|
||||||
if autoSeek:
|
if autoSeek:
|
||||||
# if default, seek to "current time" - "find time":
|
# if default, seek to "current time" - "find time":
|
||||||
if isinstance(autoSeek, bool):
|
if isinstance(autoSeek, bool):
|
||||||
|
@ -692,7 +716,7 @@ class FileFilter(Filter):
|
||||||
db = self.jail.database
|
db = self.jail.database
|
||||||
if db is not None:
|
if db is not None:
|
||||||
db.updateLog(self.jail, log)
|
db.updateLog(self.jail, log)
|
||||||
logSys.info("Removed logfile = %s" % path)
|
logSys.info("Removed logfile: %r" % path)
|
||||||
self._delLogPath(path)
|
self._delLogPath(path)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -759,49 +783,48 @@ class FileFilter(Filter):
|
||||||
if log is None:
|
if log is None:
|
||||||
logSys.error("Unable to get failures in " + filename)
|
logSys.error("Unable to get failures in " + filename)
|
||||||
return False
|
return False
|
||||||
# Try to open log file.
|
# We should always close log (file), otherwise may be locked (log-rotate, etc.)
|
||||||
try:
|
try:
|
||||||
has_content = log.open()
|
# Try to open log file.
|
||||||
# see http://python.org/dev/peps/pep-3151/
|
|
||||||
except IOError as e:
|
|
||||||
logSys.error("Unable to open %s" % filename)
|
|
||||||
logSys.exception(e)
|
|
||||||
return False
|
|
||||||
except OSError as e: # pragma: no cover - requires race condition to tigger this
|
|
||||||
logSys.error("Error opening %s" % filename)
|
|
||||||
logSys.exception(e)
|
|
||||||
return False
|
|
||||||
except Exception as e: # pragma: no cover - Requires implemention error in FileContainer to generate
|
|
||||||
logSys.error("Internal error in FileContainer open method - please report as a bug to https://github.com/fail2ban/fail2ban/issues")
|
|
||||||
logSys.exception(e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# seek to find time for first usage only (prevent performance decline with polling of big files)
|
|
||||||
if self.__autoSeek.get(filename):
|
|
||||||
startTime = self.__autoSeek[filename]
|
|
||||||
del self.__autoSeek[filename]
|
|
||||||
# prevent completely read of big files first time (after start of service),
|
|
||||||
# initial seek to start time using half-interval search algorithm:
|
|
||||||
try:
|
try:
|
||||||
self.seekToTime(log, startTime)
|
has_content = log.open()
|
||||||
except Exception as e: # pragma: no cover
|
# see http://python.org/dev/peps/pep-3151/
|
||||||
logSys.error("Error during seek to start time in \"%s\"", filename)
|
except IOError as e:
|
||||||
raise
|
logSys.error("Unable to open %s" % filename)
|
||||||
|
logSys.exception(e)
|
||||||
|
return False
|
||||||
|
except OSError as e: # pragma: no cover - requires race condition to tigger this
|
||||||
|
logSys.error("Error opening %s" % filename)
|
||||||
|
logSys.exception(e)
|
||||||
|
return False
|
||||||
|
except Exception as e: # pragma: no cover - Requires implemention error in FileContainer to generate
|
||||||
|
logSys.error("Internal error in FileContainer open method - please report as a bug to https://github.com/fail2ban/fail2ban/issues")
|
||||||
logSys.exception(e)
|
logSys.exception(e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# yoh: has_content is just a bool, so do not expect it to
|
# seek to find time for first usage only (prevent performance decline with polling of big files)
|
||||||
# change -- loop is exited upon break, and is not entered at
|
if self.__autoSeek.get(filename):
|
||||||
# all if upon container opening that one was empty. If we
|
startTime = self.__autoSeek[filename]
|
||||||
# start reading tested to be empty container -- race condition
|
del self.__autoSeek[filename]
|
||||||
# might occur leading at least to tests failures.
|
# prevent completely read of big files first time (after start of service),
|
||||||
while has_content:
|
# initial seek to start time using half-interval search algorithm:
|
||||||
line = log.readline()
|
try:
|
||||||
if not line or not self.active:
|
self.seekToTime(log, startTime)
|
||||||
# The jail reached the bottom or has been stopped
|
except Exception as e: # pragma: no cover
|
||||||
break
|
logSys.error("Error during seek to start time in \"%s\"", filename)
|
||||||
self.processLineAndAdd(line)
|
raise
|
||||||
log.close()
|
logSys.exception(e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if has_content:
|
||||||
|
while not self.idle:
|
||||||
|
line = log.readline()
|
||||||
|
if not line or not self.active:
|
||||||
|
# The jail reached the bottom or has been stopped
|
||||||
|
break
|
||||||
|
self.processLineAndAdd(line)
|
||||||
|
finally:
|
||||||
|
log.close()
|
||||||
db = self.jail.database
|
db = self.jail.database
|
||||||
if db is not None:
|
if db is not None:
|
||||||
db.updateLog(self.jail, log)
|
db.updateLog(self.jail, log)
|
||||||
|
@ -1055,10 +1078,14 @@ _decode_line_warn = {}
|
||||||
|
|
||||||
class JournalFilter(Filter): # pragma: systemd no cover
|
class JournalFilter(Filter): # pragma: systemd no cover
|
||||||
|
|
||||||
|
def clearAllParams(self):
|
||||||
|
super(JournalFilter, self).clearAllParams()
|
||||||
|
self.delJournalMatch()
|
||||||
|
|
||||||
def addJournalMatch(self, match): # pragma: no cover - Base class, not used
|
def addJournalMatch(self, match): # pragma: no cover - Base class, not used
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def delJournalMatch(self, match): # pragma: no cover - Base class, not used
|
def delJournalMatch(self, match=None): # pragma: no cover - Base class, not used
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def getJournalMatch(self, match): # pragma: no cover - Base class, not used
|
def getJournalMatch(self, match): # pragma: no cover - Base class, not used
|
||||||
|
|
|
@ -124,14 +124,15 @@ class FilterGamin(FileFilter):
|
||||||
while self.active:
|
while self.active:
|
||||||
if self.idle:
|
if self.idle:
|
||||||
# wait a little bit here for not idle, to prevent hi-load:
|
# wait a little bit here for not idle, to prevent hi-load:
|
||||||
if not Utils.wait_for(lambda: not self.idle,
|
if not Utils.wait_for(lambda: not self.active or not self.idle,
|
||||||
self.sleeptime * 10, self.sleeptime
|
self.sleeptime * 10, self.sleeptime
|
||||||
):
|
):
|
||||||
self.ticks += 1
|
self.ticks += 1
|
||||||
continue
|
continue
|
||||||
Utils.wait_for(self._handleEvents, self.sleeptime)
|
Utils.wait_for(lambda: not self.active or self._handleEvents(),
|
||||||
|
self.sleeptime)
|
||||||
self.ticks += 1
|
self.ticks += 1
|
||||||
logSys.debug(self.jail.name + ": filter terminated")
|
logSys.debug("[%s] filter terminated", self.jailName)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
|
|
@ -31,7 +31,7 @@ from .failmanager import FailManagerEmpty
|
||||||
from .filter import FileFilter
|
from .filter import FileFilter
|
||||||
from .mytime import MyTime
|
from .mytime import MyTime
|
||||||
from .utils import Utils
|
from .utils import Utils
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger, logging
|
||||||
|
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
|
@ -101,14 +101,15 @@ class FilterPoll(FileFilter):
|
||||||
logSys.log(6, "Woke up idle=%s with %d files monitored",
|
logSys.log(6, "Woke up idle=%s with %d files monitored",
|
||||||
self.idle, self.getLogCount())
|
self.idle, self.getLogCount())
|
||||||
if self.idle:
|
if self.idle:
|
||||||
if not Utils.wait_for(lambda: not self.idle,
|
if not Utils.wait_for(lambda: not self.active or not self.idle,
|
||||||
self.sleeptime * 10, self.sleeptime
|
self.sleeptime * 10, self.sleeptime
|
||||||
):
|
):
|
||||||
self.ticks += 1
|
self.ticks += 1
|
||||||
continue
|
continue
|
||||||
# Get file modification
|
# Get file modification
|
||||||
modlst = []
|
modlst = []
|
||||||
Utils.wait_for(lambda: self.getModified(modlst), self.sleeptime)
|
Utils.wait_for(lambda: not self.active or self.getModified(modlst),
|
||||||
|
self.sleeptime)
|
||||||
for filename in modlst:
|
for filename in modlst:
|
||||||
self.getFailures(filename)
|
self.getFailures(filename)
|
||||||
self.__modified = True
|
self.__modified = True
|
||||||
|
@ -122,9 +123,7 @@ class FilterPoll(FileFilter):
|
||||||
except FailManagerEmpty:
|
except FailManagerEmpty:
|
||||||
self.failManager.cleanup(MyTime.time())
|
self.failManager.cleanup(MyTime.time())
|
||||||
self.__modified = False
|
self.__modified = False
|
||||||
logSys.debug(
|
logSys.debug("[%s] filter terminated", self.jailName)
|
||||||
(self.jail is not None and self.jail.name or "jailless") +
|
|
||||||
" filter terminated")
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -137,28 +136,34 @@ class FilterPoll(FileFilter):
|
||||||
try:
|
try:
|
||||||
logStats = os.stat(filename)
|
logStats = os.stat(filename)
|
||||||
stats = logStats.st_mtime, logStats.st_ino, logStats.st_size
|
stats = logStats.st_mtime, logStats.st_ino, logStats.st_size
|
||||||
pstats = self.__prevStats.get(filename, ())
|
pstats = self.__prevStats.get(filename, (0))
|
||||||
self.__file404Cnt[filename] = 0
|
if logSys.getEffectiveLevel() <= 5:
|
||||||
if logSys.getEffectiveLevel() <= 7:
|
|
||||||
# we do not want to waste time on strftime etc if not necessary
|
# we do not want to waste time on strftime etc if not necessary
|
||||||
dt = logStats.st_mtime - pstats[0]
|
dt = logStats.st_mtime - pstats[0]
|
||||||
logSys.log(7, "Checking %s for being modified. Previous/current stats: %s / %s. dt: %s",
|
logSys.log(5, "Checking %s for being modified. Previous/current stats: %s / %s. dt: %s",
|
||||||
filename, pstats, stats, dt)
|
filename, pstats, stats, dt)
|
||||||
# os.system("stat %s | grep Modify" % filename)
|
# os.system("stat %s | grep Modify" % filename)
|
||||||
|
self.__file404Cnt[filename] = 0
|
||||||
if pstats == stats:
|
if pstats == stats:
|
||||||
return False
|
return False
|
||||||
logSys.debug("%s has been modified", filename)
|
logSys.debug("%s has been modified", filename)
|
||||||
self.__prevStats[filename] = stats
|
self.__prevStats[filename] = stats
|
||||||
return True
|
return True
|
||||||
except OSError as e:
|
except Exception as e:
|
||||||
logSys.error("Unable to get stat on %s because of: %s"
|
# stil alive (may be deleted because multi-threaded):
|
||||||
% (filename, e))
|
if not self.getLog(filename):
|
||||||
|
logSys.warning("Log %r seems to be down: %s", filename, e)
|
||||||
|
return
|
||||||
|
# log error:
|
||||||
|
if self.__file404Cnt[filename] < 2:
|
||||||
|
logSys.error("Unable to get stat on %s because of: %s",
|
||||||
|
filename, e,
|
||||||
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
|
# increase file and common error counters:
|
||||||
self.__file404Cnt[filename] += 1
|
self.__file404Cnt[filename] += 1
|
||||||
if self.__file404Cnt[filename] > 2:
|
self.commonError()
|
||||||
logSys.warning("Too many errors. Setting the jail idle")
|
if self.__file404Cnt[filename] > 50:
|
||||||
if self.jail is not None:
|
logSys.warning("Too many errors. Remove file %r from monitoring process", filename)
|
||||||
self.jail.idle = True
|
|
||||||
else:
|
|
||||||
logSys.warning("No jail is assigned to %s" % self)
|
|
||||||
self.__file404Cnt[filename] = 0
|
self.__file404Cnt[filename] = 0
|
||||||
|
self.delLogPath(filename)
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -76,7 +76,7 @@ class FilterPyinotify(FileFilter):
|
||||||
logSys.debug("Created FilterPyinotify")
|
logSys.debug("Created FilterPyinotify")
|
||||||
|
|
||||||
def callback(self, event, origin=''):
|
def callback(self, event, origin=''):
|
||||||
logSys.debug("%sCallback for Event: %s", origin, event)
|
logSys.log(7, "[%s] %sCallback for Event: %s", self.jailName, origin, event)
|
||||||
path = event.pathname
|
path = event.pathname
|
||||||
if event.mask & ( pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO ):
|
if event.mask & ( pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO ):
|
||||||
# skip directories altogether
|
# skip directories altogether
|
||||||
|
@ -119,14 +119,15 @@ class FilterPyinotify(FileFilter):
|
||||||
logSys.debug("Added file watcher for %s", path)
|
logSys.debug("Added file watcher for %s", path)
|
||||||
|
|
||||||
def _delFileWatcher(self, path):
|
def _delFileWatcher(self, path):
|
||||||
wdInt = self.__watches[path]
|
try:
|
||||||
wd = self.__monitor.rm_watch(wdInt)
|
wdInt = self.__watches.pop(path)
|
||||||
if wd[wdInt]:
|
wd = self.__monitor.rm_watch(wdInt)
|
||||||
del self.__watches[path]
|
if wd[wdInt]:
|
||||||
logSys.debug("Removed file watcher for %s", path)
|
logSys.debug("Removed file watcher for %s", path)
|
||||||
return True
|
return True
|
||||||
else:
|
except KeyError: # pragma: no cover
|
||||||
return False
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
##
|
##
|
||||||
# Add a log file path
|
# Add a log file path
|
||||||
|
@ -158,8 +159,11 @@ class FilterPyinotify(FileFilter):
|
||||||
if k.startswith(path_dir + pathsep)]):
|
if k.startswith(path_dir + pathsep)]):
|
||||||
# Remove watches for the directory
|
# Remove watches for the directory
|
||||||
# since there is no other monitored file under this directory
|
# since there is no other monitored file under this directory
|
||||||
wdInt = self.__watches.pop(path_dir)
|
try:
|
||||||
self.__monitor.rm_watch(wdInt)
|
wdInt = self.__watches.pop(path_dir)
|
||||||
|
self.__monitor.rm_watch(wdInt)
|
||||||
|
except KeyError: # pragma: no cover
|
||||||
|
pass
|
||||||
logSys.debug("Removed monitor for the parent directory %s", path_dir)
|
logSys.debug("Removed monitor for the parent directory %s", path_dir)
|
||||||
|
|
||||||
# pyinotify.ProcessEvent default handler:
|
# pyinotify.ProcessEvent default handler:
|
||||||
|
@ -174,7 +178,7 @@ class FilterPyinotify(FileFilter):
|
||||||
# slow check events while idle:
|
# slow check events while idle:
|
||||||
def __check_events(self, *args, **kwargs):
|
def __check_events(self, *args, **kwargs):
|
||||||
if self.idle:
|
if self.idle:
|
||||||
if Utils.wait_for(lambda: not self.idle,
|
if Utils.wait_for(lambda: not self.active or not self.idle,
|
||||||
self.sleeptime * 10, self.sleeptime
|
self.sleeptime * 10, self.sleeptime
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
@ -190,11 +194,12 @@ class FilterPyinotify(FileFilter):
|
||||||
def run(self):
|
def run(self):
|
||||||
prcevent = pyinotify.ProcessEvent()
|
prcevent = pyinotify.ProcessEvent()
|
||||||
prcevent.process_default = self.__process_default
|
prcevent.process_default = self.__process_default
|
||||||
|
## timeout for pyinotify must be set in milliseconds (our time values are floats contain seconds)
|
||||||
self.__notifier = pyinotify.ThreadedNotifier(self.__monitor,
|
self.__notifier = pyinotify.ThreadedNotifier(self.__monitor,
|
||||||
prcevent, timeout=self.sleeptime)
|
prcevent, timeout=self.sleeptime * 1000)
|
||||||
self.__notifier.check_events = self.__check_events
|
self.__notifier.check_events = self.__check_events
|
||||||
self.__notifier.start()
|
self.__notifier.start()
|
||||||
logSys.debug("pyinotifier started for %s.", self.jail.name)
|
logSys.debug("[%s] filter started (pyinotifier)", self.jailName)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -202,15 +207,22 @@ class FilterPyinotify(FileFilter):
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
super(FilterPyinotify, self).stop()
|
super(FilterPyinotify, self).stop()
|
||||||
|
|
||||||
# Stop the notifier thread
|
# Stop the notifier thread
|
||||||
self.__notifier.stop()
|
self.__notifier.stop()
|
||||||
self.__notifier.join() # to not exit before notifier does
|
|
||||||
self.__cleanup() # for pedantic ones
|
##
|
||||||
|
# Wait for exit with cleanup.
|
||||||
|
|
||||||
|
def join(self):
|
||||||
|
self.__cleanup()
|
||||||
|
super(FilterPyinotify, self).join()
|
||||||
|
logSys.debug("[%s] filter terminated (pyinotifier)", self.jailName)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Deallocates the resources used by pyinotify.
|
# Deallocates the resources used by pyinotify.
|
||||||
|
|
||||||
def __cleanup(self):
|
def __cleanup(self):
|
||||||
self.__notifier = None
|
if self.__notifier:
|
||||||
|
self.__notifier.join() # to not exit before notifier does
|
||||||
|
self.__notifier = None
|
||||||
self.__monitor = None
|
self.__monitor = None
|
||||||
|
|
|
@ -34,7 +34,7 @@ from .failmanager import FailManagerEmpty
|
||||||
from .filter import JournalFilter, Filter
|
from .filter import JournalFilter, Filter
|
||||||
from .mytime import MyTime
|
from .mytime import MyTime
|
||||||
from .utils import Utils
|
from .utils import Utils
|
||||||
from ..helpers import getLogger, logging, splitwords
|
from ..helpers import getLogger, logging, splitwords, uni_decode
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
@ -130,7 +130,8 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
self.resetJournalMatches()
|
self.resetJournalMatches()
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
logSys.info("Added journal match for: %r", " ".join(match))
|
logSys.info("[%s] Added journal match for: %r", self.jailName,
|
||||||
|
" ".join(match))
|
||||||
##
|
##
|
||||||
# Reset a journal match filter called on removal or failure
|
# Reset a journal match filter called on removal or failure
|
||||||
#
|
#
|
||||||
|
@ -138,7 +139,7 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
|
|
||||||
def resetJournalMatches(self):
|
def resetJournalMatches(self):
|
||||||
self.__journal.flush_matches()
|
self.__journal.flush_matches()
|
||||||
logSys.debug("Flushed all journal matches")
|
logSys.debug("[%s] Flushed all journal matches", self.jailName)
|
||||||
match_copy = self.__matches[:]
|
match_copy = self.__matches[:]
|
||||||
self.__matches = []
|
self.__matches = []
|
||||||
try:
|
try:
|
||||||
|
@ -154,13 +155,20 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
#
|
#
|
||||||
# @param match journalctl syntax matches
|
# @param match journalctl syntax matches
|
||||||
|
|
||||||
def delJournalMatch(self, match):
|
def delJournalMatch(self, match=None):
|
||||||
if match in self.__matches:
|
# clear all:
|
||||||
|
if match is None:
|
||||||
|
if not self.__matches:
|
||||||
|
return
|
||||||
|
del self.__matches[:]
|
||||||
|
# delete by index:
|
||||||
|
elif match in self.__matches:
|
||||||
del self.__matches[self.__matches.index(match)]
|
del self.__matches[self.__matches.index(match)]
|
||||||
self.resetJournalMatches()
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Match not found")
|
raise ValueError("Match %r not found" % match)
|
||||||
logSys.info("Removed journal match for: %r" % " ".join(match))
|
self.resetJournalMatches()
|
||||||
|
logSys.info("[%s] Removed journal match for: %r", self.jailName,
|
||||||
|
match if match else '*')
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get current journal match filter
|
# Get current journal match filter
|
||||||
|
@ -170,10 +178,6 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
def getJournalMatch(self):
|
def getJournalMatch(self):
|
||||||
return self.__matches
|
return self.__matches
|
||||||
|
|
||||||
def uni_decode(self, x):
|
|
||||||
v = Filter.uni_decode(x, self.getLogEncoding())
|
|
||||||
return v
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Format journal log entry into syslog style
|
# Format journal log entry into syslog style
|
||||||
#
|
#
|
||||||
|
@ -182,16 +186,16 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
|
|
||||||
def formatJournalEntry(self, logentry):
|
def formatJournalEntry(self, logentry):
|
||||||
# Be sure, all argument of line tuple should have the same type:
|
# Be sure, all argument of line tuple should have the same type:
|
||||||
uni_decode = self.uni_decode
|
enc = self.getLogEncoding()
|
||||||
logelements = []
|
logelements = []
|
||||||
v = logentry.get('_HOSTNAME')
|
v = logentry.get('_HOSTNAME')
|
||||||
if v:
|
if v:
|
||||||
logelements.append(uni_decode(v))
|
logelements.append(uni_decode(v, enc))
|
||||||
v = logentry.get('SYSLOG_IDENTIFIER')
|
v = logentry.get('SYSLOG_IDENTIFIER')
|
||||||
if not v:
|
if not v:
|
||||||
v = logentry.get('_COMM')
|
v = logentry.get('_COMM')
|
||||||
if v:
|
if v:
|
||||||
logelements.append(uni_decode(v))
|
logelements.append(uni_decode(v, enc))
|
||||||
v = logentry.get('SYSLOG_PID')
|
v = logentry.get('SYSLOG_PID')
|
||||||
if not v:
|
if not v:
|
||||||
v = logentry.get('_PID')
|
v = logentry.get('_PID')
|
||||||
|
@ -206,16 +210,16 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
logelements.append("[%12.6f]" % monotonic.total_seconds())
|
logelements.append("[%12.6f]" % monotonic.total_seconds())
|
||||||
msg = logentry.get('MESSAGE','')
|
msg = logentry.get('MESSAGE','')
|
||||||
if isinstance(msg, list):
|
if isinstance(msg, list):
|
||||||
logelements.append(" ".join(uni_decode(v) for v in msg))
|
logelements.append(" ".join(uni_decode(v, enc) for v in msg))
|
||||||
else:
|
else:
|
||||||
logelements.append(uni_decode(msg))
|
logelements.append(uni_decode(msg, enc))
|
||||||
|
|
||||||
logline = " ".join(logelements)
|
logline = " ".join(logelements)
|
||||||
|
|
||||||
date = logentry.get('_SOURCE_REALTIME_TIMESTAMP',
|
date = logentry.get('_SOURCE_REALTIME_TIMESTAMP',
|
||||||
logentry.get('__REALTIME_TIMESTAMP'))
|
logentry.get('__REALTIME_TIMESTAMP'))
|
||||||
logSys.debug("Read systemd journal entry: %r" %
|
logSys.log(5, "[%s] Read systemd journal entry: %s %s", self.jailName,
|
||||||
"".join([date.isoformat(), logline]))
|
date.isoformat(), logline)
|
||||||
## use the same type for 1st argument:
|
## use the same type for 1st argument:
|
||||||
return ((logline[:0], date.isoformat(), logline),
|
return ((logline[:0], date.isoformat(), logline),
|
||||||
time.mktime(date.timetuple()) + date.microsecond/1.0E6)
|
time.mktime(date.timetuple()) + date.microsecond/1.0E6)
|
||||||
|
@ -252,40 +256,64 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
|
|
||||||
while self.active:
|
while self.active:
|
||||||
# wait for records (or for timeout in sleeptime seconds):
|
# wait for records (or for timeout in sleeptime seconds):
|
||||||
self.__journal.wait(self.sleeptime)
|
try:
|
||||||
if self.idle:
|
## todo: find better method as wait_for to break (e.g. notify) journal.wait(self.sleeptime),
|
||||||
# because journal.wait will returns immediatelly if we have records in journal,
|
## don't use `journal.close()` for it, because in some python/systemd implementation it may
|
||||||
# just wait a little bit here for not idle, to prevent hi-load:
|
## cause abnormal program termination
|
||||||
if not Utils.wait_for(lambda: not self.idle,
|
#self.__journal.wait(self.sleeptime) != journal.NOP
|
||||||
self.sleeptime * 10, self.sleeptime
|
##
|
||||||
):
|
## wait for entries without sleep in intervals, because "sleeping" in journal.wait:
|
||||||
|
Utils.wait_for(lambda: not self.active or \
|
||||||
|
self.__journal.wait(Utils.DEFAULT_SLEEP_INTERVAL) != journal.NOP,
|
||||||
|
self.sleeptime, 0.00001)
|
||||||
|
if self.idle:
|
||||||
|
# because journal.wait will returns immediatelly if we have records in journal,
|
||||||
|
# just wait a little bit here for not idle, to prevent hi-load:
|
||||||
|
if not Utils.wait_for(lambda: not self.active or not self.idle,
|
||||||
|
self.sleeptime * 10, self.sleeptime
|
||||||
|
):
|
||||||
|
self.ticks += 1
|
||||||
|
continue
|
||||||
|
self.__modified = 0
|
||||||
|
while self.active:
|
||||||
|
logentry = None
|
||||||
|
try:
|
||||||
|
logentry = self.__journal.get_next()
|
||||||
|
except OSError as e:
|
||||||
|
logSys.error("Error reading line from systemd journal: %s",
|
||||||
|
e, exc_info=logSys.getEffectiveLevel() <= logging.DEBUG)
|
||||||
self.ticks += 1
|
self.ticks += 1
|
||||||
continue
|
if logentry:
|
||||||
self.__modified = 0
|
self.processLineAndAdd(
|
||||||
while self.active:
|
*self.formatJournalEntry(logentry))
|
||||||
logentry = None
|
self.__modified += 1
|
||||||
try:
|
if self.__modified >= 100: # todo: should be configurable
|
||||||
logentry = self.__journal.get_next()
|
break
|
||||||
except OSError as e:
|
else:
|
||||||
logSys.error("Error reading line from systemd journal: %s",
|
|
||||||
e, exc_info=logSys.getEffectiveLevel() <= logging.DEBUG)
|
|
||||||
self.ticks += 1
|
|
||||||
if logentry:
|
|
||||||
self.processLineAndAdd(
|
|
||||||
*self.formatJournalEntry(logentry))
|
|
||||||
self.__modified += 1
|
|
||||||
if self.__modified >= 100: # todo: should be configurable
|
|
||||||
break
|
break
|
||||||
else:
|
if self.__modified:
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
ticket = self.failManager.toBan()
|
||||||
|
self.jail.putFailTicket(ticket)
|
||||||
|
except FailManagerEmpty:
|
||||||
|
self.failManager.cleanup(MyTime.time())
|
||||||
|
except Exception as e: # pragma: no cover
|
||||||
|
if not self.active: # if not active - error by stop...
|
||||||
break
|
break
|
||||||
if self.__modified:
|
logSys.error("Caught unhandled exception in main cycle: %r", e,
|
||||||
try:
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
while True:
|
# incr common error counter:
|
||||||
ticket = self.failManager.toBan()
|
self.commonError()
|
||||||
self.jail.putFailTicket(ticket)
|
|
||||||
except FailManagerEmpty:
|
|
||||||
self.failManager.cleanup(MyTime.time())
|
|
||||||
|
|
||||||
|
logSys.debug("[%s] filter terminated", self.jailName)
|
||||||
|
# close journal:
|
||||||
|
try:
|
||||||
|
if self.__journal:
|
||||||
|
self.__journal.close()
|
||||||
|
except Exception as e: # pragma: no cover
|
||||||
|
logSys.error("Close journal failed: %r", e,
|
||||||
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
logSys.debug((self.jail is not None and self.jail.name
|
logSys.debug((self.jail is not None and self.jail.name
|
||||||
or "jailless") +" filter terminated")
|
or "jailless") +" filter terminated")
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -28,7 +28,7 @@ import Queue
|
||||||
|
|
||||||
from .actions import Actions
|
from .actions import Actions
|
||||||
from ..client.jailreader import JailReader
|
from ..client.jailreader import JailReader
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger, MyTime
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
@ -79,6 +79,7 @@ class Jail(object):
|
||||||
logSys.info("Creating new jail '%s'" % self.name)
|
logSys.info("Creating new jail '%s'" % self.name)
|
||||||
if backend is not None:
|
if backend is not None:
|
||||||
self._setBackend(backend)
|
self._setBackend(backend)
|
||||||
|
self.backend = backend
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "%s(%r)" % (self.__class__.__name__, self.name)
|
return "%s(%r)" % (self.__class__.__name__, self.name)
|
||||||
|
@ -193,7 +194,7 @@ class Jail(object):
|
||||||
Used by filter to add a failure for banning.
|
Used by filter to add a failure for banning.
|
||||||
"""
|
"""
|
||||||
self.__queue.put(ticket)
|
self.__queue.put(ticket)
|
||||||
if self.database is not None:
|
if not ticket.restored and self.database is not None:
|
||||||
self.database.addBan(self, ticket)
|
self.database.addBan(self, ticket)
|
||||||
|
|
||||||
def getFailTicket(self):
|
def getFailTicket(self):
|
||||||
|
@ -202,34 +203,66 @@ class Jail(object):
|
||||||
Used by actions to get a failure for banning.
|
Used by actions to get a failure for banning.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self.__queue.get(False)
|
ticket = self.__queue.get(False)
|
||||||
|
return ticket
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def restoreCurrentBans(self):
|
||||||
|
"""Restore any previous valid bans from the database.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if self.database is not None:
|
||||||
|
forbantime = self.actions.getBanTime()
|
||||||
|
for ticket in self.database.getCurrentBans(jail=self, forbantime=forbantime):
|
||||||
|
#logSys.debug('restored ticket: %s', ticket)
|
||||||
|
if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True):
|
||||||
|
# mark ticked was restored from database - does not put it again into db:
|
||||||
|
ticket.restored = True
|
||||||
|
# correct start time / ban time (by the same end of ban):
|
||||||
|
btm = ticket.getBanTime(forbantime)
|
||||||
|
diftm = MyTime.time() - ticket.getTime()
|
||||||
|
if btm != -1 and diftm > 0:
|
||||||
|
btm -= diftm
|
||||||
|
# ignore obsolete tickets:
|
||||||
|
if btm != -1 and btm <= 0:
|
||||||
|
continue
|
||||||
|
ticket.setTime(MyTime.time())
|
||||||
|
ticket.setBanTime(btm)
|
||||||
|
self.putFailTicket(ticket)
|
||||||
|
except Exception as e: # pragma: no cover
|
||||||
|
logSys.error('%s', e, exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start the jail, by starting filter and actions threads.
|
"""Start the jail, by starting filter and actions threads.
|
||||||
|
|
||||||
Once stated, also queries the persistent database to reinstate
|
Once stated, also queries the persistent database to reinstate
|
||||||
any valid bans.
|
any valid bans.
|
||||||
"""
|
"""
|
||||||
|
logSys.debug("Starting jail %r", self.name)
|
||||||
self.filter.start()
|
self.filter.start()
|
||||||
self.actions.start()
|
self.actions.start()
|
||||||
# Restore any previous valid bans from the database
|
self.restoreCurrentBans()
|
||||||
if self.database is not None:
|
logSys.info("Jail %r started", self.name)
|
||||||
for ticket in self.database.getBansMerged(
|
|
||||||
jail=self, bantime=self.actions.getBanTime()):
|
|
||||||
if not self.filter.inIgnoreIPList(ticket.getIP(), log_ignore=True):
|
|
||||||
self.__queue.put(ticket)
|
|
||||||
logSys.info("Jail '%s' started" % self.name)
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self, stop=True, join=True):
|
||||||
"""Stop the jail, by stopping filter and actions threads.
|
"""Stop the jail, by stopping filter and actions threads.
|
||||||
"""
|
"""
|
||||||
self.filter.stop()
|
if stop:
|
||||||
self.actions.stop()
|
logSys.debug("Stopping jail %r", self.name)
|
||||||
self.filter.join()
|
for obj in (self.filter, self.actions):
|
||||||
self.actions.join()
|
try:
|
||||||
logSys.info("Jail '%s' stopped" % self.name)
|
## signal to stop filter / actions:
|
||||||
|
if stop:
|
||||||
|
obj.stop()
|
||||||
|
## wait for end of threads:
|
||||||
|
if join:
|
||||||
|
obj.join()
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error("Stop %r of jail %r failed: %s", obj, self.name, e,
|
||||||
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
|
if join:
|
||||||
|
logSys.info("Jail %r stopped", self.name)
|
||||||
|
|
||||||
def isAlive(self):
|
def isAlive(self):
|
||||||
"""Check jail "isAlive" by checking filter and actions threads.
|
"""Check jail "isAlive" by checking filter and actions threads.
|
||||||
|
|
|
@ -62,14 +62,15 @@ class Jails(Mapping):
|
||||||
DuplicateJailException
|
DuplicateJailException
|
||||||
If jail name is already present.
|
If jail name is already present.
|
||||||
"""
|
"""
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
|
||||||
if name in self._jails:
|
if name in self._jails:
|
||||||
raise DuplicateJailException(name)
|
if noduplicates:
|
||||||
|
raise DuplicateJailException(name)
|
||||||
else:
|
else:
|
||||||
self._jails[name] = Jail(name, backend, db)
|
self._jails[name] = Jail(name, backend, db)
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
def exists(self, name):
|
||||||
|
return name in self._jails
|
||||||
|
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -53,8 +53,8 @@ class JailThread(Thread):
|
||||||
super(JailThread, self).__init__(name=name)
|
super(JailThread, self).__init__(name=name)
|
||||||
## Should going with main thread also:
|
## Should going with main thread also:
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
## Control the state of the thread.
|
## Control the state of the thread (None - was not started, True - active, False - stopped).
|
||||||
self.active = False
|
self.active = None
|
||||||
## Control the idle state of the thread.
|
## Control the idle state of the thread.
|
||||||
self.idle = False
|
self.idle = False
|
||||||
## The time the thread sleeps in the loop.
|
## The time the thread sleeps in the loop.
|
||||||
|
@ -93,3 +93,14 @@ class JailThread(Thread):
|
||||||
"""Abstract - Called when thread starts, thread stops when returns.
|
"""Abstract - Called when thread starts, thread stops when returns.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def join(self):
|
||||||
|
""" Safer join, that could be called also for not started (or ended) threads (used for cleanup).
|
||||||
|
"""
|
||||||
|
## if cleanup needed - create derivate and call it before join...
|
||||||
|
|
||||||
|
## if was really started - should call join:
|
||||||
|
if self.active is not None:
|
||||||
|
super(JailThread, self).join()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ from .filter import FileFilter, JournalFilter
|
||||||
from .transmitter import Transmitter
|
from .transmitter import Transmitter
|
||||||
from .asyncserver import AsyncServer, AsyncServerException
|
from .asyncserver import AsyncServer, AsyncServerException
|
||||||
from .. import version
|
from .. import version
|
||||||
from ..helpers import getLogger, excepthook
|
from ..helpers import getLogger, str2LogLevel, excepthook
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
@ -67,6 +67,7 @@ class Server:
|
||||||
self.__db = None
|
self.__db = None
|
||||||
self.__daemon = daemon
|
self.__daemon = daemon
|
||||||
self.__transm = Transmitter(self)
|
self.__transm = Transmitter(self)
|
||||||
|
self.__reload_state = {}
|
||||||
#self.__asyncServer = AsyncServer(self.__transm)
|
#self.__asyncServer = AsyncServer(self.__transm)
|
||||||
self.__asyncServer = None
|
self.__asyncServer = None
|
||||||
self.__logLevel = None
|
self.__logLevel = None
|
||||||
|
@ -170,6 +171,12 @@ class Server:
|
||||||
# Now stop all the jails
|
# Now stop all the jails
|
||||||
self.stopAllJail()
|
self.stopAllJail()
|
||||||
|
|
||||||
|
# Explicit close database (server can leave in a thread,
|
||||||
|
# so delayed GC can prevent commiting changes)
|
||||||
|
if self.__db:
|
||||||
|
self.__db.close()
|
||||||
|
self.__db = None
|
||||||
|
|
||||||
# Only now shutdown the logging.
|
# Only now shutdown the logging.
|
||||||
if self.__logTarget is not None:
|
if self.__logTarget is not None:
|
||||||
with self.__loggingLock:
|
with self.__loggingLock:
|
||||||
|
@ -184,41 +191,111 @@ class Server:
|
||||||
self.quit = lambda: False
|
self.quit = lambda: False
|
||||||
|
|
||||||
def addJail(self, name, backend):
|
def addJail(self, name, backend):
|
||||||
self.__jails.add(name, backend, self.__db)
|
addflg = True
|
||||||
|
if self.__reload_state.get(name) and self.__jails.exists(name):
|
||||||
|
jail = self.__jails[name]
|
||||||
|
# if backend switch - restart instead of reload:
|
||||||
|
if jail.backend == backend:
|
||||||
|
addflg = False
|
||||||
|
logSys.info("Reload jail %r", name)
|
||||||
|
# prevent to reload the same jail twice (temporary keep it in state, needed to commit reload):
|
||||||
|
self.__reload_state[name] = None
|
||||||
|
else:
|
||||||
|
logSys.info("Restart jail %r (reason: %r != %r)", name, jail.backend, backend)
|
||||||
|
self.delJail(name, stop=True)
|
||||||
|
# prevent to start the same jail twice (no reload more - restart):
|
||||||
|
del self.__reload_state[name]
|
||||||
|
if addflg:
|
||||||
|
self.__jails.add(name, backend, self.__db)
|
||||||
if self.__db is not None:
|
if self.__db is not None:
|
||||||
self.__db.addJail(self.__jails[name])
|
self.__db.addJail(self.__jails[name])
|
||||||
|
|
||||||
def delJail(self, name):
|
def delJail(self, name, stop=True, join=True):
|
||||||
if self.__db is not None:
|
jail = self.__jails[name]
|
||||||
self.__db.delJail(self.__jails[name])
|
if join or jail.isAlive():
|
||||||
del self.__jails[name]
|
jail.stop(stop=stop, join=join)
|
||||||
|
if join:
|
||||||
|
if self.__db is not None:
|
||||||
|
self.__db.delJail(jail)
|
||||||
|
del self.__jails[name]
|
||||||
|
|
||||||
def startJail(self, name):
|
def startJail(self, name):
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
jail = self.__jails[name]
|
||||||
if not self.__jails[name].isAlive():
|
if not jail.isAlive():
|
||||||
self.__jails[name].start()
|
jail.start()
|
||||||
finally:
|
elif name in self.__reload_state:
|
||||||
self.__lock.release()
|
logSys.info("Jail %r reloaded", name)
|
||||||
|
del self.__reload_state[name]
|
||||||
|
if jail.idle:
|
||||||
|
jail.idle = False
|
||||||
|
|
||||||
def stopJail(self, name):
|
def stopJail(self, name):
|
||||||
logSys.debug("Stopping jail %s" % name)
|
with self.__lock:
|
||||||
try:
|
self.delJail(name, stop=True)
|
||||||
self.__lock.acquire()
|
|
||||||
if self.__jails[name].isAlive():
|
|
||||||
self.__jails[name].stop()
|
|
||||||
self.delJail(name)
|
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
def stopAllJail(self):
|
def stopAllJail(self):
|
||||||
logSys.info("Stopping all jails")
|
logSys.info("Stopping all jails")
|
||||||
try:
|
with self.__lock:
|
||||||
self.__lock.acquire()
|
# 1st stop all jails (signal and stop actions/filter thread):
|
||||||
for jail in self.__jails.keys():
|
for name in self.__jails.keys():
|
||||||
self.stopJail(jail)
|
self.delJail(name, stop=True, join=False)
|
||||||
finally:
|
# 2nd wait for end and delete jails:
|
||||||
self.__lock.release()
|
for name in self.__jails.keys():
|
||||||
|
self.delJail(name, stop=False, join=True)
|
||||||
|
|
||||||
|
def reloadJails(self, name, opts, begin):
|
||||||
|
if begin:
|
||||||
|
# begin reload:
|
||||||
|
if self.__reload_state and (name == '--all' or self.__reload_state.get(name)):
|
||||||
|
raise ValueError('Reload already in progress')
|
||||||
|
logSys.info("Reload " + (("jail %s" % name) if name != '--all' else "all jails"))
|
||||||
|
with self.__lock:
|
||||||
|
# if single jail:
|
||||||
|
if name != '--all':
|
||||||
|
jail = None
|
||||||
|
# test jail exists (throws exception if not):
|
||||||
|
if "--if-exists" not in opts or self.__jails.exists(name):
|
||||||
|
jail = self.__jails[name]
|
||||||
|
if jail:
|
||||||
|
# first unban all ips (will be not restored after (re)start):
|
||||||
|
if "--unban" in opts:
|
||||||
|
self.setUnbanIP(name)
|
||||||
|
# stop if expected:
|
||||||
|
if "--restart" in opts:
|
||||||
|
self.stopJail(name)
|
||||||
|
else:
|
||||||
|
# first unban all ips (will be not restored after (re)start):
|
||||||
|
if "--unban" in opts:
|
||||||
|
self.setUnbanIP()
|
||||||
|
# stop if expected:
|
||||||
|
if "--restart" in opts:
|
||||||
|
self.stopAllJail()
|
||||||
|
# first set all affected jail(s) to idle and reset filter regex and other lists/dicts:
|
||||||
|
for jn, jail in self.__jails.iteritems():
|
||||||
|
if name == '--all' or jn == name:
|
||||||
|
jail.idle = True
|
||||||
|
self.__reload_state[jn] = jail
|
||||||
|
jail.filter.reload(begin=True)
|
||||||
|
jail.actions.reload(begin=True)
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# end reload, all affected (or new) jails have already all new parameters (via stream) and (re)started:
|
||||||
|
with self.__lock:
|
||||||
|
deljails = []
|
||||||
|
for jn, jail in self.__jails.iteritems():
|
||||||
|
# still in reload state:
|
||||||
|
if jn in self.__reload_state:
|
||||||
|
# remove jails that are not reloaded (untouched, so not in new configuration)
|
||||||
|
deljails.append(jn)
|
||||||
|
else:
|
||||||
|
# commit (reload was finished):
|
||||||
|
jail.filter.reload(begin=False)
|
||||||
|
jail.actions.reload(begin=False)
|
||||||
|
for jn in deljails:
|
||||||
|
self.delJail(jn)
|
||||||
|
self.__reload_state = {}
|
||||||
|
logSys.info("Reload finished.")
|
||||||
|
|
||||||
def setIdleJail(self, name, value):
|
def setIdleJail(self, name, value):
|
||||||
self.__jails[name].idle = value
|
self.__jails[name].idle = value
|
||||||
|
@ -309,7 +386,7 @@ class Server:
|
||||||
logSys.debug(" failregex: %r", value)
|
logSys.debug(" failregex: %r", value)
|
||||||
flt.addFailRegex(value)
|
flt.addFailRegex(value)
|
||||||
|
|
||||||
def delFailRegex(self, name, index):
|
def delFailRegex(self, name, index=None):
|
||||||
self.__jails[name].filter.delFailRegex(index)
|
self.__jails[name].filter.delFailRegex(index)
|
||||||
|
|
||||||
def getFailRegex(self, name):
|
def getFailRegex(self, name):
|
||||||
|
@ -351,7 +428,9 @@ class Server:
|
||||||
|
|
||||||
# Action
|
# Action
|
||||||
def addAction(self, name, value, *args):
|
def addAction(self, name, value, *args):
|
||||||
self.__jails[name].actions.add(value, *args)
|
## create (or reload) jail action:
|
||||||
|
self.__jails[name].actions.add(value, *args,
|
||||||
|
reload=name in self.__reload_state)
|
||||||
|
|
||||||
def getActions(self, name):
|
def getActions(self, name):
|
||||||
return self.__jails[name].actions
|
return self.__jails[name].actions
|
||||||
|
@ -368,8 +447,20 @@ class Server:
|
||||||
def setBanIP(self, name, value):
|
def setBanIP(self, name, value):
|
||||||
return self.__jails[name].filter.addBannedIP(value)
|
return self.__jails[name].filter.addBannedIP(value)
|
||||||
|
|
||||||
def setUnbanIP(self, name, value):
|
def setUnbanIP(self, name=None, value=None):
|
||||||
self.__jails[name].actions.removeBannedIP(value)
|
if name is not None:
|
||||||
|
# in all jails:
|
||||||
|
jails = [self.__jails[name]]
|
||||||
|
else:
|
||||||
|
# single jail:
|
||||||
|
jails = self.__jails.values()
|
||||||
|
# unban given or all (if value is None):
|
||||||
|
cnt = 0
|
||||||
|
for jail in jails:
|
||||||
|
cnt += jail.actions.removeBannedIP(value, ifexists=(name is None))
|
||||||
|
if value and not cnt:
|
||||||
|
logSys.info("%s is not banned", value)
|
||||||
|
return cnt
|
||||||
|
|
||||||
def getBanTime(self, name):
|
def getBanTime(self, name):
|
||||||
return self.__jails[name].actions.getBanTime()
|
return self.__jails[name].actions.getBanTime()
|
||||||
|
@ -419,11 +510,11 @@ class Server:
|
||||||
with self.__loggingLock:
|
with self.__loggingLock:
|
||||||
if self.__logLevel == value:
|
if self.__logLevel == value:
|
||||||
return
|
return
|
||||||
try:
|
ll = str2LogLevel(value)
|
||||||
getLogger("fail2ban").setLevel(getattr(logging, value))
|
# don't change real log-level if running from the test cases:
|
||||||
self.__logLevel = value
|
getLogger("fail2ban").setLevel(
|
||||||
except AttributeError:
|
ll if DEF_LOGTARGET != "INHERITED" or ll < logging.DEBUG else DEF_LOGLEVEL)
|
||||||
raise ValueError("Invalid log level %r" % value)
|
self.__logLevel = value
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get the logging level.
|
# Get the logging level.
|
||||||
|
|
|
@ -34,8 +34,13 @@ from .mytime import MyTime
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Ticket:
|
class Ticket(object):
|
||||||
|
|
||||||
|
MAX_TIME = 0X7FFFFFFFFFFF ;# 4461763-th year
|
||||||
|
|
||||||
|
RESTORED = 0x01
|
||||||
|
BANNED = 0x08
|
||||||
|
|
||||||
def __init__(self, ip=None, time=None, matches=None, data={}, ticket=None):
|
def __init__(self, ip=None, time=None, matches=None, data={}, ticket=None):
|
||||||
"""Ticket constructor
|
"""Ticket constructor
|
||||||
|
|
||||||
|
@ -49,13 +54,12 @@ class Ticket:
|
||||||
self._banCount = 0;
|
self._banCount = 0;
|
||||||
self._banTime = None;
|
self._banTime = None;
|
||||||
self._time = time if time is not None else MyTime.time()
|
self._time = time if time is not None else MyTime.time()
|
||||||
self._data = {'matches': [], 'failures': 0}
|
self._data = {'matches': matches or [], 'failures': 0}
|
||||||
self._data.update(data)
|
if data is not None:
|
||||||
|
self._data.update(data)
|
||||||
if ticket:
|
if ticket:
|
||||||
# ticket available - copy whole information from ticket:
|
# ticket available - copy whole information from ticket:
|
||||||
self.__dict__.update(i for i in ticket.__dict__.iteritems() if i[0] in self.__dict__)
|
self.__dict__.update(i for i in ticket.__dict__.iteritems() if i[0] in self.__dict__)
|
||||||
else:
|
|
||||||
self._data['matches'] = matches or []
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s: ip=%s time=%s #attempts=%d matches=%r" % \
|
return "%s: ip=%s time=%s #attempts=%d matches=%r" % \
|
||||||
|
@ -94,8 +98,8 @@ class Ticket:
|
||||||
def setBanTime(self, value):
|
def setBanTime(self, value):
|
||||||
self._banTime = value;
|
self._banTime = value;
|
||||||
|
|
||||||
def getBanTime(self, defaultBT = None):
|
def getBanTime(self, defaultBT=None):
|
||||||
return (self._banTime if not self._banTime is None else defaultBT);
|
return (self._banTime if self._banTime is not None else defaultBT)
|
||||||
|
|
||||||
def setBanCount(self, value):
|
def setBanCount(self, value):
|
||||||
self._banCount = value;
|
self._banCount = value;
|
||||||
|
@ -106,8 +110,16 @@ class Ticket:
|
||||||
def getBanCount(self):
|
def getBanCount(self):
|
||||||
return self._banCount;
|
return self._banCount;
|
||||||
|
|
||||||
def isTimedOut(self, time, defaultBT = None):
|
def getEndOfBanTime(self, defaultBT=None):
|
||||||
bantime = (self._banTime if not self._banTime is None else defaultBT);
|
bantime = (self._banTime if self._banTime is not None else defaultBT)
|
||||||
|
# permanent
|
||||||
|
if bantime == -1:
|
||||||
|
return Ticket.MAX_TIME
|
||||||
|
# unban time (end of ban):
|
||||||
|
return self._time + bantime
|
||||||
|
|
||||||
|
def isTimedOut(self, time, defaultBT=None):
|
||||||
|
bantime = (self._banTime if self._banTime is not None else defaultBT)
|
||||||
# permanent
|
# permanent
|
||||||
if bantime == -1:
|
if bantime == -1:
|
||||||
return False
|
return False
|
||||||
|
@ -126,6 +138,26 @@ class Ticket:
|
||||||
def getMatches(self):
|
def getMatches(self):
|
||||||
return self._data.get('matches', [])
|
return self._data.get('matches', [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def restored(self):
|
||||||
|
return self._flags & Ticket.RESTORED
|
||||||
|
@restored.setter
|
||||||
|
def restored(self, value):
|
||||||
|
if value:
|
||||||
|
self._flags |= Ticket.RESTORED
|
||||||
|
else:
|
||||||
|
self._flags &= ~(Ticket.RESTORED)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def banned(self):
|
||||||
|
return self._flags & Ticket.BANNED
|
||||||
|
@banned.setter
|
||||||
|
def banned(self, value):
|
||||||
|
if value:
|
||||||
|
self._flags |= Ticket.BANNED
|
||||||
|
else:
|
||||||
|
self._flags &= ~(Ticket.BANNED)
|
||||||
|
|
||||||
def setData(self, *args, **argv):
|
def setData(self, *args, **argv):
|
||||||
# if overwrite - set data and filter None values:
|
# if overwrite - set data and filter None values:
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger, logging
|
||||||
from .. import version
|
from .. import version
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
|
@ -52,13 +52,14 @@ class Transmitter:
|
||||||
|
|
||||||
def proceed(self, command):
|
def proceed(self, command):
|
||||||
# Deserialize object
|
# Deserialize object
|
||||||
logSys.debug("Command: %r", command)
|
logSys.log(5, "Command: %r", command)
|
||||||
try:
|
try:
|
||||||
ret = self.__commandHandler(command)
|
ret = self.__commandHandler(command)
|
||||||
ack = 0, ret
|
ack = 0, ret
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logSys.warning("Command %r has failed. Received %r"
|
logSys.warning("Command %r has failed. Received %r",
|
||||||
% (command, e))
|
command, e,
|
||||||
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
ack = 1, e
|
ack = 1, e
|
||||||
return ack
|
return ack
|
||||||
|
|
||||||
|
@ -72,8 +73,8 @@ class Transmitter:
|
||||||
return "pong"
|
return "pong"
|
||||||
elif command[0] == "add":
|
elif command[0] == "add":
|
||||||
name = command[1]
|
name = command[1]
|
||||||
if name == "all":
|
if name == "--all":
|
||||||
raise Exception("Reserved name")
|
raise Exception("Reserved name %r" % (name,))
|
||||||
try:
|
try:
|
||||||
backend = command[2]
|
backend = command[2]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
@ -87,12 +88,31 @@ class Transmitter:
|
||||||
elif command[0] == "stop":
|
elif command[0] == "stop":
|
||||||
if len(command) == 1:
|
if len(command) == 1:
|
||||||
self.__server.quit()
|
self.__server.quit()
|
||||||
elif command[1] == "all":
|
elif command[1] == "--all":
|
||||||
self.__server.stopAllJail()
|
self.__server.stopAllJail()
|
||||||
else:
|
else:
|
||||||
name = command[1]
|
name = command[1]
|
||||||
self.__server.stopJail(name)
|
self.__server.stopJail(name)
|
||||||
return None
|
return None
|
||||||
|
elif command[0] == "reload":
|
||||||
|
opts = command[1:3]
|
||||||
|
try:
|
||||||
|
self.__server.reloadJails(*opts, begin=True)
|
||||||
|
for cmd in command[3]:
|
||||||
|
self.__commandHandler(cmd)
|
||||||
|
finally:
|
||||||
|
self.__server.reloadJails(*opts, begin=False)
|
||||||
|
return 'OK'
|
||||||
|
elif len(command) >= 2 and command[0] == "unban":
|
||||||
|
# unban in all jails:
|
||||||
|
value = command[1:]
|
||||||
|
# if all ips:
|
||||||
|
if len(value) == 1 and value[0] == "--all":
|
||||||
|
self.__server.setUnbanIP()
|
||||||
|
return
|
||||||
|
for value in value:
|
||||||
|
self.__server.setUnbanIP(None, value)
|
||||||
|
return None
|
||||||
elif command[0] == "echo":
|
elif command[0] == "echo":
|
||||||
return command[1:]
|
return command[1:]
|
||||||
elif command[0] == "sleep":
|
elif command[0] == "sleep":
|
||||||
|
@ -265,7 +285,7 @@ class Transmitter:
|
||||||
action = self.__server.getAction(name, actionname)
|
action = self.__server.getAction(name, actionname)
|
||||||
if multiple:
|
if multiple:
|
||||||
for cmd in command[3]:
|
for cmd in command[3]:
|
||||||
logSys.debug(" %r", cmd)
|
logSys.log(5, " %r", cmd)
|
||||||
actionkey = cmd[0]
|
actionkey = cmd[0]
|
||||||
if callable(getattr(action, actionkey, None)):
|
if callable(getattr(action, actionkey, None)):
|
||||||
actionvalue = json.loads(cmd[1]) if len(cmd)>1 else {}
|
actionvalue = json.loads(cmd[1]) if len(cmd)>1 else {}
|
||||||
|
|
|
@ -21,8 +21,14 @@ __author__ = "Serg G. Brester (sebres) and Fail2Ban Contributors"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Yaroslav Halchenko, 2012-2015 Serg G. Brester"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Yaroslav Halchenko, 2012-2015 Serg G. Brester"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging, os, fcntl, subprocess, time, signal
|
import fcntl
|
||||||
from ..helpers import getLogger
|
import logging
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from ..helpers import getLogger, uni_decode
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = getLogger(__name__)
|
logSys = getLogger(__name__)
|
||||||
|
@ -46,8 +52,8 @@ class Utils():
|
||||||
"""Utilities provide diverse static methods like executes OS shell commands, etc.
|
"""Utilities provide diverse static methods like executes OS shell commands, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEFAULT_SLEEP_TIME = 0.1
|
DEFAULT_SLEEP_TIME = 2
|
||||||
DEFAULT_SLEEP_INTERVAL = 0.01
|
DEFAULT_SLEEP_INTERVAL = 0.2
|
||||||
|
|
||||||
|
|
||||||
class Cache(object):
|
class Cache(object):
|
||||||
|
@ -179,7 +185,7 @@ class Utils():
|
||||||
if stdout is not None and stdout != '' and std_level >= logSys.getEffectiveLevel():
|
if stdout is not None and stdout != '' and std_level >= logSys.getEffectiveLevel():
|
||||||
logSys.log(std_level, "%s -- stdout:", realCmd)
|
logSys.log(std_level, "%s -- stdout:", realCmd)
|
||||||
for l in stdout.splitlines():
|
for l in stdout.splitlines():
|
||||||
logSys.log(std_level, " -- stdout: %r", l)
|
logSys.log(std_level, " -- stdout: %r", uni_decode(l))
|
||||||
popen.stdout.close()
|
popen.stdout.close()
|
||||||
if popen.stderr:
|
if popen.stderr:
|
||||||
try:
|
try:
|
||||||
|
@ -191,7 +197,7 @@ class Utils():
|
||||||
if stderr is not None and stderr != '' and std_level >= logSys.getEffectiveLevel():
|
if stderr is not None and stderr != '' and std_level >= logSys.getEffectiveLevel():
|
||||||
logSys.log(std_level, "%s -- stderr:", realCmd)
|
logSys.log(std_level, "%s -- stderr:", realCmd)
|
||||||
for l in stderr.splitlines():
|
for l in stderr.splitlines():
|
||||||
logSys.log(std_level, " -- stderr: %r", l)
|
logSys.log(std_level, " -- stderr: %r", uni_decode(l))
|
||||||
popen.stderr.close()
|
popen.stderr.close()
|
||||||
|
|
||||||
success = False
|
success = False
|
||||||
|
|
|
@ -277,6 +277,24 @@ class CommandActionTest(LogCaptureTestCase):
|
||||||
self.assertRaises(RuntimeError, self.__action.ban, {'ip': None})
|
self.assertRaises(RuntimeError, self.__action.ban, {'ip': None})
|
||||||
self.assertLogged('Unable to restore environment')
|
self.assertLogged('Unable to restore environment')
|
||||||
|
|
||||||
|
def testExecuteActionCheckRepairEnvironment(self):
|
||||||
|
self.__action.actionstart = ""
|
||||||
|
self.__action.actionstop = ""
|
||||||
|
self.__action.actionban = "rm /tmp/fail2ban.test"
|
||||||
|
self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]"
|
||||||
|
self.__action.actionrepair = "echo 'repair ...'; touch /tmp/fail2ban.test"
|
||||||
|
# 1st time with success repair:
|
||||||
|
self.__action.ban({'ip': None})
|
||||||
|
self.assertLogged("Invariant check failed. Trying", "echo 'repair ...'", all=True)
|
||||||
|
self.pruneLog()
|
||||||
|
# 2nd time failed (not really repaired):
|
||||||
|
self.__action.actionrepair = "echo 'repair ...'"
|
||||||
|
self.assertRaises(RuntimeError, self.__action.ban, {'ip': None})
|
||||||
|
self.assertLogged(
|
||||||
|
"Invariant check failed. Trying",
|
||||||
|
"echo 'repair ...'",
|
||||||
|
"Unable to restore environment", all=True)
|
||||||
|
|
||||||
def testExecuteActionChangeCtags(self):
|
def testExecuteActionChangeCtags(self):
|
||||||
self.assertRaises(AttributeError, getattr, self.__action, "ROST")
|
self.assertRaises(AttributeError, getattr, self.__action, "ROST")
|
||||||
self.__action.ROST = "192.0.2.0"
|
self.__action.ROST = "192.0.2.0"
|
||||||
|
@ -294,7 +312,12 @@ class CommandActionTest(LogCaptureTestCase):
|
||||||
def testExecuteActionStartEmpty(self):
|
def testExecuteActionStartEmpty(self):
|
||||||
self.__action.actionstart = ""
|
self.__action.actionstart = ""
|
||||||
self.__action.start()
|
self.__action.start()
|
||||||
|
self.assertTrue(self.__action.executeCmd(""))
|
||||||
self.assertLogged('Nothing to do')
|
self.assertLogged('Nothing to do')
|
||||||
|
self.pruneLog()
|
||||||
|
self.assertTrue(self.__action._processCmd(""))
|
||||||
|
self.assertLogged('Nothing to do')
|
||||||
|
self.pruneLog()
|
||||||
|
|
||||||
def testExecuteIncorrectCmd(self):
|
def testExecuteIncorrectCmd(self):
|
||||||
CommandAction.executeCmd('/bin/ls >/dev/null\nbogusXXX now 2>/dev/null')
|
CommandAction.executeCmd('/bin/ls >/dev/null\nbogusXXX now 2>/dev/null')
|
||||||
|
@ -376,11 +399,11 @@ class CommandActionTest(LogCaptureTestCase):
|
||||||
|
|
||||||
def testCaptureStdOutErr(self):
|
def testCaptureStdOutErr(self):
|
||||||
CommandAction.executeCmd('echo "How now brown cow"')
|
CommandAction.executeCmd('echo "How now brown cow"')
|
||||||
self.assertLogged("stdout: 'How now brown cow'\n", "stdout: b'How now brown cow'\n")
|
self.assertLogged("stdout: 'How now brown cow'\n")
|
||||||
CommandAction.executeCmd(
|
CommandAction.executeCmd(
|
||||||
'echo "The rain in Spain stays mainly in the plain" 1>&2')
|
'echo "The rain in Spain stays mainly in the plain" 1>&2')
|
||||||
self.assertLogged(
|
self.assertLogged(
|
||||||
"stderr: 'The rain in Spain stays mainly in the plain'\n", "stderr: b'The rain in Spain stays mainly in the plain'\n")
|
"stderr: 'The rain in Spain stays mainly in the plain'\n")
|
||||||
|
|
||||||
def testCallingMap(self):
|
def testCallingMap(self):
|
||||||
mymap = CallingMap(callme=lambda: str(10), error=lambda: int('a'),
|
mymap = CallingMap(callme=lambda: str(10), error=lambda: int('a'),
|
||||||
|
|
|
@ -28,7 +28,6 @@ import unittest
|
||||||
|
|
||||||
from ..server.banmanager import BanManager
|
from ..server.banmanager import BanManager
|
||||||
from ..server.ticket import BanTicket
|
from ..server.ticket import BanTicket
|
||||||
from .utils import assert_dict_equal
|
|
||||||
|
|
||||||
class AddFailure(unittest.TestCase):
|
class AddFailure(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -53,11 +52,15 @@ class AddFailure(unittest.TestCase):
|
||||||
self.assertEqual(self.__banManager.size(), 1)
|
self.assertEqual(self.__banManager.size(), 1)
|
||||||
|
|
||||||
def testAddDuplicateWithTime(self):
|
def testAddDuplicateWithTime(self):
|
||||||
|
defBanTime = self.__banManager.getBanTime()
|
||||||
|
prevEndOfBanTime = 0
|
||||||
# add again a duplicate :
|
# add again a duplicate :
|
||||||
# 1) with newer start time and the same ban time
|
# 0) with same start time and the same (default) ban time
|
||||||
|
# 1) with newer start time and the same (default) ban time
|
||||||
# 2) with same start time and longer ban time
|
# 2) with same start time and longer ban time
|
||||||
# 3) with permanent ban time (-1)
|
# 3) with permanent ban time (-1)
|
||||||
for tnew, btnew in (
|
for tnew, btnew in (
|
||||||
|
(1167605999.0, None),
|
||||||
(1167605999.0 + 100, None),
|
(1167605999.0 + 100, None),
|
||||||
(1167605999.0, 24*60*60),
|
(1167605999.0, 24*60*60),
|
||||||
(1167605999.0, -1),
|
(1167605999.0, -1),
|
||||||
|
@ -70,10 +73,15 @@ class AddFailure(unittest.TestCase):
|
||||||
self.assertFalse(self.__banManager.addBanTicket(ticket2))
|
self.assertFalse(self.__banManager.addBanTicket(ticket2))
|
||||||
self.assertEqual(self.__banManager.size(), 1)
|
self.assertEqual(self.__banManager.size(), 1)
|
||||||
# pop ticket and check it was prolonged :
|
# pop ticket and check it was prolonged :
|
||||||
banticket = self.__banManager.getTicketByIP(ticket2.getIP())
|
banticket = self.__banManager.getTicketByID(ticket2.getID())
|
||||||
self.assertEqual(banticket.getTime(), ticket2.getTime())
|
self.assertEqual(banticket.getEndOfBanTime(defBanTime), ticket2.getEndOfBanTime(defBanTime))
|
||||||
self.assertEqual(banticket.getTime(), ticket2.getTime())
|
self.assertTrue(banticket.getEndOfBanTime(defBanTime) > prevEndOfBanTime)
|
||||||
self.assertEqual(banticket.getBanTime(), ticket2.getBanTime(self.__banManager.getBanTime()))
|
prevEndOfBanTime = ticket1.getEndOfBanTime(defBanTime)
|
||||||
|
# but the start time should not be changed (+ 100 is ignored):
|
||||||
|
self.assertEqual(banticket.getTime(), 1167605999.0)
|
||||||
|
# if prolong to permanent, it should also have permanent ban time:
|
||||||
|
if btnew == -1:
|
||||||
|
self.assertEqual(banticket.getBanTime(defBanTime), -1)
|
||||||
|
|
||||||
def testInListOK(self):
|
def testInListOK(self):
|
||||||
self.assertTrue(self.__banManager.addBanTicket(self.__ticket))
|
self.assertTrue(self.__banManager.addBanTicket(self.__ticket))
|
||||||
|
@ -87,9 +95,28 @@ class AddFailure(unittest.TestCase):
|
||||||
|
|
||||||
def testUnban(self):
|
def testUnban(self):
|
||||||
btime = self.__banManager.getBanTime()
|
btime = self.__banManager.getBanTime()
|
||||||
|
stime = self.__ticket.getTime()
|
||||||
self.assertTrue(self.__banManager.addBanTicket(self.__ticket))
|
self.assertTrue(self.__banManager.addBanTicket(self.__ticket))
|
||||||
self.assertTrue(self.__banManager._inBanList(self.__ticket))
|
self.assertTrue(self.__banManager._inBanList(self.__ticket))
|
||||||
self.assertEqual(self.__banManager.unBanList(self.__ticket.getTime() + btime + 1), [self.__ticket])
|
self.assertEqual(self.__banManager.unBanList(stime), [])
|
||||||
|
self.assertEqual(self.__banManager.unBanList(stime + btime + 1), [self.__ticket])
|
||||||
|
self.assertEqual(self.__banManager.size(), 0)
|
||||||
|
## again, but now we will prolong ban-time and then try to unban again (1st too early):
|
||||||
|
self.assertTrue(self.__banManager.addBanTicket(self.__ticket))
|
||||||
|
# prolong ban:
|
||||||
|
ticket = BanTicket(self.__ticket.getID(), stime + 600)
|
||||||
|
self.assertFalse(self.__banManager.addBanTicket(ticket))
|
||||||
|
# try unban too early:
|
||||||
|
self.assertEqual(len(self.__banManager.unBanList(stime + btime + 1)), 0)
|
||||||
|
# try unban using correct time:
|
||||||
|
self.assertEqual(len(self.__banManager.unBanList(stime + btime + 600 + 1)), 1)
|
||||||
|
## again, but now we test removing tickets particular (to test < 2/3-rule):
|
||||||
|
for i in range(5):
|
||||||
|
ticket = BanTicket('193.168.0.%s' % i, stime)
|
||||||
|
ticket.setBanTime(ticket.getBanTime(btime) + i*10)
|
||||||
|
self.assertTrue(self.__banManager.addBanTicket(ticket))
|
||||||
|
self.assertEqual(len(self.__banManager.unBanList(stime + btime + 1*10 + 1)), 2)
|
||||||
|
self.assertEqual(len(self.__banManager.unBanList(stime + btime + 5*10 + 1)), 3)
|
||||||
self.assertEqual(self.__banManager.size(), 0)
|
self.assertEqual(self.__banManager.size(), 0)
|
||||||
|
|
||||||
def testUnbanPermanent(self):
|
def testUnbanPermanent(self):
|
||||||
|
@ -122,7 +149,7 @@ class StatusExtendedCymruInfo(unittest.TestCase):
|
||||||
|
|
||||||
def testCymruInfo(self):
|
def testCymruInfo(self):
|
||||||
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
||||||
assert_dict_equal(cymru_info,
|
self.assertDictEqual(cymru_info,
|
||||||
{"asn": [self.__asn],
|
{"asn": [self.__asn],
|
||||||
"country": [self.__country],
|
"country": [self.__country],
|
||||||
"rir": [self.__rir]})
|
"rir": [self.__rir]})
|
||||||
|
@ -149,7 +176,7 @@ class StatusExtendedCymruInfo(unittest.TestCase):
|
||||||
ticket = BanTicket("0.0.0.0", 1167605999.0)
|
ticket = BanTicket("0.0.0.0", 1167605999.0)
|
||||||
self.assertTrue(self.__banManager.addBanTicket(ticket))
|
self.assertTrue(self.__banManager.addBanTicket(ticket))
|
||||||
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
||||||
assert_dict_equal(cymru_info,
|
self.assertDictEqual(cymru_info,
|
||||||
{"asn": ["nxdomain"],
|
{"asn": ["nxdomain"],
|
||||||
"country": ["nxdomain"],
|
"country": ["nxdomain"],
|
||||||
"rir": ["nxdomain"]})
|
"rir": ["nxdomain"]})
|
||||||
|
@ -160,7 +187,7 @@ class StatusExtendedCymruInfo(unittest.TestCase):
|
||||||
ticket = BanTicket("10.0.0.0", 1167606000.0)
|
ticket = BanTicket("10.0.0.0", 1167606000.0)
|
||||||
self.assertTrue(self.__banManager.addBanTicket(ticket))
|
self.assertTrue(self.__banManager.addBanTicket(ticket))
|
||||||
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
cymru_info = self.__banManager.getBanListExtendedCymruInfo()
|
||||||
assert_dict_equal(cymru_info,
|
self.assertDictEqual(dict((k, sorted(v)) for k, v in cymru_info.iteritems()),
|
||||||
{"asn": ["nxdomain", "4565",],
|
{"asn": sorted(["nxdomain", "4565",]),
|
||||||
"country": ["nxdomain", "unknown"],
|
"country": sorted(["nxdomain", "unknown"]),
|
||||||
"rir": ["nxdomain", "other"]})
|
"rir": sorted(["nxdomain", "other"])})
|
||||||
|
|
|
@ -353,6 +353,11 @@ class DatabaseTest(LogCaptureTestCase):
|
||||||
# be returned
|
# be returned
|
||||||
tickets = self.db.getBansMerged(bantime=-1)
|
tickets = self.db.getBansMerged(bantime=-1)
|
||||||
self.assertEqual(len(tickets), 2)
|
self.assertEqual(len(tickets), 2)
|
||||||
|
# getCurrentBans:
|
||||||
|
tickets = self.db.getCurrentBans(jail=self.jail)
|
||||||
|
self.assertEqual(len(tickets), 2)
|
||||||
|
ticket = self.db.getCurrentBans(jail=None, ip="127.0.0.1");
|
||||||
|
self.assertEqual(ticket.getIP(), "127.0.0.1")
|
||||||
|
|
||||||
def testActionWithDB(self):
|
def testActionWithDB(self):
|
||||||
# test action together with database functionality
|
# test action together with database functionality
|
||||||
|
|
|
@ -41,8 +41,9 @@ from ..client.fail2banclient import exec_command_line as _exec_client, VisualWai
|
||||||
from ..client.fail2banserver import Fail2banServer, exec_command_line as _exec_server
|
from ..client.fail2banserver import Fail2banServer, exec_command_line as _exec_server
|
||||||
from .. import protocol
|
from .. import protocol
|
||||||
from ..server import server
|
from ..server import server
|
||||||
|
from ..server.mytime import MyTime
|
||||||
from ..server.utils import Utils
|
from ..server.utils import Utils
|
||||||
from .utils import LogCaptureTestCase, with_tmpdir, shutil, logging
|
from .utils import LogCaptureTestCase, logSys as DefLogSys, with_tmpdir, shutil, logging
|
||||||
|
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger
|
||||||
|
|
||||||
|
@ -57,6 +58,7 @@ SERVER = "fail2ban-server"
|
||||||
BIN = dirname(Fail2banServer.getServerPath())
|
BIN = dirname(Fail2banServer.getServerPath())
|
||||||
|
|
||||||
MAX_WAITTIME = 30 if not unittest.F2B.fast else 5
|
MAX_WAITTIME = 30 if not unittest.F2B.fast else 5
|
||||||
|
MID_WAITTIME = MAX_WAITTIME
|
||||||
|
|
||||||
##
|
##
|
||||||
# Several wrappers and settings for proper testing:
|
# Several wrappers and settings for proper testing:
|
||||||
|
@ -68,7 +70,8 @@ fail2bancmdline.logSys = \
|
||||||
fail2banclient.logSys = \
|
fail2banclient.logSys = \
|
||||||
fail2banserver.logSys = logSys
|
fail2banserver.logSys = logSys
|
||||||
|
|
||||||
server.DEF_LOGTARGET = "/dev/null"
|
SRV_DEF_LOGTARGET = server.DEF_LOGTARGET
|
||||||
|
SRV_DEF_LOGLEVEL = server.DEF_LOGLEVEL
|
||||||
|
|
||||||
def _test_output(*args):
|
def _test_output(*args):
|
||||||
logSys.info(args[0])
|
logSys.info(args[0])
|
||||||
|
@ -110,17 +113,25 @@ fail2bancmdline.PRODUCTION = \
|
||||||
fail2banserver.PRODUCTION = False
|
fail2banserver.PRODUCTION = False
|
||||||
|
|
||||||
|
|
||||||
def _out_file(fn):
|
def _out_file(fn, handle=logSys.debug):
|
||||||
"""Helper which outputs content of the file at HEAVYDEBUG loglevels"""
|
"""Helper which outputs content of the file at HEAVYDEBUG loglevels"""
|
||||||
logSys.debug('---- ' + fn + ' ----')
|
handle('---- ' + fn + ' ----')
|
||||||
for line in fileinput.input(fn):
|
for line in fileinput.input(fn):
|
||||||
line = line.rstrip('\n')
|
line = line.rstrip('\n')
|
||||||
logSys.debug(line)
|
handle(line)
|
||||||
logSys.debug('-'*30)
|
handle('-'*30)
|
||||||
|
|
||||||
|
|
||||||
def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
|
def _write_file(fn, mode, *lines):
|
||||||
|
f = open(fn, mode)
|
||||||
|
f.write('\n'.join(lines))
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _start_params(tmp, use_stock=False, logtarget="/dev/null", db=":memory:"):
|
||||||
cfg = pjoin(tmp, "config")
|
cfg = pjoin(tmp, "config")
|
||||||
|
if db == 'auto':
|
||||||
|
db = pjoin(tmp, "f2b-db.sqlite3")
|
||||||
if use_stock and STOCK:
|
if use_stock and STOCK:
|
||||||
# copy config (sub-directories as alias):
|
# copy config (sub-directories as alias):
|
||||||
def ig_dirs(dir, files):
|
def ig_dirs(dir, files):
|
||||||
|
@ -146,8 +157,7 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
|
||||||
else:
|
else:
|
||||||
# just empty config directory without anything (only fail2ban.conf/jail.conf):
|
# just empty config directory without anything (only fail2ban.conf/jail.conf):
|
||||||
os.mkdir(cfg)
|
os.mkdir(cfg)
|
||||||
f = open(pjoin(cfg, "fail2ban.conf"), "w")
|
_write_file(pjoin(cfg, "fail2ban.conf"), "w",
|
||||||
f.write('\n'.join((
|
|
||||||
"[Definition]",
|
"[Definition]",
|
||||||
"loglevel = INFO",
|
"loglevel = INFO",
|
||||||
"logtarget = " + logtarget,
|
"logtarget = " + logtarget,
|
||||||
|
@ -155,19 +165,16 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
|
||||||
"socket = " + pjoin(tmp, "f2b.sock"),
|
"socket = " + pjoin(tmp, "f2b.sock"),
|
||||||
"pidfile = " + pjoin(tmp, "f2b.pid"),
|
"pidfile = " + pjoin(tmp, "f2b.pid"),
|
||||||
"backend = polling",
|
"backend = polling",
|
||||||
"dbfile = :memory:",
|
"dbfile = " + db,
|
||||||
"dbpurgeage = 1d",
|
"dbpurgeage = 1d",
|
||||||
"",
|
"",
|
||||||
)))
|
)
|
||||||
f.close()
|
_write_file(pjoin(cfg, "jail.conf"), "w",
|
||||||
f = open(pjoin(cfg, "jail.conf"), "w")
|
|
||||||
f.write('\n'.join((
|
|
||||||
"[INCLUDES]", "",
|
"[INCLUDES]", "",
|
||||||
"[DEFAULT]", "",
|
"[DEFAULT]", "",
|
||||||
"",
|
"",
|
||||||
)))
|
)
|
||||||
f.close()
|
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
|
||||||
if logSys.level < logging.DEBUG: # if HEAVYDEBUG
|
|
||||||
_out_file(pjoin(cfg, "fail2ban.conf"))
|
_out_file(pjoin(cfg, "fail2ban.conf"))
|
||||||
_out_file(pjoin(cfg, "jail.conf"))
|
_out_file(pjoin(cfg, "jail.conf"))
|
||||||
# parameters (sock/pid and config, increase verbosity, set log, etc.):
|
# parameters (sock/pid and config, increase verbosity, set log, etc.):
|
||||||
|
@ -237,19 +244,78 @@ def with_kill_srv(f):
|
||||||
_kill_srv(pidfile)
|
_kill_srv(pidfile)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
def with_foreground_server_thread(startextra={}):
|
||||||
|
"""Helper to decorate tests uses foreground server (as thread), started directly in test-cases
|
||||||
|
|
||||||
|
To be used only in subclasses
|
||||||
|
"""
|
||||||
|
def _deco_wrapper(f):
|
||||||
|
@with_tmpdir
|
||||||
|
@wraps(f)
|
||||||
|
def wrapper(self, tmp, *args, **kwargs):
|
||||||
|
th = None
|
||||||
|
phase = dict()
|
||||||
|
try:
|
||||||
|
# started directly here, so prevent overwrite test cases logger with "INHERITED"
|
||||||
|
startparams = _start_params(tmp, logtarget="INHERITED", **startextra)
|
||||||
|
# because foreground block execution - start it in thread:
|
||||||
|
th = Thread(
|
||||||
|
name="_TestCaseWorker",
|
||||||
|
target=self._testStartForeground,
|
||||||
|
args=(tmp, startparams, phase)
|
||||||
|
)
|
||||||
|
th.daemon = True
|
||||||
|
th.start()
|
||||||
|
try:
|
||||||
|
# wait for start thread:
|
||||||
|
Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME)
|
||||||
|
self.assertTrue(phase.get('start', None))
|
||||||
|
# wait for server (socket and ready):
|
||||||
|
self._wait_for_srv(tmp, True, startparams=startparams)
|
||||||
|
DefLogSys.info('=== within server: begin ===')
|
||||||
|
self.pruneLog()
|
||||||
|
# several commands to server in body of decorated function:
|
||||||
|
return f(self, tmp, startparams, *args, **kwargs)
|
||||||
|
finally:
|
||||||
|
DefLogSys.info('=== within server: end. ===')
|
||||||
|
self.pruneLog()
|
||||||
|
# stop:
|
||||||
|
self.execSuccess(startparams, "stop")
|
||||||
|
# wait for end:
|
||||||
|
Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME)
|
||||||
|
self.assertTrue(phase.get('end', None))
|
||||||
|
self.assertLogged("Shutdown successful", "Exiting Fail2ban")
|
||||||
|
finally:
|
||||||
|
if th:
|
||||||
|
# we start client/server directly in current process (new thread),
|
||||||
|
# so don't kill (same process) - if success, just wait for end of worker:
|
||||||
|
if phase.get('end', None):
|
||||||
|
th.join()
|
||||||
|
return wrapper
|
||||||
|
return _deco_wrapper
|
||||||
|
|
||||||
|
|
||||||
class Fail2banClientServerBase(LogCaptureTestCase):
|
class Fail2banClientServerBase(LogCaptureTestCase):
|
||||||
|
|
||||||
_orig_exit = Fail2banCmdLine._exit
|
_orig_exit = Fail2banCmdLine._exit
|
||||||
|
|
||||||
|
def _setLogLevel(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
LogCaptureTestCase.setUp(self)
|
LogCaptureTestCase.setUp(self)
|
||||||
|
# prevent to switch the logging in the test cases (use inherited one):
|
||||||
|
server.DEF_LOGTARGET = "INHERITED"
|
||||||
|
server.DEF_LOGLEVEL = DefLogSys.level
|
||||||
Fail2banCmdLine._exit = staticmethod(self._test_exit)
|
Fail2banCmdLine._exit = staticmethod(self._test_exit)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Call after every test case."""
|
"""Call after every test case."""
|
||||||
Fail2banCmdLine._exit = self._orig_exit
|
Fail2banCmdLine._exit = self._orig_exit
|
||||||
|
# restore server log target:
|
||||||
|
server.DEF_LOGTARGET = SRV_DEF_LOGTARGET
|
||||||
|
server.DEF_LOGLEVEL = SRV_DEF_LOGLEVEL
|
||||||
LogCaptureTestCase.tearDown(self)
|
LogCaptureTestCase.tearDown(self)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -303,47 +369,12 @@ class Fail2banClientServerBase(LogCaptureTestCase):
|
||||||
phase['end'] = True
|
phase['end'] = True
|
||||||
logSys.debug("end of test worker")
|
logSys.debug("end of test worker")
|
||||||
|
|
||||||
@with_tmpdir
|
@with_foreground_server_thread()
|
||||||
def testStartForeground(self, tmp):
|
def testStartForeground(self, tmp, startparams):
|
||||||
# intended to be ran only in subclasses
|
# several commands to server:
|
||||||
th = None
|
self.execSuccess(startparams, "ping")
|
||||||
phase = dict()
|
self.execFailed(startparams, "~~unknown~cmd~failed~~")
|
||||||
try:
|
self.execSuccess(startparams, "echo", "TEST-ECHO")
|
||||||
# started directly here, so prevent overwrite test cases logger with "INHERITED"
|
|
||||||
startparams = _start_params(tmp, logtarget="INHERITED")
|
|
||||||
# because foreground block execution - start it in thread:
|
|
||||||
th = Thread(
|
|
||||||
name="_TestCaseWorker",
|
|
||||||
target=self._testStartForeground,
|
|
||||||
args=(tmp, startparams, phase)
|
|
||||||
)
|
|
||||||
th.daemon = True
|
|
||||||
th.start()
|
|
||||||
try:
|
|
||||||
# wait for start thread:
|
|
||||||
Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME)
|
|
||||||
self.assertTrue(phase.get('start', None))
|
|
||||||
# wait for server (socket and ready):
|
|
||||||
self._wait_for_srv(tmp, True, startparams=startparams)
|
|
||||||
self.pruneLog()
|
|
||||||
# several commands to server:
|
|
||||||
self.execSuccess(startparams, "ping")
|
|
||||||
self.execFailed(startparams, "~~unknown~cmd~failed~~")
|
|
||||||
self.execSuccess(startparams, "echo", "TEST-ECHO")
|
|
||||||
finally:
|
|
||||||
self.pruneLog()
|
|
||||||
# stop:
|
|
||||||
self.execSuccess(startparams, "stop")
|
|
||||||
# wait for end:
|
|
||||||
Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME)
|
|
||||||
self.assertTrue(phase.get('end', None))
|
|
||||||
self.assertLogged("Shutdown successful", "Exiting Fail2ban")
|
|
||||||
finally:
|
|
||||||
if th:
|
|
||||||
# we start client/server directly in current process (new thread),
|
|
||||||
# so don't kill (same process) - if success, just wait for end of worker:
|
|
||||||
if phase.get('end', None):
|
|
||||||
th.join()
|
|
||||||
|
|
||||||
|
|
||||||
class Fail2banClientTest(Fail2banClientServerBase):
|
class Fail2banClientTest(Fail2banClientServerBase):
|
||||||
|
@ -508,6 +539,24 @@ class Fail2banClientTest(Fail2banClientServerBase):
|
||||||
self.assertLogged("Usage: ")
|
self.assertLogged("Usage: ")
|
||||||
self.pruneLog()
|
self.pruneLog()
|
||||||
|
|
||||||
|
@with_tmpdir
|
||||||
|
def testClientFailCommands(self, tmp):
|
||||||
|
# started directly here, so prevent overwrite test cases logger with "INHERITED"
|
||||||
|
startparams = _start_params(tmp, logtarget="INHERITED")
|
||||||
|
|
||||||
|
# not started:
|
||||||
|
self.execFailed(startparams,
|
||||||
|
"reload", "jail")
|
||||||
|
self.assertLogged("Could not find server")
|
||||||
|
self.pruneLog()
|
||||||
|
|
||||||
|
# unexpected arg:
|
||||||
|
self.execFailed(startparams,
|
||||||
|
"--async", "reload", "--xxx", "jail")
|
||||||
|
self.assertLogged("Unexpected argument(s) for reload:")
|
||||||
|
self.pruneLog()
|
||||||
|
|
||||||
|
|
||||||
def testVisualWait(self):
|
def testVisualWait(self):
|
||||||
sleeptime = 0.035
|
sleeptime = 0.035
|
||||||
for verbose in (2, 0):
|
for verbose in (2, 0):
|
||||||
|
@ -612,3 +661,315 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
||||||
# again:
|
# again:
|
||||||
self.assertTrue(_kill_srv(tmp))
|
self.assertTrue(_kill_srv(tmp))
|
||||||
self.assertLogged("cleanup: no pidfile for")
|
self.assertLogged("cleanup: no pidfile for")
|
||||||
|
|
||||||
|
@with_foreground_server_thread(startextra={'db': 'auto'})
|
||||||
|
def testServerReloadTest(self, tmp, startparams):
|
||||||
|
# Very complicated test-case, that expected running server (foreground in thread).
|
||||||
|
#
|
||||||
|
# In this test-case, each phase is related from previous one,
|
||||||
|
# so it cannot be splitted in multiple test cases.
|
||||||
|
# Additionaly many log-messages used as ready-sign (to wait for end of phase).
|
||||||
|
#
|
||||||
|
# Used file database (instead of :memory:), to restore bans and log-file positions,
|
||||||
|
# after restart/reload between phases.
|
||||||
|
cfg = pjoin(tmp, "config")
|
||||||
|
test1log = pjoin(tmp, "test1.log")
|
||||||
|
test2log = pjoin(tmp, "test2.log")
|
||||||
|
test3log = pjoin(tmp, "test3.log")
|
||||||
|
|
||||||
|
os.mkdir(pjoin(cfg, "action.d"))
|
||||||
|
def _write_action_cfg(actname="test-action1", allow=True,
|
||||||
|
start="", reload="", ban="", unban="", stop=""):
|
||||||
|
fn = pjoin(cfg, "action.d", "%s.conf" % actname)
|
||||||
|
if not allow:
|
||||||
|
os.remove(fn)
|
||||||
|
return
|
||||||
|
_write_file(fn, "w",
|
||||||
|
"[Definition]",
|
||||||
|
"actionstart = echo '[<name>] %s: ** start'" % actname, start,
|
||||||
|
"actionreload = echo '[<name>] %s: .. reload'" % actname, reload,
|
||||||
|
"actionban = echo '[<name>] %s: ++ ban <ip>'" % actname, ban,
|
||||||
|
"actionunban = echo '[<name>] %s: -- unban <ip>'" % actname, unban,
|
||||||
|
"actionstop = echo '[<name>] %s: __ stop'" % actname, stop,
|
||||||
|
)
|
||||||
|
if DefLogSys.level <= logging.DEBUG: # if DEBUG
|
||||||
|
_out_file(fn)
|
||||||
|
|
||||||
|
def _write_jail_cfg(enabled=(1, 2), actions=()):
|
||||||
|
_write_file(pjoin(cfg, "jail.conf"), "w",
|
||||||
|
"[INCLUDES]", "",
|
||||||
|
"[DEFAULT]", "",
|
||||||
|
"usedns = no",
|
||||||
|
"maxretry = 3",
|
||||||
|
"findtime = 10m",
|
||||||
|
"failregex = ^\s*failure (401|403) from <HOST>",
|
||||||
|
"",
|
||||||
|
"[test-jail1]", "backend = polling", "filter =",
|
||||||
|
"action = ",
|
||||||
|
" test-action1[name='%(__name__)s']" if 1 in actions else "",
|
||||||
|
" test-action2[name='%(__name__)s']" if 2 in actions else "",
|
||||||
|
"logpath = " + test1log,
|
||||||
|
" " + test2log if 2 in enabled else "",
|
||||||
|
" " + test3log if 2 in enabled else "",
|
||||||
|
"failregex = ^\s*failure (401|403) from <HOST>",
|
||||||
|
" ^\s*error (401|403) from <HOST>" if 2 in enabled else "",
|
||||||
|
"enabled = true" if 1 in enabled else "",
|
||||||
|
"",
|
||||||
|
"[test-jail2]", "backend = polling", "filter =",
|
||||||
|
"action =",
|
||||||
|
"logpath = " + test2log,
|
||||||
|
"enabled = true" if 2 in enabled else "",
|
||||||
|
)
|
||||||
|
if DefLogSys.level <= logging.DEBUG: # if DEBUG
|
||||||
|
_out_file(pjoin(cfg, "jail.conf"))
|
||||||
|
|
||||||
|
# create default test actions:
|
||||||
|
_write_action_cfg(actname="test-action1")
|
||||||
|
_write_action_cfg(actname="test-action2")
|
||||||
|
|
||||||
|
_write_jail_cfg(enabled=[1], actions=[1,2])
|
||||||
|
_write_file(test1log, "w", *((str(int(MyTime.time())) + " failure 401 from 192.0.2.1: test 1",) * 3))
|
||||||
|
_write_file(test2log, "w")
|
||||||
|
_write_file(test3log, "w")
|
||||||
|
|
||||||
|
# reload and wait for ban:
|
||||||
|
self.pruneLog("[test-phase 1a]")
|
||||||
|
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
|
||||||
|
_out_file(test1log)
|
||||||
|
self.execSuccess(startparams, "reload")
|
||||||
|
self.assertLogged(
|
||||||
|
"Reload finished.",
|
||||||
|
"1 ticket(s) in 'test-jail1", all=True, wait=MID_WAITTIME)
|
||||||
|
self.assertLogged("Added logfile: %r" % test1log)
|
||||||
|
self.assertLogged("[test-jail1] Ban 192.0.2.1")
|
||||||
|
# test actions started:
|
||||||
|
self.assertLogged(
|
||||||
|
"stdout: '[test-jail1] test-action1: ** start'",
|
||||||
|
"stdout: '[test-jail1] test-action2: ** start'", all=True)
|
||||||
|
|
||||||
|
# enable both jails, 3 logs for jail1, etc...
|
||||||
|
# truncate test-log - we should not find unban/ban again by reload:
|
||||||
|
self.pruneLog("[test-phase 1b]")
|
||||||
|
_write_jail_cfg(actions=[1,2])
|
||||||
|
_write_file(test1log, "w+")
|
||||||
|
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
|
||||||
|
_out_file(test1log)
|
||||||
|
self.execSuccess(startparams, "reload")
|
||||||
|
self.assertLogged("Reload finished.", all=True, wait=MID_WAITTIME)
|
||||||
|
# test not unbanned / banned again:
|
||||||
|
self.assertNotLogged(
|
||||||
|
"[test-jail1] Unban 192.0.2.1",
|
||||||
|
"[test-jail1] Ban 192.0.2.1", all=True)
|
||||||
|
# test 2 new log files:
|
||||||
|
self.assertLogged(
|
||||||
|
"Added logfile: %r" % test2log,
|
||||||
|
"Added logfile: %r" % test3log, all=True)
|
||||||
|
# test actions reloaded:
|
||||||
|
self.assertLogged(
|
||||||
|
"stdout: '[test-jail1] test-action1: .. reload'",
|
||||||
|
"stdout: '[test-jail1] test-action2: .. reload'", all=True)
|
||||||
|
# test 1 new jail:
|
||||||
|
self.assertLogged(
|
||||||
|
"Creating new jail 'test-jail2'",
|
||||||
|
"Jail 'test-jail2' started", all=True)
|
||||||
|
|
||||||
|
# update action1, delete action2 (should be stopped via configuration)...
|
||||||
|
self.pruneLog("[test-phase 2a]")
|
||||||
|
_write_jail_cfg(actions=[1])
|
||||||
|
_write_action_cfg(actname="test-action1",
|
||||||
|
start= " echo '[<name>] %s: started.'" % "test-action1",
|
||||||
|
reload=" echo '[<name>] %s: reloaded.'" % "test-action1",
|
||||||
|
stop= " echo '[<name>] %s: stopped.'" % "test-action1")
|
||||||
|
self.execSuccess(startparams, "reload")
|
||||||
|
self.assertLogged("Reload finished.", all=True, wait=MID_WAITTIME)
|
||||||
|
# test not unbanned / banned again:
|
||||||
|
self.assertNotLogged(
|
||||||
|
"[test-jail1] Unban 192.0.2.1",
|
||||||
|
"[test-jail1] Ban 192.0.2.1", all=True)
|
||||||
|
# no new log files:
|
||||||
|
self.assertNotLogged("Added logfile:")
|
||||||
|
# test action reloaded (update):
|
||||||
|
self.assertLogged(
|
||||||
|
"stdout: '[test-jail1] test-action1: .. reload'",
|
||||||
|
"stdout: '[test-jail1] test-action1: reloaded.'", all=True)
|
||||||
|
# test stopped action unbans:
|
||||||
|
self.assertLogged(
|
||||||
|
"stdout: '[test-jail1] test-action2: -- unban 192.0.2.1'")
|
||||||
|
# test action stopped:
|
||||||
|
self.assertLogged(
|
||||||
|
"stdout: '[test-jail1] test-action2: __ stop'")
|
||||||
|
self.assertNotLogged(
|
||||||
|
"stdout: '[test-jail1] test-action1: -- unban 192.0.2.1'")
|
||||||
|
|
||||||
|
# don't need both actions anymore:
|
||||||
|
_write_action_cfg(actname="test-action1", allow=False)
|
||||||
|
_write_action_cfg(actname="test-action2", allow=False)
|
||||||
|
_write_jail_cfg(actions=[])
|
||||||
|
|
||||||
|
# write new failures:
|
||||||
|
self.pruneLog("[test-phase 2b]")
|
||||||
|
_write_file(test2log, "w+", *(
|
||||||
|
(str(int(MyTime.time())) + " error 403 from 192.0.2.2: test 2",) * 3 +
|
||||||
|
(str(int(MyTime.time())) + " error 403 from 192.0.2.3: test 2",) * 3 +
|
||||||
|
(str(int(MyTime.time())) + " failure 401 from 192.0.2.4: test 2",) * 3 +
|
||||||
|
(str(int(MyTime.time())) + " failure 401 from 192.0.2.8: test 2",) * 3
|
||||||
|
))
|
||||||
|
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
|
||||||
|
_out_file(test2log)
|
||||||
|
# test all will be found in jail1 and one in jail2:
|
||||||
|
self.assertLogged(
|
||||||
|
"2 ticket(s) in 'test-jail2",
|
||||||
|
"5 ticket(s) in 'test-jail1", all=True, wait=MID_WAITTIME)
|
||||||
|
self.assertLogged(
|
||||||
|
"[test-jail1] Ban 192.0.2.2",
|
||||||
|
"[test-jail1] Ban 192.0.2.3",
|
||||||
|
"[test-jail1] Ban 192.0.2.4",
|
||||||
|
"[test-jail1] Ban 192.0.2.8",
|
||||||
|
"[test-jail2] Ban 192.0.2.4",
|
||||||
|
"[test-jail2] Ban 192.0.2.8", all=True)
|
||||||
|
# test ips at all not visible for jail2:
|
||||||
|
self.assertNotLogged(
|
||||||
|
"[test-jail2] Found 192.0.2.2",
|
||||||
|
"[test-jail2] Ban 192.0.2.2",
|
||||||
|
"[test-jail2] Found 192.0.2.3",
|
||||||
|
"[test-jail2] Ban 192.0.2.3", all=True)
|
||||||
|
|
||||||
|
# rotate logs:
|
||||||
|
_write_file(test1log, "w+")
|
||||||
|
_write_file(test2log, "w+")
|
||||||
|
|
||||||
|
# restart jail without unban all:
|
||||||
|
self.pruneLog("[test-phase 2c]")
|
||||||
|
self.execSuccess(startparams,
|
||||||
|
"restart", "test-jail2")
|
||||||
|
self.assertLogged(
|
||||||
|
"Reload finished.",
|
||||||
|
"Restore Ban",
|
||||||
|
"2 ticket(s) in 'test-jail2", all=True, wait=MID_WAITTIME)
|
||||||
|
# stop/start and unban/restore ban:
|
||||||
|
self.assertLogged(
|
||||||
|
"Jail 'test-jail2' stopped",
|
||||||
|
"Jail 'test-jail2' started",
|
||||||
|
"[test-jail2] Unban 192.0.2.4",
|
||||||
|
"[test-jail2] Unban 192.0.2.8",
|
||||||
|
"[test-jail2] Restore Ban 192.0.2.4",
|
||||||
|
"[test-jail2] Restore Ban 192.0.2.8", all=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# restart jail with unban all:
|
||||||
|
self.pruneLog("[test-phase 2d]")
|
||||||
|
self.execSuccess(startparams,
|
||||||
|
"restart", "--unban", "test-jail2")
|
||||||
|
self.assertLogged(
|
||||||
|
"Reload finished.",
|
||||||
|
"Jail 'test-jail2' started", all=True, wait=MID_WAITTIME)
|
||||||
|
self.assertLogged(
|
||||||
|
"Jail 'test-jail2' stopped",
|
||||||
|
"Jail 'test-jail2' started",
|
||||||
|
"[test-jail2] Unban 192.0.2.4",
|
||||||
|
"[test-jail2] Unban 192.0.2.8", all=True
|
||||||
|
)
|
||||||
|
# no more ban (unbanned all):
|
||||||
|
self.assertNotLogged(
|
||||||
|
"[test-jail2] Ban 192.0.2.4",
|
||||||
|
"[test-jail2] Ban 192.0.2.8", all=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# reload jail1 without restart (without ban/unban):
|
||||||
|
self.pruneLog("[test-phase 3]")
|
||||||
|
self.execSuccess(startparams, "reload", "test-jail1")
|
||||||
|
self.assertLogged(
|
||||||
|
"Reload finished.", all=True, wait=MID_WAITTIME)
|
||||||
|
self.assertLogged(
|
||||||
|
"Reload jail 'test-jail1'",
|
||||||
|
"Jail 'test-jail1' reloaded", all=True)
|
||||||
|
self.assertNotLogged(
|
||||||
|
"Reload jail 'test-jail2'",
|
||||||
|
"Jail 'test-jail2' reloaded",
|
||||||
|
"Jail 'test-jail1' started", all=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# whole reload, but this time with jail1 only (jail2 should be stopped via configuration):
|
||||||
|
self.pruneLog("[test-phase 4]")
|
||||||
|
_write_jail_cfg(enabled=[1])
|
||||||
|
self.execSuccess(startparams, "reload")
|
||||||
|
self.assertLogged("Reload finished.", all=True, wait=MID_WAITTIME)
|
||||||
|
# test both jails should be reloaded:
|
||||||
|
self.assertLogged(
|
||||||
|
"Reload jail 'test-jail1'")
|
||||||
|
# test jail2 goes down:
|
||||||
|
self.assertLogged(
|
||||||
|
"Stopping jail 'test-jail2'",
|
||||||
|
"Jail 'test-jail2' stopped", all=True)
|
||||||
|
# test 2 log files removed:
|
||||||
|
self.assertLogged(
|
||||||
|
"Removed logfile: %r" % test2log,
|
||||||
|
"Removed logfile: %r" % test3log, all=True)
|
||||||
|
|
||||||
|
# now write failures again and check already banned (jail1 was alive the whole time) and new bans occurred (jail1 was alive the whole time):
|
||||||
|
self.pruneLog("[test-phase 5]")
|
||||||
|
_write_file(test1log, "w+", *(
|
||||||
|
(str(int(MyTime.time())) + " failure 401 from 192.0.2.1: test 5",) * 3 +
|
||||||
|
(str(int(MyTime.time())) + " error 403 from 192.0.2.5: test 5",) * 3 +
|
||||||
|
(str(int(MyTime.time())) + " failure 401 from 192.0.2.6: test 5",) * 3
|
||||||
|
))
|
||||||
|
if DefLogSys.level < logging.DEBUG: # if HEAVYDEBUG
|
||||||
|
_out_file(test1log)
|
||||||
|
self.assertLogged(
|
||||||
|
"6 ticket(s) in 'test-jail1",
|
||||||
|
"[test-jail1] 192.0.2.1 already banned", all=True, wait=MID_WAITTIME)
|
||||||
|
# test "failure" regexp still available:
|
||||||
|
self.assertLogged(
|
||||||
|
"[test-jail1] Found 192.0.2.1",
|
||||||
|
"[test-jail1] Found 192.0.2.6",
|
||||||
|
"[test-jail1] 192.0.2.1 already banned",
|
||||||
|
"[test-jail1] Ban 192.0.2.6", all=True)
|
||||||
|
# test "error" regexp no more available:
|
||||||
|
self.assertNotLogged("[test-jail1] Found 192.0.2.5")
|
||||||
|
|
||||||
|
# unban single ips:
|
||||||
|
self.pruneLog("[test-phase 6]")
|
||||||
|
self.execSuccess(startparams,
|
||||||
|
"--async", "unban", "192.0.2.5", "192.0.2.6")
|
||||||
|
self.assertLogged(
|
||||||
|
"192.0.2.5 is not banned",
|
||||||
|
"[test-jail1] Unban 192.0.2.6", all=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# reload all (one jail) with unban all:
|
||||||
|
self.pruneLog("[test-phase 7]")
|
||||||
|
self.execSuccess(startparams,
|
||||||
|
"reload", "--unban")
|
||||||
|
self.assertLogged("Reload finished.", all=True, wait=MID_WAITTIME)
|
||||||
|
# reloads unbanned all:
|
||||||
|
self.assertLogged(
|
||||||
|
"Jail 'test-jail1' reloaded",
|
||||||
|
"[test-jail1] Unban 192.0.2.1",
|
||||||
|
"[test-jail1] Unban 192.0.2.2",
|
||||||
|
"[test-jail1] Unban 192.0.2.3",
|
||||||
|
"[test-jail1] Unban 192.0.2.4", all=True
|
||||||
|
)
|
||||||
|
# no restart occurred, no more ban (unbanned all using option "--unban"):
|
||||||
|
self.assertNotLogged(
|
||||||
|
"Jail 'test-jail1' stopped",
|
||||||
|
"Jail 'test-jail1' started",
|
||||||
|
"[test-jail1] Ban 192.0.2.1",
|
||||||
|
"[test-jail1] Ban 192.0.2.2",
|
||||||
|
"[test-jail1] Ban 192.0.2.3",
|
||||||
|
"[test-jail1] Ban 192.0.2.4", all=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# several small cases (cover several parts):
|
||||||
|
self.pruneLog("[test-phase end-1]")
|
||||||
|
# wrong jail (not-started):
|
||||||
|
self.execFailed(startparams,
|
||||||
|
"--async", "reload", "test-jail2")
|
||||||
|
self.assertLogged("the jail 'test-jail2' does not exist")
|
||||||
|
self.pruneLog()
|
||||||
|
# unavailable jail (but exit 0), using --if-exists option:
|
||||||
|
self.execSuccess(startparams,
|
||||||
|
"--async", "reload", "--if-exists", "test-jail2")
|
||||||
|
self.assertNotLogged(
|
||||||
|
"Creating new jail 'test-jail2'",
|
||||||
|
"Jail 'test-jail2' started", all=True)
|
||||||
|
self.pruneLog()
|
||||||
|
|
|
@ -131,6 +131,15 @@ class Fail2banRegexTest(LogCaptureTestCase):
|
||||||
self.assertTrue(fail2banRegex.start(opts, args))
|
self.assertTrue(fail2banRegex.start(opts, args))
|
||||||
self.assertLogged('Lines: 19 lines, 0 ignored, 16 matched, 3 missed')
|
self.assertLogged('Lines: 19 lines, 0 ignored, 16 matched, 3 missed')
|
||||||
|
|
||||||
|
def testDirectRE_1raw_noDns(self):
|
||||||
|
(opts, args, fail2banRegex) = _Fail2banRegex(
|
||||||
|
"--print-all-matched", "--raw", "--usedns=no",
|
||||||
|
Fail2banRegexTest.FILENAME_01,
|
||||||
|
Fail2banRegexTest.RE_00
|
||||||
|
)
|
||||||
|
self.assertTrue(fail2banRegex.start(opts, args))
|
||||||
|
self.assertLogged('Lines: 19 lines, 0 ignored, 13 matched, 6 missed')
|
||||||
|
|
||||||
def testDirectRE_2(self):
|
def testDirectRE_2(self):
|
||||||
(opts, args, fail2banRegex) = _Fail2banRegex(
|
(opts, args, fail2banRegex) = _Fail2banRegex(
|
||||||
"--print-all-matched",
|
"--print-all-matched",
|
||||||
|
|
|
@ -38,11 +38,11 @@ except ImportError:
|
||||||
|
|
||||||
from ..server.jail import Jail
|
from ..server.jail import Jail
|
||||||
from ..server.filterpoll import FilterPoll
|
from ..server.filterpoll import FilterPoll
|
||||||
from ..server.filter import Filter, FileFilter, FileContainer, locale
|
from ..server.filter import Filter, FileFilter, FileContainer
|
||||||
from ..server.failmanager import FailManagerEmpty
|
from ..server.failmanager import FailManagerEmpty
|
||||||
from ..server.ipdns import DNSUtils, IPAddr
|
from ..server.ipdns import DNSUtils, IPAddr
|
||||||
from ..server.mytime import MyTime
|
from ..server.mytime import MyTime
|
||||||
from ..server.utils import Utils
|
from ..server.utils import Utils, uni_decode
|
||||||
from .utils import setUpMyTime, tearDownMyTime, mtimesleep, LogCaptureTestCase
|
from .utils import setUpMyTime, tearDownMyTime, mtimesleep, LogCaptureTestCase
|
||||||
from .dummyjail import DummyJail
|
from .dummyjail import DummyJail
|
||||||
|
|
||||||
|
@ -311,8 +311,7 @@ class BasicFilter(unittest.TestCase):
|
||||||
b'Fail for "g\xc3\xb6ran" from 192.0.2.1'
|
b'Fail for "g\xc3\xb6ran" from 192.0.2.1'
|
||||||
):
|
):
|
||||||
# join should work if all arguments have the same type:
|
# join should work if all arguments have the same type:
|
||||||
enc = locale.getpreferredencoding()
|
"".join([uni_decode(v) for v in (a1, a2, a3)])
|
||||||
"".join([Filter.uni_decode(v, enc) for v in (a1, a2, a3)])
|
|
||||||
|
|
||||||
|
|
||||||
class IgnoreIP(LogCaptureTestCase):
|
class IgnoreIP(LogCaptureTestCase):
|
||||||
|
|
|
@ -35,7 +35,7 @@ from StringIO import StringIO
|
||||||
|
|
||||||
from utils import LogCaptureTestCase, logSys as DefLogSys
|
from utils import LogCaptureTestCase, logSys as DefLogSys
|
||||||
|
|
||||||
from ..helpers import formatExceptionInfo, mbasename, TraceBack, FormatterWithTraceBack, getLogger
|
from ..helpers import formatExceptionInfo, mbasename, TraceBack, FormatterWithTraceBack, getLogger, uni_decode
|
||||||
from ..helpers import splitwords
|
from ..helpers import splitwords
|
||||||
from ..server.datedetector import DateDetector
|
from ..server.datedetector import DateDetector
|
||||||
from ..server.datetemplate import DatePatternRegex
|
from ..server.datetemplate import DatePatternRegex
|
||||||
|
@ -74,16 +74,14 @@ class HelpersTest(unittest.TestCase):
|
||||||
|
|
||||||
if sys.version_info >= (2,7):
|
if sys.version_info >= (2,7):
|
||||||
def _sh_call(cmd):
|
def _sh_call(cmd):
|
||||||
import subprocess, locale
|
import subprocess
|
||||||
ret = subprocess.check_output(cmd, shell=True)
|
ret = subprocess.check_output(cmd, shell=True)
|
||||||
if sys.version_info >= (3,):
|
return uni_decode(ret).rstrip()
|
||||||
ret = ret.decode(locale.getpreferredencoding(), 'replace')
|
|
||||||
return str(ret).rstrip()
|
|
||||||
else:
|
else:
|
||||||
def _sh_call(cmd):
|
def _sh_call(cmd):
|
||||||
import subprocess
|
import subprocess
|
||||||
ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read()
|
ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.read()
|
||||||
return str(ret).rstrip()
|
return uni_decode(ret).rstrip()
|
||||||
|
|
||||||
def _getSysPythonVersion():
|
def _getSysPythonVersion():
|
||||||
return _sh_call("fail2ban-python -c 'import sys; print(tuple(sys.version_info))'")
|
return _sh_call("fail2ban-python -c 'import sys; print(tuple(sys.version_info))'")
|
||||||
|
@ -286,6 +284,10 @@ class TestsUtilsTest(LogCaptureTestCase):
|
||||||
self.assertLogged, 'test_zyx', 'zyx', all=False)
|
self.assertLogged, 'test_zyx', 'zyx', all=False)
|
||||||
self._testAssertionErrorRE(r"All of the .* were found present in the log",
|
self._testAssertionErrorRE(r"All of the .* were found present in the log",
|
||||||
self.assertNotLogged, 'test', 'xyz', all=False)
|
self.assertNotLogged, 'test', 'xyz', all=False)
|
||||||
|
## assertDictEqual:
|
||||||
|
self.assertDictEqual({'A': [1, 2]}, {'A': [1, 2]})
|
||||||
|
self.assertRaises(AssertionError, self.assertDictEqual,
|
||||||
|
{'A': [1, 2]}, {'A': [2, 1]})
|
||||||
|
|
||||||
def testFormatterWithTraceBack(self):
|
def testFormatterWithTraceBack(self):
|
||||||
strout = StringIO()
|
strout = StringIO()
|
||||||
|
|
|
@ -28,7 +28,6 @@ import unittest
|
||||||
import time
|
import time
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
import locale
|
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ from ..server.jail import Jail
|
||||||
from ..server.jailthread import JailThread
|
from ..server.jailthread import JailThread
|
||||||
from ..server.utils import Utils
|
from ..server.utils import Utils
|
||||||
from .utils import LogCaptureTestCase
|
from .utils import LogCaptureTestCase
|
||||||
from ..helpers import getLogger
|
from ..helpers import getLogger, PREFER_ENC
|
||||||
from .. import version
|
from .. import version
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -240,7 +239,7 @@ class Transmitter(TransmitterBase):
|
||||||
self.transm.proceed(["add", self.jailName, "polling"])[0], 1)
|
self.transm.proceed(["add", self.jailName, "polling"])[0], 1)
|
||||||
# All name is reserved
|
# All name is reserved
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["add", "all", "polling"])[0], 1)
|
self.transm.proceed(["add", "--all", "polling"])[0], 1)
|
||||||
|
|
||||||
def testStartStopJail(self):
|
def testStartStopJail(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -267,7 +266,7 @@ class Transmitter(TransmitterBase):
|
||||||
self.assertTrue( Utils.wait_for(
|
self.assertTrue( Utils.wait_for(
|
||||||
lambda: self.server.isAlive(2) and not isinstance(self.transm.proceed(["status", self.jailName]), RuntimeError),
|
lambda: self.server.isAlive(2) and not isinstance(self.transm.proceed(["status", self.jailName]), RuntimeError),
|
||||||
3) )
|
3) )
|
||||||
self.assertEqual(self.transm.proceed(["stop", "all"]), (0, None))
|
self.assertEqual(self.transm.proceed(["stop", "--all"]), (0, None))
|
||||||
self.assertTrue( Utils.wait_for( lambda: not len(self.server._Server__jails), 3) )
|
self.assertTrue( Utils.wait_for( lambda: not len(self.server._Server__jails), 3) )
|
||||||
self.assertNotIn(self.jailName, self.server._Server__jails)
|
self.assertNotIn(self.jailName, self.server._Server__jails)
|
||||||
self.assertNotIn("TestJail2", self.server._Server__jails)
|
self.assertNotIn("TestJail2", self.server._Server__jails)
|
||||||
|
@ -354,7 +353,7 @@ class Transmitter(TransmitterBase):
|
||||||
def testJailLogEncoding(self):
|
def testJailLogEncoding(self):
|
||||||
self.setGetTest("logencoding", "UTF-8", jail=self.jailName)
|
self.setGetTest("logencoding", "UTF-8", jail=self.jailName)
|
||||||
self.setGetTest("logencoding", "ascii", jail=self.jailName)
|
self.setGetTest("logencoding", "ascii", jail=self.jailName)
|
||||||
self.setGetTest("logencoding", "auto", locale.getpreferredencoding(),
|
self.setGetTest("logencoding", "auto", PREFER_ENC,
|
||||||
jail=self.jailName)
|
jail=self.jailName)
|
||||||
self.setGetTestNOK("logencoding", "Monkey", jail=self.jailName)
|
self.setGetTestNOK("logencoding", "Monkey", jail=self.jailName)
|
||||||
|
|
||||||
|
@ -843,6 +842,8 @@ class TransmitterLogging(TransmitterBase):
|
||||||
|
|
||||||
def testLogLevel(self):
|
def testLogLevel(self):
|
||||||
self.setGetTest("loglevel", "HEAVYDEBUG")
|
self.setGetTest("loglevel", "HEAVYDEBUG")
|
||||||
|
self.setGetTest("loglevel", "TRACEDEBUG")
|
||||||
|
self.setGetTest("loglevel", "9")
|
||||||
self.setGetTest("loglevel", "DEBUG")
|
self.setGetTest("loglevel", "DEBUG")
|
||||||
self.setGetTest("loglevel", "INFO")
|
self.setGetTest("loglevel", "INFO")
|
||||||
self.setGetTest("loglevel", "NOTICE")
|
self.setGetTest("loglevel", "NOTICE")
|
||||||
|
|
|
@ -108,6 +108,24 @@ class TicketTests(unittest.TestCase):
|
||||||
self.assertEqual(ft2.getLastTime(), ft.getLastTime())
|
self.assertEqual(ft2.getLastTime(), ft.getLastTime())
|
||||||
self.assertEqual(ft2.getBanTime(), ft.getBanTime())
|
self.assertEqual(ft2.getBanTime(), ft.getBanTime())
|
||||||
|
|
||||||
|
def testTicketFlags(self):
|
||||||
|
flags = ('restored', 'banned')
|
||||||
|
ticket = Ticket('test', 0)
|
||||||
|
trueflags = []
|
||||||
|
for v in (True, False, True):
|
||||||
|
for f in flags:
|
||||||
|
setattr(ticket, f, v)
|
||||||
|
if v:
|
||||||
|
trueflags.append(f)
|
||||||
|
else:
|
||||||
|
trueflags.remove(f)
|
||||||
|
for f2 in flags:
|
||||||
|
self.assertEqual(bool(getattr(ticket, f2)), f2 in trueflags)
|
||||||
|
## inherite props from another tockets:
|
||||||
|
ticket = FailTicket(ticket=ticket)
|
||||||
|
for f2 in flags:
|
||||||
|
self.assertTrue(bool(getattr(ticket, f2)))
|
||||||
|
|
||||||
def testTicketData(self):
|
def testTicketData(self):
|
||||||
t = BanTicket('193.168.0.128', None, ['first', 'second'])
|
t = BanTicket('193.168.0.128', None, ['first', 'second'])
|
||||||
# expand data (no overwrites, matches are available) :
|
# expand data (no overwrites, matches are available) :
|
||||||
|
|
|
@ -119,6 +119,7 @@ def getOptParser(doc=""):
|
||||||
|
|
||||||
def initProcess(opts):
|
def initProcess(opts):
|
||||||
# Logger:
|
# Logger:
|
||||||
|
global logSys
|
||||||
logSys = getLogger("fail2ban")
|
logSys = getLogger("fail2ban")
|
||||||
|
|
||||||
# Numerical level of verbosity corresponding to a log "level"
|
# Numerical level of verbosity corresponding to a log "level"
|
||||||
|
@ -242,6 +243,9 @@ def initTests(opts):
|
||||||
raise unittest.SkipTest('Skip test because of "--fast"')
|
raise unittest.SkipTest('Skip test because of "--fast"')
|
||||||
unittest.F2B.SkipIfFast = F2B_SkipIfFast
|
unittest.F2B.SkipIfFast = F2B_SkipIfFast
|
||||||
else:
|
else:
|
||||||
|
# smaller inertance inside test-cases (litle speedup):
|
||||||
|
Utils.DEFAULT_SLEEP_TIME = 0.25
|
||||||
|
Utils.DEFAULT_SLEEP_INTERVAL = 0.025
|
||||||
# sleep intervals are large - use replacement for sleep to check time to sleep:
|
# sleep intervals are large - use replacement for sleep to check time to sleep:
|
||||||
_org_sleep = time.sleep
|
_org_sleep = time.sleep
|
||||||
def _new_sleep(v):
|
def _new_sleep(v):
|
||||||
|
@ -462,6 +466,20 @@ def gatherTests(regexps=None, opts=None):
|
||||||
# Forwards compatibility of unittest.TestCase for some early python versions
|
# Forwards compatibility of unittest.TestCase for some early python versions
|
||||||
#
|
#
|
||||||
|
|
||||||
|
if not hasattr(unittest.TestCase, 'assertDictEqual'):
|
||||||
|
import difflib, pprint
|
||||||
|
def assertDictEqual(self, d1, d2, msg=None):
|
||||||
|
self.assert_(isinstance(d1, dict), 'First argument is not a dictionary')
|
||||||
|
self.assert_(isinstance(d2, dict), 'Second argument is not a dictionary')
|
||||||
|
if d1 != d2:
|
||||||
|
standardMsg = '%r != %r' % (d1, d2)
|
||||||
|
diff = ('\n' + '\n'.join(difflib.ndiff(
|
||||||
|
pprint.pformat(d1).splitlines(),
|
||||||
|
pprint.pformat(d2).splitlines())))
|
||||||
|
msg = msg or (standardMsg + diff)
|
||||||
|
self.fail(msg)
|
||||||
|
unittest.TestCase.assertDictEqual = assertDictEqual
|
||||||
|
|
||||||
if not hasattr(unittest.TestCase, 'assertRaisesRegexp'):
|
if not hasattr(unittest.TestCase, 'assertRaisesRegexp'):
|
||||||
def assertRaisesRegexp(self, exccls, regexp, fun, *args, **kwargs):
|
def assertRaisesRegexp(self, exccls, regexp, fun, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
|
@ -577,7 +595,8 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||||
print("")
|
print("")
|
||||||
logSys.handlers += self._old_handlers
|
logSys.handlers += self._old_handlers
|
||||||
logSys.debug('='*10 + ' %s ' + '='*20, self.id())
|
logSys.debug('='*10 + ' %s ' + '='*20, self.id())
|
||||||
logSys.setLevel(logging.DEBUG)
|
else:
|
||||||
|
logSys.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Call after every test case."""
|
"""Call after every test case."""
|
||||||
|
@ -587,8 +606,21 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||||
logSys.handlers = self._old_handlers
|
logSys.handlers = self._old_handlers
|
||||||
logSys.level = self._old_level
|
logSys.level = self._old_level
|
||||||
|
|
||||||
def _is_logged(self, s):
|
def _is_logged(self, *s, **kwargs):
|
||||||
return s in self._log.getvalue()
|
logged = self._log.getvalue()
|
||||||
|
if not kwargs.get('all', False):
|
||||||
|
# at least one entry should be found:
|
||||||
|
for s_ in s:
|
||||||
|
if s_ in logged:
|
||||||
|
return True
|
||||||
|
if True: # pragma: no cover
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# each entry should be found:
|
||||||
|
for s_ in s:
|
||||||
|
if s_ not in logged: # pragma: no cover
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def assertLogged(self, *s, **kwargs):
|
def assertLogged(self, *s, **kwargs):
|
||||||
"""Assert that one of the strings was logged
|
"""Assert that one of the strings was logged
|
||||||
|
@ -602,19 +634,23 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||||
Test should succeed if string (or any of the listed) is present in the log
|
Test should succeed if string (or any of the listed) is present in the log
|
||||||
all : boolean (default False) if True should fail if any of s not logged
|
all : boolean (default False) if True should fail if any of s not logged
|
||||||
"""
|
"""
|
||||||
logged = self._log.getvalue()
|
wait = kwargs.get('wait', None)
|
||||||
|
if wait:
|
||||||
|
res = Utils.wait_for(lambda: self._is_logged(*s, **kwargs), wait)
|
||||||
|
else:
|
||||||
|
res = self._is_logged(*s, **kwargs)
|
||||||
if not kwargs.get('all', False):
|
if not kwargs.get('all', False):
|
||||||
# at least one entry should be found:
|
# at least one entry should be found:
|
||||||
for s_ in s:
|
if not res: # pragma: no cover
|
||||||
if s_ in logged:
|
logged = self._log.getvalue()
|
||||||
return
|
|
||||||
if True: # pragma: no cover
|
|
||||||
self.fail("None among %r was found in the log: ===\n%s===" % (s, logged))
|
self.fail("None among %r was found in the log: ===\n%s===" % (s, logged))
|
||||||
else:
|
else:
|
||||||
# each entry should be found:
|
# each entry should be found:
|
||||||
for s_ in s:
|
if not res: # pragma: no cover
|
||||||
if s_ not in logged: # pragma: no cover
|
logged = self._log.getvalue()
|
||||||
self.fail("%r was not found in the log: ===\n%s===" % (s_, logged))
|
for s_ in s:
|
||||||
|
if s_ not in logged:
|
||||||
|
self.fail("%r was not found in the log: ===\n%s===" % (s_, logged))
|
||||||
|
|
||||||
def assertNotLogged(self, *s, **kwargs):
|
def assertNotLogged(self, *s, **kwargs):
|
||||||
"""Assert that strings were not logged
|
"""Assert that strings were not logged
|
||||||
|
@ -638,8 +674,10 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||||
if s_ in logged: # pragma: no cover
|
if s_ in logged: # pragma: no cover
|
||||||
self.fail("%r was found in the log: ===\n%s===" % (s_, logged))
|
self.fail("%r was found in the log: ===\n%s===" % (s_, logged))
|
||||||
|
|
||||||
def pruneLog(self):
|
def pruneLog(self, logphase=None):
|
||||||
self._log.truncate(0)
|
self._log.truncate(0)
|
||||||
|
if logphase:
|
||||||
|
logSys.debug('='*5 + ' %s ' + '='*5, logphase)
|
||||||
|
|
||||||
def getLog(self):
|
def getLog(self):
|
||||||
return self._log.getvalue()
|
return self._log.getvalue()
|
||||||
|
@ -649,9 +687,3 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
pid_exists = Utils.pid_exists
|
pid_exists = Utils.pid_exists
|
||||||
|
|
||||||
# Python 2.6 compatibility. in 2.7 assertDictEqual
|
|
||||||
def assert_dict_equal(a, b):
|
|
||||||
assert isinstance(a, dict), "Object is not dictionary: %r" % a
|
|
||||||
assert isinstance(b, dict), "Object is not dictionary: %r" % b
|
|
||||||
assert a==b, "Dictionaries differ:\n%r !=\n%r" % (a, b)
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3.
|
||||||
.TH FAIL2BAN-CLIENT "1" "July 2016" "fail2ban-client v0.10.0a1" "User Commands"
|
.TH FAIL2BAN-CLIENT "1" "September 2016" "fail2ban-client v0.10.0a2" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fail2ban-client \- configure and control the server
|
fail2ban-client \- configure and control the server
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B fail2ban-client
|
.B fail2ban-client
|
||||||
[\fIOPTIONS\fR] \fI<COMMAND>\fR
|
[\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Fail2Ban v0.10.0a1 reads log file that contains password failure report
|
Fail2Ban v0.10.0a2 reads log file that contains password failure report
|
||||||
and bans the corresponding IP addresses using firewall rules.
|
and bans the corresponding IP addresses using firewall rules.
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.TP
|
.TP
|
||||||
|
@ -68,17 +68,36 @@ starts the server and the jails
|
||||||
\fBrestart\fR
|
\fBrestart\fR
|
||||||
restarts the server
|
restarts the server
|
||||||
.TP
|
.TP
|
||||||
\fBreload\fR
|
\fBrestart [\-\-unban] [\-\-if\-exists] <JAIL>\fR
|
||||||
reloads the configuration without
|
restarts the jail <JAIL> (alias
|
||||||
restart
|
for 'reload \fB\-\-restart\fR ... <JAIL>')
|
||||||
.TP
|
.TP
|
||||||
\fBreload <JAIL>\fR
|
\fBreload [\-\-restart] [\-\-unban] [\-\-all]\fR
|
||||||
reloads the jail <JAIL>
|
reloads the configuration without
|
||||||
|
restarting of the server, the
|
||||||
|
option '\-\-restart' activates
|
||||||
|
completely restarting of affected
|
||||||
|
jails, thereby can unban IP
|
||||||
|
addresses (if option '\-\-unban'
|
||||||
|
specified)
|
||||||
|
.TP
|
||||||
|
\fBreload [\-\-restart] [\-\-unban] [\-\-if\-exists] <JAIL>\fR
|
||||||
|
reloads the jail <JAIL>, or
|
||||||
|
restarts it (if option '\-\-restart'
|
||||||
|
specified)
|
||||||
.TP
|
.TP
|
||||||
\fBstop\fR
|
\fBstop\fR
|
||||||
stops all jails and terminate the
|
stops all jails and terminate the
|
||||||
server
|
server
|
||||||
.TP
|
.TP
|
||||||
|
\fBunban \fB\-\-all\fR\fR
|
||||||
|
unbans all IP addresses (in all
|
||||||
|
jails and database)
|
||||||
|
.TP
|
||||||
|
\fBunban <IP> ... <IP>\fR
|
||||||
|
unbans <IP> (in all jails and
|
||||||
|
database)
|
||||||
|
.TP
|
||||||
\fBstatus\fR
|
\fBstatus\fR
|
||||||
gets the current status of the
|
gets the current status of the
|
||||||
server
|
server
|
||||||
|
@ -101,7 +120,9 @@ LOGGING
|
||||||
\fBset loglevel <LEVEL>\fR
|
\fBset loglevel <LEVEL>\fR
|
||||||
sets logging level to <LEVEL>.
|
sets logging level to <LEVEL>.
|
||||||
Levels: CRITICAL, ERROR, WARNING,
|
Levels: CRITICAL, ERROR, WARNING,
|
||||||
NOTICE, INFO, DEBUG
|
NOTICE, INFO, DEBUG, TRACEDEBUG,
|
||||||
|
HEAVYDEBUG or corresponding
|
||||||
|
numeric value (50\-5)
|
||||||
.TP
|
.TP
|
||||||
\fBget loglevel\fR
|
\fBget loglevel\fR
|
||||||
gets the logging level
|
gets the logging level
|
||||||
|
@ -248,9 +269,8 @@ for <JAIL>
|
||||||
\fBset <JAIL> maxlines <LINES>\fR
|
\fBset <JAIL> maxlines <LINES>\fR
|
||||||
sets the number of <LINES> to
|
sets the number of <LINES> to
|
||||||
buffer for regex search for <JAIL>
|
buffer for regex search for <JAIL>
|
||||||
.IP
|
.TP
|
||||||
set <JAIL> addaction <ACT>[ <PYTHONFILE> <JSONKWARGS>]
|
\fBset <JAIL> addaction <ACT>[ <PYTHONFILE> <JSONKWARGS>]\fR
|
||||||
.IP
|
|
||||||
adds a new action named <ACT> for
|
adds a new action named <ACT> for
|
||||||
<JAIL>. Optionally for a Python
|
<JAIL>. Optionally for a Python
|
||||||
based action, a <PYTHONFILE> and
|
based action, a <PYTHONFILE> and
|
||||||
|
@ -262,45 +282,38 @@ removes the action <ACT> from
|
||||||
<JAIL>
|
<JAIL>
|
||||||
.IP
|
.IP
|
||||||
COMMAND ACTION CONFIGURATION
|
COMMAND ACTION CONFIGURATION
|
||||||
.IP
|
.TP
|
||||||
set <JAIL> action <ACT> actionstart <CMD>
|
\fBset <JAIL> action <ACT> actionstart <CMD>\fR
|
||||||
.IP
|
|
||||||
sets the start command <CMD> of
|
sets the start command <CMD> of
|
||||||
the action <ACT> for <JAIL>
|
the action <ACT> for <JAIL>
|
||||||
.IP
|
.TP
|
||||||
set <JAIL> action <ACT> actionstop <CMD> sets the stop command <CMD> of the
|
\fBset <JAIL> action <ACT> actionstop <CMD> sets the stop command <CMD> of the\fR
|
||||||
.IP
|
|
||||||
action <ACT> for <JAIL>
|
action <ACT> for <JAIL>
|
||||||
.IP
|
.TP
|
||||||
set <JAIL> action <ACT> actioncheck <CMD>
|
\fBset <JAIL> action <ACT> actioncheck <CMD>\fR
|
||||||
.IP
|
|
||||||
sets the check command <CMD> of
|
sets the check command <CMD> of
|
||||||
the action <ACT> for <JAIL>
|
the action <ACT> for <JAIL>
|
||||||
.TP
|
.TP
|
||||||
\fBset <JAIL> action <ACT> actionban <CMD>\fR
|
\fBset <JAIL> action <ACT> actionban <CMD>\fR
|
||||||
sets the ban command <CMD> of the
|
sets the ban command <CMD> of the
|
||||||
action <ACT> for <JAIL>
|
action <ACT> for <JAIL>
|
||||||
.IP
|
.TP
|
||||||
set <JAIL> action <ACT> actionunban <CMD>
|
\fBset <JAIL> action <ACT> actionunban <CMD>\fR
|
||||||
.IP
|
|
||||||
sets the unban command <CMD> of
|
sets the unban command <CMD> of
|
||||||
the action <ACT> for <JAIL>
|
the action <ACT> for <JAIL>
|
||||||
.IP
|
.TP
|
||||||
set <JAIL> action <ACT> timeout <TIMEOUT>
|
\fBset <JAIL> action <ACT> timeout <TIMEOUT>\fR
|
||||||
.IP
|
|
||||||
sets <TIMEOUT> as the command
|
sets <TIMEOUT> as the command
|
||||||
timeout in seconds for the action
|
timeout in seconds for the action
|
||||||
<ACT> for <JAIL>
|
<ACT> for <JAIL>
|
||||||
.IP
|
.IP
|
||||||
GENERAL ACTION CONFIGURATION
|
GENERAL ACTION CONFIGURATION
|
||||||
.IP
|
.TP
|
||||||
set <JAIL> action <ACT> <PROPERTY> <VALUE>
|
\fBset <JAIL> action <ACT> <PROPERTY> <VALUE>\fR
|
||||||
.IP
|
|
||||||
sets the <VALUE> of <PROPERTY> for
|
sets the <VALUE> of <PROPERTY> for
|
||||||
the action <ACT> for <JAIL>
|
the action <ACT> for <JAIL>
|
||||||
.IP
|
.TP
|
||||||
set <JAIL> action <ACT> <METHOD>[ <JSONKWARGS>]
|
\fBset <JAIL> action <ACT> <METHOD>[ <JSONKWARGS>]\fR
|
||||||
.IP
|
|
||||||
calls the <METHOD> with
|
calls the <METHOD> with
|
||||||
<JSONKWARGS> for the action <ACT>
|
<JSONKWARGS> for the action <ACT>
|
||||||
for <JAIL>
|
for <JAIL>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3.
|
||||||
.TH FAIL2BAN-REGEX "1" "July 2016" "fail2ban-regex 0.10.0a1" "User Commands"
|
.TH FAIL2BAN-REGEX "1" "September 2016" "fail2ban-regex 0.10.0a2" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fail2ban-regex \- test Fail2ban "failregex" option
|
fail2ban-regex \- test Fail2ban "failregex" option
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B fail2ban-regex
|
.B fail2ban-regex
|
||||||
[\fIOPTIONS\fR] \fI<LOG> <REGEX> \fR[\fIIGNOREREGEX\fR]
|
[\fI\,OPTIONS\/\fR] \fI\,<LOG> <REGEX> \/\fR[\fI\,IGNOREREGEX\/\fR]
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Fail2Ban reads log file that contains password failure report
|
Fail2Ban reads log file that contains password failure report
|
||||||
and bans the corresponding IP addresses using firewall rules.
|
and bans the corresponding IP addresses using firewall rules.
|
||||||
|
@ -16,7 +16,7 @@ string
|
||||||
a string representing a log line
|
a string representing a log line
|
||||||
.TP
|
.TP
|
||||||
filename
|
filename
|
||||||
path to a log file (\fI/var/log/auth.log\fP)
|
path to a log file (\fI\,/var/log/auth.log\/\fP)
|
||||||
.TP
|
.TP
|
||||||
"systemd\-journal"
|
"systemd\-journal"
|
||||||
search systemd journal (systemd\-python required)
|
search systemd journal (systemd\-python required)
|
||||||
|
@ -42,23 +42,28 @@ show program's version number and exit
|
||||||
\fB\-h\fR, \fB\-\-help\fR
|
\fB\-h\fR, \fB\-\-help\fR
|
||||||
show this help message and exit
|
show this help message and exit
|
||||||
.TP
|
.TP
|
||||||
\fB\-d\fR DATEPATTERN, \fB\-\-datepattern\fR=\fIDATEPATTERN\fR
|
\fB\-d\fR DATEPATTERN, \fB\-\-datepattern\fR=\fI\,DATEPATTERN\/\fR
|
||||||
set custom pattern used to match date/times
|
set custom pattern used to match date/times
|
||||||
.TP
|
.TP
|
||||||
\fB\-e\fR ENCODING, \fB\-\-encoding\fR=\fIENCODING\fR
|
\fB\-e\fR ENCODING, \fB\-\-encoding\fR=\fI\,ENCODING\/\fR
|
||||||
File encoding. Default: system locale
|
File encoding. Default: system locale
|
||||||
.TP
|
.TP
|
||||||
\fB\-r\fR, \fB\-\-raw\fR
|
\fB\-r\fR, \fB\-\-raw\fR
|
||||||
Raw hosts, don't resolve dns
|
Raw hosts, don't resolve dns
|
||||||
.TP
|
.TP
|
||||||
\fB\-L\fR MAXLINES, \fB\-\-maxlines\fR=\fIMAXLINES\fR
|
\fB\-\-usedns\fR=\fI\,USEDNS\/\fR
|
||||||
|
DNS specified replacement of tags <HOST> in regexp
|
||||||
|
('yes' \- matches all form of hosts, 'no' \- IP
|
||||||
|
addresses only)
|
||||||
|
.TP
|
||||||
|
\fB\-L\fR MAXLINES, \fB\-\-maxlines\fR=\fI\,MAXLINES\/\fR
|
||||||
maxlines for multi\-line regex
|
maxlines for multi\-line regex
|
||||||
.TP
|
.TP
|
||||||
\fB\-m\fR JOURNALMATCH, \fB\-\-journalmatch\fR=\fIJOURNALMATCH\fR
|
\fB\-m\fR JOURNALMATCH, \fB\-\-journalmatch\fR=\fI\,JOURNALMATCH\/\fR
|
||||||
journalctl style matches overriding filter file.
|
journalctl style matches overriding filter file.
|
||||||
"systemd\-journal" only
|
"systemd\-journal" only
|
||||||
.TP
|
.TP
|
||||||
\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fILOG_LEVEL\fR
|
\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fI\,LOG_LEVEL\/\fR
|
||||||
Log level for the Fail2Ban logger to use
|
Log level for the Fail2Ban logger to use
|
||||||
.TP
|
.TP
|
||||||
\fB\-v\fR, \fB\-\-verbose\fR
|
\fB\-v\fR, \fB\-\-verbose\fR
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3.
|
||||||
.TH FAIL2BAN-SERVER "1" "July 2016" "fail2ban-server v0.10.0a1" "User Commands"
|
.TH FAIL2BAN-SERVER "1" "September 2016" "fail2ban-server v0.10.0a2" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fail2ban-server \- start the server
|
fail2ban-server \- start the server
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B fail2ban-server
|
.B fail2ban-server
|
||||||
[\fIOPTIONS\fR]
|
[\fI\,OPTIONS\/\fR]
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Fail2Ban v0.10.0a1 reads log file that contains password failure report
|
Fail2Ban v0.10.0a2 reads log file that contains password failure report
|
||||||
and bans the corresponding IP addresses using firewall rules.
|
and bans the corresponding IP addresses using firewall rules.
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.TP
|
.TP
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3.
|
||||||
.TH FAIL2BAN-TESTCASES "1" "July 2016" "fail2ban-testcases 0.10.0a1" "User Commands"
|
.TH FAIL2BAN-TESTCASES "1" "September 2016" "fail2ban-testcases 0.10.0a2" "User Commands"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fail2ban-testcases \- run Fail2Ban unit-tests
|
fail2ban-testcases \- run Fail2Ban unit-tests
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B fail2ban-testcases
|
.B fail2ban-testcases
|
||||||
[\fIOPTIONS\fR] [\fIregexps\fR]
|
[\fI\,OPTIONS\/\fR] [\fI\,regexps\/\fR]
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Script to run Fail2Ban tests battery
|
Script to run Fail2Ban tests battery
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
|
@ -15,9 +15,15 @@ show program's version number and exit
|
||||||
\fB\-h\fR, \fB\-\-help\fR
|
\fB\-h\fR, \fB\-\-help\fR
|
||||||
show this help message and exit
|
show this help message and exit
|
||||||
.TP
|
.TP
|
||||||
\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fILOG_LEVEL\fR
|
\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fI\,LOG_LEVEL\/\fR
|
||||||
Log level for the logger to use during running tests
|
Log level for the logger to use during running tests
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-v\fR VERBOSITY, \fB\-\-verbosity\fR=\fI\,VERBOSITY\/\fR
|
||||||
|
Set numerical level of verbosity (0..4)
|
||||||
|
.TP
|
||||||
|
\fB\-\-log\-direct\fR
|
||||||
|
Prevent lazy logging inside tests
|
||||||
|
.TP
|
||||||
\fB\-n\fR, \fB\-\-no\-network\fR
|
\fB\-n\fR, \fB\-\-no\-network\fR
|
||||||
Do not run tests that require the network
|
Do not run tests that require the network
|
||||||
.TP
|
.TP
|
||||||
|
|
|
@ -127,7 +127,7 @@ These files have one section, [Definition].
|
||||||
The items that can be set are:
|
The items that can be set are:
|
||||||
.TP
|
.TP
|
||||||
.B loglevel
|
.B loglevel
|
||||||
verbosity level of log output: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG. Default: ERROR
|
verbosity level of log output: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG, TRACEDEBUG, HEAVYDEBUG or corresponding numeric value (50-5). Default: ERROR (equal 40)
|
||||||
.TP
|
.TP
|
||||||
.B logtarget
|
.B logtarget
|
||||||
log target: filename, SYSLOG, STDERR or STDOUT. Default: STDERR
|
log target: filename, SYSLOG, STDERR or STDOUT. Default: STDERR
|
||||||
|
|
Loading…
Reference in New Issue