Merge pull request #368 from grooverdan/0.9_datedetmerge

MRG: general merge from master + date time zone
pull/413/head
Daniel Black 2013-09-28 15:18:20 -07:00
commit b8d9c07280
19 changed files with 349 additions and 164 deletions

View File

@ -67,6 +67,8 @@ ver. 0.8.11 (2013/XX/XXX) - loves-unittests
all platforms to ensure permissions are the same before and after a ban - all platforms to ensure permissions are the same before and after a ban -
closes gh-266. hostsdeny supports daemon_list now too. closes gh-266. hostsdeny supports daemon_list now too.
* filter.d/roundcube-auth - timezone offset can be positive or negative * filter.d/roundcube-auth - timezone offset can be positive or negative
* action.d/bsd-ipfw - action option unsed. Fixed to blocktype for
consistency. default to port unreach instead of deny
Rolf Fokkens Rolf Fokkens
* action.d/dshield.conf and complain.conf -- reorder mailx arguments. * action.d/dshield.conf and complain.conf -- reorder mailx arguments.
https://bugzilla.redhat.com/show_bug.cgi?id=998020 https://bugzilla.redhat.com/show_bug.cgi?id=998020
@ -78,6 +80,9 @@ ver. 0.8.11 (2013/XX/XXX) - loves-unittests
avoiding problems with getpid. Also $network and iptables moved avoiding problems with getpid. Also $network and iptables moved
to Should- rc init fields to Should- rc init fields
- New Features: - New Features:
Andy Fragen and Daniel Black
* filter.d/osx-ipfw.conf - ipfw action for OSX based on random rule
numbers.
Daniel Black & ykimon Daniel Black & ykimon
* filter.d/3proxy.conf -- filter added * filter.d/3proxy.conf -- filter added
Daniel Black Daniel Black
@ -95,6 +100,7 @@ ver. 0.8.11 (2013/XX/XXX) - loves-unittests
and extra failure examples in sample logs and extra failure examples in sample logs
* filter.d/apache-auth - added expressions for mod_authz, mod_auth and * filter.d/apache-auth - added expressions for mod_authz, mod_auth and
mod_auth_digest failures. mod_auth_digest failures.
* Support %z (Timezone offset) and %f (sub-seconds) support for datedetector
Daniel Black & Georgiy Mernov & ftoppi & Мернов Георгий Daniel Black & Georgiy Mernov & ftoppi & Мернов Георгий
* filter.d/exim.conf -- regex hardening and extra failure examples in * filter.d/exim.conf -- regex hardening and extra failure examples in
sample logs sample logs

1
THANKS
View File

@ -7,6 +7,7 @@ will be added
Adrien Clerc Adrien Clerc
ache ache
Andrey G. Grozin Andrey G. Grozin
Andy Fragen
Arturo 'Buanzo' Busleiman Arturo 'Buanzo' Busleiman
Axel Thimm Axel Thimm
Bill Heaton Bill Heaton

View File

@ -14,7 +14,7 @@
# Notes.: command executed once at the start of Fail2Ban. # Notes.: command executed once at the start of Fail2Ban.
# Values: CMD # Values: CMD
# #
actionstart = ipfw show | fgrep -q 'table(<table>)' || ( ipfw show | awk 'BEGIN { b = 1 } { if ($1 <= b) { b = $1 + 1 } else { e = b } } END { if (e) exit e <br> else exit b }'; num=$?; ipfw -q add $num deny <block> from table\(<table>\) to me <port>; echo $num > "<startstatefile>" ) actionstart = ipfw show | fgrep -q 'table(<table>)' || ( ipfw show | awk 'BEGIN { b = 1 } { if ($1 <= b) { b = $1 + 1 } else { e = b } } END { if (e) exit e <br> else exit b }'; num=$?; ipfw -q add $num <blocktype> <block> from table\(<table>\) to me <port>; echo $num > "<startstatefile>" )
# Option: actionstop # Option: actionstop
@ -68,15 +68,16 @@ port =
# Values: STRING # Values: STRING
startstatefile = /var/run/fail2ban/ipfw-started-table_<table> startstatefile = /var/run/fail2ban/ipfw-started-table_<table>
# Option: action
# Notes: This is the action to take for automaticly created rules. See the
# ACTION defination at the top of man ipfw for allowed values.
# "deny" and "unreach port" are probably the useful.
# Values: STRING
action = deny
# Option: block # Option: block
# Notes: This is how much to block. # Notes: This is how much to block.
# Can be "ip", "tcp", "udp" or various other options. # Can be "ip", "tcp", "udp" or various other options.
# Values: STRING # Values: STRING
block = ip block = ip
# Option: blocktype
# Notes.: How to block the traffic. Use a action from man 5 ipfw
# Common values: deny, unreach port, reset
# ACTION defination at the top of man ipfw for allowed values.
# Values: STRING
#
blocktype = unreach port

View File

@ -0,0 +1,87 @@
# Fail2Ban configuration file
#
# Author: Nick Munger
# Modified by: Andy Fragen and Daniel Black
#
# Mod for OS X, using random rulenum as OSX ipfw doesn't include tables
#
[Definition]
# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
#
actionstart =
# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
actionstop =
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck =
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# Values: CMD
#
actionban = ipfw add <rulenum> set <setnum> <blocktype> log <block> from <ip> to <dst> <port>
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# Values: CMD
#
actionunban = ipfw delete `ipfw -S list | grep -i 'set <setnum> <blocktype> log <block> from <ip> to <dst>' | awk '{print $1;}'`
[Init]
# Option: port
# Notes.: specifies port to block. Can be blank however may require block="ip"
# Values: [ NUM | STRING ]
#
port = ssh
# Option: dst
# Notes.: the local IP address of the network interface
# Values: IP, any, me or anything support by ipfw as a dst
#
dst = me
# Option: block
# Notes: This is how much to block.
# Can be "ip", "tcp", "udp" or various other options.
# Values: STRING
block = tcp
# Option: blocktype
# Notes.: How to block the traffic. Use a action from man 8 ipfw
# Common values: deny, unreach port, reset
# Values: STRING
#
blocktype = unreach port
# Option: set number
# Notes.: The ipset number this is added to.
# Values: 0-31
setnum = 10
# Option: number for ipfw rule
# Notes: This is meant to be automaticly generated and not overwritten
# Values: Random value between 10000 and 12000
rulenum="`echo $((RANDOM%%2000+10000))`"
# Duplicate prevention mechanism
#rulenum = "`a=$((RANDOM%%2000+10000)); while ipfw show | grep -q ^$a\ ; do a=$((RANDOM%%2000+10000)); done; echo $a`"

View File

@ -18,4 +18,4 @@ after = apache-common.local
# 2.2: [Sat Jun 01 11:23:08 2013] [error] [client 1.2.3.4] # 2.2: [Sat Jun 01 11:23:08 2013] [error] [client 1.2.3.4]
# 2.4: [Thu Jun 27 11:55:44.569531 2013] [core:info] [pid 4101:tid 2992634688] [client 1.2.3.4:46652] # 2.4: [Thu Jun 27 11:55:44.569531 2013] [core:info] [pid 4101:tid 2992634688] [client 1.2.3.4:46652]
# Reference: https://github.com/fail2ban/fail2ban/issues/268 # Reference: https://github.com/fail2ban/fail2ban/issues/268
_apache_error_client = \[[^]]*\] \[(error|\S+:\S+)\]( \[pid \d+:\S+ \d+\])? \[client <HOST>(:\d{1,5})?\] _apache_error_client = \[\] \[(error|\S+:\S+)\]( \[pid \d+:\S+ \d+\])? \[client <HOST>(:\d{1,5})?\]

View File

@ -22,10 +22,7 @@ __daemon_combs_re=(?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_re)
__line_prefix=(?:\s\S+ %(__daemon_combs_re)s\s+)? __line_prefix=(?:\s\S+ %(__daemon_combs_re)s\s+)?
# note - (\.\d+)? is a really ugly catch of the microseconds not captured in failregex = ^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: (view (internal|external): )?query(?: \(cache\))? '.*' denied\s*$
# in the date detector ^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: zone transfer '\S+/AXFR/\w+' denied\s*$
# ^%(__line_prefix)s( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: bad zone transfer request: '\S+/IN': non-authoritative zone \(NOTAUTH\)\s*$
failregex = ^%(__line_prefix)s(\.\d+)?( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: (view (internal|external): )?query(?: \(cache\))? '.*' denied\s*$
^%(__line_prefix)s(\.\d+)?( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: zone transfer '\S+/AXFR/\w+' denied\s*$
^%(__line_prefix)s(\.\d+)?( error:)?\s*client <HOST>#\S+( \([\S.]+\))?: bad zone transfer request: '\S+/IN': non-authoritative zone \(NOTAUTH\)\s*$

View File

@ -17,7 +17,7 @@ before = common.conf
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+) # (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT # Values: TEXT
# #
failregex = ^\s*(\[(\s[+-][0-9]{4})?\])?(%(__hostname)s roundcube: IMAP Error)?: (FAILED login|Login failed) for .*? from <HOST>(\. AUTHENTICATE .*)?\s*$ failregex = ^\s*(\[\])?(%(__hostname)s roundcube: IMAP Error)?: (FAILED login|Login failed) for .*? from <HOST>(\. AUTHENTICATE .*)?\s*$
# 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.

View File

@ -531,3 +531,10 @@ action = iptables-allports[name=recidive]
bantime = 604800 ; 1 week bantime = 604800 ; 1 week
findtime = 86400 ; 1 day findtime = 86400 ; 1 day
maxretry = 5 maxretry = 5
[osx-ssh-ipfw]
enabled = false
filter = sshd
action = osx-ipfw
logpath = /var/log/secure.log

View File

@ -53,31 +53,37 @@ class DateDetector:
def addDefaultTemplate(self): def addDefaultTemplate(self):
self.__lock.acquire() self.__lock.acquire()
try: try:
# asctime # asctime with subsecond: Sun Jan 23 21:59:59.011 2005
self.appendTemplate("%a %b %d %H:%M:%S.%f %Y")
# asctime: Sun Jan 23 21:59:59 2005
self.appendTemplate("%a %b %d %H:%M:%S %Y") self.appendTemplate("%a %b %d %H:%M:%S %Y")
# asctime without year # asctime without year: Sun Jan 23 21:59:59
self.appendTemplate("%a %b %d %H:%M:%S") self.appendTemplate("%a %b %d %H:%M:%S")
# standard # standard: Jan 23 21:59:59
self.appendTemplate("%b %d %H:%M:%S") self.appendTemplate("%b %d %H:%M:%S")
# simple date # simple date: 2005-01-23 21:59:59
self.appendTemplate("%Y-%m-%d %H:%M:%S")
# simple date: 2005/01/23 21:59:59
self.appendTemplate("%Y/%m/%d %H:%M:%S") self.appendTemplate("%Y/%m/%d %H:%M:%S")
# simple date too (from x11vnc) # simple date too (from x11vnc): 23/01/2005 21:59:59
self.appendTemplate("%d/%m/%Y %H:%M:%S") self.appendTemplate("%d/%m/%Y %H:%M:%S")
# previous one but with year given by 2 digits # previous one but with year given by 2 digits: 23/01/05 21:59:59
# (See http://bugs.debian.org/537610) # (See http://bugs.debian.org/537610)
self.appendTemplate("%d/%m/%y %H:%M:%S") self.appendTemplate("%d/%m/%y %H:%M:%S")
# Apache format [31/Oct/2006:09:22:55 -0000] # Apache format [31/Oct/2006:09:22:55 -0000]
self.appendTemplate("%d/%b/%Y:%H:%M:%S") self.appendTemplate("%d/%b/%Y:%H:%M:%S %z")
# CPanel 05/20/2008:01:57:39 # CPanel 05/20/2008:01:57:39
self.appendTemplate("%m/%d/%Y:%H:%M:%S") self.appendTemplate("%m/%d/%Y:%H:%M:%S")
# custom for syslog-ng 2006.12.21 06:43:20 # custom for syslog-ng 2006.12.21 06:43:20
self.appendTemplate("%Y.%m.%d %H:%M:%S") self.appendTemplate("%Y.%m.%d %H:%M:%S")
# named 26-Jul-2007 15:20:52.252 # named 26-Jul-2007 15:20:52.252
self.appendTemplate("%d-%b-%Y %H:%M:%S") self.appendTemplate("%d-%b-%Y %H:%M:%S.%f")
# roundcube 26-Jul-2007 15:20:52 +0200
self.appendTemplate("%d-%b-%Y %H:%M:%S %z")
# 17-07-2008 17:23:25 # 17-07-2008 17:23:25
self.appendTemplate("%d-%m-%Y %H:%M:%S") self.appendTemplate("%d-%m-%Y %H:%M:%S")
# 01-27-2012 16:22:44.252 # 01-27-2012 16:22:44.252
self.appendTemplate("%m-%d-%Y %H:%M:%S") self.appendTemplate("%m-%d-%Y %H:%M:%S.%f")
# TAI64N # TAI64N
template = DateTai64n() template = DateTai64n()
template.setName("TAI64N") template.setName("TAI64N")
@ -128,7 +134,7 @@ class DateDetector:
date = template.getDate(line) date = template.getDate(line)
if date is None: if date is None:
continue continue
logSys.debug("Got time using template %s" % template.getName()) logSys.debug("Got time %i for \"%r\" using template %s" % (date[0], date[1].group(), template.getName()))
return date return date
except ValueError: except ValueError:
pass pass
@ -136,10 +142,6 @@ class DateDetector:
finally: finally:
self.__lock.release() self.__lock.release()
def getUnixTime(self, line):
date = self.getTime(line)
return date and time.mktime(tuple(date))
## ##
# Sort the template lists using the hits score. This method is not called # Sort the template lists using the hits score. This method is not called
# in this object and thus should be called from time to time. # in this object and thus should be called from time to time.

View File

@ -24,7 +24,10 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import re, time import re, time, calendar
from datetime import datetime
from datetime import timedelta
from mytime import MyTime from mytime import MyTime
import iso8601 import iso8601
@ -48,6 +51,7 @@ class DateTemplate:
return self.__name return self.__name
def setRegex(self, regex, wordBegin=True): def setRegex(self, regex, wordBegin=True):
#logSys.debug(u"setRegex for %s is %r" % (self.__name, regex))
regex = regex.strip() regex = regex.strip()
if (wordBegin and not re.search(r'^\^', regex)): if (wordBegin and not re.search(r'^\^', regex)):
regex = r'\b' + regex regex = r'\b' + regex
@ -82,12 +86,11 @@ class DateEpoch(DateTemplate):
self.setRegex("^\d{10}(\.\d{6})?") self.setRegex("^\d{10}(\.\d{6})?")
def getDate(self, line): def getDate(self, line):
date = None
dateMatch = self.matchDate(line) dateMatch = self.matchDate(line)
if dateMatch: if dateMatch:
# extract part of format which represents seconds since epoch # extract part of format which represents seconds since epoch
date = list(MyTime.localtime(float(dateMatch.group()))) return (float(dateMatch.group()), dateMatch)
return date return None
## ##
@ -113,13 +116,16 @@ class DateStrptime(DateTemplate):
def __init__(self): def __init__(self):
DateTemplate.__init__(self) DateTemplate.__init__(self)
self.__pattern = "" self._pattern = ""
self._unsupportedStrptimeBits = False
def setPattern(self, pattern): def setPattern(self, pattern):
self.__pattern = pattern.strip() self._unsupported_f = not DateStrptime._f and re.search('%f', pattern)
self._unsupported_z = not DateStrptime._z and re.search('%z', pattern)
self._pattern = pattern
def getPattern(self): def getPattern(self):
return self.__pattern return self._pattern
#@staticmethod #@staticmethod
def convertLocale(date): def convertLocale(date):
@ -133,17 +139,26 @@ class DateStrptime(DateTemplate):
convertLocale = staticmethod(convertLocale) convertLocale = staticmethod(convertLocale)
def getDate(self, line): def getDate(self, line):
date = None
dateMatch = self.matchDate(line) dateMatch = self.matchDate(line)
if dateMatch: if dateMatch:
datePattern = self.getPattern()
if self._unsupported_f:
if dateMatch.group('_f'):
datePattern = re.sub(r'%f', dateMatch.group('_f'), datePattern)
logSys.debug(u"Replacing %%f with %r now %r" % (dateMatch.group('_f'), datePattern))
if self._unsupported_z:
if dateMatch.group('_z'):
datePattern = re.sub(r'%z', dateMatch.group('_z'), datePattern)
logSys.debug(u"Replacing %%z with %r now %r" % (dateMatch.group('_z'), datePattern))
try: try:
# Try first with 'C' locale # Try first with 'C' locale
date = list(time.strptime(dateMatch.group(), self.getPattern())) date = datetime.strptime(dateMatch.group(), datePattern)
except ValueError: except ValueError:
# Try to convert date string to 'C' locale # Try to convert date string to 'C' locale
conv = self.convertLocale(dateMatch.group()) conv = self.convertLocale(dateMatch.group())
try: try:
date = list(time.strptime(conv, self.getPattern())) date = datetime.strptime(conv, self.getPattern())
except (ValueError, re.error), e: except (ValueError, re.error), e:
# Try to add the current year to the pattern. Should fix # Try to add the current year to the pattern. Should fix
# the "Feb 29" issue. # the "Feb 29" issue.
@ -152,7 +167,7 @@ class DateStrptime(DateTemplate):
if not '%Y' in opattern: if not '%Y' in opattern:
pattern = "%s %%Y" % opattern pattern = "%s %%Y" % opattern
conv += " %s" % MyTime.gmtime()[0] conv += " %s" % MyTime.gmtime()[0]
date = list(time.strptime(conv, pattern)) date = datetime.strptime(conv, pattern)
else: else:
# we are helpless here # we are helpless here
raise ValueError( raise ValueError(
@ -160,44 +175,81 @@ class DateStrptime(DateTemplate):
"exception was %r and Feb 29 workaround could not " "exception was %r and Feb 29 workaround could not "
"be tested due to already present year mark in the " "be tested due to already present year mark in the "
"pattern" % (opattern, e)) "pattern" % (opattern, e))
if date[0] < 2000:
if self._unsupported_z:
z = dateMatch.group('_z')
if z:
delta = timedelta(hours=int(z[1:3]),minutes=int(z[3:]))
direction = z[0]
logSys.debug(u"Altering %r by removing time zone offset (%s)%s" % (date, direction, delta))
# here we reverse the effect of the timezone and force it to UTC
if direction == '+':
date -= delta
else:
date += delta
date = date.replace(tzinfo=iso8601.Utc())
else:
logSys.warn("No _z group captured and %%z is not supported on current platform"
" - timezone ignored and assumed to be localtime. date: %s on line: %s"
% (date, line))
if date.year < 2000:
# There is probably no year field in the logs # There is probably no year field in the logs
# NOTE: Possibly makes week/year day incorrect # NOTE: Possibly makes week/year day incorrect
date[0] = MyTime.gmtime()[0] date = date.replace(year=MyTime.gmtime()[0])
# Bug fix for #1241756 # Bug fix for #1241756
# If the date is greater than the current time, we suppose # If the date is greater than the current time, we suppose
# that the log is not from this year but from the year before # that the log is not from this year but from the year before
if time.mktime(tuple(date)) > MyTime.time(): if date > MyTime.now():
logSys.debug( logSys.debug(
u"Correcting deduced year from %d to %d since %f > %f" % u"Correcting deduced year by one since %s > now (%s)" %
(date[0], date[0]-1, time.mktime(tuple(date)), MyTime.time())) (date, MyTime.time()))
# NOTE: Possibly makes week/year day incorrect date = date.replace(year=date.year-1)
date[0] -= 1 elif date.month == 1 and date.day == 1:
elif date[1] == 1 and date[2] == 1:
# If it is Jan 1st, it is either really Jan 1st or there # If it is Jan 1st, it is either really Jan 1st or there
# is neither month nor day in the log. # is neither month nor day in the log.
# NOTE: Possibly makes week/year day incorrect # NOTE: Possibly makes week/year day incorrect
date[1] = MyTime.gmtime()[1] date = date.replace(month=MyTime.gmtime()[1], day=1)
date[2] = MyTime.gmtime()[2]
return date if date.tzinfo:
return ( calendar.timegm(date.utctimetuple()), dateMatch )
else:
return ( time.mktime(date.utctimetuple()), dateMatch )
return None
try:
time.strptime("26-Jul-2007 15:20:52.252","%d-%b-%Y %H:%M:%S.%f")
DateStrptime._f = True
except (ValueError, KeyError):
DateTemplate._f = False
try:
time.strptime("24/Mar/2013:08:58:32 -0500","%d/%b/%Y:%H:%M:%S %z")
DateStrptime._z = True
except ValueError:
DateStrptime._z = False
class DatePatternRegex(DateStrptime): class DatePatternRegex(DateStrptime):
_reEscape = r"([\\.^$*+?\(\){}\[\]|])" _reEscape = r"([\\.^$*+?\(\){}\[\]|])"
_patternRE = r"%(%|[aAbBdHIjmMpSUwWyY])" _patternRE = r"%(%|[aAbBdfHIjmMpSUwWyYz])"
_patternName = { _patternName = {
'a': "DAY", 'A': "DAYNAME", 'b': "MON", 'B': "MONTH", 'd': "Day", 'a': "DAY", 'A': "DAYNAME", 'b': "MON", 'B': "MONTH", 'd': "Day",
'H': "24hour", 'I': "12hour", 'j': "Yearday", 'm': "Month", 'H': "24hour", 'I': "12hour", 'j': "Yearday", 'm': "Month",
'M': "Minute", 'p': "AMPM", 'S': "Second", 'U': "Yearweek", 'M': "Minute", 'p': "AMPM", 'S': "Second", 'U': "Yearweek",
'w': "Weekday", 'W': "Yearweek", 'y': 'Year2', 'Y': "Year", '%': "%"} 'w': "Weekday", 'W': "Yearweek", 'y': 'Year2', 'Y': "Year", '%': "%",
'z': "Zone offset", 'f': "Microseconds" }
_patternRegex = { _patternRegex = {
'a': r"\w{3}", 'A': r"\w+", 'b': r"\w{3}", 'B': r"\w+", 'a': r"\w{3}", 'A': r"\w+", 'b': r"\w{3}", 'B': r"\w+",
'd': r"(?:3[0-1]|[1-2]\d|[ 0]?\d)", 'H': r"(?:2[0-3]|1\d|[ 0]?\d)", 'd': r"(?:3[0-1]|[1-2]\d|[ 0]?\d)",
'f': r"(?P<_f>\d{1,6})", 'H': r"(?:2[0-3]|1\d|[ 0]?\d)",
'I': r"(?:1[0-2]|[ 0]?\d)", 'I': r"(?:1[0-2]|[ 0]?\d)",
'j': r"(?:36[0-6]3[0-5]\d|[1-2]\d\d|[ 0]?\d\d|[ 0]{0,2}\d)", 'j': r"(?:36[0-6]3[0-5]\d|[1-2]\d\d|[ 0]?\d\d|[ 0]{0,2}\d)",
'm': r"(?:1[0-2]|[ 0]?[1-9])", 'M': r"[0-5]\d", 'p': r"[AP]M", 'm': r"(?:1[0-2]|[ 0]?[1-9])", 'M': r"[0-5]\d", 'p': r"[AP]M",
'S': r"(?:6[01]|[0-5]\d)", 'U': r"(?:5[0-3]|[1-4]\d|[ 0]?\d)", 'S': r"(?:6[01]|[0-5]\d)", 'U': r"(?:5[0-3]|[1-4]\d|[ 0]?\d)",
'w': r"[0-6]", 'W': r"(?:5[0-3]|[ 0]?\d)", 'y': r"\d{2}", 'w': r"[0-6]", 'W': r"(?:5[0-3]|[ 0]?\d)", 'y': r"\d{2}",
'Y': r"\d{4}", '%': "%"} 'Y': r"\d{4}",
'z': r"(?P<_z>[+-]\d{4})", '%': "%"}
def __init__(self, pattern=None, **kwargs): def __init__(self, pattern=None, **kwargs):
DateStrptime.__init__(self) DateStrptime.__init__(self)
@ -205,7 +257,7 @@ class DatePatternRegex(DateStrptime):
self.setPattern(pattern, **kwargs) self.setPattern(pattern, **kwargs)
def setPattern(self, pattern, anchor=False, **kwargs): def setPattern(self, pattern, anchor=False, **kwargs):
self.__pattern = pattern.strip() DateStrptime.setPattern(self, pattern.strip())
name = re.sub(self._patternRE, r'%(\1)s', pattern) % self._patternName name = re.sub(self._patternRE, r'%(\1)s', pattern) % self._patternName
DateStrptime.setName(self, name) DateStrptime.setName(self, name)
@ -218,15 +270,13 @@ class DatePatternRegex(DateStrptime):
regex = r"^" + regex regex = r"^" + regex
DateStrptime.setRegex(self, regex, **kwargs) DateStrptime.setRegex(self, regex, **kwargs)
def getPattern(self):
return self.__pattern
def setRegex(self, line): def setRegex(self, line):
raise NotImplementedError("Regex derived from pattern") raise NotImplementedError("Regex derived from pattern")
def setName(self, line): def setName(self, line):
raise NotImplementedError("Name derived from pattern") raise NotImplementedError("Name derived from pattern")
class DateTai64n(DateTemplate): class DateTai64n(DateTemplate):
def __init__(self): def __init__(self):
@ -236,32 +286,27 @@ class DateTai64n(DateTemplate):
self.setRegex("@[0-9a-f]{24}", wordBegin=False) self.setRegex("@[0-9a-f]{24}", wordBegin=False)
def getDate(self, line): def getDate(self, line):
date = None
dateMatch = self.matchDate(line) dateMatch = self.matchDate(line)
if dateMatch: if dateMatch:
# extract part of format which represents seconds since epoch # extract part of format which represents seconds since epoch
value = dateMatch.group() value = dateMatch.group()
seconds_since_epoch = value[2:17] seconds_since_epoch = value[2:17]
# convert seconds from HEX into local time stamp # convert seconds from HEX into local time stamp
date = list(MyTime.localtime(int(seconds_since_epoch, 16))) return (int(seconds_since_epoch, 16), dateMatch)
return date return None
class DateISO8601(DateTemplate): class DateISO8601(DateTemplate):
def __init__(self): def __init__(self):
DateTemplate.__init__(self) DateTemplate.__init__(self)
date_re = "[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}" \ self.setRegex(iso8601.ISO8601_REGEX_RAW)
".[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?" \
"(Z|(([-+])([0-9]{2}):([0-9]{2})))?"
self.setRegex(date_re)
def getDate(self, line): def getDate(self, line):
date = None
dateMatch = self.matchDate(line) dateMatch = self.matchDate(line)
if dateMatch: if dateMatch:
# Parses the date. # Parses the date.
value = dateMatch.group() value = dateMatch.group()
date = list(iso8601.parse_date(value, None).timetuple()) return (calendar.timegm(iso8601.parse_date(value).utctimetuple()), dateMatch)
return date return None

View File

@ -74,6 +74,7 @@ class Filter(JailThread):
self.__lineBuffer = [] self.__lineBuffer = []
## Store last time stamp, applicable for multi-line ## Store last time stamp, applicable for multi-line
self.__lastTimeLine = "" self.__lastTimeLine = ""
self.__lastDate = None
self.dateDetector = DateDetector() self.dateDetector = DateDetector()
self.dateDetector.addDefaultTemplate() self.dateDetector.addDefaultTemplate()
@ -363,22 +364,7 @@ class Filter(JailThread):
line = line.rstrip('\r\n') line = line.rstrip('\r\n')
logSys.log(7, "Working on line %r", line) logSys.log(7, "Working on line %r", line)
timeMatch = self.dateDetector.matchTime(line) return self.findFailure(line, returnRawHost, checkAllRegex)
if timeMatch:
# Lets split into time part and log part of the line
timeLine = timeMatch.group()
self.__lastTimeLine = timeLine
# Lets leave the beginning in as well, so if there is no
# anchore at the beginning of the time regexp, we don't
# at least allow injection. Should be harmless otherwise
logLine = line[:timeMatch.start()] + line[timeMatch.end():]
else:
timeLine = self.__lastTimeLine or line
logLine = line
self.__lineBuffer = ((self.__lineBuffer +
[logLine])[-self.__lineBufferSize:])
return self.findFailure(timeLine, "\n".join(self.__lineBuffer) + "\n",
returnRawHost, checkAllRegex)
def processLineAndAdd(self, line): def processLineAndAdd(self, line):
"""Processes the line for failures and populates failManager """Processes the line for failures and populates failManager
@ -421,11 +407,38 @@ class Filter(JailThread):
# to find the logging time. # to find the logging time.
# @return a dict with IP and timestamp. # @return a dict with IP and timestamp.
def findFailure(self, timeLine, logLine, def findFailure(self, logLine,
returnRawHost=False, checkAllRegex=False): returnRawHost=False, checkAllRegex=False):
logSys.log(5, "Date: %r, message: %r", timeLine, logLine)
failList = list() failList = list()
date = self.dateDetector.getUnixTime(timeLine)
# Checks if we must ignore this line.
if self.ignoreLine(logLine) is not None:
# The ignoreregex matched. Return.
logSys.log(7, "Matched ignoreregex and was \"%s\" ignored", logLine)
return failList
dateTimeMatch = self.dateDetector.getTime(logLine)
if dateTimeMatch is not None:
# Lets split into time part and log part of the line
date = dateTimeMatch[0]
timeMatch = dateTimeMatch[1]
timeLine = timeMatch.group()
self.__lastTimeLine = timeLine
self.__lastDate = date
# Lets leave the beginning in as well, so if there is no
# anchore at the beginning of the time regexp, we don't
# at least allow injection. Should be harmless otherwise
logLine = logLine[:timeMatch.start()] + logLine[timeMatch.end():]
else:
timeLine = self.__lastTimeLine or logLine
date = self.__lastDate
self.__lineBuffer = (self.__lineBuffer + [logLine])[-self.__lineBufferSize:]
logLine = "\n".join(self.__lineBuffer) + "\n"
# Iterates over all the regular expressions. # Iterates over all the regular expressions.
for failRegexIndex, failRegex in enumerate(self.__failRegex): for failRegexIndex, failRegex in enumerate(self.__failRegex):
failRegex.search(logLine) failRegex.search(logLine)

View File

@ -32,17 +32,17 @@ datetime.datetime(2007, 1, 25, 12, 0, tzinfo=<iso8601.iso8601.Utc ...>)
""" """
from datetime import datetime, timedelta, tzinfo from datetime import datetime, timedelta, tzinfo, time
import re import re
__all__ = ["parse_date", "ParseError"] __all__ = ["parse_date", "ParseError"]
# Adapted from http://delete.me.uk/2005/03/iso8601.html # Adapted from http://delete.me.uk/2005/03/iso8601.html
ISO8601_REGEX = re.compile(r"(?P<year>[0-9]{4})(-(?P<month>[0-9]{1,2})(-(?P<day>[0-9]{1,2})" ISO8601_REGEX_RAW = "(?P<year>[0-9]{4})-(?P<month>[0-9]{1,2})-(?P<day>[0-9]{1,2})" \
r"((?P<separator>.)(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2})(:(?P<second>[0-9]{2})(\.(?P<fraction>[0-9]+))?)?" "T(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2})(:(?P<second>[0-9]{2})(\.(?P<fraction>[0-9]+))?)?" \
r"(?P<timezone>Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?" "(?P<timezone>Z|(([-+])([0-9]{2}):([0-9]{2})))?"
) ISO8601_REGEX = re.compile(ISO8601_REGEX_RAW)
TIMEZONE_REGEX = re.compile("(?P<prefix>[+-])(?P<hours>[0-9]{2}).(?P<minutes>[0-9]{2})") TIMEZONE_REGEX = re.compile("(?P<prefix>[+-])(?P<hours>[0-9]{2}):?(?P<minutes>[0-9]{2})?")
class ParseError(Exception): class ParseError(Exception):
"""Raised when there is a problem parsing a date string""" """Raised when there is a problem parsing a date string"""
@ -67,8 +67,8 @@ class FixedOffset(tzinfo):
"""Fixed offset in hours and minutes from UTC """Fixed offset in hours and minutes from UTC
""" """
def __init__(self, offset_hours, offset_minutes, name): def __init__(self, name, offset_hours, offset_minutes, offset_seconds=0):
self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes, seconds=offset_seconds)
self.__name = name self.__name = name
def utcoffset(self, dt): def utcoffset(self, dt):
@ -83,26 +83,30 @@ class FixedOffset(tzinfo):
def __repr__(self): def __repr__(self):
return "<FixedOffset %r>" % self.__name return "<FixedOffset %r>" % self.__name
def parse_timezone(tzstring, default_timezone=UTC): def parse_timezone(tzstring):
"""Parses ISO 8601 time zone specs into tzinfo offsets """Parses ISO 8601 time zone specs into tzinfo offsets
""" """
if tzstring == "Z": if tzstring == "Z":
return default_timezone return UTC
# This isn't strictly correct, but it's common to encounter dates without
# timezones so I'll assume the default (which defaults to UTC).
# Addresses issue 4.
if tzstring is None: if tzstring is None:
return default_timezone zone_sec = -time.timezone
return FixedOffset(name=time.tzname[0],hours=(zone_sec / 3600),minutes=(zone_sec % 3600)/60,seconds=zone_sec % 60)
m = TIMEZONE_REGEX.match(tzstring) m = TIMEZONE_REGEX.match(tzstring)
prefix, hours, minutes = m.groups() prefix, hours, minutes = m.groups()
hours, minutes = int(hours), int(minutes) if minutes is None:
minutes = 0
else:
minutes = int(minutes)
hours = int(hours)
if prefix == "-": if prefix == "-":
hours = -hours hours = -hours
minutes = -minutes minutes = -minutes
return FixedOffset(hours, minutes, tzstring) return FixedOffset(tzstring, hours, minutes)
def parse_date(datestring, default_timezone=UTC): def parse_date(datestring):
"""Parses ISO 8601 dates into datetime objects """Parses ISO 8601 dates into datetime objects
The timezone is parsed from the date string. However it is quite common to The timezone is parsed from the date string. However it is quite common to
@ -116,7 +120,7 @@ def parse_date(datestring, default_timezone=UTC):
if not m: if not m:
raise ParseError("Unable to parse date string %r" % datestring) raise ParseError("Unable to parse date string %r" % datestring)
groups = m.groupdict() groups = m.groupdict()
tz = parse_timezone(groups["timezone"], default_timezone=default_timezone) tz = parse_timezone(groups["timezone"])
if groups["fraction"] is None: if groups["fraction"] is None:
groups["fraction"] = 0 groups["fraction"] = 0
else: else:

View File

@ -21,7 +21,7 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import time import time, datetime
## ##
# MyTime class. # MyTime class.
@ -74,6 +74,14 @@ class MyTime:
return time.gmtime(MyTime.myTime) return time.gmtime(MyTime.myTime)
gmtime = staticmethod(gmtime) gmtime = staticmethod(gmtime)
#@staticmethod
def now():
if MyTime.myTime is None:
return datetime.datetime.now()
else:
return datetime.datetime.fromtimestamp(MyTime.myTime)
now = staticmethod(now)
def localtime(x=None): def localtime(x=None):
if MyTime.myTime is None or x is not None: if MyTime.myTime is None or x is not None:
return time.localtime(x) return time.localtime(x)

View File

@ -24,9 +24,10 @@ __author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import unittest, calendar, datetime, re, pprint import unittest, calendar, time, datetime, re, pprint
from fail2ban.server.datedetector import DateDetector from fail2ban.server.datedetector import DateDetector
from fail2ban.server.datetemplate import DateTemplate from fail2ban.server.datetemplate import DateTemplate
from fail2ban.server.iso8601 import Utc
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime from fail2ban.tests.utils import setUpMyTime, tearDownMyTime
class DateDetectorTest(unittest.TestCase): class DateDetectorTest(unittest.TestCase):
@ -43,11 +44,12 @@ class DateDetectorTest(unittest.TestCase):
def testGetEpochTime(self): def testGetEpochTime(self):
log = "1138049999 [sshd] error: PAM: Authentication failure" log = "1138049999 [sshd] error: PAM: Authentication failure"
date = [2006, 1, 23, 21, 59, 59, 0, 23, 0] #date = [2006, 1, 23, 21, 59, 59, 0, 23, 0]
dateUnix = 1138049999.0 dateUnix = 1138049999.0
self.assertEqual(self.__datedetector.getTime(log), date) ( datelog, matchlog ) = self.__datedetector.getTime(log)
self.assertEqual(self.__datedetector.getUnixTime(log), dateUnix) self.assertEqual(datelog, dateUnix)
self.assertEqual(matchlog.group(), '1138049999')
def testGetTime(self): def testGetTime(self):
log = "Jan 23 21:59:59 [sshd] error: PAM: Authentication failure" log = "Jan 23 21:59:59 [sshd] error: PAM: Authentication failure"
@ -57,8 +59,9 @@ class DateDetectorTest(unittest.TestCase):
# is not correctly determined atm, since year is not present # is not correctly determined atm, since year is not present
# in the log entry. Since this doesn't effect the operation # in the log entry. Since this doesn't effect the operation
# of fail2ban -- we just ignore incorrect day of the week # of fail2ban -- we just ignore incorrect day of the week
self.assertEqual(self.__datedetector.getTime(log)[:6], date[:6]) ( datelog, matchlog ) = self.__datedetector.getTime(log)
self.assertEqual(self.__datedetector.getUnixTime(log), dateUnix) self.assertEqual(datelog, dateUnix)
self.assertEqual(matchlog.group(), 'Jan 23 21:59:59')
def testVariousTimes(self): def testVariousTimes(self):
"""Test detection of various common date/time formats f2b should understand """Test detection of various common date/time formats f2b should understand
@ -68,21 +71,23 @@ class DateDetectorTest(unittest.TestCase):
for sdate in ( for sdate in (
"Jan 23 21:59:59", "Jan 23 21:59:59",
"Sun Jan 23 21:59:59.011 2005",
"Sun Jan 23 21:59:59 2005", "Sun Jan 23 21:59:59 2005",
"Sun Jan 23 21:59:59", "Sun Jan 23 21:59:59",
"2005/01/23 21:59:59", "2005/01/23 21:59:59",
"2005.01.23 21:59:59", "2005.01.23 21:59:59",
"23/01/2005 21:59:59", "23/01/2005 21:59:59",
"23/01/05 21:59:59", "23/01/05 21:59:59",
"23/Jan/2005:21:59:59", "23/Jan/2005:21:59:59 +0100",
"01/23/2005:21:59:59", "01/23/2005:21:59:59",
"2005-01-23 21:59:59", "2005-01-23 21:59:59",
"23-Jan-2005 21:59:59", "23-Jan-2005 21:59:59.02",
"23-Jan-2005 21:59:59 +0100",
"23-01-2005 21:59:59", "23-01-2005 21:59:59",
"01-23-2005 21:59:59.252", # reported on f2b, causes Feb29 fix to break "01-23-2005 21:59:59.252", # reported on f2b, causes Feb29 fix to break
"@4000000041f4104f00000000", # TAI64N "@4000000041f4104f00000000", # TAI64N
"2005-01-23T21:59:59.252Z", #ISO 8601 "2005-01-23T20:59:59.252Z", #ISO 8601
"2005-01-23T21:59:59-05:00Z", #ISO 8601 with TZ "2005-01-23T15:59:59-05:00", #ISO 8601 with TZ
"<01/23/05@21:59:59>", "<01/23/05@21:59:59>",
"050123 21:59:59", # MySQL "050123 21:59:59", # MySQL
"Jan 23, 2005 9:59:59 PM", # Apache Tomcat "Jan 23, 2005 9:59:59 PM", # Apache Tomcat
@ -94,8 +99,9 @@ class DateDetectorTest(unittest.TestCase):
# yoh: on [:6] see in above test # yoh: on [:6] see in above test
logtime = self.__datedetector.getTime(log) logtime = self.__datedetector.getTime(log)
self.assertNotEqual(logtime, None, "getTime retrieved nothing: failure for %s" % sdate) self.assertNotEqual(logtime, None, "getTime retrieved nothing: failure for %s" % sdate)
self.assertEqual(logtime[:6], date[:6], "getTime comparison failure for %s: \"%s\" is not \"%s\"" % (sdate, logtime[:6], date[:6])) ( logUnix, logMatch ) = logtime
self.assertEqual(self.__datedetector.getUnixTime(log), dateUnix, "getUnixTime failure for %s: \"%s\" is not \"%s\"" % (sdate, logtime[:6], date[:6])) self.assertEqual(logUnix, dateUnix, "getTime comparison failure for %s: \"%s\" is not \"%s\"" % (sdate, logUnix, dateUnix))
self.assertEqual(logMatch.group(), sdate)
def testStableSortTemplate(self): def testStableSortTemplate(self):
old_names = [x.getName() for x in self.__datedetector.getTemplates()] old_names = [x.getName() for x in self.__datedetector.getTemplates()]
@ -112,21 +118,23 @@ 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
m1 = [2012, 10, 11, 2, 37, 17] mu = time.mktime(datetime.datetime(2012, 10, 11, 2, 37, 17).utctimetuple())
self.assertEqual( logdate = self.__datedetector.getTime('2012/10/11 02:37:17 [error] 18434#0')
self.__datedetector.getTime('2012/10/11 02:37:17 [error] 18434#0')[:6], self.assertNotEqual(logdate, None)
m1) ( logTime, logMatch ) = logdate
self.assertEqual(logTime, mu)
self.assertEqual(logMatch.group(), '2012/10/11 02:37:17')
self.__datedetector.sortTemplate() self.__datedetector.sortTemplate()
# confuse it with year being at the end # confuse it with year being at the end
for i in xrange(10): for i in xrange(10):
self.assertEqual( ( logTime, logMatch ) = self.__datedetector.getTime('11/10/2012 02:37:17 [error] 18434#0')
self.__datedetector.getTime('11/10/2012 02:37:17 [error] 18434#0')[:6], self.assertEqual(logTime, mu)
m1) self.assertEqual(logMatch.group(), '11/10/2012 02:37:17')
self.__datedetector.sortTemplate() self.__datedetector.sortTemplate()
# and now back to the original # and now back to the original
self.assertEqual( ( logTime, logMatch ) = self.__datedetector.getTime('2012/10/11 02:37:17 [error] 18434#0')
self.__datedetector.getTime('2012/10/11 02:37:17 [error] 18434#0')[:6], self.assertEqual(logTime, mu)
m1) self.assertEqual(logMatch.group(), '2012/10/11 02:37:17')
def testDateDetectorTemplateOverlap(self): def testDateDetectorTemplateOverlap(self):
patterns = [template.getPattern() patterns = [template.getPattern()
@ -141,12 +149,13 @@ class DateDetectorTest(unittest.TestCase):
for minute in xrange(0, 60, 15): for minute in xrange(0, 60, 15):
for second in xrange(0, 60, 15): # Far enough? for second in xrange(0, 60, 15): # Far enough?
yield datetime.datetime( yield datetime.datetime(
year, month, day, hour, minute, second) year, month, day, hour, minute, second, 300, Utc())
overlapedTemplates = set() overlapedTemplates = set()
for date in iterDates(year): for date in iterDates(year):
for pattern in patterns: for pattern in patterns:
datestr = date.strftime(pattern) datestr = date.strftime(pattern)
datestr = re.sub(r'%f','300', datestr) # for python 2.5 where there is no %f
datestrs = set([ datestrs = set([
datestr, datestr,
re.sub(r"(\s)0", r"\1 ", datestr), re.sub(r"(\s)0", r"\1 ", datestr),
@ -160,12 +169,12 @@ class DateDetectorTest(unittest.TestCase):
matchedTemplates = [template matchedTemplates = [template
for template in self.__datedetector.getTemplates() for template in self.__datedetector.getTemplates()
if template.getHits() > 0] if template.getHits() > 0]
assert matchedTemplates != [] # Should match at least one self.assertNotEqual(matchedTemplates, [], "Date %r should match at least one template" % pattern)
if len(matchedTemplates) > 1: if len(matchedTemplates) > 1:
overlapedTemplates.add((pattern, tuple(sorted(template.getName() overlapedTemplates.add((pattern, tuple(sorted(template.getName()
for template in matchedTemplates)))) for template in matchedTemplates))))
if overlapedTemplates: if overlapedTemplates:
print "WARNING: The following date templates overlap:" print("WARNING: The following date templates overlap:")
pprint.pprint(overlapedTemplates) pprint.pprint(overlapedTemplates)
# def testDefaultTempate(self): # def testDefaultTempate(self):

View File

@ -1,12 +1,12 @@
# failJSON: { "time": "2010-09-16T07:51:00", "match": true , "host": "80.187.101.33" } # failJSON: { "time": "2010-09-16T06: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-16T07:51:00", "match": true , "host": "176.61.140.224" } # failJSON: { "time": "2010-09-16T06: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-16T07:51:00", "match": true , "host": "192.0.43.10" } # failJSON: { "time": "2010-09-16T06: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-16T07:51:00", "match": true , "host": "176.61.140.225" } # failJSON: { "time": "2010-09-16T06: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" }

View File

@ -1,2 +1,2 @@
# failJSON: { "time": "2009-03-26T08:44:20", "match": true , "host": "66.185.212.172" } # failJSON: { "time": "2009-03-26T14:44:20", "match": true , "host": "66.185.212.172" }
66.185.212.172 - - [26/Mar/2009:08:44:20 -0500] "GET /index.php?n=http://eatmyfood.hostinginfive.com/pizza.htm? HTTP/1.1" 200 114 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)" 66.185.212.172 - - [26/Mar/2009:08:44:20 -0500] "GET /index.php?n=http://eatmyfood.hostinginfive.com/pizza.htm? HTTP/1.1" 200 114 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)"

View File

@ -1,4 +1,4 @@
# failJSON: { "time": "2013-01-22T22:28:21", "match": true , "host": "192.0.43.10" } # failJSON: { "time": "2013-01-22T21:28:21", "match": true , "host": "192.0.43.10" }
[22-Jan-2013 22:28:21 +0200]: FAILED login for user1 from 192.0.43.10 [22-Jan-2013 22:28:21 +0200]: FAILED login for user1 from 192.0.43.10
# failJSON: { "time": "2005-05-26T07:12:40", "match": true , "host": "10.1.1.47" } # failJSON: { "time": "2005-05-26T07:12:40", "match": true , "host": "10.1.1.47" }
May 26 07:12:40 hamster roundcube: IMAP Error: Login failed for sales@example.com from 10.1.1.47 May 26 07:12:40 hamster roundcube: IMAP Error: Login failed for sales@example.com from 10.1.1.47

View File

@ -39,6 +39,7 @@ from fail2ban.server.filterpoll import FilterPoll
from fail2ban.server.filter import FileFilter, DNSUtils from fail2ban.server.filter import FileFilter, DNSUtils
from fail2ban.server.failmanager import FailManager from fail2ban.server.failmanager import FailManager
from fail2ban.server.failmanager import FailManagerEmpty from fail2ban.server.failmanager import FailManagerEmpty
from fail2ban.server.mytime import MyTime
from fail2ban.tests.utils import setUpMyTime, tearDownMyTime from fail2ban.tests.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")
@ -89,8 +90,8 @@ def _assert_equal_entries(utest, found, output, count=None):
utest.assertEqual(found[0], output[0]) # IP utest.assertEqual(found[0], output[0]) # IP
utest.assertEqual(found[1], count or output[1]) # count utest.assertEqual(found[1], count or output[1]) # count
found_time, output_time = \ found_time, output_time = \
time.localtime(found[2]),\ MyTime.localtime(found[2]),\
time.localtime(output[2]) MyTime.localtime(output[2])
utest.assertEqual(found_time, output_time) utest.assertEqual(found_time, output_time)
if len(output) > 3 and count is None: # match matches if len(output) > 3 and count is None: # match matches
# do not check if custom count (e.g. going through them twice) # do not check if custom count (e.g. going through them twice)
@ -737,7 +738,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, 1124013599.0, FAILURES_01 = ('193.168.0.128', 3, 1124017199.0,
[u'Aug 14 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 193.168.0.128\n']*3) [u'Aug 14 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 193.168.0.128\n']*3)
def setUp(self): def setUp(self):
@ -783,7 +784,7 @@ class GetFailures(unittest.TestCase):
def testGetFailures02(self): def testGetFailures02(self):
output = ('141.3.81.106', 4, 1124013539.0, output = ('141.3.81.106', 4, 1124017139.0,
[u'Aug 14 11:%d:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:141.3.81.106 port 51332 ssh2\n' [u'Aug 14 11:%d:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:141.3.81.106 port 51332 ssh2\n'
% m for m in 53, 54, 57, 58]) % m for m in 53, 54, 57, 58])
@ -793,7 +794,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', 6, 1124013544.0) output = ('203.162.223.135', 7, 1124017144.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")
@ -801,8 +802,8 @@ 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, 1124013600.0), output = [('212.41.96.186', 4, 1124017200.0),
('212.41.96.185', 4, 1124013598.0)] ('212.41.96.185', 4, 1124017198.0)]
self.filter.addLogPath(GetFailures.FILENAME_04) self.filter.addLogPath(GetFailures.FILENAME_04)
self.filter.addFailRegex("Invalid user .* <HOST>") self.filter.addFailRegex("Invalid user .* <HOST>")
@ -816,11 +817,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, 1124013539.0, output_yes = ('93.184.216.119', 2, 1124017139.0,
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2\n', [u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2\n',
u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2\n']) u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2\n'])
output_no = ('93.184.216.119', 1, 1124013539.0, output_no = ('93.184.216.119', 1, 1124017139.0,
[u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2\n']) [u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2\n'])
# 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'
@ -842,7 +843,7 @@ class GetFailures(unittest.TestCase):
def testGetFailuresMultiRegex(self): def testGetFailuresMultiRegex(self):
output = ('141.3.81.106', 8, 1124013541.0) output = ('141.3.81.106', 8, 1124017141.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>")
@ -851,7 +852,7 @@ class GetFailures(unittest.TestCase):
_assert_correct_last_attempt(self, self.filter, output) _assert_correct_last_attempt(self, self.filter, output)
def testGetFailuresIgnoreRegex(self): def testGetFailuresIgnoreRegex(self):
output = ('141.3.81.106', 8, 1124013541.0) output = ('141.3.81.106', 8, 1124017141.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>")
@ -863,8 +864,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, 1124013599.0), output = [("192.0.43.10", 2, 1124017199.0),
("192.0.43.11", 1, 1124013598.0)] ("192.0.43.11", 1, 1124017198.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)
@ -882,7 +883,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, 1124013599.0)] output = [("192.0.43.10", 2, 1124017199.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")
@ -896,9 +897,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, 1124013599.0), output = [("192.0.43.10", 2, 1124017199.0),
("192.0.43.11", 1, 1124013598.0), ("192.0.43.11", 1, 1124017198.0),
("192.0.43.15", 1, 1124013598.0)] ("192.0.43.15", 1, 1124017198.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*$")

View File

@ -22,7 +22,7 @@
__copyright__ = "Copyright (c) 2013 Steven Hiscocks" __copyright__ = "Copyright (c) 2013 Steven Hiscocks"
__license__ = "GPL" __license__ = "GPL"
import unittest, sys, os, fileinput, re, datetime, inspect import unittest, sys, os, fileinput, re, time, datetime, inspect
if sys.version_info >= (2, 6): if sys.version_info >= (2, 6):
import json import json
@ -119,15 +119,19 @@ def testSampleRegexsFactory(name):
(map(lambda x: x[0], ret),logFile.filename(), logFile.filelineno())) (map(lambda x: x[0], ret),logFile.filename(), logFile.filelineno()))
# Verify timestamp and host as expected # Verify timestamp and host as expected
failregex, host, time = ret[0] failregex, host, fail2banTime = ret[0]
self.assertEqual(host, faildata.get("host", None)) self.assertEqual(host, faildata.get("host", None))
fail2banTime = datetime.datetime.fromtimestamp(time)
jsonTime = datetime.datetime.strptime( t = faildata.get("time", None)
faildata.get("time", None), "%Y-%m-%dT%H:%M:%S") jsonTimeLocal = datetime.datetime.strptime(t, "%Y-%m-%dT%H:%M:%S")
jsonTime = time.mktime(jsonTimeLocal.utctimetuple())
self.assertEqual(fail2banTime, jsonTime, self.assertEqual(fail2banTime, jsonTime,
"Time mismatch %s != %s on: %s:%i %r:" % "UTC Time mismatch fail2ban %s (%s) != failJson %s (%s) (diff %i seconds) on: %s:%i %r:" %
(fail2banTime, jsonTime, logFile.filename(), logFile.filelineno(), line ) ) (fail2banTime, time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(fail2banTime)),
jsonTime, time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(jsonTime)),
fail2banTime - jsonTime, logFile.filename(), logFile.filelineno(), line ) )
regexsUsed.add(failregex) regexsUsed.add(failregex)