mirror of https://github.com/fail2ban/fail2ban
Merge commit '0.9.1-44-gd65c4f8' into debian
* commit '0.9.1-44-gd65c4f8': (31 commits) moved debian's initd file to files/debian-initd from debian branch Update ChangeLog Monit config BF: adjusted for new IP of example.com downcase example Added an item to "Fixes" postfix-sasl failregex case insensitive clean all after test setup (removes a build directory in current root of fail2ban) exim filter: correct failregex for exim with extended log options small fix: no cover for failed case testSetupInstallRoot will be always skipped, because of "wrong" location of 'setup.py'; better and scalable solution for gh-867 (and gh-868), using only name convention like %(known/failregex)s to add custom expressions, so no interface changes in jail.conf are necessary (for example see test-known-interp in test cases); Changelog entry for preceding fix Separate php-url-fopen logpath by newline python 2.6 compatibility: preventing RuntimeError: dictionary changed size during iteration. interpolation of config readers extended with `%(known/parameter)s`. (means last known option with name `parameter`). test cases extended (now correct) BF: failregex declared direct in jail was joined to single line, (specifying of multiple expressions was not possible); feature request (gh-867): new options for jail introduced addfailregex/addignoreregex: extends regex specified in filter (opposite to failregex/ignoreregex that overwrites it); Add ignoreregex to avoid warning on start Add ignoreregex to avoid warning on start ...pull/1858/head
commit
bfadd0100b
31
ChangeLog
31
ChangeLog
|
@ -4,9 +4,38 @@
|
||||||
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
Fail2Ban (version 0.9.1) 2014/10/29
|
Fail2Ban (version 0.9.1.dev) 2014/10/29
|
||||||
================================================================================
|
================================================================================
|
||||||
|
|
||||||
|
ver. 0.9.2 (2014/XX/XXX) - wanna-be-released
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- Fixes:
|
||||||
|
* $ typo in jail.conf. Thanks Skibbi. Debian bug #767255
|
||||||
|
* grep'ing for IP in *mail-whois-lines.conf should now match also
|
||||||
|
at the begginning and EOL. Thanks Dean Lee
|
||||||
|
* jail.conf
|
||||||
|
- php-url-fopen: separate logpath entries by newline
|
||||||
|
* failregex declared direct in jail was joined to single line (specifying of
|
||||||
|
multiple expressions was not possible).
|
||||||
|
* filters.d/exim.conf - cover different settings of exim logs
|
||||||
|
details. Thanks bes.internal
|
||||||
|
* filter.d/postfix-sasl.conf - failregex is now case insensitive
|
||||||
|
|
||||||
|
- New Features:
|
||||||
|
- New interpolation feature for config readers - `%(known/parameter)s`.
|
||||||
|
(means last known option with name `parameter`). This interpolation makes
|
||||||
|
possible to extend a stock filter or jail regexp in .local file
|
||||||
|
(opposite to simply set failregex/ignoreregex that overwrites it),
|
||||||
|
see gh-867.
|
||||||
|
- Monit config for fail2ban in /files/monit
|
||||||
|
|
||||||
|
- Enhancements:
|
||||||
|
* Enable multiport for firewallcmd-new action. Closes gh-834
|
||||||
|
* files/debian-initd migrated from the debian branch and should be
|
||||||
|
suitable for manual installations now (thanks Juan Karlo de Guzman)
|
||||||
|
|
||||||
|
|
||||||
ver. 0.9.1 (2014/10/29) - better, faster, stronger
|
ver. 0.9.1 (2014/10/29) - better, faster, stronger
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
|
1
MANIFEST
1
MANIFEST
|
@ -328,6 +328,7 @@ man/fail2ban-server.h2m
|
||||||
man/fail2ban-regex.1
|
man/fail2ban-regex.1
|
||||||
man/fail2ban-regex.h2m
|
man/fail2ban-regex.h2m
|
||||||
man/generate-man
|
man/generate-man
|
||||||
|
files/debian-initd
|
||||||
files/gentoo-initd
|
files/gentoo-initd
|
||||||
files/gentoo-confd
|
files/gentoo-confd
|
||||||
files/redhat-initd
|
files/redhat-initd
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
/ _|__ _(_) |_ ) |__ __ _ _ _
|
/ _|__ _(_) |_ ) |__ __ _ _ _
|
||||||
| _/ _` | | |/ /| '_ \/ _` | ' \
|
| _/ _` | | |/ /| '_ \/ _` | ' \
|
||||||
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
|_| \__,_|_|_/___|_.__/\__,_|_||_|
|
||||||
v0.9.1 2014/10/29
|
v0.9.1.dev 2014/??/??
|
||||||
|
|
||||||
## Fail2Ban: ban hosts that cause multiple authentication errors
|
## Fail2Ban: ban hosts that cause multiple authentication errors
|
||||||
|
|
||||||
|
|
14
RELEASE
14
RELEASE
|
@ -61,24 +61,24 @@ Preparation
|
||||||
|
|
||||||
* Which indicates that testcases/files/logs/mysqld.log has been moved or is a directory::
|
* Which indicates that testcases/files/logs/mysqld.log has been moved or is a directory::
|
||||||
|
|
||||||
tar -C /tmp -jxf dist/fail2ban-0.9.1.tar.bz2
|
tar -C /tmp -jxf dist/fail2ban-0.9.2.tar.bz2
|
||||||
|
|
||||||
* clean up current direcory::
|
* clean up current direcory::
|
||||||
|
|
||||||
diff -rul --exclude \*.pyc . /tmp/fail2ban-0.9.1/
|
diff -rul --exclude \*.pyc . /tmp/fail2ban-0.9.2/
|
||||||
|
|
||||||
* Only differences should be files that you don't want distributed.
|
* Only differences should be files that you don't want distributed.
|
||||||
|
|
||||||
|
|
||||||
* Ensure the tests work from the tarball::
|
* Ensure the tests work from the tarball::
|
||||||
|
|
||||||
cd /tmp/fail2ban-0.9.1/ && export PYTHONPATH=`pwd` && bin/fail2ban-testcases
|
cd /tmp/fail2ban-0.9.2/ && export PYTHONPATH=`pwd` && bin/fail2ban-testcases
|
||||||
|
|
||||||
* Add/finalize the corresponding entry in the ChangeLog
|
* Add/finalize the corresponding entry in the ChangeLog
|
||||||
|
|
||||||
* To generate a list of committers use e.g.::
|
* To generate a list of committers use e.g.::
|
||||||
|
|
||||||
git shortlog -sn 0.9.1.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g'
|
git shortlog -sn 0.9.2.. | sed -e 's,^[ 0-9\t]*,,g' | tr '\n' '\|' | sed -e 's:|:, :g'
|
||||||
|
|
||||||
* Ensure the top of the ChangeLog has the right version and current date.
|
* Ensure the top of the ChangeLog has the right version and current date.
|
||||||
* Ensure the top entry of the ChangeLog has the right version and current date.
|
* Ensure the top entry of the ChangeLog has the right version and current date.
|
||||||
|
@ -101,7 +101,7 @@ Preparation
|
||||||
* Tag the release by using a signed (and annotated) tag. Cut/paste
|
* Tag the release by using a signed (and annotated) tag. Cut/paste
|
||||||
release ChangeLog entry as tag annotation::
|
release ChangeLog entry as tag annotation::
|
||||||
|
|
||||||
git tag -s 0.9.1
|
git tag -s 0.9.2
|
||||||
|
|
||||||
Pre Release
|
Pre Release
|
||||||
===========
|
===========
|
||||||
|
@ -144,7 +144,7 @@ Pre Release
|
||||||
|
|
||||||
* https://bugs.mageia.org/buglist.cgi?quicksearch=fail2ban
|
* https://bugs.mageia.org/buglist.cgi?quicksearch=fail2ban
|
||||||
|
|
||||||
* An potentially to the fail2ban-users email list.
|
* And potentially to the fail2ban-users email list.
|
||||||
|
|
||||||
|
|
||||||
* Wait for feedback from distributors
|
* Wait for feedback from distributors
|
||||||
|
@ -185,7 +185,7 @@ Post Release
|
||||||
|
|
||||||
Add the following to the top of the ChangeLog::
|
Add the following to the top of the ChangeLog::
|
||||||
|
|
||||||
ver. 0.9.2 (2014/XX/XXX) - wanna-be-released
|
ver. 0.9.3 (2014/XX/XXX) - wanna-be-released
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- Fixes:
|
- Fixes:
|
||||||
|
|
|
@ -10,9 +10,9 @@ before = iptables-common.conf
|
||||||
|
|
||||||
actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
|
actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
|
||||||
firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 1000 -j RETURN
|
firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 1000 -j RETURN
|
||||||
firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||||
|
|
||||||
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||||
firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
|
firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
|
||||||
firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
|
firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ chain = INPUT_direct
|
||||||
# success
|
# success
|
||||||
# $ firewall-cmd --direct --add-rule ipv4 filter fail2ban-name 1000 -j RETURN
|
# $ firewall-cmd --direct --add-rule ipv4 filter fail2ban-name 1000 -j RETURN
|
||||||
# success
|
# success
|
||||||
# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m state --state NEW -p tcp --dport 22 -j fail2ban-name
|
# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m state --state NEW -p tcp -m multiport --dports 22 -j fail2ban-name
|
||||||
# success
|
# success
|
||||||
# $ firewall-cmd --direct --get-chains ipv4 filter
|
# $ firewall-cmd --direct --get-chains ipv4 filter
|
||||||
# fail2ban-name
|
# fail2ban-name
|
||||||
|
|
|
@ -42,7 +42,7 @@ actionban = printf %%b "Hi,\n
|
||||||
Here is more information about <ip>:\n
|
Here is more information about <ip>:\n
|
||||||
`whois <ip> || echo missing whois program`\n\n
|
`whois <ip> || echo missing whois program`\n\n
|
||||||
Lines containing IP:<ip> in <logpath>\n
|
Lines containing IP:<ip> in <logpath>\n
|
||||||
`grep '[^0-9]<ip>[^0-9]' <logpath>`\n\n
|
`grep -E '(^|[^0-9])<ip>([^0-9]|$)' <logpath>`\n\n
|
||||||
Regards,\n
|
Regards,\n
|
||||||
Fail2Ban"|mail -s "[Fail2Ban] <name>: banned <ip> from `uname -n`" <dest>
|
Fail2Ban"|mail -s "[Fail2Ban] <name>: banned <ip> from `uname -n`" <dest>
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
||||||
Here is more information about <ip>:\n
|
Here is more information about <ip>:\n
|
||||||
`/usr/bin/whois <ip> || echo missing whois program`\n\n
|
`/usr/bin/whois <ip> || echo missing whois program`\n\n
|
||||||
Lines containing IP:<ip> in <logpath>\n
|
Lines containing IP:<ip> in <logpath>\n
|
||||||
`grep '[^0-9]<ip>[^0-9]' <logpath>`\n\n
|
`grep -E '(^|[^0-9])<ip>([^0-9]|$)' <logpath>`\n\n
|
||||||
Regards,\n
|
Regards,\n
|
||||||
Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>
|
Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,10 @@ before = exim-common.conf
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user|Unrouteable address|all relevant MX records point to non-existent hosts)\s*$
|
failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user|Unrouteable address|all relevant MX records point to non-existent hosts)\s*$
|
||||||
^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[<HOST>\]: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$
|
^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[<HOST>\](:\d+)?( I=\[\S+\](:\d+)?)?: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$
|
||||||
^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (relay not permitted|Sender verify failed|Unknown user)\s*$
|
^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (relay not permitted|Sender verify failed|Unknown user)\s*$
|
||||||
^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (connection from|"\S+") %(host_info)s(next )?input=".*"\s*$
|
^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (connection from|"\S+") %(host_info)s(next )?input=".*"\s*$
|
||||||
^%(pid)s SMTP call from \S+ \[<HOST>\](:\d+)? (I=\[\S+\]:\d+ )?dropped: too many nonmail commands \(last was "\S+"\)\s*$
|
^%(pid)s SMTP call from \S+ \[<HOST>\](:\d+)? (I=\[\S+\](:\d+)? )?dropped: too many nonmail commands \(last was "\S+"\)\s*$
|
||||||
|
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ failregex = ^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: (vie
|
||||||
^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: zone transfer '\S+/AXFR/\w+' denied\s*$
|
^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: zone transfer '\S+/AXFR/\w+' denied\s*$
|
||||||
^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: bad zone transfer request: '\S+/IN': non-authoritative zone \(NOTAUTH\)\s*$
|
^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: bad zone transfer request: '\S+/IN': non-authoritative zone \(NOTAUTH\)\s*$
|
||||||
|
|
||||||
|
ignoreregex =
|
||||||
|
|
||||||
# DEV Notes:
|
# DEV Notes:
|
||||||
# Trying to generalize the
|
# Trying to generalize the
|
||||||
# structure which is general to capture general patterns in log
|
# structure which is general to capture general patterns in log
|
||||||
|
|
|
@ -9,7 +9,7 @@ before = common.conf
|
||||||
|
|
||||||
_daemon = postfix/(submission/)?smtp(d|s)
|
_daemon = postfix/(submission/)?smtp(d|s)
|
||||||
|
|
||||||
failregex = ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/]*={0,2})?\s*$
|
failregex = ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL ((?i)LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/]*={0,2})?\s*$
|
||||||
|
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,8 @@ _jailname = recidive
|
||||||
|
|
||||||
failregex = ^(%(__prefix_line)s| %(_daemon)s%(__pid_re)s?:\s+)NOTICE\s+\[(?!%(_jailname)s\])(?:.*)\]\s+Ban\s+<HOST>\s*$
|
failregex = ^(%(__prefix_line)s| %(_daemon)s%(__pid_re)s?:\s+)NOTICE\s+\[(?!%(_jailname)s\])(?:.*)\]\s+Ban\s+<HOST>\s*$
|
||||||
|
|
||||||
|
ignoreregex =
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
|
||||||
journalmatch = _SYSTEMD_UNIT=fail2ban.service PRIORITY=5
|
journalmatch = _SYSTEMD_UNIT=fail2ban.service PRIORITY=5
|
||||||
|
|
|
@ -286,7 +286,7 @@ maxretry = 2
|
||||||
[apache-shellshock]
|
[apache-shellshock]
|
||||||
|
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = $(apache_error_log)s
|
logpath = %(apache_error_log)s
|
||||||
maxretry = 1
|
maxretry = 1
|
||||||
|
|
||||||
[nginx-http-auth]
|
[nginx-http-auth]
|
||||||
|
@ -302,7 +302,8 @@ logpath = %(nginx_error_log)s
|
||||||
[php-url-fopen]
|
[php-url-fopen]
|
||||||
|
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = %(nginx_access_log)s %(apache_access_log)s
|
logpath = %(nginx_access_log)s
|
||||||
|
%(apache_access_log)s
|
||||||
|
|
||||||
|
|
||||||
[suhosin]
|
[suhosin]
|
||||||
|
@ -723,4 +724,4 @@ port = 2222
|
||||||
[portsentry]
|
[portsentry]
|
||||||
enabled = false
|
enabled = false
|
||||||
logpath = /var/lib/portsentry/portsentry.history
|
logpath = /var/lib/portsentry/portsentry.history
|
||||||
maxretry = 1
|
maxretry = 1
|
||||||
|
|
|
@ -226,6 +226,13 @@ after = 1.conf
|
||||||
if isinstance(s, dict):
|
if isinstance(s, dict):
|
||||||
s2 = alls.get(n)
|
s2 = alls.get(n)
|
||||||
if isinstance(s2, dict):
|
if isinstance(s2, dict):
|
||||||
|
# save previous known values, for possible using in local interpolations later:
|
||||||
|
sk = {}
|
||||||
|
for k, v in s2.iteritems():
|
||||||
|
if not k.startswith('known/'):
|
||||||
|
sk['known/'+k] = v
|
||||||
|
s2.update(sk)
|
||||||
|
# merge section
|
||||||
s2.update(s)
|
s2.update(s)
|
||||||
else:
|
else:
|
||||||
alls[n] = s.copy()
|
alls[n] = s.copy()
|
||||||
|
@ -242,3 +249,12 @@ after = 1.conf
|
||||||
return SafeConfigParser.read(self, fileNamesFull, encoding='utf-8')
|
return SafeConfigParser.read(self, fileNamesFull, encoding='utf-8')
|
||||||
else:
|
else:
|
||||||
return SafeConfigParser.read(self, fileNamesFull)
|
return SafeConfigParser.read(self, fileNamesFull)
|
||||||
|
|
||||||
|
def merge_section(self, section, options, pref='known/'):
|
||||||
|
alls = self.get_sections()
|
||||||
|
sk = {}
|
||||||
|
for k, v in options.iteritems():
|
||||||
|
if pref == '' or not k.startswith(pref):
|
||||||
|
sk[pref+k] = v
|
||||||
|
alls[section].update(sk)
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,10 @@ class ConfigReader():
|
||||||
return self._cfg.has_section(sec)
|
return self._cfg.has_section(sec)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def merge_section(self, *args, **kwargs):
|
||||||
|
if self._cfg is not None:
|
||||||
|
return self._cfg.merge_section(*args, **kwargs)
|
||||||
|
|
||||||
def options(self, *args):
|
def options(self, *args):
|
||||||
if self._cfg is not None:
|
if self._cfg is not None:
|
||||||
return self._cfg.options(*args)
|
return self._cfg.options(*args)
|
||||||
|
|
|
@ -46,13 +46,21 @@ class FilterReader(DefinitionInitConfigReader):
|
||||||
|
|
||||||
def getFile(self):
|
def getFile(self):
|
||||||
return self.__file
|
return self.__file
|
||||||
|
|
||||||
def convert(self):
|
def getCombined(self):
|
||||||
stream = list()
|
|
||||||
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
|
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
|
||||||
|
if not len(combinedopts):
|
||||||
|
return {};
|
||||||
opts = CommandAction.substituteRecursiveTags(combinedopts)
|
opts = CommandAction.substituteRecursiveTags(combinedopts)
|
||||||
if not opts:
|
if not opts:
|
||||||
raise ValueError('recursive tag definitions unable to be resolved')
|
raise ValueError('recursive tag definitions unable to be resolved')
|
||||||
|
return opts;
|
||||||
|
|
||||||
|
def convert(self):
|
||||||
|
stream = list()
|
||||||
|
opts = self.getCombined()
|
||||||
|
if not len(opts):
|
||||||
|
return stream;
|
||||||
for opt, value in opts.iteritems():
|
for opt, value in opts.iteritems():
|
||||||
if opt == "failregex":
|
if opt == "failregex":
|
||||||
for regex in value.split('\n'):
|
for regex in value.split('\n'):
|
||||||
|
|
|
@ -87,6 +87,8 @@ class JailReader(ConfigReader):
|
||||||
return pathList
|
return pathList
|
||||||
|
|
||||||
def getOptions(self):
|
def getOptions(self):
|
||||||
|
opts1st = [["bool", "enabled", False],
|
||||||
|
["string", "filter", ""]]
|
||||||
opts = [["bool", "enabled", False],
|
opts = [["bool", "enabled", False],
|
||||||
["string", "logpath", None],
|
["string", "logpath", None],
|
||||||
["string", "logencoding", None],
|
["string", "logencoding", None],
|
||||||
|
@ -101,7 +103,9 @@ class JailReader(ConfigReader):
|
||||||
["string", "ignoreip", None],
|
["string", "ignoreip", None],
|
||||||
["string", "filter", ""],
|
["string", "filter", ""],
|
||||||
["string", "action", ""]]
|
["string", "action", ""]]
|
||||||
self.__opts = ConfigReader.getOptions(self, self.__name, opts)
|
|
||||||
|
# Read first options only needed for merge defaults ('known/...' from filter):
|
||||||
|
self.__opts = ConfigReader.getOptions(self, self.__name, opts1st)
|
||||||
if not self.__opts:
|
if not self.__opts:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -113,14 +117,24 @@ class JailReader(ConfigReader):
|
||||||
self.__filter = FilterReader(
|
self.__filter = FilterReader(
|
||||||
filterName, self.__name, filterOpt, share_config=self.share_config, basedir=self.getBaseDir())
|
filterName, self.__name, filterOpt, share_config=self.share_config, basedir=self.getBaseDir())
|
||||||
ret = self.__filter.read()
|
ret = self.__filter.read()
|
||||||
if ret:
|
# merge options from filter as 'known/...':
|
||||||
self.__filter.getOptions(self.__opts)
|
self.__filter.getOptions(self.__opts)
|
||||||
else:
|
ConfigReader.merge_section(self, self.__name, self.__filter.getCombined(), 'known/')
|
||||||
|
if not ret:
|
||||||
logSys.error("Unable to read the filter")
|
logSys.error("Unable to read the filter")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
self.__filter = None
|
self.__filter = None
|
||||||
logSys.warning("No filter set for jail %s" % self.__name)
|
logSys.warning("No filter set for jail %s" % self.__name)
|
||||||
|
|
||||||
|
# Read second all options (so variables like %(known/param) can be interpolated):
|
||||||
|
self.__opts = ConfigReader.getOptions(self, self.__name, opts)
|
||||||
|
if not self.__opts:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# cumulate filter options again (ignore given in jail):
|
||||||
|
if self.__filter:
|
||||||
|
self.__filter.getOptions(self.__opts)
|
||||||
|
|
||||||
# Read action
|
# Read action
|
||||||
for act in self.__opts["action"].split('\n'):
|
for act in self.__opts["action"].split('\n'):
|
||||||
|
@ -202,7 +216,10 @@ class JailReader(ConfigReader):
|
||||||
elif opt == "usedns":
|
elif opt == "usedns":
|
||||||
stream.append(["set", self.__name, "usedns", self.__opts[opt]])
|
stream.append(["set", self.__name, "usedns", self.__opts[opt]])
|
||||||
elif opt == "failregex":
|
elif opt == "failregex":
|
||||||
stream.append(["set", self.__name, "addfailregex", self.__opts[opt]])
|
for regex in self.__opts[opt].split('\n'):
|
||||||
|
# Do not send a command if the rule is empty.
|
||||||
|
if regex != '':
|
||||||
|
stream.append(["set", self.__name, "addfailregex", regex])
|
||||||
elif opt == "ignorecommand":
|
elif opt == "ignorecommand":
|
||||||
stream.append(["set", self.__name, "ignorecommand", self.__opts[opt]])
|
stream.append(["set", self.__name, "ignorecommand", self.__opts[opt]])
|
||||||
elif opt == "ignoreregex":
|
elif opt == "ignoreregex":
|
||||||
|
|
|
@ -243,6 +243,45 @@ class Actions(JailThread, Mapping):
|
||||||
logSys.debug(self._jail.name + ": action terminated")
|
logSys.debug(self._jail.name + ": action terminated")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def __getBansMerged(self, mi, overalljails=False):
|
||||||
|
"""Gets bans merged once, a helper for lambda(s), prevents stop of executing action by any exception inside.
|
||||||
|
|
||||||
|
This function never returns None for ainfo lambdas - always a ticket (merged or single one)
|
||||||
|
and prevents any errors through merging (to guarantee ban actions will be executed).
|
||||||
|
[TODO] move merging to observer - here we could wait for merge and read already merged info from a database
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
mi : dict
|
||||||
|
merge info, initial for lambda should contains {ip, ticket}
|
||||||
|
overalljails : bool
|
||||||
|
switch to get a merged bans :
|
||||||
|
False - (default) bans merged for current jail only
|
||||||
|
True - bans merged for all jails of current ip address
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
BanTicket
|
||||||
|
merged or self ticket only
|
||||||
|
"""
|
||||||
|
idx = 'all' if overalljails else 'jail'
|
||||||
|
if idx in mi:
|
||||||
|
return mi[idx] if mi[idx] is not None else mi['ticket']
|
||||||
|
try:
|
||||||
|
jail=self._jail
|
||||||
|
ip=mi['ip']
|
||||||
|
mi[idx] = None
|
||||||
|
if overalljails:
|
||||||
|
mi[idx] = jail.database.getBansMerged(ip=ip)
|
||||||
|
else:
|
||||||
|
mi[idx] = jail.database.getBansMerged(ip=ip, jail=jail)
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error(
|
||||||
|
"Failed to get %s bans merged, jail '%s': %s",
|
||||||
|
idx, jail.name, e,
|
||||||
|
exc_info=logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||||
|
return mi[idx] if mi[idx] is not None else mi['ticket']
|
||||||
|
|
||||||
def __checkBan(self):
|
def __checkBan(self):
|
||||||
"""Check for IP address to ban.
|
"""Check for IP address to ban.
|
||||||
|
|
||||||
|
@ -264,14 +303,12 @@ class Actions(JailThread, Mapping):
|
||||||
aInfo["time"] = bTicket.getTime()
|
aInfo["time"] = bTicket.getTime()
|
||||||
aInfo["matches"] = "\n".join(bTicket.getMatches())
|
aInfo["matches"] = "\n".join(bTicket.getMatches())
|
||||||
if self._jail.database is not None:
|
if self._jail.database is not None:
|
||||||
aInfo["ipmatches"] = lambda jail=self._jail: "\n".join(
|
mi4ip = lambda overalljails=False, self=self, \
|
||||||
jail.database.getBansMerged(ip=ip).getMatches())
|
mi={'ip':ip, 'ticket':bTicket}: self.__getBansMerged(mi, overalljails)
|
||||||
aInfo["ipjailmatches"] = lambda jail=self._jail: "\n".join(
|
aInfo["ipmatches"] = lambda: "\n".join(mi4ip(True).getMatches())
|
||||||
jail.database.getBansMerged(ip=ip, jail=jail).getMatches())
|
aInfo["ipjailmatches"] = lambda: "\n".join(mi4ip().getMatches())
|
||||||
aInfo["ipfailures"] = lambda jail=self._jail: \
|
aInfo["ipfailures"] = lambda: mi4ip(True).getAttempt()
|
||||||
jail.database.getBansMerged(ip=ip).getAttempt()
|
aInfo["ipjailfailures"] = lambda: mi4ip().getAttempt()
|
||||||
aInfo["ipjailfailures"] = lambda jail=self._jail: \
|
|
||||||
jail.database.getBansMerged(ip=ip, jail=jail).getAttempt()
|
|
||||||
if self.__banManager.addBanTicket(bTicket):
|
if self.__banManager.addBanTicket(bTicket):
|
||||||
logSys.notice("[%s] Ban %s" % (self._jail.name, aInfo["ip"]))
|
logSys.notice("[%s] Ban %s" % (self._jail.name, aInfo["ip"]))
|
||||||
for name, action in self._actions.iteritems():
|
for name, action in self._actions.iteritems():
|
||||||
|
|
|
@ -27,7 +27,7 @@ import sqlite3
|
||||||
import json
|
import json
|
||||||
import locale
|
import locale
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from threading import Lock
|
from threading import RLock
|
||||||
|
|
||||||
from .mytime import MyTime
|
from .mytime import MyTime
|
||||||
from .ticket import FailTicket
|
from .ticket import FailTicket
|
||||||
|
@ -123,7 +123,7 @@ class Fail2BanDb(object):
|
||||||
|
|
||||||
def __init__(self, filename, purgeAge=24*60*60):
|
def __init__(self, filename, purgeAge=24*60*60):
|
||||||
try:
|
try:
|
||||||
self._lock = Lock()
|
self._lock = RLock()
|
||||||
self._db = sqlite3.connect(
|
self._db = sqlite3.connect(
|
||||||
filename, check_same_thread=False,
|
filename, check_same_thread=False,
|
||||||
detect_types=sqlite3.PARSE_DECLTYPES)
|
detect_types=sqlite3.PARSE_DECLTYPES)
|
||||||
|
@ -365,6 +365,10 @@ class Fail2BanDb(object):
|
||||||
del self._bansMergedCache[(ticket.getIP(), jail)]
|
del self._bansMergedCache[(ticket.getIP(), jail)]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
try:
|
||||||
|
del self._bansMergedCache[(ticket.getIP(), None)]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
#TODO: Implement data parts once arbitrary match keys completed
|
#TODO: Implement data parts once arbitrary match keys completed
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
|
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
|
||||||
|
@ -455,40 +459,41 @@ class Fail2BanDb(object):
|
||||||
in a list. When `ip` argument passed, a single `Ticket` is
|
in a list. When `ip` argument passed, a single `Ticket` is
|
||||||
returned.
|
returned.
|
||||||
"""
|
"""
|
||||||
cacheKey = None
|
with self._lock:
|
||||||
if bantime is None or bantime < 0:
|
cacheKey = None
|
||||||
cacheKey = (ip, jail)
|
if bantime is None or bantime < 0:
|
||||||
if cacheKey in self._bansMergedCache:
|
cacheKey = (ip, jail)
|
||||||
return self._bansMergedCache[cacheKey]
|
if cacheKey in self._bansMergedCache:
|
||||||
|
return self._bansMergedCache[cacheKey]
|
||||||
|
|
||||||
tickets = []
|
tickets = []
|
||||||
ticket = None
|
ticket = None
|
||||||
|
|
||||||
results = list(self._getBans(ip=ip, jail=jail, bantime=bantime))
|
results = list(self._getBans(ip=ip, jail=jail, bantime=bantime))
|
||||||
if results:
|
if results:
|
||||||
prev_banip = results[0][0]
|
prev_banip = results[0][0]
|
||||||
matches = []
|
matches = []
|
||||||
failures = 0
|
failures = 0
|
||||||
for banip, timeofban, data in results:
|
for banip, timeofban, data in results:
|
||||||
#TODO: Implement data parts once arbitrary match keys completed
|
#TODO: Implement data parts once arbitrary match keys completed
|
||||||
if banip != prev_banip:
|
if banip != prev_banip:
|
||||||
ticket = FailTicket(prev_banip, prev_timeofban, matches)
|
ticket = FailTicket(prev_banip, prev_timeofban, matches)
|
||||||
ticket.setAttempt(failures)
|
ticket.setAttempt(failures)
|
||||||
tickets.append(ticket)
|
tickets.append(ticket)
|
||||||
# Reset variables
|
# Reset variables
|
||||||
prev_banip = banip
|
prev_banip = banip
|
||||||
matches = []
|
matches = []
|
||||||
failures = 0
|
failures = 0
|
||||||
matches.extend(data['matches'])
|
matches.extend(data['matches'])
|
||||||
failures += data['failures']
|
failures += data['failures']
|
||||||
prev_timeofban = timeofban
|
prev_timeofban = timeofban
|
||||||
ticket = FailTicket(banip, prev_timeofban, matches)
|
ticket = FailTicket(banip, prev_timeofban, matches)
|
||||||
ticket.setAttempt(failures)
|
ticket.setAttempt(failures)
|
||||||
tickets.append(ticket)
|
tickets.append(ticket)
|
||||||
|
|
||||||
if cacheKey:
|
if cacheKey:
|
||||||
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
|
||||||
|
|
||||||
@commitandrollback
|
@commitandrollback
|
||||||
def purge(self, cur):
|
def purge(self, cur):
|
||||||
|
|
|
@ -167,9 +167,10 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
logelements.append(logentry['_HOSTNAME'])
|
logelements.append(logentry['_HOSTNAME'])
|
||||||
if logentry.get('SYSLOG_IDENTIFIER'):
|
if logentry.get('SYSLOG_IDENTIFIER'):
|
||||||
logelements.append(logentry['SYSLOG_IDENTIFIER'])
|
logelements.append(logentry['SYSLOG_IDENTIFIER'])
|
||||||
if logentry.get('SYSLOG_PID') or logentry.get('_PID'):
|
if logentry.get('SYSLOG_PID'):
|
||||||
logelements[-1] += ("[%i]" % logentry.get(
|
logelements[-1] += ("[%i]" % logentry['SYSLOG_PID'])
|
||||||
'SYSLOG_PID', logentry['_PID']))
|
elif logentry.get('_PID'):
|
||||||
|
logelements[-1] += ("[%i]" % logentry['_PID'])
|
||||||
logelements[-1] += ":"
|
logelements[-1] += ":"
|
||||||
elif logentry.get('_COMM'):
|
elif logentry.get('_COMM'):
|
||||||
logelements.append(logentry['_COMM'])
|
logelements.append(logentry['_COMM'])
|
||||||
|
|
|
@ -155,12 +155,16 @@ c = d ;in line comment
|
||||||
|
|
||||||
class JailReaderTest(LogCaptureTestCase):
|
class JailReaderTest(LogCaptureTestCase):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(JailReaderTest, self).__init__(*args, **kwargs)
|
||||||
|
self.__share_cfg = {}
|
||||||
|
|
||||||
def testIncorrectJail(self):
|
def testIncorrectJail(self):
|
||||||
jail = JailReader('XXXABSENTXXX', basedir=CONFIG_DIR)
|
jail = JailReader('XXXABSENTXXX', basedir=CONFIG_DIR, share_config = self.__share_cfg)
|
||||||
self.assertRaises(ValueError, jail.read)
|
self.assertRaises(ValueError, jail.read)
|
||||||
|
|
||||||
def testJailActionEmpty(self):
|
def testJailActionEmpty(self):
|
||||||
jail = JailReader('emptyaction', basedir=IMPERFECT_CONFIG)
|
jail = JailReader('emptyaction', basedir=IMPERFECT_CONFIG, share_config = self.__share_cfg)
|
||||||
self.assertTrue(jail.read())
|
self.assertTrue(jail.read())
|
||||||
self.assertTrue(jail.getOptions())
|
self.assertTrue(jail.getOptions())
|
||||||
self.assertTrue(jail.isEnabled())
|
self.assertTrue(jail.isEnabled())
|
||||||
|
@ -168,7 +172,7 @@ class JailReaderTest(LogCaptureTestCase):
|
||||||
self.assertTrue(self._is_logged('No actions were defined for emptyaction'))
|
self.assertTrue(self._is_logged('No actions were defined for emptyaction'))
|
||||||
|
|
||||||
def testJailActionFilterMissing(self):
|
def testJailActionFilterMissing(self):
|
||||||
jail = JailReader('missingbitsjail', basedir=IMPERFECT_CONFIG)
|
jail = JailReader('missingbitsjail', basedir=IMPERFECT_CONFIG, share_config = self.__share_cfg)
|
||||||
self.assertTrue(jail.read())
|
self.assertTrue(jail.read())
|
||||||
self.assertFalse(jail.getOptions())
|
self.assertFalse(jail.getOptions())
|
||||||
self.assertTrue(jail.isEnabled())
|
self.assertTrue(jail.isEnabled())
|
||||||
|
@ -176,7 +180,7 @@ class JailReaderTest(LogCaptureTestCase):
|
||||||
self.assertTrue(self._is_logged('Unable to read the filter'))
|
self.assertTrue(self._is_logged('Unable to read the filter'))
|
||||||
|
|
||||||
def TODOtestJailActionBrokenDef(self):
|
def TODOtestJailActionBrokenDef(self):
|
||||||
jail = JailReader('brokenactiondef', basedir=IMPERFECT_CONFIG)
|
jail = JailReader('brokenactiondef', basedir=IMPERFECT_CONFIG, share_config = self.__share_cfg)
|
||||||
self.assertTrue(jail.read())
|
self.assertTrue(jail.read())
|
||||||
self.assertFalse(jail.getOptions())
|
self.assertFalse(jail.getOptions())
|
||||||
self.assertTrue(jail.isEnabled())
|
self.assertTrue(jail.isEnabled())
|
||||||
|
@ -187,7 +191,7 @@ class JailReaderTest(LogCaptureTestCase):
|
||||||
|
|
||||||
if STOCK:
|
if STOCK:
|
||||||
def testStockSSHJail(self):
|
def testStockSSHJail(self):
|
||||||
jail = JailReader('sshd', basedir=CONFIG_DIR) # we are running tests from root project dir atm
|
jail = JailReader('sshd', basedir=CONFIG_DIR, share_config = self.__share_cfg) # we are running tests from root project dir atm
|
||||||
self.assertTrue(jail.read())
|
self.assertTrue(jail.read())
|
||||||
self.assertTrue(jail.getOptions())
|
self.assertTrue(jail.getOptions())
|
||||||
self.assertFalse(jail.isEnabled())
|
self.assertFalse(jail.isEnabled())
|
||||||
|
@ -411,13 +415,17 @@ class JailsReaderTestCache(LogCaptureTestCase):
|
||||||
|
|
||||||
class JailsReaderTest(LogCaptureTestCase):
|
class JailsReaderTest(LogCaptureTestCase):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(JailsReaderTest, self).__init__(*args, **kwargs)
|
||||||
|
self.__share_cfg = {}
|
||||||
|
|
||||||
def testProvidingBadBasedir(self):
|
def testProvidingBadBasedir(self):
|
||||||
if not os.path.exists('/XXX'):
|
if not os.path.exists('/XXX'):
|
||||||
reader = JailsReader(basedir='/XXX')
|
reader = JailsReader(basedir='/XXX')
|
||||||
self.assertRaises(ValueError, reader.read)
|
self.assertRaises(ValueError, reader.read)
|
||||||
|
|
||||||
def testReadTestJailConf(self):
|
def testReadTestJailConf(self):
|
||||||
jails = JailsReader(basedir=IMPERFECT_CONFIG)
|
jails = JailsReader(basedir=IMPERFECT_CONFIG, share_config=self.__share_cfg)
|
||||||
self.assertTrue(jails.read())
|
self.assertTrue(jails.read())
|
||||||
self.assertFalse(jails.getOptions())
|
self.assertFalse(jails.getOptions())
|
||||||
self.assertRaises(ValueError, jails.convert)
|
self.assertRaises(ValueError, jails.convert)
|
||||||
|
@ -425,6 +433,11 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
self.assertEqual(sorted(comm_commands),
|
self.assertEqual(sorted(comm_commands),
|
||||||
sorted([['add', 'emptyaction', 'auto'],
|
sorted([['add', 'emptyaction', 'auto'],
|
||||||
|
['add', 'test-known-interp', 'auto'],
|
||||||
|
['set', 'test-known-interp', 'addfailregex', 'failure test 1 (filter.d/test.conf) <HOST>'],
|
||||||
|
['set', 'test-known-interp', 'addfailregex', 'failure test 2 (filter.d/test.local) <HOST>'],
|
||||||
|
['set', 'test-known-interp', 'addfailregex', 'failure test 3 (jail.local) <HOST>'],
|
||||||
|
['start', 'test-known-interp'],
|
||||||
['add', 'missinglogfiles', 'auto'],
|
['add', 'missinglogfiles', 'auto'],
|
||||||
['set', 'missinglogfiles', 'addfailregex', '<IP>'],
|
['set', 'missinglogfiles', 'addfailregex', '<IP>'],
|
||||||
['add', 'brokenaction', 'auto'],
|
['add', 'brokenaction', 'auto'],
|
||||||
|
@ -447,7 +460,7 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
|
|
||||||
if STOCK:
|
if STOCK:
|
||||||
def testReadStockJailConf(self):
|
def testReadStockJailConf(self):
|
||||||
jails = JailsReader(basedir=CONFIG_DIR) # we are running tests from root project dir atm
|
jails = JailsReader(basedir=CONFIG_DIR, share_config=self.__share_cfg) # we are running tests from root project dir atm
|
||||||
self.assertTrue(jails.read()) # opens fine
|
self.assertTrue(jails.read()) # opens fine
|
||||||
self.assertTrue(jails.getOptions()) # reads fine
|
self.assertTrue(jails.getOptions()) # reads fine
|
||||||
comm_commands = jails.convert()
|
comm_commands = jails.convert()
|
||||||
|
@ -508,7 +521,7 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
|
|
||||||
# Verify that all filters found under config/ have a jail
|
# Verify that all filters found under config/ have a jail
|
||||||
def testReadStockJailFilterComplete(self):
|
def testReadStockJailFilterComplete(self):
|
||||||
jails = JailsReader(basedir=CONFIG_DIR, force_enable=True)
|
jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=self.__share_cfg)
|
||||||
self.assertTrue(jails.read()) # opens fine
|
self.assertTrue(jails.read()) # opens fine
|
||||||
self.assertTrue(jails.getOptions()) # reads fine
|
self.assertTrue(jails.getOptions()) # reads fine
|
||||||
# grab all filter names
|
# grab all filter names
|
||||||
|
@ -525,7 +538,7 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
def testReadStockJailConfForceEnabled(self):
|
def testReadStockJailConfForceEnabled(self):
|
||||||
# more of a smoke test to make sure that no obvious surprises
|
# more of a smoke test to make sure that no obvious surprises
|
||||||
# on users' systems when enabling shipped jails
|
# on users' systems when enabling shipped jails
|
||||||
jails = JailsReader(basedir=CONFIG_DIR, force_enable=True) # we are running tests from root project dir atm
|
jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=self.__share_cfg) # we are running tests from root project dir atm
|
||||||
self.assertTrue(jails.read()) # opens fine
|
self.assertTrue(jails.read()) # opens fine
|
||||||
self.assertTrue(jails.getOptions()) # reads fine
|
self.assertTrue(jails.getOptions()) # reads fine
|
||||||
comm_commands = jails.convert(allow_no_files=True)
|
comm_commands = jails.convert(allow_no_files=True)
|
||||||
|
@ -620,7 +633,7 @@ action = testaction1[actname=test1]
|
||||||
filter = testfilter1
|
filter = testfilter1
|
||||||
""")
|
""")
|
||||||
jailfd.close()
|
jailfd.close()
|
||||||
jails = JailsReader(basedir=basedir)
|
jails = JailsReader(basedir=basedir, share_config=self.__share_cfg)
|
||||||
self.assertTrue(jails.read())
|
self.assertTrue(jails.read())
|
||||||
self.assertTrue(jails.getOptions())
|
self.assertTrue(jails.getOptions())
|
||||||
comm_commands = jails.convert(allow_no_files=True)
|
comm_commands = jails.convert(allow_no_files=True)
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
#[INCLUDES]
|
||||||
|
#before = common.conf
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
failregex = failure test 1 (filter.d/test.conf) <HOST>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
#[INCLUDES]
|
||||||
|
#before = common.conf
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
failregex = %(known/failregex)s
|
||||||
|
failure test 2 (filter.d/test.local) <HOST>
|
||||||
|
|
|
@ -13,6 +13,12 @@ failregex = <IP>
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
ignoreip =
|
ignoreip =
|
||||||
|
|
||||||
|
[test-known-interp]
|
||||||
|
enabled = true
|
||||||
|
filter = test
|
||||||
|
failregex = %(known/failregex)s
|
||||||
|
failure test 3 (jail.local) <HOST>
|
||||||
|
|
||||||
[missinglogfiles]
|
[missinglogfiles]
|
||||||
enabled = true
|
enabled = true
|
||||||
logpath = /weapons/of/mass/destruction
|
logpath = /weapons/of/mass/destruction
|
||||||
|
|
|
@ -32,18 +32,21 @@ import shutil
|
||||||
from ..server.filter import FileContainer
|
from ..server.filter import FileContainer
|
||||||
from ..server.mytime import MyTime
|
from ..server.mytime import MyTime
|
||||||
from ..server.ticket import FailTicket
|
from ..server.ticket import FailTicket
|
||||||
|
from ..server.actions import Actions
|
||||||
from .dummyjail import DummyJail
|
from .dummyjail import DummyJail
|
||||||
try:
|
try:
|
||||||
from ..server.database import Fail2BanDb
|
from ..server.database import Fail2BanDb
|
||||||
except ImportError:
|
except ImportError:
|
||||||
Fail2BanDb = None
|
Fail2BanDb = None
|
||||||
|
from .utils import LogCaptureTestCase
|
||||||
|
|
||||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||||
|
|
||||||
class DatabaseTest(unittest.TestCase):
|
class DatabaseTest(LogCaptureTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
|
super(DatabaseTest, self).setUp()
|
||||||
if Fail2BanDb is None and sys.version_info >= (2,7): # pragma: no cover
|
if Fail2BanDb is None and sys.version_info >= (2,7): # pragma: no cover
|
||||||
raise unittest.SkipTest(
|
raise unittest.SkipTest(
|
||||||
"Unable to import fail2ban database module as sqlite is not "
|
"Unable to import fail2ban database module as sqlite is not "
|
||||||
|
@ -55,6 +58,7 @@ class DatabaseTest(unittest.TestCase):
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Call after every test case."""
|
"""Call after every test case."""
|
||||||
|
super(DatabaseTest, self).tearDown()
|
||||||
if Fail2BanDb is None: # pragma: no cover
|
if Fail2BanDb is None: # pragma: no cover
|
||||||
return
|
return
|
||||||
# Cleanup
|
# Cleanup
|
||||||
|
@ -267,6 +271,22 @@ class DatabaseTest(unittest.TestCase):
|
||||||
tickets = self.db.getBansMerged(bantime=-1)
|
tickets = self.db.getBansMerged(bantime=-1)
|
||||||
self.assertEqual(len(tickets), 2)
|
self.assertEqual(len(tickets), 2)
|
||||||
|
|
||||||
|
def testActionWithDB(self):
|
||||||
|
# test action together with database functionality
|
||||||
|
self.testAddJail() # Jail required
|
||||||
|
self.jail.database = self.db;
|
||||||
|
actions = Actions(self.jail)
|
||||||
|
actions.add(
|
||||||
|
"action_checkainfo",
|
||||||
|
os.path.join(TEST_FILES_DIR, "action.d/action_checkainfo.py"),
|
||||||
|
{})
|
||||||
|
ticket = FailTicket("1.2.3.4", MyTime.time(), ['test', 'test'])
|
||||||
|
ticket.setAttempt(5)
|
||||||
|
self.jail.putFailTicket(ticket)
|
||||||
|
actions._Actions__checkBan()
|
||||||
|
self.assertTrue(self._is_logged("ban ainfo %s, %s, %s, %s" % (True, True, True, True)))
|
||||||
|
|
||||||
|
|
||||||
def testPurge(self):
|
def testPurge(self):
|
||||||
if Fail2BanDb is None: # pragma: no cover
|
if Fail2BanDb is None: # pragma: no cover
|
||||||
return
|
return
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
from fail2ban.server.action import ActionBase
|
||||||
|
|
||||||
|
class TestAction(ActionBase):
|
||||||
|
|
||||||
|
def ban(self, aInfo):
|
||||||
|
self._logSys.info("ban ainfo %s, %s, %s, %s",
|
||||||
|
aInfo["ipmatches"] != '', aInfo["ipjailmatches"] != '', aInfo["ipfailures"] > 0, aInfo["ipjailfailures"] > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def unban(self, aInfo):
|
||||||
|
pass
|
||||||
|
|
||||||
|
Action = TestAction
|
|
@ -40,3 +40,6 @@
|
||||||
|
|
||||||
# failJSON: { "time": "2014-01-12T02:07:48", "match": true , "host": "85.214.85.40" }
|
# failJSON: { "time": "2014-01-12T02:07:48", "match": true , "host": "85.214.85.40" }
|
||||||
2014-01-12 02:07:48 dovecot_login authenticator failed for h1832461.stratoserver.net (User) [85.214.85.40]: 535 Incorrect authentication data (set_id=scanner)
|
2014-01-12 02:07:48 dovecot_login authenticator failed for h1832461.stratoserver.net (User) [85.214.85.40]: 535 Incorrect authentication data (set_id=scanner)
|
||||||
|
|
||||||
|
# failJSON: { "time": "2014-12-02T03:00:23", "match": true , "host": "193.254.202.35" }
|
||||||
|
2014-12-02 03:00:23 auth_plain authenticator failed for (rom182) [193.254.202.35]:41556 I=[10.0.0.1]:25: 535 Incorrect authentication data (set_id=webmaster)
|
||||||
|
|
|
@ -8,3 +8,7 @@ Mar 10 13:33:30 gandalf postfix/smtpd[3937]: warning: HOSTNAME[1.1.1.1]: SASL LO
|
||||||
#3 Example from postfix post-debian changes to rename to add "submission" to syslog name
|
#3 Example from postfix post-debian changes to rename to add "submission" to syslog name
|
||||||
# failJSON: { "time": "2004-09-06T00:44:56", "match": true , "host": "82.221.106.233" }
|
# failJSON: { "time": "2004-09-06T00:44:56", "match": true , "host": "82.221.106.233" }
|
||||||
Sep 6 00:44:56 trianon postfix/submission/smtpd[11538]: warning: unknown[82.221.106.233]: SASL LOGIN authentication failed: UGFzc3dvcmQ6
|
Sep 6 00:44:56 trianon postfix/submission/smtpd[11538]: warning: unknown[82.221.106.233]: SASL LOGIN authentication failed: UGFzc3dvcmQ6
|
||||||
|
|
||||||
|
#4 Example from postfix post-debian changes to rename to add "submission" to syslog name + downcase
|
||||||
|
# failJSON: { "time": "2004-09-06T00:44:57", "match": true , "host": "82.221.106.233" }
|
||||||
|
Sep 6 00:44:57 trianon postfix/submission/smtpd[11538]: warning: unknown[82.221.106.233]: SASL login authentication failed: UGFzc3dvcmQ6
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2
|
Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2
|
||||||
Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2
|
Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2
|
||||||
|
|
|
@ -888,12 +888,12 @@ class GetFailures(unittest.TestCase):
|
||||||
|
|
||||||
def testGetFailuresUseDNS(self):
|
def testGetFailuresUseDNS(self):
|
||||||
# We should still catch failures with usedns = no ;-)
|
# We should still catch failures with usedns = no ;-)
|
||||||
output_yes = ('93.184.216.119', 2, 1124013539.0,
|
output_yes = ('93.184.216.34', 2, 1124013539.0,
|
||||||
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2',
|
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2',
|
||||||
u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2'])
|
u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2'])
|
||||||
|
|
||||||
output_no = ('93.184.216.119', 1, 1124013539.0,
|
output_no = ('93.184.216.34', 1, 1124013539.0,
|
||||||
[u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2'])
|
[u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2'])
|
||||||
|
|
||||||
# Actually no exception would be raised -- it will be just set to 'no'
|
# Actually no exception would be raised -- it will be just set to 'no'
|
||||||
#self.assertRaises(ValueError,
|
#self.assertRaises(ValueError,
|
||||||
|
@ -993,9 +993,9 @@ class DNSUtilsTests(unittest.TestCase):
|
||||||
res = DNSUtils.textToIp('www.example.com', 'no')
|
res = DNSUtils.textToIp('www.example.com', 'no')
|
||||||
self.assertEqual(res, [])
|
self.assertEqual(res, [])
|
||||||
res = DNSUtils.textToIp('www.example.com', 'warn')
|
res = DNSUtils.textToIp('www.example.com', 'warn')
|
||||||
self.assertEqual(res, ['93.184.216.119'])
|
self.assertEqual(res, ['93.184.216.34'])
|
||||||
res = DNSUtils.textToIp('www.example.com', 'yes')
|
res = DNSUtils.textToIp('www.example.com', 'yes')
|
||||||
self.assertEqual(res, ['93.184.216.119'])
|
self.assertEqual(res, ['93.184.216.34'])
|
||||||
|
|
||||||
def testTextToIp(self):
|
def testTextToIp(self):
|
||||||
# Test hostnames
|
# Test hostnames
|
||||||
|
@ -1007,7 +1007,7 @@ class DNSUtilsTests(unittest.TestCase):
|
||||||
for s in hostnames:
|
for s in hostnames:
|
||||||
res = DNSUtils.textToIp(s, 'yes')
|
res = DNSUtils.textToIp(s, 'yes')
|
||||||
if s == 'www.example.com':
|
if s == 'www.example.com':
|
||||||
self.assertEqual(res, ['93.184.216.119'])
|
self.assertEqual(res, ['93.184.216.34'])
|
||||||
else:
|
else:
|
||||||
self.assertEqual(res, [])
|
self.assertEqual(res, [])
|
||||||
|
|
||||||
|
|
|
@ -55,21 +55,12 @@ class HelpersTest(unittest.TestCase):
|
||||||
# might be fragile due to ' vs "
|
# might be fragile due to ' vs "
|
||||||
self.assertEqual(args, "('Very bad', None)")
|
self.assertEqual(args, "('Very bad', None)")
|
||||||
|
|
||||||
# based on
|
|
||||||
# http://stackoverflow.com/questions/2186525/use-a-glob-to-find-files-recursively-in-python
|
|
||||||
def recursive_glob(treeroot, pattern):
|
|
||||||
results = []
|
|
||||||
for base, dirs, files in os.walk(treeroot):
|
|
||||||
goodfiles = fnmatch.filter(dirs + files, pattern)
|
|
||||||
results.extend(os.path.join(base, f) for f in goodfiles)
|
|
||||||
return results
|
|
||||||
|
|
||||||
class SetupTest(unittest.TestCase):
|
class SetupTest(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
setup = os.path.join(os.path.dirname(__file__), '..', 'setup.py')
|
setup = os.path.join(os.path.dirname(__file__), '..', '..', 'setup.py')
|
||||||
self.setup = os.path.exists(setup) and setup or None
|
self.setup = os.path.exists(setup) and setup or None
|
||||||
if not self.setup and sys.version_info >= (2,7): # running not out of the source
|
if not self.setup and sys.version_info >= (2,7): # pragma: no cover - running not out of the source
|
||||||
raise unittest.SkipTest(
|
raise unittest.SkipTest(
|
||||||
"Seems to be running not out of source distribution"
|
"Seems to be running not out of source distribution"
|
||||||
" -- cannot locate setup.py")
|
" -- cannot locate setup.py")
|
||||||
|
@ -77,42 +68,53 @@ class SetupTest(unittest.TestCase):
|
||||||
def testSetupInstallRoot(self):
|
def testSetupInstallRoot(self):
|
||||||
if not self.setup: return # if verbose skip didn't work out
|
if not self.setup: return # if verbose skip didn't work out
|
||||||
tmp = tempfile.mkdtemp()
|
tmp = tempfile.mkdtemp()
|
||||||
os.system("%s %s install --root=%s >/dev/null"
|
try:
|
||||||
% (sys.executable, self.setup, tmp))
|
os.system("%s %s install --root=%s >/dev/null"
|
||||||
|
% (sys.executable, self.setup, tmp))
|
||||||
|
|
||||||
def addpath(l):
|
def strippath(l):
|
||||||
return [os.path.join(tmp, x) for x in l]
|
return [x[len(tmp)+1:] for x in l]
|
||||||
|
|
||||||
def strippath(l):
|
got = strippath(sorted(glob('%s/*' % tmp)))
|
||||||
return [x[len(tmp)+1:] for x in l]
|
need = ['etc', 'usr', 'var']
|
||||||
|
|
||||||
got = strippath(sorted(glob('%s/*' % tmp)))
|
# if anything is missing
|
||||||
need = ['etc', 'usr', 'var']
|
if set(need).difference(got): # pragma: no cover
|
||||||
|
# below code was actually to print out not missing but
|
||||||
|
# rather files in 'excess'. Left in place in case we
|
||||||
|
# decide to revert to such more strict test
|
||||||
|
|
||||||
# if anything is missing
|
# based on
|
||||||
if set(need).difference(got):
|
# http://stackoverflow.com/questions/2186525/use-a-glob-to-find-files-recursively-in-python
|
||||||
# below code was actually to print out not missing but
|
def recursive_glob(treeroot, pattern):
|
||||||
# rather files in 'excess'. Left in place in case we
|
results = []
|
||||||
# decide to revert to such more strict test
|
for base, dirs, files in os.walk(treeroot):
|
||||||
files = {}
|
goodfiles = fnmatch.filter(dirs + files, pattern)
|
||||||
for missing in set(got).difference(need):
|
results.extend(os.path.join(base, f) for f in goodfiles)
|
||||||
missing_full = os.path.join(tmp, missing)
|
return results
|
||||||
files[missing] = os.path.exists(missing_full) \
|
|
||||||
and strippath(recursive_glob(missing_full, '*')) or None
|
|
||||||
|
|
||||||
self.assertEqual(
|
files = {}
|
||||||
got, need,
|
for missing in set(got).difference(need):
|
||||||
msg="Got: %s Needed: %s under %s. Files under new paths: %s"
|
missing_full = os.path.join(tmp, missing)
|
||||||
% (got, need, tmp, files))
|
files[missing] = os.path.exists(missing_full) \
|
||||||
|
and strippath(recursive_glob(missing_full, '*')) or None
|
||||||
|
|
||||||
# Assure presence of some files we expect to see in the installation
|
self.assertEqual(
|
||||||
for f in ('etc/fail2ban/fail2ban.conf',
|
got, need,
|
||||||
'etc/fail2ban/jail.conf'):
|
msg="Got: %s Needed: %s under %s. Files under new paths: %s"
|
||||||
self.assertTrue(os.path.exists(os.path.join(tmp, f)),
|
% (got, need, tmp, files))
|
||||||
msg="Can't find %s" % f)
|
|
||||||
|
|
||||||
# clean up
|
# Assure presence of some files we expect to see in the installation
|
||||||
shutil.rmtree(tmp)
|
for f in ('etc/fail2ban/fail2ban.conf',
|
||||||
|
'etc/fail2ban/jail.conf'):
|
||||||
|
self.assertTrue(os.path.exists(os.path.join(tmp, f)),
|
||||||
|
msg="Can't find %s" % f)
|
||||||
|
finally:
|
||||||
|
# clean up
|
||||||
|
shutil.rmtree(tmp)
|
||||||
|
# remove build directory
|
||||||
|
os.system("%s %s clean --all >/dev/null"
|
||||||
|
% (sys.executable, self.setup))
|
||||||
|
|
||||||
class TestsUtilsTest(unittest.TestCase):
|
class TestsUtilsTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -804,7 +804,7 @@ class RegexTests(unittest.TestCase):
|
||||||
|
|
||||||
class _BadThread(JailThread):
|
class _BadThread(JailThread):
|
||||||
def run(self):
|
def run(self):
|
||||||
int("ignore this exception -- raised for testing")
|
raise RuntimeError('run bad thread exception')
|
||||||
|
|
||||||
class LoggingTests(LogCaptureTestCase):
|
class LoggingTests(LogCaptureTestCase):
|
||||||
|
|
||||||
|
@ -814,7 +814,15 @@ class LoggingTests(LogCaptureTestCase):
|
||||||
self.assertEqual(testLogSys.name, "fail2ban.name")
|
self.assertEqual(testLogSys.name, "fail2ban.name")
|
||||||
|
|
||||||
def testFail2BanExceptHook(self):
|
def testFail2BanExceptHook(self):
|
||||||
badThread = _BadThread()
|
prev_exchook = sys.__excepthook__
|
||||||
badThread.start()
|
x = []
|
||||||
badThread.join()
|
sys.__excepthook__ = lambda *args: x.append(args)
|
||||||
self.assertTrue(self._is_logged("Unhandled exception"))
|
try:
|
||||||
|
badThread = _BadThread()
|
||||||
|
badThread.start()
|
||||||
|
badThread.join()
|
||||||
|
self.assertTrue(self._is_logged("Unhandled exception"))
|
||||||
|
finally:
|
||||||
|
sys.__excepthook__ = prev_exchook
|
||||||
|
self.assertEqual(len(x), 1)
|
||||||
|
self.assertEqual(x[0][0], RuntimeError)
|
||||||
|
|
|
@ -24,4 +24,4 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko, Steven Hiscocks, Daniel Black"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2014 Yaroslav Halchenko, 2013-2013 Steven Hiscocks, Daniel Black"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2014 Yaroslav Halchenko, 2013-2013 Steven Hiscocks, Daniel Black"
|
||||||
__license__ = "GPL-v2+"
|
__license__ = "GPL-v2+"
|
||||||
|
|
||||||
version = "0.9.1"
|
version = "0.9.1.dev"
|
||||||
|
|
|
@ -0,0 +1,248 @@
|
||||||
|
#! /bin/sh
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: fail2ban
|
||||||
|
# Required-Start: $local_fs $remote_fs
|
||||||
|
# Required-Stop: $local_fs $remote_fs
|
||||||
|
# Should-Start: $time $network $syslog iptables firehol shorewall ipmasq arno-iptables-firewall iptables-persistent ferm
|
||||||
|
# Should-Stop: $network $syslog iptables firehol shorewall ipmasq arno-iptables-firewall iptables-persistent ferm
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: Start/stop fail2ban
|
||||||
|
# Description: Start/stop fail2ban, a daemon scanning the log files and
|
||||||
|
# banning potential attackers.
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
# Author: Aaron Isotton <aaron@isotton.com>
|
||||||
|
# Modified: by Yaroslav Halchenko <debian@onerussian.com>
|
||||||
|
# reindented + minor corrections + to work on sarge without modifications
|
||||||
|
# Modified: by Glenn Aaldering <glenn@openvideo.nl>
|
||||||
|
# added exit codes for status command
|
||||||
|
# Modified: by Juan Karlo de Guzman <jkarlodg@gmail.com>
|
||||||
|
# corrected the DAEMON's path and the SOCKFILE
|
||||||
|
# rename this file: (sudo) mv /etc/init.d/fail2ban.init /etc/init.d/fail2ban
|
||||||
|
# same with the logrotate file: (sudo) mv /etc/logrotate.d/fail2ban.logrotate /etc/logrotate.d/fail2ban
|
||||||
|
#
|
||||||
|
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
DESC="authentication failure monitor"
|
||||||
|
NAME=fail2ban
|
||||||
|
|
||||||
|
# fail2ban-client is not a daemon itself but starts a daemon and
|
||||||
|
# loads its with configuration
|
||||||
|
DAEMON=/usr/local/bin/$NAME-client
|
||||||
|
SCRIPTNAME=/etc/init.d/$NAME
|
||||||
|
|
||||||
|
# Ad-hoc way to parse out socket file name
|
||||||
|
SOCKFILE=`grep -h '^[^#]*socket *=' /etc/$NAME/$NAME.conf /etc/$NAME/$NAME.local 2>/dev/null \
|
||||||
|
| tail -n 1 | sed -e 's/.*socket *= *//g' -e 's/ *$//g'`
|
||||||
|
[ -z "$SOCKFILE" ] && SOCKFILE='/var/run/fail2ban.sock'
|
||||||
|
|
||||||
|
# Exit if the package is not installed
|
||||||
|
[ -x "$DAEMON" ] || exit 0
|
||||||
|
|
||||||
|
# Run as root by default.
|
||||||
|
FAIL2BAN_USER=root
|
||||||
|
|
||||||
|
# Read configuration variable file if it is present
|
||||||
|
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||||
|
DAEMON_ARGS="$FAIL2BAN_OPTS"
|
||||||
|
|
||||||
|
# Load the VERBOSE setting and other rcS variables
|
||||||
|
[ -f /etc/default/rcS ] && . /etc/default/rcS
|
||||||
|
|
||||||
|
# Predefine what can be missing from lsb source later on -- necessary to run
|
||||||
|
# on sarge. Just present it in a bit more compact way from what was shipped
|
||||||
|
log_daemon_msg () {
|
||||||
|
[ -z "$1" ] && return 1
|
||||||
|
echo -n "$1:"
|
||||||
|
[ -z "$2" ] || echo -n " $2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Define LSB log_* functions.
|
||||||
|
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
|
||||||
|
# Actually has to (>=2.0-7) present in sarge. log_daemon_msg is predefined
|
||||||
|
# so we must be ok
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
#
|
||||||
|
# Shortcut function for abnormal init script interruption
|
||||||
|
#
|
||||||
|
report_bug()
|
||||||
|
{
|
||||||
|
echo $*
|
||||||
|
echo "Please submit a bug report to Debian BTS (reportbug fail2ban)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Helper function to check if socket is present, which is often left after
|
||||||
|
# abnormal exit of fail2ban and needs to be removed
|
||||||
|
#
|
||||||
|
check_socket()
|
||||||
|
{
|
||||||
|
# Return
|
||||||
|
# 0 if socket is present and readable
|
||||||
|
# 1 if socket file is not present
|
||||||
|
# 2 if socket file is present but not readable
|
||||||
|
# 3 if socket file is present but is not a socket
|
||||||
|
[ -e "$SOCKFILE" ] || return 1
|
||||||
|
[ -r "$SOCKFILE" ] || return 2
|
||||||
|
[ -S "$SOCKFILE" ] || return 3
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function that starts the daemon/service
|
||||||
|
#
|
||||||
|
do_start()
|
||||||
|
{
|
||||||
|
# Return
|
||||||
|
# 0 if daemon has been started
|
||||||
|
# 1 if daemon was already running
|
||||||
|
# 2 if daemon could not be started
|
||||||
|
do_status && return 1
|
||||||
|
|
||||||
|
if [ -e "$SOCKFILE" ]; then
|
||||||
|
log_failure_msg "Socket file $SOCKFILE is present"
|
||||||
|
[ "$1" = "force-start" ] \
|
||||||
|
&& log_success_msg "Starting anyway as requested" \
|
||||||
|
|| return 2
|
||||||
|
DAEMON_ARGS="$DAEMON_ARGS -x"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Assure that /var/run/fail2ban exists
|
||||||
|
[ -d /var/run/fail2ban ] || mkdir -p /var/run/fail2ban
|
||||||
|
|
||||||
|
if [ "$FAIL2BAN_USER" != "root" ]; then
|
||||||
|
# Make the socket directory, IP lists and fail2ban log
|
||||||
|
# files writable by fail2ban
|
||||||
|
chown "$FAIL2BAN_USER" /var/run/fail2ban
|
||||||
|
# Create the logfile if it doesn't exist
|
||||||
|
touch /var/log/fail2ban.log
|
||||||
|
chown "$FAIL2BAN_USER" /var/log/fail2ban.log
|
||||||
|
find /proc/net/xt_recent -name 'fail2ban-*' -exec chown "$FAIL2BAN_USER" {} \;
|
||||||
|
fi
|
||||||
|
|
||||||
|
start-stop-daemon --start --quiet --chuid "$FAIL2BAN_USER" --exec $DAEMON -- \
|
||||||
|
$DAEMON_ARGS start > /dev/null\
|
||||||
|
|| return 2
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function that checks the status of fail2ban and returns
|
||||||
|
# corresponding code
|
||||||
|
#
|
||||||
|
do_status()
|
||||||
|
{
|
||||||
|
$DAEMON ping > /dev/null 2>&1
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function that stops the daemon/service
|
||||||
|
#
|
||||||
|
do_stop()
|
||||||
|
{
|
||||||
|
# Return
|
||||||
|
# 0 if daemon has been stopped
|
||||||
|
# 1 if daemon was already stopped
|
||||||
|
# 2 if daemon could not be stopped
|
||||||
|
# other if a failure occurred
|
||||||
|
$DAEMON status > /dev/null 2>&1 || return 1
|
||||||
|
$DAEMON stop > /dev/null || return 2
|
||||||
|
|
||||||
|
# now we need actually to wait a bit since it might take time
|
||||||
|
# for server to react on client's stop request. Especially
|
||||||
|
# important for restart command on slow boxes
|
||||||
|
count=1
|
||||||
|
while do_status && [ $count -lt 60 ]; do
|
||||||
|
sleep 1
|
||||||
|
count=$(($count+1))
|
||||||
|
done
|
||||||
|
[ $count -lt 60 ] || return 3 # failed to stop
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function to reload configuration
|
||||||
|
#
|
||||||
|
do_reload() {
|
||||||
|
$DAEMON reload > /dev/null && return 0 || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# yoh:
|
||||||
|
# shortcut function to don't duplicate case statements and to don't use
|
||||||
|
# bashisms (arrays). Fixes #368218
|
||||||
|
#
|
||||||
|
log_end_msg_wrapper()
|
||||||
|
{
|
||||||
|
if [ "$3" != "no" ]; then
|
||||||
|
[ $1 -lt $2 ] && value=0 || value=1
|
||||||
|
log_end_msg $value
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
command="$1"
|
||||||
|
case "$command" in
|
||||||
|
start|force-start)
|
||||||
|
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||||
|
do_start "$command"
|
||||||
|
log_end_msg_wrapper $? 2 "$VERBOSE"
|
||||||
|
;;
|
||||||
|
|
||||||
|
stop)
|
||||||
|
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||||
|
do_stop
|
||||||
|
log_end_msg_wrapper $? 2 "$VERBOSE"
|
||||||
|
;;
|
||||||
|
|
||||||
|
restart|force-reload)
|
||||||
|
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||||
|
do_stop
|
||||||
|
case "$?" in
|
||||||
|
0|1)
|
||||||
|
do_start
|
||||||
|
log_end_msg_wrapper $? 1 "always"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Failed to stop
|
||||||
|
log_end_msg 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
|
||||||
|
reload|force-reload)
|
||||||
|
log_daemon_msg "Reloading $DESC" "$NAME"
|
||||||
|
do_reload
|
||||||
|
log_end_msg $?
|
||||||
|
;;
|
||||||
|
|
||||||
|
status)
|
||||||
|
log_daemon_msg "Status of $DESC"
|
||||||
|
do_status
|
||||||
|
case $? in
|
||||||
|
0) log_success_msg " $NAME is running" ;;
|
||||||
|
255)
|
||||||
|
check_socket
|
||||||
|
case $? in
|
||||||
|
1) log_failure_msg " $NAME is not running" && exit 3 ;;
|
||||||
|
0) log_failure_msg " $NAME is not running but $SOCKFILE exists" && exit 3 ;;
|
||||||
|
2) log_failure_msg " $SOCKFILE not readable, status of $NAME is unknown" && exit 3 ;;
|
||||||
|
3) log_failure_msg " $SOCKFILE exists but not a socket, status of $NAME is unknown" && exit 3 ;;
|
||||||
|
*) report_bug "Unknown return code from $NAME:check_socket." && exit 4 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*) report_bug "Unknown $NAME status code" && exit 4
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $SCRIPTNAME {start|force-start|stop|restart|force-reload|status}" >&2
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
:
|
|
@ -0,0 +1,9 @@
|
||||||
|
check process fail2ban with pidfile /var/run/fail2ban/fail2ban.pid
|
||||||
|
group services
|
||||||
|
start program = "/etc/init.d/fail2ban force-start"
|
||||||
|
stop program = "/etc/init.d/fail2ban stop || :"
|
||||||
|
if failed unixsocket /var/run/fail2ban/fail2ban.sock then restart
|
||||||
|
if 5 restarts within 5 cycles then timeout
|
||||||
|
|
||||||
|
check file fail2ban_log with path /var/log/fail2ban.log
|
||||||
|
if match "ERROR|WARNING" then alert
|
Loading…
Reference in New Issue