mirror of https://github.com/fail2ban/fail2ban
MRG: from 0.9
commit
2333b2d5d9
28
ChangeLog
28
ChangeLog
|
@ -55,7 +55,11 @@ configuration before relying on it.
|
||||||
* Added action xarf-login-attack to report formatted attack messages
|
* Added action xarf-login-attack to report formatted attack messages
|
||||||
according to the XARF standard (v0.2). Close gh-105
|
according to the XARF standard (v0.2). Close gh-105
|
||||||
* Support PyPy
|
* Support PyPy
|
||||||
|
* Add filter for apache-botsearch
|
||||||
* Filter for stunnel
|
* Filter for stunnel
|
||||||
|
* Filter for Counter Strike 1.6. Thanks to onorua for logs.
|
||||||
|
Close gh-347
|
||||||
|
* Filter for squirrelmail. Close gh-261
|
||||||
|
|
||||||
- Enhancements
|
- Enhancements
|
||||||
* Jail names increased to 26 characters and iptables prefix reduced
|
* Jail names increased to 26 characters and iptables prefix reduced
|
||||||
|
@ -64,9 +68,11 @@ configuration before relying on it.
|
||||||
* Multiline regex for Disconnecting: Too many authentication failures for
|
* Multiline regex for Disconnecting: Too many authentication failures for
|
||||||
root [preauth]\nConnection closed by 6X.XXX.XXX.XXX [preauth]
|
root [preauth]\nConnection closed by 6X.XXX.XXX.XXX [preauth]
|
||||||
* Replacing use of deprecated API (.warning, .assertEqual, etc)
|
* Replacing use of deprecated API (.warning, .assertEqual, etc)
|
||||||
* [..a648cc2] Filters can have options now too
|
* [..a648cc2] Filters can have options now too which are substituted into
|
||||||
|
failregex / ignoreregex
|
||||||
* [..e019ab7] Multiple instances of the same action are allowed in the
|
* [..e019ab7] Multiple instances of the same action are allowed in the
|
||||||
same jail -- use actname option to disambiguate.
|
same jail -- use actname option to disambiguate.
|
||||||
|
* Add honeypot email address to exim-spam filter as argument
|
||||||
|
|
||||||
ver. 0.8.12 (2013/12/XX) - things-can-only-get-better
|
ver. 0.8.12 (2013/12/XX) - things-can-only-get-better
|
||||||
-----------
|
-----------
|
||||||
|
@ -92,6 +98,8 @@ ver. 0.8.12 (2013/12/XX) - things-can-only-get-better
|
||||||
Closes gh-516
|
Closes gh-516
|
||||||
- Asynchat changed to use push method which verifys whether all data was
|
- Asynchat changed to use push method which verifys whether all data was
|
||||||
send. This ensures that all data is sent before closing the connection.
|
send. This ensures that all data is sent before closing the connection.
|
||||||
|
- Removed unnecessary reference to as yet undeclared $jail_name when checking
|
||||||
|
a specific jail in nagios script.
|
||||||
|
|
||||||
- Enhancements:
|
- Enhancements:
|
||||||
- added firewallcmd-ipset action
|
- added firewallcmd-ipset action
|
||||||
|
@ -107,15 +115,27 @@ ver. 0.8.12 (2013/12/XX) - things-can-only-get-better
|
||||||
- exim-spam filter to match spamassassin log entry for option SAdevnull.
|
- exim-spam filter to match spamassassin log entry for option SAdevnull.
|
||||||
Thanks Ivo Truxa. Closes gh-533
|
Thanks Ivo Truxa. Closes gh-533
|
||||||
- filter.d/nsd.conf -- also amended Unix date template to match nsd format
|
- filter.d/nsd.conf -- also amended Unix date template to match nsd format
|
||||||
|
- Added to sshd filter expression for "Received disconnect from <HOST>: 3:
|
||||||
|
...: Auth fail". Thanks Marcel Dopita. Closes gh-289
|
||||||
- loglines now also report "[PID]" after the name portion
|
- loglines now also report "[PID]" after the name portion
|
||||||
|
- Added filter.d/ejabberd-auth
|
||||||
|
- Improved ACL-handling for Asterisk
|
||||||
|
- loglines now also report "[PID]" after the name portion
|
||||||
|
- Added improper command pipelining to postfix filter.
|
||||||
|
|
||||||
- New Features:
|
- New Features:
|
||||||
|
|
||||||
- Added filter for solid-pop3d -- thanks to Jacques Lav!gnotte on mailinglist.
|
- filter.d/solid-pop3d -- added thanks to Jacques Lav!gnotte on mailinglist.
|
||||||
- Added filter for apache-modsecurity
|
- Add filter for apache-modsecurity
|
||||||
- Added filter for openwebmail thanks Ivo Truxa. Closes gh-543
|
- filter.d/nsd.conf -- also amended Unix date template to match nsd format
|
||||||
|
- Added openwebmail filter thanks Ivo Truxa. Closes gh-543
|
||||||
|
- Added filter for freeswitch. Thanks Jim and editors and authors of
|
||||||
|
http://wiki.freeswitch.org/wiki/Fail2ban
|
||||||
|
- Added groupoffice filter thanks to logs from Merijn Schering.
|
||||||
|
Closes gh-566
|
||||||
- Added filter for horde
|
- Added filter for horde
|
||||||
|
|
||||||
|
|
||||||
ver. 0.8.11 (2013/11/13) - loves-unittests-and-tight-DoS-free-filter-regexes
|
ver. 0.8.11 (2013/11/13) - loves-unittests-and-tight-DoS-free-filter-regexes
|
||||||
|
|
||||||
In light of CVE-2013-2178 that triggered our last release we have put
|
In light of CVE-2013-2178 that triggered our last release we have put
|
||||||
|
|
9
MANIFEST
9
MANIFEST
|
@ -86,6 +86,7 @@ fail2ban/tests/files/config/apache-auth/README
|
||||||
fail2ban/tests/files/config/apache-auth/noentry/.htaccess
|
fail2ban/tests/files/config/apache-auth/noentry/.htaccess
|
||||||
fail2ban/tests/files/database_v1.db
|
fail2ban/tests/files/database_v1.db
|
||||||
fail2ban/tests/files/ignorecommand.py
|
fail2ban/tests/files/ignorecommand.py
|
||||||
|
fail2ban/tests/files/filter.d/substition.conf
|
||||||
fail2ban/tests/files/filter.d/testcase-common.conf
|
fail2ban/tests/files/filter.d/testcase-common.conf
|
||||||
fail2ban/tests/files/filter.d/testcase01.conf
|
fail2ban/tests/files/filter.d/testcase01.conf
|
||||||
fail2ban/tests/files/testcase01.log
|
fail2ban/tests/files/testcase01.log
|
||||||
|
@ -101,12 +102,14 @@ fail2ban/tests/files/logs/bsd/syslog-vv.txt
|
||||||
fail2ban/tests/files/logs/3proxy
|
fail2ban/tests/files/logs/3proxy
|
||||||
fail2ban/tests/files/logs/apache-auth
|
fail2ban/tests/files/logs/apache-auth
|
||||||
fail2ban/tests/files/logs/apache-badbots
|
fail2ban/tests/files/logs/apache-badbots
|
||||||
|
fail2ban/tests/files/logs/apache-botscripts
|
||||||
fail2ban/tests/files/logs/apache-modsecurity
|
fail2ban/tests/files/logs/apache-modsecurity
|
||||||
fail2ban/tests/files/logs/apache-nohome
|
fail2ban/tests/files/logs/apache-nohome
|
||||||
fail2ban/tests/files/logs/apache-noscript
|
fail2ban/tests/files/logs/apache-noscript
|
||||||
fail2ban/tests/files/logs/apache-overflows
|
fail2ban/tests/files/logs/apache-overflows
|
||||||
fail2ban/tests/files/logs/assp
|
fail2ban/tests/files/logs/assp
|
||||||
fail2ban/tests/files/logs/asterisk
|
fail2ban/tests/files/logs/asterisk
|
||||||
|
fail2ban/tests/files/logs/counter-strike
|
||||||
fail2ban/tests/files/logs/courier-auth
|
fail2ban/tests/files/logs/courier-auth
|
||||||
fail2ban/tests/files/logs/courier-smtp
|
fail2ban/tests/files/logs/courier-smtp
|
||||||
fail2ban/tests/files/logs/cyrus-imap
|
fail2ban/tests/files/logs/cyrus-imap
|
||||||
|
@ -115,6 +118,8 @@ fail2ban/tests/files/logs/dropbear
|
||||||
fail2ban/tests/files/logs/ejabberd-auth
|
fail2ban/tests/files/logs/ejabberd-auth
|
||||||
fail2ban/tests/files/logs/exim
|
fail2ban/tests/files/logs/exim
|
||||||
fail2ban/tests/files/logs/exim-spam
|
fail2ban/tests/files/logs/exim-spam
|
||||||
|
fail2ban/tests/files/logs/freeswitch
|
||||||
|
fail2ban/tests/files/logs/groupoffice
|
||||||
fail2ban/tests/files/logs/gssftpd
|
fail2ban/tests/files/logs/gssftpd
|
||||||
fail2ban/tests/files/logs/guacamole
|
fail2ban/tests/files/logs/guacamole
|
||||||
fail2ban/tests/files/logs/kerio
|
fail2ban/tests/files/logs/kerio
|
||||||
|
@ -167,10 +172,12 @@ config/fail2ban.conf
|
||||||
config/filter.d/common.conf
|
config/filter.d/common.conf
|
||||||
config/filter.d/apache-auth.conf
|
config/filter.d/apache-auth.conf
|
||||||
config/filter.d/apache-badbots.conf
|
config/filter.d/apache-badbots.conf
|
||||||
|
config/filter.d/apache-botsearch.conf
|
||||||
config/filter.d/apache-nohome.conf
|
config/filter.d/apache-nohome.conf
|
||||||
config/filter.d/apache-noscript.conf
|
config/filter.d/apache-noscript.conf
|
||||||
config/filter.d/apache-overflows.conf
|
config/filter.d/apache-overflows.conf
|
||||||
config/filter.d/nginx-http-auth.conf
|
config/filter.d/nginx-http-auth.conf
|
||||||
|
config/filter.d/counter-strike.conf
|
||||||
config/filter.d/courier-auth.conf
|
config/filter.d/courier-auth.conf
|
||||||
config/filter.d/courier-smtp.conf
|
config/filter.d/courier-smtp.conf
|
||||||
config/filter.d/cyrus-imap.conf
|
config/filter.d/cyrus-imap.conf
|
||||||
|
@ -214,6 +221,8 @@ config/filter.d/3proxy.conf
|
||||||
config/filter.d/apache-common.conf
|
config/filter.d/apache-common.conf
|
||||||
config/filter.d/exim-common.conf
|
config/filter.d/exim-common.conf
|
||||||
config/filter.d/exim-spam.conf
|
config/filter.d/exim-spam.conf
|
||||||
|
config/filter.d/freeswitch.conf
|
||||||
|
config/filter.d/groupoffice.conf
|
||||||
config/filter.d/perdition.conf
|
config/filter.d/perdition.conf
|
||||||
config/filter.d/uwimap-auth.conf
|
config/filter.d/uwimap-auth.conf
|
||||||
config/filter.d/courier-auth.conf
|
config/filter.d/courier-auth.conf
|
||||||
|
|
3
THANKS
3
THANKS
|
@ -62,9 +62,11 @@ Markus Hoffmann
|
||||||
Marvin Rouge
|
Marvin Rouge
|
||||||
mEDI
|
mEDI
|
||||||
Мернов Георгий
|
Мернов Георгий
|
||||||
|
Merijn Schering
|
||||||
Michael C. Haller
|
Michael C. Haller
|
||||||
Michael Hanselmann
|
Michael Hanselmann
|
||||||
Nick Munger
|
Nick Munger
|
||||||
|
onorua
|
||||||
Patrick Börjesson
|
Patrick Börjesson
|
||||||
Raphaël Marichez
|
Raphaël Marichez
|
||||||
RealRancor
|
RealRancor
|
||||||
|
@ -81,6 +83,7 @@ Stephen Gildea
|
||||||
Steven Hiscocks
|
Steven Hiscocks
|
||||||
TESTOVIK
|
TESTOVIK
|
||||||
Tom Pike
|
Tom Pike
|
||||||
|
Tomas Pihl
|
||||||
Tyler
|
Tyler
|
||||||
Vaclav Misek
|
Vaclav Misek
|
||||||
Vincent Deffontaines
|
Vincent Deffontaines
|
||||||
|
|
|
@ -41,7 +41,7 @@ except ImportError:
|
||||||
journal = None
|
journal = None
|
||||||
|
|
||||||
from fail2ban.version import version
|
from fail2ban.version import version
|
||||||
from fail2ban.client.configparserinc import SafeConfigParserWithIncludes
|
from fail2ban.client.filterreader import FilterReader
|
||||||
from fail2ban.server.filter import Filter
|
from fail2ban.server.filter import Filter
|
||||||
from fail2ban.server.failregex import RegexException
|
from fail2ban.server.failregex import RegexException
|
||||||
|
|
||||||
|
@ -206,8 +206,6 @@ class LineStats(object):
|
||||||
|
|
||||||
class Fail2banRegex(object):
|
class Fail2banRegex(object):
|
||||||
|
|
||||||
CONFIG_DEFAULTS = {'configpath' : "/etc/fail2ban/"}
|
|
||||||
|
|
||||||
def __init__(self, opts):
|
def __init__(self, opts):
|
||||||
self._verbose = opts.verbose
|
self._verbose = opts.verbose
|
||||||
self._debuggex = opts.debuggex
|
self._debuggex = opts.debuggex
|
||||||
|
@ -257,44 +255,34 @@ class Fail2banRegex(object):
|
||||||
assert(regextype in ('fail', 'ignore'))
|
assert(regextype in ('fail', 'ignore'))
|
||||||
regex = regextype + 'regex'
|
regex = regextype + 'regex'
|
||||||
if os.path.isfile(value):
|
if os.path.isfile(value):
|
||||||
reader = SafeConfigParserWithIncludes(defaults=self.CONFIG_DEFAULTS)
|
|
||||||
try:
|
|
||||||
reader.read(value)
|
|
||||||
print "Use %11s file : %s" % (regex, value)
|
print "Use %11s file : %s" % (regex, value)
|
||||||
# TODO: reuse functionality in client
|
reader = FilterReader(value, 'fail2ban-regex-jail', {})
|
||||||
regex_values = [
|
reader.setBaseDir(None)
|
||||||
RegexStat(m)
|
|
||||||
for m in reader.get("Definition", regex).split('\n')
|
|
||||||
if m != ""]
|
|
||||||
except NoSectionError:
|
|
||||||
print "No [Definition] section in %s" % value
|
|
||||||
return False
|
|
||||||
except NoOptionError:
|
|
||||||
print "No %s option in %s" % (regex, value)
|
|
||||||
return False
|
|
||||||
except MissingSectionHeaderError:
|
|
||||||
print "No section headers in %s" % value
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
if reader.readexplicit():
|
||||||
|
reader.getOptions(None)
|
||||||
|
readercommands = reader.convert()
|
||||||
|
regex_values = [
|
||||||
|
RegexStat(m[3])
|
||||||
|
for m in filter(
|
||||||
|
lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype,
|
||||||
|
readercommands)]
|
||||||
# Read out and set possible value of maxlines
|
# Read out and set possible value of maxlines
|
||||||
try:
|
for command in readercommands:
|
||||||
maxlines = reader.get("Init", "maxlines")
|
if command[2] == "maxlines":
|
||||||
except (NoSectionError, NoOptionError):
|
maxlines = int(command[3])
|
||||||
# No [Init].maxlines found.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
self.setMaxLines(maxlines)
|
self.setMaxLines(maxlines)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print "ERROR: Invalid value for maxlines (%(maxlines)r) " \
|
print "ERROR: Invalid value for maxlines (%(maxlines)r) " \
|
||||||
"read from %(value)s" % locals()
|
"read from %(value)s" % locals()
|
||||||
return False
|
return False
|
||||||
# Read out and set possible value for journalmatch
|
elif command[2] == 'addjournalmatch':
|
||||||
try:
|
journalmatch = command[3]
|
||||||
journalmatch = reader.get("Init", "journalmatch")
|
self.setJournalMatch(shlex.split(journalmatch))
|
||||||
except (NoSectionError, NoOptionError):
|
elif command[2] == 'datepattern':
|
||||||
# No [Init].journalmatch found.
|
datepattern = command[3]
|
||||||
pass
|
self.setDatePattern(datepattern)
|
||||||
else:
|
else:
|
||||||
self.setJournalMatch(shlex.split(journalmatch))
|
self.setJournalMatch(shlex.split(journalmatch))
|
||||||
# Read out and set possible value for journalmatch
|
# Read out and set possible value for journalmatch
|
||||||
|
|
|
@ -1,45 +1,17 @@
|
||||||
# Fail2Ban configuration file
|
# Fail2Ban configuration file
|
||||||
|
# https://www.rfxn.com/projects/advanced-policy-firewall/
|
||||||
#
|
#
|
||||||
# Author: Mark McKinstry
|
# Note: APF doesn't play nicely with other actions. It has been observed to
|
||||||
|
# remove bans created by other iptables based actions. If you are going to use
|
||||||
|
# this action, use it for all of your jails.
|
||||||
#
|
#
|
||||||
|
# DON'T MIX APF and other IPTABLES based actions
|
||||||
[Definition]
|
[Definition]
|
||||||
|
|
||||||
# Option: actionstart
|
|
||||||
# Notes.: command executed once at the start of Fail2Ban.
|
|
||||||
# Values: CMD
|
|
||||||
#
|
|
||||||
actionstart =
|
actionstart =
|
||||||
|
|
||||||
# Option: actionstop
|
|
||||||
# Notes.: command executed once at the end of Fail2Ban
|
|
||||||
# Values: CMD
|
|
||||||
#
|
|
||||||
actionstop =
|
actionstop =
|
||||||
|
|
||||||
# Option: actioncheck
|
|
||||||
# Notes.: command executed once before each actionban command
|
|
||||||
# Values: CMD
|
|
||||||
#
|
|
||||||
actioncheck =
|
actioncheck =
|
||||||
|
|
||||||
# Option: actionban
|
|
||||||
# Notes.: command executed when banning an IP. Take care that the
|
|
||||||
# command is executed with Fail2Ban user rights.
|
|
||||||
# Tags: <ip> IP address
|
|
||||||
# <failures> number of failures
|
|
||||||
# <time> unix timestamp of the ban time
|
|
||||||
# Values: CMD
|
|
||||||
#
|
|
||||||
actionban = apf --deny <ip> "banned by Fail2Ban <name>"
|
actionban = apf --deny <ip> "banned by Fail2Ban <name>"
|
||||||
|
|
||||||
# Option: actionunban
|
|
||||||
# Notes.: command executed when unbanning an IP. Take care that the
|
|
||||||
# command is executed with Fail2Ban user rights.
|
|
||||||
# Tags: <ip> IP address
|
|
||||||
# <failures> number of failures
|
|
||||||
# <time> unix timestamp of the ban time
|
|
||||||
# Values: CMD
|
|
||||||
#
|
|
||||||
actionunban = apf --remove <ip>
|
actionunban = apf --remove <ip>
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
|
@ -48,3 +20,6 @@ actionunban = apf --remove <ip>
|
||||||
#
|
#
|
||||||
name = default
|
name = default
|
||||||
|
|
||||||
|
# DEV NOTES:
|
||||||
|
#
|
||||||
|
# Author: Mark McKinstry
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#
|
#
|
||||||
# Author: Cyril Jaquier
|
# Author: Cyril Jaquier
|
||||||
# Copied from iptables.conf and modified by Yaroslav Halchenko
|
# Copied from iptables.conf and modified by Yaroslav Halchenko
|
||||||
# to fullfill the needs of bugreporter dbts#350746.
|
# to fulfill the needs of bugreporter dbts#350746.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ blocktype = unreach port
|
||||||
setnum = 10
|
setnum = 10
|
||||||
|
|
||||||
# Option: number for ipfw rule
|
# Option: number for ipfw rule
|
||||||
# Notes: This is meant to be automaticly generated and not overwritten
|
# Notes: This is meant to be automatically generated and not overwritten
|
||||||
# Values: Random value between 10000 and 12000
|
# Values: Random value between 10000 and 12000
|
||||||
rulenum="`echo $((RANDOM%%2000+10000))`"
|
rulenum="`echo $((RANDOM%%2000+10000))`"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,225 @@
|
||||||
|
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
|
||||||
|
# vi: set ft=python sts=4 ts=4 sw=4 noet :
|
||||||
|
|
||||||
|
# This file is part of Fail2Ban.
|
||||||
|
#
|
||||||
|
# Fail2Ban is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Fail2Ban is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Fail2Ban; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
import smtplib
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.utils import formatdate, formataddr
|
||||||
|
|
||||||
|
from fail2ban.server.actions import ActionBase, CallingMap
|
||||||
|
|
||||||
|
messages = {}
|
||||||
|
messages['start'] = \
|
||||||
|
"""Hi,
|
||||||
|
|
||||||
|
The jail %(jailname)s has been started successfully.
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
Fail2Ban"""
|
||||||
|
|
||||||
|
messages['stop'] = \
|
||||||
|
"""Hi,
|
||||||
|
|
||||||
|
The jail %(jailname)s has been stopped.
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
Fail2Ban"""
|
||||||
|
|
||||||
|
messages['ban'] = {}
|
||||||
|
messages['ban']['head'] = \
|
||||||
|
"""Hi,
|
||||||
|
|
||||||
|
The IP %(ip)s has just been banned for %(bantime)s seconds
|
||||||
|
by Fail2Ban after %(failures)i attempts against %(jailname)s.
|
||||||
|
"""
|
||||||
|
messages['ban']['tail'] = \
|
||||||
|
"""
|
||||||
|
Regards,
|
||||||
|
Fail2Ban"""
|
||||||
|
messages['ban']['matches'] = \
|
||||||
|
"""
|
||||||
|
Matches for this ban:
|
||||||
|
%(matches)s
|
||||||
|
"""
|
||||||
|
messages['ban']['ipmatches'] = \
|
||||||
|
"""
|
||||||
|
Matches for %(ip)s:
|
||||||
|
%(ipmatches)s
|
||||||
|
"""
|
||||||
|
messages['ban']['ipjailmatches'] = \
|
||||||
|
"""
|
||||||
|
Matches for %(ip)s for jail %(jailname)s:
|
||||||
|
%(ipjailmatches)s
|
||||||
|
"""
|
||||||
|
|
||||||
|
class SMTPAction(ActionBase):
|
||||||
|
"""Fail2Ban action which sends emails to inform on jail starting,
|
||||||
|
stopping and bans.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, jail, name, host="localhost", user=None, password=None,
|
||||||
|
sendername="Fail2Ban", sender="fail2ban", dest="root", matches=None):
|
||||||
|
"""Initialise action.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
jail : Jail
|
||||||
|
The jail which the action belongs to.
|
||||||
|
name : str
|
||||||
|
Named assigned to the action.
|
||||||
|
host : str, optional
|
||||||
|
SMTP host, of host:port format. Default host "localhost" and
|
||||||
|
port "25"
|
||||||
|
user : str, optional
|
||||||
|
Username used for authentication with SMTP server.
|
||||||
|
password : str, optional
|
||||||
|
Password used for authentication with SMTP server.
|
||||||
|
sendername : str, optional
|
||||||
|
Name to use for from address in email. Default "Fail2Ban".
|
||||||
|
sender : str, optional
|
||||||
|
Email address to use for from address in email.
|
||||||
|
Default "fail2ban".
|
||||||
|
dest : str, optional
|
||||||
|
Email addresses of intended recipient(s) in comma delimited
|
||||||
|
format. Default "root".
|
||||||
|
matches : str, optional
|
||||||
|
Type of matches to be included from ban in email. Can be one
|
||||||
|
of "matches", "ipmatches" or "ipjailmatches". Default None
|
||||||
|
(see man jail.conf.5).
|
||||||
|
"""
|
||||||
|
|
||||||
|
super(SMTPAction, self).__init__(jail, name)
|
||||||
|
|
||||||
|
self.host = host
|
||||||
|
#TODO: self.ssl = ssl
|
||||||
|
|
||||||
|
self.user = user
|
||||||
|
self.password =password
|
||||||
|
|
||||||
|
self.fromname = sendername
|
||||||
|
self.fromaddr = sender
|
||||||
|
self.toaddr = dest
|
||||||
|
|
||||||
|
self.matches = matches
|
||||||
|
|
||||||
|
self.message_values = CallingMap(
|
||||||
|
jailname = self._jail.getName(), # Doesn't change
|
||||||
|
hostname = socket.gethostname,
|
||||||
|
bantime = self._jail.actions.getBanTime,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _sendMessage(self, subject, text):
|
||||||
|
"""Sends message based on arguments and instance's properties.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
subject : str
|
||||||
|
Subject of the email.
|
||||||
|
text : str
|
||||||
|
Body of the email.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
SMTPConnectionError
|
||||||
|
Error on connecting to host.
|
||||||
|
SMTPAuthenticationError
|
||||||
|
Error authenticating with SMTP server.
|
||||||
|
SMTPException
|
||||||
|
See Python `smtplib` for full list of other possible
|
||||||
|
exceptions.
|
||||||
|
"""
|
||||||
|
msg = MIMEText(text)
|
||||||
|
msg['Subject'] = subject
|
||||||
|
msg['From'] = formataddr((self.fromname, self.fromaddr))
|
||||||
|
msg['To'] = self.toaddr
|
||||||
|
msg['Date'] = formatdate()
|
||||||
|
|
||||||
|
smtp = smtplib.SMTP()
|
||||||
|
try:
|
||||||
|
self._logSys.debug("Connected to SMTP '%s', response: %i: %s",
|
||||||
|
self.host, *smtp.connect(self.host))
|
||||||
|
if self.user and self.password:
|
||||||
|
smtp.login(self.user, self.password)
|
||||||
|
failed_recipients = smtp.sendmail(
|
||||||
|
self.fromaddr, self.toaddr, msg.as_string())
|
||||||
|
except smtplib.SMTPConnectError:
|
||||||
|
self._logSys.error("Error connecting to host '%s'", self.host)
|
||||||
|
raise
|
||||||
|
except smtplib.SMTPAuthenticationError:
|
||||||
|
self._logSys.error(
|
||||||
|
"Failed to authenticate with host '%s' user '%s'",
|
||||||
|
self.host, self.user)
|
||||||
|
raise
|
||||||
|
except smtplib.SMTPException:
|
||||||
|
self._logSys.error(
|
||||||
|
"Error sending mail to host '%s' from '%s' to '%s'",
|
||||||
|
self.host, self.fromaddr, self.toaddr)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
if failed_recipients:
|
||||||
|
self._logSys.warning(
|
||||||
|
"Email to '%s' failed to following recipients: %r",
|
||||||
|
self.toaddr, failed_recipients)
|
||||||
|
self._logSys.debug("Email '%s' successfully sent", subject)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
self._logSys.debug("Disconnected from '%s', response %i: %s",
|
||||||
|
self.host, *smtp.quit())
|
||||||
|
except smtplib.SMTPServerDisconnected:
|
||||||
|
pass # Not connected
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Sends email to recipients informing that the jail has started.
|
||||||
|
"""
|
||||||
|
self._sendMessage(
|
||||||
|
"[Fail2Ban] %(jailname)s: started on %(hostname)s" %
|
||||||
|
self.message_values,
|
||||||
|
messages['start'] % self.message_values)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Sends email to recipients informing that the jail has stopped.
|
||||||
|
"""
|
||||||
|
self._sendMessage(
|
||||||
|
"[Fail2Ban] %(jailname)s: stopped on %(hostname)s" %
|
||||||
|
self.message_values,
|
||||||
|
messages['stop'] % self.message_values)
|
||||||
|
|
||||||
|
def ban(self, aInfo):
|
||||||
|
"""Sends email to recipients informing that ban has occurred.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
aInfo : dict
|
||||||
|
Dictionary which includes information in relation to
|
||||||
|
the ban.
|
||||||
|
"""
|
||||||
|
aInfo.update(self.message_values)
|
||||||
|
message = "".join([
|
||||||
|
messages['ban']['head'],
|
||||||
|
messages['ban'].get(self.matches, ""),
|
||||||
|
messages['ban']['tail']
|
||||||
|
])
|
||||||
|
self._sendMessage(
|
||||||
|
"[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" %
|
||||||
|
aInfo,
|
||||||
|
message % aInfo)
|
||||||
|
|
||||||
|
Action = SMTPAction
|
|
@ -0,0 +1,48 @@
|
||||||
|
# Fail2Ban filter to match web requests for selected URLs that don't exist
|
||||||
|
#
|
||||||
|
# This filter is aimed at blocking specific URLs that don't exist. This
|
||||||
|
# could be a set of URLs places in a Disallow: directive in robots.txt or
|
||||||
|
# just some web services that don't exist caused bots are searching for
|
||||||
|
# exploitable content. This filter is designed to have a low false postitive
|
||||||
|
# rate due.
|
||||||
|
#
|
||||||
|
# An alternative to this is the apache-noscript filter which blocks all
|
||||||
|
# types of scripts that don't exist.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# This is normally a predefined list of exploitable or valuable web services
|
||||||
|
# that are hidden or aren't actually installed.
|
||||||
|
#
|
||||||
|
|
||||||
|
[INCLUDES]
|
||||||
|
|
||||||
|
# overwrite with apache-common.local if _apache_error_client is incorrect.
|
||||||
|
before = apache-common.conf
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
|
||||||
|
failregex = ^%(_apache_error_client)s ((AH001(28|30): )?File does not exist|(AH01264: )?script not found or unable to stat): <webroot><block>(, referer: \S+)?\s*$
|
||||||
|
^%(_apache_error_client)s script '<webroot><block>' not found or unable to stat(, referer: \S+)?\s*$
|
||||||
|
|
||||||
|
ignoreregex =
|
||||||
|
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
|
||||||
|
# Webroot represents the webroot on which all other files are based
|
||||||
|
webroot = /var/www/
|
||||||
|
# Block is the actual non-found directories to block
|
||||||
|
block = (<webmail>|<phpmyadmin>|<wordpress>)[^,]*
|
||||||
|
|
||||||
|
# These are just convient definitions that assist the blocking of stuff that
|
||||||
|
# isn't installed
|
||||||
|
webmail = roundcube|(ext)?mail|horde|(v-?)?webmail
|
||||||
|
|
||||||
|
phpmyadmin = (typo3/|xampp/|admin/|)(pma|(php)?[Mm]y[Aa]dmin)
|
||||||
|
|
||||||
|
wordpress = wp-(login|signup)\.php
|
||||||
|
|
||||||
|
|
||||||
|
# DEV Notes:
|
||||||
|
#
|
||||||
|
# Author: Daniel Black
|
|
@ -1,5 +1,13 @@
|
||||||
# Fail2Ban filter to block web requests for scripts (on non scripted websites)
|
# Fail2Ban filter to block web requests for scripts (on non scripted websites)
|
||||||
#
|
#
|
||||||
|
# This matches many types of scripts that don't exist. This could generate a
|
||||||
|
# lot of false positive matches in cases like wikis and forums where users
|
||||||
|
# no affiliated with the website can insert links to missing files/scripts into
|
||||||
|
# pages and cause non-malicious browsers of the site to trigger against this
|
||||||
|
# filter.
|
||||||
|
#
|
||||||
|
# If you'd like to match specific URLs that don't exist see the
|
||||||
|
# apache-botsearch filter.
|
||||||
#
|
#
|
||||||
|
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
|
@ -19,6 +27,6 @@ ignoreregex =
|
||||||
#
|
#
|
||||||
# https://wiki.apache.org/httpd/ListOfErrors for apache error IDs
|
# https://wiki.apache.org/httpd/ListOfErrors for apache error IDs
|
||||||
#
|
#
|
||||||
# Second regex, script '/\S*(\.php|\.asp|\.exe|\.pl)\S*' not found or unable to stat\s*$ is Before http-2.2
|
# Second regex, script '/\S*(\.php|\.asp|\.exe|\.pl)\S*' not found or unable to stat\s*$ is in httpd-2.2
|
||||||
#
|
#
|
||||||
# Author: Cyril Jaquier
|
# Author: Cyril Jaquier
|
||||||
|
|
|
@ -15,7 +15,7 @@ failregex = ^%(log_prefix)s Registration from '[^']*' failed for '<HOST>(:\d+)?'
|
||||||
^%(log_prefix)s Host <HOST> failed MD5 authentication for '[^']*' \([^)]+\)$
|
^%(log_prefix)s Host <HOST> failed MD5 authentication for '[^']*' \([^)]+\)$
|
||||||
^%(log_prefix)s Failed to authenticate (user|device) [^@]+@<HOST>\S*$
|
^%(log_prefix)s Failed to authenticate (user|device) [^@]+@<HOST>\S*$
|
||||||
^%(log_prefix)s (?:handle_request_subscribe: )?Sending fake auth rejection for (device|user) \d*<sip:[^@]+@<HOST>>;tag=\w+\S*$
|
^%(log_prefix)s (?:handle_request_subscribe: )?Sending fake auth rejection for (device|user) \d*<sip:[^@]+@<HOST>>;tag=\w+\S*$
|
||||||
^%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="[\d-]+",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="\d+",SessionID="0x[\da-f]+",LocalAddress="IPV[46]/(UD|TC)P/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UD|TC)P/<HOST>/\d+"(,Challenge="\w+",ReceivedChallenge="\w+")?(,ReceivedHash="[\da-f]+")?$
|
^%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="[\d-]+",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="\d*",SessionID="0x[\da-f]+",LocalAddress="IPV[46]/(UD|TC)P/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UD|TC)P/<HOST>/\d+"(,Challenge="\w+",ReceivedChallenge="\w+")?(,ReceivedHash="[\da-f]+")?(,ACLName="\w+")?$
|
||||||
^\[\]\s*WARNING%(__pid_re)s:?(?:\[C-[\da-f]*\])? Ext\. s: "Rejecting unknown SIP connection from <HOST>"$
|
^\[\]\s*WARNING%(__pid_re)s:?(?:\[C-[\da-f]*\])? Ext\. s: "Rejecting unknown SIP connection from <HOST>"$
|
||||||
|
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Fail2Ban filter for failure attempts in Counter Strike-1.6
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
|
||||||
|
failregex = ^: Bad Rcon: "rcon \d+ "\S+" sv_contact ".*?"" from "<HOST>:\d+"$
|
||||||
|
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
|
||||||
|
datepattern = ^L %%d/%%m/%%Y - %%H:%%M:%%S
|
||||||
|
|
||||||
|
|
||||||
|
# Author: Daniel Black
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
# Values: TEXT
|
# Values: TEXT
|
||||||
#
|
#
|
||||||
failregex = ^=INFO REPORT==== ===\nI\(<0\.\d+\.0>:ejabberd_c2s:\d+\) : \([^)]+\) Failed authentication for .+ from IP <HOST> \({{(?:\d+,){3}\d+},\d+}\)$
|
failregex = ^=INFO REPORT==== ===\nI\(<0\.\d+\.0>:ejabberd_c2s:\d+\) : \([^)]+\) Failed authentication for .+ from IP <HOST> \({{(?:\d+,){3}\d+},\d+}\)$
|
||||||
|
^(?:\.\d+)? \[info\] <0\.\d+\.\d>@ejabberd_c2s:wait_for_feature_request:\d+ \([^\)]+\) Failed authentication for \S+ from IP <HOST>$
|
||||||
|
|
||||||
# Option: ignoreregex
|
# Option: ignoreregex
|
||||||
# Notes.: regex to ignore. If this regex matches, the line is ignored.
|
# Notes.: regex to ignore. If this regex matches, the line is ignored.
|
||||||
|
|
|
@ -1,6 +1,22 @@
|
||||||
# Fail2Ban filter for exim the spam rejection messages
|
# Fail2Ban filter for exim the spam rejection messages
|
||||||
#
|
#
|
||||||
## For the SA: Action: silently tossed message... to be logged exim's SAdevnull option needs to be used.
|
# Honeypot traps are very useful for fighting spam. You just activate an email
|
||||||
|
# address on your domain that you do not intend to use at all, and that normal
|
||||||
|
# people do not risk to try for contacting you. It may be something that
|
||||||
|
# spammers often test. You can also hide the address on a web page to be picked
|
||||||
|
# by spam spiders. Or simply parse your mail logs for an invalid address
|
||||||
|
# already being frequently targeted by spammers. Enable the address and
|
||||||
|
# redirect it to the blackhole. In Exim's alias file, you would add the
|
||||||
|
# following line (assuming the address is honeypot@yourdomain.com):
|
||||||
|
#
|
||||||
|
# honeypot: :blackhole:
|
||||||
|
#
|
||||||
|
# For the SA: Action: silently tossed message... to be logged exim's SAdevnull option needs to be used.
|
||||||
|
#
|
||||||
|
# To this filter use the jail.local should contain in the right jail:
|
||||||
|
#
|
||||||
|
# filter = exim-spam[honeypot=honeypot@yourdomain.com]
|
||||||
|
#
|
||||||
|
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
|
|
||||||
|
@ -13,10 +29,20 @@ before = exim-common.conf
|
||||||
failregex = ^%(pid)s \S+ F=(<>|\S+@\S+) %(host_info)srejected by local_scan\(\): .{0,256}$
|
failregex = ^%(pid)s \S+ F=(<>|\S+@\S+) %(host_info)srejected by local_scan\(\): .{0,256}$
|
||||||
^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: .*dnsbl.*\s*$
|
^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: .*dnsbl.*\s*$
|
||||||
^%(pid)s \S+ %(host_info)sF=(<>|[^@]+@\S+) rejected after DATA: This message contains a virus \(\S+\)\.\s*$
|
^%(pid)s \S+ %(host_info)sF=(<>|[^@]+@\S+) rejected after DATA: This message contains a virus \(\S+\)\.\s*$
|
||||||
|
^%(pid)s \S+ SA: Action: flagged as Spam but accepted: score=\d+\.\d+ required=\d+\.\d+ \(scanned in \d+/\d+ secs \| Message-Id: \S+\)\. From \S+ \(host=\S+ \[<HOST>\]\) for <honeypot>$
|
||||||
^%(pid)s \S+ SA: Action: silently tossed message: score=\d+\.\d+ required=\d+\.\d+ trigger=\d+\.\d+ \(scanned in \d+/\d+ secs \| Message-Id: \S+\)\. From \S+ \(host=(\S+ )?\[<HOST>\]\) for \S+$
|
^%(pid)s \S+ SA: Action: silently tossed message: score=\d+\.\d+ required=\d+\.\d+ trigger=\d+\.\d+ \(scanned in \d+/\d+ secs \| Message-Id: \S+\)\. From \S+ \(host=(\S+ )?\[<HOST>\]\) for \S+$
|
||||||
|
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
|
||||||
|
# Option: honeypot
|
||||||
|
# Notes.: honeypot is an email address that isn't published anywhere that a
|
||||||
|
# legitimate email sender would send email too.
|
||||||
|
# Values: email address
|
||||||
|
|
||||||
|
honeypot = trap@example.com
|
||||||
|
|
||||||
# DEV Notes:
|
# DEV Notes:
|
||||||
# The %(host_info) defination contains a <HOST> match
|
# The %(host_info) defination contains a <HOST> match
|
||||||
#
|
#
|
||||||
|
|
|
@ -14,7 +14,7 @@ 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 (plain|login) 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>\]: 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*$
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Fail2Ban configuration file
|
||||||
|
#
|
||||||
|
# Enable "log-auth-failures" on each Sofia profile to monitor
|
||||||
|
# <param name="log-auth-failures" value="true"/>
|
||||||
|
# -- this requires a high enough loglevel on your logs to save these messages.
|
||||||
|
#
|
||||||
|
# In the fail2ban jail.local file for this filter set ignoreip to the internal
|
||||||
|
# IP addresses on your LAN.
|
||||||
|
#
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
|
||||||
|
failregex = ^\.\d+ \[WARNING\] sofia_reg\.c:\d+ SIP auth (failure|challenge) \((REGISTER|INVITE)\) on sofia profile \'[^']+\' for \[.*\] from ip <HOST>$
|
||||||
|
^\.\d+ \[WARNING\] sofia_reg\.c:\d+ Can't find user \[\d+@\d+\.\d+\.\d+\.\d+\] from <HOST>$
|
||||||
|
|
||||||
|
ignoreregex =
|
||||||
|
|
||||||
|
# Author: Rupa SChomaker, soapee01, Daniel Black
|
||||||
|
# http://wiki.freeswitch.org/wiki/Fail2ban
|
||||||
|
# Thanks to Jim on mailing list of samples and guidance
|
||||||
|
#
|
||||||
|
# No need to match the following. Its a duplicate of the SIP auth regex.
|
||||||
|
# ^\.\d+ \[DEBUG\] sofia\.c:\d+ IP <HOST> Rejected by acl "\S+"\. Falling back to Digest auth\.$
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Fail2Ban filter for Group-Office
|
||||||
|
#
|
||||||
|
# Enable logging with:
|
||||||
|
# $config['info_log']='/home/groupoffice/log/info.log';
|
||||||
|
#
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
|
||||||
|
failregex = ^\[\]LOGIN FAILED for user: "\S+" from IP: <HOST>$
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Author: Daniel Black
|
||||||
|
|
|
@ -15,6 +15,7 @@ _daemon = postfix/smtpd
|
||||||
failregex = ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 554 5\.7\.1 .*$
|
failregex = ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 554 5\.7\.1 .*$
|
||||||
^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 450 4\.7\.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= *$
|
^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 450 4\.7\.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= *$
|
||||||
^%(__prefix_line)sNOQUEUE: reject: VRFY from \S+\[<HOST>\]: 550 5\.1\.1 .*$
|
^%(__prefix_line)sNOQUEUE: reject: VRFY from \S+\[<HOST>\]: 550 5\.1\.1 .*$
|
||||||
|
^%(__prefix_line)simproper command pipelining after \S+ from [^[]*\[<HOST>\]:?$
|
||||||
|
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
# Fail2Ban filter for pureftp
|
# Fail2Ban filter for pureftp
|
||||||
#
|
#
|
||||||
|
# Disable hostname based logging by:
|
||||||
|
#
|
||||||
|
# Start pure-ftpd with the -H switch or on Ubuntu 'echo yes > /etc/pure-ftpd/conf/DontResolve'
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
|
|
||||||
before = common.conf
|
before = common.conf
|
||||||
|
@ -17,3 +21,4 @@ ignoreregex =
|
||||||
|
|
||||||
# Author: Cyril Jaquier
|
# Author: Cyril Jaquier
|
||||||
# Modified: Yaroslav Halchenko for pure-ftpd
|
# Modified: Yaroslav Halchenko for pure-ftpd
|
||||||
|
# Documentation thanks to Blake on http://www.fail2ban.org/wiki/index.php?title=Fail2ban:Community_Portal
|
||||||
|
|
|
@ -23,7 +23,7 @@ ignoreregex =
|
||||||
#
|
#
|
||||||
# Assume that the user can inject "from <HOST>" into the imap response
|
# Assume that the user can inject "from <HOST>" into the imap response
|
||||||
# somehow. Write test cases around this to ensure that the combination of
|
# somehow. Write test cases around this to ensure that the combination of
|
||||||
# arbitary user input and IMAP response doesn't inject the wrong IP for
|
# arbitrary user input and IMAP response doesn't inject the wrong IP for
|
||||||
# fail2ban
|
# fail2ban
|
||||||
#
|
#
|
||||||
# Author: Teodor Micu & Yaroslav Halchenko & terence namusonge & Daniel Black
|
# Author: Teodor Micu & Yaroslav Halchenko & terence namusonge & Daniel Black
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
|
||||||
|
failregex = ^ \[LOGIN_ERROR\].*from <HOST>: Unknown user or password incorrect\.$
|
||||||
|
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
|
||||||
|
datepattern = ^%%m/%%d/%%Y %%H:%%M:%%S
|
||||||
|
|
||||||
|
# DEV NOTES:
|
||||||
|
#
|
||||||
|
# Author: Daniel Black
|
|
@ -20,6 +20,7 @@ failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|erro
|
||||||
^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$
|
^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers\s*$
|
||||||
^%(__prefix_line)sUser .+ from <HOST> not allowed because not in any group\s*$
|
^%(__prefix_line)sUser .+ from <HOST> not allowed because not in any group\s*$
|
||||||
^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$
|
^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$
|
||||||
|
^%(__prefix_line)sReceived disconnect from <HOST>: 3: \S+: Auth fail$
|
||||||
^%(__prefix_line)sUser .+ from <HOST> not allowed because a group is listed in DenyGroups\s*$
|
^%(__prefix_line)sUser .+ from <HOST> not allowed because a group is listed in DenyGroups\s*$
|
||||||
^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$
|
^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$
|
||||||
^(?P<__prefix>%(__prefix_line)s)User .+ not allowed because account is locked<SKIPLINES>(?P=__prefix)(?:error: )?Received disconnect from <HOST>: 11: Bye Bye \[preauth\]$
|
^(?P<__prefix>%(__prefix_line)s)User .+ not allowed because account is locked<SKIPLINES>(?P=__prefix)(?:error: )?Received disconnect from <HOST>: 11: Bye Bye \[preauth\]$
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
failregex = ^ LOG\d\[\d+:\d+\]:\ SSL_accept from <HOST>:\d+ : (?P<CODE>[\dA-F]+): error:(?P=CODE):SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate$
|
failregex = ^ LOG\d\[\d+:\d+\]:\ SSL_accept from <HOST>:\d+ : (?P<CODE>[\dA-F]+): error:(?P=CODE):SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate$
|
||||||
|
|
||||||
datepattern = ^%Y.%m.%d %H:%M:%S
|
|
||||||
|
|
||||||
# DEV NOTES:
|
# DEV NOTES:
|
||||||
#
|
#
|
||||||
# Author: Daniel Black
|
# Author: Daniel Black
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
# Fail2Ban filter for vsftp
|
# Fail2Ban filter for vsftp
|
||||||
#
|
#
|
||||||
|
# Configure VSFTP for "dual_log_enable=YES", and have fail2ban watch
|
||||||
|
# /var/log/vsftpd.log instead of /var/log/secure. vsftpd.log file shows the
|
||||||
|
# incoming ip address rather than domain names.
|
||||||
|
|
||||||
[INCLUDES]
|
[INCLUDES]
|
||||||
|
|
||||||
|
@ -16,3 +19,4 @@ failregex = ^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S*
|
||||||
ignoreregex =
|
ignoreregex =
|
||||||
|
|
||||||
# Author: Cyril Jaquier
|
# Author: Cyril Jaquier
|
||||||
|
# Documentation from fail2ban wiki
|
||||||
|
|
209
config/jail.conf
209
config/jail.conf
|
@ -206,11 +206,9 @@ logpath = /var/log/auth.log
|
||||||
[dropbear]
|
[dropbear]
|
||||||
|
|
||||||
port = ssh
|
port = ssh
|
||||||
filter = sshd
|
|
||||||
logpath = /var/log/dropbear
|
logpath = /var/log/dropbear
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[selinux-ssh]
|
[selinux-ssh]
|
||||||
|
|
||||||
port = ssh
|
port = ssh
|
||||||
|
@ -290,9 +288,9 @@ filter = sshd
|
||||||
action = bsd-ipfw[port=ssh,table=1]
|
action = bsd-ipfw[port=ssh,table=1]
|
||||||
logpath = /var/log/auth.log
|
logpath = /var/log/auth.log
|
||||||
|
|
||||||
# PF is a BSD based firewall
|
|
||||||
[sshd-pf]
|
|
||||||
|
|
||||||
|
[sshd-pf]
|
||||||
|
# PF is a BSD based firewall
|
||||||
filter = sshd
|
filter = sshd
|
||||||
action = pf
|
action = pf
|
||||||
logpath = /var/log/sshd.log
|
logpath = /var/log/sshd.log
|
||||||
|
@ -313,6 +311,7 @@ filter = sshd
|
||||||
action = osx-afctl[bantime=600]
|
action = osx-afctl[bantime=600]
|
||||||
logpath = /var/log/secure.log
|
logpath = /var/log/secure.log
|
||||||
maxretry = 5
|
maxretry = 5
|
||||||
|
|
||||||
#
|
#
|
||||||
# HTTP servers
|
# HTTP servers
|
||||||
#
|
#
|
||||||
|
@ -322,11 +321,10 @@ maxretry = 5
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = /var/log/apache*/*error.log
|
logpath = /var/log/apache*/*error.log
|
||||||
|
|
||||||
# Ban hosts which agent identifies spammer robots crawling the web
|
|
||||||
# for email addresses. The mail outputs are buffered.
|
|
||||||
|
|
||||||
[apache-badbots]
|
[apache-badbots]
|
||||||
|
# Ban hosts which agent identifies spammer robots crawling the web
|
||||||
|
# for email addresses. The mail outputs are buffered.
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = /var/log/apache*/*access.log
|
logpath = /var/log/apache*/*access.log
|
||||||
/var/www/*/logs/access_log
|
/var/www/*/logs/access_log
|
||||||
|
@ -343,6 +341,22 @@ maxretry = 6
|
||||||
|
|
||||||
[apache-overflows]
|
[apache-overflows]
|
||||||
|
|
||||||
|
port = http,https
|
||||||
|
logpath = /var/log/apache*/*error.log
|
||||||
|
/var/www/*/logs/error_log
|
||||||
|
maxretry = 2
|
||||||
|
|
||||||
|
|
||||||
|
[apache-nohome]
|
||||||
|
|
||||||
|
port = http,https
|
||||||
|
logpath = /var/log/apache*/*error.log
|
||||||
|
/var/www/*/logs/error_log
|
||||||
|
maxretry = 2
|
||||||
|
|
||||||
|
|
||||||
|
[apache-botsearch]
|
||||||
|
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = /var/log/apache*/*error.log
|
logpath = /var/log/apache*/*error.log
|
||||||
maxretry = 2
|
maxretry = 2
|
||||||
|
@ -352,6 +366,7 @@ maxretry = 2
|
||||||
|
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = /var/log/apache*/*error.log
|
logpath = /var/log/apache*/*error.log
|
||||||
|
/var/www/*/logs/error_log
|
||||||
maxretry = 2
|
maxretry = 2
|
||||||
|
|
||||||
|
|
||||||
|
@ -370,20 +385,24 @@ logpath = /var/log/nginx/error.log
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = /var/www/*/logs/access_log
|
logpath = /var/www/*/logs/access_log
|
||||||
|
|
||||||
|
|
||||||
[suhosin]
|
[suhosin]
|
||||||
|
|
||||||
filter = suhosin
|
|
||||||
# Same as above for mod_auth
|
|
||||||
# It catches wrong authentifications
|
|
||||||
logpath = /var/log/lighttpd/error.log
|
|
||||||
|
|
||||||
|
|
||||||
[lighttpd-auth]
|
|
||||||
|
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = /var/log/lighttpd/error.log
|
logpath = /var/log/lighttpd/error.log
|
||||||
|
|
||||||
|
|
||||||
|
[lighttpd-auth]
|
||||||
|
# Same as above for Apache's mod_auth
|
||||||
|
# It catches wrong authentifications
|
||||||
|
port = http,https
|
||||||
|
logpath = /var/log/lighttpd/error.log
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Webmail and groupware servers
|
||||||
|
#
|
||||||
|
|
||||||
[roundcube-auth]
|
[roundcube-auth]
|
||||||
|
|
||||||
port = http,https
|
port = http,https
|
||||||
|
@ -393,22 +412,34 @@ logpath = /var/log/roundcube/userlogins
|
||||||
[openwebmail]
|
[openwebmail]
|
||||||
|
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = /var/log/openwebmail.log`
|
logpath = /var/log/openwebmail.log
|
||||||
|
|
||||||
|
|
||||||
|
[horde]
|
||||||
|
|
||||||
|
port = http,https
|
||||||
|
logpath = /var/log/horde/horde.log
|
||||||
|
|
||||||
|
|
||||||
|
[groupoffice]
|
||||||
|
|
||||||
|
port = http,https
|
||||||
|
logpath = /home/groupoffice/log/info.log
|
||||||
|
|
||||||
|
|
||||||
[sogo-auth]
|
[sogo-auth]
|
||||||
# Monitor SOGo groupware server
|
# Monitor SOGo groupware server
|
||||||
# without proxy this would be:
|
# without proxy this would be:
|
||||||
# port = 20000
|
# port = 20000
|
||||||
|
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = /var/log/sogo/sogo.log
|
logpath = /var/log/sogo/sogo.log
|
||||||
|
|
||||||
|
|
||||||
[3proxy]
|
|
||||||
|
|
||||||
port = 3128
|
|
||||||
logpath = /var/log/3proxy.log
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Web Applications
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
[guacamole]
|
[guacamole]
|
||||||
|
|
||||||
|
@ -422,43 +453,61 @@ port = 10000
|
||||||
logpath = /var/log/auth.log
|
logpath = /var/log/auth.log
|
||||||
|
|
||||||
|
|
||||||
# ... custom jails
|
#
|
||||||
|
# HTTP Proxy servers
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
# This jail demonstrates the use of wildcards in "logpath".
|
[squid]
|
||||||
# Moreover, it is possible to give other files on a new line.
|
|
||||||
[apache-tcpwrapper]
|
|
||||||
|
|
||||||
filter = apache-auth
|
port = 80,443,3128,8080
|
||||||
action = hostsdeny
|
logpath = /var/log/squid/access.log
|
||||||
logpath = /var/log/apache*/*error.log
|
|
||||||
maxretry = 6
|
|
||||||
|
[3proxy]
|
||||||
|
|
||||||
|
port = 3128
|
||||||
|
logpath = /var/log/3proxy.log
|
||||||
|
|
||||||
#
|
#
|
||||||
# FTP servers
|
# FTP servers
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
# The hosts.deny path can be defined with the "file" argument if it is
|
|
||||||
# not in /etc.
|
|
||||||
[proftpd]
|
[proftpd]
|
||||||
|
|
||||||
port = ftp,ftp-data,ftps,ftps-data
|
port = ftp,ftp-data,ftps,ftps-data
|
||||||
logpath = /var/log/proftpd/proftpd.log
|
logpath = /var/log/proftpd/proftpd.log
|
||||||
|
|
||||||
|
|
||||||
[pure-ftpd]
|
[pure-ftpd]
|
||||||
|
|
||||||
port = ftp,ftp-data,ftps,ftps-data
|
port = ftp,ftp-data,ftps,ftps-data
|
||||||
logpath = /var/log/auth.log
|
logpath = /var/log/auth.log
|
||||||
maxretry = 6
|
maxretry = 6
|
||||||
|
|
||||||
[vsftpd]
|
|
||||||
|
[gssftpd]
|
||||||
|
|
||||||
port = ftp,ftp-data,ftps,ftps-data
|
port = ftp,ftp-data,ftps,ftps-data
|
||||||
logpath = /var/log/vsftpd.log
|
logpath = /var/log/daemon.log
|
||||||
|
maxretry = 6
|
||||||
|
|
||||||
|
|
||||||
|
[wuftpd]
|
||||||
|
|
||||||
|
port = ftp,ftp-data,ftps,ftps-data
|
||||||
|
logpath = /var/log/daemon.log
|
||||||
|
maxretry = 6
|
||||||
|
|
||||||
|
|
||||||
|
[vsftpd]
|
||||||
# or overwrite it in jails.local to be
|
# or overwrite it in jails.local to be
|
||||||
# logpath = /var/log/auth.log
|
# logpath = /var/log/auth.log
|
||||||
# if you want to rely on PAM failed login attempts
|
# if you want to rely on PAM failed login attempts
|
||||||
# vsftpd's failregex should match both of those formats
|
# vsftpd's failregex should match both of those formats
|
||||||
|
port = ftp,ftp-data,ftps,ftps-data
|
||||||
|
logpath = /var/log/vsftpd.log
|
||||||
|
|
||||||
|
|
||||||
# Do not ban anybody. Just report information about the remote host.
|
# Do not ban anybody. Just report information about the remote host.
|
||||||
|
@ -488,22 +537,31 @@ bantime = 1800
|
||||||
# ASSP SMTP Proxy Jail
|
# ASSP SMTP Proxy Jail
|
||||||
[assp]
|
[assp]
|
||||||
|
|
||||||
port = smtp,ssmtp,submission
|
port = smtp,465,submission
|
||||||
logpath = /root/path/to/assp/logs/maillog.txt
|
logpath = /root/path/to/assp/logs/maillog.txt
|
||||||
|
|
||||||
|
|
||||||
[courier-smtp]
|
[courier-smtp]
|
||||||
|
|
||||||
port = smtp,ssmtp,submission
|
port = smtp,465,submission
|
||||||
logpath = /var/log/mail.log
|
logpath = /var/log/mail.log
|
||||||
|
|
||||||
|
|
||||||
[postfix]
|
[postfix]
|
||||||
|
|
||||||
port = smtp,ssmtp,submission
|
port = smtp,465,submission
|
||||||
logpath = /var/log/mail.log
|
logpath = /var/log/mail.log
|
||||||
|
|
||||||
|
|
||||||
|
[qmail-rbl]
|
||||||
|
|
||||||
|
filter = qmail
|
||||||
|
port = smtp,465,submission
|
||||||
|
logpath = /service/qmail/log/main/current
|
||||||
|
|
||||||
|
|
||||||
|
# The hosts.deny path can be defined with the "file" argument if it is
|
||||||
|
# not in /etc.
|
||||||
[postfix-tcpwrapper]
|
[postfix-tcpwrapper]
|
||||||
|
|
||||||
filter = postfix
|
filter = postfix
|
||||||
|
@ -517,6 +575,7 @@ bantime = 300
|
||||||
|
|
||||||
logpath = /var/log/mail.log
|
logpath = /var/log/mail.log
|
||||||
|
|
||||||
|
|
||||||
# dovecot defaults to logging to the mail syslog facility
|
# dovecot defaults to logging to the mail syslog facility
|
||||||
# but can be set by syslog_facility in the dovecot configuration.
|
# but can be set by syslog_facility in the dovecot configuration.
|
||||||
[dovecot]
|
[dovecot]
|
||||||
|
@ -532,6 +591,12 @@ port = pop3,pop3s,imap,imaps,submission,465,sieve
|
||||||
logpath = /var/log/secure
|
logpath = /var/log/secure
|
||||||
|
|
||||||
|
|
||||||
|
[sieve]
|
||||||
|
|
||||||
|
port = smtp,465,submission
|
||||||
|
logpath = /var/log/mail*log
|
||||||
|
|
||||||
|
|
||||||
[solid-pop3d]
|
[solid-pop3d]
|
||||||
|
|
||||||
port = pop3,pop3s
|
port = pop3,pop3s
|
||||||
|
@ -540,12 +605,12 @@ logpath = /var/log/mail.log
|
||||||
|
|
||||||
[exim]
|
[exim]
|
||||||
|
|
||||||
port = smtp,ssmtp,submission
|
port = smtp,465,submission
|
||||||
logpath = /var/log/exim/mainlog
|
logpath = /var/log/exim/mainlog
|
||||||
|
|
||||||
|
|
||||||
[exim-spam]
|
[exim-spam]
|
||||||
port = smtp,ssmtp,submission
|
port = smtp,465,submission
|
||||||
logpath = /var/log/exim/mainlog
|
logpath = /var/log/exim/mainlog
|
||||||
|
|
||||||
|
|
||||||
|
@ -561,13 +626,13 @@ logpath = /opt/kerio/mailserver/store/logs/security.log
|
||||||
|
|
||||||
[courier-auth]
|
[courier-auth]
|
||||||
|
|
||||||
port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s
|
port = smtp,465,submission,imap3,imaps,pop3,pop3s
|
||||||
logpath = /var/log/mail.log
|
logpath = /var/log/mail.log
|
||||||
|
|
||||||
|
|
||||||
[postfix-sasl]
|
[postfix-sasl]
|
||||||
|
|
||||||
port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s
|
port = smtp,465,submission,imap3,imaps,pop3,pop3s
|
||||||
# You might consider monitoring /var/log/mail.warn instead if you are
|
# You might consider monitoring /var/log/mail.warn instead if you are
|
||||||
# running postfix since it would provide the same log lines at the
|
# running postfix since it would provide the same log lines at the
|
||||||
# "warn" level but overall at the smaller filesize.
|
# "warn" level but overall at the smaller filesize.
|
||||||
|
@ -576,9 +641,29 @@ logpath = /var/log/mail.log
|
||||||
|
|
||||||
[perdition]
|
[perdition]
|
||||||
|
|
||||||
port = imap2,imap3,imaps,pop3,pop3s
|
port = imap3,imaps,pop3,pop3s
|
||||||
logpath = /var/log/maillog
|
logpath = /var/log/maillog
|
||||||
|
|
||||||
|
|
||||||
|
[squirrelmail]
|
||||||
|
|
||||||
|
port = smtp,465,submission,imap2,imap3,imaps,pop3,pop3s,http,https,socks
|
||||||
|
logpath = /var/lib/squirrelmail/prefs/squirrelmail_access_log
|
||||||
|
|
||||||
|
|
||||||
|
[cyrus-imap]
|
||||||
|
|
||||||
|
port = imap3,imaps
|
||||||
|
logpath = /var/log/mail*log
|
||||||
|
|
||||||
|
|
||||||
|
[uwimap-auth]
|
||||||
|
|
||||||
|
port = imap3,imaps
|
||||||
|
logpath = /var/log/mail*log
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
#
|
#
|
||||||
# DNS servers
|
# DNS servers
|
||||||
#
|
#
|
||||||
|
@ -609,6 +694,15 @@ logpath = /var/log/maillog
|
||||||
port = domain,953
|
port = domain,953
|
||||||
logpath = /var/log/named/security.log
|
logpath = /var/log/named/security.log
|
||||||
|
|
||||||
|
|
||||||
|
[nsd]
|
||||||
|
|
||||||
|
port = 53
|
||||||
|
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
|
||||||
|
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
|
||||||
|
logpath = /var/log/nsd.log
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Miscelaneous
|
# Miscelaneous
|
||||||
#
|
#
|
||||||
|
@ -622,6 +716,17 @@ action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp",
|
||||||
logpath = /var/log/asterisk/messages
|
logpath = /var/log/asterisk/messages
|
||||||
maxretry = 10
|
maxretry = 10
|
||||||
|
|
||||||
|
|
||||||
|
[freeswitch]
|
||||||
|
|
||||||
|
port = 5060,5061
|
||||||
|
action = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
|
||||||
|
%(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
|
||||||
|
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s"]
|
||||||
|
logpath = /var/log/freeswitch.log
|
||||||
|
maxretry = 10
|
||||||
|
|
||||||
|
|
||||||
# Historical support (before https://github.com/fail2ban/fail2ban/issues/37 was fixed )
|
# Historical support (before https://github.com/fail2ban/fail2ban/issues/37 was fixed )
|
||||||
# use [asterisk] for new jails
|
# use [asterisk] for new jails
|
||||||
[asterisk-tcp]
|
[asterisk-tcp]
|
||||||
|
@ -694,3 +799,25 @@ logpath = /var/log/auth.log
|
||||||
banaction = iptables-multiport-log
|
banaction = iptables-multiport-log
|
||||||
logpath = /var/log/daemon.log
|
logpath = /var/log/daemon.log
|
||||||
maxretry = 2
|
maxretry = 2
|
||||||
|
|
||||||
|
|
||||||
|
# stunnel - need to set port for this
|
||||||
|
[stunnel]
|
||||||
|
|
||||||
|
logpath = /var/log/stunnel4/stunnel.log
|
||||||
|
|
||||||
|
|
||||||
|
[ejabberd-auth]
|
||||||
|
|
||||||
|
port = 5222
|
||||||
|
logpath = /var/log/ejabberd/ejabberd.log
|
||||||
|
|
||||||
|
|
||||||
|
[counter-strike]
|
||||||
|
|
||||||
|
logpath = /opt/cstrike/logs/L[0-9]*.log
|
||||||
|
# Firewall: http://www.cstrike-planet.com/faq/6
|
||||||
|
tcpport = 27030,27031,27032,27033,27034,27035,27036,27037,27038,27039
|
||||||
|
udpport = 1200,27000,27001,27002,27003,27004,27005,27006,27007,27008,27009,27010,27011,27012,27013,27014,27015
|
||||||
|
action = %(banaction)s[name=%(__name__)s-tcp, port="%(tcpport)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
|
||||||
|
%(banaction)s[name=%(__name__)s-udp, port="%(udpport)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
|
||||||
|
|
|
@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging, os
|
import logging, os
|
||||||
from configreader import ConfigReader, DefinitionInitConfigReader
|
|
||||||
|
from .configreader import ConfigReader, DefinitionInitConfigReader
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -58,22 +59,20 @@ class ActionReader(DefinitionInitConfigReader):
|
||||||
head = ["set", self._jailName]
|
head = ["set", self._jailName]
|
||||||
stream = list()
|
stream = list()
|
||||||
stream.append(head + ["addaction", self._name])
|
stream.append(head + ["addaction", self._name])
|
||||||
|
head.extend(["action", self._name])
|
||||||
for opt in self._opts:
|
for opt in self._opts:
|
||||||
if opt == "actionstart":
|
if opt == "actionstart":
|
||||||
stream.append(head + ["actionstart", self._name, self._opts[opt]])
|
stream.append(head + ["actionstart", self._opts[opt]])
|
||||||
elif opt == "actionstop":
|
elif opt == "actionstop":
|
||||||
stream.append(head + ["actionstop", self._name, self._opts[opt]])
|
stream.append(head + ["actionstop", self._opts[opt]])
|
||||||
elif opt == "actioncheck":
|
elif opt == "actioncheck":
|
||||||
stream.append(head + ["actioncheck", self._name, self._opts[opt]])
|
stream.append(head + ["actioncheck", self._opts[opt]])
|
||||||
elif opt == "actionban":
|
elif opt == "actionban":
|
||||||
stream.append(head + ["actionban", self._name, self._opts[opt]])
|
stream.append(head + ["actionban", self._opts[opt]])
|
||||||
elif opt == "actionunban":
|
elif opt == "actionunban":
|
||||||
stream.append(head + ["actionunban", self._name, self._opts[opt]])
|
stream.append(head + ["actionunban", self._opts[opt]])
|
||||||
if self._initOpts:
|
if self._initOpts:
|
||||||
if "timeout" in self._initOpts:
|
|
||||||
stream.append(head + ["timeout", self._name, self._opts["timeout"]])
|
|
||||||
# cInfo
|
|
||||||
for p in self._initOpts:
|
for p in self._initOpts:
|
||||||
stream.append(head + ["setcinfo", self._name, p, self._initOpts[p]])
|
stream.append(head + [p, self._initOpts[p]])
|
||||||
|
|
||||||
return stream
|
return stream
|
||||||
|
|
|
@ -23,7 +23,7 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from fail2ban.exceptions import UnknownJailException, DuplicateJailException
|
from ..exceptions import UnknownJailException, DuplicateJailException
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -165,7 +165,23 @@ class Beautifier:
|
||||||
msg = "No actions for jail %s" % inC[1]
|
msg = "No actions for jail %s" % inC[1]
|
||||||
else:
|
else:
|
||||||
msg = "The jail %s has the following actions:\n" % inC[1]
|
msg = "The jail %s has the following actions:\n" % inC[1]
|
||||||
msg += ", ".join(action.getName() for action in response)
|
msg += ", ".join(response)
|
||||||
|
elif inC[2] == "actionproperties":
|
||||||
|
if len(response) == 0:
|
||||||
|
msg = "No properties for jail %s action %s" % (
|
||||||
|
inC[1], inC[3])
|
||||||
|
else:
|
||||||
|
msg = "The jail %s action %s has the following " \
|
||||||
|
"properties:\n" % (inC[1], inC[3])
|
||||||
|
msg += ", ".join(response)
|
||||||
|
elif inC[2] == "actionmethods":
|
||||||
|
if len(response) == 0:
|
||||||
|
msg = "No methods for jail %s action %s" % (
|
||||||
|
inC[1], inC[3])
|
||||||
|
else:
|
||||||
|
msg = "The jail %s action %s has the following " \
|
||||||
|
"methods:\n" % (inC[1], inC[3])
|
||||||
|
msg += ", ".join(response)
|
||||||
except Exception:
|
except Exception:
|
||||||
logSys.warning("Beautifier error. Please report the error")
|
logSys.warning("Beautifier error. Please report the error")
|
||||||
logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` +
|
logSys.error("Beautify " + `response` + " with " + `self.__inputCmd` +
|
||||||
|
|
|
@ -25,9 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import glob, logging, os
|
import glob, logging, os
|
||||||
from configparserinc import SafeConfigParserWithIncludes
|
|
||||||
from ConfigParser import NoOptionError, NoSectionError
|
from ConfigParser import NoOptionError, NoSectionError
|
||||||
|
|
||||||
|
from .configparserinc import SafeConfigParserWithIncludes
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -158,6 +159,10 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||||
def read(self):
|
def read(self):
|
||||||
return ConfigReader.read(self, self._file)
|
return ConfigReader.read(self, self._file)
|
||||||
|
|
||||||
|
# needed for fail2ban-regex that doesn't need fancy directories
|
||||||
|
def readexplicit(self):
|
||||||
|
return SafeConfigParserWithIncludes.read(self, self._file)
|
||||||
|
|
||||||
def getOptions(self, pOpts):
|
def getOptions(self, pOpts):
|
||||||
self._opts = ConfigReader.getOptions(
|
self._opts = ConfigReader.getOptions(
|
||||||
self, "Definition", self._configOpts, pOpts)
|
self, "Definition", self._configOpts, pOpts)
|
||||||
|
|
|
@ -25,9 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from configreader import ConfigReader
|
|
||||||
from fail2banreader import Fail2banReader
|
from .configreader import ConfigReader
|
||||||
from jailsreader import JailsReader
|
from .fail2banreader import Fail2banReader
|
||||||
|
from .jailsreader import JailsReader
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -25,7 +25,8 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from configreader import ConfigReader
|
|
||||||
|
from .configreader import ConfigReader
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -25,7 +25,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging, os, shlex
|
import logging, os, shlex
|
||||||
from configreader import ConfigReader, DefinitionInitConfigReader
|
|
||||||
|
from .configreader import ConfigReader, DefinitionInitConfigReader
|
||||||
|
from ..server.action import CommandAction
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -42,14 +44,18 @@ class FilterReader(DefinitionInitConfigReader):
|
||||||
|
|
||||||
def convert(self):
|
def convert(self):
|
||||||
stream = list()
|
stream = list()
|
||||||
for opt in self._opts:
|
combinedopts = dict(list(self._opts.items()) + list(self._initOpts.items()))
|
||||||
|
opts = CommandAction.substituteRecursiveTags(combinedopts)
|
||||||
|
if not opts:
|
||||||
|
raise ValueError('recursive tag definitions unable to be resolved')
|
||||||
|
for opt, value in opts.iteritems():
|
||||||
if opt == "failregex":
|
if opt == "failregex":
|
||||||
for regex in self._opts[opt].split('\n'):
|
for regex in value.split('\n'):
|
||||||
# Do not send a command if the rule is empty.
|
# Do not send a command if the rule is empty.
|
||||||
if regex != '':
|
if regex != '':
|
||||||
stream.append(["set", self._jailName, "addfailregex", regex])
|
stream.append(["set", self._jailName, "addfailregex", regex])
|
||||||
elif opt == "ignoreregex":
|
elif opt == "ignoreregex":
|
||||||
for regex in self._opts[opt].split('\n'):
|
for regex in value.split('\n'):
|
||||||
# Do not send a command if the rule is empty.
|
# Do not send a command if the rule is empty.
|
||||||
if regex != '':
|
if regex != '':
|
||||||
stream.append(["set", self._jailName, "addignoreregex", regex])
|
stream.append(["set", self._jailName, "addignoreregex", regex])
|
||||||
|
|
|
@ -25,10 +25,11 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging, re, glob, os.path
|
import logging, re, glob, os.path
|
||||||
|
import json
|
||||||
|
|
||||||
from configreader import ConfigReader
|
from .configreader import ConfigReader
|
||||||
from filterreader import FilterReader
|
from .filterreader import FilterReader
|
||||||
from actionreader import ActionReader
|
from .actionreader import ActionReader
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -45,6 +46,11 @@ class JailReader(ConfigReader):
|
||||||
self.__filter = None
|
self.__filter = None
|
||||||
self.__force_enable = force_enable
|
self.__force_enable = force_enable
|
||||||
self.__actions = list()
|
self.__actions = list()
|
||||||
|
self.__opts = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self):
|
||||||
|
return self.__opts
|
||||||
|
|
||||||
def setName(self, value):
|
def setName(self, value):
|
||||||
self.__name = value
|
self.__name = value
|
||||||
|
@ -120,8 +126,20 @@ class JailReader(ConfigReader):
|
||||||
if not act: # skip empty actions
|
if not act: # skip empty actions
|
||||||
continue
|
continue
|
||||||
actName, actOpt = JailReader.extractOptions(act)
|
actName, actOpt = JailReader.extractOptions(act)
|
||||||
|
if actName.endswith(".py"):
|
||||||
|
self.__actions.append([
|
||||||
|
"set",
|
||||||
|
self.__name,
|
||||||
|
"addaction",
|
||||||
|
actOpt.pop("actname", os.path.splitext(actName)[0]),
|
||||||
|
os.path.join(
|
||||||
|
self.getBaseDir(), "action.d", actName),
|
||||||
|
json.dumps(actOpt),
|
||||||
|
])
|
||||||
|
else:
|
||||||
action = ActionReader(
|
action = ActionReader(
|
||||||
actName, self.__name, actOpt, basedir=self.getBaseDir())
|
actName, self.__name, actOpt,
|
||||||
|
basedir=self.getBaseDir())
|
||||||
ret = action.read()
|
ret = action.read()
|
||||||
if ret:
|
if ret:
|
||||||
action.getOptions(self.__opts)
|
action.getOptions(self.__opts)
|
||||||
|
@ -193,7 +211,10 @@ class JailReader(ConfigReader):
|
||||||
if self.__filter:
|
if self.__filter:
|
||||||
stream.extend(self.__filter.convert())
|
stream.extend(self.__filter.convert())
|
||||||
for action in self.__actions:
|
for action in self.__actions:
|
||||||
|
if isinstance(action, ConfigReader):
|
||||||
stream.extend(action.convert())
|
stream.extend(action.convert())
|
||||||
|
else:
|
||||||
|
stream.append(action)
|
||||||
stream.insert(0, ["add", self.__name, backend])
|
stream.insert(0, ["add", self.__name, backend])
|
||||||
return stream
|
return stream
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,9 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from configreader import ConfigReader
|
|
||||||
from jailreader import JailReader
|
from .configreader import ConfigReader
|
||||||
|
from .jailreader import JailReader
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -45,6 +46,10 @@ class JailsReader(ConfigReader):
|
||||||
self.__jails = list()
|
self.__jails = list()
|
||||||
self.__force_enable = force_enable
|
self.__force_enable = force_enable
|
||||||
|
|
||||||
|
@property
|
||||||
|
def jails(self):
|
||||||
|
return self.__jails
|
||||||
|
|
||||||
def read(self):
|
def read(self):
|
||||||
return ConfigReader.read(self, "jail")
|
return ConfigReader.read(self, "jail")
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ __license__ = "GPL"
|
||||||
class DuplicateJailException(Exception):
|
class DuplicateJailException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class UnknownJailException(Exception):
|
class UnknownJailException(KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -76,19 +76,21 @@ protocol = [
|
||||||
["set <JAIL> unbanip <IP>", "manually Unban <IP> in <JAIL>"],
|
["set <JAIL> unbanip <IP>", "manually Unban <IP> in <JAIL>"],
|
||||||
["set <JAIL> maxretry <RETRY>", "sets the number of failures <RETRY> before banning the host for <JAIL>"],
|
["set <JAIL> maxretry <RETRY>", "sets the number of failures <RETRY> before banning the host for <JAIL>"],
|
||||||
["set <JAIL> maxlines <LINES>", "sets the number of <LINES> to buffer for regex search for <JAIL>"],
|
["set <JAIL> maxlines <LINES>", "sets the number of <LINES> to buffer for regex search for <JAIL>"],
|
||||||
["set <JAIL> addaction <ACT>", "adds a new action named <NAME> for <JAIL>"],
|
["set <JAIL> addaction <ACT>[ <PYTHONFILE> <JSONKWARGS>]", "adds a new action named <NAME> for <JAIL>. Optionally for a Python based action, a <PYTHONFILE> and <JSONKWARGS> can be specified, else will be a Command Action"],
|
||||||
["set <JAIL> delaction <ACT>", "removes the action <NAME> from <JAIL>"],
|
["set <JAIL> delaction <ACT>", "removes the action <ACT> from <JAIL>"],
|
||||||
["set <JAIL> setcinfo <ACT> <KEY> <VALUE>", "sets <VALUE> for <KEY> of the action <NAME> for <JAIL>"],
|
["", "COMMAND ACTION CONFIGURATION", ""],
|
||||||
["set <JAIL> delcinfo <ACT> <KEY>", "removes <KEY> for the action <NAME> for <JAIL>"],
|
["set <JAIL> action <ACT> actionstart <CMD>", "sets the start command <CMD> of the action <ACT> for <JAIL>"],
|
||||||
["set <JAIL> timeout <ACT> <TIMEOUT>", "sets <TIMEOUT> as the command timeout in seconds for the action <ACT> for <JAIL>"],
|
["set <JAIL> action <ACT> actionstop <CMD>", "sets the stop command <CMD> of the action <ACT> for <JAIL>"],
|
||||||
["set <JAIL> actionstart <ACT> <CMD>", "sets the start command <CMD> of the action <ACT> for <JAIL>"],
|
["set <JAIL> action <ACT> actioncheck <CMD>", "sets the check command <CMD> of the action <ACT> for <JAIL>"],
|
||||||
["set <JAIL> actionstop <ACT> <CMD>", "sets the stop command <CMD> of the action <ACT> for <JAIL>"],
|
["set <JAIL> action <ACT> actionban <CMD>", "sets the ban command <CMD> of the action <ACT> for <JAIL>"],
|
||||||
["set <JAIL> actioncheck <ACT> <CMD>", "sets the check command <CMD> of the action <ACT> for <JAIL>"],
|
["set <JAIL> action <ACT> actionunban <CMD>", "sets the unban command <CMD> of the action <ACT> for <JAIL>"],
|
||||||
["set <JAIL> actionban <ACT> <CMD>", "sets the ban command <CMD> of the action <ACT> for <JAIL>"],
|
["set <JAIL> action <ACT> timeout <TIMEOUT>", "sets <TIMEOUT> as the command timeout in seconds for the action <ACT> for <JAIL>"],
|
||||||
["set <JAIL> actionunban <ACT> <CMD>", "sets the unban command <CMD> of the action <ACT> for <JAIL>"],
|
["", "GENERAL ACTION CONFIGURATION", ""],
|
||||||
|
["set <JAIL> action <ACT> <PROPERTY> <VALUE>", "sets the <VALUE> of <PROPERTY> for the action <ACT> for <JAIL>"],
|
||||||
|
["set <JAIL> action <ACT> <METHOD>[ <JSONKWARGS>]", "calls the <METHOD> with <JSONKWARGS> for the action <ACT> for <JAIL>"],
|
||||||
['', "JAIL INFORMATION", ""],
|
['', "JAIL INFORMATION", ""],
|
||||||
["get <JAIL> logpath", "gets the list of the monitored files for <JAIL>"],
|
["get <JAIL> logpath", "gets the list of the monitored files for <JAIL>"],
|
||||||
["get <JAIL> logencoding <ENCODING>", "gets the <ENCODING> of the log files for <JAIL>"],
|
["get <JAIL> logencoding", "gets the encoding of the log files for <JAIL>"],
|
||||||
["get <JAIL> journalmatch", "gets the journal filter match for <JAIL>"],
|
["get <JAIL> journalmatch", "gets the journal filter match for <JAIL>"],
|
||||||
["get <JAIL> ignoreip", "gets the list of ignored IP addresses for <JAIL>"],
|
["get <JAIL> ignoreip", "gets the list of ignored IP addresses for <JAIL>"],
|
||||||
["get <JAIL> ignorecommand", "gets ignorecommand of <JAIL>"],
|
["get <JAIL> ignorecommand", "gets ignorecommand of <JAIL>"],
|
||||||
|
@ -100,15 +102,18 @@ protocol = [
|
||||||
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
|
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
|
||||||
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
|
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
|
||||||
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],
|
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],
|
||||||
["get <JAIL> addaction", "gets the last action which has been added for <JAIL>"],
|
|
||||||
["get <JAIL> actions", "gets a list of actions for <JAIL>"],
|
["get <JAIL> actions", "gets a list of actions for <JAIL>"],
|
||||||
["get <JAIL> actionstart <ACT>", "gets the start command for the action <ACT> for <JAIL>"],
|
["", "COMMAND ACTION INFORMATION",""],
|
||||||
["get <JAIL> actionstop <ACT>", "gets the stop command for the action <ACT> for <JAIL>"],
|
["get <JAIL> action <ACT> actionstart", "gets the start command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> actioncheck <ACT>", "gets the check command for the action <ACT> for <JAIL>"],
|
["get <JAIL> action <ACT> actionstop", "gets the stop command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> actionban <ACT>", "gets the ban command for the action <ACT> for <JAIL>"],
|
["get <JAIL> action <ACT> actioncheck", "gets the check command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> actionunban <ACT>", "gets the unban command for the action <ACT> for <JAIL>"],
|
["get <JAIL> action <ACT> actionban", "gets the ban command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> cinfo <ACT> <KEY>", "gets the value for <KEY> for the action <ACT> for <JAIL>"],
|
["get <JAIL> action <ACT> actionunban", "gets the unban command for the action <ACT> for <JAIL>"],
|
||||||
["get <JAIL> timeout <ACT>", "gets the command timeout in seconds for the action <ACT> for <JAIL>"],
|
["get <JAIL> action <ACT> timeout", "gets the command timeout in seconds for the action <ACT> for <JAIL>"],
|
||||||
|
["", "GENERAL ACTION INFORMATION", ""],
|
||||||
|
["get <JAIL> actionproperties <ACT>", "gets a list of properties for the action <ACT> for <JAIL>"],
|
||||||
|
["get <JAIL> actionmethods <ACT>", "gets a list of methods for the action <ACT> for <JAIL>"],
|
||||||
|
["get <JAIL> action <ACT> <PROPERTY>", "gets the value of <PROPERTY> for the action <ACT> for <JAIL>"],
|
||||||
]
|
]
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -125,12 +130,14 @@ def printFormatted():
|
||||||
print
|
print
|
||||||
firstHeading = True
|
firstHeading = True
|
||||||
first = True
|
first = True
|
||||||
for n in textwrap.wrap(m[1], WIDTH):
|
if len(m[0]) >= MARGIN:
|
||||||
|
m[1] = ' ' * WIDTH + m[1]
|
||||||
|
for n in textwrap.wrap(m[1], WIDTH, drop_whitespace=False):
|
||||||
if first:
|
if first:
|
||||||
line = ' ' * INDENT + m[0] + ' ' * (MARGIN - len(m[0])) + n
|
line = ' ' * INDENT + m[0] + ' ' * (MARGIN - len(m[0])) + n.strip()
|
||||||
first = False
|
first = False
|
||||||
else:
|
else:
|
||||||
line = ' ' * (INDENT + MARGIN) + n
|
line = ' ' * (INDENT + MARGIN) + n.strip()
|
||||||
print line
|
print line
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -23,6 +23,8 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import logging, os, subprocess, time, signal, tempfile
|
import logging, os, subprocess, time, signal, tempfile
|
||||||
import threading, re
|
import threading, re
|
||||||
|
from abc import ABCMeta
|
||||||
|
from collections import MutableMapping
|
||||||
#from subprocess import call
|
#from subprocess import call
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
|
@ -46,343 +48,477 @@ _RETCODE_HINTS = {
|
||||||
signame = dict((num, name)
|
signame = dict((num, name)
|
||||||
for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
|
for name, num in signal.__dict__.iteritems() if name.startswith("SIG"))
|
||||||
|
|
||||||
##
|
class CallingMap(MutableMapping):
|
||||||
# Execute commands.
|
"""A Mapping type which returns the result of callable values.
|
||||||
#
|
|
||||||
# This class reads the failures from the Jail queue and decide if an
|
|
||||||
# action has to be taken. A BanManager take care of the banned IP
|
|
||||||
# addresses.
|
|
||||||
|
|
||||||
class Action:
|
`CallingMap` behaves similar to a standard python dictionary,
|
||||||
|
with the exception that any values which are callable, are called
|
||||||
|
and the result is returned as the value.
|
||||||
|
No error handling is in place, such that any errors raised in the
|
||||||
|
callable will raised as usual.
|
||||||
|
Actual dictionary is stored in property `data`, and can be accessed
|
||||||
|
to obtain original callable values.
|
||||||
|
|
||||||
def __init__(self, name):
|
Attributes
|
||||||
self.__name = name
|
----------
|
||||||
self.__timeout = 60
|
data : dict
|
||||||
self.__cInfo = dict()
|
The dictionary data which can be accessed to obtain items
|
||||||
## Command executed in order to initialize the system.
|
without callable values being called.
|
||||||
self.__actionStart = ''
|
|
||||||
## Command executed when an IP address gets banned.
|
|
||||||
self.__actionBan = ''
|
|
||||||
## Command executed when an IP address gets removed.
|
|
||||||
self.__actionUnban = ''
|
|
||||||
## Command executed in order to check requirements.
|
|
||||||
self.__actionCheck = ''
|
|
||||||
## Command executed in order to stop the system.
|
|
||||||
self.__actionStop = ''
|
|
||||||
logSys.debug("Created Action")
|
|
||||||
|
|
||||||
##
|
"""
|
||||||
# Sets the action name.
|
|
||||||
#
|
|
||||||
# @param name the name of the action
|
|
||||||
|
|
||||||
def setName(self, name):
|
def __init__(self, *args, **kwargs):
|
||||||
self.__name = name
|
self.data = dict(*args, **kwargs)
|
||||||
|
|
||||||
##
|
def __getitem__(self, key):
|
||||||
# Returns the action name.
|
value = self.data[key]
|
||||||
#
|
if callable(value):
|
||||||
# @return the name of the action
|
return value()
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
def getName(self):
|
def __setitem__(self, key, value):
|
||||||
return self.__name
|
self.data[key] = value
|
||||||
|
|
||||||
##
|
def __delitem__(self, key):
|
||||||
# Sets the timeout period for commands.
|
del self.data[key]
|
||||||
#
|
|
||||||
# @param timeout timeout period in seconds
|
|
||||||
|
|
||||||
def setTimeout(self, timeout):
|
def __iter__(self):
|
||||||
self.__timeout = int(timeout)
|
return iter(self.data)
|
||||||
logSys.debug("Set action %s timeout = %i" % (self.__name, timeout))
|
|
||||||
|
|
||||||
##
|
def __len__(self):
|
||||||
# Returns the action timeout period for commands.
|
return len(self.data)
|
||||||
#
|
|
||||||
# @return the timeout period in seconds
|
|
||||||
|
|
||||||
def getTimeout(self):
|
class ActionBase(object):
|
||||||
return self.__timeout
|
"""An abstract base class for actions in Fail2Ban.
|
||||||
|
|
||||||
##
|
Action Base is a base definition of what methods need to be in
|
||||||
# Sets a "CInfo".
|
place to create a Python based action for Fail2Ban. This class can
|
||||||
#
|
be inherited from to ease implementation.
|
||||||
# CInfo are statically defined properties. They can be definied by
|
Required methods:
|
||||||
# the user and are used to set e-mail addresses, port, host or
|
- __init__(jail, name)
|
||||||
# anything that should not change during the life of the server.
|
- start()
|
||||||
#
|
- stop()
|
||||||
# @param key the property name
|
- ban(aInfo)
|
||||||
# @param value the property value
|
- unban(aInfo)
|
||||||
|
"""
|
||||||
|
__metaclass__ = ABCMeta
|
||||||
|
|
||||||
def setCInfo(self, key, value):
|
@classmethod
|
||||||
self.__cInfo[key] = value
|
def __subclasshook__(cls, C):
|
||||||
|
required = (
|
||||||
##
|
"start",
|
||||||
# Returns a "CInfo".
|
"stop",
|
||||||
#
|
"ban",
|
||||||
# @param key the property name
|
"unban",
|
||||||
|
)
|
||||||
def getCInfo(self, key):
|
for method in required:
|
||||||
return self.__cInfo[key]
|
if not callable(getattr(C, method, None)):
|
||||||
|
|
||||||
##
|
|
||||||
# Removes a "CInfo".
|
|
||||||
#
|
|
||||||
# @param key the property name
|
|
||||||
|
|
||||||
def delCInfo(self, key):
|
|
||||||
del self.__cInfo[key]
|
|
||||||
|
|
||||||
##
|
|
||||||
# Set the "start" command.
|
|
||||||
#
|
|
||||||
# @param value the command
|
|
||||||
|
|
||||||
def setActionStart(self, value):
|
|
||||||
self.__actionStart = value
|
|
||||||
logSys.debug("Set actionStart = %s" % value)
|
|
||||||
|
|
||||||
##
|
|
||||||
# Get the "start" command.
|
|
||||||
#
|
|
||||||
# @return the command
|
|
||||||
|
|
||||||
def getActionStart(self):
|
|
||||||
return self.__actionStart
|
|
||||||
|
|
||||||
##
|
|
||||||
# Executes the action "start" command.
|
|
||||||
#
|
|
||||||
# Replaces the tags in the action command with value of "cInfo"
|
|
||||||
# and executes the resulting command.
|
|
||||||
#
|
|
||||||
# @return True if the command succeeded
|
|
||||||
|
|
||||||
def execActionStart(self):
|
|
||||||
if self.__cInfo:
|
|
||||||
if not Action.substituteRecursiveTags(self.__cInfo):
|
|
||||||
logSys.error("Cinfo/definitions contain self referencing definitions and cannot be resolved")
|
|
||||||
return False
|
return False
|
||||||
startCmd = Action.replaceTag(self.__actionStart, self.__cInfo)
|
return True
|
||||||
return Action.executeCmd(startCmd, self.__timeout)
|
|
||||||
|
|
||||||
##
|
def __init__(self, jail, name):
|
||||||
# Set the "ban" command.
|
"""Initialise action.
|
||||||
#
|
|
||||||
# @param value the command
|
|
||||||
|
|
||||||
def setActionBan(self, value):
|
Called when action is created, but before the jail/actions is
|
||||||
self.__actionBan = value
|
started. This should carry out necessary methods to initialise
|
||||||
logSys.debug("Set actionBan = %s" % value)
|
the action but not "start" the action.
|
||||||
|
|
||||||
##
|
Parameters
|
||||||
# Get the "ban" command.
|
----------
|
||||||
#
|
jail : Jail
|
||||||
# @return the command
|
The jail in which the action belongs to.
|
||||||
|
name : str
|
||||||
|
Name assigned to the action.
|
||||||
|
|
||||||
def getActionBan(self):
|
Notes
|
||||||
return self.__actionBan
|
-----
|
||||||
|
Any additional arguments specified in `jail.conf` or passed
|
||||||
|
via `fail2ban-client` will be passed as keyword arguments.
|
||||||
|
"""
|
||||||
|
self._jail = jail
|
||||||
|
self._name = name
|
||||||
|
self._logSys = logging.getLogger(
|
||||||
|
'%s.%s' % (__name__, self.__class__.__name__))
|
||||||
|
|
||||||
##
|
def start(self):
|
||||||
# Executes the action "ban" command.
|
"""Executed when the jail/action is started.
|
||||||
#
|
"""
|
||||||
# @return True if the command succeeded
|
pass
|
||||||
|
|
||||||
def execActionBan(self, aInfo):
|
def stop(self):
|
||||||
return self.__processCmd(self.__actionBan, aInfo)
|
"""Executed when the jail/action is stopped.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
##
|
def ban(self, aInfo):
|
||||||
# Set the "unban" command.
|
"""Executed when a ban occurs.
|
||||||
#
|
|
||||||
# @param value the command
|
|
||||||
|
|
||||||
def setActionUnban(self, value):
|
Parameters
|
||||||
self.__actionUnban = value
|
----------
|
||||||
logSys.debug("Set actionUnban = %s" % value)
|
aInfo : dict
|
||||||
|
Dictionary which includes information in relation to
|
||||||
|
the ban.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
##
|
def unban(self, aInfo):
|
||||||
# Get the "unban" command.
|
"""Executed when a ban expires.
|
||||||
#
|
|
||||||
# @return the command
|
|
||||||
|
|
||||||
def getActionUnban(self):
|
Parameters
|
||||||
return self.__actionUnban
|
----------
|
||||||
|
aInfo : dict
|
||||||
|
Dictionary which includes information in relation to
|
||||||
|
the ban.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
##
|
class CommandAction(ActionBase):
|
||||||
# Executes the action "unban" command.
|
"""A action which executes OS shell commands.
|
||||||
#
|
|
||||||
# @return True if the command succeeded
|
|
||||||
|
|
||||||
def execActionUnban(self, aInfo):
|
This is the default type of action which Fail2Ban uses.
|
||||||
return self.__processCmd(self.__actionUnban, aInfo)
|
"""
|
||||||
|
|
||||||
##
|
def __init__(self, jail, name):
|
||||||
# Set the "check" command.
|
"""Initialise action.
|
||||||
#
|
|
||||||
# @param value the command
|
|
||||||
|
|
||||||
def setActionCheck(self, value):
|
Default sets all commands for actions as empty string, such
|
||||||
self.__actionCheck = value
|
no command is executed.
|
||||||
logSys.debug("Set actionCheck = %s" % value)
|
|
||||||
|
|
||||||
##
|
Parameters
|
||||||
# Get the "check" command.
|
----------
|
||||||
#
|
jail : Jail
|
||||||
# @return the command
|
The jail in which the action belongs to.
|
||||||
|
name : str
|
||||||
|
Name assigned to the action.
|
||||||
|
"""
|
||||||
|
|
||||||
def getActionCheck(self):
|
super(CommandAction, self).__init__(jail, name)
|
||||||
return self.__actionCheck
|
self.timeout = 60
|
||||||
|
## Command executed in order to initialize the system.
|
||||||
|
self.actionstart = ''
|
||||||
|
## Command executed when an IP address gets banned.
|
||||||
|
self.actionban = ''
|
||||||
|
## Command executed when an IP address gets removed.
|
||||||
|
self.actionunban = ''
|
||||||
|
## Command executed in order to check requirements.
|
||||||
|
self.actioncheck = ''
|
||||||
|
## Command executed in order to stop the system.
|
||||||
|
self.actionstop = ''
|
||||||
|
self._logSys.debug("Created %s" % self.__class__)
|
||||||
|
|
||||||
##
|
@classmethod
|
||||||
# Set the "stop" command.
|
def __subclasshook__(cls, C):
|
||||||
#
|
return NotImplemented # Standard checks
|
||||||
# @param value the command
|
|
||||||
|
|
||||||
def setActionStop(self, value):
|
@property
|
||||||
self.__actionStop = value
|
def timeout(self):
|
||||||
logSys.debug("Set actionStop = %s" % value)
|
"""Time out period in seconds for execution of commands.
|
||||||
|
"""
|
||||||
|
return self._timeout
|
||||||
|
|
||||||
##
|
@timeout.setter
|
||||||
# Get the "stop" command.
|
def timeout(self, timeout):
|
||||||
#
|
self._timeout = int(timeout)
|
||||||
# @return the command
|
self._logSys.debug("Set action %s timeout = %i" %
|
||||||
|
(self._name, self.timeout))
|
||||||
|
|
||||||
def getActionStop(self):
|
@property
|
||||||
return self.__actionStop
|
def _properties(self):
|
||||||
|
"""A dictionary of the actions properties.
|
||||||
|
|
||||||
##
|
This is used to subsitute "tags" in the commands.
|
||||||
# Executes the action "stop" command.
|
"""
|
||||||
#
|
return dict(
|
||||||
# Replaces the tags in the action command with value of "cInfo"
|
(key, getattr(self, key))
|
||||||
# and executes the resulting command.
|
for key in dir(self)
|
||||||
#
|
if not key.startswith("_") and not callable(getattr(self, key)))
|
||||||
# @return True if the command succeeded
|
|
||||||
|
|
||||||
def execActionStop(self):
|
@property
|
||||||
stopCmd = Action.replaceTag(self.__actionStop, self.__cInfo)
|
def actionstart(self):
|
||||||
return Action.executeCmd(stopCmd, self.__timeout)
|
"""The command executed on start of the jail/action.
|
||||||
|
"""
|
||||||
|
return self._actionstart
|
||||||
|
|
||||||
##
|
@actionstart.setter
|
||||||
# Sort out tag definitions within other tags
|
def actionstart(self, value):
|
||||||
#
|
self._actionstart = value
|
||||||
# so: becomes:
|
self._logSys.debug("Set actionstart = %s" % value)
|
||||||
# a = 3 a = 3
|
|
||||||
# b = <a>_3 b = 3_3
|
def start(self):
|
||||||
# @param tags, a dictionary
|
"""Executes the "actionstart" command.
|
||||||
# @returns tags altered or False if there is a recursive definition
|
|
||||||
#@staticmethod
|
Replace the tags in the action command with actions properties
|
||||||
|
and executes the resulting command.
|
||||||
|
"""
|
||||||
|
if (self._properties and
|
||||||
|
not self.substituteRecursiveTags(self._properties)):
|
||||||
|
self._logSys.error(
|
||||||
|
"properties contain self referencing definitions "
|
||||||
|
"and cannot be resolved")
|
||||||
|
raise RuntimeError("Error starting action")
|
||||||
|
startCmd = self.replaceTag(self.actionstart, self._properties)
|
||||||
|
if not self.executeCmd(startCmd, self.timeout):
|
||||||
|
raise RuntimeError("Error starting action")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actionban(self):
|
||||||
|
"""The command used when a ban occurs.
|
||||||
|
"""
|
||||||
|
return self._actionban
|
||||||
|
|
||||||
|
@actionban.setter
|
||||||
|
def actionban(self, value):
|
||||||
|
self._actionban = value
|
||||||
|
self._logSys.debug("Set actionban = %s" % value)
|
||||||
|
|
||||||
|
def ban(self, aInfo):
|
||||||
|
"""Executes the "actionban" command.
|
||||||
|
|
||||||
|
Replaces the tags in the action command with actions properties
|
||||||
|
and ban information, and executes the resulting command.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
aInfo : dict
|
||||||
|
Dictionary which includes information in relation to
|
||||||
|
the ban.
|
||||||
|
"""
|
||||||
|
if not self._processCmd(self.actionban, aInfo):
|
||||||
|
raise RuntimeError("Error banning %(ip)s" % aInfo)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actionunban(self):
|
||||||
|
"""The command used when an unban occurs.
|
||||||
|
"""
|
||||||
|
return self._actionunban
|
||||||
|
|
||||||
|
@actionunban.setter
|
||||||
|
def actionunban(self, value):
|
||||||
|
self._actionunban = value
|
||||||
|
self._logSys.debug("Set actionunban = %s" % value)
|
||||||
|
|
||||||
|
def unban(self, aInfo):
|
||||||
|
"""Executes the "actionunban" command.
|
||||||
|
|
||||||
|
Replaces the tags in the action command with actions properties
|
||||||
|
and ban information, and executes the resulting command.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
aInfo : dict
|
||||||
|
Dictionary which includes information in relation to
|
||||||
|
the ban.
|
||||||
|
"""
|
||||||
|
if not self._processCmd(self.actionunban, aInfo):
|
||||||
|
raise RuntimeError("Error unbanning %(ip)s" % aInfo)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actioncheck(self):
|
||||||
|
"""The command used to check the environment.
|
||||||
|
|
||||||
|
This is used prior to a ban taking place to ensure the
|
||||||
|
environment is appropriate. If this check fails, `stop` and
|
||||||
|
`start` is executed prior to the check being called again.
|
||||||
|
"""
|
||||||
|
return self._actioncheck
|
||||||
|
|
||||||
|
@actioncheck.setter
|
||||||
|
def actioncheck(self, value):
|
||||||
|
self._actioncheck = value
|
||||||
|
self._logSys.debug("Set actioncheck = %s" % value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actionstop(self):
|
||||||
|
"""The command executed when the jail/actions stops.
|
||||||
|
"""
|
||||||
|
return self._actionstop
|
||||||
|
|
||||||
|
@actionstop.setter
|
||||||
|
def actionstop(self, value):
|
||||||
|
self._actionstop = value
|
||||||
|
self._logSys.debug("Set actionstop = %s" % value)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Executes the "actionstop" command.
|
||||||
|
|
||||||
|
Replaces the tags in the action command with actions properties
|
||||||
|
and executes the resulting command.
|
||||||
|
"""
|
||||||
|
stopCmd = self.replaceTag(self.actionstop, self._properties)
|
||||||
|
if not self.executeCmd(stopCmd, self.timeout):
|
||||||
|
raise RuntimeError("Error stopping action")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
def substituteRecursiveTags(tags):
|
def substituteRecursiveTags(tags):
|
||||||
|
"""Sort out tag definitions within other tags.
|
||||||
|
|
||||||
|
so: becomes:
|
||||||
|
a = 3 a = 3
|
||||||
|
b = <a>_3 b = 3_3
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
tags : dict
|
||||||
|
Dictionary of tags(keys) and their values.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict
|
||||||
|
Dictionary of tags(keys) and their values, with tags
|
||||||
|
within the values recursively replaced.
|
||||||
|
"""
|
||||||
t = re.compile(r'<([^ >]+)>')
|
t = re.compile(r'<([^ >]+)>')
|
||||||
for tag, value in tags.iteritems():
|
for tag, value in tags.iteritems():
|
||||||
value = str(value)
|
value = str(value)
|
||||||
m = t.search(value)
|
m = t.search(value)
|
||||||
|
done = []
|
||||||
|
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
|
||||||
while m:
|
while m:
|
||||||
if m.group(1) == tag:
|
found_tag = m.group(1)
|
||||||
|
#logSys.log(5, 'found: %s' % found_tag)
|
||||||
|
if found_tag == tag or found_tag in done:
|
||||||
# recursive definitions are bad
|
# recursive definitions are bad
|
||||||
|
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if tags.has_key(m.group(1)):
|
if tags.has_key(found_tag):
|
||||||
value = value[0:m.start()] + tags[m.group(1)] + value[m.end():]
|
value = value.replace('<%s>' % found_tag , tags[found_tag])
|
||||||
|
#logSys.log(5, 'value now: %s' % value)
|
||||||
|
done.append(found_tag)
|
||||||
m = t.search(value, m.start())
|
m = t.search(value, m.start())
|
||||||
else:
|
else:
|
||||||
# Missing tags are ok so we just continue on searching.
|
# Missing tags are ok so we just continue on searching.
|
||||||
# cInfo can contain aInfo elements like <HOST> and valid shell
|
# cInfo can contain aInfo elements like <HOST> and valid shell
|
||||||
# constructs like <STDIN>.
|
# constructs like <STDIN>.
|
||||||
m = t.search(value, m.start() + 1)
|
m = t.search(value, m.start() + 1)
|
||||||
|
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
|
||||||
tags[tag] = value
|
tags[tag] = value
|
||||||
return tags
|
return tags
|
||||||
substituteRecursiveTags = staticmethod(substituteRecursiveTags)
|
|
||||||
|
|
||||||
#@staticmethod
|
@staticmethod
|
||||||
def escapeTag(tag):
|
def escapeTag(value):
|
||||||
|
"""Escape characters which may be used for command injection.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
value : str
|
||||||
|
A string of which characters will be escaped.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
`value` with certain characters escaped.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
The following characters are escaped::
|
||||||
|
|
||||||
|
\\#&;`|*?~<>^()[]{}$'"
|
||||||
|
|
||||||
|
"""
|
||||||
for c in '\\#&;`|*?~<>^()[]{}$\'"':
|
for c in '\\#&;`|*?~<>^()[]{}$\'"':
|
||||||
if c in tag:
|
if c in value:
|
||||||
tag = tag.replace(c, '\\' + c)
|
value = value.replace(c, '\\' + c)
|
||||||
return tag
|
return value
|
||||||
escapeTag = staticmethod(escapeTag)
|
|
||||||
|
|
||||||
##
|
@classmethod
|
||||||
# Replaces tags in query with property values in aInfo.
|
def replaceTag(cls, query, aInfo):
|
||||||
#
|
"""Replaces tags in `query` with property values.
|
||||||
# @param query the query string with tags
|
|
||||||
# @param aInfo the properties
|
|
||||||
# @return a string
|
|
||||||
|
|
||||||
#@staticmethod
|
Parameters
|
||||||
def replaceTag(query, aInfo):
|
----------
|
||||||
""" Replace tags in query
|
query : str
|
||||||
|
String with tags.
|
||||||
|
aInfo : dict
|
||||||
|
Tags(keys) and associated values for substitution in query.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
str
|
||||||
|
`query` string with tags replaced.
|
||||||
"""
|
"""
|
||||||
string = query
|
string = query
|
||||||
for tag, value in aInfo.iteritems():
|
for tag in aInfo:
|
||||||
if "<%s>" % tag in query:
|
if "<%s>" % tag in query:
|
||||||
if callable(value):
|
value = str(aInfo[tag]) # assure string
|
||||||
value = value()
|
|
||||||
value = str(value) # assure string
|
|
||||||
if tag.endswith('matches'):
|
if tag.endswith('matches'):
|
||||||
# That one needs to be escaped since its content is
|
# That one needs to be escaped since its content is
|
||||||
# out of our control
|
# out of our control
|
||||||
value = Action.escapeTag(value)
|
value = cls.escapeTag(value)
|
||||||
string = string.replace('<' + tag + '>', value)
|
string = string.replace('<' + tag + '>', value)
|
||||||
# New line
|
# New line
|
||||||
string = string.replace("<br>", '\n')
|
string = string.replace("<br>", '\n')
|
||||||
return string
|
return string
|
||||||
replaceTag = staticmethod(replaceTag)
|
|
||||||
|
|
||||||
##
|
def _processCmd(self, cmd, aInfo = None):
|
||||||
# Executes a command with preliminary checks and substitutions.
|
"""Executes a command with preliminary checks and substitutions.
|
||||||
#
|
|
||||||
# Before executing any commands, executes the "check" command first
|
|
||||||
# in order to check if pre-requirements are met. If this check fails,
|
|
||||||
# it tries to restore a sane environment before executing the real
|
|
||||||
# command.
|
|
||||||
# Replaces "aInfo" and "cInfo" in the query too.
|
|
||||||
#
|
|
||||||
# @param cmd The command to execute
|
|
||||||
# @param aInfo Dynamic properties
|
|
||||||
# @return True if the command succeeded
|
|
||||||
|
|
||||||
def __processCmd(self, cmd, aInfo = None):
|
Before executing any commands, executes the "check" command first
|
||||||
""" Executes an OS command.
|
in order to check if pre-requirements are met. If this check fails,
|
||||||
|
it tries to restore a sane environment before executing the real
|
||||||
|
command.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
cmd : str
|
||||||
|
The command to execute.
|
||||||
|
aInfo : dictionary
|
||||||
|
Dynamic properties.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if the command succeeded.
|
||||||
"""
|
"""
|
||||||
if cmd == "":
|
if cmd == "":
|
||||||
logSys.debug("Nothing to do")
|
self._logSys.debug("Nothing to do")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
checkCmd = Action.replaceTag(self.__actionCheck, self.__cInfo)
|
checkCmd = self.replaceTag(self.actioncheck, self._properties)
|
||||||
if not Action.executeCmd(checkCmd, self.__timeout):
|
if not self.executeCmd(checkCmd, self.timeout):
|
||||||
logSys.error("Invariant check failed. Trying to restore a sane" +
|
self._logSys.error(
|
||||||
" environment")
|
"Invariant check failed. Trying to restore a sane environment")
|
||||||
self.execActionStop()
|
self.stop()
|
||||||
self.execActionStart()
|
self.start()
|
||||||
if not Action.executeCmd(checkCmd, self.__timeout):
|
if not self.executeCmd(checkCmd, self.timeout):
|
||||||
logSys.fatal("Unable to restore environment")
|
self._logSys.fatal("Unable to restore environment")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Replace tags
|
# Replace tags
|
||||||
if not aInfo is None:
|
if not aInfo is None:
|
||||||
realCmd = Action.replaceTag(cmd, aInfo)
|
realCmd = self.replaceTag(cmd, aInfo)
|
||||||
else:
|
else:
|
||||||
realCmd = cmd
|
realCmd = cmd
|
||||||
|
|
||||||
# Replace static fields
|
# Replace static fields
|
||||||
realCmd = Action.replaceTag(realCmd, self.__cInfo)
|
realCmd = self.replaceTag(realCmd, self._properties)
|
||||||
|
|
||||||
return Action.executeCmd(realCmd, self.__timeout)
|
return self.executeCmd(realCmd, self.timeout)
|
||||||
|
|
||||||
##
|
@staticmethod
|
||||||
# Executes a command.
|
|
||||||
#
|
|
||||||
# We need a shell here because commands are mainly shell script. They
|
|
||||||
# contain pipe, redirection, etc.
|
|
||||||
#
|
|
||||||
# @todo Force the use of bash!?
|
|
||||||
# @todo Kill the command after a given timeout
|
|
||||||
#
|
|
||||||
# @param realCmd the command to execute
|
|
||||||
# @return True if the command succeeded
|
|
||||||
|
|
||||||
#@staticmethod
|
|
||||||
def executeCmd(realCmd, timeout=60):
|
def executeCmd(realCmd, timeout=60):
|
||||||
|
"""Executes a command.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
realCmd : str
|
||||||
|
The command to execute.
|
||||||
|
timeout : int
|
||||||
|
The time out in seconds for the command.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if the command succeeded.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
OSError
|
||||||
|
If command fails to be executed.
|
||||||
|
RuntimeError
|
||||||
|
If command execution times out.
|
||||||
|
"""
|
||||||
logSys.debug(realCmd)
|
logSys.debug(realCmd)
|
||||||
if not realCmd:
|
if not realCmd:
|
||||||
logSys.debug("Nothing to do")
|
logSys.debug("Nothing to do")
|
||||||
|
@ -412,7 +548,6 @@ class Action:
|
||||||
retcode = popen.poll()
|
retcode = popen.poll()
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
logSys.error("%s -- failed with %s" % (realCmd, e))
|
logSys.error("%s -- failed with %s" % (realCmd, e))
|
||||||
return False
|
|
||||||
finally:
|
finally:
|
||||||
_cmd_lock.release()
|
_cmd_lock.release()
|
||||||
|
|
||||||
|
@ -440,5 +575,5 @@ class Action:
|
||||||
logSys.info("HINT on %i: %s"
|
logSys.info("HINT on %i: %s"
|
||||||
% (retcode, msg % locals()))
|
% (retcode, msg % locals()))
|
||||||
return False
|
return False
|
||||||
executeCmd = staticmethod(executeCmd)
|
raise RuntimeError("Command execution failed: %s" % realCmd)
|
||||||
|
|
||||||
|
|
|
@ -24,93 +24,112 @@ __author__ = "Cyril Jaquier"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from banmanager import BanManager
|
|
||||||
from jailthread import JailThread
|
|
||||||
from action import Action
|
|
||||||
from mytime import MyTime
|
|
||||||
import time, logging
|
import time, logging
|
||||||
|
import os
|
||||||
|
import imp
|
||||||
|
from collections import Mapping
|
||||||
|
|
||||||
|
from .banmanager import BanManager
|
||||||
|
from .jailthread import JailThread
|
||||||
|
from .action import ActionBase, CommandAction, CallingMap
|
||||||
|
from .mytime import MyTime
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
##
|
class Actions(JailThread, Mapping):
|
||||||
# Execute commands.
|
"""Handles jail actions.
|
||||||
#
|
|
||||||
# This class reads the failures from the Jail queue and decide if an
|
|
||||||
# action has to be taken. A BanManager take care of the banned IP
|
|
||||||
# addresses.
|
|
||||||
|
|
||||||
class Actions(JailThread):
|
This class handles the actions of the jail. Creation, deletion or to
|
||||||
|
actions must be done through this class. This class is based on the
|
||||||
##
|
Mapping type, and the `add` method must be used to add new actions.
|
||||||
# Constructor.
|
This class also starts and stops the actions, and fetches bans from
|
||||||
#
|
the jail executing these bans via the actions.
|
||||||
# Initialize the filter object with default values.
|
"""
|
||||||
# @param jail the jail object
|
|
||||||
|
|
||||||
def __init__(self, jail):
|
def __init__(self, jail):
|
||||||
|
"""Initialise an empty Actions instance.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
jail: Jail
|
||||||
|
The jail of which the actions belongs to.
|
||||||
|
"""
|
||||||
JailThread.__init__(self)
|
JailThread.__init__(self)
|
||||||
## The jail which contains this action.
|
## The jail which contains this action.
|
||||||
self.jail = jail
|
self._jail = jail
|
||||||
self.__actions = list()
|
self._actions = dict()
|
||||||
## The ban manager.
|
## The ban manager.
|
||||||
self.__banManager = BanManager()
|
self.__banManager = BanManager()
|
||||||
|
|
||||||
##
|
def add(self, name, pythonModule=None, initOpts=None):
|
||||||
# Adds an action.
|
"""Adds a new action.
|
||||||
#
|
|
||||||
# @param name The action name
|
|
||||||
|
|
||||||
def addAction(self, name):
|
Add a new action if not already present, defaulting to standard
|
||||||
|
`CommandAction`, or specified Python module.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name : str
|
||||||
|
The name of the action.
|
||||||
|
pythonModule : str, optional
|
||||||
|
Path to Python file which must contain `Action` class.
|
||||||
|
Default None, which means `CommandAction` is used.
|
||||||
|
initOpts : dict, optional
|
||||||
|
Options for Python Action, used as keyword arguments for
|
||||||
|
initialisation. Default None.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
If action name already exists.
|
||||||
|
RuntimeError
|
||||||
|
If external Python module does not have `Action` class
|
||||||
|
or does not implement necessary methods as per `ActionBase`
|
||||||
|
abstract class.
|
||||||
|
"""
|
||||||
# Check is action name already exists
|
# Check is action name already exists
|
||||||
if name in [action.getName() for action in self.__actions]:
|
if name in self._actions:
|
||||||
raise ValueError("Action %s already exists" % name)
|
raise ValueError("Action %s already exists" % name)
|
||||||
action = Action(name)
|
if pythonModule is None:
|
||||||
self.__actions.append(action)
|
action = CommandAction(self._jail, name)
|
||||||
|
else:
|
||||||
|
pythonModuleName = os.path.basename(pythonModule.strip(".py"))
|
||||||
|
customActionModule = imp.load_source(
|
||||||
|
pythonModuleName, pythonModule)
|
||||||
|
if not hasattr(customActionModule, "Action"):
|
||||||
|
raise RuntimeError(
|
||||||
|
"%s module does not have 'Action' class" % pythonModule)
|
||||||
|
elif not issubclass(customActionModule.Action, ActionBase):
|
||||||
|
raise RuntimeError(
|
||||||
|
"%s module %s does not implement required methods" % (
|
||||||
|
pythonModule, customActionModule.Action.__name__))
|
||||||
|
action = customActionModule.Action(self._jail, name, **initOpts)
|
||||||
|
self._actions[name] = action
|
||||||
|
|
||||||
##
|
def __getitem__(self, name):
|
||||||
# Removes an action.
|
try:
|
||||||
#
|
return self._actions[name]
|
||||||
# @param name The action name
|
except KeyError:
|
||||||
|
|
||||||
def delAction(self, name):
|
|
||||||
for action in self.__actions:
|
|
||||||
if action.getName() == name:
|
|
||||||
self.__actions.remove(action)
|
|
||||||
return
|
|
||||||
raise KeyError("Invalid Action name: %s" % name)
|
raise KeyError("Invalid Action name: %s" % name)
|
||||||
|
|
||||||
##
|
def __delitem__(self, name):
|
||||||
# Returns an action.
|
try:
|
||||||
#
|
del self._actions[name]
|
||||||
# Raises a KeyError exception if the action does not exist.
|
except KeyError:
|
||||||
#
|
raise KeyError("Invalid Action name: %s" % name)
|
||||||
# @param name the action name
|
|
||||||
# @return the action
|
|
||||||
|
|
||||||
def getAction(self, name):
|
def __iter__(self):
|
||||||
for action in self.__actions:
|
return iter(self._actions)
|
||||||
if action.getName() == name:
|
|
||||||
return action
|
|
||||||
raise KeyError("Invalid Action name")
|
|
||||||
|
|
||||||
##
|
def __len__(self):
|
||||||
# Returns the last defined action.
|
return len(self._actions)
|
||||||
#
|
|
||||||
# @return The last defined action.
|
|
||||||
|
|
||||||
def getLastAction(self):
|
def __eq__(self, other): # Required for Threading
|
||||||
action = self.__actions.pop()
|
return False
|
||||||
self.__actions.append(action)
|
|
||||||
return action
|
|
||||||
|
|
||||||
##
|
def __hash__(self): # Required for Threading
|
||||||
# Returns the list of actions
|
return id(self)
|
||||||
#
|
|
||||||
# @return list of actions
|
|
||||||
|
|
||||||
def getActions(self):
|
|
||||||
return self.__actions
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Set the ban time.
|
# Set the ban time.
|
||||||
|
@ -129,33 +148,51 @@ class Actions(JailThread):
|
||||||
def getBanTime(self):
|
def getBanTime(self):
|
||||||
return self.__banManager.getBanTime()
|
return self.__banManager.getBanTime()
|
||||||
|
|
||||||
##
|
|
||||||
# Remove a banned IP now, rather than waiting for it to expire, even if set to never expire.
|
|
||||||
#
|
|
||||||
# @return the IP string or 'None' if not unbanned.
|
|
||||||
def removeBannedIP(self, ip):
|
def removeBannedIP(self, ip):
|
||||||
|
"""Removes banned IP calling actions' unban method
|
||||||
|
|
||||||
|
Remove a banned IP now, rather than waiting for it to expire,
|
||||||
|
even if set to never expire.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
ip : str
|
||||||
|
The IP address to unban
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
If `ip` is not banned
|
||||||
|
"""
|
||||||
# Find the ticket with the IP.
|
# Find the ticket with the IP.
|
||||||
ticket = self.__banManager.getTicketByIP(ip)
|
ticket = self.__banManager.getTicketByIP(ip)
|
||||||
if ticket is not None:
|
if ticket is not None:
|
||||||
# Unban the IP.
|
# Unban the IP.
|
||||||
self.__unBan(ticket)
|
self.__unBan(ticket)
|
||||||
return ip
|
else:
|
||||||
raise ValueError("IP %s is not banned" % ip)
|
raise ValueError("IP %s is not banned" % ip)
|
||||||
|
|
||||||
##
|
|
||||||
# Main loop.
|
|
||||||
#
|
|
||||||
# This function is the main loop of the thread. It checks the Jail
|
|
||||||
# queue and executes commands when an IP address is banned.
|
|
||||||
# @return True when the thread exits nicely
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
"""Main loop for Threading.
|
||||||
|
|
||||||
|
This function is the main loop of the thread. It checks the jail
|
||||||
|
queue and executes commands when an IP address is banned.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True when the thread exits nicely.
|
||||||
|
"""
|
||||||
self.setActive(True)
|
self.setActive(True)
|
||||||
for action in self.__actions:
|
for name, action in self._actions.iteritems():
|
||||||
action.execActionStart()
|
try:
|
||||||
|
action.start()
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error("Failed to start jail '%s' action '%s': %s",
|
||||||
|
self._jail.getName(), name, e)
|
||||||
while self._isActive():
|
while self._isActive():
|
||||||
if not self.getIdle():
|
if not self.getIdle():
|
||||||
#logSys.debug(self.jail.getName() + ": action")
|
#logSys.debug(self._jail.getName() + ": action")
|
||||||
ret = self.__checkBan()
|
ret = self.__checkBan()
|
||||||
if not ret:
|
if not ret:
|
||||||
self.__checkUnBan()
|
self.__checkUnBan()
|
||||||
|
@ -163,94 +200,116 @@ class Actions(JailThread):
|
||||||
else:
|
else:
|
||||||
time.sleep(self.getSleepTime())
|
time.sleep(self.getSleepTime())
|
||||||
self.__flushBan()
|
self.__flushBan()
|
||||||
for action in self.__actions:
|
for name, action in self._actions.iteritems():
|
||||||
action.execActionStop()
|
try:
|
||||||
logSys.debug(self.jail.getName() + ": action terminated")
|
action.stop()
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error("Failed to stop jail '%s' action '%s': %s",
|
||||||
|
self._jail.getName(), name, e)
|
||||||
|
logSys.debug(self._jail.getName() + ": action terminated")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
##
|
|
||||||
# Check for IP address to ban.
|
|
||||||
#
|
|
||||||
# Look in the Jail queue for FailTicket. If a ticket is available,
|
|
||||||
# it executes the "ban" command and add a ticket to the BanManager.
|
|
||||||
# @return True if an IP address get banned
|
|
||||||
|
|
||||||
def __checkBan(self):
|
def __checkBan(self):
|
||||||
ticket = self.jail.getFailTicket()
|
"""Check for IP address to ban.
|
||||||
|
|
||||||
|
Look in the jail queue for FailTicket. If a ticket is available,
|
||||||
|
it executes the "ban" command and adds a ticket to the BanManager.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
bool
|
||||||
|
True if an IP address get banned.
|
||||||
|
"""
|
||||||
|
ticket = self._jail.getFailTicket()
|
||||||
if ticket != False:
|
if ticket != False:
|
||||||
aInfo = dict()
|
aInfo = CallingMap()
|
||||||
bTicket = BanManager.createBanTicket(ticket)
|
bTicket = BanManager.createBanTicket(ticket)
|
||||||
aInfo["ip"] = bTicket.getIP()
|
aInfo["ip"] = bTicket.getIP()
|
||||||
aInfo["failures"] = bTicket.getAttempt()
|
aInfo["failures"] = bTicket.getAttempt()
|
||||||
aInfo["time"] = bTicket.getTime()
|
aInfo["time"] = bTicket.getTime()
|
||||||
aInfo["matches"] = "\n".join(bTicket.getMatches())
|
aInfo["matches"] = "\n".join(bTicket.getMatches())
|
||||||
if self.jail.getDatabase() is not None:
|
if self._jail.getDatabase() is not None:
|
||||||
aInfo["ipmatches"] = lambda: "\n".join(
|
aInfo["ipmatches"] = lambda: "\n".join(
|
||||||
self.jail.getDatabase().getBansMerged(
|
self._jail.getDatabase().getBansMerged(
|
||||||
ip=bTicket.getIP()).getMatches())
|
ip=bTicket.getIP()).getMatches())
|
||||||
aInfo["ipjailmatches"] = lambda: "\n".join(
|
aInfo["ipjailmatches"] = lambda: "\n".join(
|
||||||
self.jail.getDatabase().getBansMerged(
|
self._jail.getDatabase().getBansMerged(
|
||||||
ip=bTicket.getIP(), jail=self.jail).getMatches())
|
ip=bTicket.getIP(), jail=self._jail).getMatches())
|
||||||
aInfo["ipfailures"] = lambda: "\n".join(
|
aInfo["ipfailures"] = lambda: "\n".join(
|
||||||
self.jail.getDatabase().getBansMerged(
|
self._jail.getDatabase().getBansMerged(
|
||||||
ip=bTicket.getIP()).getAttempt())
|
ip=bTicket.getIP()).getAttempt())
|
||||||
aInfo["ipjailfailures"] = lambda: "\n".join(
|
aInfo["ipjailfailures"] = lambda: "\n".join(
|
||||||
self.jail.getDatabase().getBansMerged(
|
self._jail.getDatabase().getBansMerged(
|
||||||
ip=bTicket.getIP(), jail=self.jail).getAttempt())
|
ip=bTicket.getIP(), jail=self._jail).getAttempt())
|
||||||
if self.__banManager.addBanTicket(bTicket):
|
if self.__banManager.addBanTicket(bTicket):
|
||||||
logSys.warning("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"]))
|
logSys.warning("[%s] Ban %s" % (self._jail.getName(), aInfo["ip"]))
|
||||||
for action in self.__actions:
|
for name, action in self._actions.iteritems():
|
||||||
action.execActionBan(aInfo)
|
try:
|
||||||
|
action.ban(aInfo)
|
||||||
|
except Exception as e:
|
||||||
|
logSys.error(
|
||||||
|
"Failed to execute ban jail '%s' action '%s': %s",
|
||||||
|
self._jail.getName(), name, e)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logSys.info("[%s] %s already banned" % (self.jail.getName(),
|
logSys.info("[%s] %s already banned" % (self._jail.getName(),
|
||||||
aInfo["ip"]))
|
aInfo["ip"]))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
##
|
|
||||||
# Check for IP address to unban.
|
|
||||||
#
|
|
||||||
# Unban IP address which are outdated.
|
|
||||||
|
|
||||||
def __checkUnBan(self):
|
def __checkUnBan(self):
|
||||||
|
"""Check for IP address to unban.
|
||||||
|
|
||||||
|
Unban IP addresses which are outdated.
|
||||||
|
"""
|
||||||
for ticket in self.__banManager.unBanList(MyTime.time()):
|
for ticket in self.__banManager.unBanList(MyTime.time()):
|
||||||
self.__unBan(ticket)
|
self.__unBan(ticket)
|
||||||
|
|
||||||
##
|
|
||||||
# Flush the ban list.
|
|
||||||
#
|
|
||||||
# Unban all IP address which are still in the banning list.
|
|
||||||
|
|
||||||
def __flushBan(self):
|
def __flushBan(self):
|
||||||
|
"""Flush the ban list.
|
||||||
|
|
||||||
|
Unban all IP address which are still in the banning list.
|
||||||
|
"""
|
||||||
logSys.debug("Flush ban list")
|
logSys.debug("Flush ban list")
|
||||||
for ticket in self.__banManager.flushBanList():
|
for ticket in self.__banManager.flushBanList():
|
||||||
self.__unBan(ticket)
|
self.__unBan(ticket)
|
||||||
|
|
||||||
##
|
|
||||||
# Unbans host corresponding to the ticket.
|
|
||||||
#
|
|
||||||
# Executes the actions in order to unban the host given in the
|
|
||||||
# ticket.
|
|
||||||
|
|
||||||
def __unBan(self, ticket):
|
def __unBan(self, ticket):
|
||||||
|
"""Unbans host corresponding to the ticket.
|
||||||
|
|
||||||
|
Executes the actions in order to unban the host given in the
|
||||||
|
ticket.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
ticket : FailTicket
|
||||||
|
Ticket of failures of which to unban
|
||||||
|
"""
|
||||||
aInfo = dict()
|
aInfo = dict()
|
||||||
aInfo["ip"] = ticket.getIP()
|
aInfo["ip"] = ticket.getIP()
|
||||||
aInfo["failures"] = ticket.getAttempt()
|
aInfo["failures"] = ticket.getAttempt()
|
||||||
aInfo["time"] = ticket.getTime()
|
aInfo["time"] = ticket.getTime()
|
||||||
aInfo["matches"] = "".join(ticket.getMatches())
|
aInfo["matches"] = "".join(ticket.getMatches())
|
||||||
logSys.warning("[%s] Unban %s" % (self.jail.getName(), aInfo["ip"]))
|
logSys.warning("[%s] Unban %s" % (self._jail.getName(), aInfo["ip"]))
|
||||||
for action in self.__actions:
|
for name, action in self._actions.iteritems():
|
||||||
action.execActionUnban(aInfo)
|
try:
|
||||||
|
action.unban(aInfo)
|
||||||
|
except Exception as e:
|
||||||
##
|
logSys.error(
|
||||||
# Get the status of the filter.
|
"Failed to execute unban jail '%s' action '%s': %s",
|
||||||
#
|
self._jail.getName(), name, e)
|
||||||
# Get some informations about the filter state such as the total
|
|
||||||
# number of failures.
|
|
||||||
# @return a list with tuple
|
|
||||||
|
|
||||||
def status(self):
|
def status(self):
|
||||||
|
"""Get the status of the filter.
|
||||||
|
|
||||||
|
Get some informations about the filter state such as the total
|
||||||
|
number of failures.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
list
|
||||||
|
List of tuple pairs, each containing a description and value
|
||||||
|
for general status information.
|
||||||
|
"""
|
||||||
ret = [("Currently banned", self.__banManager.size()),
|
ret = [("Currently banned", self.__banManager.size()),
|
||||||
("Total banned", self.__banManager.getBanTotal()),
|
("Total banned", self.__banManager.getBanTotal()),
|
||||||
("IP list", self.__banManager.getBanList())]
|
("IP list", self.__banManager.getBanList())]
|
||||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
||||||
from pickle import dumps, loads, HIGHEST_PROTOCOL
|
from pickle import dumps, loads, HIGHEST_PROTOCOL
|
||||||
import asyncore, asynchat, socket, os, logging, sys, traceback, fcntl
|
import asyncore, asynchat, socket, os, logging, sys, traceback, fcntl
|
||||||
|
|
||||||
from fail2ban import helpers
|
from .. import helpers
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -24,10 +24,11 @@ __author__ = "Cyril Jaquier"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from ticket import BanTicket
|
|
||||||
from threading import Lock
|
|
||||||
from mytime import MyTime
|
|
||||||
import logging
|
import logging
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
from .ticket import BanTicket
|
||||||
|
from .mytime import MyTime
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -29,8 +29,8 @@ import json
|
||||||
import locale
|
import locale
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from fail2ban.server.mytime import MyTime
|
from .mytime import MyTime
|
||||||
from fail2ban.server.ticket import FailTicket
|
from .ticket import FailTicket
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -22,10 +22,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import sys, time, logging
|
import sys, time, logging
|
||||||
|
|
||||||
from datetemplate import DatePatternRegex, DateTai64n, DateEpoch, DateISO8601
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
|
from .datetemplate import DatePatternRegex, DateTai64n, DateEpoch, DateISO8601
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,13 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import re, time, calendar
|
import re, time, calendar
|
||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from mytime import MyTime
|
from .mytime import MyTime
|
||||||
import iso8601
|
from . import iso8601
|
||||||
|
|
||||||
import logging
|
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,12 @@ __author__ = "Cyril Jaquier"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from faildata import FailData
|
|
||||||
from ticket import FailTicket
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from .faildata import FailData
|
||||||
|
from .ticket import FailTicket
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -21,18 +21,17 @@ __author__ = "Cyril Jaquier and Fail2Ban Contributors"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from failmanager import FailManagerEmpty
|
|
||||||
from failmanager import FailManager
|
|
||||||
from ticket import FailTicket
|
|
||||||
from jailthread import JailThread
|
|
||||||
from datedetector import DateDetector
|
|
||||||
from datetemplate import DatePatternRegex, DateISO8601, DateEpoch, DateTai64n
|
|
||||||
from mytime import MyTime
|
|
||||||
from failregex import FailRegex, Regex, RegexException
|
|
||||||
from action import Action
|
|
||||||
|
|
||||||
import logging, re, os, fcntl, time, sys, locale, codecs
|
import logging, re, os, fcntl, time, sys, locale, codecs
|
||||||
|
|
||||||
|
from .failmanager import FailManagerEmpty, FailManager
|
||||||
|
from .ticket import FailTicket
|
||||||
|
from .jailthread import JailThread
|
||||||
|
from .datedetector import DateDetector
|
||||||
|
from .datetemplate import DatePatternRegex, DateISO8601, DateEpoch, DateTai64n
|
||||||
|
from .mytime import MyTime
|
||||||
|
from .failregex import FailRegex, Regex, RegexException
|
||||||
|
from .action import CommandAction
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -378,9 +377,9 @@ class Filter(JailThread):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if self.__ignoreCommand:
|
if self.__ignoreCommand:
|
||||||
command = Action.replaceTag(self.__ignoreCommand, { 'ip': ip } )
|
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
|
||||||
logSys.debug('ignore command: ' + command)
|
logSys.debug('ignore command: ' + command)
|
||||||
return Action.executeCmd(command)
|
return CommandAction.executeCmd(command)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -23,11 +23,13 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from failmanager import FailManagerEmpty
|
import time, logging, fcntl
|
||||||
from filter import FileFilter
|
|
||||||
from mytime import MyTime
|
|
||||||
|
|
||||||
import time, logging, gamin, fcntl
|
import gamin
|
||||||
|
|
||||||
|
from .failmanager import FailManagerEmpty
|
||||||
|
from .filter import FileFilter
|
||||||
|
from .mytime import MyTime
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
|
@ -24,12 +24,12 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier; 2012 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier; 2012 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from failmanager import FailManagerEmpty
|
|
||||||
from filter import FileFilter
|
|
||||||
from mytime import MyTime
|
|
||||||
|
|
||||||
import time, logging, os
|
import time, logging, os
|
||||||
|
|
||||||
|
from .failmanager import FailManagerEmpty
|
||||||
|
from .filter import FileFilter
|
||||||
|
from .mytime import MyTime
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,12 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2012 Lee Clemens, 2012 Y
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import time, logging, pyinotify
|
import time, logging, pyinotify
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
from os.path import dirname, sep as pathsep
|
from os.path import dirname, sep as pathsep
|
||||||
|
|
||||||
from failmanager import FailManagerEmpty
|
from .failmanager import FailManagerEmpty
|
||||||
from filter import FileFilter
|
from .filter import FileFilter
|
||||||
from mytime import MyTime
|
from .mytime import MyTime
|
||||||
|
|
||||||
|
|
||||||
if not hasattr(pyinotify, '__version__') \
|
if not hasattr(pyinotify, '__version__') \
|
||||||
|
|
|
@ -29,9 +29,9 @@ from systemd import journal
|
||||||
if LooseVersion(getattr(journal, '__version__', "0")) < '204':
|
if LooseVersion(getattr(journal, '__version__', "0")) < '204':
|
||||||
raise ImportError("Fail2Ban requires systemd >= 204")
|
raise ImportError("Fail2Ban requires systemd >= 204")
|
||||||
|
|
||||||
from failmanager import FailManagerEmpty
|
from .failmanager import FailManagerEmpty
|
||||||
from filter import JournalFilter
|
from .filter import JournalFilter
|
||||||
from mytime import MyTime
|
from .mytime import MyTime
|
||||||
|
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
|
|
|
@ -25,7 +25,7 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import Queue, logging
|
import Queue, logging
|
||||||
|
|
||||||
from actions import Actions
|
from .actions import Actions
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -71,7 +71,7 @@ class Jail:
|
||||||
"%r was requested" % (b, backend))
|
"%r was requested" % (b, backend))
|
||||||
else:
|
else:
|
||||||
logSys.info("Initiated %r backend" % b)
|
logSys.info("Initiated %r backend" % b)
|
||||||
self.__action = Actions(self)
|
self.__actions = Actions(self)
|
||||||
return # we are done
|
return # we are done
|
||||||
except ImportError, e:
|
except ImportError, e:
|
||||||
logSys.debug(
|
logSys.debug(
|
||||||
|
@ -123,11 +123,17 @@ class Jail:
|
||||||
def getDatabase(self):
|
def getDatabase(self):
|
||||||
return self.__db
|
return self.__db
|
||||||
|
|
||||||
def getFilter(self):
|
@property
|
||||||
|
def filter(self):
|
||||||
|
"""The filter which the jail is using to monitor log files.
|
||||||
|
"""
|
||||||
return self.__filter
|
return self.__filter
|
||||||
|
|
||||||
def getAction(self):
|
@property
|
||||||
return self.__action
|
def actions(self):
|
||||||
|
"""Actions object used to manage actions for jail.
|
||||||
|
"""
|
||||||
|
return self.__actions
|
||||||
|
|
||||||
def putFailTicket(self, ticket):
|
def putFailTicket(self, ticket):
|
||||||
self.__queue.put(ticket)
|
self.__queue.put(ticket)
|
||||||
|
@ -142,36 +148,36 @@ class Jail:
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.__filter.start()
|
self.__filter.start()
|
||||||
self.__action.start()
|
self.__actions.start()
|
||||||
# Restore any previous valid bans from the database
|
# Restore any previous valid bans from the database
|
||||||
if self.__db is not None:
|
if self.__db is not None:
|
||||||
for ticket in self.__db.getBans(
|
for ticket in self.__db.getBans(
|
||||||
jail=self, bantime=self.__action.getBanTime()):
|
jail=self, bantime=self.__actions.getBanTime()):
|
||||||
self.__queue.put(ticket)
|
self.__queue.put(ticket)
|
||||||
logSys.info("Jail '%s' started" % self.__name)
|
logSys.info("Jail '%s' started" % self.__name)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.__filter.stop()
|
self.__filter.stop()
|
||||||
self.__action.stop()
|
self.__actions.stop()
|
||||||
self.__filter.join()
|
self.__filter.join()
|
||||||
self.__action.join()
|
self.__actions.join()
|
||||||
logSys.info("Jail '%s' stopped" % self.__name)
|
logSys.info("Jail '%s' stopped" % self.__name)
|
||||||
|
|
||||||
def isAlive(self):
|
def isAlive(self):
|
||||||
isAlive0 = self.__filter.isAlive()
|
isAlive0 = self.__filter.isAlive()
|
||||||
isAlive1 = self.__action.isAlive()
|
isAlive1 = self.__actions.isAlive()
|
||||||
return isAlive0 or isAlive1
|
return isAlive0 or isAlive1
|
||||||
|
|
||||||
def setIdle(self, value):
|
def setIdle(self, value):
|
||||||
self.__filter.setIdle(value)
|
self.__filter.setIdle(value)
|
||||||
self.__action.setIdle(value)
|
self.__actions.setIdle(value)
|
||||||
|
|
||||||
def getIdle(self):
|
def getIdle(self):
|
||||||
return self.__filter.getIdle() or self.__action.getIdle()
|
return self.__filter.getIdle() or self.__actions.getIdle()
|
||||||
|
|
||||||
def getStatus(self):
|
def getStatus(self):
|
||||||
fStatus = self.__filter.status()
|
fStatus = self.__filter.status()
|
||||||
aStatus = self.__action.status()
|
aStatus = self.__actions.status()
|
||||||
ret = [("filter", fStatus),
|
ret = [("filter", fStatus),
|
||||||
("action", aStatus)]
|
("action", aStatus)]
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -21,137 +21,84 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2013- Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2013- Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from fail2ban.exceptions import DuplicateJailException, UnknownJailException
|
|
||||||
|
|
||||||
from jail import Jail
|
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
from collections import Mapping
|
||||||
|
|
||||||
##
|
from ..exceptions import DuplicateJailException, UnknownJailException
|
||||||
# Handles the jails.
|
from .jail import Jail
|
||||||
#
|
|
||||||
# This class handles the jails. Creation, deletion or access to a jail must be
|
|
||||||
# done through this class. This class is thread-safe which is not the case of
|
|
||||||
# the jail itself, including filter and actions.
|
|
||||||
|
|
||||||
class Jails:
|
|
||||||
|
|
||||||
##
|
class Jails(Mapping):
|
||||||
# Constructor.
|
"""Handles the jails.
|
||||||
|
|
||||||
|
This class handles the jails. Creation, deletion or access to a jail
|
||||||
|
must be done through this class. This class is thread-safe which is
|
||||||
|
not the case of the jail itself, including filter and actions. This
|
||||||
|
class is based on Mapping type, and the `add` method must be used to
|
||||||
|
add additional jails.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
"""Initialise an empty Jails instance.
|
||||||
|
"""
|
||||||
self.__lock = Lock()
|
self.__lock = Lock()
|
||||||
self.__jails = dict()
|
self._jails = dict()
|
||||||
|
|
||||||
##
|
|
||||||
# Adds a jail.
|
|
||||||
#
|
|
||||||
# Adds a new jail which should use the given backend. Raises a
|
|
||||||
# <code>DuplicateJailException</code> if the jail is already defined.
|
|
||||||
# @param name The name of the jail
|
|
||||||
# @param backend The backend to use
|
|
||||||
|
|
||||||
def add(self, name, backend, db=None):
|
def add(self, name, backend, db=None):
|
||||||
|
"""Adds a jail.
|
||||||
|
|
||||||
|
Adds a new jail if not already present which should use the
|
||||||
|
given backend.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name : str
|
||||||
|
The name of the jail.
|
||||||
|
backend : str
|
||||||
|
The backend to use.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
DuplicateJailException
|
||||||
|
If jail name is already present.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if self.__jails.has_key(name):
|
if name in self._jails:
|
||||||
raise DuplicateJailException(name)
|
raise DuplicateJailException(name)
|
||||||
else:
|
else:
|
||||||
self.__jails[name] = Jail(name, backend, db)
|
self._jails[name] = Jail(name, backend, db)
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
##
|
def __getitem__(self, name):
|
||||||
# Removes a jail.
|
|
||||||
#
|
|
||||||
# Removes the jail <code>name</code>. Raise an <code>UnknownJailException</code>
|
|
||||||
# if the jail does not exist.
|
|
||||||
# @param name The name of the jail
|
|
||||||
|
|
||||||
def remove(self, name):
|
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if self.__jails.has_key(name):
|
return self._jails[name]
|
||||||
del self.__jails[name]
|
except KeyError:
|
||||||
else:
|
|
||||||
raise UnknownJailException(name)
|
raise UnknownJailException(name)
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
##
|
def __delitem__(self, name):
|
||||||
# Returns a jail.
|
|
||||||
#
|
|
||||||
# Returns the jail <code>name</code>. Raise an <code>UnknownJailException</code>
|
|
||||||
# if the jail does not exist.
|
|
||||||
# @param name The name of the jail
|
|
||||||
|
|
||||||
def get(self, name):
|
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if self.__jails.has_key(name):
|
del self._jails[name]
|
||||||
jail = self.__jails[name]
|
except KeyError:
|
||||||
return jail
|
|
||||||
else:
|
|
||||||
raise UnknownJailException(name)
|
raise UnknownJailException(name)
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
##
|
def __len__(self):
|
||||||
# Returns an action class instance.
|
|
||||||
#
|
|
||||||
# Returns the action object of the jail <code>name</code>. Raise an
|
|
||||||
# <code>UnknownJailException</code> if the jail does not exist.
|
|
||||||
# @param name The name of the jail
|
|
||||||
|
|
||||||
def getAction(self, name):
|
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if self.__jails.has_key(name):
|
return len(self._jails)
|
||||||
action = self.__jails[name].getAction()
|
|
||||||
return action
|
|
||||||
else:
|
|
||||||
raise UnknownJailException(name)
|
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
##
|
def __iter__(self):
|
||||||
# Returns a filter class instance.
|
|
||||||
#
|
|
||||||
# Returns the filter object of the jail <code>name</code>. Raise an
|
|
||||||
# <code>UnknownJailException</code> if the jail does not exist.
|
|
||||||
# @param name The name of the jail
|
|
||||||
|
|
||||||
def getFilter(self, name):
|
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if self.__jails.has_key(name):
|
return iter(self._jails)
|
||||||
action = self.__jails[name].getFilter()
|
|
||||||
return action
|
|
||||||
else:
|
|
||||||
raise UnknownJailException(name)
|
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
##
|
|
||||||
# Returns the jails.
|
|
||||||
#
|
|
||||||
# Returns a copy of the jails list.
|
|
||||||
|
|
||||||
def getAll(self):
|
|
||||||
try:
|
|
||||||
self.__lock.acquire()
|
|
||||||
return self.__jails.copy()
|
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
##
|
|
||||||
# Returns the size of the jails.
|
|
||||||
#
|
|
||||||
# Returns the number of jails.
|
|
||||||
|
|
||||||
def size(self):
|
|
||||||
try:
|
|
||||||
self.__lock.acquire()
|
|
||||||
return len(self.__jails)
|
|
||||||
finally:
|
|
||||||
self.__lock.release()
|
|
||||||
|
|
||||||
|
|
|
@ -25,15 +25,16 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
from threading import Lock, RLock
|
from threading import Lock, RLock
|
||||||
from jails import Jails
|
|
||||||
from filter import FileFilter, JournalFilter
|
|
||||||
from transmitter import Transmitter
|
|
||||||
from asyncserver import AsyncServer
|
|
||||||
from asyncserver import AsyncServerException
|
|
||||||
from database import Fail2BanDb
|
|
||||||
from fail2ban import version
|
|
||||||
import logging, logging.handlers, sys, os, signal
|
import logging, logging.handlers, sys, os, signal
|
||||||
|
|
||||||
|
from .jails import Jails
|
||||||
|
from .filter import FileFilter, JournalFilter
|
||||||
|
from .transmitter import Transmitter
|
||||||
|
from .asyncserver import AsyncServer, AsyncServerException
|
||||||
|
from .database import Fail2BanDb
|
||||||
|
from .action import CommandAction
|
||||||
|
from .. import version
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -121,10 +122,10 @@ class Server:
|
||||||
def addJail(self, name, backend):
|
def addJail(self, name, backend):
|
||||||
self.__jails.add(name, backend, self.__db)
|
self.__jails.add(name, backend, self.__db)
|
||||||
if self.__db is not None:
|
if self.__db is not None:
|
||||||
self.__db.addJail(self.__jails.get(name))
|
self.__db.addJail(self.__jails[name])
|
||||||
|
|
||||||
def delJail(self, name):
|
def delJail(self, name):
|
||||||
self.__jails.remove(name)
|
del self.__jails[name]
|
||||||
if self.__db is not None:
|
if self.__db is not None:
|
||||||
self.__db.delJailName(name)
|
self.__db.delJailName(name)
|
||||||
|
|
||||||
|
@ -132,7 +133,7 @@ class Server:
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if not self.isAlive(name):
|
if not self.isAlive(name):
|
||||||
self.__jails.get(name).start()
|
self.__jails[name].start()
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
|
@ -141,7 +142,7 @@ class Server:
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
if self.isAlive(name):
|
if self.isAlive(name):
|
||||||
self.__jails.get(name).stop()
|
self.__jails[name].stop()
|
||||||
self.delJail(name)
|
self.delJail(name)
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
@ -150,43 +151,43 @@ class Server:
|
||||||
logSys.info("Stopping all jails")
|
logSys.info("Stopping all jails")
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
for jail in self.__jails.getAll():
|
for jail in self.__jails.keys():
|
||||||
self.stopJail(jail)
|
self.stopJail(jail)
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
def isAlive(self, name):
|
def isAlive(self, name):
|
||||||
return self.__jails.get(name).isAlive()
|
return self.__jails[name].isAlive()
|
||||||
|
|
||||||
def setIdleJail(self, name, value):
|
def setIdleJail(self, name, value):
|
||||||
self.__jails.get(name).setIdle(value)
|
self.__jails[name].setIdle(value)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def getIdleJail(self, name):
|
def getIdleJail(self, name):
|
||||||
return self.__jails.get(name).getIdle()
|
return self.__jails[name].getIdle()
|
||||||
|
|
||||||
# Filter
|
# Filter
|
||||||
def addIgnoreIP(self, name, ip):
|
def addIgnoreIP(self, name, ip):
|
||||||
self.__jails.getFilter(name).addIgnoreIP(ip)
|
self.__jails[name].filter.addIgnoreIP(ip)
|
||||||
|
|
||||||
def delIgnoreIP(self, name, ip):
|
def delIgnoreIP(self, name, ip):
|
||||||
self.__jails.getFilter(name).delIgnoreIP(ip)
|
self.__jails[name].filter.delIgnoreIP(ip)
|
||||||
|
|
||||||
def getIgnoreIP(self, name):
|
def getIgnoreIP(self, name):
|
||||||
return self.__jails.getFilter(name).getIgnoreIP()
|
return self.__jails[name].filter.getIgnoreIP()
|
||||||
|
|
||||||
def addLogPath(self, name, fileName, tail=False):
|
def addLogPath(self, name, fileName, tail=False):
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, FileFilter):
|
if isinstance(filter_, FileFilter):
|
||||||
filter_.addLogPath(fileName, tail)
|
filter_.addLogPath(fileName, tail)
|
||||||
|
|
||||||
def delLogPath(self, name, fileName):
|
def delLogPath(self, name, fileName):
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, FileFilter):
|
if isinstance(filter_, FileFilter):
|
||||||
filter_.delLogPath(fileName)
|
filter_.delLogPath(fileName)
|
||||||
|
|
||||||
def getLogPath(self, name):
|
def getLogPath(self, name):
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, FileFilter):
|
if isinstance(filter_, FileFilter):
|
||||||
return [m.getFileName()
|
return [m.getFileName()
|
||||||
for m in filter_.getLogPath()]
|
for m in filter_.getLogPath()]
|
||||||
|
@ -195,17 +196,17 @@ class Server:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def addJournalMatch(self, name, match): # pragma: systemd no cover
|
def addJournalMatch(self, name, match): # pragma: systemd no cover
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, JournalFilter):
|
if isinstance(filter_, JournalFilter):
|
||||||
filter_.addJournalMatch(match)
|
filter_.addJournalMatch(match)
|
||||||
|
|
||||||
def delJournalMatch(self, name, match): # pragma: systemd no cover
|
def delJournalMatch(self, name, match): # pragma: systemd no cover
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, JournalFilter):
|
if isinstance(filter_, JournalFilter):
|
||||||
filter_.delJournalMatch(match)
|
filter_.delJournalMatch(match)
|
||||||
|
|
||||||
def getJournalMatch(self, name): # pragma: systemd no cover
|
def getJournalMatch(self, name): # pragma: systemd no cover
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, JournalFilter):
|
if isinstance(filter_, JournalFilter):
|
||||||
return filter_.getJournalMatch()
|
return filter_.getJournalMatch()
|
||||||
else:
|
else:
|
||||||
|
@ -213,154 +214,109 @@ class Server:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def setLogEncoding(self, name, encoding):
|
def setLogEncoding(self, name, encoding):
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, FileFilter):
|
if isinstance(filter_, FileFilter):
|
||||||
filter_.setLogEncoding(encoding)
|
filter_.setLogEncoding(encoding)
|
||||||
|
|
||||||
def getLogEncoding(self, name):
|
def getLogEncoding(self, name):
|
||||||
filter_ = self.__jails.getFilter(name)
|
filter_ = self.__jails[name].filter
|
||||||
if isinstance(filter_, FileFilter):
|
if isinstance(filter_, FileFilter):
|
||||||
return filter_.getLogEncoding()
|
return filter_.getLogEncoding()
|
||||||
|
|
||||||
def setFindTime(self, name, value):
|
def setFindTime(self, name, value):
|
||||||
self.__jails.getFilter(name).setFindTime(value)
|
self.__jails[name].filter.setFindTime(value)
|
||||||
|
|
||||||
def getFindTime(self, name):
|
def getFindTime(self, name):
|
||||||
return self.__jails.getFilter(name).getFindTime()
|
return self.__jails[name].filter.getFindTime()
|
||||||
|
|
||||||
def setDatePattern(self, name, pattern):
|
def setDatePattern(self, name, pattern):
|
||||||
self.__jails.getFilter(name).setDatePattern(pattern)
|
self.__jails[name].filter.setDatePattern(pattern)
|
||||||
|
|
||||||
def getDatePattern(self, name):
|
def getDatePattern(self, name):
|
||||||
return self.__jails.getFilter(name).getDatePattern()
|
return self.__jails[name].filter.getDatePattern()
|
||||||
|
|
||||||
def setIgnoreCommand(self, name, value):
|
def setIgnoreCommand(self, name, value):
|
||||||
self.__jails.getFilter(name).setIgnoreCommand(value)
|
self.__jails[name].filter.setIgnoreCommand(value)
|
||||||
|
|
||||||
def getIgnoreCommand(self, name):
|
def getIgnoreCommand(self, name):
|
||||||
return self.__jails.getFilter(name).getIgnoreCommand()
|
return self.__jails[name].filter.getIgnoreCommand()
|
||||||
|
|
||||||
def addFailRegex(self, name, value):
|
def addFailRegex(self, name, value):
|
||||||
self.__jails.getFilter(name).addFailRegex(value)
|
self.__jails[name].filter.addFailRegex(value)
|
||||||
|
|
||||||
def delFailRegex(self, name, index):
|
def delFailRegex(self, name, index):
|
||||||
self.__jails.getFilter(name).delFailRegex(index)
|
self.__jails[name].filter.delFailRegex(index)
|
||||||
|
|
||||||
def getFailRegex(self, name):
|
def getFailRegex(self, name):
|
||||||
return self.__jails.getFilter(name).getFailRegex()
|
return self.__jails[name].filter.getFailRegex()
|
||||||
|
|
||||||
def addIgnoreRegex(self, name, value):
|
def addIgnoreRegex(self, name, value):
|
||||||
self.__jails.getFilter(name).addIgnoreRegex(value)
|
self.__jails[name].filter.addIgnoreRegex(value)
|
||||||
|
|
||||||
def delIgnoreRegex(self, name, index):
|
def delIgnoreRegex(self, name, index):
|
||||||
self.__jails.getFilter(name).delIgnoreRegex(index)
|
self.__jails[name].filter.delIgnoreRegex(index)
|
||||||
|
|
||||||
def getIgnoreRegex(self, name):
|
def getIgnoreRegex(self, name):
|
||||||
return self.__jails.getFilter(name).getIgnoreRegex()
|
return self.__jails[name].filter.getIgnoreRegex()
|
||||||
|
|
||||||
def setUseDns(self, name, value):
|
def setUseDns(self, name, value):
|
||||||
self.__jails.getFilter(name).setUseDns(value)
|
self.__jails[name].filter.setUseDns(value)
|
||||||
|
|
||||||
def getUseDns(self, name):
|
def getUseDns(self, name):
|
||||||
return self.__jails.getFilter(name).getUseDns()
|
return self.__jails[name].filter.getUseDns()
|
||||||
|
|
||||||
def setMaxRetry(self, name, value):
|
def setMaxRetry(self, name, value):
|
||||||
self.__jails.getFilter(name).setMaxRetry(value)
|
self.__jails[name].filter.setMaxRetry(value)
|
||||||
|
|
||||||
def getMaxRetry(self, name):
|
def getMaxRetry(self, name):
|
||||||
return self.__jails.getFilter(name).getMaxRetry()
|
return self.__jails[name].filter.getMaxRetry()
|
||||||
|
|
||||||
def setMaxLines(self, name, value):
|
def setMaxLines(self, name, value):
|
||||||
self.__jails.getFilter(name).setMaxLines(value)
|
self.__jails[name].filter.setMaxLines(value)
|
||||||
|
|
||||||
def getMaxLines(self, name):
|
def getMaxLines(self, name):
|
||||||
return self.__jails.getFilter(name).getMaxLines()
|
return self.__jails[name].filter.getMaxLines()
|
||||||
|
|
||||||
# Action
|
# Action
|
||||||
def addAction(self, name, value):
|
def addAction(self, name, value, *args):
|
||||||
self.__jails.getAction(name).addAction(value)
|
self.__jails[name].actions.add(value, *args)
|
||||||
|
|
||||||
def getLastAction(self, name):
|
|
||||||
return self.__jails.getAction(name).getLastAction()
|
|
||||||
|
|
||||||
def getActions(self, name):
|
def getActions(self, name):
|
||||||
return self.__jails.getAction(name).getActions()
|
return self.__jails[name].actions
|
||||||
|
|
||||||
def delAction(self, name, value):
|
def delAction(self, name, value):
|
||||||
self.__jails.getAction(name).delAction(value)
|
del self.__jails[name].actions[value]
|
||||||
|
|
||||||
def setCInfo(self, name, action, key, value):
|
def getAction(self, name, value):
|
||||||
self.__jails.getAction(name).getAction(action).setCInfo(key, value)
|
return self.__jails[name].actions[value]
|
||||||
|
|
||||||
def getCInfo(self, name, action, key):
|
|
||||||
return self.__jails.getAction(name).getAction(action).getCInfo(key)
|
|
||||||
|
|
||||||
def delCInfo(self, name, action, key):
|
|
||||||
self.__jails.getAction(name).getAction(action).delCInfo(key)
|
|
||||||
|
|
||||||
def setBanTime(self, name, value):
|
def setBanTime(self, name, value):
|
||||||
self.__jails.getAction(name).setBanTime(value)
|
self.__jails[name].actions.setBanTime(value)
|
||||||
|
|
||||||
def setBanIP(self, name, value):
|
def setBanIP(self, name, value):
|
||||||
return self.__jails.getFilter(name).addBannedIP(value)
|
return self.__jails[name].filter.addBannedIP(value)
|
||||||
|
|
||||||
def setUnbanIP(self, name, value):
|
def setUnbanIP(self, name, value):
|
||||||
return self.__jails.getAction(name).removeBannedIP(value)
|
self.__jails[name].actions.removeBannedIP(value)
|
||||||
|
|
||||||
def getBanTime(self, name):
|
def getBanTime(self, name):
|
||||||
return self.__jails.getAction(name).getBanTime()
|
return self.__jails[name].actions.getBanTime()
|
||||||
|
|
||||||
def setActionStart(self, name, action, value):
|
|
||||||
self.__jails.getAction(name).getAction(action).setActionStart(value)
|
|
||||||
|
|
||||||
def getActionStart(self, name, action):
|
|
||||||
return self.__jails.getAction(name).getAction(action).getActionStart()
|
|
||||||
|
|
||||||
def setActionStop(self, name, action, value):
|
|
||||||
self.__jails.getAction(name).getAction(action).setActionStop(value)
|
|
||||||
|
|
||||||
def getActionStop(self, name, action):
|
|
||||||
return self.__jails.getAction(name).getAction(action).getActionStop()
|
|
||||||
|
|
||||||
def setActionCheck(self, name, action, value):
|
|
||||||
self.__jails.getAction(name).getAction(action).setActionCheck(value)
|
|
||||||
|
|
||||||
def getActionCheck(self, name, action):
|
|
||||||
return self.__jails.getAction(name).getAction(action).getActionCheck()
|
|
||||||
|
|
||||||
def setActionBan(self, name, action, value):
|
|
||||||
self.__jails.getAction(name).getAction(action).setActionBan(value)
|
|
||||||
|
|
||||||
def getActionBan(self, name, action):
|
|
||||||
return self.__jails.getAction(name).getAction(action).getActionBan()
|
|
||||||
|
|
||||||
def setActionUnban(self, name, action, value):
|
|
||||||
self.__jails.getAction(name).getAction(action).setActionUnban(value)
|
|
||||||
|
|
||||||
def getActionUnban(self, name, action):
|
|
||||||
return self.__jails.getAction(name).getAction(action).getActionUnban()
|
|
||||||
|
|
||||||
def setActionTimeout(self, name, action, value):
|
|
||||||
self.__jails.getAction(name).getAction(action).setTimeout(value)
|
|
||||||
|
|
||||||
def getActionTimeout(self, name, action):
|
|
||||||
return self.__jails.getAction(name).getAction(action).getTimeout()
|
|
||||||
|
|
||||||
# Status
|
# Status
|
||||||
def status(self):
|
def status(self):
|
||||||
try:
|
try:
|
||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
jails = list(self.__jails.getAll())
|
jails = list(self.__jails)
|
||||||
jails.sort()
|
jails.sort()
|
||||||
jailList = ", ".join(jails)
|
jailList = ", ".join(jails)
|
||||||
ret = [("Number of jail", self.__jails.size()),
|
ret = [("Number of jail", len(self.__jails)),
|
||||||
("Jail list", jailList)]
|
("Jail list", jailList)]
|
||||||
return ret
|
return ret
|
||||||
finally:
|
finally:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
def statusJail(self, name):
|
def statusJail(self, name):
|
||||||
return self.__jails.get(name).getStatus()
|
return self.__jails[name].getStatus()
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
|
|
||||||
|
@ -488,7 +444,7 @@ class Server:
|
||||||
return "flushed"
|
return "flushed"
|
||||||
|
|
||||||
def setDatabase(self, filename):
|
def setDatabase(self, filename):
|
||||||
if self.__jails.size() == 0:
|
if len(self.__jails) == 0:
|
||||||
if filename.lower() == "none":
|
if filename.lower() == "none":
|
||||||
self.__db = None
|
self.__db = None
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -25,6 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging, time
|
import logging, time
|
||||||
|
import json
|
||||||
|
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
@ -226,56 +227,29 @@ class Transmitter:
|
||||||
return self.__server.setBanIP(name,value)
|
return self.__server.setBanIP(name,value)
|
||||||
elif command[1] == "unbanip":
|
elif command[1] == "unbanip":
|
||||||
value = command[2]
|
value = command[2]
|
||||||
return self.__server.setUnbanIP(name,value)
|
self.__server.setUnbanIP(name, value)
|
||||||
|
return value
|
||||||
elif command[1] == "addaction":
|
elif command[1] == "addaction":
|
||||||
value = command[2]
|
args = [command[2]]
|
||||||
self.__server.addAction(name, value)
|
if len(command) > 3:
|
||||||
return self.__server.getLastAction(name).getName()
|
args.extend([command[3], json.loads(command[4])])
|
||||||
|
self.__server.addAction(name, *args)
|
||||||
|
return args[0]
|
||||||
elif command[1] == "delaction":
|
elif command[1] == "delaction":
|
||||||
value = command[2]
|
value = command[2]
|
||||||
self.__server.delAction(name, value)
|
self.__server.delAction(name, value)
|
||||||
return None
|
return None
|
||||||
elif command[1] == "setcinfo":
|
elif command[1] == "action":
|
||||||
act = command[2]
|
actionname = command[2]
|
||||||
key = command[3]
|
actionkey = command[3]
|
||||||
value = " ".join(command[4:])
|
action = self.__server.getAction(name, actionname)
|
||||||
self.__server.setCInfo(name, act, key, value)
|
if callable(getattr(action, actionkey, None)):
|
||||||
return self.__server.getCInfo(name, act, key)
|
actionvalue = json.loads(command[4]) if len(command)>4 else {}
|
||||||
elif command[1] == "delcinfo":
|
return getattr(action, actionkey)(**actionvalue)
|
||||||
act = command[2]
|
else:
|
||||||
key = command[3]
|
actionvalue = command[4]
|
||||||
self.__server.delCInfo(name, act, key)
|
setattr(action, actionkey, actionvalue)
|
||||||
return None
|
return getattr(action, actionkey)
|
||||||
elif command[1] == "actionstart":
|
|
||||||
act = command[2]
|
|
||||||
value = " ".join(command[3:])
|
|
||||||
self.__server.setActionStart(name, act, value)
|
|
||||||
return self.__server.getActionStart(name, act)
|
|
||||||
elif command[1] == "actionstop":
|
|
||||||
act = command[2]
|
|
||||||
value = " ".join(command[3:])
|
|
||||||
self.__server.setActionStop(name, act, value)
|
|
||||||
return self.__server.getActionStop(name, act)
|
|
||||||
elif command[1] == "actioncheck":
|
|
||||||
act = command[2]
|
|
||||||
value = " ".join(command[3:])
|
|
||||||
self.__server.setActionCheck(name, act, value)
|
|
||||||
return self.__server.getActionCheck(name, act)
|
|
||||||
elif command[1] == "actionban":
|
|
||||||
act = command[2]
|
|
||||||
value = " ".join(command[3:])
|
|
||||||
self.__server.setActionBan(name, act, value)
|
|
||||||
return self.__server.getActionBan(name, act)
|
|
||||||
elif command[1] == "actionunban":
|
|
||||||
act = command[2]
|
|
||||||
value = " ".join(command[3:])
|
|
||||||
self.__server.setActionUnban(name, act, value)
|
|
||||||
return self.__server.getActionUnban(name, act)
|
|
||||||
elif command[1] == "timeout":
|
|
||||||
act = command[2]
|
|
||||||
value = int(command[3])
|
|
||||||
self.__server.setActionTimeout(name, act, value)
|
|
||||||
return self.__server.getActionTimeout(name, act)
|
|
||||||
raise Exception("Invalid command (no set action or not yet implemented)")
|
raise Exception("Invalid command (no set action or not yet implemented)")
|
||||||
|
|
||||||
def __commandGet(self, command):
|
def __commandGet(self, command):
|
||||||
|
@ -327,31 +301,25 @@ class Transmitter:
|
||||||
elif command[1] == "bantime":
|
elif command[1] == "bantime":
|
||||||
return self.__server.getBanTime(name)
|
return self.__server.getBanTime(name)
|
||||||
elif command[1] == "actions":
|
elif command[1] == "actions":
|
||||||
return self.__server.getActions(name)
|
return self.__server.getActions(name).keys()
|
||||||
elif command[1] == "addaction":
|
elif command[1] == "action":
|
||||||
return self.__server.getLastAction(name).getName()
|
actionname = command[2]
|
||||||
elif command[1] == "actionstart":
|
actionvalue = command[3]
|
||||||
act = command[2]
|
action = self.__server.getAction(name, actionname)
|
||||||
return self.__server.getActionStart(name, act)
|
return getattr(action, actionvalue)
|
||||||
elif command[1] == "actionstop":
|
elif command[1] == "actionproperties":
|
||||||
act = command[2]
|
actionname = command[2]
|
||||||
return self.__server.getActionStop(name, act)
|
action = self.__server.getAction(name, actionname)
|
||||||
elif command[1] == "actioncheck":
|
return [
|
||||||
act = command[2]
|
key for key in dir(action)
|
||||||
return self.__server.getActionCheck(name, act)
|
if not key.startswith("_") and
|
||||||
elif command[1] == "actionban":
|
not callable(getattr(action, key))]
|
||||||
act = command[2]
|
elif command[1] == "actionmethods":
|
||||||
return self.__server.getActionBan(name, act)
|
actionname = command[2]
|
||||||
elif command[1] == "actionunban":
|
action = self.__server.getAction(name, actionname)
|
||||||
act = command[2]
|
return [
|
||||||
return self.__server.getActionUnban(name, act)
|
key for key in dir(action)
|
||||||
elif command[1] == "cinfo":
|
if not key.startswith("_") and callable(getattr(action, key))]
|
||||||
act = command[2]
|
|
||||||
key = command[3]
|
|
||||||
return self.__server.getCInfo(name, act, key)
|
|
||||||
elif command[1] == "timeout":
|
|
||||||
act = command[2]
|
|
||||||
return self.__server.getActionTimeout(name, act)
|
|
||||||
raise Exception("Invalid command (no get action or not yet implemented)")
|
raise Exception("Invalid command (no get action or not yet implemented)")
|
||||||
|
|
||||||
def status(self, command):
|
def status(self, command):
|
||||||
|
|
|
@ -26,39 +26,49 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import unittest, time
|
import unittest, time
|
||||||
import sys, os, tempfile
|
import sys, os, tempfile
|
||||||
from fail2ban.server.actions import Actions
|
|
||||||
from dummyjail import DummyJail
|
|
||||||
|
|
||||||
class ExecuteActions(unittest.TestCase):
|
from ..server.actions import Actions
|
||||||
|
from .dummyjail import DummyJail
|
||||||
|
from .utils import LogCaptureTestCase
|
||||||
|
|
||||||
|
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||||
|
|
||||||
|
class ExecuteActions(LogCaptureTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
|
super(ExecuteActions, self).setUp()
|
||||||
self.__jail = DummyJail()
|
self.__jail = DummyJail()
|
||||||
self.__actions = Actions(self.__jail)
|
self.__actions = Actions(self.__jail)
|
||||||
self.__tmpfile, self.__tmpfilename = tempfile.mkstemp()
|
self.__tmpfile, self.__tmpfilename = tempfile.mkstemp()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
super(ExecuteActions, self).tearDown()
|
||||||
os.remove(self.__tmpfilename)
|
os.remove(self.__tmpfilename)
|
||||||
|
|
||||||
def defaultActions(self):
|
def defaultActions(self):
|
||||||
self.__actions.addAction('ip')
|
self.__actions.add('ip')
|
||||||
self.__ip = self.__actions.getAction('ip')
|
self.__ip = self.__actions['ip']
|
||||||
self.__ip.setActionStart('echo ip start 64 >> "%s"' % self.__tmpfilename )
|
self.__ip.actionstart = 'echo ip start 64 >> "%s"' % self.__tmpfilename
|
||||||
self.__ip.setActionBan('echo ip ban <ip> >> "%s"' % self.__tmpfilename )
|
self.__ip.actionban = 'echo ip ban <ip> >> "%s"' % self.__tmpfilename
|
||||||
self.__ip.setActionUnban('echo ip unban <ip> >> "%s"' % self.__tmpfilename )
|
self.__ip.actionunban = 'echo ip unban <ip> >> "%s"' % self.__tmpfilename
|
||||||
self.__ip.setActionCheck('echo ip check <ip> >> "%s"' % self.__tmpfilename )
|
self.__ip.actioncheck = 'echo ip check <ip> >> "%s"' % self.__tmpfilename
|
||||||
self.__ip.setActionStop('echo ip stop >> "%s"' % self.__tmpfilename )
|
self.__ip.actionstop = 'echo ip stop >> "%s"' % self.__tmpfilename
|
||||||
|
|
||||||
|
def testActionsAddDuplicateName(self):
|
||||||
|
self.__actions.add('test')
|
||||||
|
self.assertRaises(ValueError, self.__actions.add, 'test')
|
||||||
|
|
||||||
def testActionsManipulation(self):
|
def testActionsManipulation(self):
|
||||||
self.__actions.addAction('test')
|
self.__actions.add('test')
|
||||||
self.assertTrue(self.__actions.getAction('test'))
|
self.assertTrue(self.__actions['test'])
|
||||||
self.assertTrue(self.__actions.getLastAction())
|
self.assertTrue('test' in self.__actions)
|
||||||
self.assertRaises(KeyError,self.__actions.getAction,*['nonexistant action'])
|
self.assertFalse('nonexistant action' in self.__actions)
|
||||||
self.__actions.addAction('test1')
|
self.__actions.add('test1')
|
||||||
self.__actions.delAction('test')
|
del self.__actions['test']
|
||||||
self.__actions.delAction('test1')
|
del self.__actions['test1']
|
||||||
self.assertRaises(KeyError, self.__actions.getAction, *['test'])
|
self.assertFalse('test' in self.__actions)
|
||||||
self.assertRaises(IndexError,self.__actions.getLastAction)
|
self.assertEqual(len(self.__actions), 0)
|
||||||
|
|
||||||
self.__actions.setBanTime(127)
|
self.__actions.setBanTime(127)
|
||||||
self.assertEqual(self.__actions.getBanTime(),127)
|
self.assertEqual(self.__actions.getBanTime(),127)
|
||||||
|
@ -77,3 +87,55 @@ class ExecuteActions(unittest.TestCase):
|
||||||
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
|
self.assertEqual(self.__actions.status(),[("Currently banned", 0 ),
|
||||||
("Total banned", 0 ), ("IP list", [] )])
|
("Total banned", 0 ), ("IP list", [] )])
|
||||||
|
|
||||||
|
|
||||||
|
def testAddActionPython(self):
|
||||||
|
self.__actions.add(
|
||||||
|
"Action", os.path.join(TEST_FILES_DIR, "action.d/action.py"),
|
||||||
|
{'opt1': 'value'})
|
||||||
|
|
||||||
|
self.assertTrue(self._is_logged("TestAction initialised"))
|
||||||
|
|
||||||
|
self.__actions.start()
|
||||||
|
time.sleep(3)
|
||||||
|
self.assertTrue(self._is_logged("TestAction action start"))
|
||||||
|
|
||||||
|
self.__actions.stop()
|
||||||
|
self.__actions.join()
|
||||||
|
self.assertTrue(self._is_logged("TestAction action stop"))
|
||||||
|
|
||||||
|
self.assertRaises(IOError,
|
||||||
|
self.__actions.add, "Action3", "/does/not/exist.py", {})
|
||||||
|
|
||||||
|
# With optional argument
|
||||||
|
self.__actions.add(
|
||||||
|
"Action4", os.path.join(TEST_FILES_DIR, "action.d/action.py"),
|
||||||
|
{'opt1': 'value', 'opt2': 'value2'})
|
||||||
|
# With too many arguments
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError, self.__actions.add, "Action5",
|
||||||
|
os.path.join(TEST_FILES_DIR, "action.d/action.py"),
|
||||||
|
{'opt1': 'value', 'opt2': 'value2', 'opt3': 'value3'})
|
||||||
|
# Missing required argument
|
||||||
|
self.assertRaises(
|
||||||
|
TypeError, self.__actions.add, "Action5",
|
||||||
|
os.path.join(TEST_FILES_DIR, "action.d/action.py"), {})
|
||||||
|
|
||||||
|
def testAddPythonActionNOK(self):
|
||||||
|
self.assertRaises(RuntimeError, self.__actions.add,
|
||||||
|
"Action", os.path.join(TEST_FILES_DIR,
|
||||||
|
"action.d/action_noAction.py"),
|
||||||
|
{})
|
||||||
|
self.assertRaises(RuntimeError, self.__actions.add,
|
||||||
|
"Action", os.path.join(TEST_FILES_DIR,
|
||||||
|
"action.d/action_nomethod.py"),
|
||||||
|
{})
|
||||||
|
self.__actions.add(
|
||||||
|
"Action", os.path.join(TEST_FILES_DIR,
|
||||||
|
"action.d/action_errors.py"),
|
||||||
|
{})
|
||||||
|
self.__actions.start()
|
||||||
|
time.sleep(3)
|
||||||
|
self.assertTrue(self._is_logged("Failed to start"))
|
||||||
|
self.__actions.stop()
|
||||||
|
self.__actions.join()
|
||||||
|
self.assertTrue(self._is_logged("Failed to stop"))
|
||||||
|
|
|
@ -27,26 +27,21 @@ __license__ = "GPL"
|
||||||
import time
|
import time
|
||||||
import logging, sys
|
import logging, sys
|
||||||
|
|
||||||
from fail2ban.server.action import Action
|
from ..server.action import CommandAction, CallingMap
|
||||||
|
|
||||||
from fail2ban.tests.utils import LogCaptureTestCase
|
from .utils import LogCaptureTestCase
|
||||||
|
|
||||||
class ExecuteAction(LogCaptureTestCase):
|
class CommandActionTest(LogCaptureTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Call before every test case."""
|
"""Call before every test case."""
|
||||||
self.__action = Action("Test")
|
self.__action = CommandAction(None, "Test")
|
||||||
LogCaptureTestCase.setUp(self)
|
LogCaptureTestCase.setUp(self)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""Call after every test case."""
|
"""Call after every test case."""
|
||||||
LogCaptureTestCase.tearDown(self)
|
LogCaptureTestCase.tearDown(self)
|
||||||
self.__action.execActionStop()
|
self.__action.stop()
|
||||||
|
|
||||||
def testNameChange(self):
|
|
||||||
self.assertEqual(self.__action.getName(), "Test")
|
|
||||||
self.__action.setName("Tricky Test")
|
|
||||||
self.assertEqual(self.__action.getName(), "Tricky Test")
|
|
||||||
|
|
||||||
def testSubstituteRecursiveTags(self):
|
def testSubstituteRecursiveTags(self):
|
||||||
aInfo = {
|
aInfo = {
|
||||||
|
@ -55,15 +50,24 @@ class ExecuteAction(LogCaptureTestCase):
|
||||||
'xyz': "890 <ABC>",
|
'xyz': "890 <ABC>",
|
||||||
}
|
}
|
||||||
# Recursion is bad
|
# Recursion is bad
|
||||||
self.assertFalse(Action.substituteRecursiveTags({'A': '<A>'}))
|
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<A>'}))
|
||||||
self.assertFalse(Action.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
|
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<A>'}))
|
||||||
self.assertFalse(Action.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
|
self.assertFalse(CommandAction.substituteRecursiveTags({'A': '<B>', 'B': '<C>', 'C': '<A>'}))
|
||||||
|
# Unresolveable substition
|
||||||
|
self.assertFalse(CommandAction.substituteRecursiveTags({'A': 'to=<B> fromip=<IP>', 'C': '<B>', 'B': '<C>', 'D': ''}))
|
||||||
|
self.assertFalse(CommandAction.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP>', 'sweet': '<honeypot>', 'honeypot': '<sweet>', 'ignoreregex': ''}))
|
||||||
# missing tags are ok
|
# missing tags are ok
|
||||||
self.assertEqual(Action.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
|
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
|
||||||
self.assertEqual(Action.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
|
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
|
||||||
self.assertEqual(Action.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
|
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <B>', 'B': 'cool'}), {'A': '<C> cool', 'B': 'cool'})
|
||||||
|
# Multiple stuff on same line is ok
|
||||||
|
self.assertEqual(CommandAction.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP> evilperson=<honeypot>', 'honeypot': 'pokie', 'ignoreregex': ''}),
|
||||||
|
{ 'failregex': "to=pokie fromip=<IP> evilperson=pokie",
|
||||||
|
'honeypot': 'pokie',
|
||||||
|
'ignoreregex': '',
|
||||||
|
})
|
||||||
# rest is just cool
|
# rest is just cool
|
||||||
self.assertEqual(Action.substituteRecursiveTags(aInfo),
|
self.assertEqual(CommandAction.substituteRecursiveTags(aInfo),
|
||||||
{ 'HOST': "192.0.2.0",
|
{ 'HOST': "192.0.2.0",
|
||||||
'ABC': '123 192.0.2.0',
|
'ABC': '123 192.0.2.0',
|
||||||
'xyz': '890 123 192.0.2.0',
|
'xyz': '890 123 192.0.2.0',
|
||||||
|
@ -99,90 +103,102 @@ class ExecuteAction(LogCaptureTestCase):
|
||||||
|
|
||||||
# Callable
|
# Callable
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.__action.replaceTag("09 <callable> 11",
|
self.__action.replaceTag("09 <callme> 11",
|
||||||
{'callable': lambda: str(10)}),
|
CallingMap(callme=lambda: str(10))),
|
||||||
"09 10 11")
|
"09 10 11")
|
||||||
|
|
||||||
# As tag not present, therefore callable should not be called
|
# As tag not present, therefore callable should not be called
|
||||||
# Will raise ValueError if it is
|
# Will raise ValueError if it is
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.__action.replaceTag("abc",
|
self.__action.replaceTag("abc",
|
||||||
{'callable': lambda: int("a")}), "abc")
|
CallingMap(callme=lambda: int("a"))), "abc")
|
||||||
|
|
||||||
def testExecuteActionBan(self):
|
def testExecuteActionBan(self):
|
||||||
self.__action.setActionStart("touch /tmp/fail2ban.test")
|
self.__action.actionstart = "touch /tmp/fail2ban.test"
|
||||||
self.assertEqual(self.__action.getActionStart(), "touch /tmp/fail2ban.test")
|
self.assertEqual(self.__action.actionstart, "touch /tmp/fail2ban.test")
|
||||||
self.__action.setActionStop("rm -f /tmp/fail2ban.test")
|
self.__action.actionstop = "rm -f /tmp/fail2ban.test"
|
||||||
self.assertEqual(self.__action.getActionStop(), 'rm -f /tmp/fail2ban.test')
|
self.assertEqual(self.__action.actionstop, 'rm -f /tmp/fail2ban.test')
|
||||||
self.__action.setActionBan("echo -n")
|
self.__action.actionban = "echo -n"
|
||||||
self.assertEqual(self.__action.getActionBan(), 'echo -n')
|
self.assertEqual(self.__action.actionban, 'echo -n')
|
||||||
self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]")
|
self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]"
|
||||||
self.assertEqual(self.__action.getActionCheck(), '[ -e /tmp/fail2ban.test ]')
|
self.assertEqual(self.__action.actioncheck, '[ -e /tmp/fail2ban.test ]')
|
||||||
self.__action.setActionUnban("true")
|
self.__action.actionunban = "true"
|
||||||
self.assertEqual(self.__action.getActionUnban(), 'true')
|
self.assertEqual(self.__action.actionunban, 'true')
|
||||||
|
|
||||||
self.assertFalse(self._is_logged('returned'))
|
self.assertFalse(self._is_logged('returned'))
|
||||||
# no action was actually executed yet
|
# no action was actually executed yet
|
||||||
|
|
||||||
self.assertTrue(self.__action.execActionBan(None))
|
self.__action.ban({'ip': None})
|
||||||
self.assertTrue(self._is_logged('Invariant check failed'))
|
self.assertTrue(self._is_logged('Invariant check failed'))
|
||||||
self.assertTrue(self._is_logged('returned successfully'))
|
self.assertTrue(self._is_logged('returned successfully'))
|
||||||
|
|
||||||
def testExecuteActionEmptyUnban(self):
|
def testExecuteActionEmptyUnban(self):
|
||||||
self.__action.setActionUnban("")
|
self.__action.actionunban = ""
|
||||||
self.assertTrue(self.__action.execActionUnban(None))
|
self.__action.unban({})
|
||||||
self.assertTrue(self._is_logged('Nothing to do'))
|
self.assertTrue(self._is_logged('Nothing to do'))
|
||||||
|
|
||||||
def testExecuteActionStartCtags(self):
|
def testExecuteActionStartCtags(self):
|
||||||
self.__action.setCInfo("HOST","192.0.2.0")
|
self.__action.HOST = "192.0.2.0"
|
||||||
self.__action.setActionStart("touch /tmp/fail2ban.test.<HOST>")
|
self.__action.actionstart = "touch /tmp/fail2ban.test.<HOST>"
|
||||||
self.__action.setActionStop("rm -f /tmp/fail2ban.test.<HOST>")
|
self.__action.actionstop = "rm -f /tmp/fail2ban.test.<HOST>"
|
||||||
self.__action.setActionCheck("[ -e /tmp/fail2ban.test.192.0.2.0 ]")
|
self.__action.actioncheck = "[ -e /tmp/fail2ban.test.192.0.2.0 ]"
|
||||||
self.assertTrue(self.__action.execActionStart())
|
self.__action.start()
|
||||||
|
|
||||||
def testExecuteActionCheckRestoreEnvironment(self):
|
def testExecuteActionCheckRestoreEnvironment(self):
|
||||||
self.__action.setActionStart("")
|
self.__action.actionstart = ""
|
||||||
self.__action.setActionStop("rm -f /tmp/fail2ban.test")
|
self.__action.actionstop = "rm -f /tmp/fail2ban.test"
|
||||||
self.__action.setActionBan("rm /tmp/fail2ban.test")
|
self.__action.actionban = "rm /tmp/fail2ban.test"
|
||||||
self.__action.setActionCheck("[ -e /tmp/fail2ban.test ]")
|
self.__action.actioncheck = "[ -e /tmp/fail2ban.test ]"
|
||||||
self.assertFalse(self.__action.execActionBan(None))
|
self.assertRaises(RuntimeError, self.__action.ban, {'ip': None})
|
||||||
self.assertTrue(self._is_logged('Unable to restore environment'))
|
self.assertTrue(self._is_logged('Unable to restore environment'))
|
||||||
|
|
||||||
def testExecuteActionChangeCtags(self):
|
def testExecuteActionChangeCtags(self):
|
||||||
self.__action.setCInfo("ROST","192.0.2.0")
|
self.assertRaises(AttributeError, getattr, self.__action, "ROST")
|
||||||
self.assertEqual(self.__action.getCInfo("ROST"),"192.0.2.0")
|
self.__action.ROST = "192.0.2.0"
|
||||||
self.__action.delCInfo("ROST")
|
self.assertEqual(self.__action.ROST,"192.0.2.0")
|
||||||
self.assertRaises(KeyError, self.__action.getCInfo, "ROST")
|
|
||||||
|
|
||||||
def testExecuteActionUnbanAinfo(self):
|
def testExecuteActionUnbanAinfo(self):
|
||||||
aInfo = {
|
aInfo = {
|
||||||
'ABC': "123",
|
'ABC': "123",
|
||||||
}
|
}
|
||||||
self.__action.setActionBan("touch /tmp/fail2ban.test.123")
|
self.__action.actionban = "touch /tmp/fail2ban.test.123"
|
||||||
self.__action.setActionUnban("rm /tmp/fail2ban.test.<ABC>")
|
self.__action.actionunban = "rm /tmp/fail2ban.test.<ABC>"
|
||||||
self.assertTrue(self.__action.execActionBan(None))
|
self.__action.ban(aInfo)
|
||||||
self.assertTrue(self.__action.execActionUnban(aInfo))
|
self.__action.unban(aInfo)
|
||||||
|
|
||||||
def testExecuteActionStartEmpty(self):
|
def testExecuteActionStartEmpty(self):
|
||||||
self.__action.setActionStart("")
|
self.__action.actionstart = ""
|
||||||
self.assertTrue(self.__action.execActionStart())
|
self.__action.start()
|
||||||
self.assertTrue(self._is_logged('Nothing to do'))
|
self.assertTrue(self._is_logged('Nothing to do'))
|
||||||
|
|
||||||
def testExecuteIncorrectCmd(self):
|
def testExecuteIncorrectCmd(self):
|
||||||
Action.executeCmd('/bin/ls >/dev/null\nbogusXXX now 2>/dev/null')
|
CommandAction.executeCmd('/bin/ls >/dev/null\nbogusXXX now 2>/dev/null')
|
||||||
self.assertTrue(self._is_logged('HINT on 127: "Command not found"'))
|
self.assertTrue(self._is_logged('HINT on 127: "Command not found"'))
|
||||||
|
|
||||||
def testExecuteTimeout(self):
|
def testExecuteTimeout(self):
|
||||||
stime = time.time()
|
stime = time.time()
|
||||||
Action.executeCmd('sleep 60', timeout=2) # Should take a minute
|
# Should take a minute
|
||||||
|
self.assertRaises(
|
||||||
|
RuntimeError, CommandAction.executeCmd, 'sleep 60', timeout=2)
|
||||||
self.assertAlmostEqual(time.time() - stime, 2, places=0)
|
self.assertAlmostEqual(time.time() - stime, 2, places=0)
|
||||||
self.assertTrue(self._is_logged('sleep 60 -- timed out after 2 seconds'))
|
self.assertTrue(self._is_logged('sleep 60 -- timed out after 2 seconds'))
|
||||||
self.assertTrue(self._is_logged('sleep 60 -- killed with SIGTERM'))
|
self.assertTrue(self._is_logged('sleep 60 -- killed with SIGTERM'))
|
||||||
|
|
||||||
def testCaptureStdOutErr(self):
|
def testCaptureStdOutErr(self):
|
||||||
Action.executeCmd('echo "How now brown cow"')
|
CommandAction.executeCmd('echo "How now brown cow"')
|
||||||
self.assertTrue(self._is_logged("'How now brown cow\\n'"))
|
self.assertTrue(self._is_logged("'How now brown cow\\n'"))
|
||||||
Action.executeCmd(
|
CommandAction.executeCmd(
|
||||||
'echo "The rain in Spain stays mainly in the plain" 1>&2')
|
'echo "The rain in Spain stays mainly in the plain" 1>&2')
|
||||||
self.assertTrue(self._is_logged(
|
self.assertTrue(self._is_logged(
|
||||||
"'The rain in Spain stays mainly in the plain\\n'"))
|
"'The rain in Spain stays mainly in the plain\\n'"))
|
||||||
|
|
||||||
|
def testCallingMap(self):
|
||||||
|
mymap = CallingMap(callme=lambda: str(10), error=lambda: int('a'),
|
||||||
|
dontcallme= "string", number=17)
|
||||||
|
|
||||||
|
# Should work fine
|
||||||
|
self.assertEqual(
|
||||||
|
"%(callme)s okay %(dontcallme)s %(number)i" % mymap,
|
||||||
|
"10 okay string 17")
|
||||||
|
# Error will now trip, demonstrating delayed call
|
||||||
|
self.assertRaises(ValueError, lambda x: "%(error)i" % x, mymap)
|
||||||
|
|
|
@ -26,8 +26,8 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from fail2ban.server.banmanager import BanManager
|
from ..server.banmanager import BanManager
|
||||||
from fail2ban.server.ticket import BanTicket
|
from ..server.ticket import BanTicket
|
||||||
|
|
||||||
class AddFailure(unittest.TestCase):
|
class AddFailure(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -21,15 +21,15 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import os, shutil, sys, tempfile, unittest
|
import os, glob, shutil, sys, tempfile, unittest
|
||||||
|
|
||||||
from fail2ban.client.configreader import ConfigReader
|
from ..client.configreader import ConfigReader
|
||||||
from fail2ban.client.jailreader import JailReader
|
from ..client.jailreader import JailReader
|
||||||
from fail2ban.client.filterreader import FilterReader
|
from ..client.filterreader import FilterReader
|
||||||
from fail2ban.client.jailsreader import JailsReader
|
from ..client.jailsreader import JailsReader
|
||||||
from fail2ban.client.actionreader import ActionReader
|
from ..client.actionreader import ActionReader
|
||||||
from fail2ban.client.configurator import Configurator
|
from ..client.configurator import Configurator
|
||||||
from fail2ban.tests.utils import LogCaptureTestCase
|
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")
|
||||||
if os.path.exists(os.path.join('config','fail2ban.conf')):
|
if os.path.exists(os.path.join('config','fail2ban.conf')):
|
||||||
|
@ -311,6 +311,34 @@ class FilterReaderTest(unittest.TestCase):
|
||||||
output[-1][-1] = "5"
|
output[-1][-1] = "5"
|
||||||
self.assertEqual(sorted(filterReader.convert()), sorted(output))
|
self.assertEqual(sorted(filterReader.convert()), sorted(output))
|
||||||
|
|
||||||
|
|
||||||
|
def testFilterReaderSubstitionDefault(self):
|
||||||
|
output = [['set', 'jailname', 'addfailregex', 'to=sweet@example.com fromip=<IP>']]
|
||||||
|
filterReader = FilterReader('substition', "jailname", {})
|
||||||
|
filterReader.setBaseDir(TEST_FILES_DIR)
|
||||||
|
filterReader.read()
|
||||||
|
filterReader.getOptions(None)
|
||||||
|
c = filterReader.convert()
|
||||||
|
self.assertEqual(sorted(c), sorted(output))
|
||||||
|
|
||||||
|
def testFilterReaderSubstitionSet(self):
|
||||||
|
output = [['set', 'jailname', 'addfailregex', 'to=sour@example.com fromip=<IP>']]
|
||||||
|
filterReader = FilterReader('substition', "jailname", {'honeypot': 'sour@example.com'})
|
||||||
|
filterReader.setBaseDir(TEST_FILES_DIR)
|
||||||
|
filterReader.read()
|
||||||
|
filterReader.getOptions(None)
|
||||||
|
c = filterReader.convert()
|
||||||
|
self.assertEqual(sorted(c), sorted(output))
|
||||||
|
|
||||||
|
def testFilterReaderSubstitionFail(self):
|
||||||
|
output = [['set', 'jailname', 'addfailregex', 'to=sour@example.com fromip=<IP>']]
|
||||||
|
filterReader = FilterReader('substition', "jailname", {'honeypot': '<sweet>', 'sweet': '<honeypot>'})
|
||||||
|
filterReader.setBaseDir(TEST_FILES_DIR)
|
||||||
|
filterReader.read()
|
||||||
|
filterReader.getOptions(None)
|
||||||
|
self.assertRaises(ValueError, FilterReader.convert, filterReader)
|
||||||
|
|
||||||
|
|
||||||
class JailsReaderTest(LogCaptureTestCase):
|
class JailsReaderTest(LogCaptureTestCase):
|
||||||
|
|
||||||
def testProvidingBadBasedir(self):
|
def testProvidingBadBasedir(self):
|
||||||
|
@ -356,13 +384,18 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
['set', 'brokenaction', 'addaction', 'brokenaction'],
|
['set', 'brokenaction', 'addaction', 'brokenaction'],
|
||||||
['set',
|
['set',
|
||||||
'brokenaction',
|
'brokenaction',
|
||||||
'actionban',
|
'action',
|
||||||
'brokenaction',
|
'brokenaction',
|
||||||
|
'actionban',
|
||||||
'hit with big stick <ip>'],
|
'hit with big stick <ip>'],
|
||||||
['set', 'brokenaction', 'actionstop', 'brokenaction', ''],
|
['set', 'brokenaction', 'action', 'brokenaction',
|
||||||
['set', 'brokenaction', 'actionstart', 'brokenaction', ''],
|
'actionstop', ''],
|
||||||
['set', 'brokenaction', 'actionunban', 'brokenaction', ''],
|
['set', 'brokenaction', 'action', 'brokenaction',
|
||||||
['set', 'brokenaction', 'actioncheck', 'brokenaction', ''],
|
'actionstart', ''],
|
||||||
|
['set', 'brokenaction', 'action', 'brokenaction',
|
||||||
|
'actionunban', ''],
|
||||||
|
['set', 'brokenaction', 'action', 'brokenaction',
|
||||||
|
'actioncheck', ''],
|
||||||
['add', 'parse_to_end_of_jail.conf', 'auto'],
|
['add', 'parse_to_end_of_jail.conf', 'auto'],
|
||||||
['set', 'parse_to_end_of_jail.conf', 'usedns', 'warn'],
|
['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', 'maxretry', 3],
|
||||||
|
@ -439,16 +472,20 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
self.assertTrue(actionReader._opts.get('actionban', '').strip())
|
self.assertTrue(actionReader._opts.get('actionban', '').strip())
|
||||||
|
|
||||||
# Verify that all filters found under config/ have a jail
|
# Verify that all filters found under config/ have a jail
|
||||||
def get_all_confs(d):
|
def testReadStockJailFilterComplete(self):
|
||||||
from glob import glob
|
jails = JailsReader(basedir=CONFIG_DIR, force_enable=True)
|
||||||
return set(
|
self.assertTrue(jails.read()) # opens fine
|
||||||
os.path.basename(x.replace('.conf', ''))
|
self.assertTrue(jails.getOptions()) # reads fine
|
||||||
for x in glob(os.path.join(CONFIG_DIR, d, '*.conf')))
|
# grab all filter names
|
||||||
|
filters = set(os.path.splitext(os.path.split(a)[1])[0]
|
||||||
# TODO: provide jails for some additional filters
|
for a in glob.glob(os.path.join('config', 'filter.d', '*.conf'))
|
||||||
# ['gssftpd', 'qmail', 'apache-nohome', 'exim', 'dropbear', 'webmin-auth', 'cyrus-imap', 'sieve']
|
if not a.endswith('common.conf'))
|
||||||
# self.assertEqual(get_all_confs('filter.d').difference(allFilters),
|
filters_jail = set(jail.options['filter'] for jail in jails.jails)
|
||||||
# set(['common']))
|
self.maxDiff = None
|
||||||
|
self.assertTrue(filters.issubset(filters_jail),
|
||||||
|
"More filters exists than are referenced in stock jail.conf %r" % filters.difference(filters_jail))
|
||||||
|
self.assertTrue(filters_jail.issubset(filters),
|
||||||
|
"Stock jail.conf references non-existent filters %r" % filters_jail.difference(filters))
|
||||||
|
|
||||||
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
|
||||||
|
@ -489,7 +526,7 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
self.assertTrue('blocktype' in action._initOpts)
|
self.assertTrue('blocktype' in action._initOpts)
|
||||||
# Verify that we have a call to set it up
|
# Verify that we have a call to set it up
|
||||||
blocktype_present = False
|
blocktype_present = False
|
||||||
target_command = [ 'set', jail_name, 'setcinfo', action_name, 'blocktype' ]
|
target_command = ['set', jail_name, 'action', action_name, 'blocktype']
|
||||||
for command in commands:
|
for command in commands:
|
||||||
if (len(command) > 5 and
|
if (len(command) > 5 and
|
||||||
command[:5] == target_command):
|
command[:5] == target_command):
|
||||||
|
@ -542,6 +579,8 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||||
[testjail1]
|
[testjail1]
|
||||||
action = testaction1[actname=test1]
|
action = testaction1[actname=test1]
|
||||||
testaction1[actname=test2]
|
testaction1[actname=test2]
|
||||||
|
testaction.py
|
||||||
|
testaction.py[actname=test3]
|
||||||
filter = testfilter1
|
filter = testfilter1
|
||||||
""")
|
""")
|
||||||
jailfd.close()
|
jailfd.close()
|
||||||
|
@ -550,8 +589,12 @@ filter = testfilter1
|
||||||
self.assertTrue(jails.getOptions())
|
self.assertTrue(jails.getOptions())
|
||||||
comm_commands = jails.convert(allow_no_files=True)
|
comm_commands = jails.convert(allow_no_files=True)
|
||||||
|
|
||||||
action_names = [comm[-1] for comm in comm_commands if comm[:3] == ['set', 'testjail1', 'addaction']]
|
add_actions = [comm[3:] for comm in comm_commands
|
||||||
|
if comm[:3] == ['set', 'testjail1', 'addaction']]
|
||||||
|
|
||||||
self.assertNotEqual(len(set(action_names)), 1)
|
self.assertEqual(len(set(action[0] for action in add_actions)), 4)
|
||||||
|
|
||||||
|
# Python actions should not be passed `actname`
|
||||||
|
self.assertEqual(add_actions[-1][-1], "{}")
|
||||||
|
|
||||||
shutil.rmtree(basedir)
|
shutil.rmtree(basedir)
|
||||||
|
|
|
@ -28,11 +28,11 @@ import tempfile
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from fail2ban.server.database import Fail2BanDb
|
from ..server.database import Fail2BanDb
|
||||||
from fail2ban.server.filter import FileContainer
|
from ..server.filter import FileContainer
|
||||||
from fail2ban.server.mytime import MyTime
|
from ..server.mytime import MyTime
|
||||||
from fail2ban.server.ticket import FailTicket
|
from ..server.ticket import FailTicket
|
||||||
from fail2ban.tests.dummyjail import DummyJail
|
from .dummyjail import DummyJail
|
||||||
|
|
||||||
class DatabaseTest(unittest.TestCase):
|
class DatabaseTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,10 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import unittest, calendar, time, datetime, re, pprint
|
import unittest, calendar, time, datetime, re, pprint
|
||||||
from fail2ban.server.datedetector import DateDetector
|
from ..server.datedetector import DateDetector
|
||||||
from fail2ban.server.datetemplate import DateTemplate
|
from ..server.datetemplate import DateTemplate
|
||||||
from fail2ban.server.iso8601 import Utc
|
from ..server.iso8601 import Utc
|
||||||
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
|
from .utils import setUpMyTime, tearDownMyTime
|
||||||
|
|
||||||
class DateDetectorTest(unittest.TestCase):
|
class DateDetectorTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,8 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import unittest, socket, time, pickle
|
import unittest, socket, time, pickle
|
||||||
|
|
||||||
from fail2ban.server.failmanager import FailManager, FailManagerEmpty
|
from ..server.failmanager import FailManager, FailManagerEmpty
|
||||||
from fail2ban.server.ticket import FailTicket
|
from ..server.ticket import FailTicket
|
||||||
|
|
||||||
class AddFailure(unittest.TestCase):
|
class AddFailure(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
from fail2ban.server.action import ActionBase
|
||||||
|
|
||||||
|
class TestAction(ActionBase):
|
||||||
|
|
||||||
|
def __init__(self, jail, name, opt1, opt2=None):
|
||||||
|
super(TestAction, self).__init__(jail, name)
|
||||||
|
self._logSys.debug("%s initialised" % self.__class__.__name__)
|
||||||
|
self.opt1 = opt1
|
||||||
|
self.opt2 = opt2
|
||||||
|
self._opt3 = "Hello"
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self._logSys.debug("%s action start" % self.__class__.__name__)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self._logSys.debug("%s action stop" % self.__class__.__name__)
|
||||||
|
|
||||||
|
def ban(self, aInfo):
|
||||||
|
self._logSys.debug("%s action ban" % self.__class__.__name__)
|
||||||
|
|
||||||
|
def unban(self, aInfo):
|
||||||
|
self._logSys.debug("%s action unban" % self.__class__.__name__)
|
||||||
|
|
||||||
|
def testmethod(self, text):
|
||||||
|
return "%s %s %s" % (self._opt3, text, self.opt1)
|
||||||
|
|
||||||
|
Action = TestAction
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
from fail2ban.server.action import ActionBase
|
||||||
|
|
||||||
|
class TestAction(ActionBase):
|
||||||
|
|
||||||
|
def __init__(self, jail, name):
|
||||||
|
super(TestAction, self).__init__(jail, name)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
def ban(self):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
def unban(self):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
Action = TestAction
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
from fail2ban.server.action import ActionBase
|
||||||
|
|
||||||
|
class TestAction(ActionBase):
|
||||||
|
pass
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
from fail2ban.server.action import ActionBase
|
||||||
|
|
||||||
|
class TestAction():
|
||||||
|
|
||||||
|
def __init__(self, jail, name):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
Action = TestAction
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
|
||||||
|
failregex = to=<honeypot> fromip=<IP>
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
|
||||||
|
honeypot = sweet@example.com
|
|
@ -0,0 +1,43 @@
|
||||||
|
# failJSON: { "time": "2008-07-22T06:48:30", "match": true , "host": "198.51.100.86" }
|
||||||
|
[Tue Jul 22 06:48:30 2008] [error] [client 198.51.100.86] script not found or unable to stat: /var/www/wp-login.php
|
||||||
|
|
||||||
|
# failJSON: { "time": "2013-12-23T09:49:10", "match": true , "host": "115.249.248.145" }
|
||||||
|
[Mon Dec 23 09:49:10 2013] [error] [client 115.249.248.145] File does not exist: /var/www/pma
|
||||||
|
# failJSON: { "time": "2013-12-23T09:49:10", "match": true , "host": "115.249.248.145" }
|
||||||
|
[Mon Dec 23 09:49:10 2013] [error] [client 115.249.248.145] File does not exist: /var/www/phpmyadmin
|
||||||
|
# failJSON: { "time": "2013-12-23T09:49:13", "match": true , "host": "115.249.248.145" }
|
||||||
|
[Mon Dec 23 09:49:13 2013] [error] [client 115.249.248.145] File does not exist: /var/www/webmail
|
||||||
|
# failJSON: { "time": "2013-12-23T09:49:13", "match": true , "host": "115.249.248.145" }
|
||||||
|
[Mon Dec 23 09:49:13 2013] [error] [client 115.249.248.145] File does not exist: /var/www/mail
|
||||||
|
|
||||||
|
# failJSON: { "time": "2013-12-31T09:13:47", "match": true , "host": "176.102.37.56" }
|
||||||
|
[Tue Dec 31 09:13:47 2013] [error] [client 176.102.37.56] script '/var/www/wp-login.php' not found or unable to stat
|
||||||
|
|
||||||
|
# failJSON: { "time": "2014-01-03T09:20:23", "match": true , "host": "46.23.77.174" }
|
||||||
|
[Fri Jan 03 09:20:23 2014] [error] [client 46.23.77.174] File does not exist: /var/www/mail
|
||||||
|
# failJSON: { "time": "2014-01-03T09:20:25", "match": true , "host": "46.23.77.174" }
|
||||||
|
[Fri Jan 03 09:20:25 2014] [error] [client 46.23.77.174] File does not exist: /var/www/mail_this_entry
|
||||||
|
# failJSON: { "time": "2014-01-03T09:26:52", "match": true , "host": "46.23.77.174" }
|
||||||
|
[Fri Jan 03 09:26:52 2014] [error] [client 46.23.77.174] File does not exist: /var/www/pmapper-3.2-beta3
|
||||||
|
# failJSON: { "time": "2014-01-03T09:33:53", "match": true , "host": "46.23.77.174" }
|
||||||
|
[Fri Jan 03 09:33:53 2014] [error] [client 46.23.77.174] File does not exist: /var/www/v-webmail
|
||||||
|
# failJSON: { "time": "2014-01-03T09:34:15", "match": true , "host": "46.23.77.174" }
|
||||||
|
[Fri Jan 03 09:34:15 2014] [error] [client 46.23.77.174] File does not exist: /var/www/vwebmail
|
||||||
|
# failJSON: { "time": "2014-01-03T09:35:47", "match": true , "host": "46.23.77.174" }
|
||||||
|
[Fri Jan 03 09:35:47 2014] [error] [client 46.23.77.174] File does not exist: /var/www/webmail
|
||||||
|
# failJSON: { "time": "2013-12-23T21:21:39", "match": true , "host": "183.60.244.49" }
|
||||||
|
[Mon Dec 23 21:21:39 2013] [error] [client 183.60.244.49] File does not exist: /var/www/extmail, referer: http://www.baidu.com
|
||||||
|
# failJSON: { "time": "2013-12-23T21:21:44", "match": true , "host": "183.60.244.49" }
|
||||||
|
[Mon Dec 23 21:21:44 2013] [error] [client 183.60.244.49] File does not exist: /var/www/extmail, referer: http://www.baidu.com
|
||||||
|
# failJSON: { "time": "2013-12-23T21:21:47", "match": true , "host": "183.60.244.49" }
|
||||||
|
[Mon Dec 23 21:21:47 2013] [error] [client 183.60.244.49] File does not exist: /var/www/mails, referer: http://www.baidu.com
|
||||||
|
# failJSON: { "time": "2013-12-23T21:22:00", "match": true , "host": "183.60.244.49" }
|
||||||
|
[Mon Dec 23 21:22:00 2013] [error] [client 183.60.244.49] File does not exist: /var/www/extmail, referer: http://www.baidu.com
|
||||||
|
# failJSON: { "time": "2013-12-23T21:22:16", "match": true , "host": "183.60.244.49" }
|
||||||
|
[Mon Dec 23 21:22:16 2013] [error] [client 183.60.244.49] File does not exist: /var/www/phpmyadmin, referer: http://www.baidu.com
|
||||||
|
|
||||||
|
# failJSON: { "time": "2014-01-03T14:50:39", "match": false , "host": "92.43.20.165" }
|
||||||
|
[Fri Jan 03 14:50:39 2014] [error] [client 92.43.20.165] script '/var/www/forum/mail.php' not found or unable to stat
|
||||||
|
|
||||||
|
# failJSON: { "time": "2014-12-06T09:29:34", "match": false , "host": "122.49.201.178" }
|
||||||
|
[Fri Dec 06 09:29:34 2013] [error] [client 122.49.201.178] client denied by server configuration: /var/www/webmail/.htaccess
|
|
@ -40,6 +40,8 @@
|
||||||
[2009-12-22 16:35:24] NOTICE[14916]: chan_sip.c:15644 handle_request_subscribe: Sending fake auth rejection for user <sip:CS@192.168.2.102>;tag=6pwd6erg54
|
[2009-12-22 16:35:24] NOTICE[14916]: chan_sip.c:15644 handle_request_subscribe: Sending fake auth rejection for user <sip:CS@192.168.2.102>;tag=6pwd6erg54
|
||||||
# failJSON: { "time": "2013-07-06T09:09:25", "match": true , "host": "141.255.164.106" }
|
# failJSON: { "time": "2013-07-06T09:09:25", "match": true , "host": "141.255.164.106" }
|
||||||
[2013-07-06 09:09:25] SECURITY[3308] res_security_log.c: SecurityEvent="InvalidPassword",EventTV="1373098165-824497",Severity="Error",Service="SIP",EventVersion="2",AccountID="972592891005",SessionID="0x88aab6c",LocalAddress="IPV4/UDP/92.28.73.180/5060",RemoteAddress="IPV4/UDP/141.255.164.106/5084",Challenge="41d26de5",ReceivedChallenge="41d26de5",ReceivedHash="7a6a3a2e95a05260aee612896e1b4a39"
|
[2013-07-06 09:09:25] SECURITY[3308] res_security_log.c: SecurityEvent="InvalidPassword",EventTV="1373098165-824497",Severity="Error",Service="SIP",EventVersion="2",AccountID="972592891005",SessionID="0x88aab6c",LocalAddress="IPV4/UDP/92.28.73.180/5060",RemoteAddress="IPV4/UDP/141.255.164.106/5084",Challenge="41d26de5",ReceivedChallenge="41d26de5",ReceivedHash="7a6a3a2e95a05260aee612896e1b4a39"
|
||||||
|
# failJSON: { "time": "2014-01-10T16:39:06", "match": true , "host": "50.30.42.14" }
|
||||||
|
[2014-01-10 16:39:06] SECURITY[1503] res_security_log.c: SecurityEvent="FailedACL",EventTV="1389368346-880526",Severity="Error",Service="SIP",EventVersion="1",AccountID="",SessionID="0x7ff408103b18",LocalAddress="IPV4/UDP/83.11.20.23/5060",RemoteAddress="IPV4/UDP/50.30.42.14/5066",ACLName="domain_must_match"
|
||||||
|
|
||||||
# failJSON: { "time": "2013-11-11T14:33:38", "match": true , "host": "192.168.55.152" }
|
# failJSON: { "time": "2013-11-11T14:33:38", "match": true , "host": "192.168.55.152" }
|
||||||
[2013-11-11 14:33:38] WARNING[6756][C-0000001d] Ext. s: "Rejecting unknown SIP connection from 192.168.55.152"
|
[2013-11-11 14:33:38] WARNING[6756][C-0000001d] Ext. s: "Rejecting unknown SIP connection from 192.168.55.152"
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
# failJSON: { "time": "2014-01-01T01:25:17", "match": true, "host": "31.29.29.89" }
|
||||||
|
L 01/01/2014 - 01:25:17: Bad Rcon: "rcon 1146003691 "284" sv_contact "HLBrute 1.10"" from "31.29.29.89:57370"
|
||||||
|
# failJSON: { "time": "2014-01-01T04:17:01", "match": true, "host": "105.158.241.147" }
|
||||||
|
L 01/01/2014 - 04:17:01: Bad Rcon: "rcon 260639614 "admin" sv_contact "HLBrute 1.10"" from "105.158.241.147:53772"
|
|
@ -7,3 +7,5 @@ I(<0.370.0>:ejabberd_listener:281) : (#Port<0.6910>) Accepted connection {{192,0
|
||||||
=INFO REPORT==== 2013-07-14 17:53:40 ===
|
=INFO REPORT==== 2013-07-14 17:53:40 ===
|
||||||
# failJSON: { "time": "2013-07-14T17:53:40", "match": true , "host": "192.0.2.4" }
|
# failJSON: { "time": "2013-07-14T17:53:40", "match": true , "host": "192.0.2.4" }
|
||||||
I(<0.1440.0>:ejabberd_c2s:813) : ({socket_state,tls,{tlssock,#Port<0.6910>,#Port<0.6912>},<0.1439.0>}) Failed authentication for user@example.com from IP 192.0.2.4 ({{192,0,2,4},12716})
|
I(<0.1440.0>:ejabberd_c2s:813) : ({socket_state,tls,{tlssock,#Port<0.6910>,#Port<0.6912>},<0.1439.0>}) Failed authentication for user@example.com from IP 192.0.2.4 ({{192,0,2,4},12716})
|
||||||
|
# failJSON: { "time": "2014-01-07T18:09:08", "match": true , "host": "1.2.3.4" }
|
||||||
|
2014-01-07 18:09:08.512 [info] <0.22741.1>@ejabberd_c2s:wait_for_feature_request:662 ({socket_state,p1_tls,{tlssock,#Port<0.24718>,#Port<0.24720>},<0.22740.1>}) Failed authentication for test@example.com from IP 1.2.3.4
|
||||||
|
|
|
@ -37,3 +37,6 @@
|
||||||
|
|
||||||
# failJSON: { "time": "2013-09-02T09:19:07", "match": true , "host": "118.233.20.68" }
|
# failJSON: { "time": "2013-09-02T09:19:07", "match": true , "host": "118.233.20.68" }
|
||||||
2013-09-02 09:19:07 login authenticator failed for (gkzwsoju) [118.233.20.68]: 535 Incorrect authentication data
|
2013-09-02 09:19:07 login authenticator failed for (gkzwsoju) [118.233.20.68]: 535 Incorrect authentication data
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
2013-06-15 11:20:36 [2516] 1Unmew-0000ea-SE H=egeftech.static.otenet.gr [83.235.177.148]:32706 I=[1.2.3.4]:25 F=auguriesvbd40@google.com rejected after DATA: This message contains a virus (Sanesecurity.Junk.39934.UNOFFICIAL).
|
2013-06-15 11:20:36 [2516] 1Unmew-0000ea-SE H=egeftech.static.otenet.gr [83.235.177.148]:32706 I=[1.2.3.4]:25 F=auguriesvbd40@google.com rejected after DATA: This message contains a virus (Sanesecurity.Junk.39934.UNOFFICIAL).
|
||||||
# failJSON: { "time": "2013-06-16T02:50:43", "match": true , "host": "111.67.203.114" }
|
# failJSON: { "time": "2013-06-16T02:50:43", "match": true , "host": "111.67.203.114" }
|
||||||
2013-06-16 02:50:43 H=dbs.marsukov.com [111.67.203.114] F=<trudofspiori@mail.ru> rejected RCPT <info@nanomedtech.ua>: rejected because 111.67.203.114 is in a black list at dnsbl.sorbs.net\nCurrently Sending Spam See: http://www.sorbs.net/lookup.shtml?111.67.203.114
|
2013-06-16 02:50:43 H=dbs.marsukov.com [111.67.203.114] F=<trudofspiori@mail.ru> rejected RCPT <info@nanomedtech.ua>: rejected because 111.67.203.114 is in a black list at dnsbl.sorbs.net\nCurrently Sending Spam See: http://www.sorbs.net/lookup.shtml?111.67.203.114
|
||||||
|
# https://github.com/fail2ban/fail2ban/issues/541
|
||||||
|
# failJSON: { "time": "2013-12-30T00:24:50", "match": true , "host": "178.123.108.196" }
|
||||||
|
2013-12-30 00:24:50 1VxPit-000MMd-U4 SA: Action: flagged as Spam but accepted: score=8.2 required=5.0 (scanned in 6/6 secs | Message-Id: 008701cf04ed_24497d70_6cdc7850_@xxx.xx). From <spammer@xxx.xx> (host=ip-4.net-3-2-1.rev.xxx.xx [178.123.108.196]) for trap@example.com
|
||||||
# https://github.com/fail2ban/fail2ban/issues/533
|
# https://github.com/fail2ban/fail2ban/issues/533
|
||||||
# failJSON: { "time": "2013-12-29T15:34:12", "match": true , "host": "188.76.45.72" }
|
# failJSON: { "time": "2013-12-29T15:34:12", "match": true , "host": "188.76.45.72" }
|
||||||
2013-12-29 15:34:12 1VxHRO-000NiI-Ly SA: Action: silently tossed message: score=31.0 required=5.0 trigger=30.0 (scanned in 6/6 secs | Message-Id: etPan.09bd0c40.c3d5f675.fdf7@server.local). From <Flossiedpd@jazztel.es> (host=72.45.76.188.dynamic.jazztel.es [188.76.45.72]) for me@my.com
|
2013-12-29 15:34:12 1VxHRO-000NiI-Ly SA: Action: silently tossed message: score=31.0 required=5.0 trigger=30.0 (scanned in 6/6 secs | Message-Id: etPan.09bd0c40.c3d5f675.fdf7@server.local). From <Flossiedpd@jazztel.es> (host=72.45.76.188.dynamic.jazztel.es [188.76.45.72]) for me@my.com
|
||||||
|
@ -21,4 +24,3 @@
|
||||||
# failJSON: { "time": "2013-12-29T15:39:11", "match": true , "host": "178.123.108.196" }
|
# failJSON: { "time": "2013-12-29T15:39:11", "match": true , "host": "178.123.108.196" }
|
||||||
2013-12-29 15:39:11 1VxHWD-000NuW-83 SA: Action: silently tossed message: score=35.8 required=5.0 trigger=30.0 (scanned in 6/6 secs | Message-Id: 1VxHWD-000NuW-83). From <> (host=NULL [178.123.108.196]) for me@my.com
|
2013-12-29 15:39:11 1VxHWD-000NuW-83 SA: Action: silently tossed message: score=35.8 required=5.0 trigger=30.0 (scanned in 6/6 secs | Message-Id: 1VxHWD-000NuW-83). From <> (host=NULL [178.123.108.196]) for me@my.com
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# failJSON: { "time": "2013-12-31T17:39:54", "match": true, "host": "81.94.202.251" }
|
||||||
|
2013-12-31 17:39:54.767815 [WARNING] sofia_reg.c:1533 SIP auth challenge (INVITE) on sofia profile 'internal' for [011448708752617@192.168.2.51] from ip 81.94.202.251
|
||||||
|
# failJSON: { "time": "2013-12-31T17:39:54", "match": true, "host": "5.11.47.236" }
|
||||||
|
2013-12-31 17:39:54.767815 [WARNING] sofia_reg.c:1478 SIP auth failure (INVITE) on sofia profile 'internal' for [000972543480510@192.168.2.51] from ip 5.11.47.236
|
||||||
|
# failJSON: { "time": "2013-12-31T17:39:54", "match": false }
|
||||||
|
2013-12-31 17:39:54.767815 [DEBUG] sofia.c:7954 IP 185.24.234.141 Rejected by acl "domains". Falling back to Digest auth.
|
||||||
|
|
||||||
|
# failJSON: { "time": "2013-12-31T17:39:54", "match": true, "host": "5.11.47.236" }
|
||||||
|
2013-12-31 17:39:54.767815 [WARNING] sofia_reg.c:2531 Can't find user [1001@192.168.2.51] from 5.11.47.236
|
||||||
|
# failJSON: { "time": "2013-12-31T17:39:54", "match": true, "host": "185.24.234.141" }
|
||||||
|
2013-12-31 17:39:54.767815 [WARNING] sofia_reg.c:2531 Can't find user [100@192.168.2.51] from 185.24.234.141
|
|
@ -0,0 +1,4 @@
|
||||||
|
# failJSON: { "time": "2014-01-06T10:59:38", "match": true, "host": "127.0.0.1" }
|
||||||
|
[2014-01-06 10:59:38]LOGIN FAILED for user: "asdsad" from IP: 127.0.0.1
|
||||||
|
# failJSON: { "time": "2014-01-06T10:59:49", "match": false, "host": "127.0.0.1" }
|
||||||
|
[2014-01-06 10:59:49]LOGIN SUCCESS for user: "admin" from IP: 127.0.0.1
|
|
@ -10,3 +10,13 @@ Jul 18 23:12:56 xxx postfix/smtpd[8738]: NOQUEUE: reject: RCPT from foo[192.51.1
|
||||||
Jul 18 23:12:56 xxx postfix/smtpd[8738]: NOQUEUE: reject: RCPT from foo[192.51.100.43]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<192.51.100.43>
|
Jul 18 23:12:56 xxx postfix/smtpd[8738]: NOQUEUE: reject: RCPT from foo[192.51.100.43]: 554 5.7.1 <foo@bad.domain>: Sender address rejected: match bad.domain; from=<foo@bad.domain> to=<foo@porcupine.org> proto=SMTP helo=<192.51.100.43>
|
||||||
# failJSON: { "time": "2005-08-10T10:55:38", "match": true , "host": "72.53.132.234" }
|
# failJSON: { "time": "2005-08-10T10:55:38", "match": true , "host": "72.53.132.234" }
|
||||||
Aug 10 10:55:38 f-vanier-bourgeois postfix/smtpd[2162]: NOQUEUE: reject: VRFY from 72-53-132-234.cpe.distributel.net[72.53.132.234]: 550 5.1.1 : Recipient address rejected: User unknown in local recipient tab
|
Aug 10 10:55:38 f-vanier-bourgeois postfix/smtpd[2162]: NOQUEUE: reject: VRFY from 72-53-132-234.cpe.distributel.net[72.53.132.234]: 550 5.1.1 : Recipient address rejected: User unknown in local recipient tab
|
||||||
|
|
||||||
|
|
||||||
|
# failJSON: { "time": "2005-01-12T11:07:49", "match": true , "host": "181.21.131.88" }
|
||||||
|
Jan 12 11:07:49 emf1pt2-2-35-70 postfix/smtpd[13767]: improper command pipelining after DATA from unknown[181.21.131.88]:
|
||||||
|
|
||||||
|
# failJSON: { "time": "2004-12-25T02:35:54", "match": true , "host": "173.10.140.217" }
|
||||||
|
Dec 25 02:35:54 platypus postfix/smtpd[9144]: improper command pipelining after RSET from 173-10-140-217-BusName-washingtonDC.hfc.comcastbusiness.net[173.10.140.217]
|
||||||
|
|
||||||
|
# failJSON: { "time": "2004-12-18T02:05:46", "match": true , "host": "216.245.198.245" }
|
||||||
|
Dec 18 02:05:46 platypus postfix/smtpd[16349]: improper command pipelining after NOOP from unknown[216.245.198.245]
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
# failJSON: { "time": "2013-10-06T15:50:41", "match": true , "host": "151.64.44.11" }
|
||||||
|
10/06/2013 15:50:41 [LOGIN_ERROR] dadas (mydomain.org) from 151.64.44.11: Unknown user or password incorrect.
|
|
@ -119,7 +119,6 @@ Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 po
|
||||||
Nov 11 08:04:51 redbamboo sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2
|
Nov 11 08:04:51 redbamboo sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# failJSON: { "match": false }
|
# failJSON: { "match": false }
|
||||||
Nov 23 21:50:19 sshd[8148]: Disconnecting: Too many authentication failures for root [preauth]
|
Nov 23 21:50:19 sshd[8148]: Disconnecting: Too many authentication failures for root [preauth]
|
||||||
# failJSON: { "time": "2004-11-23T21:50:37", "match": true , "host": "61.0.0.1", "desc": "Multiline match for preauth failures" }
|
# failJSON: { "time": "2004-11-23T21:50:37", "match": true , "host": "61.0.0.1", "desc": "Multiline match for preauth failures" }
|
||||||
|
@ -129,3 +128,7 @@ Nov 23 21:50:37 sshd[8148]: Connection closed by 61.0.0.1 [preauth]
|
||||||
Nov 23 21:50:19 sshd[9148]: Disconnecting: Too many authentication failures for root [preauth]
|
Nov 23 21:50:19 sshd[9148]: Disconnecting: Too many authentication failures for root [preauth]
|
||||||
# failJSON: { "match": false , "desc": "Pids don't match" }
|
# failJSON: { "match": false , "desc": "Pids don't match" }
|
||||||
Nov 23 21:50:37 sshd[7148]: Connection closed by 61.0.0.1 [preauth]
|
Nov 23 21:50:37 sshd[7148]: Connection closed by 61.0.0.1 [preauth]
|
||||||
|
|
||||||
|
# failJSON: { "time": "2005-07-13T18:44:28", "match": true , "host": "89.24.13.192", "desc": "from gh-289" }
|
||||||
|
Jul 13 18:44:28 mdop sshd[4931]: Received disconnect from 89.24.13.192: 3: com.jcraft.jsch.JSchException: Auth fail
|
||||||
|
|
||||||
|
|
|
@ -34,19 +34,16 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
journal = None
|
journal = None
|
||||||
|
|
||||||
from fail2ban.server.jail import Jail
|
from ..server.jail import Jail
|
||||||
from fail2ban.server.filterpoll import FilterPoll
|
from ..server.filterpoll import FilterPoll
|
||||||
from fail2ban.server.filter import Filter, FileFilter, DNSUtils
|
from ..server.filter import Filter, FileFilter, DNSUtils
|
||||||
from fail2ban.server.failmanager import FailManager
|
from ..server.failmanager import FailManager, FailManagerEmpty
|
||||||
from fail2ban.server.failmanager import FailManagerEmpty
|
from ..server.mytime import MyTime
|
||||||
from fail2ban.server.mytime import MyTime
|
from .utils import setUpMyTime, tearDownMyTime, mtimesleep, LogCaptureTestCase
|
||||||
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
|
from .dummyjail import DummyJail
|
||||||
from fail2ban.tests.utils import mtimesleep, LogCaptureTestCase
|
|
||||||
|
|
||||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||||
|
|
||||||
from fail2ban.tests.dummyjail import DummyJail
|
|
||||||
|
|
||||||
# yoh: per Steven Hiscocks's insight while troubleshooting
|
# yoh: per Steven Hiscocks's insight while troubleshooting
|
||||||
# https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836
|
# https://github.com/fail2ban/fail2ban/issues/103#issuecomment-15542836
|
||||||
# adding a sufficiently large buffer might help to guarantee that
|
# adding a sufficiently large buffer might help to guarantee that
|
||||||
|
|
|
@ -28,8 +28,8 @@ import shutil
|
||||||
|
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
|
||||||
from utils import mbasename, TraceBack, FormatterWithTraceBack
|
from .utils import mbasename, TraceBack, FormatterWithTraceBack
|
||||||
from fail2ban.helpers import formatExceptionInfo
|
from ..helpers import formatExceptionInfo
|
||||||
|
|
||||||
class HelpersTest(unittest.TestCase):
|
class HelpersTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ class TestsUtilsTest(unittest.TestCase):
|
||||||
self.assertTrue(pindex > 10) # we should have some traceback
|
self.assertTrue(pindex > 10) # we should have some traceback
|
||||||
self.assertEqual(s[:pindex], s[pindex+1:pindex*2 + 1])
|
self.assertEqual(s[:pindex], s[pindex+1:pindex*2 + 1])
|
||||||
|
|
||||||
from fail2ban.server import iso8601
|
from ..server import iso8601
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,9 @@ else:
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
next = lambda x: x.next()
|
next = lambda x: x.next()
|
||||||
|
|
||||||
from fail2ban.server.filter import Filter
|
from ..server.filter import Filter
|
||||||
from fail2ban.client.filterreader import FilterReader
|
from ..client.filterreader import FilterReader
|
||||||
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
|
from .utils import setUpMyTime, tearDownMyTime
|
||||||
|
|
||||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||||
if os.path.exists('config/fail2ban.conf'):
|
if os.path.exists('config/fail2ban.conf'):
|
||||||
|
@ -151,6 +151,7 @@ def testSampleRegexsFactory(name):
|
||||||
|
|
||||||
for filter_ in filter(lambda x: not x.endswith('common.conf'), os.listdir(os.path.join(CONFIG_DIR, "filter.d"))):
|
for filter_ in filter(lambda x: not x.endswith('common.conf'), os.listdir(os.path.join(CONFIG_DIR, "filter.d"))):
|
||||||
filterName = filter_.rpartition(".")[0]
|
filterName = filter_.rpartition(".")[0]
|
||||||
|
if not filterName.startswith('.'):
|
||||||
setattr(
|
setattr(
|
||||||
FilterSamplesRegex,
|
FilterSamplesRegex,
|
||||||
"testSampleRegexs%s" % filterName.upper(),
|
"testSampleRegexs%s" % filterName.upper(),
|
||||||
|
|
|
@ -26,14 +26,14 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import unittest, socket, time, tempfile, os, locale, sys, logging
|
import unittest, socket, time, tempfile, os, locale, sys, logging
|
||||||
|
|
||||||
from fail2ban.server.failregex import Regex, FailRegex, RegexException
|
from ..server.failregex import Regex, FailRegex, RegexException
|
||||||
from fail2ban.server.server import Server, logSys
|
from ..server.server import Server, logSys
|
||||||
from fail2ban.server.jail import Jail
|
from ..server.jail import Jail
|
||||||
from fail2ban.exceptions import UnknownJailException
|
from ..exceptions import UnknownJailException
|
||||||
from fail2ban.tests.utils import LogCaptureTestCase
|
from .utils import LogCaptureTestCase
|
||||||
#from bin.fail2ban-client import Fail2banClient
|
#from bin.fail2ban-client import Fail2banClient
|
||||||
try:
|
try:
|
||||||
from fail2ban.server import filtersystemd
|
from ..server import filtersystemd
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
filtersystemd = None
|
filtersystemd = None
|
||||||
|
|
||||||
|
@ -518,45 +518,38 @@ class Transmitter(TransmitterBase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["set", self.jailName, "addaction", action]),
|
self.transm.proceed(["set", self.jailName, "addaction", action]),
|
||||||
(0, action))
|
(0, action))
|
||||||
self.assertEqual(
|
|
||||||
self.transm.proceed(["get", self.jailName, "addaction"]),
|
|
||||||
(0, action))
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["get", self.jailName, "actions"])[1][0].getName(),
|
["get", self.jailName, "actions"])[1][0],
|
||||||
action)
|
action)
|
||||||
for cmd, value in zip(cmdList, cmdValueList):
|
for cmd, value in zip(cmdList, cmdValueList):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["set", self.jailName, cmd, action, value]),
|
["set", self.jailName, "action", action, cmd, value]),
|
||||||
(0, value))
|
(0, value))
|
||||||
for cmd, value in zip(cmdList, cmdValueList):
|
for cmd, value in zip(cmdList, cmdValueList):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["get", self.jailName, cmd, action]),
|
self.transm.proceed(["get", self.jailName, "action", action, cmd]),
|
||||||
(0, value))
|
(0, value))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["set", self.jailName, "setcinfo", action, "KEY", "VALUE"]),
|
["set", self.jailName, "action", action, "KEY", "VALUE"]),
|
||||||
(0, "VALUE"))
|
(0, "VALUE"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["get", self.jailName, "cinfo", action, "KEY"]),
|
["get", self.jailName, "action", action, "KEY"]),
|
||||||
(0, "VALUE"))
|
(0, "VALUE"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["get", self.jailName, "cinfo", action, "InvalidKey"])[0],
|
["get", self.jailName, "action", action, "InvalidKey"])[0],
|
||||||
1)
|
1)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["set", self.jailName, "delcinfo", action, "KEY"]),
|
["set", self.jailName, "action", action, "timeout", "10"]),
|
||||||
(0, None))
|
|
||||||
self.assertEqual(
|
|
||||||
self.transm.proceed(
|
|
||||||
["set", self.jailName, "timeout", action, "10"]),
|
|
||||||
(0, 10))
|
(0, 10))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["get", self.jailName, "timeout", action]),
|
["get", self.jailName, "action", action, "timeout"]),
|
||||||
(0, 10))
|
(0, 10))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.transm.proceed(["set", self.jailName, "delaction", action]),
|
self.transm.proceed(["set", self.jailName, "delaction", action]),
|
||||||
|
@ -565,6 +558,42 @@ class Transmitter(TransmitterBase):
|
||||||
self.transm.proceed(
|
self.transm.proceed(
|
||||||
["set", self.jailName, "delaction", "Doesn't exist"])[0],1)
|
["set", self.jailName, "delaction", "Doesn't exist"])[0],1)
|
||||||
|
|
||||||
|
def testPythonActionMethodsAndProperties(self):
|
||||||
|
action = "TestCaseAction"
|
||||||
|
self.assertEqual(
|
||||||
|
self.transm.proceed(["set", self.jailName, "addaction", action,
|
||||||
|
os.path.join(TEST_FILES_DIR, "action.d", "action.py"),
|
||||||
|
'{"opt1": "value"}']),
|
||||||
|
(0, action))
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(self.transm.proceed(["get", self.jailName,
|
||||||
|
"actionproperties", action])[1]),
|
||||||
|
['opt1', 'opt2'])
|
||||||
|
self.assertEqual(
|
||||||
|
self.transm.proceed(["get", self.jailName, "action", action,
|
||||||
|
"opt1"]),
|
||||||
|
(0, 'value'))
|
||||||
|
self.assertEqual(
|
||||||
|
self.transm.proceed(["get", self.jailName, "action", action,
|
||||||
|
"opt2"]),
|
||||||
|
(0, None))
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(self.transm.proceed(["get", self.jailName, "actionmethods",
|
||||||
|
action])[1]),
|
||||||
|
['ban', 'start', 'stop', 'testmethod', 'unban'])
|
||||||
|
self.assertEqual(
|
||||||
|
self.transm.proceed(["set", self.jailName, "action", action,
|
||||||
|
"testmethod", '{"text": "world!"}']),
|
||||||
|
(0, 'Hello world! value'))
|
||||||
|
self.assertEqual(
|
||||||
|
self.transm.proceed(["set", self.jailName, "action", action,
|
||||||
|
"opt1", "another value"]),
|
||||||
|
(0, 'another value'))
|
||||||
|
self.assertEqual(
|
||||||
|
self.transm.proceed(["set", self.jailName, "action", action,
|
||||||
|
"testmethod", '{"text": "world!"}']),
|
||||||
|
(0, 'Hello world! another value'))
|
||||||
|
|
||||||
def testNOK(self):
|
def testNOK(self):
|
||||||
self.assertEqual(self.transm.proceed(["INVALID", "COMMAND"])[0],1)
|
self.assertEqual(self.transm.proceed(["INVALID", "COMMAND"])[0],1)
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,8 @@ __license__ = "GPL"
|
||||||
|
|
||||||
import unittest, time, tempfile, os, threading
|
import unittest, time, tempfile, os, threading
|
||||||
|
|
||||||
from fail2ban.server.asyncserver import AsyncServer, AsyncServerException
|
from ..server.asyncserver import AsyncServer, AsyncServerException
|
||||||
from fail2ban.client.csocket import CSocket
|
from ..client.csocket import CSocket
|
||||||
|
|
||||||
class Socket(unittest.TestCase):
|
class Socket(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -25,16 +25,9 @@ __license__ = "GPL"
|
||||||
import logging, os, re, traceback, time, unittest, sys
|
import logging, os, re, traceback, time, unittest, sys
|
||||||
from os.path import basename, dirname
|
from os.path import basename, dirname
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
import json
|
||||||
|
|
||||||
if sys.version_info >= (2, 6):
|
from ..server.mytime import MyTime
|
||||||
import json
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
import simplejson as json
|
|
||||||
except ImportError:
|
|
||||||
json = None
|
|
||||||
|
|
||||||
from fail2ban.server.mytime import MyTime
|
|
||||||
|
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -138,19 +131,18 @@ def tearDownMyTime():
|
||||||
def gatherTests(regexps=None, no_network=False):
|
def gatherTests(regexps=None, no_network=False):
|
||||||
# Import all the test cases here instead of a module level to
|
# Import all the test cases here instead of a module level to
|
||||||
# avoid circular imports
|
# avoid circular imports
|
||||||
from fail2ban.tests import banmanagertestcase
|
from . import banmanagertestcase
|
||||||
from fail2ban.tests import clientreadertestcase
|
from . import clientreadertestcase
|
||||||
from fail2ban.tests import failmanagertestcase
|
from . import failmanagertestcase
|
||||||
from fail2ban.tests import filtertestcase
|
from . import filtertestcase
|
||||||
from fail2ban.tests import servertestcase
|
from . import servertestcase
|
||||||
from fail2ban.tests import datedetectortestcase
|
from . import datedetectortestcase
|
||||||
from fail2ban.tests import actiontestcase
|
from . import actiontestcase
|
||||||
from fail2ban.tests import actionstestcase
|
from . import actionstestcase
|
||||||
from fail2ban.tests import sockettestcase
|
from . import sockettestcase
|
||||||
from fail2ban.tests import misctestcase
|
from . import misctestcase
|
||||||
from fail2ban.tests import databasetestcase
|
from . import databasetestcase
|
||||||
if json:
|
from . import samplestestcase
|
||||||
from fail2ban.tests import samplestestcase
|
|
||||||
|
|
||||||
if not regexps: # pragma: no cover
|
if not regexps: # pragma: no cover
|
||||||
tests = unittest.TestSuite()
|
tests = unittest.TestSuite()
|
||||||
|
@ -172,7 +164,7 @@ def gatherTests(regexps=None, no_network=False):
|
||||||
tests.addTest(unittest.makeSuite(servertestcase.Transmitter))
|
tests.addTest(unittest.makeSuite(servertestcase.Transmitter))
|
||||||
tests.addTest(unittest.makeSuite(servertestcase.JailTests))
|
tests.addTest(unittest.makeSuite(servertestcase.JailTests))
|
||||||
tests.addTest(unittest.makeSuite(servertestcase.RegexTests))
|
tests.addTest(unittest.makeSuite(servertestcase.RegexTests))
|
||||||
tests.addTest(unittest.makeSuite(actiontestcase.ExecuteAction))
|
tests.addTest(unittest.makeSuite(actiontestcase.CommandActionTest))
|
||||||
tests.addTest(unittest.makeSuite(actionstestcase.ExecuteActions))
|
tests.addTest(unittest.makeSuite(actionstestcase.ExecuteActions))
|
||||||
# FailManager
|
# FailManager
|
||||||
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure))
|
tests.addTest(unittest.makeSuite(failmanagertestcase.AddFailure))
|
||||||
|
@ -207,30 +199,27 @@ def gatherTests(regexps=None, no_network=False):
|
||||||
|
|
||||||
# DateDetector
|
# DateDetector
|
||||||
tests.addTest(unittest.makeSuite(datedetectortestcase.DateDetectorTest))
|
tests.addTest(unittest.makeSuite(datedetectortestcase.DateDetectorTest))
|
||||||
if json:
|
|
||||||
# Filter Regex tests with sample logs
|
# Filter Regex tests with sample logs
|
||||||
tests.addTest(unittest.makeSuite(samplestestcase.FilterSamplesRegex))
|
tests.addTest(unittest.makeSuite(samplestestcase.FilterSamplesRegex))
|
||||||
else:
|
|
||||||
logSys.warning("I: Skipping filter samples testing. No simplejson/json module")
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Extensive use-tests of different available filters backends
|
# Extensive use-tests of different available filters backends
|
||||||
#
|
#
|
||||||
|
|
||||||
from fail2ban.server.filterpoll import FilterPoll
|
from ..server.filterpoll import FilterPoll
|
||||||
filters = [FilterPoll] # always available
|
filters = [FilterPoll] # always available
|
||||||
|
|
||||||
# Additional filters available only if external modules are available
|
# Additional filters available only if external modules are available
|
||||||
# yoh: Since I do not know better way for parametric tests
|
# yoh: Since I do not know better way for parametric tests
|
||||||
# with good old unittest
|
# with good old unittest
|
||||||
try:
|
try:
|
||||||
from fail2ban.server.filtergamin import FilterGamin
|
from ..server.filtergamin import FilterGamin
|
||||||
filters.append(FilterGamin)
|
filters.append(FilterGamin)
|
||||||
except Exception, e: # pragma: no cover
|
except Exception, e: # pragma: no cover
|
||||||
logSys.warning("Skipping gamin backend testing. Got exception '%s'" % e)
|
logSys.warning("Skipping gamin backend testing. Got exception '%s'" % e)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from fail2ban.server.filterpyinotify import FilterPyinotify
|
from ..server.filterpyinotify import FilterPyinotify
|
||||||
filters.append(FilterPyinotify)
|
filters.append(FilterPyinotify)
|
||||||
except Exception, e: # pragma: no cover
|
except Exception, e: # pragma: no cover
|
||||||
logSys.warning("I: Skipping pyinotify backend testing. Got exception '%s'" % e)
|
logSys.warning("I: Skipping pyinotify backend testing. Got exception '%s'" % e)
|
||||||
|
@ -239,7 +228,7 @@ def gatherTests(regexps=None, no_network=False):
|
||||||
tests.addTest(unittest.makeSuite(
|
tests.addTest(unittest.makeSuite(
|
||||||
filtertestcase.get_monitor_failures_testcase(Filter_)))
|
filtertestcase.get_monitor_failures_testcase(Filter_)))
|
||||||
try: # pragma: systemd no cover
|
try: # pragma: systemd no cover
|
||||||
from fail2ban.server.filtersystemd import FilterSystemd
|
from ..server.filtersystemd import FilterSystemd
|
||||||
tests.addTest(unittest.makeSuite(filtertestcase.get_monitor_failures_journal_testcase(FilterSystemd)))
|
tests.addTest(unittest.makeSuite(filtertestcase.get_monitor_failures_journal_testcase(FilterSystemd)))
|
||||||
except Exception, e: # pragma: no cover
|
except Exception, e: # pragma: no cover
|
||||||
logSys.warning("I: Skipping systemd backend testing. Got exception '%s'" % e)
|
logSys.warning("I: Skipping systemd backend testing. Got exception '%s'" % e)
|
||||||
|
|
|
@ -108,6 +108,7 @@ my ($critical,$warning) = (2,1);
|
||||||
my $fail2ban_client_path = '/usr/bin/fail2ban-client';
|
my $fail2ban_client_path = '/usr/bin/fail2ban-client';
|
||||||
my $fail2ban_socket = '';
|
my $fail2ban_socket = '';
|
||||||
my $jail_specific = '';
|
my $jail_specific = '';
|
||||||
|
my $jail_name = '';
|
||||||
|
|
||||||
GetOptions (
|
GetOptions (
|
||||||
'P=s' => \ $fail2ban_client_path,
|
'P=s' => \ $fail2ban_client_path,
|
||||||
|
@ -190,7 +191,7 @@ if ($jail_specific) {
|
||||||
else {
|
else {
|
||||||
$how_many_banned = int($current_ban_number);
|
$how_many_banned = int($current_ban_number);
|
||||||
$return_print = $how_many_banned.' current banned IP(s) for the specific jail '.$jail_specific;
|
$return_print = $how_many_banned.' current banned IP(s) for the specific jail '.$jail_specific;
|
||||||
$perf_print .= "$jail_name.currentBannedIP=$current_ban_number " if ($perfdata_value);
|
$perf_print .= "$current_ban_number " if ($perfdata_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
### To analyze all the jail
|
### To analyze all the jail
|
||||||
|
|
302
man/jail.conf.5
302
man/jail.conf.5
|
@ -3,23 +3,34 @@
|
||||||
jail.conf \- configuration for the fail2ban server
|
jail.conf \- configuration for the fail2ban server
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|
||||||
.I fail2ban.conf fail2ban.d/*.conf fail2ban.d/*.local
|
.I fail2ban.conf fail2ban.d/*.conf fail2ban.local fail2ban.d/*.local
|
||||||
|
|
||||||
.I jail.conf / jail.local
|
.I jail.conf jail.d/*.conf jail.local jail.d/*.local
|
||||||
|
|
||||||
.I action.d/*.conf action.d/*.local
|
.I action.d/*.conf action.d/*.local action.d/*.py
|
||||||
|
|
||||||
.I filter.d/*.conf filter.d/*.local
|
.I filter.d/*.conf filter.d/*.local
|
||||||
.SH DESCRIPTION
|
|
||||||
Fail2ban has three configuration file types. Action files are the commands for banning and unbanning of IP address,
|
|
||||||
Filter files tell fail2ban how to detect authentication failures, and Jail configurations combine filters with actions into jails.
|
|
||||||
|
|
||||||
There are *.conf files that are distributed by fail2ban and *.local file that contain user customizations.
|
.SH DESCRIPTION
|
||||||
All configuration files should be UTF-8 encoded for python3.
|
Fail2ban has four configuration file types:
|
||||||
It is recommended that *.conf files should remain unchanged. If needed, customizations should be provided in *.local files.
|
|
||||||
For instance, if you would like to customize the [ssh-iptables-ipset] jail, create a jail.local to extend jail.conf
|
.TP
|
||||||
(the configuration for the fail2ban server). The jail.local file will be the following if you only need to enable
|
\fIfail2ban.conf\fR
|
||||||
it:
|
Fail2Ban global configuration (such as logging)
|
||||||
|
.TP
|
||||||
|
\fIfilter.d/*.conf\fR
|
||||||
|
Filters specifying how to detect authentication failures
|
||||||
|
.TP
|
||||||
|
\fIaction.d/*.conf\fR
|
||||||
|
Actions defining the commands for banning and unbanning of IP address
|
||||||
|
.TP
|
||||||
|
\fIjail.conf\fR
|
||||||
|
Jails defining combinations of Filters with Actions.
|
||||||
|
|
||||||
|
|
||||||
|
.SH "CONFIGURATION FILES FORMAT"
|
||||||
|
|
||||||
|
\fI*.conf\fR files are distributed by Fail2Ban. It is recommended that *.conf files should remain unchanged to ease upgrades. If needed, customizations should be provided in \fI*.local\fR files. For example, if you would like to enable the [ssh-iptables-ipset] jail specified in jail.conf, create jail.local containing
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fIjail.local\fR
|
\fIjail.local\fR
|
||||||
|
@ -28,134 +39,194 @@ it:
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
Override only the settings you need to change and the rest of the configuration will come from the corresponding
|
In .local files specify only the settings you would like to change and the rest of the configuration will then come from the corresponding .conf file which is parsed first.
|
||||||
*.conf file.
|
|
||||||
|
.TP
|
||||||
|
\fIjail.d/\fR and \fIfail2ban.d/\fR
|
||||||
|
|
||||||
|
In addition to .local, for jail.conf or fail2ban.conf file there can
|
||||||
|
be a corresponding \fI.d/\fR directory containing additional .conf
|
||||||
|
files. The order e.g. for \fIjail\fR configuration would be:
|
||||||
|
|
||||||
\fI*.d/\fR
|
|
||||||
.RS
|
.RS
|
||||||
In addition to .local, for any .conf file there can be a corresponding
|
jail.conf
|
||||||
\fI.d/\fR directory to contain additional .conf files that will be read after the
|
.RE
|
||||||
appropriate .local file. Last parsed file will take precidence over
|
.RS
|
||||||
identical entries, parsed alphabetically, e.g.
|
jail.d/*.conf (in alphabetical order)
|
||||||
|
.RE
|
||||||
|
.RS
|
||||||
|
jail.local
|
||||||
|
.RE
|
||||||
|
.RS
|
||||||
|
jail.d/*.local (in alphabetical order).
|
||||||
|
|
||||||
|
i.e. all .local files are parsed after .conf files in the original
|
||||||
|
configuration file and files under .d directory. Settings in the file
|
||||||
|
parsed later take precedence over identical entries in previously
|
||||||
|
parsed files. Files are ordered alphabetically, e.g.
|
||||||
|
|
||||||
|
\fIfail2ban.d/01_custom_log.conf\fR - to use a different log path
|
||||||
|
.RE
|
||||||
.RS
|
.RS
|
||||||
\fIjail.d/01_enable.conf\fR - to enable a specific jail
|
\fIjail.d/01_enable.conf\fR - to enable a specific jail
|
||||||
.RE
|
.RE
|
||||||
.RS
|
.RS
|
||||||
\fIjail.d/02_custom_port.conf\fR - containing specific configuration entry to change the port of the jail specified in the configuration
|
\fIjail.d/02_custom_port.conf\fR - to change the port(s) of a jail.
|
||||||
|
.RE
|
||||||
|
.RE
|
||||||
|
.RE
|
||||||
|
|
||||||
|
Configuration files have sections, those specified with [section name], and name = value pairs. For those name items that can accept multiple values, specify the values separated by spaces, or in separate lines space indented at the beginning of the line before the second value.
|
||||||
|
|
||||||
|
.PP
|
||||||
|
Configuration files can include other (defining common variables) configuration files, which is often used in Filters and Actions. Such inclusions are defined in a section called [INCLUDES]:
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B before
|
||||||
|
indicates that the specified file is to be parsed before the current file.
|
||||||
|
.TP
|
||||||
|
.B after
|
||||||
|
indicates that the specified file is to be parsed after the current file.
|
||||||
|
.RE
|
||||||
|
|
||||||
|
Using Python "string interpolation" mechanisms, other definitions are allowed and can later be used within other definitions as %(name)s. For example.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
baduseragents = IE|wget
|
||||||
.RE
|
.RE
|
||||||
.RS
|
.RS
|
||||||
\fIfail2ban.d/01_custom_log.conf\fR - containing specific configuration entry to use a different log path.
|
failregex = useragent=%(baduseragents)s
|
||||||
.RE
|
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
The order \fIjail\fR configuration is parsed is:
|
Comments: use '#' for comment lines and '; ' (space is important) for inline comments. When using Python2.X '; ' can only be used on the first line due to an Python library bug.
|
||||||
|
|
||||||
jail.conf ,
|
.SH "FAIL2BAN CONFIGURATION FILE(S) (\fIfail2ban.conf\fB)"
|
||||||
jail.d/*.conf (in alphabetical order),
|
|
||||||
jail.local, followed by
|
|
||||||
jail.d/*.local (in alphabetical order).
|
|
||||||
|
|
||||||
Likewise for fail2ban configuration.
|
These files have one section, [Definition].
|
||||||
|
|
||||||
Comments: use '#' for comment lines and ';' (following a space) for inline comments
|
The items that can be set are:
|
||||||
|
.TP
|
||||||
|
.B loglevel
|
||||||
|
verbosity level of log output: 1 = ERROR, 2 = WARN, 3 = INFO, 4 = DEBUG. Default: 1
|
||||||
|
.TP
|
||||||
|
.B logtarget
|
||||||
|
log target: filename, SYSLOG, STDERR or STDOUT. Default: STDERR . Only a single log target can be specified.
|
||||||
|
If you change logtarget from the default value and you are using logrotate -- also adjust or disable rotation in the
|
||||||
|
corresponding configuration file (e.g. /etc/logrotate.d/fail2ban on Debian systems).
|
||||||
|
.TP
|
||||||
|
.B socket
|
||||||
|
socket filename. Default: /var/run/fail2ban/fail2ban.sock .
|
||||||
|
This is used for communication with the fail2ban server daemon. Do not remove this file when Fail2ban is running. It will not be possible to communicate with the server afterwards.
|
||||||
|
.TP
|
||||||
|
.B pidfile
|
||||||
|
PID filename. Default: /var/run/fail2ban/fail2ban.pid.
|
||||||
|
This is used to store the process ID of the fail2ban server.
|
||||||
|
|
||||||
|
.SH "JAIL CONFIGURATION FILE(S) (\fIjail.conf\fB)"
|
||||||
|
The following options are applicable to any jail. They appear in a section specifying the jail name or in the \fI[DEFAULT]\fR section which defines default values to be used if not specified in the individual section.
|
||||||
|
.TP
|
||||||
|
.B filter
|
||||||
|
name of the filter -- filename of the filter in /etc/fail2ban/filter.d/ without the .conf/.local extension. Only one filter can be specified.
|
||||||
|
.TP
|
||||||
|
.B logpath
|
||||||
|
filename(s) of the log files to be monitored. Globs -- paths containing * and ? or [0-9] -- can be used however only the files that exist at start up matching this glob pattern will be considered.
|
||||||
|
.TP
|
||||||
|
.B action
|
||||||
|
action(s) from \fI/etc/fail2ban/action.d/\fR without the \fI.conf\fR/\fI.local\fR extension. Arguments can be passed to actions to override the default values from the [Init] section in the action file. Arguments are specified by [name=value,name2=value]. Values can also be quoted. More that one action can be specified (in separate lines).
|
||||||
|
.TP
|
||||||
|
.B ignoreip
|
||||||
|
list of IPs not to ban. They can include a CIDR mask too.
|
||||||
|
.TP
|
||||||
|
.B ignorecommand
|
||||||
|
command that is executed to determine if the current candidate IP for banning should not be banned. IP will not be banned if command returns successfully (exit code 0).
|
||||||
|
Like ACTION FILES, tags like <ip> are can be included in the ignorecommand value and will be substituted before execution. Currently only <ip> is supported however more will be added later.
|
||||||
|
.TP
|
||||||
|
.B bantime
|
||||||
|
effective ban duration (in seconds).
|
||||||
|
.TP
|
||||||
|
.B findtime
|
||||||
|
time interval (in seconds) before the current time where failures will count towards a ban.
|
||||||
|
.TP
|
||||||
|
.B maxretry
|
||||||
|
number of failures that have to occur in the last \fBfindtime\fR seconds to ban then IP.
|
||||||
|
.TP
|
||||||
|
.B backend
|
||||||
|
backend to be used to detect changes in the logpath. It defaults to "auto" which will try "pyinotify", "gamin", "systemd" before "polling". Any of these can be specified. "pyinotify" is only valid on Linux systems with the "pyinotify" Python libraries. "gamin" requires the "gamin" libraries.
|
||||||
|
.TP
|
||||||
|
.B usedns
|
||||||
|
use DNS to resolve HOST names that appear in the logs. By default it is "warn" which will resolve hostnames to IPs however it will also log a warning. If you are using DNS here you could be blocking the wrong IPs due to the asymmetric nature of reverse DNS (that the application used to write the domain name to log) compared to forward DNS that fail2ban uses to resolve this back to an IP (but not necessarily the same one). Ideally you should configure your applications to log a real IP. This can be set to "yes" to prevent warnings in the log or "no" to disable DNS resolution altogether (thus ignoring entries where hostname, not an IP is logged)..
|
||||||
|
.TP
|
||||||
|
.B failregex
|
||||||
|
regex (Python \fBreg\fRular \fBex\fRpression) to be added to the filter's failregexes. If this is useful for others using your application please share you regular expression with the fail2ban developers by reporting an issue (see REPORTING BUGS below).
|
||||||
|
.TP
|
||||||
|
.B ignoreregex
|
||||||
|
regex which, if the log line matches, would cause Fail2Ban not consider that line. This line will be ignored even if it matches a failregex of the jail or any of its filters.
|
||||||
|
|
||||||
.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
|
|
||||||
Specify one or more log files for monitoring for failures, separated by new lines. Optional space separated option 'tail' can be added to the end of the path to cause the log file to be read from the end, else default 'head' option reads file from the beginning
|
|
||||||
.TP
|
|
||||||
\fBaction\fR
|
|
||||||
.TP
|
|
||||||
\fBignoreip\fR
|
|
||||||
A space separated list of IPs not to ban.
|
|
||||||
.TP
|
|
||||||
\fBignorecommand\fR
|
|
||||||
A command that is executed to determine if the current ban's actionban is to be executed. This command will return true if the current ban should be ignored. A false return value will result in the ban's actionban executed.
|
|
||||||
Like ACTION FILES, tags like <ip> are can be included in the ignore command value and will be substitued before execution. Currently only <ip> is supported however more will be added later.
|
|
||||||
.TP
|
|
||||||
\fBbantime\fR
|
|
||||||
.TP
|
|
||||||
\fBfindtime\fR
|
|
||||||
.TP
|
|
||||||
\fBmaxretry\fR
|
|
||||||
.TP
|
|
||||||
\fBbackend\fR
|
|
||||||
.TP
|
|
||||||
\fBusedns\fR
|
|
||||||
.TP
|
|
||||||
\fBfailregex\fR
|
|
||||||
.TP
|
|
||||||
\fBignoreregex\fR
|
|
||||||
|
|
||||||
.PP
|
|
||||||
.SS Backends
|
.SS Backends
|
||||||
\fBbackend\fR specifies the backend used to get files modification. This option can be overridden in each jail as well.
|
|
||||||
Available options are listed below.
|
Available options are listed below.
|
||||||
.TP
|
.TP
|
||||||
\fIpyinotify\fR
|
.B pyinotify
|
||||||
requires pyinotify (a file alteration monitor) to be installed. If pyinotify is not installed, Fail2ban will use auto.
|
requires pyinotify (a file alteration monitor) to be installed. If pyinotify is not installed, Fail2ban will use auto.
|
||||||
.TP
|
.TP
|
||||||
\fIgamin\fR
|
.B gamin
|
||||||
requires Gamin (a file alteration monitor) to be installed. If Gamin is not installed, Fail2ban will use auto.
|
requires Gamin (a file alteration monitor) to be installed. If Gamin is not installed, Fail2ban will use auto.
|
||||||
.TP
|
.TP
|
||||||
\fIpolling\fR
|
.B polling
|
||||||
uses a polling algorithm which does not require external libraries.
|
uses a polling algorithm which does not require external libraries.
|
||||||
.TP
|
.TP
|
||||||
\fIsystemd\fR
|
.B systemd
|
||||||
uses systemd python library to access the systemd journal. Specifying \fBlogpath\fR is not valid for this backend and instead utilises \fBjournalmatch\fR from the jails associated filter config.
|
uses systemd python library to access the systemd journal. Specifying \fBlogpath\fR is not valid for this backend and instead utilises \fBjournalmatch\fR from the jails associated filter config.
|
||||||
.TP
|
|
||||||
\fIauto\fR
|
|
||||||
will try to use the following backends, in order: pyinotify, gamin, polling
|
|
||||||
.PP
|
|
||||||
.SS Actions
|
.SS Actions
|
||||||
Each jail can be configured with only a single filter, but may have multiple actions. By default, the name of a action is the action filename. In the case where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplicatione.g.:
|
Each jail can be configured with only a single filter, but may have multiple actions. By default, the name of a action is the action filename, and in the case of Python actions, the ".py" file extension is stripped. Where multiple of the same action are to be used, the \fBactname\fR option can be assigned to the action to avoid duplication e.g.:
|
||||||
.PP
|
.PP
|
||||||
.nf
|
.nf
|
||||||
[ssh-iptables-ipset]
|
[ssh-iptables-ipset]
|
||||||
enabled = true
|
enabled = true
|
||||||
action = sendmail[name=ssh, dest=john@example.com, actname=mail-john]
|
action = smtp.py[dest=chris@example.com, actname=smtp-chris]
|
||||||
sendmail[name=ssh, dest=paul@example.com, actname=mail-paul]
|
smtp.py[dest=sally@example.com, actname=smtp-sally]
|
||||||
.fi
|
.fi
|
||||||
|
|
||||||
.SH "ACTION FILES"
|
.SH "ACTION CONFIGURATION FILES (\fIaction.d/*.conf\fB)"
|
||||||
Action files specify which commands are executed to ban and unban an IP address. They are located under \fI/etc/fail2ban/action.d\fR.
|
Action files specify which commands are executed to ban and unban an IP address.
|
||||||
|
|
||||||
Like with jail.conf files, if you desire local changes create an \fI[actionname].local\fR file in the \fI/etc/fail2ban/action.d\fR directory
|
Like with jail.conf files, if you desire local changes create an \fI[actionname].local\fR file in the \fI/etc/fail2ban/action.d\fR directory
|
||||||
and override the required settings.
|
and override the required settings.
|
||||||
|
|
||||||
Action files are ini files that have two sections, \fBDefinition\fR and \fBInit\fR .
|
Action files have two sections, \fBDefinition\fR and \fBInit\fR .
|
||||||
|
|
||||||
The [Init] section allows for action-specific settings. In \fIjail.conf/jail.local\fR these can be overwritten for a particular jail as options to the jail.
|
The [Init] section enables action-specific settings. In \fIjail.conf/jail.local\fR these can be overridden for a particular jail as options of the action's specification in that jail.
|
||||||
|
|
||||||
The following commands can be present in the [Definition] section.
|
The following commands can be present in the [Definition] section.
|
||||||
.TP
|
.TP
|
||||||
\fBactionstart\fR
|
.B actionstart
|
||||||
command(s) executed when the jail starts.
|
command(s) executed when the jail starts.
|
||||||
.TP
|
.TP
|
||||||
\fBactionstop\fR
|
.B actionstop
|
||||||
command(s) executed when the jail stops.
|
command(s) executed when the jail stops.
|
||||||
.TP
|
.TP
|
||||||
\fBactioncheck\fR
|
.B actioncheck
|
||||||
the command ran before any other action. It aims to verify if the environment is still ok.
|
command(s) ran before any other action. It aims to verify if the environment is still ok.
|
||||||
.TP
|
.TP
|
||||||
\fBactionban\fR
|
.B actionban
|
||||||
command(s) that bans the IP address after \fBmaxretry\fR log lines matches within last \fBfindtime\fR seconds.
|
command(s) that bans the IP address after \fBmaxretry\fR log lines matches within last \fBfindtime\fR seconds.
|
||||||
.TP
|
.TP
|
||||||
\fBactionunban\fR
|
.B actionunban
|
||||||
command(s) that unbans the IP address after \fBbantime\fR.
|
command(s) that unbans the IP address after \fBbantime\fR.
|
||||||
|
.PP
|
||||||
|
The [Init] section allows for action-specific settings. In \fIjail.conf/jail.local\fR these can be overwritten for a particular jail as options to the jail. The following are special tags which can be set in the [Init] section:
|
||||||
|
.TP
|
||||||
|
\fBtimeout\fR
|
||||||
|
The maximum period of time in seconds that a command can executed, before being killed.
|
||||||
|
.PP
|
||||||
|
.RE
|
||||||
|
|
||||||
Commands specified in the [Definition] section are executed through a system shell so shell redirection and process control is allowed. The commands should
|
Commands specified in the [Definition] section are executed through a system shell so shell redirection and process control is allowed. The commands should
|
||||||
return 0, otherwise error would be logged. Moreover if \fBactioncheck\fR exits with non-0 status, it is taken as indication that firewall status has changed and fail2ban needs to reinitialize itself (i.e. issue \fBactionstop\fR and \fBactionstart\fR commands).
|
return 0, otherwise error would be logged. Moreover if \fBactioncheck\fR exits with non-0 status, it is taken as indication that firewall status has changed and fail2ban needs to reinitialize itself (i.e. issue \fBactionstop\fR and \fBactionstart\fR commands).
|
||||||
|
|
||||||
Tags are enclosed in <>. All the elements of [Init] are tags that are replaced in all action commands. Tags can be added by the
|
Tags are enclosed in <>. All the elements of [Init] are tags that are replaced in all action commands. Tags can be added by the
|
||||||
\fBfail2ban-client\fR using the setctag command. \fB<br>\fR is a tag that is always a new line (\\n).
|
\fBfail2ban-client\fR using the "set <JAIL> action <ACT>" command. \fB<br>\fR is a tag that is always a new line (\\n).
|
||||||
|
|
||||||
More than a single command is allowed to be specified. Each command needs to be on a separate line and indented with whitespaces without blank lines. The following example defines
|
More than a single command is allowed to be specified. Each command needs to be on a separate line and indented with whitespace(s) without blank lines. The following example defines
|
||||||
two commands to be executed.
|
two commands to be executed.
|
||||||
|
|
||||||
actionban = iptables -I fail2ban-<name> --source <ip> -j DROP
|
actionban = iptables -I fail2ban-<name> --source <ip> -j DROP
|
||||||
|
@ -164,56 +235,53 @@ two commands to be executed.
|
||||||
.SS "Action Tags"
|
.SS "Action Tags"
|
||||||
The following tags are substituted in the actionban, actionunban and actioncheck (when called before actionban/actionunban) commands.
|
The following tags are substituted in the actionban, actionunban and actioncheck (when called before actionban/actionunban) commands.
|
||||||
.TP
|
.TP
|
||||||
\fBip\fR
|
.B ip
|
||||||
An IPv4 ip address to be banned. e.g. 192.168.0.2
|
IPv4 IP address to be banned. e.g. 192.168.0.2
|
||||||
.TP
|
.TP
|
||||||
\fBtime\fR
|
.B failures
|
||||||
The unix time of the ban. e.g. 1357508484
|
number of times the failure occurred in the log file. e.g. 3
|
||||||
.TP
|
.TP
|
||||||
\fBfailures\fR
|
.B ipfailures
|
||||||
The number of times the failure occurred in the log file. e.g. 3
|
|
||||||
.TP
|
|
||||||
\fBipfailures\fR
|
|
||||||
As per \fBfailures\fR, but total of all failures for that ip address across all jails from the fail2ban persistent database. Therefore the database must be set for this tag to function.
|
As per \fBfailures\fR, but total of all failures for that ip address across all jails from the fail2ban persistent database. Therefore the database must be set for this tag to function.
|
||||||
.TP
|
.TP
|
||||||
\fBipjailfailures\fR
|
.B ipjailfailures
|
||||||
As per \fBipfailures\fR, but total based on the IPs failures for the current jail.
|
As per \fBipfailures\fR, but total based on the IPs failures for the current jail.
|
||||||
.TP
|
.TP
|
||||||
\fBmatches\fR
|
.B time
|
||||||
The string of the log file lines of the matches that generated the ban. Many characters interpreted by shell get escaped. New lines are maintained, so actions should be careful to enclose the tag in quotes.
|
UNIX (epoch) time of the ban. e.g. 1357508484
|
||||||
.TP
|
.TP
|
||||||
\fBipmatches\fR
|
.B matches
|
||||||
|
concatenated string of the log file lines of the matches that generated the ban. Many characters interpreted by shell get escaped to prevent injection, nevertheless use with caution.
|
||||||
|
.TP
|
||||||
|
.B ipmatches
|
||||||
As per \fBmatches\fR, but includes all lines for the IP which are contained with the fail2ban persistent database. Therefore the database must be set for this tag to function.
|
As per \fBmatches\fR, but includes all lines for the IP which are contained with the fail2ban persistent database. Therefore the database must be set for this tag to function.
|
||||||
.TP
|
.TP
|
||||||
\fBipjailmatches\fR
|
.B ipjailmatches\
|
||||||
As per \fBipmatches\fR, but matches are limited for the IP and for the current jail.
|
As per \fBipmatches\fR, but matches are limited for the IP and for the current jail.
|
||||||
|
|
||||||
.SH FILTER FILES
|
.SH "PYTHON ACTION FILES"
|
||||||
|
Python based actions can also be used, where the file name must be \fI[actionname].py\fR. The Python file must contain a variable \fIAction\fR which points to Python class. This class must implement a minimum interface as described by \fIfail2ban.server.action.ActionBase\fR, which can be inherited from to ease implementation.
|
||||||
|
|
||||||
|
.SH "FILTER FILES (\fIfilter.d/*.conf\fB)"
|
||||||
|
|
||||||
Filter definitions are those in \fI/etc/fail2ban/filter.d/*.conf\fR and \fIfilter.d/*.local\fR.
|
Filter definitions are those in \fI/etc/fail2ban/filter.d/*.conf\fR and \fIfilter.d/*.local\fR.
|
||||||
|
|
||||||
These are used to identify failed authentication attempts in logs and to extract the host IP address (or hostname if \fBusedns\fR is \fBtrue\fR).
|
These are used to identify failed authentication attempts in log files and to extract the host IP address (or hostname if \fBusedns\fR is \fBtrue\fR).
|
||||||
|
|
||||||
Like action files, filter files are ini files. The main section is the [Definition] section.
|
Like action files, filter files are ini files. The main section is the [Definition] section.
|
||||||
|
|
||||||
There are two filter definitions used in the [Definition] section:
|
There are two filter definitions used in the [Definition] section:
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fBfailregex\fR
|
.B failregex
|
||||||
is the regex (\fBreg\fRular \fBex\fRpression) that will match failed attempts. The tag \fI<HOST>\fR is used as part of the regex and is itself a regex
|
is the regex (\fBreg\fRular \fBex\fRpression) that will match failed attempts. The tag \fI<HOST>\fR is used as part of the regex and is itself a regex
|
||||||
for IPv4 addresses and hostnames. fail2ban will work out which one of these it actually is.
|
for IPv4 addresses and hostnames. fail2ban will work out which one of these it actually is.
|
||||||
For multiline regexs the tag \fI<SKIPLINES>\fR should be used to separate lines. This allows lines between the matched lines to continue to be searched for other failures. The tag can be used multiple times.
|
For multiline regexs the tag \fI<SKIPLINES>\fR should be used to separate lines. This allows lines between the matched lines to continue to be searched for other failures. The tag can be used multiple times.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
\fBignoreregex\fR
|
.B ignoreregex
|
||||||
is the regex to identify log entries that should be ignored by fail2ban, even if they match failregex.
|
is the regex to identify log entries that should be ignored by fail2ban, even if they match failregex.
|
||||||
|
|
||||||
|
|
||||||
Using Python "string interpolation" mechanisms, other definitions are allowed and can later be used within other definitions as %(defnname)s. For example.
|
|
||||||
|
|
||||||
baduseragents = IE|wget
|
|
||||||
failregex = useragent=%(baduseragents)s
|
|
||||||
|
|
||||||
.PP
|
.PP
|
||||||
Similar to actions, filters have an [Init] section which can be overridden in \fIjail.conf/jail.local\fR. The filter [Init] section is limited to the following options:
|
Similar to actions, filters have an [Init] section which can be overridden in \fIjail.conf/jail.local\fR. The filter [Init] section is limited to the following options:
|
||||||
.TP
|
.TP
|
||||||
|
@ -244,18 +312,28 @@ indicates that this file is read before the [Definition] section.
|
||||||
\fBafter\fR
|
\fBafter\fR
|
||||||
indicates that this file is read after the [Definition] section.
|
indicates that this file is read after the [Definition] section.
|
||||||
|
|
||||||
|
.B failregex
|
||||||
|
regex that will match failed attempts. The tag <HOST> is used as part of the regex and is itself a regex
|
||||||
|
for IPv4 addresses (and hostnames if \fBusedns\fR). Fail2Ban will work out which one of these it actually is.
|
||||||
|
.TP
|
||||||
|
.B ignoreregex
|
||||||
|
regex to identify log entries that should be ignored by Fail2Ban, even if they match failregex.
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Fail2ban was originally written by Cyril Jaquier <cyril.jaquier@fail2ban.org>.
|
Fail2ban was originally written by Cyril Jaquier <cyril.jaquier@fail2ban.org>.
|
||||||
At the moment it is maintained and further developed by Yaroslav O. Halchenko <debian@onerussian.com> and a number of contributors. See \fBTHANKS\fR file shipped with Fail2Ban for a full list.
|
At the moment it is maintained and further developed by Yaroslav O. Halchenko <debian@onerussian.com>, Daniel Black <daniel.subs@internode.on.net> and Steven Hiscocks <steven-fail2ban@hiscocks.me.uk> along with a number of contributors. See \fBTHANKS\fR file shipped with Fail2Ban for a full list.
|
||||||
.
|
.
|
||||||
Manual page written by Daniel Black and Yaroslav Halchenko.
|
Manual page written by Daniel Black and Yaroslav Halchenko.
|
||||||
.SH "REPORTING BUGS"
|
.SH "REPORTING BUGS"
|
||||||
Report bugs to https://github.com/fail2ban/fail2ban/issues
|
Report bugs to https://github.com/fail2ban/fail2ban/issues
|
||||||
.SH COPYRIGHT
|
.SH COPYRIGHT
|
||||||
Copyright \(co 2013 Daniel Black
|
Copyright \(co 2013 the Fail2Ban Team
|
||||||
.br
|
.br
|
||||||
Copyright of modifications held by their respective authors.
|
Copyright of modifications held by their respective authors.
|
||||||
Licensed under the GNU General Public License v2 (GPL).
|
.br
|
||||||
|
Licensed under the GNU General Public License v2 (GPL) or
|
||||||
|
(at your option) any later version.
|
||||||
|
.
|
||||||
.SH "SEE ALSO"
|
.SH "SEE ALSO"
|
||||||
.br
|
.br
|
||||||
fail2ban-server(1)
|
fail2ban-server(1)
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -120,7 +120,8 @@ setup(
|
||||||
glob("config/filter.d/*.conf")
|
glob("config/filter.d/*.conf")
|
||||||
),
|
),
|
||||||
('/etc/fail2ban/action.d',
|
('/etc/fail2ban/action.d',
|
||||||
glob("config/action.d/*.conf")
|
glob("config/action.d/*.conf") +
|
||||||
|
glob("config/action.d/*.py")
|
||||||
),
|
),
|
||||||
('/etc/fail2ban/fail2ban.d',
|
('/etc/fail2ban/fail2ban.d',
|
||||||
''
|
''
|
||||||
|
|
Loading…
Reference in New Issue