diff --git a/config/filter.d/sshd.conf b/config/filter.d/sshd.conf
index 320ab59c..95915fcc 100644
--- a/config/filter.d/sshd.conf
+++ b/config/filter.d/sshd.conf
@@ -37,24 +37,24 @@ cmnfailre = ^[aA]uthentication (?:failure|error|failed) for .*
^User .+ from not allowed because listed in DenyUsers\s*%(__suff)s$
^User .+ from not allowed because not in any group\s*%(__suff)s$
^refused connect from \S+ \(\)\s*%(__suff)s$
- ^Received disconnect from %(__on_port_opt)s:\s*3: .*: Auth fail%(__suff)s$
+ ^Received disconnect from %(__on_port_opt)s:\s*3: .*: Auth fail%(__suff)s$
^User .+ from not allowed because a group is listed in DenyGroups\s*%(__suff)s$
^User .+ from not allowed because none of user's groups are listed in AllowGroups\s*%(__suff)s$
^pam_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$
^(error: )?maximum authentication attempts exceeded for .* from %(__on_port_opt)s(?: ssh\d*)?%(__suff)s$
^User .+ not allowed because account is locked%(__suff)s
- ^Disconnecting: Too many authentication failures(?: for .+?)?%(__suff)s
- ^Received disconnect from : 11:
- ^Connection closed by %(__suff)s$
+ ^Disconnecting: Too many authentication failures(?: for .+?)?%(__suff)s
+ ^Received disconnect from : 11:
+ ^Connection closed by %(__suff)s$
mdre-normal =
mdre-ddos = ^Did not receive identification string from %(__suff)s$
- ^Connection reset by %(__on_port_opt)s%(__suff)s
+ ^Connection reset by %(__on_port_opt)s%(__suff)s
^SSH: Server;Ltype: (?:Authname|Version|Kex);Remote: -\d+;[A-Z]\w+:
- ^Read from socket failed: Connection reset by peer%(__suff)s
+ ^Read from socket failed: Connection reset by peer%(__suff)s
-mdre-extra = ^Received disconnect from %(__on_port_opt)s:\s*14: No supported authentication methods available%(__suff)s$
+mdre-extra = ^Received disconnect from %(__on_port_opt)s:\s*14: No supported authentication methods available%(__suff)s$
^Unable to negotiate with %(__on_port_opt)s: no matching (?:cipher|key exchange method) found.
^Unable to negotiate a (?:cipher|key exchange method)%(__suff)s$
diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py
index ca2dae86..cbdb4857 100644
--- a/fail2ban/server/filter.py
+++ b/fail2ban/server/filter.py
@@ -554,20 +554,29 @@ class Filter(JailThread):
mlfidGroups = mlfidFail[1]
# if current line not failure, but previous was failure:
if fail.get('nofail') and not mlfidGroups.get('nofail'):
- del fail['nofail'] # remove nofail flag - was already market as failure
+ del fail['nofail'] # remove nofail flag - completed with fid (host, ip)
self.mlfidCache.unset(mlfid) # remove cache entry
# if current line is failure, but previous was not:
elif not fail.get('nofail') and mlfidGroups.get('nofail'):
- del mlfidGroups['nofail'] # remove nofail flag
+ del mlfidGroups['nofail'] # remove nofail flag - completed as failure
self.mlfidCache.unset(mlfid) # remove cache entry
+ else:
+ # cache this line info (if not forget):
+ if not fail.get('mlfforget'):
+ mlfidFail = [self.__lastDate, fail]
+ self.mlfidCache.set(mlfid, mlfidFail)
+ else:
+ self.mlfidCache.unset(mlfid) # remove cache entry
+ return fail
fail2 = mlfidGroups.copy()
fail2.update(fail)
fail2["matches"] = fail.get("matches", []) + failRegex.getMatchedTupleLines()
fail = fail2
- elif fail.get('nofail'):
- fail["matches"] = failRegex.getMatchedTupleLines()
+ elif not fail.get('mlfforget'):
mlfidFail = [self.__lastDate, fail]
self.mlfidCache.set(mlfid, mlfidFail)
+ if fail.get('nofail'):
+ fail["matches"] = failRegex.getMatchedTupleLines()
return fail
@@ -683,6 +692,11 @@ class Filter(JailThread):
mlfid = fail.get('mlfid')
if mlfid is not None:
fail = self._mergeFailure(mlfid, fail, failRegex)
+ # bypass if no-failure case:
+ if fail.get('nofail'):
+ logSys.log(7, "Nofail by mlfid %r in regex %s: waiting for failure",
+ mlfid, failRegexIndex)
+ if not self.checkAllRegex: return failList
else:
# matched lines:
fail["matches"] = fail.get("matches", []) + failRegex.getMatchedTupleLines()
@@ -702,18 +716,16 @@ class Filter(JailThread):
host = fail.get('dns')
if host is None:
# first try to check we have mlfid case (cache connection id):
- if fid is None:
- if mlfid:
- fail = self._mergeFailure(mlfid, fail, failRegex)
- else:
+ if fid is None and mlfid is None:
# if no failure-id also (obscure case, wrong regex), throw error inside getFailID:
fid = failRegex.getFailID()
host = fid
cidr = IPAddr.CIDR_RAW
# if mlfid case (not failure):
if host is None:
- if not self.checkAllRegex: # or fail.get('nofail'):
- return failList
+ logSys.log(7, "No failure-id by mlfid %r in regex %s: waiting for identifier",
+ mlfid, failRegexIndex)
+ if not self.checkAllRegex: return failList
ips = [None]
# if raw - add single ip or failure-id,
# otherwise expand host to multiple ips using dns (or ignore it if not valid):
diff --git a/fail2ban/tests/files/logs/sshd b/fail2ban/tests/files/logs/sshd
index fe19591c..6f9a1468 100644
--- a/fail2ban/tests/files/logs/sshd
+++ b/fail2ban/tests/files/logs/sshd
@@ -113,6 +113,11 @@ May 27 00:16:33 host sshd[2364]: Received disconnect from 198.51.100.76: 11: Bye
# failJSON: { "time": "2004-09-29T16:28:02", "match": true , "host": "127.0.0.1" }
Sep 29 16:28:02 spaceman sshd[16699]: Failed password for dan from 127.0.0.1 port 45416 ssh1
+# failJSON: { "match": false, "desc": "no failure, just cache mlfid (conn-id)" }
+Sep 29 16:28:05 localhost sshd[16700]: Connection from 192.0.2.5
+# failJSON: { "match": false, "desc": "no failure, just covering mlfid (conn-id) forget" }
+Sep 29 16:28:05 localhost sshd[16700]: Connection closed by 192.0.2.5 [preauth]
+
# failJSON: { "time": "2004-09-29T17:15:02", "match": true , "host": "127.0.0.1" }
Sep 29 17:15:02 spaceman sshd[12946]: Failed hostbased for dan from 127.0.0.1 port 45785 ssh2: RSA 8c:e3:aa:0f:64:51:02:f7:14:79:89:3f:65:84:7c:30, client user "dan", client host "localhost.localdomain"