NF: Generate datetemplates from strptime format strings

Can also set custom time format from fail2ban-client, replacing the
default detectors
pull/214/merge^2
Steven Hiscocks 2013-05-04 16:59:01 +01:00
parent e019ab784c
commit 20049dd3a0
8 changed files with 130 additions and 91 deletions

View File

@ -113,6 +113,12 @@ class Beautifier:
elif inC[2] == "logencoding":
msg = "Current log encoding is set to:\n"
msg = msg + response
elif inC[2] == "datepattern":
msg = "Current date pattern set to: "
if response is None:
msg = msg + "Default Detectors"
else:
msg = msg + "%s (%s)" % response
elif inC[2] in ("ignoreip", "addignoreip", "delignoreip"):
if len(response) == 0:
msg = "No IP address/network is ignored"

View File

@ -64,6 +64,7 @@ protocol = [
["set <JAIL> delignoreregex <INDEX>", "removes the regular expression at <INDEX> for ignoreregex"],
["set <JAIL> findtime <TIME>", "sets the number of seconds <TIME> for which the filter will look back for <JAIL>"],
["set <JAIL> bantime <TIME>", "sets the number of seconds <TIME> a host will be banned for <JAIL>"],
["set <JAIL> datepattern <PATTERN>", "sets the <PATTERN> used to match date/times for <JAIL>"],
["set <JAIL> usedns <VALUE>", "sets the usedns mode for <JAIL>"],
["set <JAIL> banip <IP>", "manually Ban <IP> for <JAIL>"],
["set <JAIL> unbanip <IP>", "manually Unban <IP> in <JAIL>"],
@ -86,6 +87,7 @@ protocol = [
["get <JAIL> ignoreregex", "gets the list of regular expressions which matches patterns to ignore for <JAIL>"],
["get <JAIL> findtime", "gets the time for which the filter will look back for failures for <JAIL>"],
["get <JAIL> bantime", "gets the time a host is banned for <JAIL>"],
["get <JAIL> datepattern", "gets the patern used to match date/times for <JAIL>"],
["get <JAIL> usedns", "gets the usedns setting for <JAIL>"],
["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"],
["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"],

View File

@ -29,7 +29,7 @@ __license__ = "GPL"
import time, logging
from datetemplate import DateStrptime, DateTai64n, DateEpoch, DateISO8601
from datetemplate import DatePatternRegex, DateTai64n, DateEpoch, DateISO8601
from threading import Lock
# Gets the instance of the logger.
@ -49,124 +49,63 @@ class DateDetector:
self.__known_names.add(name)
self.__templates.append(template)
def appendTemplate(self, template, **kwargs):
if isinstance(template, str):
template = DatePatternRegex(template, **kwargs)
else:
assert not kwargs
DateDetector._appendTemplate(self, template)
def addDefaultTemplate(self):
self.__lock.acquire()
try:
# standard
template = DateStrptime()
template.setName("MONTH Day Hour:Minute:Second")
template.setRegex("\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}")
template.setPattern("%b %d %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%b %d %H:%M:%S")
# asctime
template = DateStrptime()
template.setName("WEEKDAY MONTH Day Hour:Minute:Second Year")
template.setRegex("\S{3} \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2} \d{4}")
template.setPattern("%a %b %d %H:%M:%S %Y")
self._appendTemplate(template)
self.appendTemplate("%a %b %d %H:%M:%S %Y")
# asctime without year
template = DateStrptime()
template.setName("WEEKDAY MONTH Day Hour:Minute:Second")
template.setRegex("\S{3} \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}")
template.setPattern("%a %b %d %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%a %b %d %H:%M:%S")
# simple date
template = DateStrptime()
template.setName("Year/Month/Day Hour:Minute:Second")
template.setRegex("\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}")
template.setPattern("%Y/%m/%d %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%Y/%m/%d %H:%M:%S")
# simple date too (from x11vnc)
template = DateStrptime()
template.setName("Day/Month/Year Hour:Minute:Second")
template.setRegex("\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}")
template.setPattern("%d/%m/%Y %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%d/%m/%Y %H:%M:%S")
# previous one but with year given by 2 digits
# (See http://bugs.debian.org/537610)
template = DateStrptime()
template.setName("Day/Month/Year2 Hour:Minute:Second")
template.setRegex("\d{2}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}")
template.setPattern("%d/%m/%y %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%d/%m/%y %H:%M:%S")
# Apache format [31/Oct/2006:09:22:55 -0000]
template = DateStrptime()
template.setName("Day/MONTH/Year:Hour:Minute:Second")
template.setRegex("\d{2}/\S{3}/\d{4}:\d{2}:\d{2}:\d{2}")
template.setPattern("%d/%b/%Y:%H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%d/%b/%Y:%H:%M:%S")
# CPanel 05/20/2008:01:57:39
template = DateStrptime()
template.setName("Month/Day/Year:Hour:Minute:Second")
template.setRegex("\d{2}/\d{2}/\d{4}:\d{2}:\d{2}:\d{2}")
template.setPattern("%m/%d/%Y:%H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%m/%d/%Y:%H:%M:%S")
# Exim 2006-12-21 06:43:20
template = DateStrptime()
template.setName("Year-Month-Day Hour:Minute:Second")
template.setRegex("\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}")
template.setPattern("%Y-%m-%d %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%Y-%m-%d %H:%M:%S")
# custom for syslog-ng 2006.12.21 06:43:20
template = DateStrptime()
template.setName("Year.Month.Day Hour:Minute:Second")
template.setRegex("\d{4}.\d{2}.\d{2} \d{2}:\d{2}:\d{2}")
template.setPattern("%Y.%m.%d %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%Y.%m.%d %H:%M:%S")
# named 26-Jul-2007 15:20:52.252
template = DateStrptime()
template.setName("Day-MONTH-Year Hour:Minute:Second[.Millisecond]")
template.setRegex("\d{2}-\S{3}-\d{4} \d{2}:\d{2}:\d{2}")
template.setPattern("%d-%b-%Y %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%d-%b-%Y %H:%M:%S")
# 17-07-2008 17:23:25
template = DateStrptime()
template.setName("Day-Month-Year Hour:Minute:Second")
template.setRegex("\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}")
template.setPattern("%d-%m-%Y %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%d-%m-%Y %H:%M:%S")
# 01-27-2012 16:22:44.252
template = DateStrptime()
template.setName("Month-Day-Year Hour:Minute:Second[.Millisecond]")
template.setRegex("\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}")
template.setPattern("%m-%d-%Y %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%m-%d-%Y %H:%M:%S")
# TAI64N
template = DateTai64n()
template.setName("TAI64N")
self._appendTemplate(template)
self.appendTemplate(template)
# Epoch
template = DateEpoch()
template.setName("Epoch")
self._appendTemplate(template)
self.appendTemplate(template)
# ISO 8601
template = DateISO8601()
template.setName("ISO 8601")
self._appendTemplate(template)
self.appendTemplate(template)
# Only time information in the log
template = DateStrptime()
template.setName("Hour:Minute:Second")
template.setRegex("^\d{2}:\d{2}:\d{2}")
template.setPattern("%H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%H:%M:%S", anchor=True)
# <09/16/08@05:03:30>
template = DateStrptime()
template.setName("<Month/Day/Year@Hour:Minute:Second>")
template.setRegex("^<\d{2}/\d{2}/\d{2}@\d{2}:\d{2}:\d{2}>")
template.setPattern("<%m/%d/%y@%H:%M:%S>")
self._appendTemplate(template)
self.appendTemplate("<%m/%d/%y@%H:%M:%S>", anchor=True)
# MySQL: 130322 11:46:11
template = DateStrptime()
template.setName("MonthDayYear Hour:Minute:Second")
template.setRegex("^\d{2}\d{2}\d{2} +\d{1,2}:\d{2}:\d{2}")
template.setPattern("%y%m%d %H:%M:%S")
self._appendTemplate(template)
self.appendTemplate("%y%m%d %H:%M:%S", anchor=True)
# Apache Tomcat
template = DateStrptime()
template.setName("MONTH Day, Year 12hour:Minute:Second AM/PM")
template.setRegex("\S{3}\s{1,2}\d{1,2}, \d{4} \d{1,2}:\d{2}:\d{2} [AP]M")
template.setPattern("%b %d, %Y %I:%M:%S %p")
self._appendTemplate(template)
self.appendTemplate("%b %d, %Y %I:%M:%S %p")
finally:
self.__lock.release()

View File

@ -55,7 +55,7 @@ class DateTemplate:
if (wordBegin and not re.search(r'^\^', regex)):
regex = r'\b' + regex
self.__regex = regex
self.__cRegex = re.compile(regex)
self.__cRegex = re.compile(regex, re.UNICODE)
def getRegex(self):
return self.__regex
@ -180,6 +180,51 @@ class DateStrptime(DateTemplate):
date[2] = MyTime.gmtime()[2]
return date
class DatePatternRegex(DateStrptime):
_reEscape = r"([\\.^$*+?\(\){}\[\]|])"
_patternRE = r"%(%|[aAbBdHIjmMpSUwWyY])"
_patternName = {
'a': "DAY", 'A': "DAYNAME", 'b': "MON", 'B': "MONTH", 'd': "Day",
'H': "24hour", 'I': "12hour", 'j': "Yearday", 'm': "Month",
'M': "Minute", 'p': "AMPM", 'S': "Second", 'U': "Yearweek",
'w': "Weekday", 'W': "Yearweek", 'y': 'Year2', 'Y': "Year", '%': "%"}
_patternRegex = {
'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)",
'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)",
'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)",
'w': r"[0-6]", 'W': r"(?:5[0-3]|[ 0]?\d)", 'y': r"\d{2}",
'Y': r"\d{4}", '%': "%"}
def __init__(self, pattern=None, **kwargs):
DateStrptime.__init__(self)
if pattern:
self.setPattern(pattern, **kwargs)
def setPattern(self, pattern, anchor=False, **kwargs):
self.__pattern = pattern.strip()
name = re.sub(self._patternRE, r'%(\1)s', pattern) % self._patternName
DateStrptime.setName(self, name)
# Custom escape as don't want to escape "%"
pattern = re.sub(self._reEscape, r'\\\1', pattern)
regex = re.sub(
self._patternRE, r'%(\1)s', pattern) % self._patternRegex
if anchor:
regex = r"^" + regex
DateStrptime.setRegex(self, regex, **kwargs)
def getPattern(self):
return self.__pattern
def setRegex(self, line):
raise NotImplementedError("Regex derived from pattern")
def setName(self, line):
raise NotImplementedError("Name derived from pattern")
class DateTai64n(DateTemplate):

View File

@ -32,6 +32,7 @@ from failmanager import FailManager
from ticket import FailTicket
from jailthread import JailThread
from datedetector import DateDetector
from datetemplate import DatePatternRegex
from mytime import MyTime
from failregex import FailRegex, Regex, RegexException
@ -195,6 +196,40 @@ class Filter(JailThread):
def getFindTime(self):
return self.__findTime
##
# Set the date detector pattern, removing Defaults
#
# @param pattern the date template pattern
def setDatePattern(self, pattern):
dateDetector = DateDetector()
template = DatePatternRegex()
if pattern[0] == "^": # Special extra to enable anchor
template.setPattern(pattern[1:], anchor=True)
else:
template.setPattern(pattern, anchor=False)
dateDetector.appendTemplate(template)
self.dateDetector = dateDetector
logSys.info("Date pattern set to `%r`: `%s`" %
(pattern, template.getName()))
logSys.debug("Date pattern regex for %r: %s" %
(pattern, template.getRegex()))
##
# Get the date detector pattern, or Default Detectors if not changed
#
# @return pattern of the date template pattern
def getDatePattern(self):
templates = self.dateDetector.getTemplates()
if len(templates) > 1:
return None # Default Detectors in use
elif len(templates) == 1:
pattern = templates[0].getPattern()
if templates[0].getRegex()[0] == "^":
pattern = "^" + pattern
return pattern, templates[0].getName()
##
# Set the maximum retry value.
#

View File

@ -193,6 +193,12 @@ class Server:
def getFindTime(self, name):
return self.__jails.getFilter(name).getFindTime()
def setDatePattern(self, name, pattern):
self.__jails.getFilter(name).setDatePattern(pattern)
def getDatePattern(self, name):
return self.__jails.getFilter(name).getDatePattern()
def addFailRegex(self, name, value):
self.__jails.getFilter(name).addFailRegex(value)

View File

@ -171,6 +171,10 @@ class Transmitter:
value = command[2]
self.__server.setFindTime(name, int(value))
return self.__server.getFindTime(name)
elif command[1] == "datepattern":
value = command[2]
self.__server.setDatePattern(name, value)
return self.__server.getDatePattern(name)
elif command[1] == "maxretry":
value = command[2]
self.__server.setMaxRetry(name, int(value))
@ -258,6 +262,8 @@ class Transmitter:
return self.__server.getUseDns(name)
elif command[1] == "findtime":
return self.__server.getFindTime(name)
elif command[1] == "datepattern":
return self.__server.getDatePattern(name)
elif command[1] == "maxretry":
return self.__server.getMaxRetry(name)
elif command[1] == "maxlines":

View File

@ -106,7 +106,7 @@ class DateDetectorTest(unittest.TestCase):
self.assertEqual(old_name, n.getName()) # "Sort must be stable"
def testAllUniqueTemplateNames(self):
self.assertRaises(ValueError, self.__datedetector._appendTemplate,
self.assertRaises(ValueError, self.__datedetector.appendTemplate,
self.__datedetector.getTemplates()[0])
def testFullYearMatch_gh130(self):