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"
|
||||
- "3.2"
|
||||
- "3.3"
|
||||
- "3.4"
|
||||
- "pypy"
|
||||
before_install:
|
||||
- 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
|
||||
* fail2ban-regex assertion error caused by miscount missed lines with
|
||||
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:
|
||||
- Added monit filter thanks Jason H Martin.
|
||||
|
||||
|
||||
- Enhancements
|
||||
* Fail2ban-regex - add print-all-matched option. Closes gh-652
|
||||
* 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
|
||||
----------
|
||||
|
|
2
THANKS
2
THANKS
|
@ -48,6 +48,7 @@ Ivo Truxa
|
|||
John Thoe
|
||||
Jacques Lav!gnotte
|
||||
Ioan Indreias
|
||||
Jason H Martin
|
||||
Jonathan Kamens
|
||||
Jonathan Lanning
|
||||
Jonathan Underwood
|
||||
|
@ -85,6 +86,7 @@ Rolf Fokkens
|
|||
Roman Gelfand
|
||||
Russell Odom
|
||||
Sebastian Arcus
|
||||
sebres
|
||||
Sireyessire
|
||||
silviogarbes
|
||||
Stefan Tatschner
|
||||
|
|
|
@ -51,6 +51,7 @@ class Fail2banClient:
|
|||
self.__conf["conf"] = "/etc/fail2ban"
|
||||
self.__conf["dump"] = False
|
||||
self.__conf["force"] = False
|
||||
self.__conf["background"] = True
|
||||
self.__conf["verbose"] = 1
|
||||
self.__conf["interactive"] = False
|
||||
self.__conf["socket"] = None
|
||||
|
@ -83,6 +84,8 @@ class Fail2banClient:
|
|||
print " -v increase verbosity"
|
||||
print " -q decrease verbosity"
|
||||
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 " -V, --version print the version"
|
||||
print
|
||||
|
@ -125,6 +128,10 @@ class Fail2banClient:
|
|||
self.__conf["force"] = True
|
||||
elif opt[0] == "-i":
|
||||
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"]:
|
||||
self.dispUsage()
|
||||
sys.exit(0)
|
||||
|
@ -194,7 +201,8 @@ class Fail2banClient:
|
|||
# Start the server
|
||||
self.__startServerAsync(self.__conf["socket"],
|
||||
self.__conf["pidfile"],
|
||||
self.__conf["force"])
|
||||
self.__conf["force"],
|
||||
self.__conf["background"])
|
||||
try:
|
||||
# Wait for the server to start
|
||||
self.__waitOnServer()
|
||||
|
@ -242,14 +250,12 @@ class Fail2banClient:
|
|||
#
|
||||
# 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.
|
||||
pid = os.fork()
|
||||
if pid == 0:
|
||||
args = list()
|
||||
args.append(self.SERVER)
|
||||
# Start in background mode.
|
||||
args.append("-b")
|
||||
# Set the socket path.
|
||||
args.append("-s")
|
||||
args.append(socket)
|
||||
|
@ -259,6 +265,12 @@ class Fail2banClient:
|
|||
# Force the execution if needed.
|
||||
if force:
|
||||
args.append("-x")
|
||||
# Start in foreground mode if requested.
|
||||
if background:
|
||||
args.append("-b")
|
||||
else:
|
||||
args.append("-f")
|
||||
|
||||
try:
|
||||
# Use the current directory.
|
||||
exe = os.path.abspath(os.path.join(sys.path[0], self.SERVER))
|
||||
|
@ -312,7 +324,7 @@ class Fail2banClient:
|
|||
|
||||
# Reads the command line options.
|
||||
try:
|
||||
cmdOpts = 'hc:s:p:xdviqV'
|
||||
cmdOpts = 'hc:s:p:xfbdviqV'
|
||||
cmdLongOpts = ['help', 'version']
|
||||
optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts)
|
||||
except getopt.GetoptError:
|
||||
|
|
|
@ -45,7 +45,7 @@ from fail2ban.client.filterreader import FilterReader
|
|||
from fail2ban.server.filter import Filter
|
||||
from fail2ban.server.failregex import RegexException
|
||||
|
||||
from fail2ban.tests.utils import FormatterWithTraceBack
|
||||
from fail2ban.helpers import FormatterWithTraceBack
|
||||
# Gets the instance of the logger.
|
||||
logSys = logging.getLogger("fail2ban")
|
||||
|
||||
|
|
|
@ -34,7 +34,8 @@ if os.path.exists("fail2ban/__init__.py"):
|
|||
sys.path.insert(0, ".")
|
||||
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 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)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*$
|
||||
^(?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)Connection from <HOST> port \d+<SKIPLINES>(?P=__prefix)Disconnecting: Too many authentication failures for .+? \[preauth\]$
|
||||
|
||||
|
|
|
@ -409,6 +409,12 @@ maxretry = 5
|
|||
port = http,https
|
||||
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]
|
||||
|
||||
|
|
|
@ -20,9 +20,90 @@
|
|||
__author__ = "Cyril Jaquier, Arturo 'Buanzo' Busleiman, Yaroslav Halchenko"
|
||||
__license__ = "GPL"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
import re
|
||||
import logging
|
||||
|
||||
def formatExceptionInfo():
|
||||
""" Consistently format exception information """
|
||||
import sys
|
||||
cla, exc = sys.exc_info()[:2]
|
||||
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
|
||||
"""
|
||||
|
||||
_escapedTags = set(('matches', 'ipmatches', 'ipjailmatches'))
|
||||
|
||||
def __init__(self, jail, name):
|
||||
super(CommandAction, self).__init__(jail, name)
|
||||
self.timeout = 60
|
||||
|
@ -351,8 +353,8 @@ class CommandAction(ActionBase):
|
|||
if not self.executeCmd(stopCmd, self.timeout):
|
||||
raise RuntimeError("Error stopping action")
|
||||
|
||||
@staticmethod
|
||||
def substituteRecursiveTags(tags):
|
||||
@classmethod
|
||||
def substituteRecursiveTags(cls, tags):
|
||||
"""Sort out tag definitions within other tags.
|
||||
|
||||
so: becomes:
|
||||
|
@ -371,8 +373,11 @@ class CommandAction(ActionBase):
|
|||
within the values recursively replaced.
|
||||
"""
|
||||
t = re.compile(r'<([^ >]+)>')
|
||||
for tag, value in tags.iteritems():
|
||||
value = str(value)
|
||||
for tag in tags.iterkeys():
|
||||
if tag in cls._escapedTags:
|
||||
# Escaped so won't match
|
||||
continue
|
||||
value = str(tags[tag])
|
||||
m = t.search(value)
|
||||
done = []
|
||||
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
|
||||
|
@ -383,6 +388,9 @@ class CommandAction(ActionBase):
|
|||
# recursive definitions are bad
|
||||
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
|
||||
return False
|
||||
elif found_tag in cls._escapedTags:
|
||||
# Escaped so won't match
|
||||
continue
|
||||
else:
|
||||
if tags.has_key(found_tag):
|
||||
value = value.replace('<%s>' % found_tag , tags[found_tag])
|
||||
|
@ -441,10 +449,11 @@ class CommandAction(ActionBase):
|
|||
`query` string with tags replaced.
|
||||
"""
|
||||
string = query
|
||||
aInfo = cls.substituteRecursiveTags(aInfo)
|
||||
for tag in aInfo:
|
||||
if "<%s>" % tag in query:
|
||||
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
|
||||
# out of our control
|
||||
value = cls.escapeTag(value)
|
||||
|
|
|
@ -417,7 +417,7 @@ class Fail2BanDb(object):
|
|||
if jail is not None:
|
||||
query += " AND jail=?"
|
||||
queryArgs.append(jail.name)
|
||||
if bantime is not None:
|
||||
if bantime is not None and bantime >= 0:
|
||||
query += " AND timeofban > ?"
|
||||
queryArgs.append(MyTime.time() - bantime)
|
||||
if ip is not None:
|
||||
|
@ -436,7 +436,8 @@ class Fail2BanDb(object):
|
|||
Jail that the ban belongs to. Default `None`; all jails.
|
||||
bantime : int
|
||||
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 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.
|
||||
bantime : int
|
||||
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 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
|
||||
returned.
|
||||
"""
|
||||
if bantime is None:
|
||||
cacheKey = None
|
||||
if bantime is None or bantime < 0:
|
||||
cacheKey = (ip, jail)
|
||||
if cacheKey in self._bansMergedCache:
|
||||
return self._bansMergedCache[cacheKey]
|
||||
|
@ -505,7 +508,7 @@ class Fail2BanDb(object):
|
|||
ticket.setAttempt(failures)
|
||||
tickets.append(ticket)
|
||||
|
||||
if bantime is None:
|
||||
if cacheKey:
|
||||
self._bansMergedCache[cacheKey] = tickets if ip is None else ticket
|
||||
return tickets if ip is None else ticket
|
||||
|
||||
|
|
|
@ -529,11 +529,19 @@ class Server:
|
|||
except (AttributeError, ValueError):
|
||||
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):
|
||||
try:
|
||||
if not os.path.sameopenfile(urandom_fd, fd):
|
||||
os.close(fd)
|
||||
except OSError: # ERROR (ignore)
|
||||
pass
|
||||
os.close(urandom_fd)
|
||||
else:
|
||||
os.closerange(0, maxfd)
|
||||
|
||||
# Redirect the standard file descriptors to /dev/null.
|
||||
os.open("/dev/null", os.O_RDONLY) # standard input (0)
|
||||
|
|
|
@ -190,5 +190,5 @@ def reGroupDictStrptime(found_dict):
|
|||
if gmtoff is not None:
|
||||
return calendar.timegm(date_result.utctimetuple())
|
||||
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"}),
|
||||
"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
|
||||
self.assertEqual(
|
||||
self.__action.replaceTag("09 <callme> 11",
|
||||
CallingMap(callme=lambda: str(10))),
|
||||
self.__action.replaceTag("09 <matches> 11",
|
||||
CallingMap(matches=lambda: str(10))),
|
||||
"09 10 11")
|
||||
|
||||
# As tag not present, therefore callable should not be called
|
||||
# Will raise ValueError if it is
|
||||
self.assertEqual(
|
||||
self.__action.replaceTag("abc",
|
||||
CallingMap(callme=lambda: int("a"))), "abc")
|
||||
CallingMap(matches=lambda: int("a"))), "abc")
|
||||
|
||||
def testExecuteActionBan(self):
|
||||
self.__action.actionstart = "touch /tmp/fail2ban.test"
|
||||
|
|
|
@ -177,10 +177,15 @@ class DatabaseTest(unittest.TestCase):
|
|||
if Fail2BanDb is None: # pragma: no cover
|
||||
return
|
||||
self.testAddJail()
|
||||
ticket = FailTicket("127.0.0.1", MyTime.time() - 40, ["abc\n"])
|
||||
self.db.addBan(self.jail, ticket)
|
||||
self.db.addBan(
|
||||
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=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):
|
||||
if Fail2BanDb is None: # pragma: no cover
|
||||
|
@ -251,6 +256,10 @@ class DatabaseTest(unittest.TestCase):
|
|||
self.assertEqual(len(tickets), 1)
|
||||
tickets = self.db.getBansMerged(bantime=5)
|
||||
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):
|
||||
if Fail2BanDb is None: # pragma: no cover
|
||||
|
|
|
@ -131,7 +131,7 @@ class DateDetectorTest(unittest.TestCase):
|
|||
# see https://github.com/fail2ban/fail2ban/pull/130
|
||||
# yoh: unfortunately this test is not really effective to reproduce the
|
||||
# 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')
|
||||
self.assertNotEqual(logdate, None)
|
||||
( 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
|
||||
|
||||
# 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
|
||||
# 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
|
||||
# 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
|
||||
|
||||
# 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'
|
||||
|
||||
# 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'
|
||||
|
||||
# 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'
|
||||
|
||||
# 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'
|
||||
|
||||
# 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'
|
||||
|
||||
# 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'
|
||||
|
||||
# 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'
|
||||
|
||||
# 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'
|
||||
|
||||
# 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
|
||||
# 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]
|
||||
|
||||
# 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")
|
||||
|
||||
# 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)
|
||||
|
||||
def setUp(self):
|
||||
|
@ -844,7 +844,7 @@ class GetFailures(unittest.TestCase):
|
|||
|
||||
|
||||
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'
|
||||
% m for m in 53, 54, 57, 58])
|
||||
|
||||
|
@ -854,7 +854,7 @@ class GetFailures(unittest.TestCase):
|
|||
_assert_correct_last_attempt(self, self.filter, output)
|
||||
|
||||
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.addFailRegex("error,relay=<HOST>,.*550 User unknown")
|
||||
|
@ -862,7 +862,7 @@ class GetFailures(unittest.TestCase):
|
|||
_assert_correct_last_attempt(self, self.filter, output)
|
||||
|
||||
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)]
|
||||
|
||||
self.filter.addLogPath(GetFailures.FILENAME_04)
|
||||
|
@ -877,11 +877,11 @@ class GetFailures(unittest.TestCase):
|
|||
|
||||
def testGetFailuresUseDNS(self):
|
||||
# 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: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'])
|
||||
|
||||
# Actually no exception would be raised -- it will be just set to 'no'
|
||||
|
@ -904,7 +904,7 @@ class GetFailures(unittest.TestCase):
|
|||
|
||||
|
||||
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.addFailRegex("Failed .* from <HOST>")
|
||||
|
@ -923,8 +923,8 @@ class GetFailures(unittest.TestCase):
|
|||
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
||||
|
||||
def testGetFailuresMultiLine(self):
|
||||
output = [("192.0.43.10", 2, 1124017199.0),
|
||||
("192.0.43.11", 1, 1124017198.0)]
|
||||
output = [("192.0.43.10", 2, 1124013599.0),
|
||||
("192.0.43.11", 1, 1124013598.0)]
|
||||
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE)
|
||||
self.filter.addFailRegex("^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
||||
self.filter.setMaxLines(100)
|
||||
|
@ -942,7 +942,7 @@ class GetFailures(unittest.TestCase):
|
|||
self.assertEqual(sorted(foundList), sorted(output))
|
||||
|
||||
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.addFailRegex("^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
||||
self.filter.addIgnoreRegex("rsync error: Received SIGINT")
|
||||
|
@ -956,9 +956,9 @@ class GetFailures(unittest.TestCase):
|
|||
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
||||
|
||||
def testGetFailuresMultiLineMultiRegex(self):
|
||||
output = [("192.0.43.10", 2, 1124017199.0),
|
||||
("192.0.43.11", 1, 1124017198.0),
|
||||
("192.0.43.15", 1, 1124017198.0)]
|
||||
output = [("192.0.43.10", 2, 1124013599.0),
|
||||
("192.0.43.11", 1, 1124013598.0),
|
||||
("192.0.43.15", 1, 1124013598.0)]
|
||||
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("^.* 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 StringIO import StringIO
|
||||
|
||||
from .utils import mbasename, TraceBack, FormatterWithTraceBack
|
||||
from ..helpers import formatExceptionInfo
|
||||
from ..helpers import formatExceptionInfo, mbasename, TraceBack, FormatterWithTraceBack
|
||||
from ..server.datetemplate import DatePatternRegex
|
||||
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ def testSampleRegexsFactory(name):
|
|||
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
|
||||
|
||||
|
|
|
@ -22,90 +22,17 @@ __author__ = "Yaroslav Halchenko"
|
|||
__copyright__ = "Copyright (c) 2013 Yaroslav Halchenko"
|
||||
__license__ = "GPL"
|
||||
|
||||
import logging, os, re, traceback, time, unittest
|
||||
from os.path import basename, dirname
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import unittest
|
||||
from StringIO import StringIO
|
||||
|
||||
from ..server.mytime import MyTime
|
||||
|
||||
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():
|
||||
# no sleep now should be necessary since polling tracks now not only
|
||||
# mtime but also ino and size
|
||||
|
@ -146,7 +73,6 @@ def gatherTests(regexps=None, no_network=False):
|
|||
if not regexps: # pragma: no cover
|
||||
tests = unittest.TestSuite()
|
||||
else: # pragma: no cover
|
||||
import re
|
||||
class FilteredTestSuite(unittest.TestSuite):
|
||||
_regexps = [re.compile(r) for r in regexps]
|
||||
def addTest(self, suite):
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
description "fail2ban - ban hosts that cause multiple authentication errors"
|
||||
|
||||
start on filesystem and started networking
|
||||
stop on deconfiguring-networking
|
||||
start on filesystem and static-network-up
|
||||
stop on runlevel [016]
|
||||
|
||||
expect fork
|
||||
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
|
||||
|
||||
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
|
||||
force execution of the server (remove socket file)
|
||||
.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
|
||||
display this help message
|
||||
.TP
|
||||
|
|
Loading…
Reference in New Issue