diff --git a/config/filter.d/pam-generic.conf b/config/filter.d/pam-generic.conf index e0d4e9c1..ff4ea802 100644 --- a/config/filter.d/pam-generic.conf +++ b/config/filter.d/pam-generic.conf @@ -16,7 +16,12 @@ _ttys_re=\S* __pam_re=\(?%(__pam_auth)s(?:\(\S+\))?\)?:? _daemon = \S+ -failregex = ^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=%(_ttys_re)s ruser=\S* rhost=(?:\s+user=.*)?\s*$ +prefregex = ^%(__prefix_line)s%(__pam_re)s\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=%(_ttys_re)s .+$ + +failregex = ^ruser=\S* rhost=\s*$ + ^ruser= rhost=\s+user=\S*\s*$ + ^ruser= rhost=\s+user=.*?\s*$ + ^ruser=.*? rhost=\s*$ ignoreregex = diff --git a/config/filter.d/sshd.conf b/config/filter.d/sshd.conf index ebdc06ec..872a73e4 100644 --- a/config/filter.d/sshd.conf +++ b/config/filter.d/sshd.conf @@ -32,23 +32,23 @@ __prefix_line_ml2 = %(__suff)s$^(?P=__prefix)%(__pref)s mode = %(normal)s -normal = ^%(__prefix_line_sl)s[aA]uthentication (?:failure|error|failed) for .* from ( via \S+)?\s*%(__suff)s$ - ^%(__prefix_line_sl)sUser not known to the underlying authentication module for .* from \s*%(__suff)s$ - ^%(__prefix_line_sl)sFailed \S+ for (?Pinvalid user )?(?P(?P\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)) from %(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$) - ^%(__prefix_line_sl)sROOT LOGIN REFUSED.* FROM \s*%(__suff)s$ - ^%(__prefix_line_sl)s[iI](?:llegal|nvalid) user .*? from %(__on_port_opt)s\s*$ - ^%(__prefix_line_sl)sUser .+ from not allowed because not listed in AllowUsers\s*%(__suff)s$ - ^%(__prefix_line_sl)sUser .+ from not allowed because listed in DenyUsers\s*%(__suff)s$ - ^%(__prefix_line_sl)sUser .+ from not allowed because not in any group\s*%(__suff)s$ +normal = ^%(__prefix_line_sl)s[aA]uthentication (?:failure|error|failed) for .* from ( via \S+)?\s*%(__suff)s$ + ^%(__prefix_line_sl)sUser not known to the underlying authentication module for .* from \s*%(__suff)s$ + ^%(__prefix_line_sl)sFailed \S+ for (?Pinvalid user )?(?P\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+) from %(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$) + ^%(__prefix_line_sl)sROOT LOGIN REFUSED.* FROM \s*%(__suff)s$ + ^%(__prefix_line_sl)s[iI](?:llegal|nvalid) user .*? from %(__on_port_opt)s\s*$ + ^%(__prefix_line_sl)sUser .+ from not allowed because not listed in AllowUsers\s*%(__suff)s$ + ^%(__prefix_line_sl)sUser .+ from not allowed because listed in DenyUsers\s*%(__suff)s$ + ^%(__prefix_line_sl)sUser .+ from not allowed because not in any group\s*%(__suff)s$ ^%(__prefix_line_sl)srefused connect from \S+ \(\)\s*%(__suff)s$ ^%(__prefix_line_sl)sReceived disconnect from %(__on_port_opt)s:\s*3: .*: Auth fail%(__suff)s$ - ^%(__prefix_line_sl)sUser .+ from not allowed because a group is listed in DenyGroups\s*%(__suff)s$ - ^%(__prefix_line_sl)sUser .+ from not allowed because none of user's groups are listed in AllowGroups\s*%(__suff)s$ - ^%(__prefix_line_sl)spam_unix\(sshd:auth\):\s+authentication failure;\s*logname=\S*\s*uid=\d*\s*euid=\d*\s*tty=\S*\s*ruser=\S*\s*rhost=\s.*%(__suff)s$ - ^%(__prefix_line_sl)s(error: )?maximum authentication attempts exceeded for .* from %(__on_port_opt)s(?: ssh\d*)? \[preauth\]$ - ^%(__prefix_line_ml1)sUser .+ not allowed because account is locked%(__prefix_line_ml2)sReceived disconnect from : 11: .+%(__suff)s$ - ^%(__prefix_line_ml1)sDisconnecting: Too many authentication failures for .+?%(__prefix_line_ml2)sConnection closed by %(__suff)s$ - ^%(__prefix_line_ml1)sConnection from %(__on_port_opt)s%(__prefix_line_ml2)sDisconnecting: Too many authentication failures for .+%(__suff)s$ + ^%(__prefix_line_sl)sUser .+ from not allowed because a group is listed in DenyGroups\s*%(__suff)s$ + ^%(__prefix_line_sl)sUser .+ from not allowed because none of user's groups are listed in AllowGroups\s*%(__suff)s$ + ^%(__prefix_line_sl)spam_unix\(sshd:auth\):\s+authentication failure;\s*logname=\S*\s*uid=\d*\s*euid=\d*\s*tty=\S*\s*ruser=\S*\s*rhost=\s.*%(__suff)s$ + ^%(__prefix_line_sl)s(error: )?maximum authentication attempts exceeded for .* from %(__on_port_opt)s(?: ssh\d*)? \[preauth\]$ + ^%(__prefix_line_ml1)sUser .+ not allowed because account is locked%(__prefix_line_ml2)sReceived disconnect from : 11: .+%(__suff)s$ + ^%(__prefix_line_ml1)sDisconnecting: Too many authentication failures for .+?%(__prefix_line_ml2)sConnection closed by %(__suff)s$ + ^%(__prefix_line_ml1)sConnection from %(__on_port_opt)s%(__prefix_line_ml2)sDisconnecting: Too many authentication failures for .+%(__suff)s$ ddos = ^%(__prefix_line_sl)sDid not receive identification string from %(__suff)s$ ^%(__prefix_line_sl)sReceived disconnect from %(__on_port_opt)s:\s*14: No supported authentication methods available%(__suff)s$ diff --git a/fail2ban/client/fail2banregex.py b/fail2ban/client/fail2banregex.py index d111e09c..84106d02 100644 --- a/fail2ban/client/fail2banregex.py +++ b/fail2ban/client/fail2banregex.py @@ -271,7 +271,7 @@ class Fail2banRegex(object): def readRegex(self, value, regextype): assert(regextype in ('fail', 'ignore')) regex = regextype + 'regex' - if os.path.isfile(value) or os.path.isfile(value + '.conf'): + if regextype == 'fail' and (os.path.isfile(value) or os.path.isfile(value + '.conf')): if os.path.basename(os.path.dirname(value)) == 'filter.d': ## within filter.d folder - use standard loading algorithm to load filter completely (with .local etc.): basedir = os.path.dirname(os.path.dirname(value)) @@ -291,43 +291,51 @@ class Fail2banRegex(object): return False reader.getOptions(None) readercommands = reader.convert() - regex_values = [ - RegexStat(m[3]) - for m in filter( - lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype, - readercommands) - ] + [ - RegexStat(m) - for mm in filter( - lambda x: x[0] == 'multi-set' and x[2] == "add%sregex" % regextype, - readercommands) - for m in mm[3] - ] - # Read out and set possible value of maxlines - for command in readercommands: - if command[2] == "maxlines": - maxlines = int(command[3]) + + regex_values = {} + for opt in readercommands: + if opt[0] == 'multi-set': + optval = opt[3] + elif opt[0] == 'set': + optval = [opt[3]] + else: + continue + for optval in optval: try: - self.setMaxLines(maxlines) - except ValueError: - output( "ERROR: Invalid value for maxlines (%(maxlines)r) " \ - "read from %(value)s" % locals() ) + if opt[2] == "prefregex": + self._filter.prefRegex = optval + elif opt[2] == "addfailregex": + stor = regex_values.get('fail') + if not stor: stor = regex_values['fail'] = list() + stor.append(RegexStat(optval)) + #self._filter.addFailRegex(optval) + elif opt[2] == "addignoreregex": + stor = regex_values.get('ignore') + if not stor: stor = regex_values['ignore'] = list() + stor.append(RegexStat(optval)) + #self._filter.addIgnoreRegex(optval) + elif opt[2] == "maxlines": + self.setMaxLines(optval) + elif opt[2] == "datepattern": + self.setDatePattern(optval) + elif opt[2] == "addjournalmatch": + self.setJournalMatch(optval) + except ValueError as e: # pragma: no cover + output( "ERROR: Invalid value for %s (%r) " \ + "read from %s: %s" % (opt[2], optval, value, e) ) return False - elif command[2] == 'addjournalmatch': - journalmatch = command[3:] - self.setJournalMatch(journalmatch) - elif command[2] == 'datepattern': - datepattern = command[3] - self.setDatePattern(datepattern) + else: output( "Use %11s line : %s" % (regex, shortstr(value)) ) - regex_values = [RegexStat(value)] + regex_values = {regextype: [RegexStat(value)]} - setattr(self, "_" + regex, regex_values) - for regex in regex_values: - getattr( - self._filter, - 'add%sRegex' % regextype.title())(regex.getFailRegex()) + for regextype, regex_values in regex_values.iteritems(): + regex = regextype + 'regex' + setattr(self, "_" + regex, regex_values) + for regex in regex_values: + getattr( + self._filter, + 'add%sRegex' % regextype.title())(regex.getFailRegex()) return True def testIgnoreRegex(self, line): diff --git a/fail2ban/client/filterreader.py b/fail2ban/client/filterreader.py index d89ef5ad..59e78307 100644 --- a/fail2ban/client/filterreader.py +++ b/fail2ban/client/filterreader.py @@ -37,6 +37,7 @@ logSys = getLogger(__name__) class FilterReader(DefinitionInitConfigReader): _configOpts = { + "prefregex": ["string", None], "ignoreregex": ["string", None], "failregex": ["string", ""], "maxlines": ["int", None], @@ -72,8 +73,8 @@ class FilterReader(DefinitionInitConfigReader): # We warn when multiline regex is used without maxlines > 1 # therefore keep sure we set this option first. stream.insert(0, ["set", self._jailName, "maxlines", value]) - elif opt == 'datepattern': - stream.append(["set", self._jailName, "datepattern", value]) + elif opt in ('datepattern', 'prefregex'): + stream.append(["set", self._jailName, opt, value]) # Do not send a command if the match is empty. elif opt == 'journalmatch': if value is None: continue diff --git a/fail2ban/server/failregex.py b/fail2ban/server/failregex.py index cbd0cef0..5f6ca01f 100644 --- a/fail2ban/server/failregex.py +++ b/fail2ban/server/failregex.py @@ -189,40 +189,45 @@ class Regex: # method of this object. # @param a list of tupples. The tupples are ( prematch, datematch, postdatematch ) - def search(self, tupleLines): + def search(self, tupleLines, orgLines=None): self._matchCache = self._regexObj.search( "\n".join("".join(value[::2]) for value in tupleLines) + "\n") - if self.hasMatched(): - # Find start of the first line where the match was found - try: - self._matchLineStart = self._matchCache.string.rindex( - "\n", 0, self._matchCache.start() +1 ) + 1 - except ValueError: - self._matchLineStart = 0 - # Find end of the last line where the match was found - try: - self._matchLineEnd = self._matchCache.string.index( - "\n", self._matchCache.end() - 1) + 1 - except ValueError: - self._matchLineEnd = len(self._matchCache.string) + if self._matchCache: + if orgLines is None: orgLines = tupleLines + # if single-line: + if len(orgLines) <= 1: + self._matchedTupleLines = orgLines + self._unmatchedTupleLines = [] + else: + # Find start of the first line where the match was found + try: + matchLineStart = self._matchCache.string.rindex( + "\n", 0, self._matchCache.start() +1 ) + 1 + except ValueError: + matchLineStart = 0 + # Find end of the last line where the match was found + try: + matchLineEnd = self._matchCache.string.index( + "\n", self._matchCache.end() - 1) + 1 + except ValueError: + matchLineEnd = len(self._matchCache.string) - lineCount1 = self._matchCache.string.count( - "\n", 0, self._matchLineStart) - lineCount2 = self._matchCache.string.count( - "\n", 0, self._matchLineEnd) - self._matchedTupleLines = tupleLines[lineCount1:lineCount2] - self._unmatchedTupleLines = tupleLines[:lineCount1] - - n = 0 - for skippedLine in self.getSkippedLines(): - for m, matchedTupleLine in enumerate( - self._matchedTupleLines[n:]): - if "".join(matchedTupleLine[::2]) == skippedLine: - self._unmatchedTupleLines.append( - self._matchedTupleLines.pop(n+m)) - n += m - break - self._unmatchedTupleLines.extend(tupleLines[lineCount2:]) + lineCount1 = self._matchCache.string.count( + "\n", 0, matchLineStart) + lineCount2 = self._matchCache.string.count( + "\n", 0, matchLineEnd) + self._matchedTupleLines = orgLines[lineCount1:lineCount2] + self._unmatchedTupleLines = orgLines[:lineCount1] + n = 0 + for skippedLine in self.getSkippedLines(): + for m, matchedTupleLine in enumerate( + self._matchedTupleLines[n:]): + if "".join(matchedTupleLine[::2]) == skippedLine: + self._unmatchedTupleLines.append( + self._matchedTupleLines.pop(n+m)) + n += m + break + self._unmatchedTupleLines.extend(orgLines[lineCount2:]) # Checks if the previous call to search() matched. # @@ -234,6 +239,13 @@ class Regex: else: return False + ## + # Returns all matched groups. + # + + def getGroups(self): + return self._matchCache.groupdict() + ## # Returns skipped lines. # @@ -332,13 +344,6 @@ class FailRegex(Regex): if not [grp for grp in FAILURE_ID_GROPS if grp in self._regexObj.groupindex]: raise RegexException("No failure-id group in '%s'" % self._regex) - ## - # Returns all matched groups. - # - - def getGroups(self): - return self._matchCache.groupdict() - ## # Returns the matched failure id. # diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py index b7c78b9b..d25e8dc5 100644 --- a/fail2ban/server/filter.py +++ b/fail2ban/server/filter.py @@ -65,6 +65,8 @@ class Filter(JailThread): self.jail = jail ## The failures manager. self.failManager = FailManager() + ## Regular expression pre-filtering matching the failures. + self.__prefRegex = None ## The regular expression list matching the failures. self.__failRegex = list() ## The regular expression list with expressions to ignore. @@ -129,6 +131,16 @@ class Filter(JailThread): self.delLogPath(path) delattr(self, '_reload_logs') + @property + def prefRegex(self): + return self.__prefRegex + @prefRegex.setter + def prefRegex(self, value): + if value: + self.__prefRegex = Regex(value, useDns=self.__useDns) + else: + self.__prefRegex = None + ## # Add a regular expression which matches the failure. # @@ -582,13 +594,30 @@ class Filter(JailThread): date, MyTime.time(), self.getFindTime()) return failList - self.__lineBuffer = ( - self.__lineBuffer + [tupleLine[:3]])[-self.__lineBufferSize:] - logSys.log(5, "Looking for failregex match of %r" % self.__lineBuffer) + if self.__lineBufferSize > 1: + orgBuffer = self.__lineBuffer = ( + self.__lineBuffer + [tupleLine[:3]])[-self.__lineBufferSize:] + else: + orgBuffer = self.__lineBuffer = [tupleLine[:3]] + logSys.log(5, "Looking for failregex match of %r", self.__lineBuffer) + + # Pre-filter fail regex (if available): + preGroups = {} + if self.__prefRegex: + failRegex = self.__prefRegex.search(self.__lineBuffer) + if not self.__prefRegex.hasMatched(): + return failList + logSys.log(7, "Pre-filter matched %s", failRegex) + preGroups = self.__prefRegex.getGroups() + repl = preGroups.get('content') + # Content replacement: + if repl: + del preGroups['content'] + self.__lineBuffer = [('', '', repl)] # Iterates over all the regular expressions. for failRegexIndex, failRegex in enumerate(self.__failRegex): - failRegex.search(self.__lineBuffer) + failRegex.search(self.__lineBuffer, orgBuffer) if failRegex.hasMatched(): # The failregex matched. logSys.log(7, "Matched %s", failRegex) @@ -617,7 +646,11 @@ class Filter(JailThread): # retrieve failure-id, host, etc from failure match: raw = returnRawHost try: - fail = failRegex.getGroups() + if preGroups: + fail = preGroups.copy() + fail.update(failRegex.getGroups()) + else: + fail = failRegex.getGroups() # failure-id: fid = fail.get('fid') # ip-address or host: diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 313b6ee5..dfab1e38 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -379,6 +379,14 @@ class Server: def getIgnoreCommand(self, name): return self.__jails[name].filter.getIgnoreCommand() + def setPrefRegex(self, name, value): + flt = self.__jails[name].filter + logSys.debug(" prefregex: %r", value) + flt.prefRegex = value + + def getPrefRegex(self, name): + return self.__jails[name].filter.prefRegex + def addFailRegex(self, name, value, multiple=False): flt = self.__jails[name].filter if not multiple: value = (value,) diff --git a/fail2ban/server/transmitter.py b/fail2ban/server/transmitter.py index d23f12e2..265b9704 100644 --- a/fail2ban/server/transmitter.py +++ b/fail2ban/server/transmitter.py @@ -221,6 +221,10 @@ class Transmitter: value = command[2:] self.__server.delJournalMatch(name, value) return self.__server.getJournalMatch(name) + elif command[1] == "prefregex": + value = command[2] + self.__server.setPrefRegex(name, value) + return self.__server.getPrefRegex(name) elif command[1] == "addfailregex": value = command[2] self.__server.addFailRegex(name, value, multiple=multiple) @@ -341,6 +345,8 @@ class Transmitter: return self.__server.getIgnoreIP(name) elif command[1] == "ignorecommand": return self.__server.getIgnoreCommand(name) + elif command[1] == "prefregex": + return self.__server.getPrefRegex(name) elif command[1] == "failregex": return self.__server.getFailRegex(name) elif command[1] == "ignoreregex": diff --git a/fail2ban/tests/files/logs/pam-generic b/fail2ban/tests/files/logs/pam-generic index e562ac7f..1740f0c8 100644 --- a/fail2ban/tests/files/logs/pam-generic +++ b/fail2ban/tests/files/logs/pam-generic @@ -1,17 +1,23 @@ -# failJSON: { "time": "2005-02-07T15:10:42", "match": true , "host": "192.168.1.1" } +# failJSON: { "time": "2005-02-07T15:10:42", "match": true , "host": "192.168.1.1", "user": "sample-user" } Feb 7 15:10:42 example pure-ftpd: (pam_unix) authentication failure; logname= uid=0 euid=0 tty=pure-ftpd ruser=sample-user rhost=192.168.1.1 -# failJSON: { "time": "2005-05-12T09:47:54", "match": true , "host": "71-13-115-12.static.mdsn.wi.charter.com" } +# failJSON: { "time": "2005-05-12T09:47:54", "match": true , "host": "71-13-115-12.static.mdsn.wi.charter.com", "user": "root" } May 12 09:47:54 vaio sshd[16004]: (pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=71-13-115-12.static.mdsn.wi.charter.com user=root # failJSON: { "time": "2005-05-12T09:48:03", "match": true , "host": "71-13-115-12.static.mdsn.wi.charter.com" } May 12 09:48:03 vaio sshd[16021]: (pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=71-13-115-12.static.mdsn.wi.charter.com -# failJSON: { "time": "2005-05-15T18:02:12", "match": true , "host": "66.232.129.62" } +# failJSON: { "time": "2005-05-15T18:02:12", "match": true , "host": "66.232.129.62", "user": "mark" } May 15 18:02:12 localhost proftpd: (pam_unix) authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=66.232.129.62 user=mark # linux-pam messages before commit f0f9c4479303b5a9c37667cf07f58426dc081676 (release 0.99.2.0 ) - nolonger supported # failJSON: { "time": "2004-11-25T17:12:13", "match": false } Nov 25 17:12:13 webmail pop(pam_unix)[4920]: authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=192.168.10.3 user=mailuser -# failJSON: { "time": "2005-07-19T18:11:26", "match": true , "host": "www.google.com" } +# failJSON: { "time": "2005-07-19T18:11:26", "match": true , "host": "www.google.com", "user": "an8767" } Jul 19 18:11:26 srv2 vsftpd: pam_unix(vsftpd:auth): authentication failure; logname= uid=0 euid=0 tty=ftp ruser=an8767 rhost=www.google.com # failJSON: { "time": "2005-07-19T18:11:26", "match": true , "host": "www.google.com" } Jul 19 18:11:26 srv2 vsftpd: pam_unix: authentication failure; logname= uid=0 euid=0 tty=ftp ruser=an8767 rhost=www.google.com + + +# failJSON: { "time": "2005-07-19T18:11:50", "match": true , "host": "192.0.2.1", "user": "test rhost=192.0.2.151", "desc": "Injecting on username"} +Jul 19 18:11:50 srv2 daemon: pam_unix(auth): authentication failure; logname= uid=0 euid=0 tty=xxx ruser=test rhost=192.0.2.151 rhost=192.0.2.1 +# failJSON: { "time": "2005-07-19T18:11:52", "match": true , "host": "192.0.2.2", "user": "test rhost=192.0.2.152", "desc": "Injecting on username after host"} +Jul 19 18:11:52 srv2 daemon: pam_unix(auth): authentication failure; logname= uid=0 euid=0 tty=xxx ruser= rhost=192.0.2.2 user=test rhost=192.0.2.152 diff --git a/fail2ban/tests/samplestestcase.py b/fail2ban/tests/samplestestcase.py index 3fdc50c6..a97e92c3 100644 --- a/fail2ban/tests/samplestestcase.py +++ b/fail2ban/tests/samplestestcase.py @@ -102,7 +102,9 @@ def testSampleRegexsFactory(name, basedir): else: continue for optval in optval: - if opt[2] == "addfailregex": + if opt[2] == "prefregex": + self.filter.prefRegex = optval + elif opt[2] == "addfailregex": self.filter.addFailRegex(optval) elif opt[2] == "addignoreregex": self.filter.addIgnoreRegex(optval) @@ -126,7 +128,7 @@ def testSampleRegexsFactory(name, basedir): # test regexp contains greedy catch-all before , that is # not hard-anchored at end or has not precise sub expression after : for fr in self.filter.getFailRegex(): - if RE_WRONG_GREED.search(fr): #pragma: no cover + if RE_WRONG_GREED.search(fr): # pragma: no cover raise AssertionError("Following regexp of \"%s\" contains greedy catch-all before , " "that is not hard-anchored at end or has not precise sub expression after :\n%s" % (name, str(fr).replace(RE_HOST, ''))) @@ -152,12 +154,12 @@ def testSampleRegexsFactory(name, basedir): if not ret: # Check line is flagged as none match self.assertFalse(faildata.get('match', True), - "Line not matched when should have: %s:%i %r" % + "Line not matched when should have: %s:%i, line:\n%s" % (logFile.filename(), logFile.filelineno(), line)) elif ret: # Check line is flagged to match self.assertTrue(faildata.get('match', False), - "Line matched when shouldn't have: %s:%i %r" % + "Line matched when shouldn't have: %s:%i, line:\n%s" % (logFile.filename(), logFile.filelineno(), line)) self.assertEqual(len(ret), 1, "Multiple regexs matched %r - %s:%i" % (map(lambda x: x[0], ret),logFile.filename(), logFile.filelineno())) @@ -165,6 +167,12 @@ def testSampleRegexsFactory(name, basedir): # Verify timestamp and host as expected failregex, host, fail2banTime, lines, fail = ret[0] self.assertEqual(host, faildata.get("host", None)) + # Verify other captures: + for k, v in faildata.iteritems(): + if k not in ("time", "match", "host", "desc"): + fv = fail.get(k, None) + self.assertEqual(fv, v, "Value of %s mismatch %r != %r on: %s:%i, line:\n%s" % ( + k, fv, v, logFile.filename(), logFile.filelineno(), line)) t = faildata.get("time", None) try: @@ -177,7 +185,7 @@ def testSampleRegexsFactory(name, basedir): jsonTime += jsonTimeLocal.microsecond / 1000000 self.assertEqual(fail2banTime, jsonTime, - "UTC Time mismatch fail2ban %s (%s) != failJson %s (%s) (diff %.3f seconds) on: %s:%i %r:" % + "UTC Time mismatch %s (%s) != %s (%s) (diff %.3f seconds) on: %s:%i, line:\n%s" % (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 ) )