diff --git a/fail2ban/server/failregex.py b/fail2ban/server/failregex.py index d5c9345f..7aa5d3df 100644 --- a/fail2ban/server/failregex.py +++ b/fail2ban/server/failregex.py @@ -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. diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py index 5b9125ed..8326f049 100644 --- a/fail2ban/server/filter.py +++ b/fail2ban/server/filter.py @@ -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: diff --git a/fail2ban/tests/samplestestcase.py b/fail2ban/tests/samplestestcase.py index 5f0a447a..90ce0b7f 100644 --- a/fail2ban/tests/samplestestcase.py +++ b/fail2ban/tests/samplestestcase.py @@ -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():