mirror of https://github.com/fail2ban/fail2ban
Merge remote-tracking branch 'remotes/kwirk/sebres-strptime-bug' into ban-time-incr
commit
8fd083a1ea
|
@ -6,6 +6,7 @@ python:
|
||||||
- "2.7"
|
- "2.7"
|
||||||
- "3.2"
|
- "3.2"
|
||||||
- "3.3"
|
- "3.3"
|
||||||
|
- "3.4"
|
||||||
- "pypy"
|
- "pypy"
|
||||||
before_install:
|
before_install:
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get update -qq; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then sudo apt-get update -qq; fi
|
||||||
|
|
|
@ -22,13 +22,21 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger
|
||||||
* Nginx filter to support missing server_name. Closes gh-676
|
* Nginx filter to support missing server_name. Closes gh-676
|
||||||
* fail2ban-regex assertion error caused by miscount missed lines with
|
* fail2ban-regex assertion error caused by miscount missed lines with
|
||||||
multiline regex
|
multiline regex
|
||||||
|
* Fix actions failing to execute for Python 3.4.0. Workaround for
|
||||||
|
http://bugs.python.org/issue21207
|
||||||
|
* Database now returns persistent bans on restart (bantime < 0)
|
||||||
|
* Recursive action tags now fully processed. Fixes issue with bsd-ipfw
|
||||||
|
action
|
||||||
|
* Correct times for non-timezone date times formats - Thanks sebres
|
||||||
|
|
||||||
- New features:
|
- New features:
|
||||||
|
- Added monit filter thanks Jason H Martin.
|
||||||
|
|
||||||
|
|
||||||
- Enhancements
|
- Enhancements
|
||||||
* Fail2ban-regex - add print-all-matched option. Closes gh-652
|
* Fail2ban-regex - add print-all-matched option. Closes gh-652
|
||||||
* Suppress fail2ban-client warnings for non-critical config options
|
* Suppress fail2ban-client warnings for non-critical config options
|
||||||
|
* Match non "Bye Bye" disconnect messages for sshd locked account regex
|
||||||
|
|
||||||
ver. 0.9.0 (2014/03/14) - beta
|
ver. 0.9.0 (2014/03/14) - beta
|
||||||
----------
|
----------
|
||||||
|
|
2
THANKS
2
THANKS
|
@ -48,6 +48,7 @@ Ivo Truxa
|
||||||
John Thoe
|
John Thoe
|
||||||
Jacques Lav!gnotte
|
Jacques Lav!gnotte
|
||||||
Ioan Indreias
|
Ioan Indreias
|
||||||
|
Jason H Martin
|
||||||
Jonathan Kamens
|
Jonathan Kamens
|
||||||
Jonathan Lanning
|
Jonathan Lanning
|
||||||
Jonathan Underwood
|
Jonathan Underwood
|
||||||
|
@ -85,6 +86,7 @@ Rolf Fokkens
|
||||||
Roman Gelfand
|
Roman Gelfand
|
||||||
Russell Odom
|
Russell Odom
|
||||||
Sebastian Arcus
|
Sebastian Arcus
|
||||||
|
sebres
|
||||||
Sireyessire
|
Sireyessire
|
||||||
silviogarbes
|
silviogarbes
|
||||||
Stefan Tatschner
|
Stefan Tatschner
|
||||||
|
|
|
@ -51,6 +51,7 @@ class Fail2banClient:
|
||||||
self.__conf["conf"] = "/etc/fail2ban"
|
self.__conf["conf"] = "/etc/fail2ban"
|
||||||
self.__conf["dump"] = False
|
self.__conf["dump"] = False
|
||||||
self.__conf["force"] = False
|
self.__conf["force"] = False
|
||||||
|
self.__conf["background"] = True
|
||||||
self.__conf["verbose"] = 1
|
self.__conf["verbose"] = 1
|
||||||
self.__conf["interactive"] = False
|
self.__conf["interactive"] = False
|
||||||
self.__conf["socket"] = None
|
self.__conf["socket"] = None
|
||||||
|
@ -83,6 +84,8 @@ class Fail2banClient:
|
||||||
print " -v increase verbosity"
|
print " -v increase verbosity"
|
||||||
print " -q decrease verbosity"
|
print " -q decrease verbosity"
|
||||||
print " -x force execution of the server (remove socket file)"
|
print " -x force execution of the server (remove socket file)"
|
||||||
|
print " -b start server in background (default)"
|
||||||
|
print " -f start server in foreground (note that the client forks once itself)"
|
||||||
print " -h, --help display this help message"
|
print " -h, --help display this help message"
|
||||||
print " -V, --version print the version"
|
print " -V, --version print the version"
|
||||||
print
|
print
|
||||||
|
@ -125,6 +128,10 @@ class Fail2banClient:
|
||||||
self.__conf["force"] = True
|
self.__conf["force"] = True
|
||||||
elif opt[0] == "-i":
|
elif opt[0] == "-i":
|
||||||
self.__conf["interactive"] = True
|
self.__conf["interactive"] = True
|
||||||
|
elif opt[0] == "-b":
|
||||||
|
self.__conf["background"] = True
|
||||||
|
elif opt[0] == "-f":
|
||||||
|
self.__conf["background"] = False
|
||||||
elif opt[0] in ["-h", "--help"]:
|
elif opt[0] in ["-h", "--help"]:
|
||||||
self.dispUsage()
|
self.dispUsage()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
@ -194,7 +201,8 @@ class Fail2banClient:
|
||||||
# Start the server
|
# Start the server
|
||||||
self.__startServerAsync(self.__conf["socket"],
|
self.__startServerAsync(self.__conf["socket"],
|
||||||
self.__conf["pidfile"],
|
self.__conf["pidfile"],
|
||||||
self.__conf["force"])
|
self.__conf["force"],
|
||||||
|
self.__conf["background"])
|
||||||
try:
|
try:
|
||||||
# Wait for the server to start
|
# Wait for the server to start
|
||||||
self.__waitOnServer()
|
self.__waitOnServer()
|
||||||
|
@ -242,14 +250,12 @@ class Fail2banClient:
|
||||||
#
|
#
|
||||||
# Start the Fail2ban server in daemon mode.
|
# Start the Fail2ban server in daemon mode.
|
||||||
|
|
||||||
def __startServerAsync(self, socket, pidfile, force = False):
|
def __startServerAsync(self, socket, pidfile, force = False, background = True):
|
||||||
# Forks the current process.
|
# Forks the current process.
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
if pid == 0:
|
if pid == 0:
|
||||||
args = list()
|
args = list()
|
||||||
args.append(self.SERVER)
|
args.append(self.SERVER)
|
||||||
# Start in background mode.
|
|
||||||
args.append("-b")
|
|
||||||
# Set the socket path.
|
# Set the socket path.
|
||||||
args.append("-s")
|
args.append("-s")
|
||||||
args.append(socket)
|
args.append(socket)
|
||||||
|
@ -259,6 +265,12 @@ class Fail2banClient:
|
||||||
# Force the execution if needed.
|
# Force the execution if needed.
|
||||||
if force:
|
if force:
|
||||||
args.append("-x")
|
args.append("-x")
|
||||||
|
# Start in foreground mode if requested.
|
||||||
|
if background:
|
||||||
|
args.append("-b")
|
||||||
|
else:
|
||||||
|
args.append("-f")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Use the current directory.
|
# Use the current directory.
|
||||||
exe = os.path.abspath(os.path.join(sys.path[0], self.SERVER))
|
exe = os.path.abspath(os.path.join(sys.path[0], self.SERVER))
|
||||||
|
@ -312,7 +324,7 @@ class Fail2banClient:
|
||||||
|
|
||||||
# Reads the command line options.
|
# Reads the command line options.
|
||||||
try:
|
try:
|
||||||
cmdOpts = 'hc:s:p:xdviqV'
|
cmdOpts = 'hc:s:p:xfbdviqV'
|
||||||
cmdLongOpts = ['help', 'version']
|
cmdLongOpts = ['help', 'version']
|
||||||
optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts)
|
optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts)
|
||||||
except getopt.GetoptError:
|
except getopt.GetoptError:
|
||||||
|
|
|
@ -45,7 +45,7 @@ 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
|
||||||
|
|
||||||
from fail2ban.tests.utils import FormatterWithTraceBack
|
from fail2ban.helpers import FormatterWithTraceBack
|
||||||
# Gets the instance of the logger.
|
# Gets the instance of the logger.
|
||||||
logSys = logging.getLogger("fail2ban")
|
logSys = logging.getLogger("fail2ban")
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,8 @@ if os.path.exists("fail2ban/__init__.py"):
|
||||||
sys.path.insert(0, ".")
|
sys.path.insert(0, ".")
|
||||||
from fail2ban.version import version
|
from fail2ban.version import version
|
||||||
|
|
||||||
from fail2ban.tests.utils import FormatterWithTraceBack, gatherTests
|
from fail2ban.tests.utils import gatherTests
|
||||||
|
from fail2ban.helpers import FormatterWithTraceBack
|
||||||
from fail2ban.server.mytime import MyTime
|
from fail2ban.server.mytime import MyTime
|
||||||
|
|
||||||
from optparse import OptionParser, Option
|
from optparse import OptionParser, Option
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Fail2Ban filter for monit.conf, looks for failed access attempts
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
|
||||||
|
failregex = ^\[[A-Z]+\s+\]\s*error\s*:\s*Warning:\s+Client '<HOST>' supplied unknown user '\w+' accessing monit httpd$
|
||||||
|
^\[[A-Z]+\s+\]\s*error\s*:\s*Warning:\s+Client '<HOST>' supplied wrong password for user '\w+' accessing monit httpd$
|
||||||
|
|
|
@ -30,7 +30,7 @@ failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|erro
|
||||||
^%(__prefix_line)sReceived disconnect from <HOST>: 3: \S+: Auth fail$
|
^%(__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: .+ \[preauth\]$
|
||||||
^(?P<__prefix>%(__prefix_line)s)Disconnecting: Too many authentication failures for .+? \[preauth\]<SKIPLINES>(?P=__prefix)(?:error: )?Connection closed by <HOST> \[preauth\]$
|
^(?P<__prefix>%(__prefix_line)s)Disconnecting: Too many authentication failures for .+? \[preauth\]<SKIPLINES>(?P=__prefix)(?:error: )?Connection closed by <HOST> \[preauth\]$
|
||||||
^(?P<__prefix>%(__prefix_line)s)Connection from <HOST> port \d+<SKIPLINES>(?P=__prefix)Disconnecting: Too many authentication failures for .+? \[preauth\]$
|
^(?P<__prefix>%(__prefix_line)s)Connection from <HOST> port \d+<SKIPLINES>(?P=__prefix)Disconnecting: Too many authentication failures for .+? \[preauth\]$
|
||||||
|
|
||||||
|
|
|
@ -409,6 +409,12 @@ maxretry = 5
|
||||||
port = http,https
|
port = http,https
|
||||||
logpath = /var/log/tomcat*/catalina.out
|
logpath = /var/log/tomcat*/catalina.out
|
||||||
|
|
||||||
|
[monit]
|
||||||
|
#Ban clients brute-forcing the monit gui login
|
||||||
|
filter = monit
|
||||||
|
port = 2812
|
||||||
|
logpath = /var/log/monit
|
||||||
|
|
||||||
|
|
||||||
[webmin-auth]
|
[webmin-auth]
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,90 @@
|
||||||
__author__ = "Cyril Jaquier, Arturo 'Buanzo' Busleiman, Yaroslav Halchenko"
|
__author__ = "Cyril Jaquier, Arturo 'Buanzo' Busleiman, Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
def formatExceptionInfo():
|
def formatExceptionInfo():
|
||||||
""" Consistently format exception information """
|
""" Consistently format exception information """
|
||||||
import sys
|
|
||||||
cla, exc = sys.exc_info()[:2]
|
cla, exc = sys.exc_info()[:2]
|
||||||
return (cla.__name__, str(exc))
|
return (cla.__name__, str(exc))
|
||||||
|
|
||||||
|
#
|
||||||
|
# Following "traceback" functions are adopted from PyMVPA distributed
|
||||||
|
# under MIT/Expat and copyright by PyMVPA developers (i.e. me and
|
||||||
|
# Michael). Hereby I re-license derivative work on these pieces under GPL
|
||||||
|
# to stay in line with the main Fail2Ban license
|
||||||
|
#
|
||||||
|
def mbasename(s):
|
||||||
|
"""Custom function to include directory name if filename is too common
|
||||||
|
|
||||||
|
Also strip .py at the end
|
||||||
|
"""
|
||||||
|
base = os.path.basename(s)
|
||||||
|
if base.endswith('.py'):
|
||||||
|
base = base[:-3]
|
||||||
|
if base in set(['base', '__init__']):
|
||||||
|
base = os.path.basename(os.path.dirname(s)) + '.' + base
|
||||||
|
return base
|
||||||
|
|
||||||
|
class TraceBack(object):
|
||||||
|
"""Customized traceback to be included in debug messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, compress=False):
|
||||||
|
"""Initialize TrackBack metric
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
compress : bool
|
||||||
|
if True then prefix common with previous invocation gets
|
||||||
|
replaced with ...
|
||||||
|
"""
|
||||||
|
self.__prev = ""
|
||||||
|
self.__compress = compress
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
ftb = traceback.extract_stack(limit=100)[:-2]
|
||||||
|
entries = [
|
||||||
|
[mbasename(x[0]), os.path.dirname(x[0]), str(x[1])] for x in ftb]
|
||||||
|
entries = [ [e[0], e[2]] for e in entries
|
||||||
|
if not (e[0] in ['unittest', 'logging.__init__']
|
||||||
|
or e[1].endswith('/unittest'))]
|
||||||
|
|
||||||
|
# lets make it more concise
|
||||||
|
entries_out = [entries[0]]
|
||||||
|
for entry in entries[1:]:
|
||||||
|
if entry[0] == entries_out[-1][0]:
|
||||||
|
entries_out[-1][1] += ',%s' % entry[1]
|
||||||
|
else:
|
||||||
|
entries_out.append(entry)
|
||||||
|
sftb = '>'.join(['%s:%s' % (mbasename(x[0]),
|
||||||
|
x[1]) for x in entries_out])
|
||||||
|
if self.__compress:
|
||||||
|
# lets remove part which is common with previous invocation
|
||||||
|
prev_next = sftb
|
||||||
|
common_prefix = os.path.commonprefix((self.__prev, sftb))
|
||||||
|
common_prefix2 = re.sub('>[^>]*$', '', common_prefix)
|
||||||
|
|
||||||
|
if common_prefix2 != "":
|
||||||
|
sftb = '...' + sftb[len(common_prefix2):]
|
||||||
|
self.__prev = prev_next
|
||||||
|
|
||||||
|
return sftb
|
||||||
|
|
||||||
|
class FormatterWithTraceBack(logging.Formatter):
|
||||||
|
"""Custom formatter which expands %(tb) and %(tbc) with tracebacks
|
||||||
|
|
||||||
|
TODO: might need locking in case of compressed tracebacks
|
||||||
|
"""
|
||||||
|
def __init__(self, fmt, *args, **kwargs):
|
||||||
|
logging.Formatter.__init__(self, fmt=fmt, *args, **kwargs)
|
||||||
|
compress = '%(tbc)s' in fmt
|
||||||
|
self._tb = TraceBack(compress=compress)
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
record.tbc = record.tb = self._tb()
|
||||||
|
return logging.Formatter.format(self, record)
|
||||||
|
|
|
@ -194,6 +194,8 @@ class CommandAction(ActionBase):
|
||||||
timeout
|
timeout
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_escapedTags = set(('matches', 'ipmatches', 'ipjailmatches'))
|
||||||
|
|
||||||
def __init__(self, jail, name):
|
def __init__(self, jail, name):
|
||||||
super(CommandAction, self).__init__(jail, name)
|
super(CommandAction, self).__init__(jail, name)
|
||||||
self.timeout = 60
|
self.timeout = 60
|
||||||
|
@ -351,8 +353,8 @@ class CommandAction(ActionBase):
|
||||||
if not self.executeCmd(stopCmd, self.timeout):
|
if not self.executeCmd(stopCmd, self.timeout):
|
||||||
raise RuntimeError("Error stopping action")
|
raise RuntimeError("Error stopping action")
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def substituteRecursiveTags(tags):
|
def substituteRecursiveTags(cls, tags):
|
||||||
"""Sort out tag definitions within other tags.
|
"""Sort out tag definitions within other tags.
|
||||||
|
|
||||||
so: becomes:
|
so: becomes:
|
||||||
|
@ -371,8 +373,11 @@ class CommandAction(ActionBase):
|
||||||
within the values recursively replaced.
|
within the values recursively replaced.
|
||||||
"""
|
"""
|
||||||
t = re.compile(r'<([^ >]+)>')
|
t = re.compile(r'<([^ >]+)>')
|
||||||
for tag, value in tags.iteritems():
|
for tag in tags.iterkeys():
|
||||||
value = str(value)
|
if tag in cls._escapedTags:
|
||||||
|
# Escaped so won't match
|
||||||
|
continue
|
||||||
|
value = str(tags[tag])
|
||||||
m = t.search(value)
|
m = t.search(value)
|
||||||
done = []
|
done = []
|
||||||
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
|
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
|
||||||
|
@ -383,6 +388,9 @@ class CommandAction(ActionBase):
|
||||||
# recursive definitions are bad
|
# recursive definitions are bad
|
||||||
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
|
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
|
||||||
return False
|
return False
|
||||||
|
elif found_tag in cls._escapedTags:
|
||||||
|
# Escaped so won't match
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
if tags.has_key(found_tag):
|
if tags.has_key(found_tag):
|
||||||
value = value.replace('<%s>' % found_tag , tags[found_tag])
|
value = value.replace('<%s>' % found_tag , tags[found_tag])
|
||||||
|
@ -441,10 +449,11 @@ class CommandAction(ActionBase):
|
||||||
`query` string with tags replaced.
|
`query` string with tags replaced.
|
||||||
"""
|
"""
|
||||||
string = query
|
string = query
|
||||||
|
aInfo = cls.substituteRecursiveTags(aInfo)
|
||||||
for tag in aInfo:
|
for tag in aInfo:
|
||||||
if "<%s>" % tag in query:
|
if "<%s>" % tag in query:
|
||||||
value = str(aInfo[tag]) # assure string
|
value = str(aInfo[tag]) # assure string
|
||||||
if tag.endswith('matches'):
|
if tag in cls._escapedTags:
|
||||||
# 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 = cls.escapeTag(value)
|
value = cls.escapeTag(value)
|
||||||
|
|
|
@ -417,7 +417,7 @@ class Fail2BanDb(object):
|
||||||
if jail is not None:
|
if jail is not None:
|
||||||
query += " AND jail=?"
|
query += " AND jail=?"
|
||||||
queryArgs.append(jail.name)
|
queryArgs.append(jail.name)
|
||||||
if bantime is not None:
|
if bantime is not None and bantime >= 0:
|
||||||
query += " AND timeofban > ?"
|
query += " AND timeofban > ?"
|
||||||
queryArgs.append(MyTime.time() - bantime)
|
queryArgs.append(MyTime.time() - bantime)
|
||||||
if ip is not None:
|
if ip is not None:
|
||||||
|
@ -436,7 +436,8 @@ class Fail2BanDb(object):
|
||||||
Jail that the ban belongs to. Default `None`; all jails.
|
Jail that the ban belongs to. Default `None`; all jails.
|
||||||
bantime : int
|
bantime : int
|
||||||
Ban time in seconds, such that bans returned would still be
|
Ban time in seconds, such that bans returned would still be
|
||||||
valid now. Default `None`; no limit.
|
valid now. Negative values are equivalent to `None`.
|
||||||
|
Default `None`; no limit.
|
||||||
ip : str
|
ip : str
|
||||||
IP Address to filter bans by. Default `None`; all IPs.
|
IP Address to filter bans by. Default `None`; all IPs.
|
||||||
|
|
||||||
|
@ -464,7 +465,8 @@ class Fail2BanDb(object):
|
||||||
Jail that the ban belongs to. Default `None`; all jails.
|
Jail that the ban belongs to. Default `None`; all jails.
|
||||||
bantime : int
|
bantime : int
|
||||||
Ban time in seconds, such that bans returned would still be
|
Ban time in seconds, such that bans returned would still be
|
||||||
valid now. Default `None`; no limit.
|
valid now. Negative values are equivalent to `None`.
|
||||||
|
Default `None`; no limit.
|
||||||
ip : str
|
ip : str
|
||||||
IP Address to filter bans by. Default `None`; all IPs.
|
IP Address to filter bans by. Default `None`; all IPs.
|
||||||
|
|
||||||
|
@ -475,7 +477,8 @@ class Fail2BanDb(object):
|
||||||
in a list. When `ip` argument passed, a single `Ticket` is
|
in a list. When `ip` argument passed, a single `Ticket` is
|
||||||
returned.
|
returned.
|
||||||
"""
|
"""
|
||||||
if bantime is None:
|
cacheKey = None
|
||||||
|
if bantime is None or bantime < 0:
|
||||||
cacheKey = (ip, jail)
|
cacheKey = (ip, jail)
|
||||||
if cacheKey in self._bansMergedCache:
|
if cacheKey in self._bansMergedCache:
|
||||||
return self._bansMergedCache[cacheKey]
|
return self._bansMergedCache[cacheKey]
|
||||||
|
@ -505,7 +508,7 @@ class Fail2BanDb(object):
|
||||||
ticket.setAttempt(failures)
|
ticket.setAttempt(failures)
|
||||||
tickets.append(ticket)
|
tickets.append(ticket)
|
||||||
|
|
||||||
if bantime is None:
|
if cacheKey:
|
||||||
self._bansMergedCache[cacheKey] = tickets if ip is None else ticket
|
self._bansMergedCache[cacheKey] = tickets if ip is None else ticket
|
||||||
return tickets if ip is None else ticket
|
return tickets if ip is None else ticket
|
||||||
|
|
||||||
|
|
|
@ -529,11 +529,19 @@ class Server:
|
||||||
except (AttributeError, ValueError):
|
except (AttributeError, ValueError):
|
||||||
maxfd = 256 # default maximum
|
maxfd = 256 # default maximum
|
||||||
|
|
||||||
|
# urandom should not be closed in Python 3.4.0. Fixed in 3.4.1
|
||||||
|
# http://bugs.python.org/issue21207
|
||||||
|
if sys.version_info[0:3] == (3, 4, 0): # pragma: no cover
|
||||||
|
urandom_fd = os.open("/dev/urandom", os.O_RDONLY)
|
||||||
for fd in range(0, maxfd):
|
for fd in range(0, maxfd):
|
||||||
try:
|
try:
|
||||||
|
if not os.path.sameopenfile(urandom_fd, fd):
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
except OSError: # ERROR (ignore)
|
except OSError: # ERROR (ignore)
|
||||||
pass
|
pass
|
||||||
|
os.close(urandom_fd)
|
||||||
|
else:
|
||||||
|
os.closerange(0, maxfd)
|
||||||
|
|
||||||
# Redirect the standard file descriptors to /dev/null.
|
# Redirect the standard file descriptors to /dev/null.
|
||||||
os.open("/dev/null", os.O_RDONLY) # standard input (0)
|
os.open("/dev/null", os.O_RDONLY) # standard input (0)
|
||||||
|
|
|
@ -190,5 +190,5 @@ def reGroupDictStrptime(found_dict):
|
||||||
if gmtoff is not None:
|
if gmtoff is not None:
|
||||||
return calendar.timegm(date_result.utctimetuple())
|
return calendar.timegm(date_result.utctimetuple())
|
||||||
else:
|
else:
|
||||||
return time.mktime(date_result.utctimetuple())
|
return time.mktime(date_result.timetuple())
|
||||||
|
|
||||||
|
|
|
@ -100,17 +100,24 @@ class CommandActionTest(LogCaptureTestCase):
|
||||||
{'ipjailmatches': "some >char< should \< be[ escap}ed&\n"}),
|
{'ipjailmatches': "some >char< should \< be[ escap}ed&\n"}),
|
||||||
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n")
|
"some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n")
|
||||||
|
|
||||||
|
|
||||||
|
# Recursive
|
||||||
|
aInfo["ABC"] = "<xyz>"
|
||||||
|
self.assertEqual(
|
||||||
|
self.__action.replaceTag("Text <xyz> text <ABC> ABC", aInfo),
|
||||||
|
"Text 890 text 890 ABC")
|
||||||
|
|
||||||
# Callable
|
# Callable
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.__action.replaceTag("09 <callme> 11",
|
self.__action.replaceTag("09 <matches> 11",
|
||||||
CallingMap(callme=lambda: str(10))),
|
CallingMap(matches=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",
|
||||||
CallingMap(callme=lambda: int("a"))), "abc")
|
CallingMap(matches=lambda: int("a"))), "abc")
|
||||||
|
|
||||||
def testExecuteActionBan(self):
|
def testExecuteActionBan(self):
|
||||||
self.__action.actionstart = "touch /tmp/fail2ban.test"
|
self.__action.actionstart = "touch /tmp/fail2ban.test"
|
||||||
|
|
|
@ -177,10 +177,15 @@ class DatabaseTest(unittest.TestCase):
|
||||||
if Fail2BanDb is None: # pragma: no cover
|
if Fail2BanDb is None: # pragma: no cover
|
||||||
return
|
return
|
||||||
self.testAddJail()
|
self.testAddJail()
|
||||||
ticket = FailTicket("127.0.0.1", MyTime.time() - 40, ["abc\n"])
|
self.db.addBan(
|
||||||
self.db.addBan(self.jail, ticket)
|
self.jail, FailTicket("127.0.0.1", MyTime.time() - 60, ["abc\n"]))
|
||||||
|
self.db.addBan(
|
||||||
|
self.jail, FailTicket("127.0.0.1", MyTime.time() - 40, ["abc\n"]))
|
||||||
self.assertEqual(len(self.db.getBans(jail=self.jail,bantime=50)), 1)
|
self.assertEqual(len(self.db.getBans(jail=self.jail,bantime=50)), 1)
|
||||||
self.assertEqual(len(self.db.getBans(jail=self.jail,bantime=20)), 0)
|
self.assertEqual(len(self.db.getBans(jail=self.jail,bantime=20)), 0)
|
||||||
|
# Negative values are for persistent bans, and such all bans should
|
||||||
|
# be returned
|
||||||
|
self.assertEqual(len(self.db.getBans(jail=self.jail,bantime=-1)), 2)
|
||||||
|
|
||||||
def testGetBansMerged(self):
|
def testGetBansMerged(self):
|
||||||
if Fail2BanDb is None: # pragma: no cover
|
if Fail2BanDb is None: # pragma: no cover
|
||||||
|
@ -251,6 +256,10 @@ class DatabaseTest(unittest.TestCase):
|
||||||
self.assertEqual(len(tickets), 1)
|
self.assertEqual(len(tickets), 1)
|
||||||
tickets = self.db.getBansMerged(bantime=5)
|
tickets = self.db.getBansMerged(bantime=5)
|
||||||
self.assertEqual(len(tickets), 0)
|
self.assertEqual(len(tickets), 0)
|
||||||
|
# Negative values are for persistent bans, and such all bans should
|
||||||
|
# be returned
|
||||||
|
tickets = self.db.getBansMerged(bantime=-1)
|
||||||
|
self.assertEqual(len(tickets), 2)
|
||||||
|
|
||||||
def testPurge(self):
|
def testPurge(self):
|
||||||
if Fail2BanDb is None: # pragma: no cover
|
if Fail2BanDb is None: # pragma: no cover
|
||||||
|
|
|
@ -131,7 +131,7 @@ class DateDetectorTest(unittest.TestCase):
|
||||||
# see https://github.com/fail2ban/fail2ban/pull/130
|
# see https://github.com/fail2ban/fail2ban/pull/130
|
||||||
# yoh: unfortunately this test is not really effective to reproduce the
|
# yoh: unfortunately this test is not really effective to reproduce the
|
||||||
# situation but left in place to assure consistent behavior
|
# situation but left in place to assure consistent behavior
|
||||||
mu = time.mktime(datetime.datetime(2012, 10, 11, 2, 37, 17).utctimetuple())
|
mu = time.mktime(datetime.datetime(2012, 10, 11, 2, 37, 17).timetuple())
|
||||||
logdate = self.__datedetector.getTime('2012/10/11 02:37:17 [error] 18434#0')
|
logdate = self.__datedetector.getTime('2012/10/11 02:37:17 [error] 18434#0')
|
||||||
self.assertNotEqual(logdate, None)
|
self.assertNotEqual(logdate, None)
|
||||||
( logTime, logMatch ) = logdate
|
( logTime, logMatch ) = logdate
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
# failJSON: { "time": "2010-09-16T06:51:00", "match": true , "host": "80.187.101.33" }
|
# failJSON: { "time": "2010-09-16T07:51:00", "match": true , "host": "80.187.101.33" }
|
||||||
@400000004c91b044077a9e94 imap-login: Info: Aborted login (auth failed, 1 attempts): user=<martin@waschbuesch.de>, method=CRAM-MD5, rip=80.187.101.33, lip=80.254.129.240, TLS
|
@400000004c91b044077a9e94 imap-login: Info: Aborted login (auth failed, 1 attempts): user=<martin@waschbuesch.de>, method=CRAM-MD5, rip=80.187.101.33, lip=80.254.129.240, TLS
|
||||||
|
|
||||||
# failJSON: { "time": "2010-09-16T06:51:00", "match": true , "host": "176.61.140.224" }
|
# failJSON: { "time": "2010-09-16T07:51:00", "match": true , "host": "176.61.140.224" }
|
||||||
@400000004c91b044077a9e94 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=web rhost=176.61.140.224
|
@400000004c91b044077a9e94 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=web rhost=176.61.140.224
|
||||||
# Above example with injected rhost into ruser -- should not match for 1.2.3.4
|
# Above example with injected rhost into ruser -- should not match for 1.2.3.4
|
||||||
# failJSON: { "time": "2010-09-16T06:51:00", "match": true , "host": "192.0.43.10" }
|
# failJSON: { "time": "2010-09-16T07:51:00", "match": true , "host": "192.0.43.10" }
|
||||||
@400000004c91b044077a9e94 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=rhost=1.2.3.4 rhost=192.0.43.10
|
@400000004c91b044077a9e94 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=rhost=1.2.3.4 rhost=192.0.43.10
|
||||||
# failJSON: { "time": "2010-09-16T06:51:00", "match": true , "host": "176.61.140.225" }
|
# failJSON: { "time": "2010-09-16T07:51:00", "match": true , "host": "176.61.140.225" }
|
||||||
@400000004c91b044077a9e94 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=root rhost=176.61.140.225 user=root
|
@400000004c91b044077a9e94 dovecot-auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=root rhost=176.61.140.225 user=root
|
||||||
|
|
||||||
# failJSON: { "time": "2004-12-12T11:19:11", "match": true , "host": "190.210.136.21" }
|
# failJSON: { "time": "2004-12-12T11:19:11", "match": true , "host": "190.210.136.21" }
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
# failJSON: { "time": "2005-04-16T21:05:29", "match": true , "host": "69.93.127.111" }
|
||||||
|
[PDT Apr 16 21:05:29] error : Warning: Client '69.93.127.111' supplied unknown user 'foo' accessing monit httpd
|
||||||
|
|
||||||
|
# failJSON: { "time": "2005-04-16T20:59:33", "match": true , "host": "97.113.189.111" }
|
||||||
|
[PDT Apr 16 20:59:33] error : Warning: Client '97.113.189.111' supplied wrong password for user 'admin' accessing monit httpd
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
# failJSON: { "time": "2013-07-09T01:45:16", "match": false , "host": "173.242.116.187" }
|
# failJSON: { "time": "2013-07-09T02:45:16", "match": false , "host": "173.242.116.187" }
|
||||||
type=USER_LOGIN msg=audit(1373330716.415:4063): user pid=11998 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=login acct="root" exe="/usr/sbin/sshd" hostname=? addr=173.242.116.187 terminal=ssh res=failed'
|
type=USER_LOGIN msg=audit(1373330716.415:4063): user pid=11998 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=login acct="root" exe="/usr/sbin/sshd" hostname=? addr=173.242.116.187 terminal=ssh res=failed'
|
||||||
|
|
||||||
# failJSON: { "time": "2013-07-09T01:45:17", "match": false , "host": "173.242.116.187" }
|
# failJSON: { "time": "2013-07-09T02:45:17", "match": false , "host": "173.242.116.187" }
|
||||||
type=USER_LOGIN msg=audit(1373330717.000:4068): user pid=12000 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=login acct=28756E6B6E6F776E207573657229 exe="/usr/sbin/sshd" hostname=? addr=173.242.116.187 terminal=ssh res=failed'
|
type=USER_LOGIN msg=audit(1373330717.000:4068): user pid=12000 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=login acct=28756E6B6E6F776E207573657229 exe="/usr/sbin/sshd" hostname=? addr=173.242.116.187 terminal=ssh res=failed'
|
||||||
|
|
||||||
# failJSON: { "time": "2013-07-09T01:45:17", "match": true , "host": "173.242.116.187" }
|
# failJSON: { "time": "2013-07-09T02:45:17", "match": true , "host": "173.242.116.187" }
|
||||||
type=USER_ERR msg=audit(1373330717.000:4070): user pid=12000 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=PAM:bad_ident acct="?" exe="/usr/sbin/sshd" hostname=173.242.116.187 addr=173.242.116.187 terminal=ssh res=failed'
|
type=USER_ERR msg=audit(1373330717.000:4070): user pid=12000 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=PAM:bad_ident acct="?" exe="/usr/sbin/sshd" hostname=173.242.116.187 addr=173.242.116.187 terminal=ssh res=failed'
|
||||||
|
|
||||||
# failJSON: { "time": "2013-07-09T01:45:17", "match": false , "host": "173.242.116.187" }
|
# failJSON: { "time": "2013-07-09T02:45:17", "match": false , "host": "173.242.116.187" }
|
||||||
type=USER_LOGIN msg=audit(1373330717.000:4073): user pid=12000 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=login acct=28696E76616C6964207573657229 exe="/usr/sbin/sshd" hostname=? addr=173.242.116.187 terminal=ssh res=failed'
|
type=USER_LOGIN msg=audit(1373330717.000:4073): user pid=12000 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=login acct=28696E76616C6964207573657229 exe="/usr/sbin/sshd" hostname=? addr=173.242.116.187 terminal=ssh res=failed'
|
||||||
|
|
||||||
# failJSON: { "time": "2013-06-30T01:02:08", "match": false , "host": "113.240.248.18" }
|
# failJSON: { "time": "2013-06-30T02:02:08", "match": false , "host": "113.240.248.18" }
|
||||||
type=USER_LOGIN msg=audit(1372546928.000:52008): user pid=21569 uid=0 auid=0 ses=76 subj=unconfined_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=login acct="sshd" exe="/usr/sbin/sshd" hostname=? addr=113.240.248.18 terminal=ssh res=failed'
|
type=USER_LOGIN msg=audit(1372546928.000:52008): user pid=21569 uid=0 auid=0 ses=76 subj=unconfined_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=login acct="sshd" exe="/usr/sbin/sshd" hostname=? addr=113.240.248.18 terminal=ssh res=failed'
|
||||||
|
|
||||||
# failJSON: { "time": "2013-06-30T02:58:20", "match": true , "host": "113.240.248.18" }
|
# failJSON: { "time": "2013-06-30T03:58:20", "match": true , "host": "113.240.248.18" }
|
||||||
type=USER_ERR msg=audit(1372557500.000:61747): user pid=23684 uid=0 auid=0 ses=76 subj=unconfined_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=PAM:bad_ident acct="?" exe="/usr/sbin/sshd" hostname=113.240.248.18 addr=113.240.248.18 terminal=ssh res=failed'
|
type=USER_ERR msg=audit(1372557500.000:61747): user pid=23684 uid=0 auid=0 ses=76 subj=unconfined_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=PAM:bad_ident acct="?" exe="/usr/sbin/sshd" hostname=113.240.248.18 addr=113.240.248.18 terminal=ssh res=failed'
|
||||||
|
|
||||||
# failJSON: { "time": "2013-06-30T03:58:20", "match": false , "host": "113.240.248.18" }
|
# failJSON: { "time": "2013-06-30T04:58:20", "match": false , "host": "113.240.248.18" }
|
||||||
type=USER_LOGIN msg=audit(1372557500.000:61750): user pid=23684 uid=0 auid=0 ses=76 subj=unconfined_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=login acct=28696E76616C6964207573657229 exe="/usr/sbin/sshd" hostname=? addr=113.240.248.18 terminal=ssh res=failed'
|
type=USER_LOGIN msg=audit(1372557500.000:61750): user pid=23684 uid=0 auid=0 ses=76 subj=unconfined_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=login acct=28696E76616C6964207573657229 exe="/usr/sbin/sshd" hostname=? addr=113.240.248.18 terminal=ssh res=failed'
|
||||||
|
|
||||||
# failJSON: { "time": "2013-07-06T17:48:00", "match": true , "host": "194.228.20.113" }
|
# failJSON: { "time": "2013-07-06T18:48:00", "match": true , "host": "194.228.20.113" }
|
||||||
type=USER_AUTH msg=audit(1373129280.000:9): user pid=1277 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=pubkey acct="root" exe="/usr/sbin/sshd" hostname=? addr=194.228.20.113 terminal=ssh res=failed'
|
type=USER_AUTH msg=audit(1373129280.000:9): user pid=1277 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 msg='op=pubkey acct="root" exe="/usr/sbin/sshd" hostname=? addr=194.228.20.113 terminal=ssh res=failed'
|
||||||
|
|
||||||
# failJSON: { "time": "2013-10-30T07:57:43", "match": true , "host": "192.168.3.100" }
|
# failJSON: { "time": "2013-10-30T07:57:43", "match": true , "host": "192.168.3.100" }
|
||||||
|
|
|
@ -136,3 +136,10 @@ Jul 13 18:44:28 mdop sshd[4931]: Received disconnect from 89.24.13.192: 3: com.j
|
||||||
Feb 12 04:09:18 localhost sshd[26713]: Connection from 115.249.163.77 port 51353
|
Feb 12 04:09:18 localhost sshd[26713]: Connection from 115.249.163.77 port 51353
|
||||||
# failJSON: { "time": "2005-02-12T04:09:21", "match": true , "host": "115.249.163.77", "desc": "from gh-457" }
|
# failJSON: { "time": "2005-02-12T04:09:21", "match": true , "host": "115.249.163.77", "desc": "from gh-457" }
|
||||||
Feb 12 04:09:21 localhost sshd[26713]: Disconnecting: Too many authentication failures for root [preauth]
|
Feb 12 04:09:21 localhost sshd[26713]: Disconnecting: Too many authentication failures for root [preauth]
|
||||||
|
|
||||||
|
# failJSON: { "match": false }
|
||||||
|
Apr 27 13:02:04 host sshd[29116]: User root not allowed because account is locked
|
||||||
|
# failJSON: { "match": false }
|
||||||
|
Apr 27 13:02:04 host sshd[29116]: input_userauth_request: invalid user root [preauth]
|
||||||
|
# failJSON: { "time": "2005-04-27T13:02:04", "match": true , "host": "1.2.3.4", "desc": "No Bye-Bye" }
|
||||||
|
Apr 27 13:02:04 host sshd[29116]: Received disconnect from 1.2.3.4: 11: Normal Shutdown, Thank you for playing [preauth]
|
||||||
|
|
|
@ -794,7 +794,7 @@ class GetFailures(unittest.TestCase):
|
||||||
FILENAME_MULTILINE = os.path.join(TEST_FILES_DIR, "testcase-multiline.log")
|
FILENAME_MULTILINE = os.path.join(TEST_FILES_DIR, "testcase-multiline.log")
|
||||||
|
|
||||||
# so that they could be reused by other tests
|
# so that they could be reused by other tests
|
||||||
FAILURES_01 = ('193.168.0.128', 3, 1124017199.0,
|
FAILURES_01 = ('193.168.0.128', 3, 1124013599.0,
|
||||||
[u'Aug 14 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 193.168.0.128']*3)
|
[u'Aug 14 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 193.168.0.128']*3)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -844,7 +844,7 @@ class GetFailures(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
def testGetFailures02(self):
|
def testGetFailures02(self):
|
||||||
output = ('141.3.81.106', 4, 1124017139.0,
|
output = ('141.3.81.106', 4, 1124013539.0,
|
||||||
[u'Aug 14 11:%d:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:141.3.81.106 port 51332 ssh2'
|
[u'Aug 14 11:%d:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:141.3.81.106 port 51332 ssh2'
|
||||||
% m for m in 53, 54, 57, 58])
|
% m for m in 53, 54, 57, 58])
|
||||||
|
|
||||||
|
@ -854,7 +854,7 @@ class GetFailures(unittest.TestCase):
|
||||||
_assert_correct_last_attempt(self, self.filter, output)
|
_assert_correct_last_attempt(self, self.filter, output)
|
||||||
|
|
||||||
def testGetFailures03(self):
|
def testGetFailures03(self):
|
||||||
output = ('203.162.223.135', 7, 1124017144.0)
|
output = ('203.162.223.135', 7, 1124013544.0)
|
||||||
|
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_03)
|
self.filter.addLogPath(GetFailures.FILENAME_03)
|
||||||
self.filter.addFailRegex("error,relay=<HOST>,.*550 User unknown")
|
self.filter.addFailRegex("error,relay=<HOST>,.*550 User unknown")
|
||||||
|
@ -862,7 +862,7 @@ class GetFailures(unittest.TestCase):
|
||||||
_assert_correct_last_attempt(self, self.filter, output)
|
_assert_correct_last_attempt(self, self.filter, output)
|
||||||
|
|
||||||
def testGetFailures04(self):
|
def testGetFailures04(self):
|
||||||
output = [('212.41.96.186', 4, 1124017200.0),
|
output = [('212.41.96.186', 4, 1124013600.0),
|
||||||
('212.41.96.185', 4, 1124017198.0)]
|
('212.41.96.185', 4, 1124017198.0)]
|
||||||
|
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_04)
|
self.filter.addLogPath(GetFailures.FILENAME_04)
|
||||||
|
@ -877,11 +877,11 @@ class GetFailures(unittest.TestCase):
|
||||||
|
|
||||||
def testGetFailuresUseDNS(self):
|
def testGetFailuresUseDNS(self):
|
||||||
# We should still catch failures with usedns = no ;-)
|
# We should still catch failures with usedns = no ;-)
|
||||||
output_yes = ('93.184.216.119', 2, 1124017139.0,
|
output_yes = ('93.184.216.119', 2, 1124013539.0,
|
||||||
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2',
|
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2',
|
||||||
u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2'])
|
u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2'])
|
||||||
|
|
||||||
output_no = ('93.184.216.119', 1, 1124017139.0,
|
output_no = ('93.184.216.119', 1, 1124013539.0,
|
||||||
[u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2'])
|
[u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2'])
|
||||||
|
|
||||||
# Actually no exception would be raised -- it will be just set to 'no'
|
# Actually no exception would be raised -- it will be just set to 'no'
|
||||||
|
@ -904,7 +904,7 @@ class GetFailures(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
def testGetFailuresMultiRegex(self):
|
def testGetFailuresMultiRegex(self):
|
||||||
output = ('141.3.81.106', 8, 1124017141.0)
|
output = ('141.3.81.106', 8, 1124013541.0)
|
||||||
|
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_02)
|
self.filter.addLogPath(GetFailures.FILENAME_02)
|
||||||
self.filter.addFailRegex("Failed .* from <HOST>")
|
self.filter.addFailRegex("Failed .* from <HOST>")
|
||||||
|
@ -923,8 +923,8 @@ class GetFailures(unittest.TestCase):
|
||||||
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
||||||
|
|
||||||
def testGetFailuresMultiLine(self):
|
def testGetFailuresMultiLine(self):
|
||||||
output = [("192.0.43.10", 2, 1124017199.0),
|
output = [("192.0.43.10", 2, 1124013599.0),
|
||||||
("192.0.43.11", 1, 1124017198.0)]
|
("192.0.43.11", 1, 1124013598.0)]
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE)
|
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE)
|
||||||
self.filter.addFailRegex("^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
self.filter.addFailRegex("^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
||||||
self.filter.setMaxLines(100)
|
self.filter.setMaxLines(100)
|
||||||
|
@ -942,7 +942,7 @@ class GetFailures(unittest.TestCase):
|
||||||
self.assertEqual(sorted(foundList), sorted(output))
|
self.assertEqual(sorted(foundList), sorted(output))
|
||||||
|
|
||||||
def testGetFailuresMultiLineIgnoreRegex(self):
|
def testGetFailuresMultiLineIgnoreRegex(self):
|
||||||
output = [("192.0.43.10", 2, 1124017199.0)]
|
output = [("192.0.43.10", 2, 1124013599.0)]
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE)
|
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE)
|
||||||
self.filter.addFailRegex("^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
self.filter.addFailRegex("^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
||||||
self.filter.addIgnoreRegex("rsync error: Received SIGINT")
|
self.filter.addIgnoreRegex("rsync error: Received SIGINT")
|
||||||
|
@ -956,9 +956,9 @@ class GetFailures(unittest.TestCase):
|
||||||
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
||||||
|
|
||||||
def testGetFailuresMultiLineMultiRegex(self):
|
def testGetFailuresMultiLineMultiRegex(self):
|
||||||
output = [("192.0.43.10", 2, 1124017199.0),
|
output = [("192.0.43.10", 2, 1124013599.0),
|
||||||
("192.0.43.11", 1, 1124017198.0),
|
("192.0.43.11", 1, 1124013598.0),
|
||||||
("192.0.43.15", 1, 1124017198.0)]
|
("192.0.43.15", 1, 1124013598.0)]
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE)
|
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE)
|
||||||
self.filter.addFailRegex("^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
self.filter.addFailRegex("^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
||||||
self.filter.addFailRegex("^.* sendmail\[.*, msgid=<(?P<msgid>[^>]+).*relay=\[<HOST>\].*$<SKIPLINES>^.+ spamd: result: Y \d+ .*,mid=<(?P=msgid)>(,bayes=[.\d]+)?(,autolearn=\S+)?\s*$")
|
self.filter.addFailRegex("^.* sendmail\[.*, msgid=<(?P<msgid>[^>]+).*relay=\[<HOST>\].*$<SKIPLINES>^.+ spamd: result: Y \d+ .*,mid=<(?P=msgid)>(,bayes=[.\d]+)?(,autolearn=\S+)?\s*$")
|
||||||
|
|
|
@ -32,8 +32,7 @@ import datetime
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
|
||||||
from .utils import mbasename, TraceBack, FormatterWithTraceBack
|
from ..helpers import formatExceptionInfo, mbasename, TraceBack, FormatterWithTraceBack
|
||||||
from ..helpers import formatExceptionInfo
|
|
||||||
from ..server.datetemplate import DatePatternRegex
|
from ..server.datetemplate import DatePatternRegex
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -129,7 +129,7 @@ def testSampleRegexsFactory(name):
|
||||||
jsonTimeLocal = datetime.datetime.strptime(t, "%Y-%m-%dT%H:%M:%S.%f")
|
jsonTimeLocal = datetime.datetime.strptime(t, "%Y-%m-%dT%H:%M:%S.%f")
|
||||||
|
|
||||||
|
|
||||||
jsonTime = time.mktime(jsonTimeLocal.utctimetuple())
|
jsonTime = time.mktime(jsonTimeLocal.timetuple())
|
||||||
|
|
||||||
jsonTime += jsonTimeLocal.microsecond / 1000000
|
jsonTime += jsonTimeLocal.microsecond / 1000000
|
||||||
|
|
||||||
|
|
|
@ -22,90 +22,17 @@ __author__ = "Yaroslav Halchenko"
|
||||||
__copyright__ = "Copyright (c) 2013 Yaroslav Halchenko"
|
__copyright__ = "Copyright (c) 2013 Yaroslav Halchenko"
|
||||||
__license__ = "GPL"
|
__license__ = "GPL"
|
||||||
|
|
||||||
import logging, os, re, traceback, time, unittest
|
import logging
|
||||||
from os.path import basename, dirname
|
import os
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
|
||||||
from ..server.mytime import MyTime
|
from ..server.mytime import MyTime
|
||||||
|
|
||||||
logSys = logging.getLogger(__name__)
|
logSys = logging.getLogger(__name__)
|
||||||
|
|
||||||
#
|
|
||||||
# Following "traceback" functions are adopted from PyMVPA distributed
|
|
||||||
# under MIT/Expat and copyright by PyMVPA developers (i.e. me and
|
|
||||||
# Michael). Hereby I re-license derivative work on these pieces under GPL
|
|
||||||
# to stay in line with the main Fail2Ban license
|
|
||||||
#
|
|
||||||
def mbasename(s):
|
|
||||||
"""Custom function to include directory name if filename is too common
|
|
||||||
|
|
||||||
Also strip .py at the end
|
|
||||||
"""
|
|
||||||
base = basename(s)
|
|
||||||
if base.endswith('.py'):
|
|
||||||
base = base[:-3]
|
|
||||||
if base in set(['base', '__init__']):
|
|
||||||
base = basename(dirname(s)) + '.' + base
|
|
||||||
return base
|
|
||||||
|
|
||||||
class TraceBack(object):
|
|
||||||
"""Customized traceback to be included in debug messages
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, compress=False):
|
|
||||||
"""Initialize TrackBack metric
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
compress : bool
|
|
||||||
if True then prefix common with previous invocation gets
|
|
||||||
replaced with ...
|
|
||||||
"""
|
|
||||||
self.__prev = ""
|
|
||||||
self.__compress = compress
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
ftb = traceback.extract_stack(limit=100)[:-2]
|
|
||||||
entries = [[mbasename(x[0]), dirname(x[0]), str(x[1])] for x in ftb]
|
|
||||||
entries = [ [e[0], e[2]] for e in entries
|
|
||||||
if not (e[0] in ['unittest', 'logging.__init__']
|
|
||||||
or e[1].endswith('/unittest'))]
|
|
||||||
|
|
||||||
# lets make it more concise
|
|
||||||
entries_out = [entries[0]]
|
|
||||||
for entry in entries[1:]:
|
|
||||||
if entry[0] == entries_out[-1][0]:
|
|
||||||
entries_out[-1][1] += ',%s' % entry[1]
|
|
||||||
else:
|
|
||||||
entries_out.append(entry)
|
|
||||||
sftb = '>'.join(['%s:%s' % (mbasename(x[0]),
|
|
||||||
x[1]) for x in entries_out])
|
|
||||||
if self.__compress:
|
|
||||||
# lets remove part which is common with previous invocation
|
|
||||||
prev_next = sftb
|
|
||||||
common_prefix = os.path.commonprefix((self.__prev, sftb))
|
|
||||||
common_prefix2 = re.sub('>[^>]*$', '', common_prefix)
|
|
||||||
|
|
||||||
if common_prefix2 != "":
|
|
||||||
sftb = '...' + sftb[len(common_prefix2):]
|
|
||||||
self.__prev = prev_next
|
|
||||||
|
|
||||||
return sftb
|
|
||||||
|
|
||||||
class FormatterWithTraceBack(logging.Formatter):
|
|
||||||
"""Custom formatter which expands %(tb) and %(tbc) with tracebacks
|
|
||||||
|
|
||||||
TODO: might need locking in case of compressed tracebacks
|
|
||||||
"""
|
|
||||||
def __init__(self, fmt, *args, **kwargs):
|
|
||||||
logging.Formatter.__init__(self, fmt=fmt, *args, **kwargs)
|
|
||||||
compress = '%(tbc)s' in fmt
|
|
||||||
self._tb = TraceBack(compress=compress)
|
|
||||||
|
|
||||||
def format(self, record):
|
|
||||||
record.tbc = record.tb = self._tb()
|
|
||||||
return logging.Formatter.format(self, record)
|
|
||||||
|
|
||||||
def mtimesleep():
|
def mtimesleep():
|
||||||
# no sleep now should be necessary since polling tracks now not only
|
# no sleep now should be necessary since polling tracks now not only
|
||||||
# mtime but also ino and size
|
# mtime but also ino and size
|
||||||
|
@ -146,7 +73,6 @@ def gatherTests(regexps=None, no_network=False):
|
||||||
if not regexps: # pragma: no cover
|
if not regexps: # pragma: no cover
|
||||||
tests = unittest.TestSuite()
|
tests = unittest.TestSuite()
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
import re
|
|
||||||
class FilteredTestSuite(unittest.TestSuite):
|
class FilteredTestSuite(unittest.TestSuite):
|
||||||
_regexps = [re.compile(r) for r in regexps]
|
_regexps = [re.compile(r) for r in regexps]
|
||||||
def addTest(self, suite):
|
def addTest(self, suite):
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
description "fail2ban - ban hosts that cause multiple authentication errors"
|
description "fail2ban - ban hosts that cause multiple authentication errors"
|
||||||
|
|
||||||
start on filesystem and started networking
|
start on filesystem and static-network-up
|
||||||
stop on deconfiguring-networking
|
stop on runlevel [016]
|
||||||
|
|
||||||
expect fork
|
expect fork
|
||||||
respawn
|
respawn
|
||||||
|
|
||||||
exec /usr/bin/fail2ban-client -x -b start
|
env RUNDIR=/var/run/fail2ban
|
||||||
|
|
||||||
|
pre-start script
|
||||||
|
test -d $RUNDIR || mkdir -p $RUNDIR
|
||||||
|
test ! -e $RUNDIR/fail2ban.sock || rm -f $RUNDIR/fail2ban.sock
|
||||||
|
end script
|
||||||
|
|
||||||
|
exec /usr/bin/fail2ban-client -f -x start
|
||||||
|
|
||||||
pre-stop exec /usr/bin/fail2ban-client stop
|
pre-stop exec /usr/bin/fail2ban-client stop
|
||||||
|
|
||||||
post-stop exec rm -f /var/run/fail2ban/fail2ban.pid
|
post-stop exec rm -f $RUNDIR/fail2ban.pid
|
||||||
|
|
|
@ -34,6 +34,12 @@ decrease verbosity
|
||||||
\fB\-x\fR
|
\fB\-x\fR
|
||||||
force execution of the server (remove socket file)
|
force execution of the server (remove socket file)
|
||||||
.TP
|
.TP
|
||||||
|
\fB\-b\fR
|
||||||
|
start the server in background mode (default)
|
||||||
|
.TP
|
||||||
|
\fB\-f\fR
|
||||||
|
start the server in foreground mode (note that the client forks once itself)
|
||||||
|
.TP
|
||||||
\fB\-h\fR, \fB\-\-help\fR
|
\fB\-h\fR, \fB\-\-help\fR
|
||||||
display this help message
|
display this help message
|
||||||
.TP
|
.TP
|
||||||
|
|
Loading…
Reference in New Issue