failregex: introduced capturing alternate groups, for example non-empty values of `alt_user_1`, `alt_user_2` will overwrite `user` if it is empty (or `alt_host` -> `host`, etc.)

pull/2090/head
sebres 2018-03-19 21:44:22 +01:00
parent 8028d3940d
commit 5603055a58
3 changed files with 63 additions and 36 deletions

View File

@ -89,6 +89,11 @@ def mapTag2Opt(tag):
except KeyError:
return tag.lower()
# alternate names to be merged, e. g. alt_user_1 -> user ...
ALTNAME_PRE = 'alt_'
ALTNAME_CRE = re.compile(r'^' + ALTNAME_PRE + r'(.*)(?:_\d+)?$')
##
# Regular expression class.
#
@ -114,6 +119,14 @@ class Regex:
try:
self._regexObj = re.compile(regex, re.MULTILINE if multiline else 0)
self._regex = regex
self._altValues = {}
for k in filter(
lambda k: len(k) > len(ALTNAME_PRE) and k.startswith(ALTNAME_PRE),
self._regexObj.groupindex
):
n = ALTNAME_CRE.match(k).group(1)
self._altValues[k] = n
self._altValues = list(self._altValues.items()) if len(self._altValues) else None
except sre_constants.error:
raise RegexException("Unable to compile regular expression '%s'" %
regex)
@ -248,7 +261,16 @@ class Regex:
#
def getGroups(self):
return self._matchCache.groupdict()
if not self._altValues:
return self._matchCache.groupdict()
# merge alternate values (e. g. 'alt_user_1' -> 'user' or 'alt_host' -> 'host'):
fail = self._matchCache.groupdict()
#fail = fail.copy()
for k,n in self._altValues:
v = fail.get(k)
if v and not fail.get(n):
fail[n] = v
return fail
##
# Returns skipped lines.

View File

@ -692,43 +692,46 @@ class Filter(JailThread):
# Iterates over all the regular expressions.
for failRegexIndex, failRegex in enumerate(self.__failRegex):
if logSys.getEffectiveLevel() <= logging.HEAVYDEBUG: # pragma: no cover
logSys.log(5, " Looking for failregex %d - %r", failRegexIndex, failRegex.getRegex())
failRegex.search(self.__lineBuffer, orgBuffer)
if not failRegex.hasMatched():
continue
# The failregex matched.
logSys.log(7, " Matched failregex %d: %s", failRegexIndex, failRegex.getGroups())
# Checks if we must ignore this match.
if self.ignoreLine(failRegex.getMatchedTupleLines()) \
is not None:
# The ignoreregex matched. Remove ignored match.
self.__lineBuffer = failRegex.getUnmatchedTupleLines()
logSys.log(7, " Matched ignoreregex and was ignored")
if not self.checkAllRegex:
break
else:
continue
if date is None:
logSys.warning(
"Found a match for %r but no valid date/time "
"found for %r. Please try setting a custom "
"date pattern (see man page jail.conf(5)). "
"If format is complex, please "
"file a detailed issue on"
" https://github.com/fail2ban/fail2ban/issues "
"in order to get support for this format.",
"\n".join(failRegex.getMatchedLines()), timeText)
continue
self.__lineBuffer = failRegex.getUnmatchedTupleLines()
# retrieve failure-id, host, etc from failure match:
try:
if logSys.getEffectiveLevel() <= logging.HEAVYDEBUG: # pragma: no cover
logSys.log(5, " Looking for failregex %d - %r", failRegexIndex, failRegex.getRegex())
failRegex.search(self.__lineBuffer, orgBuffer)
if not failRegex.hasMatched():
continue
# current failure data (matched group dict):
fail = failRegex.getGroups()
# The failregex matched.
logSys.log(7, " Matched failregex %d: %s", failRegexIndex, fail)
# Checks if we must ignore this match.
if self.ignoreLine(failRegex.getMatchedTupleLines()) \
is not None:
# The ignoreregex matched. Remove ignored match.
self.__lineBuffer = failRegex.getUnmatchedTupleLines()
logSys.log(7, " Matched ignoreregex and was ignored")
if not self.checkAllRegex:
break
else:
continue
if date is None:
logSys.warning(
"Found a match for %r but no valid date/time "
"found for %r. Please try setting a custom "
"date pattern (see man page jail.conf(5)). "
"If format is complex, please "
"file a detailed issue on"
" https://github.com/fail2ban/fail2ban/issues "
"in order to get support for this format.",
"\n".join(failRegex.getMatchedLines()), timeText)
continue
# we should check all regex (bypass on multi-line, otherwise too complex):
if not self.checkAllRegex or self.getMaxLines() > 1:
self.__lineBuffer = failRegex.getUnmatchedTupleLines()
# merge data if multi-line failure:
raw = returnRawHost
if preGroups:
fail = preGroups.copy()
fail.update(failRegex.getGroups())
else:
fail = failRegex.getGroups()
currFail, fail = fail, preGroups.copy()
fail.update(currFail)
# first try to check we have mlfid case (caching of connection id by multi-line):
mlfid = fail.get('mlfid')
if mlfid is not None:

View File

@ -144,6 +144,7 @@ def testSampleRegexsFactory(name, basedir):
regexsUsedRe = set()
# process each test-file (note: array filenames can grow during processing):
faildata = {}
i = 0
while i < len(filenames):
filename = filenames[i]; i += 1;
@ -195,6 +196,7 @@ def testSampleRegexsFactory(name, basedir):
regexList = flt.getFailRegex()
try:
fail = {}
ret = flt.processLine(line)
if not ret:
# Bypass if filter constraint specified:
@ -246,8 +248,8 @@ def testSampleRegexsFactory(name, basedir):
regexsUsedIdx.add(failregex)
regexsUsedRe.add(regexList[failregex])
except AssertionError as e: # pragma: no cover
raise AssertionError("%s: %s on: %s:%i, line:\n%s" % (
fltName, e, logFile.filename(), logFile.filelineno(), line))
raise AssertionError("%s: %s on: %s:%i, line:\n%s\nfaildata:%r, fail:%r" % (
fltName, e, logFile.filename(), logFile.filelineno(), line, faildata, fail))
# check missing samples for regex using each filter-options combination:
for fltName, flt in self._filters.iteritems():