diff --git a/config/filter.d/sshd.conf b/config/filter.d/sshd.conf
index d764a076..b382ffc1 100644
--- a/config/filter.d/sshd.conf
+++ b/config/filter.d/sshd.conf
@@ -55,7 +55,7 @@ cmnfailre = ^[aA]uthentication (?:failure|error|failed) for .*
^(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(?: from)?(?: (?:invalid|authenticating)) user \S+ %(__on_port_opt)s:\s*Change of username or service not allowed:\s*.*\[preauth\]\s*$
- ^Disconnecting: Too many authentication failures(?: for .+?)?%(__suff)s$
+ ^Disconnecting: Too many authentication failures(?: for .+?)?%(__suff)s$
^Received disconnect from %(__on_port_opt)s:\s*11:
-other>
^Accepted \w+ for \S+ from (?:\s|$)
diff --git a/fail2ban/client/fail2banregex.py b/fail2ban/client/fail2banregex.py
index 513b765d..afcb4282 100644
--- a/fail2ban/client/fail2banregex.py
+++ b/fail2ban/client/fail2banregex.py
@@ -272,6 +272,8 @@ class Fail2banRegex(object):
self._filter.returnRawHost = opts.raw
self._filter.checkFindTime = False
self._filter.checkAllRegex = opts.checkAllRegex and not opts.out
+ # ignore pending (without ID/IP), added to matches if it hits later (if ID/IP can be retreved)
+ self._filter.ignorePending = opts.out;
self._backend = 'auto'
def output(self, line):
@@ -452,7 +454,6 @@ class Fail2banRegex(object):
try:
found = self._filter.processLine(line, date)
lines = []
- line = self._filter.processedLine()
ret = []
for match in found:
# Append True/False flag depending if line was matched by
@@ -488,7 +489,7 @@ class Fail2banRegex(object):
self._line_stats.matched += 1
self._line_stats.missed -= 1
if lines: # pre-lines parsed in multiline mode (buffering)
- lines.append(line)
+ lines.append(self._filter.processedLine())
line = "\n".join(lines)
return line, ret, is_ignored
@@ -523,7 +524,8 @@ class Fail2banRegex(object):
output(ret[1])
elif self._opts.out == 'msg':
for ret in ret:
- output('\n'.join(map(lambda v:''.join(v for v in v), ret[3].get('matches'))))
+ for ret in ret[3].get('matches'):
+ output(''.join(v for v in ret))
elif self._opts.out == 'row':
for ret in ret:
output('[%r,\t%r,\t%r],' % (ret[1],ret[2],dict((k,v) for k, v in ret[3].iteritems() if k != 'matches')))
diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py
index 0c44c7ac..3a100fbd 100644
--- a/fail2ban/server/filter.py
+++ b/fail2ban/server/filter.py
@@ -105,6 +105,8 @@ class Filter(JailThread):
self.returnRawHost = False
## check each regex (used for test purposes):
self.checkAllRegex = False
+ ## avoid finding of pending failures (without ID/IP, used in fail2ban-regex):
+ self.ignorePending = True
## if true ignores obsolete failures (failure time < now - findTime):
self.checkFindTime = True
## Ticks counter
@@ -651,7 +653,7 @@ class Filter(JailThread):
fail['users'] = users = set()
users.add(user)
return users
- return None
+ return users
# # ATM incremental (non-empty only) merge deactivated ...
# @staticmethod
@@ -680,25 +682,22 @@ class Filter(JailThread):
if not fail.get('nofail'):
fail['nofail'] = fail["mlfgained"]
elif fail.get('nofail'): nfflgs |= 1
- if fail.get('mlfforget'): nfflgs |= 2
+ if fail.pop('mlfforget', None): nfflgs |= 2
# if multi-line failure id (connection id) known:
if mlfidFail:
mlfidGroups = mlfidFail[1]
# update users set (hold all users of connect):
users = self._updateUsers(mlfidGroups, fail.get('user'))
# be sure we've correct current state ('nofail' and 'mlfgained' only from last failure)
- try:
- del mlfidGroups['nofail']
- del mlfidGroups['mlfgained']
- except KeyError:
- pass
+ mlfidGroups.pop('nofail', None)
+ mlfidGroups.pop('mlfgained', None)
# # ATM incremental (non-empty only) merge deactivated (for future version only),
# # it can be simulated using alternate value tags, like ...,
# # so previous value 'val' will be overwritten only if 'alt_val' is not empty...
# _updateFailure(mlfidGroups, fail)
#
# overwrite multi-line failure with all values, available in fail:
- mlfidGroups.update(fail)
+ mlfidGroups.update(((k,v) for k,v in fail.iteritems() if v is not None))
# new merged failure data:
fail = mlfidGroups
# if forget (disconnect/reset) - remove cached entry:
@@ -709,20 +708,14 @@ class Filter(JailThread):
mlfidFail = [self.__lastDate, fail]
self.mlfidCache.set(mlfid, mlfidFail)
# check users in order to avoid reset failure by multiple logon-attempts:
- if users and len(users) > 1:
+ if fail.pop('mlfpending', 0) or users and len(users) > 1:
# we've new user, reset 'nofail' because of multiple users attempts:
- try:
- del fail['nofail']
- nfflgs &= ~1 # reset nofail
- except KeyError:
- pass
+ fail.pop('nofail', None)
+ nfflgs &= ~1 # reset nofail
# merge matches:
if not (nfflgs & 1): # current nofail state (corresponding users)
- try:
- m = fail.pop("nofail-matches")
- m += fail.get("matches", [])
- except KeyError:
- m = fail.get("matches", [])
+ m = fail.pop("nofail-matches", [])
+ m += fail.get("matches", [])
if not (nfflgs & 8): # no gain signaled
m += failRegex.getMatchedTupleLines()
fail["matches"] = m
@@ -888,7 +881,8 @@ class Filter(JailThread):
if host is None:
if ll <= 7: logSys.log(7, "No failure-id by mlfid %r in regex %s: %s",
mlfid, failRegexIndex, fail.get('mlfforget', "waiting for identifier"))
- if not self.checkAllRegex: return failList
+ fail['mlfpending'] = 1; # mark failure is pending
+ if not self.checkAllRegex and self.ignorePending: 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 a5f64939..2b8e6621 100644
--- a/fail2ban/tests/files/logs/sshd
+++ b/fail2ban/tests/files/logs/sshd
@@ -134,7 +134,7 @@ Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 po
# failJSON: { "time": "2004-09-29T17:15:02", "match": true , "host": "127.0.0.1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" }
Sep 29 17:15:02 spaceman sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4
# failJSON: { "time": "2004-09-29T17:15:03", "match": true , "host": "aaaa:bbbb:cccc:1234::1:1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" }
-Sep 29 17:15:03 spaceman sshd[12946]: Failed password for user from aaaa:bbbb:cccc:1234::1:1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4
+Sep 29 17:15:03 spaceman sshd[12947]: Failed password for user from aaaa:bbbb:cccc:1234::1:1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4
# failJSON: { "time": "2004-11-11T08:04:51", "match": true , "host": "127.0.0.1", "desc": "Injecting on username ssh 'from 10.10.1.1'@localhost" }
Nov 11 08:04:51 redbamboo sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2
diff --git a/fail2ban/tests/files/logs/sshd-journal b/fail2ban/tests/files/logs/sshd-journal
index 07e34efe..ba755645 100644
--- a/fail2ban/tests/files/logs/sshd-journal
+++ b/fail2ban/tests/files/logs/sshd-journal
@@ -135,7 +135,7 @@ srv sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser
# failJSON: { "match": true , "host": "127.0.0.1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" }
srv sshd[12946]: Failed password for user from 127.0.0.1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4
# failJSON: { "match": true , "host": "aaaa:bbbb:cccc:1234::1:1", "desc": "Injecting while exhausting initially present {0,100} match length limits set for ruser etc" }
-srv sshd[12946]: Failed password for user from aaaa:bbbb:cccc:1234::1:1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4
+srv sshd[12947]: Failed password for user from aaaa:bbbb:cccc:1234::1:1 port 20000 ssh1: ruser XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX from 1.2.3.4
# failJSON: { "match": true , "host": "127.0.0.1", "desc": "Injecting on username ssh 'from 10.10.1.1'@localhost" }
srv sshd[2737]: Failed password for invalid user from 10.10.1.1 from 127.0.0.1 port 58946 ssh2