mirror of https://github.com/fail2ban/fail2ban
MRG: merge so far - flushLogs not working yet
commit
7c0efc8ec8
20
ChangeLog
20
ChangeLog
|
@ -70,8 +70,27 @@ ver. 0.8.12 (2013/12/XX) - things-can-only-get-better
|
|||
- IMPORTANT incompatible changes:
|
||||
|
||||
- Fixes:
|
||||
- Rename firewall-cmd-direct-new to firewall-cmd-new to fit within jail name
|
||||
name length. As per gh-395
|
||||
- allow for ",milliseconds" in the custom date format of proftpd.log
|
||||
- allow for ", referer ..." in apache-* filter for apache error logs.
|
||||
- allow for spaces at the beginning of kernel messages. Closes gh-448
|
||||
- recidive jail to block all protocols. Closes gh-440. Thanks Ioan Indreias
|
||||
- smtps not a IANA standard and has been removed from Arch. Replaced with
|
||||
465. Thanks Stefan. Closes gh-447
|
||||
- mysqld-syslog-iptables rule was too long. Part of gh-447.
|
||||
- add 'flushlogs' command to allow logrotation without clobbering logtarget
|
||||
settings. Closes gh-458, Debian bug #697333, Redhat bug #891798.
|
||||
- complain action - ensure where not matching other IPs in log sample.
|
||||
Closes gh-467
|
||||
- Fix firewall-cmd actioncheck - patch from Adam Tkac. Redhat Bug #979622
|
||||
|
||||
- Enhancements:
|
||||
- long names on jails documented based on iptables limit of 30 less
|
||||
len("fail2ban-").
|
||||
- remove indentation of name and loglevel while logging to SYSLOG to
|
||||
resolve syslog(-ng) parsing problems. Closes Debian bug #730202.
|
||||
- added squid filter. Thanks Roman Gelfand.
|
||||
|
||||
- New Features:
|
||||
|
||||
|
@ -79,6 +98,7 @@ ver. 0.8.12 (2013/12/XX) - things-can-only-get-better
|
|||
* filter.d/solid-pop3d -- added thanks to Jacques Lav!gnotte on mailinglist.
|
||||
|
||||
- Enhancements:
|
||||
- loglines now also report "[PID]" after the name portion
|
||||
|
||||
ver. 0.8.11 (2013/11/13) - loves-unittests-and-tight-DoS-free-filter-regexes
|
||||
|
||||
|
|
4
DEVELOP
4
DEVELOP
|
@ -743,10 +743,14 @@ Releasing
|
|||
|
||||
* https://github.com/fail2ban/fail2ban/issues?sort=updated&state=open
|
||||
* http://bugs.debian.org/cgi-bin/pkgreport.cgi?dist=unstable;package=fail2ban
|
||||
* https://bugs.launchpad.net/ubuntu/+source/fail2ban
|
||||
* http://bugs.sabayon.org/buglist.cgi?quicksearch=net-analyzer%2Ffail2ban
|
||||
* https://bugs.archlinux.org/?project=5&cat%5B%5D=33&string=fail2ban
|
||||
* https://bugs.gentoo.org/buglist.cgi?query_format=advanced&short_desc=fail2ban&bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=IN_PROGRESS&short_desc_type=allwords
|
||||
* https://bugzilla.redhat.com/buglist.cgi?query_format=advanced&bug_status=NEW&bug_status=ASSIGNED&component=fail2ban&classification=Red%20Hat&classification=Fedora
|
||||
* http://www.freebsd.org/cgi/query-pr-summary.cgi?text=fail2ban
|
||||
* https://bugs.mageia.org/buglist.cgi?quicksearch=fail2ban
|
||||
* https://build.opensuse.org/package/requests/openSUSE:Factory/fail2ban
|
||||
|
||||
# Make sure the tests pass
|
||||
|
||||
|
|
4
MANIFEST
4
MANIFEST
|
@ -261,3 +261,7 @@ files/fail2ban-tmpfiles.conf
|
|||
files/fail2ban.service
|
||||
files/ipmasq-ZZZzzz_fail2ban.rul
|
||||
files/gen_badbots
|
||||
testcases/config/jail.conf
|
||||
testcases/config/fail2ban.conf
|
||||
testcases/config/filter.d/simple.conf
|
||||
testcases/config/action.d/brokenaction.conf
|
||||
|
|
5
THANKS
5
THANKS
|
@ -6,8 +6,10 @@ the project. If you have been left off, please let us know
|
|||
(preferably send a pull request on github with the "fix") and you will
|
||||
be added
|
||||
|
||||
Adam Tkac
|
||||
Adrien Clerc
|
||||
ache
|
||||
ag4ve (Shawn)
|
||||
Amir Caspi
|
||||
Andrey G. Grozin
|
||||
Andy Fragen
|
||||
|
@ -35,6 +37,7 @@ Hanno 'Rince' Wagner
|
|||
Iain Lea
|
||||
John Thoe
|
||||
Jacques Lav!gnotte
|
||||
Ioan Indreias
|
||||
Jonathan Kamens
|
||||
Jonathan Lanning
|
||||
Jonathan Underwood
|
||||
|
@ -61,10 +64,12 @@ RealRancor
|
|||
René Berber
|
||||
Robert Edeker
|
||||
Rolf Fokkens
|
||||
Roman Gelfand
|
||||
Russell Odom
|
||||
Sebastian Arcus
|
||||
Sireyessire
|
||||
silviogarbes
|
||||
Stefan Tatschner
|
||||
Stephen Gildea
|
||||
Steven Hiscocks
|
||||
TESTOVIK
|
||||
|
|
|
@ -48,7 +48,7 @@ def get_opt_parser():
|
|||
p.add_options([
|
||||
Option('-l', "--log-level", type="choice",
|
||||
dest="log_level",
|
||||
choices=('heavydebug', 'debug', 'info', 'warn', 'error', 'fatal'),
|
||||
choices=('heavydebug', 'debug', 'info', 'warning', 'error', 'fatal'),
|
||||
default=None,
|
||||
help="Log level for the logger to use during running tests"),
|
||||
Option('-n', "--no-network", action="store_true",
|
||||
|
@ -72,7 +72,7 @@ parser = get_opt_parser()
|
|||
logSys = logging.getLogger("fail2ban")
|
||||
|
||||
# Numerical level of verbosity corresponding to a log "level"
|
||||
verbosity = {'heavydebug': 3,
|
||||
verbosity = {'heavydebug': 4,
|
||||
'debug': 3,
|
||||
'info': 2,
|
||||
'warning': 1,
|
||||
|
|
|
@ -41,3 +41,10 @@ actionban = apf --deny <ip> "banned by Fail2Ban <name>"
|
|||
# Values: CMD
|
||||
#
|
||||
actionunban = apf --remove <ip>
|
||||
|
||||
[Init]
|
||||
|
||||
# Name used in APF configuration
|
||||
#
|
||||
name = default
|
||||
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
# Fail2Ban configuration file
|
||||
#
|
||||
# Author: Steven Hiscocks
|
||||
#
|
||||
#
|
||||
|
||||
# Action to report IP address to blocklist.de
|
||||
# Blocklist.de must be signed up to at www.blocklist.de
|
||||
# Once registered, one or more servers can be added.
|
||||
# This action requires the server 'email address' and the assoicate apikey.
|
||||
#
|
||||
# From blocklist.de:
|
||||
# www.blocklist.de is a free and voluntary service provided by a
|
||||
# Fraud/Abuse-specialist, whose servers are often attacked on SSH-,
|
||||
# Mail-Login-, FTP-, Webserver- and other services.
|
||||
# The mission is to report all attacks to the abuse deparments of the
|
||||
# infected PCs/servers to ensure that the responsible provider can inform
|
||||
# the customer about the infection and disable them
|
||||
#
|
||||
# IMPORTANT:
|
||||
#
|
||||
# Reporting an IP of abuse is a serious complaint. Make sure that it is
|
||||
# serious. Fail2ban developers and network owners recommend you only use this
|
||||
# action for:
|
||||
# * The recidive where the IP has been banned multiple times
|
||||
# * Where maxretry has been set quite high, beyond the normal user typing
|
||||
# password incorrectly.
|
||||
# * For filters that have a low likelyhood of receiving human errors
|
||||
#
|
||||
|
||||
[Definition]
|
||||
|
||||
# Option: actionstart
|
||||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart =
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop =
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck =
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = curl --fail --data-urlencode 'server=<email>' --data 'apikey=<apikey>' --data 'service=<service>' --data 'ip=<ip>' --data-urlencode 'logs=<matches>' --data 'format=text' --user-agent "fail2ban v0.8.12" "https://www.blocklist.de/en/httpreports.html"
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban =
|
||||
|
||||
[Init]
|
||||
|
||||
# Option: email
|
||||
# Notes server email address, as per blocklise.de account
|
||||
# Values: STRING Default: None
|
||||
#
|
||||
#email =
|
||||
|
||||
# Option: apikey
|
||||
# Notes your user blocklist.de user account apikey
|
||||
# Values: STRING Default: None
|
||||
#
|
||||
#apikey =
|
||||
|
||||
# Option: service
|
||||
# Notes service name you are reporting on, typically aligns with filter name
|
||||
# see http://www.blocklist.de/en/httpreports.html for full list
|
||||
# Values: STRING Default: None
|
||||
#
|
||||
#service =
|
|
@ -58,7 +58,7 @@ actioncheck =
|
|||
actionban = ADDRESSES=`whois <ip> | perl -e 'while (<STDIN>) { next if /^changed|@(ripe|apnic)\.net/io; $m += (/abuse|trouble:|report|spam|security/io?3:0); if (/([a-z0-9_\-\.+]+@[a-z0-9\-]+(\.[[a-z0-9\-]+)+)/io) { while (s/([a-z0-9_\-\.+]+@[a-z0-9\-]+(\.[[a-z0-9\-]+)+)//io) { if ($m) { $a{lc($1)}=$m } else { $b{lc($1)}=$m } } $m=0 } else { $m && --$m } } if (%%a) {print join(",",keys(%%a))} else {print join(",",keys(%%b))}'`
|
||||
IP=<ip>
|
||||
if [ ! -z "$ADDRESSES" ]; then
|
||||
(printf %%b "<message>\n"; date '+Note: Local timezone is %%z (%%Z)'; grep '<ip>' <logpath>) | <mailcmd> "Abuse from <ip>" <mailargs> $ADDRESSES
|
||||
(printf %%b "<message>\n"; date '+Note: Local timezone is %%z (%%Z)'; grep -E '(^|[^0-9])<ip>([^0-9]|$)' <logpath>) | <mailcmd> "Abuse from <ip>" <mailargs> $ADDRESSES
|
||||
fi
|
||||
|
||||
# Option: actionunban
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
# Fail2Ban configuration file
|
||||
#
|
||||
# Author: Edgar Hoch
|
||||
# Copied from iptables-new.conf and modified for use with firewalld by Edgar Hoch.
|
||||
# It uses "firewall-cmd" instead of "iptables".
|
||||
#
|
||||
# Because of the --remove-rules in stop this action requires firewalld-0.3.8+
|
||||
|
||||
[INCLUDES]
|
||||
|
@ -20,7 +16,7 @@ actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state
|
|||
firewall-cmd --direct --remove-rules ipv4 filter fail2ban-<name>
|
||||
firewall-cmd --direct --remove-chain ipv4 filter fail2ban-<name>
|
||||
|
||||
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q 'fail2ban-<name>[ \t]'
|
||||
actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q '^fail2ban-<name>$'
|
||||
|
||||
actionban = firewall-cmd --direct --add-rule ipv4 filter fail2ban-<name> 0 -s <ip> -j <blocktype>
|
||||
|
||||
|
@ -50,3 +46,27 @@ protocol = tcp
|
|||
# Values: [ STRING ]
|
||||
#
|
||||
chain = INPUT_direct
|
||||
|
||||
# DEV NOTES:
|
||||
#
|
||||
# Author: Edgar Hoch
|
||||
# Copied from iptables-new.conf and modified for use with firewalld by Edgar Hoch.
|
||||
# It uses "firewall-cmd" instead of "iptables".
|
||||
#
|
||||
# Output:
|
||||
#
|
||||
# $ firewall-cmd --direct --add-chain ipv4 filter fail2ban-name
|
||||
# success
|
||||
# $ firewall-cmd --direct --add-rule ipv4 filter fail2ban-name 1000 -j RETURN
|
||||
# success
|
||||
# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m state --state NEW -p tcp --dport 22 -j fail2ban-name
|
||||
# success
|
||||
# $ firewall-cmd --direct --get-chains ipv4 filter
|
||||
# fail2ban-name
|
||||
# $ firewall-cmd --direct --get-chains ipv4 filter | od -h
|
||||
# 0000000 6166 6c69 6232 6e61 6e2d 6d61 0a65
|
||||
# $ firewall-cmd --direct --get-chains ipv4 filter | grep -Eq 'fail2ban-name( |$)' ; echo $?
|
||||
# 0
|
||||
# $ firewall-cmd -V
|
||||
# 0.3.8
|
||||
|
|
@ -43,7 +43,7 @@ actionban = ipfw add <blocktype> tcp from <ip> to <localhost> <port>
|
|||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = ipfw delete `ipfw list | grep -i <ip> | awk '{print $1;}'`
|
||||
actionunban = ipfw delete `ipfw list | grep -i "[^0-9]<ip>[^0-9]" | awk '{print $1;}'`
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -39,10 +39,10 @@ actioncheck =
|
|||
actionban = printf %%b "Hi,\n
|
||||
The IP <ip> has just been banned by Fail2Ban after
|
||||
<failures> attempts against <name>.\n\n
|
||||
Here are more information about <ip>:\n
|
||||
`whois <ip>`\n\n
|
||||
Here is more information about <ip>:\n
|
||||
`whois <ip> || echo missing whois program`\n\n
|
||||
Lines containing IP:<ip> in <logpath>\n
|
||||
`grep '\<<ip>\>' <logpath>`\n\n
|
||||
`grep '[^0-9]<ip>[^0-9]' <logpath>`\n\n
|
||||
Regards,\n
|
||||
Fail2Ban"|mail -s "[Fail2Ban] <name>: banned <ip> from `uname -n`" <dest>
|
||||
|
||||
|
|
|
@ -39,8 +39,8 @@ actioncheck =
|
|||
actionban = printf %%b "Hi,\n
|
||||
The IP <ip> has just been banned by Fail2Ban after
|
||||
<failures> attempts against <name>.\n\n
|
||||
Here are more information about <ip>:\n
|
||||
`whois <ip>`\n
|
||||
Here is more information about <ip>:\n
|
||||
`whois <ip> || echo missing whois program`\n
|
||||
Regards,\n
|
||||
Fail2Ban"|mail -s "[Fail2Ban] <name>: banned <ip> from `uname -n`" <dest>
|
||||
|
||||
|
|
|
@ -23,10 +23,10 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
|||
Hi,\n
|
||||
The IP <ip> has just been banned by Fail2Ban after
|
||||
<failures> attempts against <name>.\n\n
|
||||
Here are more information about <ip>:\n
|
||||
`/usr/bin/whois <ip>`\n\n
|
||||
Here is more information about <ip>:\n
|
||||
`/usr/bin/whois <ip> || echo missing whois program`\n\n
|
||||
Lines containing IP:<ip> in <logpath>\n
|
||||
`grep '\<<ip>\>' <logpath>`\n\n
|
||||
`grep '[^0-9]<ip>[^0-9]' <logpath>`\n\n
|
||||
Regards,\n
|
||||
Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@ actionban = printf %%b "Subject: [Fail2Ban] <name>: banned <ip> from `uname -n`
|
|||
Hi,\n
|
||||
The IP <ip> has just been banned by Fail2Ban after
|
||||
<failures> attempts against <name>.\n\n
|
||||
Here are more information about <ip>:\n
|
||||
`/usr/bin/whois <ip>`\n
|
||||
Here is more information about <ip>:\n
|
||||
`/usr/bin/whois <ip> || echo missing whois program`\n
|
||||
Regards,\n
|
||||
Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ __daemon_combs_re = (?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_r
|
|||
|
||||
# Some messages have a kernel prefix with a timestamp
|
||||
# EXAMPLES: kernel: [769570.846956]
|
||||
__kernel_prefix = kernel: \[\d+\.\d+\]
|
||||
__kernel_prefix = kernel: \[ *\d+\.\d+\]
|
||||
|
||||
__hostname = \S+
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Fail2Ban fitler for the Proftpd FTP daemon
|
||||
#
|
||||
# Set "UseReverseDNS off" in proftpd.conf to avoid the need for DNS.
|
||||
# See: http://www.proftpd.org/docs/howto/DNS.html
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ _daemon = fail2ban\.server\.actions
|
|||
# jail using this filter 'recidive', or change this line!
|
||||
_jailname = recidive
|
||||
|
||||
failregex = ^(%(__prefix_line)s|,\d{3} %(_daemon)s:\s+)WARNING\s+\[(?!%(_jailname)s\])(?:.*)\]\s+Ban\s+<HOST>\s*$
|
||||
failregex = ^(%(__prefix_line)s|,\d{3} fail2ban.actions%(__pid_re)s?:\s+)WARNING\s+\[(?!%(_jailname)s\])(?:.*)\]\s+Ban\s+<HOST>\s*$
|
||||
|
||||
[Init]
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# Fail2Ban filter for Squid attempted proxy bypasses
|
||||
#
|
||||
#
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^\s+\d\s<HOST>\s+[A-Z_]+_DENIED/403 .*$
|
||||
^\s+\d\s<HOST>\s+NONE/405 .*$
|
||||
|
||||
|
||||
|
||||
# Author: Daniel Black
|
||||
|
|
@ -158,6 +158,17 @@ action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(proto
|
|||
action_xarf = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||
xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath=%(logpath)s, port="%(port)s"]
|
||||
|
||||
|
||||
# Report block via blocklist.de fail2ban reporting service API
|
||||
#
|
||||
# See the IMPORTANT note in action.d/blocklist_de.conf for when to
|
||||
# use this action. Create a file jail.d/blocklist_de.local containing
|
||||
# [Init]
|
||||
# blocklist_de_apikey = {api key from registration]
|
||||
#
|
||||
action_blocklist_de = blocklist_de[email="%(sender)s", service=%(filter)s, apikey="%(blocklist_de_apikey)s"]
|
||||
|
||||
|
||||
# Choose default action. To change, just override value of 'action' with the
|
||||
# interpolation to the chosen action shortcut (e.g. action_mw, action_mwl, etc) in jail.local
|
||||
# globally (section [DEFAULT]) or per specific section
|
||||
|
@ -646,9 +657,6 @@ findtime = 86400 ; 1 day
|
|||
maxretry = 5
|
||||
|
||||
|
||||
# Generic filter for PAM. Has to be used with action which bans all
|
||||
# ports such as iptables-allports, shorewall
|
||||
|
||||
[pam-generic]
|
||||
# pam-generic filter can be customized to monitor specific subset of 'tty's
|
||||
banaction = iptables-allports
|
||||
|
|
|
@ -63,6 +63,8 @@ class Beautifier:
|
|||
msg = "Jail stopped"
|
||||
elif inC[0] == "add":
|
||||
msg = "Added jail " + response
|
||||
elif inC[0] == "flushlogs":
|
||||
msg = "logs: " + response
|
||||
elif inC[0:1] == ['status']:
|
||||
if len(inC) > 1:
|
||||
# Create IP list
|
||||
|
|
|
@ -113,6 +113,7 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
|||
# No "Definition" section or wrong basedir
|
||||
logSys.error(e)
|
||||
values[option[1]] = option[2]
|
||||
# TODO: validate error handling here.
|
||||
except NoOptionError:
|
||||
if not option[2] is None:
|
||||
logSys.warning("'%s' not defined in '%s'. Using default one: %r"
|
||||
|
|
|
@ -62,7 +62,7 @@ class JailReader(ConfigReader):
|
|||
return out
|
||||
|
||||
def isEnabled(self):
|
||||
return self.__force_enable or self.__opts["enabled"]
|
||||
return self.__force_enable or ( self.__opts and self.__opts["enabled"] )
|
||||
|
||||
@staticmethod
|
||||
def _glob(path):
|
||||
|
@ -72,12 +72,10 @@ class JailReader(ConfigReader):
|
|||
"""
|
||||
pathList = []
|
||||
for p in glob.glob(path):
|
||||
if not os.path.exists(p):
|
||||
logSys.warning("File %s doesn't even exist, thus cannot be monitored" % p)
|
||||
elif not os.path.lexists(p):
|
||||
logSys.warning("File %s is a dangling link, thus cannot be monitored" % p)
|
||||
else:
|
||||
if os.path.exists(p):
|
||||
pathList.append(p)
|
||||
else:
|
||||
logSys.warning("File %s is a dangling link, thus cannot be monitored" % p)
|
||||
return pathList
|
||||
|
||||
def getOptions(self):
|
||||
|
@ -95,20 +93,26 @@ class JailReader(ConfigReader):
|
|||
["string", "filter", ""],
|
||||
["string", "action", ""]]
|
||||
self.__opts = ConfigReader.getOptions(self, self.__name, opts)
|
||||
if not self.__opts:
|
||||
return False
|
||||
|
||||
if self.isEnabled():
|
||||
# Read filter
|
||||
filterName, filterOpt = JailReader.extractOptions(
|
||||
self.__opts["filter"])
|
||||
self.__filter = FilterReader(
|
||||
filterName, self.__name, filterOpt, basedir=self.getBaseDir())
|
||||
ret = self.__filter.read()
|
||||
if ret:
|
||||
self.__filter.getOptions(self.__opts)
|
||||
if self.__opts["filter"]:
|
||||
filterName, filterOpt = JailReader.extractOptions(
|
||||
self.__opts["filter"])
|
||||
self.__filter = FilterReader(
|
||||
filterName, self.__name, filterOpt, basedir=self.getBaseDir())
|
||||
ret = self.__filter.read()
|
||||
if ret:
|
||||
self.__filter.getOptions(self.__opts)
|
||||
else:
|
||||
logSys.error("Unable to read the filter")
|
||||
return False
|
||||
else:
|
||||
logSys.error("Unable to read the filter")
|
||||
return False
|
||||
|
||||
self.__filter = None
|
||||
logSys.warn("No filter set for jail %s" % self.__name)
|
||||
|
||||
# Read action
|
||||
for act in self.__opts["action"].split('\n'):
|
||||
try:
|
||||
|
@ -180,7 +184,8 @@ class JailReader(ConfigReader):
|
|||
# Do not send a command if the rule is empty.
|
||||
if regex != '':
|
||||
stream.append(["set", self.__name, "addignoreregex", regex])
|
||||
stream.extend(self.__filter.convert())
|
||||
if self.__filter:
|
||||
stream.extend(self.__filter.convert())
|
||||
for action in self.__actions:
|
||||
stream.extend(action.convert())
|
||||
stream.insert(0, ["add", self.__name, backend])
|
||||
|
@ -188,7 +193,11 @@ class JailReader(ConfigReader):
|
|||
|
||||
#@staticmethod
|
||||
def extractOptions(option):
|
||||
option_name, optstr = JailReader.optionCRE.match(option).groups()
|
||||
match = JailReader.optionCRE.match(option)
|
||||
if not match:
|
||||
# TODO propper error handling
|
||||
return None, None
|
||||
option_name, optstr = match.groups()
|
||||
option_opts = dict()
|
||||
if optstr:
|
||||
for optmatch in JailReader.optionExtractRE.finditer(optstr):
|
||||
|
|
|
@ -60,6 +60,7 @@ class JailsReader(ConfigReader):
|
|||
sections = [ section ]
|
||||
|
||||
# Get the options of all jails.
|
||||
parse_status = True
|
||||
for sec in sections:
|
||||
jail = JailReader(sec, basedir=self.getBaseDir(),
|
||||
force_enable=self.__force_enable)
|
||||
|
@ -71,8 +72,8 @@ class JailsReader(ConfigReader):
|
|||
self.__jails.append(jail)
|
||||
else:
|
||||
logSys.error("Errors in jail %r. Skipping..." % sec)
|
||||
return False
|
||||
return True
|
||||
parse_status = False
|
||||
return parse_status
|
||||
|
||||
def convert(self, allow_no_files=False):
|
||||
"""Convert read before __opts and jails to the commands stream
|
||||
|
|
|
@ -43,6 +43,7 @@ protocol = [
|
|||
["get loglevel", "gets the logging level"],
|
||||
["set logtarget <TARGET>", "sets logging target to <TARGET>. Can be STDOUT, STDERR, SYSLOG or a file"],
|
||||
["get logtarget", "gets logging target"],
|
||||
["flushlogs", "flushes the logtarget if a file and reopens it. For log rotation."],
|
||||
['', "DATABASE", ""],
|
||||
["set dbfile <FILE>", "set the location of fail2ban persistent datastore. Set to \"None\" to disable"],
|
||||
["get dbfile", "get the location of fail2ban persistent datastore"],
|
||||
|
|
|
@ -140,7 +140,7 @@ class DateDetector:
|
|||
date = template.getDate(line)
|
||||
if date is None:
|
||||
continue
|
||||
logSys.debug("Got time %i for \"%r\" using template %s" % (date[0], date[1].group(), template.getName()))
|
||||
logSys.debug("Got time %f for \"%r\" using template %s" % (date[0], date[1].group(), template.getName()))
|
||||
return date
|
||||
except ValueError:
|
||||
pass
|
||||
|
|
|
@ -293,6 +293,9 @@ class Filter(JailThread):
|
|||
# to enable banip fail2ban-client BAN command
|
||||
|
||||
def addBannedIP(self, ip):
|
||||
if self.inIgnoreIPList(ip):
|
||||
logSys.warning('Requested to manually ban an ignored IP %s. User knows best. Proceeding to ban it.' % ip)
|
||||
|
||||
unixTime = MyTime.time()
|
||||
for i in xrange(self.failManager.getMaxRetry()):
|
||||
self.failManager.addFailure(FailTicket(ip, unixTime))
|
||||
|
@ -561,7 +564,7 @@ class FileFilter(Filter):
|
|||
self._delLogPath(path)
|
||||
return
|
||||
|
||||
def _delLogPath(self, path):
|
||||
def _delLogPath(self, path): # pragma: no cover - overwritten function
|
||||
# nothing to do by default
|
||||
# to be overridden by backends
|
||||
pass
|
||||
|
|
|
@ -110,9 +110,11 @@ class Jail:
|
|||
self.__filter = FilterSystemd(self)
|
||||
|
||||
def setName(self, name):
|
||||
# 20 based on iptable chain name limit of 30 less len('fail2ban-')
|
||||
if len(name) >= 20:
|
||||
logSys.warning("Jail name %r might be too long and some commands "
|
||||
"might not function correctly. Please shorten"
|
||||
logSys.warning("Jail name %r might be too long and some commands"
|
||||
" (e.g. iptables) might not function correctly."
|
||||
" Please shorten"
|
||||
% name)
|
||||
self.__name = name
|
||||
|
||||
|
|
|
@ -409,13 +409,12 @@ class Server:
|
|||
try:
|
||||
self.__loggingLock.acquire()
|
||||
# set a format which is simpler for console use
|
||||
formatter = logging.Formatter("%(asctime)s %(name)-16s: %(levelname)-6s %(message)s")
|
||||
formatter = logging.Formatter("%(asctime)s %(name)-16s[%(process)d]: %(levelname)-7s %(message)s")
|
||||
if target == "SYSLOG":
|
||||
# Syslog daemons already add date to the message.
|
||||
formatter = logging.Formatter("%(name)-16s: %(levelname)-6s %(message)s")
|
||||
formatter = logging.Formatter("%(name)s[%(process)d]: %(levelname)s %(message)s")
|
||||
facility = logging.handlers.SysLogHandler.LOG_DAEMON
|
||||
hdlr = logging.handlers.SysLogHandler("/dev/log",
|
||||
facility = facility)
|
||||
hdlr = logging.handlers.SysLogHandler("/dev/log", facility=facility)
|
||||
elif target == "STDOUT":
|
||||
hdlr = logging.StreamHandler(sys.stdout)
|
||||
elif target == "STDERR":
|
||||
|
@ -424,7 +423,7 @@ class Server:
|
|||
# Target should be a file
|
||||
try:
|
||||
open(target, "a").close()
|
||||
hdlr = logging.FileHandler(target)
|
||||
hdlr = logging.handlers.RotatingFileHandler(target)
|
||||
except IOError:
|
||||
logSys.error("Unable to log to " + target)
|
||||
logSys.info("Logging to previous target " + self.__logTarget)
|
||||
|
@ -466,6 +465,19 @@ class Server:
|
|||
finally:
|
||||
self.__loggingLock.release()
|
||||
|
||||
def flushLogs(self):
|
||||
if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']:
|
||||
for handler in logging.getLogger("fail2ban").handlers:
|
||||
try:
|
||||
handler.doRollover()
|
||||
except AttributeError:
|
||||
handler.flush()
|
||||
return "rolled over"
|
||||
else:
|
||||
for handler in logging.getLogger("fail2ban").handlers:
|
||||
handler.flush()
|
||||
return "flushed"
|
||||
|
||||
def setDatabase(self, filename):
|
||||
if self.__jails.size() == 0:
|
||||
if filename.lower() == "none":
|
||||
|
@ -480,6 +492,7 @@ class Server:
|
|||
def getDatabase(self):
|
||||
return self.__db
|
||||
|
||||
|
||||
def __createDaemon(self): # pragma: no cover
|
||||
""" Detach a process from the controlling terminal and run it in the
|
||||
background as a daemon.
|
||||
|
@ -487,6 +500,14 @@ class Server:
|
|||
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
|
||||
"""
|
||||
|
||||
# When the first child terminates, all processes in the second child
|
||||
# are sent a SIGHUP, so it's ignored.
|
||||
|
||||
# We need to set this in the parent process, so it gets inherited by the
|
||||
# child process, and this makes sure that it is effect even if the parent
|
||||
# terminates quickly.
|
||||
signal.signal(signal.SIGHUP, signal.SIG_IGN)
|
||||
|
||||
try:
|
||||
# Fork a child process so the parent can exit. This will return control
|
||||
# to the command line or shell. This is required so that the new process
|
||||
|
@ -509,10 +530,6 @@ class Server:
|
|||
# leader.
|
||||
os.setsid()
|
||||
|
||||
# When the first child terminates, all processes in the second child
|
||||
# are sent a SIGHUP, so it's ignored.
|
||||
signal.signal(signal.SIGHUP, signal.SIG_IGN)
|
||||
|
||||
try:
|
||||
# Fork a second child to prevent zombies. Since the first child is
|
||||
# a session leader without a controlling terminal, it's possible for
|
||||
|
|
|
@ -92,6 +92,8 @@ class Transmitter:
|
|||
value = command[1]
|
||||
time.sleep(int(value))
|
||||
return None
|
||||
elif command[0] == "flushlogs":
|
||||
return self.__server.flushLogs()
|
||||
elif command[0] == "set":
|
||||
return self.__commandSet(command[1:])
|
||||
elif command[0] == "get":
|
||||
|
|
|
@ -24,41 +24,25 @@ __author__ = "Cyril Jaquier"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||
__license__ = "GPL"
|
||||
|
||||
import unittest, time
|
||||
import time
|
||||
import logging, sys
|
||||
from StringIO import StringIO
|
||||
|
||||
from fail2ban.server.action import Action
|
||||
|
||||
class ExecuteAction(unittest.TestCase):
|
||||
from fail2ban.tests.utils import LogCaptureTestCase
|
||||
|
||||
class ExecuteAction(LogCaptureTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
self.__action = Action("Test")
|
||||
|
||||
# For extended testing of what gets output into logging
|
||||
# system, we will redirect it to a string
|
||||
logSys = logging.getLogger("fail2ban")
|
||||
|
||||
# Keep old settings
|
||||
self._old_level = logSys.level
|
||||
self._old_handlers = logSys.handlers
|
||||
# Let's log everything into a string
|
||||
self._log = StringIO()
|
||||
logSys.handlers = [logging.StreamHandler(self._log)]
|
||||
logSys.setLevel(getattr(logging, 'DEBUG'))
|
||||
LogCaptureTestCase.setUp(self)
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
# print "O: >>%s<<" % self._log.getvalue()
|
||||
logSys = logging.getLogger("fail2ban")
|
||||
logSys.handlers = self._old_handlers
|
||||
logSys.level = self._old_level
|
||||
LogCaptureTestCase.tearDown(self)
|
||||
self.__action.execActionStop()
|
||||
|
||||
def _is_logged(self, s):
|
||||
return s in self._log.getvalue()
|
||||
|
||||
def testNameChange(self):
|
||||
self.assertEqual(self.__action.getName(), "Test")
|
||||
self.__action.setName("Tricky Test")
|
||||
|
|
|
@ -29,6 +29,7 @@ from fail2ban.client.filterreader import FilterReader
|
|||
from fail2ban.client.jailsreader import JailsReader
|
||||
from fail2ban.client.actionreader import ActionReader
|
||||
from fail2ban.client.configurator import Configurator
|
||||
from fail2ban.tests.utils import LogCaptureTestCase
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
if os.path.exists('config/fail2ban.conf'):
|
||||
|
@ -36,6 +37,9 @@ if os.path.exists('config/fail2ban.conf'):
|
|||
else:
|
||||
CONFIG_DIR='/etc/fail2ban'
|
||||
|
||||
IMPERFECT_CONFIG = os.path.join('fail2ban', 'tests','config')
|
||||
|
||||
|
||||
class ConfigReaderTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -79,7 +83,14 @@ option = %s
|
|||
self._write('d.conf', 0)
|
||||
self.assertEqual(self._getoption('d'), 0)
|
||||
os.chmod(f, 0)
|
||||
self.assertFalse(self.c.read('d')) # should not be readable BUT present
|
||||
# fragile test and known to fail e.g. under Cygwin where permissions
|
||||
# seems to be not enforced, thus condition
|
||||
if not os.access(f, os.R_OK):
|
||||
self.assertFalse(self.c.read('d')) # should not be readable BUT present
|
||||
else:
|
||||
# SkipTest introduced only in 2.7 thus can't yet use generally
|
||||
# raise unittest.SkipTest("Skipping on %s -- access rights are not enforced" % platform)
|
||||
pass
|
||||
|
||||
|
||||
def testOptionalDotDDir(self):
|
||||
|
@ -143,11 +154,37 @@ c = d ;in line comment
|
|||
self.assertEqual(self.c.get('DEFAULT', 'b'), 'a')
|
||||
self.assertEqual(self.c.get('DEFAULT', 'c'), 'd')
|
||||
|
||||
class JailReaderTest(unittest.TestCase):
|
||||
class JailReaderTest(LogCaptureTestCase):
|
||||
|
||||
def testIncorrectJail(self):
|
||||
jail = JailReader('XXXABSENTXXX', basedir=CONFIG_DIR)
|
||||
self.assertRaises(ValueError, jail.read)
|
||||
|
||||
def testJailActionEmpty(self):
|
||||
jail = JailReader('emptyaction', basedir=IMPERFECT_CONFIG)
|
||||
self.assertTrue(jail.read())
|
||||
self.assertTrue(jail.getOptions())
|
||||
self.assertTrue(jail.isEnabled())
|
||||
self.assertTrue(self._is_logged('No filter set for jail emptyaction'))
|
||||
self.assertTrue(self._is_logged('No actions were defined for emptyaction'))
|
||||
|
||||
def testJailActionFilterMissing(self):
|
||||
jail = JailReader('missingbitsjail', basedir=IMPERFECT_CONFIG)
|
||||
self.assertTrue(jail.read())
|
||||
self.assertFalse(jail.getOptions())
|
||||
self.assertTrue(jail.isEnabled())
|
||||
self.assertTrue(self._is_logged("Found no accessible config files for 'filter.d/catchallthebadies' under %s" % IMPERFECT_CONFIG))
|
||||
self.assertTrue(self._is_logged('Unable to read the filter'))
|
||||
|
||||
def TODOtestJailActionBrokenDef(self):
|
||||
jail = JailReader('brokenactiondef', basedir=IMPERFECT_CONFIG)
|
||||
self.assertTrue(jail.read())
|
||||
self.assertFalse(jail.getOptions())
|
||||
self.assertTrue(jail.isEnabled())
|
||||
self.printLog()
|
||||
self.assertTrue(self._is_logged('Error in action definition joho[foo'))
|
||||
self.assertTrue(self._is_logged('Caught exception: While reading action joho[foo we should have got 1 or 2 groups. Got: 0'))
|
||||
|
||||
|
||||
def testStockSSHJail(self):
|
||||
jail = JailReader('sshd', basedir=CONFIG_DIR) # we are running tests from root project dir atm
|
||||
|
@ -155,7 +192,9 @@ class JailReaderTest(unittest.TestCase):
|
|||
self.assertTrue(jail.getOptions())
|
||||
self.assertFalse(jail.isEnabled())
|
||||
self.assertEqual(jail.getName(), 'sshd')
|
||||
|
||||
jail.setName('ssh-funky-blocker')
|
||||
self.assertEqual(jail.getName(), 'ssh-funky-blocker')
|
||||
|
||||
def testSplitOption(self):
|
||||
# Simple example
|
||||
option = "mail-whois[name=SSH]"
|
||||
|
@ -163,6 +202,19 @@ class JailReaderTest(unittest.TestCase):
|
|||
result = JailReader.extractOptions(option)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
self.assertEqual(('mail.who_is', {}), JailReader.extractOptions("mail.who_is"))
|
||||
self.assertEqual(('mail.who_is', {'a':'cat', 'b':'dog'}), JailReader.extractOptions("mail.who_is[a=cat,b=dog]"))
|
||||
self.assertEqual(('mail--ho_is', {}), JailReader.extractOptions("mail--ho_is"))
|
||||
|
||||
self.assertEqual(('mail--ho_is', {}), JailReader.extractOptions("mail--ho_is['s']"))
|
||||
#self.printLog()
|
||||
#self.assertTrue(self._is_logged("Invalid argument ['s'] in ''s''"))
|
||||
|
||||
self.assertEqual(('mail', {'a': ','}), JailReader.extractOptions("mail[a=',']"))
|
||||
|
||||
#self.assertRaises(ValueError, JailReader.extractOptions ,'mail-how[')
|
||||
|
||||
|
||||
# Empty option
|
||||
option = "abc[]"
|
||||
expected = ('abc', {})
|
||||
|
@ -187,6 +239,27 @@ class JailReaderTest(unittest.TestCase):
|
|||
result = JailReader.extractOptions(option)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def testGlob(self):
|
||||
d = tempfile.mkdtemp(prefix="f2b-temp")
|
||||
# Generate few files
|
||||
# regular file
|
||||
f1 = os.path.join(d, 'f1')
|
||||
open(f1, 'w').close()
|
||||
# dangling link
|
||||
f2 = os.path.join(d, 'f2')
|
||||
os.symlink('nonexisting',f2)
|
||||
|
||||
# must be only f1
|
||||
self.assertEqual(JailReader._glob(os.path.join(d, '*')), [f1])
|
||||
# since f2 is dangling -- empty list
|
||||
self.assertEqual(JailReader._glob(f2), [])
|
||||
self.assertTrue(self._is_logged('File %s is a dangling link, thus cannot be monitored' % f2))
|
||||
self.assertEqual(JailReader._glob(os.path.join(d, 'nonexisting')), [])
|
||||
os.remove(f1)
|
||||
os.remove(f2)
|
||||
os.rmdir(d)
|
||||
|
||||
|
||||
class FilterReaderTest(unittest.TestCase):
|
||||
|
||||
def testConvert(self):
|
||||
|
@ -235,13 +308,74 @@ class FilterReaderTest(unittest.TestCase):
|
|||
output[-1][-1] = "5"
|
||||
self.assertEqual(sorted(filterReader.convert()), sorted(output))
|
||||
|
||||
class JailsReaderTest(unittest.TestCase):
|
||||
class JailsReaderTest(LogCaptureTestCase):
|
||||
|
||||
def testProvidingBadBasedir(self):
|
||||
if not os.path.exists('/XXX'):
|
||||
reader = JailsReader(basedir='/XXX')
|
||||
self.assertRaises(ValueError, reader.read)
|
||||
|
||||
def testReadTestJailConf(self):
|
||||
jails = JailsReader(basedir=IMPERFECT_CONFIG)
|
||||
self.assertTrue(jails.read())
|
||||
self.assertFalse(jails.getOptions())
|
||||
self.assertRaises(ValueError, jails.convert)
|
||||
comm_commands = jails.convert(allow_no_files=True)
|
||||
self.maxDiff = None
|
||||
self.assertEqual(sorted(comm_commands),
|
||||
sorted([['add', 'emptyaction', 'auto'],
|
||||
['set', 'emptyaction', 'usedns', 'warn'],
|
||||
['set', 'emptyaction', 'maxretry', 3],
|
||||
['set', 'emptyaction', 'findtime', 600],
|
||||
['set', 'emptyaction', 'logencoding', 'auto'],
|
||||
['set', 'emptyaction', 'bantime', 600],
|
||||
['add', 'special', 'auto'],
|
||||
['set', 'special', 'usedns', 'warn'],
|
||||
['set', 'special', 'maxretry', 3],
|
||||
['set', 'special', 'addfailregex', '<IP>'],
|
||||
['set', 'special', 'findtime', 600],
|
||||
['set', 'special', 'logencoding', 'auto'],
|
||||
['set', 'special', 'bantime', 600],
|
||||
['add', 'missinglogfiles', 'auto'],
|
||||
['set', 'missinglogfiles', 'usedns', 'warn'],
|
||||
['set', 'missinglogfiles', 'maxretry', 3],
|
||||
['set', 'missinglogfiles', 'findtime', 600],
|
||||
['set', 'missinglogfiles', 'logencoding', 'auto'],
|
||||
['set', 'missinglogfiles', 'bantime', 600],
|
||||
['set', 'missinglogfiles', 'addfailregex', '<IP>'],
|
||||
['add', 'brokenaction', 'auto'],
|
||||
['set', 'brokenaction', 'usedns', 'warn'],
|
||||
['set', 'brokenaction', 'maxretry', 3],
|
||||
['set', 'brokenaction', 'findtime', 600],
|
||||
['set', 'brokenaction', 'logencoding', 'auto'],
|
||||
['set', 'brokenaction', 'bantime', 600],
|
||||
['set', 'brokenaction', 'addfailregex', '<IP>'],
|
||||
['set', 'brokenaction', 'addaction', 'brokenaction'],
|
||||
['set',
|
||||
'brokenaction',
|
||||
'actionban',
|
||||
'brokenaction',
|
||||
'hit with big stick <ip>'],
|
||||
['set', 'brokenaction', 'actionstop', 'brokenaction', ''],
|
||||
['set', 'brokenaction', 'actionstart', 'brokenaction', ''],
|
||||
['set', 'brokenaction', 'actionunban', 'brokenaction', ''],
|
||||
['set', 'brokenaction', 'actioncheck', 'brokenaction', ''],
|
||||
['add', 'parse_to_end_of_jail.conf', 'auto'],
|
||||
['set', 'parse_to_end_of_jail.conf', 'usedns', 'warn'],
|
||||
['set', 'parse_to_end_of_jail.conf', 'maxretry', 3],
|
||||
['set', 'parse_to_end_of_jail.conf', 'findtime', 600],
|
||||
['set', 'parse_to_end_of_jail.conf', 'logencoding', 'auto'],
|
||||
['set', 'parse_to_end_of_jail.conf', 'bantime', 600],
|
||||
['set', 'parse_to_end_of_jail.conf', 'addfailregex', '<IP>'],
|
||||
['start', 'emptyaction'],
|
||||
['start', 'special'],
|
||||
['start', 'missinglogfiles'],
|
||||
['start', 'brokenaction'],
|
||||
['start', 'parse_to_end_of_jail.conf'],]))
|
||||
self.assertTrue(self._is_logged("Errors in jail 'missingbitsjail'. Skipping..."))
|
||||
self.assertTrue(self._is_logged("No file(s) found for glob /weapons/of/mass/destruction"))
|
||||
|
||||
|
||||
def testReadStockJailConf(self):
|
||||
jails = JailsReader(basedir=CONFIG_DIR) # we are running tests from root project dir atm
|
||||
self.assertTrue(jails.read()) # opens fine
|
||||
|
@ -251,6 +385,15 @@ class JailsReaderTest(unittest.TestCase):
|
|||
# commands to communicate to the server
|
||||
self.assertEqual(comm_commands, [])
|
||||
|
||||
# TODO: make sure this is handled well
|
||||
## We should not "read" some bogus jail
|
||||
#old_comm_commands = comm_commands[:] # make a copy
|
||||
#self.assertRaises(ValueError, jails.getOptions, "BOGUS")
|
||||
#self.printLog()
|
||||
#self.assertTrue(self._is_logged("No section: 'BOGUS'"))
|
||||
## and there should be no side-effects
|
||||
#self.assertEqual(jails.convert(), old_comm_commands)
|
||||
|
||||
allFilters = set()
|
||||
|
||||
# All jails must have filter and action set
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
[Definition]
|
||||
|
||||
actionban = hit with big stick <ip>
|
|
@ -0,0 +1,5 @@
|
|||
[Definition]
|
||||
|
||||
# 3 = INFO
|
||||
loglevel = 3
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
[Definition]
|
||||
|
||||
failregex = <IP>
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
[DEFAULT]
|
||||
filter = simple
|
||||
logpath = /non/exist
|
||||
|
||||
[emptyaction]
|
||||
enabled = true
|
||||
filter =
|
||||
action =
|
||||
|
||||
[special]
|
||||
failregex = <IP>
|
||||
ignoreregex =
|
||||
ignoreip =
|
||||
|
||||
[missinglogfiles]
|
||||
logpath = /weapons/of/mass/destruction
|
||||
|
||||
[brokenactiondef]
|
||||
enabled = true
|
||||
action = joho[foo
|
||||
|
||||
[brokenaction]
|
||||
enabled = true
|
||||
action = brokenaction
|
||||
|
||||
[missingbitsjail]
|
||||
filter = catchallthebadies
|
||||
action = thefunkychickendance
|
||||
|
||||
[parse_to_end_of_jail.conf]
|
||||
enabled = true
|
||||
action =
|
|
@ -1,5 +1,7 @@
|
|||
# failJSON: { "time": "2006-02-13T15:52:30", "match": true , "host": "1.2.3.4" }
|
||||
2006-02-13 15:52:30,388 fail2ban.server.actions: WARNING [sendmail] Ban 1.2.3.4
|
||||
# failJSON: { "time": "2006-02-13T15:52:30", "match": true , "host": "1.2.3.4", "desc": "Extended with [PID]" }
|
||||
2006-02-13 15:52:30,388 fail2ban.server.actions[123]: WARNING [sendmail] Ban 1.2.3.4
|
||||
# failJSON: { "match": false }
|
||||
2006-02-13 16:07:31,183 fail2ban.server.actions: WARNING [sendmail] Unban 1.2.3.4
|
||||
# failJSON: { "match": false }
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# Logs thanks to Roman Gelfand
|
||||
#
|
||||
# failJSON: { "time": "2013-12-08T23:55:23.000", "match": true , "host": "91.188.124.227" }
|
||||
1386543323.000 4 91.188.124.227 TCP_DENIED/403 4099 GET http://www.proxy-listen.de/azenv.php - HIER_NONE/- text/html
|
||||
|
||||
# failJSON: { "time": "2013-12-08T23:58:20", "match": true , "host": "175.44.0.184" }
|
||||
1386543500.000 5 175.44.0.184 NONE/405 3364 CONNECT error:method-not-allowed - HIER_NONE/- text/html
|
||||
|
||||
# failJSON: { "time": "2013-12-09T00:08:04.000", "match": true , "host": "198.74.125.200" }
|
||||
1386544084.000 3 198.74.125.200 TCP_DENIED/403 3722 GET http://www2t.biglobe.ne.jp/~take52/test/env.cgi - HIER_NONE/- text/html
|
||||
|
||||
# failJSON: { "time": "2013-12-09T00:09:06.000", "match": true , "host": "175.42.91.151" }
|
||||
1386544146.000 1 175.42.91.151 TCP_DENIED/403 3745 GET http://pkfsp.ru/wp-content/uploads/proxyc/engine.php - HIER_NONE/- text/html
|
|
@ -41,14 +41,11 @@ from fail2ban.server.failmanager import FailManager
|
|||
from fail2ban.server.failmanager import FailManagerEmpty
|
||||
from fail2ban.server.mytime import MyTime
|
||||
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
|
||||
from fail2ban.tests.utils import mtimesleep, LogCaptureTestCase
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
|
||||
#
|
||||
# Useful helpers
|
||||
#
|
||||
|
||||
from utils import mtimesleep
|
||||
from fail2ban.tests.dummyjail import DummyJail
|
||||
|
||||
# yoh: per Steven Hiscocks's insight while troubleshooting
|
||||
# https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836
|
||||
|
@ -192,14 +189,27 @@ def _copy_lines_to_journal(in_, fields={},n=None, skip=0, terminal_line=""): # p
|
|||
# Actual tests
|
||||
#
|
||||
|
||||
class IgnoreIP(unittest.TestCase):
|
||||
class BasicFilter(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.filter = Filter('name')
|
||||
|
||||
def testGetSetUseDNS(self):
|
||||
# default is warn
|
||||
self.assertEqual(self.filter.getUseDns(), 'warn')
|
||||
self.filter.setUseDns(True)
|
||||
self.assertEqual(self.filter.getUseDns(), 'yes')
|
||||
self.filter.setUseDns(False)
|
||||
self.assertEqual(self.filter.getUseDns(), 'no')
|
||||
|
||||
|
||||
class IgnoreIP(LogCaptureTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
self.filter = FileFilter(None)
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
LogCaptureTestCase.setUp(self)
|
||||
self.jail = DummyJail()
|
||||
self.filter = FileFilter(self.jail)
|
||||
|
||||
def testIgnoreIPOK(self):
|
||||
ipList = "127.0.0.1", "192.168.0.1", "255.255.255.255", "99.99.99.99"
|
||||
|
@ -207,19 +217,47 @@ class IgnoreIP(unittest.TestCase):
|
|||
self.filter.addIgnoreIP(ip)
|
||||
|
||||
self.assertTrue(self.filter.inIgnoreIPList(ip))
|
||||
# Test DNS
|
||||
self.filter.addIgnoreIP("www.epfl.ch")
|
||||
|
||||
self.assertTrue(self.filter.inIgnoreIPList("128.178.50.12"))
|
||||
|
||||
def testIgnoreIPNOK(self):
|
||||
ipList = "", "999.999.999.999", "abcdef", "192.168.0."
|
||||
for ip in ipList:
|
||||
self.filter.addIgnoreIP(ip)
|
||||
self.assertFalse(self.filter.inIgnoreIPList(ip))
|
||||
|
||||
def testIgnoreIPCIDR(self):
|
||||
self.filter.addIgnoreIP('192.168.1.0/25')
|
||||
self.assertTrue(self.filter.inIgnoreIPList('192.168.1.0'))
|
||||
self.assertTrue(self.filter.inIgnoreIPList('192.168.1.1'))
|
||||
self.assertTrue(self.filter.inIgnoreIPList('192.168.1.127'))
|
||||
self.assertFalse(self.filter.inIgnoreIPList('192.168.1.128'))
|
||||
self.assertFalse(self.filter.inIgnoreIPList('192.168.1.255'))
|
||||
self.assertFalse(self.filter.inIgnoreIPList('192.168.0.255'))
|
||||
|
||||
def testIgnoreInProcessLine(self):
|
||||
self.filter.addIgnoreIP('192.168.1.0/25')
|
||||
self.filter.addFailRegex('<HOST>')
|
||||
self.filter.processLineAndAdd('1387203300.222 192.168.1.32')
|
||||
self.assertTrue(self._is_logged('Ignore 192.168.1.32'))
|
||||
|
||||
def testIgnoreAddBannedIP(self):
|
||||
self.filter.addIgnoreIP('192.168.1.0/25')
|
||||
self.filter.addBannedIP('192.168.1.32')
|
||||
self.assertFalse(self._is_logged('Ignore 192.168.1.32'))
|
||||
self.assertTrue(self._is_logged('Requested to manually ban an ignored IP 192.168.1.32. User knows best. Proceeding to ban it.'))
|
||||
|
||||
|
||||
class IgnoreIPDNS(IgnoreIP):
|
||||
|
||||
def testIgnoreIPDNSOK(self):
|
||||
self.filter.addIgnoreIP("www.epfl.ch")
|
||||
self.assertTrue(self.filter.inIgnoreIPList("128.178.50.12"))
|
||||
|
||||
def testIgnoreIPDNSNOK(self):
|
||||
# Test DNS
|
||||
self.filter.addIgnoreIP("www.epfl.ch")
|
||||
self.assertFalse(self.filter.inIgnoreIPList("127.177.50.10"))
|
||||
self.assertFalse(self.filter.inIgnoreIPList("128.178.50.11"))
|
||||
self.assertFalse(self.filter.inIgnoreIPList("128.178.50.13"))
|
||||
|
||||
|
||||
class LogFile(unittest.TestCase):
|
||||
|
@ -242,12 +280,13 @@ class LogFile(unittest.TestCase):
|
|||
self.assertTrue(self.filter.isModified(LogFile.FILENAME))
|
||||
|
||||
|
||||
class LogFileMonitor(unittest.TestCase):
|
||||
class LogFileMonitor(LogCaptureTestCase):
|
||||
"""Few more tests for FilterPoll API
|
||||
"""
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
setUpMyTime()
|
||||
LogCaptureTestCase.setUp(self)
|
||||
self.filter = self.name = 'NA'
|
||||
_, self.name = tempfile.mkstemp('fail2ban', 'monitorfailures')
|
||||
self.file = open(self.name, 'a')
|
||||
|
@ -258,6 +297,7 @@ class LogFileMonitor(unittest.TestCase):
|
|||
|
||||
def tearDown(self):
|
||||
tearDownMyTime()
|
||||
LogCaptureTestCase.tearDown(self)
|
||||
_killfile(self.file, self.name)
|
||||
pass
|
||||
|
||||
|
@ -275,6 +315,21 @@ class LogFileMonitor(unittest.TestCase):
|
|||
# shorter wait time for not modified status
|
||||
return not self.isModified(0.4)
|
||||
|
||||
def testNoLogFile(self):
|
||||
os.chmod(self.name, 0)
|
||||
self.filter.getFailures(self.name)
|
||||
self.assertTrue(self._is_logged('Unable to open %s' % self.name))
|
||||
|
||||
def testRemovingFailRegex(self):
|
||||
self.filter.delFailRegex(0)
|
||||
self.assertFalse(self._is_logged('Cannot remove regular expression. Index 0 is not valid'))
|
||||
self.filter.delFailRegex(0)
|
||||
self.assertTrue(self._is_logged('Cannot remove regular expression. Index 0 is not valid'))
|
||||
|
||||
def testRemovingIgnoreRegex(self):
|
||||
self.filter.delIgnoreRegex(0)
|
||||
self.assertTrue(self._is_logged('Cannot remove regular expression. Index 0 is not valid'))
|
||||
|
||||
def testNewChangeViaIsModified(self):
|
||||
# it is a brand new one -- so first we think it is modified
|
||||
self.assertTrue(self.isModified())
|
||||
|
@ -357,7 +412,6 @@ class LogFileMonitor(unittest.TestCase):
|
|||
|
||||
|
||||
from threading import Lock
|
||||
from dummyjail import DummyJail
|
||||
|
||||
def get_monitor_failures_testcase(Filter_):
|
||||
"""Generator of TestCase's for different filters/backends
|
||||
|
@ -726,7 +780,13 @@ class GetFailures(unittest.TestCase):
|
|||
"""Call after every test case."""
|
||||
tearDownMyTime()
|
||||
|
||||
|
||||
def testTail(self):
|
||||
self.filter.addLogPath(LogFile.FILENAME, tail=True)
|
||||
self.assertEqual(self.filter.getLogPath()[-1].getPos(), 1653)
|
||||
self.filter.getLogPath()[-1].close()
|
||||
self.assertEqual(self.filter.getLogPath()[-1].readline(), "")
|
||||
self.filter.delLogPath(LogFile.FILENAME)
|
||||
self.assertEqual(self.filter.getLogPath(),[])
|
||||
|
||||
def testGetFailures01(self, filename=None, failures=None):
|
||||
filename = filename or GetFailures.FILENAME_01
|
||||
|
|
|
@ -141,7 +141,6 @@ def testSampleRegexsFactory(name):
|
|||
|
||||
regexsUsed.add(failregex)
|
||||
|
||||
# TODO: Remove exception handling once all regexs have samples
|
||||
for failRegexIndex, failRegex in enumerate(self.filter.getFailRegex()):
|
||||
self.assertTrue(
|
||||
failRegexIndex in regexsUsed,
|
||||
|
|
|
@ -26,7 +26,7 @@ __license__ = "GPL"
|
|||
|
||||
import unittest, socket, time, tempfile, os, locale, sys
|
||||
|
||||
from fail2ban.server.server import Server
|
||||
from fail2ban.server.server import Server, logSys
|
||||
from fail2ban.server.jail import Jail
|
||||
from fail2ban.exceptions import UnknownJailException
|
||||
try:
|
||||
|
@ -662,6 +662,38 @@ class TransmitterLogging(TransmitterBase):
|
|||
self.setGetTest("loglevel", "0", 0)
|
||||
self.setGetTestNOK("loglevel", "Bird")
|
||||
|
||||
def testFlushLogs(self):
|
||||
self.assertEqual(self.transm.proceed(["flushlogs"]), (0, "rolled over"))
|
||||
try:
|
||||
f, fn = tempfile.mkstemp("fail2ban.log")
|
||||
os.close(f)
|
||||
self.server.setLogLevel(2)
|
||||
self.assertEqual(self.transm.proceed(["set", "logtarget", fn]), (0, fn))
|
||||
logSys.warn("Before file moved")
|
||||
try:
|
||||
f2, fn2 = tempfile.mkstemp("fail2ban.log")
|
||||
os.close(f2)
|
||||
os.rename(fn, fn2)
|
||||
logSys.warn("After file moved")
|
||||
self.assertEqual(self.transm.proceed(["flushlogs"]), (0, "rolled over"))
|
||||
logSys.warn("After flushlogs")
|
||||
with open(fn2,'r') as f:
|
||||
self.assertTrue(f.next().endswith("Before file moved\n"))
|
||||
self.assertTrue(f.next().endswith("After file moved\n"))
|
||||
self.assertRaises(StopIteration, f.next)
|
||||
with open(fn,'r') as f:
|
||||
self.assertTrue(f.next().endswith("After flushlogs\n"))
|
||||
self.assertRaises(StopIteration, f.next)
|
||||
finally:
|
||||
os.remove(fn2)
|
||||
finally:
|
||||
try:
|
||||
os.remove(fn)
|
||||
except OSError:
|
||||
pass
|
||||
self.assertEqual(self.transm.proceed(["set", "logtarget", "STDERR"]), (0, "STDERR"))
|
||||
self.assertEqual(self.transm.proceed(["flushlogs"]), (0, "flushed"))
|
||||
|
||||
|
||||
class JailTests(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ __license__ = "GPL"
|
|||
|
||||
import logging, os, re, traceback, time, unittest, sys
|
||||
from os.path import basename, dirname
|
||||
from StringIO import StringIO
|
||||
|
||||
if sys.version_info >= (2, 6):
|
||||
import json
|
||||
|
@ -244,3 +245,32 @@ def gatherTests(regexps=None, no_network=False):
|
|||
tests.addTest(unittest.makeSuite(servertestcase.TransmitterLogging))
|
||||
|
||||
return tests
|
||||
|
||||
class LogCaptureTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
# For extended testing of what gets output into logging
|
||||
# system, we will redirect it to a string
|
||||
logSys = logging.getLogger("fail2ban")
|
||||
|
||||
# Keep old settings
|
||||
self._old_level = logSys.level
|
||||
self._old_handlers = logSys.handlers
|
||||
# Let's log everything into a string
|
||||
self._log = StringIO()
|
||||
logSys.handlers = [logging.StreamHandler(self._log)]
|
||||
logSys.setLevel(getattr(logging, 'DEBUG'))
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
# print "O: >>%s<<" % self._log.getvalue()
|
||||
logSys = logging.getLogger("fail2ban")
|
||||
logSys.handlers = self._old_handlers
|
||||
logSys.level = self._old_level
|
||||
|
||||
def _is_logged(self, s):
|
||||
return s in self._log.getvalue()
|
||||
|
||||
def printLog(self):
|
||||
print(self._log.getvalue())
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
missingok
|
||||
compress
|
||||
postrotate
|
||||
/usr/bin/fail2ban-client set logtarget /var/log/fail2ban.log 1>/dev/null || true
|
||||
/usr/bin/fail2ban-client flushlogs 1>/dev/null || true
|
||||
endscript
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# chkconfig: 345 92 08
|
||||
# chkconfig: - 92 08
|
||||
# processname: fail2ban-server
|
||||
# config: /etc/fail2ban/fail2ban.conf
|
||||
# pidfile: /var/run/fail2ban/fail2ban.pid
|
||||
|
|
|
@ -64,6 +64,12 @@ Comments: use '#' for comment lines and ';' (following a space) for inline comme
|
|||
.SH DEFAULT
|
||||
The following options are applicable to all jails. Their meaning is described in the default \fIjail.conf\fR file.
|
||||
.TP
|
||||
\fBfilter\fR
|
||||
.TP
|
||||
\fBlogpath\fR
|
||||
.TP
|
||||
\fBaction\fR
|
||||
.TP
|
||||
\fBignoreip\fR
|
||||
.TP
|
||||
\fBbantime\fR
|
||||
|
@ -75,6 +81,11 @@ The following options are applicable to all jails. Their meaning is described in
|
|||
\fBbackend\fR
|
||||
.TP
|
||||
\fBusedns\fR
|
||||
.TP
|
||||
\fBfailregex\fR
|
||||
.TP
|
||||
\fBignoreregex\fR
|
||||
|
||||
.PP
|
||||
.SS Backends
|
||||
\fBbackend\fR specifies the backend used to get files modification. This option can be overridden in each jail as well.
|
||||
|
|
Loading…
Reference in New Issue