fail2ban/fail2ban/server/datedetector.py

210 lines
6.2 KiB
Python

# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
__author__ = "Cyril Jaquier and Fail2Ban Contributors"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from threading import Lock
from .datetemplate import DatePatternRegex, DateTai64n, DateEpoch
from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
class DateDetector(object):
"""Manages one or more date templates to find a date within a log line.
Attributes
----------
templates
"""
def __init__(self):
self.__lock = Lock()
self.__templates = list()
self.__known_names = set()
def _appendTemplate(self, template):
name = template.name
if name in self.__known_names:
raise ValueError(
"There is already a template with name %s" % name)
self.__known_names.add(name)
self.__templates.append(template)
def appendTemplate(self, template):
"""Add a date template to manage and use in search of dates.
Parameters
----------
template : DateTemplate or str
Can be either a `DateTemplate` instance, or a string which will
be used as the pattern for the `DatePatternRegex` template. The
template will then be added to the detector.
Raises
------
ValueError
If a template already exists with the same name.
"""
if isinstance(template, str):
template = DatePatternRegex(template)
self._appendTemplate(template)
def addDefaultTemplate(self):
"""Add Fail2Ban's default set of date templates.
"""
self.__lock.acquire()
try:
# asctime with optional day, subsecond and/or year:
# Sun Jan 23 21:59:59.011 2005
self.appendTemplate("(?:%a )?%b %d %H:%M:%S(?:\.%f)?(?: %Y)?")
# simple date, optional subsecond (proftpd):
# 2005-01-23 21:59:59
# simple date: 2005/01/23 21:59:59
# custom for syslog-ng 2006.12.21 06:43:20
self.appendTemplate("%Y(?P<_sep>[-/.])%m(?P=_sep)%d %H:%M:%S(?:,%f)?")
# simple date too (from x11vnc): 23/01/2005 21:59:59
# and with optional year given by 2 digits: 23/01/05 21:59:59
# (See http://bugs.debian.org/537610)
# 17-07-2008 17:23:25
self.appendTemplate("%d(?P<_sep>[-/])%m(?P=_sep)(?:%Y|%y) %H:%M:%S")
# Apache format optional time zone:
# [31/Oct/2006:09:22:55 -0000]
# 26-Jul-2007 15:20:52
self.appendTemplate("%d(?P<_sep>[-/])%b(?P=_sep)%Y[ :]?%H:%M:%S(?:\.%f)?(?: %z)?")
# CPanel 05/20/2008:01:57:39
self.appendTemplate("%m/%d/%Y:%H:%M:%S")
# named 26-Jul-2007 15:20:52.252
# roundcube 26-Jul-2007 15:20:52 +0200
# 01-27-2012 16:22:44.252
# subseconds explicit to avoid possible %m<->%d confusion
# with previous
self.appendTemplate("%m-%d-%Y %H:%M:%S\.%f")
# TAI64N
template = DateTai64n()
template.name = "TAI64N"
self.appendTemplate(template)
# Epoch
template = DateEpoch()
template.name = "Epoch"
self.appendTemplate(template)
# ISO 8601
self.appendTemplate("%Y-%m-%d[T ]%H:%M:%S(?:\.%f)?(?:%z)?")
# Only time information in the log
self.appendTemplate("^%H:%M:%S")
# <09/16/08@05:03:30>
self.appendTemplate("^<%m/%d/%y@%H:%M:%S>")
# MySQL: 130322 11:46:11
self.appendTemplate("^%y%m%d ?%H:%M:%S")
# Apache Tomcat
self.appendTemplate("%b %d, %Y %I:%M:%S %p")
# ASSP: Apr-27-13 02:33:06
self.appendTemplate("^%b-%d-%y %H:%M:%S")
finally:
self.__lock.release()
@property
def templates(self):
"""List of template instances managed by the detector.
"""
return self.__templates
def matchTime(self, line):
"""Attempts to find date on a log line using templates.
This uses the templates' `matchDate` method in an attempt to find
a date. It also increments the match hit count for the winning
template.
Parameters
----------
line : str
Line which is searched by the date templates.
Returns
-------
re.MatchObject
The regex match returned from the first successfully matched
template.
"""
self.__lock.acquire()
try:
for template in self.__templates:
match = template.matchDate(line)
if not match is None:
logSys.debug("Matched time template %s" % template.name)
template.hits += 1
return match
return None
finally:
self.__lock.release()
def getTime(self, line):
"""Attempts to return the date on a log line using templates.
This uses the templates' `getDate` method in an attempt to find
a date.
Parameters
----------
line : str
Line which is searched by the date templates.
Returns
-------
float
The Unix timestamp returned from the first successfully matched
template.
"""
self.__lock.acquire()
try:
for template in self.__templates:
try:
date = template.getDate(line)
if date is None:
continue
logSys.debug("Got time %f for \"%r\" using template %s" %
(date[0], date[1].group(), template.name))
return date
except ValueError:
pass
return None
finally:
self.__lock.release()
def sortTemplate(self):
"""Sort the date templates by number of hits
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.
This ensures the most commonly matched templates are checked first,
improving performance of matchTime and getTime.
"""
self.__lock.acquire()
try:
logSys.debug("Sorting the template list")
self.__templates.sort(key=lambda x: x.hits, reverse=True)
t = self.__templates[0]
logSys.debug("Winning template: %s with %d hits" % (t.name, t.hits))
finally:
self.__lock.release()