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
Yaroslav Halchenko 2014-12-30 16:46:11 -05:00
commit bfadd0100b
34 changed files with 600 additions and 137 deletions

View File

@ -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
---------- ----------

View File

@ -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

View File

@ -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
View File

@ -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:

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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 =

View File

@ -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

View File

@ -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 =

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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'):

View File

@ -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":

View File

@ -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():

View File

@ -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):

View File

@ -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'])

View File

@ -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)

View File

@ -0,0 +1,6 @@
#[INCLUDES]
#before = common.conf
[Definition]
failregex = failure test 1 (filter.d/test.conf) <HOST>

View File

@ -0,0 +1,7 @@
#[INCLUDES]
#before = common.conf
[Definition]
failregex = %(known/failregex)s
failure test 2 (filter.d/test.local) <HOST>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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, [])

View File

@ -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):

View File

@ -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)

View File

@ -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"

248
files/debian-initd Executable file
View File

@ -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
:

9
files/monit/fail2ban Normal file
View File

@ -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