summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsebres <serg.brester@sebres.de>2016-07-11 11:28:34 +0200
committersebres <serg.brester@sebres.de>2016-07-11 11:28:34 +0200
commit4c1bcac0c7461f713dadd9346c290fd1f8742847 (patch)
treec2d4f1564963780de10063e15a1031c5baaa4ba2
parent06dcad76504f82621a11a5156279d02593538d00 (diff)
parent7582f13003879f55a5e2bbb18eff48341e33e432 (diff)
downloadfail2ban-4c1bcac0c7461f713dadd9346c290fd1f8742847.tar.gz
Merge branch '0.10' into f2b-perfom-prepare-716-cs-0.10
-rw-r--r--ChangeLog21
-rw-r--r--MANIFEST2
-rwxr-xr-xbin/fail2ban-client2
-rwxr-xr-xbin/fail2ban-regex2
-rwxr-xr-xbin/fail2ban-server2
-rwxr-xr-xbin/fail2ban-testcases2
-rw-r--r--config/action.d/badips.py11
-rw-r--r--config/action.d/firewallcmd-allports.conf28
-rw-r--r--config/action.d/firewallcmd-common.conf76
-rw-r--r--config/action.d/firewallcmd-ipset.conf22
-rw-r--r--config/action.d/firewallcmd-multiport.conf57
-rw-r--r--config/action.d/firewallcmd-new.conf29
-rw-r--r--config/action.d/firewallcmd-rich-logging.conf34
-rw-r--r--config/action.d/firewallcmd-rich-rules.conf35
-rw-r--r--config/action.d/pf.conf23
-rw-r--r--config/filter.d/asterisk.conf23
-rw-r--r--config/filter.d/common.conf10
-rw-r--r--config/filter.d/courier-smtp.conf2
-rw-r--r--config/filter.d/exim-common.conf4
-rw-r--r--config/filter.d/exim.conf14
-rwxr-xr-xconfig/filter.d/ignorecommands/apache-fakegooglebot2
-rw-r--r--config/filter.d/nsd.conf4
-rw-r--r--config/jail.conf1
-rw-r--r--fail2ban/client/csocket.py4
-rw-r--r--[-rwxr-xr-x]fail2ban/client/fail2banregex.py6
-rw-r--r--fail2ban/client/jailreader.py11
-rw-r--r--fail2ban/helpers.py6
-rw-r--r--fail2ban/server/action.py28
-rw-r--r--fail2ban/server/actions.py1
-rw-r--r--fail2ban/server/database.py29
-rw-r--r--fail2ban/server/failmanager.py26
-rw-r--r--fail2ban/server/failregex.py95
-rw-r--r--fail2ban/server/filter.py48
-rw-r--r--fail2ban/server/ipdns.py139
-rw-r--r--fail2ban/server/server.py4
-rw-r--r--fail2ban/server/ticket.py10
-rw-r--r--fail2ban/server/transmitter.py3
-rw-r--r--fail2ban/tests/action_d/test_badips.py1
-rw-r--r--fail2ban/tests/actiontestcase.py57
-rw-r--r--fail2ban/tests/clientreadertestcase.py9
-rw-r--r--fail2ban/tests/config/filter.d/zzz-generic-example.conf17
-rw-r--r--fail2ban/tests/fail2banregextestcase.py9
-rwxr-xr-xfail2ban/tests/files/config/apache-auth/digest.py2
-rwxr-xr-xfail2ban/tests/files/ignorecommand.py2
-rw-r--r--fail2ban/tests/files/logs/asterisk4
-rw-r--r--fail2ban/tests/files/logs/courier-smtp2
-rw-r--r--fail2ban/tests/files/logs/exim4
-rw-r--r--fail2ban/tests/files/logs/zzz-generic-example31
-rw-r--r--fail2ban/tests/filtertestcase.py106
-rw-r--r--fail2ban/tests/misctestcase.py18
-rw-r--r--fail2ban/tests/samplestestcase.py27
-rw-r--r--fail2ban/tests/servertestcase.py281
-rw-r--r--fail2ban/version.py2
-rwxr-xr-xfiles/gentoo-initd8
-rwxr-xr-xsetup.py2
55 files changed, 1031 insertions, 367 deletions
diff --git a/ChangeLog b/ChangeLog
index e30ea8c6..cc72ed0a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -72,14 +72,33 @@ ver. 0.9.5 (2016/XX/XXX) - wanna-be-released
- failregex of previous monit version merged as single expression.
* filter.d/postfix.conf, filter.d/postfix-sasl.conf
- extended failregex daemon part, matching also `postfix/smtps/smtpd` now (gh-1391)
+ * fixed a grave bug within tags substitutions because of incorrect detection of recursion
+ in case of multiple inline substitutions of the same tag (affected actions: `bsd-ipfw`, etc).
+ Now tracks the actual list of the already substituted tags (per tag instead of single list)
+ * filter.d/common.conf
+ - unexpected extra regex-space in generic `__prefix_line` (gh-1405)
+ - all optional spaces normalized in `common.conf`, test covered now
+ - generic `__prefix_line` extended with optional brackets for the date ambit (gh-1421),
+ added new parameter `__date_ambit`
+ * gentoo-initd fixed --pidfile bug: `--pidfile` is option of start-stop-daemon,
+ not argument of fail2ban (see gh-1434)
+ * filter.d/asterisk.conf - fix security log support for PJSIP and Asterisk 13+
- New Features:
* New Actions:
- action.d/firewallcmd-rich-rules and action.d/firewallcmd-rich-logging (gh-1367)
+
- Enhancements:
+ * Extreme speedup of all sqlite database operations (gh-1436),
+ by using of following sqlite options:
+ - (synchronous = OFF) write data through OS without syncing
+ - (journal_mode = MEMORY) use memory for the transaction logging
+ - (temp_store = MEMORY) temporary tables and indices are kept in memory
* journald journalmatch for pure-ftpd (gh-1362)
* Add additional regex filter for dovecot ldap authentication failures (gh-1370)
- * added additional regex filters for exim (gh-1371)
+ * filter.d/exim*conf
+ - added additional regexes (gh-1371)
+ - made port entry optional
ver. 0.9.4 (2016/03/08) - for-you-ladies
diff --git a/MANIFEST b/MANIFEST
index 713b64c3..cd250d3d 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -167,7 +167,6 @@ fail2ban/client/jailreader.py
fail2ban/client/jailsreader.py
fail2ban/exceptions.py
fail2ban/helpers.py
-fail2ban/ipdns.py
fail2ban/__init__.py
fail2ban/protocol.py
fail2ban/server/action.py
@@ -185,6 +184,7 @@ fail2ban/server/filter.py
fail2ban/server/filterpyinotify.py
fail2ban/server/filtersystemd.py
fail2ban/server/__init__.py
+fail2ban/server/ipdns.py
fail2ban/server/iso8601.py
fail2ban/server/jail.py
fail2ban/server/jails.py
diff --git a/bin/fail2ban-client b/bin/fail2ban-client
index f5ae7946..5e6843ed 100755
--- a/bin/fail2ban-client
+++ b/bin/fail2ban-client
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
diff --git a/bin/fail2ban-regex b/bin/fail2ban-regex
index 584c1ea7..09044f0a 100755
--- a/bin/fail2ban-regex
+++ b/bin/fail2ban-regex
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
#
diff --git a/bin/fail2ban-server b/bin/fail2ban-server
index ffafabe2..860a7607 100755
--- a/bin/fail2ban-server
+++ b/bin/fail2ban-server
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
diff --git a/bin/fail2ban-testcases b/bin/fail2ban-testcases
index 7d122039..20d444f0 100755
--- a/bin/fail2ban-testcases
+++ b/bin/fail2ban-testcases
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
"""Script to run Fail2Ban tests battery
diff --git a/config/action.d/badips.py b/config/action.d/badips.py
index 025289ca..4bc879a1 100644
--- a/config/action.d/badips.py
+++ b/config/action.d/badips.py
@@ -80,14 +80,17 @@ class BadIPsAction(ActionBase):
If invalid `category`, `score`, `banaction` or `updateperiod`.
"""
+ TIMEOUT = 10
_badips = "http://www.badips.com"
def _Request(self, url, **argv):
return Request(url, headers={'User-Agent': self.agent}, **argv)
def __init__(self, jail, name, category, score=3, age="24h", key=None,
- banaction=None, bancategory=None, bankey=None, updateperiod=900, agent="Fail2Ban"):
+ banaction=None, bancategory=None, bankey=None, updateperiod=900, agent="Fail2Ban",
+ timeout=TIMEOUT):
super(BadIPsAction, self).__init__(jail, name)
+ self.timeout = timeout
self.agent = agent
self.category = category
self.score = score
@@ -119,7 +122,7 @@ class BadIPsAction(ActionBase):
"""
try:
response = urlopen(
- self._Request("/".join([self._badips, "get", "categories"])), None, 3)
+ self._Request("/".join([self._badips, "get", "categories"])), timeout=self.timeout)
except HTTPError as response:
messages = json.loads(response.read().decode('utf-8'))
self._logSys.error(
@@ -173,7 +176,7 @@ class BadIPsAction(ActionBase):
urlencode({'age': age})])
if key:
url = "&".join([url, urlencode({'key': key})])
- response = urlopen(self._Request(url))
+ response = urlopen(self._Request(url), timeout=self.timeout)
except HTTPError as response:
messages = json.loads(response.read().decode('utf-8'))
self._logSys.error(
@@ -358,7 +361,7 @@ class BadIPsAction(ActionBase):
url = "/".join([self._badips, "add", self.category, aInfo['ip']])
if self.key:
url = "?".join([url, urlencode({'key': self.key})])
- response = urlopen(self._Request(url))
+ response = urlopen(self._Request(url), timeout=self.timeout)
except HTTPError as response:
messages = json.loads(response.read().decode('utf-8'))
self._logSys.error(
diff --git a/config/action.d/firewallcmd-allports.conf b/config/action.d/firewallcmd-allports.conf
index 571d5ba6..de0e7f91 100644
--- a/config/action.d/firewallcmd-allports.conf
+++ b/config/action.d/firewallcmd-allports.conf
@@ -6,34 +6,26 @@
[INCLUDES]
-before = iptables-common.conf
+before = firewallcmd-common.conf
[Definition]
-actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
- firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 1000 -j RETURN
- firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -j f2b-<name>
+actionstart = firewall-cmd --direct --add-chain <family> filter f2b-<name>
+ firewall-cmd --direct --add-rule <family> filter f2b-<name> 1000 -j RETURN
+ firewall-cmd --direct --add-rule <family> filter <chain> 0 -j f2b-<name>
-actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -j f2b-<name>
- firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
- firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
+actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -j f2b-<name>
+ firewall-cmd --direct --remove-rules <family> filter f2b-<name>
+ firewall-cmd --direct --remove-chain <family> filter f2b-<name>
# Example actioncheck: firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-recidive$'
-actioncheck = firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-<name>$'
+actioncheck = firewall-cmd --direct --get-chains <family> filter | sed -e 's, ,\n,g' | grep -q '^f2b-<name>$'
-actionban = firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
+actionban = firewall-cmd --direct --add-rule <family> filter f2b-<name> 0 -s <ip> -j <blocktype>
-actionunban = firewall-cmd --direct --remove-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
-
-[Init]
-
-# Default name of the chain
-#
-name = default
-
-chain = INPUT_direct
+actionunban = firewall-cmd --direct --remove-rule <family> filter f2b-<name> 0 -s <ip> -j <blocktype>
# DEV NOTES:
#
diff --git a/config/action.d/firewallcmd-common.conf b/config/action.d/firewallcmd-common.conf
new file mode 100644
index 00000000..4abe5318
--- /dev/null
+++ b/config/action.d/firewallcmd-common.conf
@@ -0,0 +1,76 @@
+# Fail2Ban configuration file
+#
+# Author: Donald Yandt
+#
+
+[Init]
+
+# Option: name
+# Notes Default name of the chain
+# Values: STRING
+name = default
+
+# Option port
+# Notes Can also use port numbers separated by a comma and in rich-rules comma and/or space.
+# Value STRING Default: 1:65535
+port = 1:65535
+
+# Option: protocol
+# Notes [ tcp | udp | icmp | all ]
+# Values: STRING Default: tcp
+protocol = tcp
+
+# Option: family(ipv4)
+# Notes specifies the socket address family type
+# Values: STRING
+family = ipv4
+
+# Option: chain
+# Notes specifies the firewalld chain to which the Fail2Ban rules should be
+# added
+# Values: STRING Default: INPUT_direct
+chain = INPUT_direct
+
+# Option: zone
+# Notes use command firewall-cmd --get-active-zones to see a list of all active zones. See firewalld man pages for more information on zones
+# Values: STRING Default: public
+zone = public
+
+# Option: service
+# Notes use command firewall-cmd --get-services to see a list of services available
+# Examples services: amanda-client amanda-k5-client bacula bacula-client dhcp dhcpv6 dhcpv6-client dns freeipa-ldap freeipa-ldaps
+# freeipa-replication ftp high-availability http https imaps ipp ipp-client ipsec iscsi-target kadmin kerberos
+# kpasswd ldap ldaps libvirt libvirt-tls mdns mosh mountd ms-wbt mysql nfs ntp openvpn pmcd pmproxy pmwebapi pmwebapis pop3s
+# postgresql privoxy proxy-dhcp puppetmaster radius rpc-bind rsyncd samba samba-client sane smtp squid ssh synergy
+# telnet tftp tftp-client tinc tor-socks transmission-client vdsm vnc-server wbem-https xmpp-bosh xmpp-client xmpp-local xmpp-server
+# Values: STRING Default: ssh
+service = ssh
+
+# Option: rejecttype (ipv4)
+# Notes See iptables/firewalld man pages for ipv4 reject types.
+# Values: STRING
+rejecttype = icmp-port-unreachable
+
+# Option: blocktype (ipv4/ipv6)
+# Notes See iptables/firewalld man pages for jump targets. Common values are REJECT,
+# REJECT --reject-with icmp-port-unreachable, DROP
+# Values: STRING
+blocktype = REJECT --reject-with <rejecttype>
+
+# Option: rich-blocktype (ipv4/ipv6)
+# Notes See firewalld man pages for jump targets. Common values are reject,
+# reject type="icmp-port-unreachable", drop
+# Values: STRING
+rich-blocktype = reject type='<rejecttype>'
+
+[Init?family=inet6]
+
+# Option: family(ipv6)
+# Notes specifies the socket address family type
+# Values: STRING
+family = ipv6
+
+# Option: rejecttype (ipv6)
+# Note: See iptables/firewalld man pages for ipv6 reject types.
+# Values: STRING
+rejecttype = icmp6-port-unreachable
diff --git a/config/action.d/firewallcmd-ipset.conf b/config/action.d/firewallcmd-ipset.conf
index 38b0f3d3..69447627 100644
--- a/config/action.d/firewallcmd-ipset.conf
+++ b/config/action.d/firewallcmd-ipset.conf
@@ -14,20 +14,20 @@
[INCLUDES]
-before = iptables-common.conf
+before = firewallcmd-common.conf
[Definition]
-actionstart = ipset create fail2ban-<name> hash:ip timeout <bantime>
- firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
+actionstart = ipset create <ipmset> hash:ip timeout <bantime>
+ firewall-cmd --direct --add-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
-actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set fail2ban-<name> src -j <blocktype>
- ipset flush fail2ban-<name>
- ipset destroy fail2ban-<name>
+actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
+ ipset flush <ipmset>
+ ipset destroy <ipmset>
-actionban = ipset add fail2ban-<name> <ip> timeout <bantime> -exist
+actionban = ipset add <ipmset> <ip> timeout <bantime> -exist
-actionunban = ipset del fail2ban-<name> <ip> -exist
+actionunban = ipset del <ipmset> <ip> -exist
[Init]
@@ -44,6 +44,12 @@ chain = INPUT_direct
bantime = 600
+ipmset = f2b-<name>
+
+[Init?family=inet6]
+
+ipmset = f2b-<name>6
+
# DEV NOTES:
#
diff --git a/config/action.d/firewallcmd-multiport.conf b/config/action.d/firewallcmd-multiport.conf
index 438d4cf7..81540e5b 100644
--- a/config/action.d/firewallcmd-multiport.conf
+++ b/config/action.d/firewallcmd-multiport.conf
@@ -5,59 +5,22 @@
[INCLUDES]
-before = iptables-common.conf
+before = firewallcmd-common.conf
[Definition]
-actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
- firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 1000 -j RETURN
- firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
+actionstart = firewall-cmd --direct --add-chain <family> filter f2b-<name>
+ firewall-cmd --direct --add-rule <family> filter f2b-<name> 1000 -j RETURN
+ firewall-cmd --direct --add-rule <family> filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
-actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
- firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
- firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
+actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -m conntrack --ctstate NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
+ firewall-cmd --direct --remove-rules <family> filter f2b-<name>
+ firewall-cmd --direct --remove-chain <family> filter f2b-<name>
# Example actioncheck: firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-apache-modsecurity$'
-actioncheck = firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-<name>$'
+actioncheck = firewall-cmd --direct --get-chains <family> filter | sed -e 's, ,\n,g' | grep -q '^f2b-<name>$'
-actionban = firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
-
-actionunban = firewall-cmd --direct --remove-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
-
-[Init]
-
-# Default name of the chain
-name = default
-
-chain = INPUT_direct
-
-# Could also use port numbers separated by a comma.
-port = 1:65535
-
-
-# Option: protocol
-# Values: [ tcp | udp | icmp | all ]
-
-protocol = tcp
-
-
-
-# DEV NOTES:
-#
-# Author: Donald Yandt
-# Uses "FirewallD" instead of the "iptables daemon".
-#
-#
-# Output:
-# actionstart:
-# $ firewall-cmd --direct --add-chain ipv4 filter f2b-apache-modsecurity
-# success
-# $ firewall-cmd --direct --add-rule ipv4 filter f2b-apache-modsecurity 1000 -j RETURN
-# success
-# $ sudo firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -m state --state NEW -p tcp -m multiport --dports 80,443 -j f2b-apache-modsecurity
-# success
-# actioncheck:
-# $ firewall-cmd --direct --get-chains ipv4 filter f2b-apache-modsecurity | sed -e 's, ,\n,g' | grep -q '^f2b-apache-modsecurity$'
-# f2b-apache-modsecurity
+actionban = firewall-cmd --direct --add-rule <family> filter f2b-<name> 0 -s <ip> -j <blocktype>
+actionunban = firewall-cmd --direct --remove-rule <family> filter f2b-<name> 0 -s <ip> -j <blocktype>
diff --git a/config/action.d/firewallcmd-new.conf b/config/action.d/firewallcmd-new.conf
index ac72a68a..e64601e1 100644
--- a/config/action.d/firewallcmd-new.conf
+++ b/config/action.d/firewallcmd-new.conf
@@ -4,32 +4,23 @@
[INCLUDES]
-before = iptables-common.conf
+before = firewallcmd-common.conf
[Definition]
-actionstart = firewall-cmd --direct --add-chain ipv4 filter f2b-<name>
- firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 1000 -j RETURN
- firewall-cmd --direct --add-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
+actionstart = firewall-cmd --direct --add-chain <family> filter f2b-<name>
+ firewall-cmd --direct --add-rule <family> filter f2b-<name> 1000 -j RETURN
+ firewall-cmd --direct --add-rule <family> filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
-actionstop = firewall-cmd --direct --remove-rule ipv4 filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
- firewall-cmd --direct --remove-rules ipv4 filter f2b-<name>
- firewall-cmd --direct --remove-chain ipv4 filter f2b-<name>
+actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -m state --state NEW -p <protocol> -m multiport --dports <port> -j f2b-<name>
+ firewall-cmd --direct --remove-rules <family> filter f2b-<name>
+ firewall-cmd --direct --remove-chain <family> filter f2b-<name>
-actioncheck = firewall-cmd --direct --get-chains ipv4 filter | grep -q 'f2b-<name>$'
+actioncheck = firewall-cmd --direct --get-chains <family> filter | grep -q 'f2b-<name>$'
-actionban = firewall-cmd --direct --add-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
+actionban = firewall-cmd --direct --add-rule <family> filter f2b-<name> 0 -s <ip> -j <blocktype>
-actionunban = firewall-cmd --direct --remove-rule ipv4 filter f2b-<name> 0 -s <ip> -j <blocktype>
-
-[Init]
-
-# Option: chain
-# Notes specifies the iptables chain to which the fail2ban rules should be
-# added
-# Values: [ STRING ]
-#
-chain = INPUT_direct
+actionunban = firewall-cmd --direct --remove-rule <family> filter f2b-<name> 0 -s <ip> -j <blocktype>
# DEV NOTES:
#
diff --git a/config/action.d/firewallcmd-rich-logging.conf b/config/action.d/firewallcmd-rich-logging.conf
index 1b88c2d9..d2c8fc2f 100644
--- a/config/action.d/firewallcmd-rich-logging.conf
+++ b/config/action.d/firewallcmd-rich-logging.conf
@@ -15,6 +15,10 @@
# firewall-cmd [--zone=<zone>] --list-all
# firewall-cmd [--zone=zone] --query-rich-rule='rule'
+[INCLUDES]
+
+before = firewallcmd-common.conf
+
[Definition]
actionstart =
@@ -26,40 +30,22 @@ actioncheck =
# you can also use zones and/or service names.
#
# zone example:
-# firewall-cmd --zone=<zone> --add-rich-rule="rule family='ipv4' source address='<ip>' port port='<port>' protocol='<protocol>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <blocktype>"
+# firewall-cmd --zone=<zone> --add-rich-rule="rule family='<family>' source address='<ip>' port port='<port>' protocol='<protocol>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <rich-blocktype>"
+#
# service name example:
-# firewall-cmd --zone=<zone> --add-rich-rule="rule family='ipv4' source address='<ip>' service name='<service>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <blocktype>"
+# firewall-cmd --zone=<zone> --add-rich-rule="rule family='<family>' source address='<ip>' service name='<service>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <rich-blocktype>"
+#
# Because rich rules can only handle single or a range of ports we must split ports and execute the command for each port. Ports can be single and ranges seperated by a comma or space for an example: http, https, 22-60, 18 smtp
-actionban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --add-rich-rule="rule family='ipv4' source address='<ip>' port port='$p' protocol='<protocol>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <blocktype>"; done
+actionban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --add-rich-rule="rule family='<family>' source address='<ip>' port port='$p' protocol='<protocol>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <rich-blocktype>"; done
-actionunban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --remove-rich-rule="rule family='ipv4' source address='<ip>' port port='$p' protocol='<protocol>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <blocktype>"; done
+actionunban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --remove-rich-rule="rule family='<family>' source address='<ip>' port port='$p' protocol='<protocol>' log prefix='f2b-<name>' level='<level>' limit value='<rate>/m' <rich-blocktype>"; done
[Init]
-name = default
-
# log levels are "emerg", "alert", "crit", "error", "warning", "notice", "info" or "debug"
level = info
# log rate per minute
rate = 1
-zone = public
-
-# use command firewall-cmd --get-services to see a list of services available
-#
-# Examples:
-#
-# amanda-client amanda-k5-client bacula bacula-client dhcp dhcpv6 dhcpv6-client dns freeipa-ldap freeipa-ldaps
-# freeipa-replication ftp high-availability http https imaps ipp ipp-client ipsec iscsi-target kadmin kerberos
-# kpasswd ldap ldaps libvirt libvirt-tls mdns mosh mountd ms-wbt mysql nfs ntp openvpn pmcd pmproxy pmwebapi pmwebapis pop3s
-# postgresql privoxy proxy-dhcp puppetmaster radius rpc-bind rsyncd samba samba-client sane smtp squid ssh synergy
-# telnet tftp tftp-client tinc tor-socks transmission-client vdsm vnc-server wbem-https xmpp-bosh xmpp-client xmpp-local xmpp-server
-
-service = ssh
-
-# reject types: 'icmp-net-unreachable', 'icmp-host-unreachable', 'icmp-port-unreachable', 'icmp-proto-unreachable',
-# 'icmp-net-prohibited', 'icmp-host-prohibited', 'icmp-admin-prohibited' or 'tcp-reset'
-
-blocktype = reject type='icmp-port-unreachable'
diff --git a/config/action.d/firewallcmd-rich-rules.conf b/config/action.d/firewallcmd-rich-rules.conf
index 4e39df54..e64c3823 100644
--- a/config/action.d/firewallcmd-rich-rules.conf
+++ b/config/action.d/firewallcmd-rich-rules.conf
@@ -13,6 +13,10 @@
# firewall-cmd [--zone=<zone>] --list-all
# firewall-cmd [--zone=zone] --query-rich-rule='rule'
+[INCLUDES]
+
+before = firewallcmd-common.conf
+
[Definition]
actionstart =
@@ -24,34 +28,15 @@ actioncheck =
#you can also use zones and/or service names.
#
# zone example:
-# firewall-cmd --zone=<zone> --add-rich-rule="rule family='ipv4' source address='<ip>' port port='<port>' protocol='<protocol>' <blocktype>"
+# firewall-cmd --zone=<zone> --add-rich-rule="rule family='ipv4' source address='<ip>' port port='<port>' protocol='<protocol>' <rich-blocktype>"
+#
# service name example:
-# firewall-cmd --zone=<zone> --add-rich-rule="rule family='ipv4' source address='<ip>' service name='<service>' <blocktype>"
+# firewall-cmd --zone=<zone> --add-rich-rule="rule family='ipv4' source address='<ip>' service name='<service>' <rich-blocktype>"
+#
# Because rich rules can only handle single or a range of ports we must split ports and execute the command for each port. Ports can be single and ranges seperated by a comma or space for an example: http, https, 22-60, 18 smtp
-actionban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --add-rich-rule="rule family='ipv4' source address='<ip>' port port='$p' protocol='<protocol>' <blocktype>"; done
+actionban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --add-rich-rule="rule family='<family>' source address='<ip>' port port='$p' protocol='<protocol>' <rich-blocktype>"; done
-actionunban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --remove-rich-rule="rule family='ipv4' source address='<ip>' port port='$p' protocol='<protocol>' <blocktype>"; done
-
-[Init]
-
-name = default
-
-zone = public
-
-# use command firewall-cmd --get-services to see a list of services available
-#
-# Examples:
-#
-# amanda-client amanda-k5-client bacula bacula-client dhcp dhcpv6 dhcpv6-client dns freeipa-ldap freeipa-ldaps
-# freeipa-replication ftp high-availability http https imaps ipp ipp-client ipsec iscsi-target kadmin kerberos
-# kpasswd ldap ldaps libvirt libvirt-tls mdns mosh mountd ms-wbt mysql nfs ntp openvpn pmcd pmproxy pmwebapi pmwebapis pop3s
-# postgresql privoxy proxy-dhcp puppetmaster radius rpc-bind rsyncd samba samba-client sane smtp squid ssh synergy
-# telnet tftp tftp-client tinc tor-socks transmission-client vdsm vnc-server wbem-https xmpp-bosh xmpp-client xmpp-local xmpp-server
-
-service = ssh
+actionunban = ports="<port>"; for p in $(echo $ports | tr ", " " "); do firewall-cmd --remove-rich-rule="rule family='<family>' source address='<ip>' port port='$p' protocol='<protocol>' <rich-blocktype>"; done
-# reject types: 'icmp-net-unreachable', 'icmp-host-unreachable', 'icmp-port-unreachable', 'icmp-proto-unreachable',
-# 'icmp-net-prohibited', 'icmp-host-prohibited', 'icmp-admin-prohibited' or 'tcp-reset'
-blocktype = reject type='icmp-port-unreachable'
diff --git a/config/action.d/pf.conf b/config/action.d/pf.conf
index effd080c..b7476fa2 100644
--- a/config/action.d/pf.conf
+++ b/config/action.d/pf.conf
@@ -16,7 +16,7 @@
# we don't enable PF automatically; to enable run pfctl -e
# or add `pf_enable="YES"` to /etc/rc.conf (tested on FreeBSD)
actionstart = echo "table <<tablename>-<name>> persist counters" | pfctl -f-
- echo "block proto <protocol> from <<tablename>-<name>> to any port <port>" | pfctl -f-
+ echo "block proto <protocol> from <<tablename>-<name>> to <actiontype>" | pfctl -f-
# Option: actionstop
@@ -71,9 +71,20 @@ tablename = f2b
#
protocol = tcp
-# Option: port
-# Notes.: the port to block, defaults to any
-# Values: [ STRING ]
-#
-port = any
+
+# Option: actiontype
+# Notes.: defines additions to the blocking rule
+# Values: leave empty to block all attempts from the host
+# Default: Value of the multiport
+actiontype = <multiport>
+
+# Option: allports
+# Notes.: default addition to block all ports
+# Usage.: use in jail config: "banaction = pf[actiontype=<allports>]"
+allports = any
+
+# Option: multiport
+# Notes.: addition to block access only to specific ports
+# Usage.: use in jail config: "banaction = pf[actiontype=<multiport>]"
+multiport = any port <port>
diff --git a/config/filter.d/asterisk.conf b/config/filter.d/asterisk.conf
index 3975fb29..f6ccdd4f 100644
--- a/config/filter.d/asterisk.conf
+++ b/config/filter.d/asterisk.conf
@@ -16,17 +16,18 @@ __pid_re = (?:\[\d+\])
iso8601 = \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+[+-]\d{4}
# All Asterisk log messages begin like this:
-log_prefix= (?:NOTICE|SECURITY)%(__pid_re)s:?(?:\[C-[\da-f]*\])? \S+:\d*( in \w+:)?
-
-failregex = ^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Registration from '[^']*' failed for '<HOST>(:\d+)?' - (Wrong password|Username/auth name mismatch|No matching peer found|Not a local domain|Device does not match ACL|Peer is not supposed to register|ACL error \(permit/deny\)|Not a local domain)$
- ^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Call from '[^']*' \(<HOST>:\d+\) to extension '[^']*' rejected because extension not found in context
- ^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Host <HOST> failed to authenticate as '[^']*'$
- ^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s No registration for peer '[^']*' \(from <HOST>\)$
- ^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Host <HOST> failed MD5 authentication for '[^']*' \([^)]+\)$
- ^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s Failed to authenticate (user|device) [^@]+@<HOST>\S*$
- ^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s hacking attempt detected '<HOST>'$
- ^(%(__prefix_line)s|\[\]\s*)%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="([\d-]+|%(iso8601)s)",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="(\d*|<unknown>)",SessionID=".+",LocalAddress="IPV[46]/(UDP|TCP|WS)/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UDP|TCP|WS)/<HOST>/\d+"(,Challenge="[\w/]+")?(,ReceivedChallenge="\w+")?(,Response="\w+",ExpectedResponse="\w*")?(,ReceivedHash="[\da-f]+")?(,ACLName="\w+")?$
- ^(%(__prefix_line)s|\[\]\s*WARNING%(__pid_re)s:?(?:\[C-[\da-f]*\])? )Ext\. s: "Rejecting unknown SIP connection from <HOST>"$
+log_prefix= (?:NOTICE|SECURITY|WARNING)%(__pid_re)s:?(?:\[C-[\da-f]*\])? [^:]+:\d*( in \w+:)?
+
+failregex = ^%(__prefix_line)s%(log_prefix)s Registration from '[^']*' failed for '<HOST>(:\d+)?' - (Wrong password|Username/auth name mismatch|No matching peer found|Not a local domain|Device does not match ACL|Peer is not supposed to register|ACL error \(permit/deny\)|Not a local domain)$
+ ^%(__prefix_line)s%(log_prefix)s Call from '[^']*' \(<HOST>:\d+\) to extension '[^']*' rejected because extension not found in context
+ ^%(__prefix_line)s%(log_prefix)s Host <HOST> failed to authenticate as '[^']*'$
+ ^%(__prefix_line)s%(log_prefix)s No registration for peer '[^']*' \(from <HOST>\)$
+ ^%(__prefix_line)s%(log_prefix)s Host <HOST> failed MD5 authentication for '[^']*' \([^)]+\)$
+ ^%(__prefix_line)s%(log_prefix)s Failed to authenticate (user|device) [^@]+@<HOST>\S*$
+ ^%(__prefix_line)s%(log_prefix)s hacking attempt detected '<HOST>'$
+ ^%(__prefix_line)s%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="([\d-]+|%(iso8601)s)",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="(\d*|<unknown>)",SessionID=".+",LocalAddress="IPV[46]/(UDP|TCP|WS)/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UDP|TCP|WS)/<HOST>/\d+"(,Challenge="[\w/]+")?(,ReceivedChallenge="\w+")?(,Response="\w+",ExpectedResponse="\w*")?(,ReceivedHash="[\da-f]+")?(,ACLName="\w+")?$
+ ^%(__prefix_line)s%(log_prefix)s "Rejecting unknown SIP connection from <HOST>"$
+ ^%(__prefix_line)s%(log_prefix)s Request from '[^']*' failed for '<HOST>(?::\d+)?' \(callid: \w*\) - No matching endpoint found$
ignoreregex =
diff --git a/config/filter.d/common.conf b/config/filter.d/common.conf
index 3e35f1d8..586f428a 100644
--- a/config/filter.d/common.conf
+++ b/config/filter.d/common.conf
@@ -26,7 +26,7 @@ __daemon_re = [\[\(]?%(_daemon)s(?:\(\S+\))?[\]\)]?:?
# extra daemon info
# EXAMPLE: [ID 800047 auth.info]
-__daemon_extra_re = (?:\[ID \d+ \S+\])
+__daemon_extra_re = \[ID \d+ \S+\]
# Combinations of daemon name and PID
# EXAMPLES: sshd[31607], pop(pam_unix)[4920]
@@ -44,14 +44,18 @@ __md5hex = (?:[\da-f]{2}:){15}[\da-f]{2}
# bsdverbose is where syslogd is started with -v or -vv and results in <4.3> or
# <auth.info> appearing before the host as per testcases/files/logs/bsd/*.
-__bsd_syslog_verbose = (<[^.]+\.[^.]+>)
+__bsd_syslog_verbose = <[^.]+\.[^.]+>
+
+__vserver = @vserver_\S+
+
+__date_ambit = (?:\[\])
# Common line prefixes (beginnings) which could be used in filters
#
# [bsdverbose]? [hostname] [vserver tag] daemon_id spaces
#
# This can be optional (for instance if we match named native log files)
-__prefix_line = \s*%(__bsd_syslog_verbose)s?\s*(?:%(__hostname)s )?(?:%(__kernel_prefix)s )?(?:@vserver_\S+ )?%(__daemon_combs_re)s?\s%(__daemon_extra_re)s?\s*
+__prefix_line = %(__date_ambit)s?\s*(?:%(__bsd_syslog_verbose)s\s+)?(?:%(__hostname)s\s+)?(?:%(__kernel_prefix)s\s+)?(?:%(__vserver)s\s+)?(?:%(__daemon_combs_re)s\s+)?(?:%(__daemon_extra_re)s\s+)?
# PAM authentication mechanism check for failures, e.g.: pam_unix, pam_sss,
# pam_ldap
diff --git a/config/filter.d/courier-smtp.conf b/config/filter.d/courier-smtp.conf
index 7df385bf..fc0afc26 100644
--- a/config/filter.d/courier-smtp.conf
+++ b/config/filter.d/courier-smtp.conf
@@ -13,7 +13,7 @@ before = common.conf
_daemon = courieresmtpd
failregex = ^%(__prefix_line)serror,relay=<HOST>,.*: 550 User (<.*> )?unknown\.?$
- ^%(__prefix_line)serror,relay=<HOST>,msg="535 Authentication failed\.",cmd:( AUTH \S+)?( [0-9a-zA-Z\+/=]+)?$
+ ^%(__prefix_line)serror,relay=<HOST>,msg="535 Authentication failed\.",cmd:( AUTH \S+)?( [0-9a-zA-Z\+/=]+)?(?: \S+)$
ignoreregex =
diff --git a/config/filter.d/exim-common.conf b/config/filter.d/exim-common.conf
index 1c0a0a20..0e1b74fa 100644
--- a/config/filter.d/exim-common.conf
+++ b/config/filter.d/exim-common.conf
@@ -9,8 +9,8 @@ after = exim-common.local
[Definition]
-host_info = H=([\w.-]+ )?(\(\S+\) )?\[<HOST>\](:\d+)? (I=\[\S+\]:\d+ )?(U=\S+ )?(P=e?smtp )?
-pid = ( \[\d+\])?
+host_info = (?:H=([\w.-]+ )?(?:\(\S+\) )?)?\[<HOST>\](?::\d+)? (?:I=\[\S+\](:\d+)? )?(?:U=\S+ )?(?:P=e?smtp )?
+pid = (?: \[\d+\])?
# DEV Notes:
# From exim source code: ./src/receive.c:add_host_info_for_log
diff --git a/config/filter.d/exim.conf b/config/filter.d/exim.conf
index 4aadf15c..a1d699c0 100644
--- a/config/filter.d/exim.conf
+++ b/config/filter.d/exim.conf
@@ -14,13 +14,13 @@ before = exim-common.conf
[Definition]
failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user|Unrouteable address|all relevant MX records point to non-existent hosts)\s*$
- ^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[<HOST>\](:\d+)?( I=\[\S+\](:\d+)?)?: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$
- ^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (relay not permitted|Sender verify failed|Unknown user)\s*$
- ^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (connection from|"\S+") %(host_info)s(next )?input=".*"\s*$
- ^%(pid)s SMTP call from \S+ \[<HOST>\](:\d+)? (I=\[\S+\](:\d+)? )?dropped: too many nonmail commands \(last was "\S+"\)\s*$
- ^%(pid)s SMTP protocol error in "AUTH \S*(| \S*)" H=(|\S* )(|\(\S*\) )\[<HOST>\]\:\d+ I=\[\S*\]\:\d+ AUTH command used when not advertised\s*$
- ^%(pid)s no MAIL in SMTP connection from (|\S* )(|\(\S*\) )\[<HOST>\]\:\d+ I=\[\S*\]\:\d+ D=\d+s(| C=\S*)\s*$
- ^%(pid)s \S+ SMTP connection from (|\S* )(|\(\S*\) )\[<HOST>\]\:\d+ I=\[\S*\]\:\d+ closed by DROP in ACL\s*$
+ ^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[<HOST>\](?::\d+)?(?: I=\[\S+\](:\d+)?)?: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$
+ ^%(pid)s %(host_info)sF=(?:<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (?:relay not permitted|Sender verify failed|Unknown user)\s*$
+ ^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (?:connection from|"\S+") %(host_info)s(?:next )?input=".*"\s*$
+ ^%(pid)s SMTP call from \S+ %(host_info)sdropped: too many nonmail commands \(last was "\S+"\)\s*$
+ ^%(pid)s SMTP protocol error in "AUTH \S*(?: \S*)?" %(host_info)sAUTH command used when not advertised\s*$
+ ^%(pid)s no MAIL in SMTP connection from (?:\S* )?(?:\(\S*\) )?%(host_info)sD=\d+s(?: C=\S*)?\s*$
+ ^%(pid)s \S+ SMTP connection from (?:\S* )?(?:\(\S*\) )?%(host_info)sclosed by DROP in ACL\s*$
ignoreregex =
diff --git a/config/filter.d/ignorecommands/apache-fakegooglebot b/config/filter.d/ignorecommands/apache-fakegooglebot
index 86a28eaa..fe4b6591 100755
--- a/config/filter.d/ignorecommands/apache-fakegooglebot
+++ b/config/filter.d/ignorecommands/apache-fakegooglebot
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# Inspired by https://isc.sans.edu/forums/diary/When+Google+isnt+Google/15968/
#
# Written in Python to reuse built-in Python batteries and not depend on
diff --git a/config/filter.d/nsd.conf b/config/filter.d/nsd.conf
index 70b41ca4..8f32f7be 100644
--- a/config/filter.d/nsd.conf
+++ b/config/filter.d/nsd.conf
@@ -22,7 +22,7 @@ _daemon = nsd
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
-failregex = ^\[\]%(__prefix_line)sinfo: ratelimit block .* query <HOST> TYPE255$
- ^\[\]%(__prefix_line)sinfo: .* <HOST> refused, no acl matches\.$
+failregex = ^%(__prefix_line)sinfo: ratelimit block .* query <HOST> TYPE255$
+ ^%(__prefix_line)sinfo: .* <HOST> refused, no acl matches\.$
ignoreregex =
diff --git a/config/jail.conf b/config/jail.conf
index 36c4eecb..a65c7a46 100644
--- a/config/jail.conf
+++ b/config/jail.conf
@@ -94,6 +94,7 @@ backend = auto
# but it will be logged as a warning.
# no: if a hostname is encountered, will not be used for banning,
# but it will be logged as info.
+# raw: use raw value (no hostname), allow use it for no-host filters/actions (example user)
usedns = warn
# "logencoding" specifies the encoding of the log files handled by the jail
diff --git a/fail2ban/client/csocket.py b/fail2ban/client/csocket.py
index 2e22e5ee..45d58cc6 100644
--- a/fail2ban/client/csocket.py
+++ b/fail2ban/client/csocket.py
@@ -44,7 +44,9 @@ class CSocket:
def send(self, msg):
# Convert every list member to string
- obj = dumps([str(m) for m in msg], HIGHEST_PROTOCOL)
+ obj = dumps(map(
+ lambda m: str(m) if not isinstance(m, (list, dict, set)) else m, msg),
+ HIGHEST_PROTOCOL)
self.__csock.send(obj + CSPROTO.END)
return self.receive(self.__csock)
diff --git a/fail2ban/client/fail2banregex.py b/fail2ban/client/fail2banregex.py
index 9ecb7229..aa55a0ff 100755..100644
--- a/fail2ban/client/fail2banregex.py
+++ b/fail2ban/client/fail2banregex.py
@@ -1,4 +1,3 @@
-#!/usr/bin/python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
#
@@ -126,6 +125,8 @@ Report bugs to https://github.com/fail2ban/fail2ban/issues
help="set custom pattern used to match date/times"),
Option("-e", "--encoding",
help="File encoding. Default: system locale"),
+ Option("-r", "--raw", action='store_true',
+ help="Raw hosts, don't resolve dns"),
Option("-L", "--maxlines", type=int, default=0,
help="maxlines for multi-line regex"),
Option("-m", "--journalmatch",
@@ -239,6 +240,7 @@ class Fail2banRegex(object):
self.encoding = opts.encoding
else:
self.encoding = locale.getpreferredencoding()
+ self.raw = True if opts.raw else False
def decode_line(self, line):
return FileContainer.decode_line('<LOG>', self.encoding, line)
@@ -342,7 +344,7 @@ class Fail2banRegex(object):
orgLineBuffer = self._filter._Filter__lineBuffer
fullBuffer = len(orgLineBuffer) >= self._filter.getMaxLines()
try:
- line, ret = self._filter.processLine(line, date, checkAllRegex=True)
+ line, ret = self._filter.processLine(line, date, checkAllRegex=True, returnRawHost=self.raw)
for match in ret:
# Append True/False flag depending if line was matched by
# more than one regex
diff --git a/fail2ban/client/jailreader.py b/fail2ban/client/jailreader.py
index f0cf293a..5f2e64b2 100644
--- a/fail2ban/client/jailreader.py
+++ b/fail2ban/client/jailreader.py
@@ -34,7 +34,7 @@ from .filterreader import FilterReader
from .actionreader import ActionReader
from ..version import version
from ..helpers import getLogger
-from ..helpers import splitcommaspace
+from ..helpers import splitwords
# Gets the instance of the logger.
logSys = getLogger(__name__)
@@ -42,9 +42,14 @@ logSys = getLogger(__name__)
class JailReader(ConfigReader):
+ # regex, to extract list of options:
optionCRE = re.compile("^((?:\w|-|_|\.)+)(?:\[(.*)\])?$")
+ # regex, to iterate over single option in option list, syntax:
+ # `action = act[p1="...", p2='...', p3=...]`, where the p3=... not contains `,` or ']'
+ # since v0.10 separator extended with `]\s*[` for support of multiple option groups, syntax
+ # `action = act[p1=...][p2=...]`
optionExtractRE = re.compile(
- r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,]*))(?:,|$)')
+ r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$)')
def __init__(self, name, force_enable=False, **kwargs):
ConfigReader.__init__(self, **kwargs)
@@ -214,7 +219,7 @@ class JailReader(ConfigReader):
elif opt == "maxretry":
stream.append(["set", self.__name, "maxretry", value])
elif opt == "ignoreip":
- for ip in splitcommaspace(value):
+ for ip in splitwords(value):
stream.append(["set", self.__name, "addignoreip", ip])
elif opt == "findtime":
stream.append(["set", self.__name, "findtime", value])
diff --git a/fail2ban/helpers.py b/fail2ban/helpers.py
index b5ffe601..7660e64a 100644
--- a/fail2ban/helpers.py
+++ b/fail2ban/helpers.py
@@ -133,15 +133,15 @@ def excepthook(exctype, value, traceback):
"Unhandled exception in Fail2Ban:", exc_info=True)
return sys.__excepthook__(exctype, value, traceback)
-def splitcommaspace(s):
- """Helper to split on any comma or space
+def splitwords(s):
+ """Helper to split words on any comma, space, or a new line
Returns empty list if input is empty (or None) and filters
out empty entries
"""
if not s:
return []
- return filter(bool, re.split('[ ,]', s))
+ return filter(bool, map(str.strip, re.split('[ ,\n]+', s)))
class BgService(object):
diff --git a/fail2ban/server/action.py b/fail2ban/server/action.py
index cdd4e28c..f579c31e 100644
--- a/fail2ban/server/action.py
+++ b/fail2ban/server/action.py
@@ -46,9 +46,13 @@ _cmd_lock = threading.Lock()
# Todo: make it configurable resp. automatically set, ex.: `[ -f /proc/net/if_inet6 ] && echo 'yes' || echo 'no'`:
allowed_ipv6 = True
+# max tag replacement count:
+MAX_TAG_REPLACE_COUNT = 10
+
# compiled RE for tag name (replacement name)
TAG_CRE = re.compile(r'<([^ <>]+)>')
+
class CallingMap(MutableMapping):
"""A Mapping type which returns the result of callable values.
@@ -229,8 +233,8 @@ class CommandAction(ActionBase):
def __setattr__(self, name, value):
if not name.startswith('_') and not callable(value):
# special case for some pasrameters:
- if name == 'timeout':
- value = MyTime.str2seconds(value)
+ if name in ('timeout', 'bantime'):
+ value = str(MyTime.str2seconds(value))
# parameters changed - clear properties and substitution cache:
self.__properties = None
self.__substCache.clear()
@@ -356,27 +360,28 @@ class CommandAction(ActionBase):
tags = inptags.copy()
t = TAG_CRE
# repeat substitution while embedded-recursive (repFlag is True)
+ done = cls._escapedTags.copy()
while True:
repFlag = False
# substitute each value:
for tag in tags.iterkeys():
- if tag in cls._escapedTags:
- # Escaped so won't match
- continue
+ # ignore escaped or already done:
+ if tag in done: continue
value = str(tags[tag])
# search and replace all tags within value, that can be interpolated using other tags:
m = t.search(value)
- done = []
+ refCounts = {}
#logSys.log(5, 'TAG: %s, value: %s' % (tag, value))
while m:
found_tag = m.group(1)
#logSys.log(5, 'found: %s' % found_tag)
- if found_tag == tag or found_tag in done:
+ if found_tag == tag or refCounts.get(found_tag, 1) > MAX_TAG_REPLACE_COUNT:
# recursive definitions are bad
#logSys.log(5, 'recursion fail tag: %s value: %s' % (tag, value) )
raise ValueError(
"properties contain self referencing definitions "
- "and cannot be resolved, fail tag: %s value: %s" % (tag, value))
+ "and cannot be resolved, fail tag: %s, found: %s in %s, value: %s" %
+ (tag, found_tag, refCounts, value))
repl = None
if found_tag not in cls._escapedTags:
repl = tags.get(found_tag + '?' + conditional)
@@ -390,7 +395,9 @@ class CommandAction(ActionBase):
continue
value = value.replace('<%s>' % found_tag, repl)
#logSys.log(5, 'value now: %s' % value)
- done.append(found_tag)
+ # increment reference count:
+ refCounts[found_tag] = refCounts.get(found_tag, 0) + 1
+ # the next match for replace:
m = t.search(value, m.start())
#logSys.log(5, 'TAG: %s, newvalue: %s' % (tag, value))
# was substituted?
@@ -399,6 +406,9 @@ class CommandAction(ActionBase):
if t.search(value):
repFlag = True
tags[tag] = value
+ # no more sub tags (and no possible composite), add this tag to done set (just to be faster):
+ if '<' not in value: done.add(tag)
+ # stop interpolation, if no replacements anymore:
if not repFlag:
break
return tags
diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py
index 1b72af91..050c12e6 100644
--- a/fail2ban/server/actions.py
+++ b/fail2ban/server/actions.py
@@ -42,7 +42,6 @@ from .banmanager import BanManager
from .jailthread import JailThread
from .action import ActionBase, CommandAction, CallingMap
from .mytime import MyTime
-from .filter import IPAddr
from .utils import Utils
from ..helpers import getLogger
diff --git a/fail2ban/server/database.py b/fail2ban/server/database.py
index 6a3d87c3..46d40051 100644
--- a/fail2ban/server/database.py
+++ b/fail2ban/server/database.py
@@ -182,8 +182,23 @@ class Fail2BanDb(object):
filename, e.args[0])
raise
+ # differentiate pypy: switch journal mode later (save it during the upgrade),
+ # to prevent errors like "database table is locked":
+ try:
+ import __pypy__
+ pypy = True
+ except ImportError:
+ pypy = False
+
cur = self._db.cursor()
- cur.execute("PRAGMA foreign_keys = ON;")
+ cur.execute("PRAGMA foreign_keys = ON")
+ # speedup: write data through OS without syncing (no wait):
+ cur.execute("PRAGMA synchronous = OFF")
+ # speedup: transaction log in memory, alternate using OFF (disable, rollback will be impossible):
+ if not pypy:
+ cur.execute("PRAGMA journal_mode = MEMORY")
+ # speedup: temporary tables and indices are kept in memory:
+ cur.execute("PRAGMA temp_store = MEMORY")
try:
cur.execute("SELECT version FROM fail2banDb LIMIT 1")
@@ -203,6 +218,9 @@ class Fail2BanDb(object):
Fail2BanDb.__version__, version, newversion)
raise RuntimeError('Failed to fully update')
finally:
+ # pypy: set journal mode after possible upgrade db:
+ if pypy:
+ cur.execute("PRAGMA journal_mode = MEMORY")
cur.close()
@property
@@ -245,13 +263,14 @@ class Fail2BanDb(object):
A timestamped backup is also created prior to attempting the update.
"""
- self._dbBackupFilename = self.filename + '.' + time.strftime('%Y%m%d-%H%M%S', MyTime.gmtime())
- shutil.copyfile(self.filename, self._dbBackupFilename)
- logSys.info("Database backup created: %s", self._dbBackupFilename)
if version > Fail2BanDb.__version__:
raise NotImplementedError(
"Attempt to travel to future version of database ...how did you get here??")
+ self._dbBackupFilename = self.filename + '.' + time.strftime('%Y%m%d-%H%M%S', MyTime.gmtime())
+ shutil.copyfile(self.filename, self._dbBackupFilename)
+ logSys.info("Database backup created: %s", self._dbBackupFilename)
+
if version < 2:
cur.executescript("BEGIN TRANSACTION;"
"CREATE TEMPORARY TABLE logs_temp AS SELECT * FROM logs;"
@@ -455,7 +474,7 @@ class Fail2BanDb(object):
queryArgs.append(MyTime.time() - bantime)
if ip is not None:
query += " AND ip=?"
- queryArgs.append(ip)
+ queryArgs.append(str(ip))
query += " ORDER BY ip, timeofban desc"
return cur.execute(query, queryArgs)
diff --git a/fail2ban/server/failmanager.py b/fail2ban/server/failmanager.py
index b342b280..ee4b049d 100644
--- a/fail2ban/server/failmanager.py
+++ b/fail2ban/server/failmanager.py
@@ -78,9 +78,9 @@ class FailManager:
def addFailure(self, ticket, count=1):
attempts = 1
with self.__lock:
- ip = ticket.getIP()
+ fid = ticket.getID()
try:
- fData = self.__failList[ip]
+ fData = self.__failList[fid]
# if the same object - the same matches but +1 attempt:
if fData is ticket:
matches = None
@@ -109,7 +109,7 @@ class FailManager:
fData = FailTicket(ticket=ticket)
if count > ticket.getAttempt():
fData.setRetry(count)
- self.__failList[ip] = fData
+ self.__failList[fid] = fData
attempts = fData.getRetry()
self.__failTotal += 1
@@ -132,7 +132,7 @@ class FailManager:
def cleanup(self, time):
with self.__lock:
- todelete = [ip for ip,item in self.__failList.iteritems() \
+ todelete = [fid for fid,item in self.__failList.iteritems() \
if item.getLastTime() + self.__maxTime <= time]
if len(todelete) == len(self.__failList):
# remove all:
@@ -142,27 +142,27 @@ class FailManager:
return
if len(todelete) / 2.0 <= len(self.__failList) / 3.0:
# few as 2/3 should be removed - remove particular items:
- for ip in todelete:
- del self.__failList[ip]
+ for fid in todelete:
+ del self.__failList[fid]
else:
# create new dictionary without items to be deleted:
- self.__failList = dict((ip,item) for ip,item in self.__failList.iteritems() \
+ self.__failList = dict((fid,item) for fid,item in self.__failList.iteritems() \
if item.getLastTime() + self.__maxTime > time)
self.__bgSvc.service()
- def delFailure(self, ip):
+ def delFailure(self, fid):
with self.__lock:
try:
- del self.__failList[ip]
+ del self.__failList[fid]
except KeyError:
pass
- def toBan(self, ip=None):
+ def toBan(self, fid=None):
with self.__lock:
- for ip in ([ip] if ip != None and ip in self.__failList else self.__failList):
- data = self.__failList[ip]
+ for fid in ([fid] if fid != None and fid in self.__failList else self.__failList):
+ data = self.__failList[fid]
if data.getRetry() >= self.__maxRetry:
- del self.__failList[ip]
+ del self.__failList[fid]
return data
self.__bgSvc.service()
raise FailManagerEmpty
diff --git a/fail2ban/server/failregex.py b/fail2ban/server/failregex.py
index 6076acc3..12b8986a 100644
--- a/fail2ban/server/failregex.py
+++ b/fail2ban/server/failregex.py
@@ -62,18 +62,39 @@ class Regex:
def __str__(self):
return "%s(%r)" % (self.__class__.__name__, self._regex)
+ ##
+ # Replaces "<HOST>", "<IP4>", "<IP6>", "<FID>" with default regular expression for host
+ #
+ # (see gh-1374 for the discussion about other candidates)
+ # @return the replaced regular expression as string
+
@staticmethod
def _resolveHostTag(regex):
- # Replace "<HOST>" with default regular expression for host:
- # Other candidates (see gh-1374 for the discussion about):
- # differentiate: r"""(?:(?:::f{4,6}:)?(?P<IPv4>(?:\d{1,3}\.){3}\d{1,3})|\[?(?P<IPv6>(?:[0-9a-fA-F]{1,4}::?|::){1,7}(?:[0-9a-fA-F]{1,4}|(?<=:):))\]?|(?P<HOST>[\w\-.^_]*\w))"""
- # expected many changes in filter, failregex, etc...
- # simple: r"""(?:::f{4,6}:)?(?P<host>[\w\-.^_:]*\w)"""
- # not good enough, if not precise expressions around <HOST>, because for example will match '1.2.3.4:23930' as ip-address;
- # Todo: move this functionality to filter reader, as default <HOST> replacement,
- # make it configurable (via jail/filter configs)
- return regex.replace("<HOST>",
- r"""(?:::f{4,6}:)?(?P<host>(?:\d{1,3}\.){3}\d{1,3}|\[?(?:[0-9a-fA-F]{1,4}::?|::){1,7}(?:[0-9a-fA-F]{1,4}\]?|(?<=:):)|[\w\-.^_]*\w)""")
+ # 3 groups instead of <HOST> - separated ipv4, ipv6 and host
+ regex = regex.replace("<HOST>",
+ r"""(?:(?:::f{4,6}:)?(?P<ip4>(?:\d{1,3}\.){3}\d{1,3})|\[?(?P<ip6>(?:[0-9a-fA-F]{1,4}::?|::){1,7}(?:[0-9a-fA-F]{1,4}|(?<=:):))\]?|(?P<dns>[\w\-.^_]*\w))""")
+ # separated ipv4:
+ r = r"""(?:::f{4,6}:)?(?P<ip4>(?:\d{1,3}\.){3}\d{1,3})"""
+ regex = regex.replace("<IP4>", r); # self closed
+ regex = regex.replace("<F-IP4/>", r); # closed
+ # separated ipv6:
+ r = r"""(?P<ip6>(?:[0-9a-fA-F]{1,4}::?|::){1,7}(?:[0-9a-fA-F]{1,4}?|(?<=:):))"""
+ regex = regex.replace("<IP6>", r); # self closed
+ regex = regex.replace("<F-IP6/>", r); # closed
+ # separated dns:
+ r = r"""(?P<dns>[\w\-.^_]*\w)"""
+ regex = regex.replace("<DNS>", r); # self closed
+ regex = regex.replace("<F-DNS/>", r); # closed
+ # default failure-id as no space tag:
+ regex = regex.replace("<F-ID/>", r"""(?P<fid>\S+)"""); # closed
+ # default failure port, like 80 or http :
+ regex = regex.replace("<F-PORT/>", r"""(?P<port>\w+)"""); # closed
+ # default failure groups (begin / end tag) for customizable expressions:
+ for o,r in (('IP4', 'ip4'), ('IP6', 'ip6'), ('DNS', 'dns'), ('ID', 'fid'), ('PORT', 'fport')):
+ regex = regex.replace("<F-%s>" % o, "(?P<%s>" % r); # open tag
+ regex = regex.replace("</F-%s>" % o, ")"); # close tag
+
+ return regex
##
# Gets the regular expression.
@@ -208,6 +229,13 @@ class RegexException(Exception):
##
+# Groups used as failure identifier.
+#
+# The order of this tuple is important while searching for failure-id
+#
+FAILURE_ID_GROPS = ("fid", "ip4", "ip6", "dns")
+
+##
# Regular expression class.
#
# This class represents a regular expression with its compiled version.
@@ -220,25 +248,48 @@ class FailRegex(Regex):
# Creates a new object. This method can throw RegexException in order to
# avoid construction of invalid object.
# @param value the regular expression
-
+
def __init__(self, regex):
# Initializes the parent.
Regex.__init__(self, regex)
- # Check for group "host"
- if "host" not in self._regexObj.groupindex:
- raise RegexException("No 'host' group in '%s'" % self._regex)
+ # Check for group "dns", "ip4", "ip6", "fid"
+ 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 the matched host.
+ # Returns all matched groups.
#
- # This corresponds to the pattern matched by the named group "host".
- # @return the matched host
+
+ def getGroups(self):
+ return self._matchCache.groupdict()
+
+ ##
+ # Returns the matched failure id.
+ #
+ # This corresponds to the pattern matched by the named group from given groups.
+ # @return the matched failure-id
- def getHost(self):
- host = self._matchCache.group("host")
- if host is None:
+ def getFailID(self, groups=FAILURE_ID_GROPS):
+ fid = None
+ for grp in groups:
+ try:
+ fid = self._matchCache.group(grp)
+ except (IndexError, KeyError):
+ continue
+ if fid is not None:
+ break
+ if fid is None:
# Gets a few information.
s = self._matchCache.string
r = self._matchCache.re
- raise RegexException("No 'host' found in '%s' using '%s'" % (s, r))
- return str(host)
+ raise RegexException("No group found in '%s' using '%s'" % (s, r))
+ return str(fid)
+
+ ##
+ # Returns the matched host.
+ #
+ # This corresponds to the pattern matched by the named group "ip4", "ip6" or "dns".
+ # @return the matched host
+
+ def getHost(self):
+ return self.getFailID(("ip4", "ip6", "dns"))
diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py
index 59d2e0d7..318d8e49 100644
--- a/fail2ban/server/filter.py
+++ b/fail2ban/server/filter.py
@@ -171,7 +171,7 @@ class Filter(JailThread):
if isinstance(value, bool):
value = {True: 'yes', False: 'no'}[value]
value = value.lower() # must be a string by now
- if not (value in ('yes', 'no', 'warn')):
+ if not (value in ('yes', 'warn', 'no', 'raw')):
logSys.error("Incorrect value %r specified for usedns. "
"Using safe 'no'" % (value,))
value = 'no'
@@ -418,6 +418,9 @@ class Filter(JailThread):
ip = element[1]
unixTime = element[2]
lines = element[3]
+ fail = {}
+ if len(element) > 4:
+ fail = element[4]
logSys.debug("Processing line with time:%s and ip:%s",
unixTime, ip)
if unixTime < MyTime.time() - self.getFindTime():
@@ -429,7 +432,7 @@ class Filter(JailThread):
logSys.info(
"[%s] Found %s - %s", self.jail.name, ip, datetime.datetime.fromtimestamp(unixTime).strftime("%Y-%m-%d %H:%M:%S")
)
- tick = FailTicket(ip, unixTime, lines)
+ tick = FailTicket(ip, unixTime, lines, data=fail)
self.failManager.addFailure(tick)
##
@@ -457,7 +460,12 @@ class Filter(JailThread):
checkAllRegex=False):
failList = list()
- # Checks if we must ignore this line.
+ cidr = IPAddr.CIDR_UNSPEC
+ if self.__useDns == "raw":
+ returnRawHost = True
+ cidr = IPAddr.CIDR_RAW
+
+ # Checks if we mut ignore this line.
if self.ignoreLine([tupleLine[::2]]) is not None:
# The ignoreregex matched. Return.
logSys.log(7, "Matched ignoreregex and was \"%s\" ignored",
@@ -518,19 +526,41 @@ class Filter(JailThread):
% ("\n".join(failRegex.getMatchedLines()), timeText))
else:
self.__lineBuffer = failRegex.getUnmatchedTupleLines()
+ # retrieve failure-id, host, etc from failure match:
+ raw = returnRawHost
try:
- host = failRegex.getHost()
- if returnRawHost:
- failList.append([failRegexIndex, IPAddr(host), date,
- failRegex.getMatchedLines()])
+ fail = failRegex.getGroups()
+ # failure-id:
+ fid = fail.get('fid')
+ # ip-address or host:
+ host = fail.get('ip4') or fail.get('ip6')
+ if host is not None:
+ raw = True
+ else:
+ host = fail.get('dns')
+ if host is None:
+ # if no failure-id also (obscure case, wrong regex), throw error inside getFailID:
+ if fid is None:
+ fid = failRegex.getFailID()
+ host = fid
+ cidr = IPAddr.CIDR_RAW
+ # if raw - add single ip or failure-id,
+ # otherwise expand host to multiple ips using dns (or ignore it if not valid):
+ if raw:
+ ip = IPAddr(host, cidr)
+ # check host equal failure-id, if not - failure with complex id:
+ if fid is not None and fid != host:
+ ip = IPAddr(fid, IPAddr.CIDR_RAW)
+ failList.append([failRegexIndex, ip, date,
+ failRegex.getMatchedLines(), fail])
if not checkAllRegex:
break
else:
ips = DNSUtils.textToIp(host, self.__useDns)
if ips:
for ip in ips:
- failList.append([failRegexIndex, ip,
- date, failRegex.getMatchedLines()])
+ failList.append([failRegexIndex, ip, date,
+ failRegex.getMatchedLines(), fail])
if not checkAllRegex:
break
except RegexException, e: # pragma: no cover - unsure if reachable
diff --git a/fail2ban/server/ipdns.py b/fail2ban/server/ipdns.py
index bca03428..cca63535 100644
--- a/fail2ban/server/ipdns.py
+++ b/fail2ban/server/ipdns.py
@@ -85,10 +85,7 @@ class DNSUtils:
return v
# retrieve name
try:
- if not isinstance(ip, IPAddr):
- v = socket.gethostbyaddr(ip)[0]
- else:
- v = socket.gethostbyaddr(ip.ntoa)[0]
+ v = socket.gethostbyaddr(ip)[0]
except socket.error, e:
logSys.debug("Unable to find a name for the IP %s: %s", ip, e)
v = None
@@ -138,18 +135,21 @@ class IPAddr(object):
# todo: make configurable the expired time and max count of cache entries:
CACHE_OBJ = Utils.Cache(maxCount=1000, maxTime=5*60)
- def __new__(cls, ipstr, cidr=-1):
+ CIDR_RAW = -2
+ CIDR_UNSPEC = -1
+
+ def __new__(cls, ipstr, cidr=CIDR_UNSPEC):
# check already cached as IPAddr
args = (ipstr, cidr)
ip = IPAddr.CACHE_OBJ.get(args)
if ip is not None:
return ip
# wrap mask to cidr (correct plen):
- if cidr == -1:
+ if cidr == IPAddr.CIDR_UNSPEC:
ipstr, cidr = IPAddr.__wrap_ipstr(ipstr)
args = (ipstr, cidr)
# check cache again:
- if cidr != -1:
+ if cidr != IPAddr.CIDR_UNSPEC:
ip = IPAddr.CACHE_OBJ.get(args)
if ip is not None:
return ip
@@ -166,17 +166,17 @@ class IPAddr(object):
ipstr = ipstr[1:-1]
# test mask:
if "/" not in ipstr:
- return ipstr, -1
+ return ipstr, IPAddr.CIDR_UNSPEC
s = ipstr.split('/', 1)
# IP address without CIDR mask
if len(s) > 2:
raise ValueError("invalid ipstr %r, too many plen representation" % (ipstr,))
- if "." in s[1]: # 255.255.255.0 style mask
+ if "." in s[1] or ":" in s[1]: # 255.255.255.0 resp. ffff:: style mask
s[1] = IPAddr.masktoplen(s[1])
s[1] = long(s[1])
return s
- def __init(self, ipstr, cidr=-1):
+ def __init(self, ipstr, cidr=CIDR_UNSPEC):
""" initialize IP object by converting IP address string
to binary to integer
"""
@@ -184,55 +184,62 @@ class IPAddr(object):
self._addr = 0
self._plen = 0
self._maskplen = None
- self._raw = ""
-
- for family in [socket.AF_INET, socket.AF_INET6]:
- try:
- binary = socket.inet_pton(family, ipstr)
- self._family = family
- break
- except socket.error:
- continue
-
- if self._family == socket.AF_INET:
- # convert host to network byte order
- self._addr, = struct.unpack("!L", binary)
- self._plen = 32
-
- # mask out host portion if prefix length is supplied
- if cidr is not None and cidr >= 0:
- mask = ~(0xFFFFFFFFL >> cidr)
- self._addr &= mask
- self._plen = cidr
-
- elif self._family == socket.AF_INET6:
- # convert host to network byte order
- hi, lo = struct.unpack("!QQ", binary)
- self._addr = (hi << 64) | lo
- self._plen = 128
-
- # mask out host portion if prefix length is supplied
- if cidr is not None and cidr >= 0:
- mask = ~(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL >> cidr)
- self._addr &= mask
- self._plen = cidr
-
- # if IPv6 address is a IPv4-compatible, make instance a IPv4
- elif self.isInNet(IPAddr.IP6_4COMPAT):
- self._addr = lo & 0xFFFFFFFFL
- self._family = socket.AF_INET
+ # always save raw value (normally used if really raw or not valid only):
+ self._raw = ipstr
+ # if not raw - recognize family, set addr, etc.:
+ if cidr != IPAddr.CIDR_RAW:
+ for family in [socket.AF_INET, socket.AF_INET6]:
+ try:
+ binary = socket.inet_pton(family, ipstr)
+ self._family = family
+ break
+ except socket.error:
+ continue
+
+ if self._family == socket.AF_INET:
+ # convert host to network byte order
+ self._addr, = struct.unpack("!L", binary)
self._plen = 32
+
+ # mask out host portion if prefix length is supplied
+ if cidr is not None and cidr >= 0:
+ mask = ~(0xFFFFFFFFL >> cidr)
+ self._addr &= mask
+ self._plen = cidr
+
+ elif self._family == socket.AF_INET6:
+ # convert host to network byte order
+ hi, lo = struct.unpack("!QQ", binary)
+ self._addr = (hi << 64) | lo
+ self._plen = 128
+
+ # mask out host portion if prefix length is supplied
+ if cidr is not None and cidr >= 0:
+ mask = ~(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL >> cidr)
+ self._addr &= mask
+ self._plen = cidr
+
+ # if IPv6 address is a IPv4-compatible, make instance a IPv4
+ elif self.isInNet(IPAddr.IP6_4COMPAT):
+ self._addr = lo & 0xFFFFFFFFL
+ self._family = socket.AF_INET
+ self._plen = 32
else:
- # string couldn't be converted neither to a IPv4 nor
- # to a IPv6 address - retain raw input for later use
- # (e.g. DNS resolution)
- self._raw = ipstr
+ self._family = IPAddr.CIDR_RAW
def __repr__(self):
return self.ntoa
def __str__(self):
return self.ntoa
+
+ def __reduce__(self):
+ """IPAddr pickle-handler, that simply wraps IPAddr to the str
+
+ Returns a string as instance to be pickled, because fail2ban-client can't
+ unserialize IPAddr objects
+ """
+ return (str, (self.ntoa,))
@property
def addr(self):
@@ -262,6 +269,8 @@ class IPAddr(object):
return self._family != socket.AF_UNSPEC
def __eq__(self, other):
+ if self._family == IPAddr.CIDR_RAW and not isinstance(other, IPAddr):
+ return self._raw == other
if not isinstance(other, IPAddr):
if other is None: return False
other = IPAddr(other)
@@ -277,6 +286,8 @@ class IPAddr(object):
return not (self == other)
def __lt__(self, other):
+ if self._family == IPAddr.CIDR_RAW and not isinstance(other, IPAddr):
+ return self._raw < other
if not isinstance(other, IPAddr):
if other is None: return False
other = IPAddr(other)
@@ -345,7 +356,7 @@ class IPAddr(object):
if not suffix:
suffix = "in-addr.arpa."
elif self.isIPv6:
- exploded_ip = self.hexdump()
+ exploded_ip = self.hexdump
if not suffix:
suffix = "ip6.arpa."
else:
@@ -384,17 +395,29 @@ class IPAddr(object):
return (self.addr & mask) == net.addr
+ # Pre-calculated map: addr to maskplen
+ def __getMaskMap():
+ m6 = (1 << 128)-1
+ m4 = (1 << 32)-1
+ mmap = {m6: 128, m4: 32, 0: 0}
+ m = 0
+ for i in xrange(0, 128):
+ m |= 1 << i
+ if i < 32:
+ mmap[m ^ m4] = 32-1-i
+ mmap[m ^ m6] = 128-1-i
+ return mmap
+
+ MAP_ADDR2MASKPLEN = __getMaskMap()
+
@property
def maskplen(self):
mplen = 0
if self._maskplen is not None:
return self._maskplen
- maddr = self._addr
- while maddr:
- if not (maddr & 0x80000000):
- raise ValueError("invalid mask %r, no plen representation" % (str(self),))
- maddr = (maddr << 1) & 0xFFFFFFFFL
- mplen += 1
+ mplen = IPAddr.MAP_ADDR2MASKPLEN.get(self._addr)
+ if mplen is None:
+ raise ValueError("invalid mask %r, no plen representation" % (str(self),))
self._maskplen = mplen
return mplen
diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py
index 9377ad5b..4a2a7c11 100644
--- a/fail2ban/server/server.py
+++ b/fail2ban/server/server.py
@@ -299,8 +299,10 @@ class Server:
flt = self.__jails[name].filter
if multiple:
for value in value:
+ logSys.debug(" failregex: %r", value)
flt.addFailRegex(value)
else:
+ logSys.debug(" failregex: %r", value)
flt.addFailRegex(value)
def delFailRegex(self, name, index):
@@ -313,8 +315,10 @@ class Server:
flt = self.__jails[name].filter
if multiple:
for value in value:
+ logSys.debug(" ignoreregex: %r", value)
flt.addIgnoreRegex(value)
else:
+ logSys.debug(" ignoreregex: %r", value)
flt.addIgnoreRegex(value)
def delIgnoreRegex(self, name, index):
diff --git a/fail2ban/server/ticket.py b/fail2ban/server/ticket.py
index 130de4f2..65ed83c3 100644
--- a/fail2ban/server/ticket.py
+++ b/fail2ban/server/ticket.py
@@ -36,7 +36,7 @@ logSys = getLogger(__name__)
class Ticket:
- def __init__(self, ip=None, time=None, matches=None, ticket=None):
+ def __init__(self, ip=None, time=None, matches=None, data={}, ticket=None):
"""Ticket constructor
@param ip the IP address
@@ -50,6 +50,7 @@ class Ticket:
self._banTime = None;
self._time = time if time is not None else MyTime.time()
self._data = {'matches': [], 'failures': 0}
+ self._data.update(data)
if ticket:
# ticket available - copy whole information from ticket:
self.__dict__.update(i for i in ticket.__dict__.iteritems() if i[0] in self.__dict__)
@@ -78,6 +79,9 @@ class Ticket:
value = IPAddr(value)
self.__ip = value
+ def getID(self):
+ return self._data.get('fid', self.__ip)
+
def getIP(self):
return self.__ip
@@ -164,12 +168,12 @@ class Ticket:
class FailTicket(Ticket):
- def __init__(self, ip=None, time=None, matches=None, ticket=None):
+ def __init__(self, ip=None, time=None, matches=None, data={}, ticket=None):
# this class variables:
self.__retry = 0
self.__lastReset = None
# create/copy using default ticket constructor:
- Ticket.__init__(self, ip, time, matches, ticket)
+ Ticket.__init__(self, ip, time, matches, data, ticket)
# init:
if ticket is None:
self.__lastReset = time if time is not None else self.getTime()
diff --git a/fail2ban/server/transmitter.py b/fail2ban/server/transmitter.py
index 2d6f0d77..e4b9d81a 100644
--- a/fail2ban/server/transmitter.py
+++ b/fail2ban/server/transmitter.py
@@ -52,7 +52,7 @@ class Transmitter:
def proceed(self, command):
# Deserialize object
- logSys.debug("Command: " + repr(command))
+ logSys.debug("Command: %r", command)
try:
ret = self.__commandHandler(command)
ack = 0, ret
@@ -265,6 +265,7 @@ class Transmitter:
action = self.__server.getAction(name, actionname)
if multiple:
for cmd in command[3]:
+ logSys.debug(" %r", cmd)
actionkey = cmd[0]
if callable(getattr(action, actionkey, None)):
actionvalue = json.loads(cmd[1]) if len(cmd)>1 else {}
diff --git a/fail2ban/tests/action_d/test_badips.py b/fail2ban/tests/action_d/test_badips.py
index 74594420..97dabada 100644
--- a/fail2ban/tests/action_d/test_badips.py
+++ b/fail2ban/tests/action_d/test_badips.py
@@ -39,6 +39,7 @@ if sys.version_info >= (2,7):
self.jail.actions.add("badips", pythonModule, initOpts={
'category': "ssh",
'banaction': "test",
+ 'timeout': (3 if unittest.F2B.fast else 30),
})
self.action = self.jail.actions["badips"]
diff --git a/fail2ban/tests/actiontestcase.py b/fail2ban/tests/actiontestcase.py
index cffabcf0..57c4856a 100644
--- a/fail2ban/tests/actiontestcase.py
+++ b/fail2ban/tests/actiontestcase.py
@@ -30,6 +30,7 @@ import time
import unittest
from ..server.action import CommandAction, CallingMap
+from ..server.actions import OrderedDict
from ..server.utils import Utils
from .utils import LogCaptureTestCase
@@ -65,6 +66,62 @@ class CommandActionTest(LogCaptureTestCase):
lambda: CommandAction.substituteRecursiveTags({'A': 'to=<B> fromip=<IP>', 'C': '<B>', 'B': '<C>', 'D': ''}))
self.assertRaises(ValueError,
lambda: CommandAction.substituteRecursiveTags({'failregex': 'to=<honeypot> fromip=<IP>', 'sweet': '<honeypot>', 'honeypot': '<sweet>', 'ignoreregex': ''}))
+ # We need here an ordered, because the sequence of iteration is very important for this test
+ if OrderedDict:
+ # No cyclic recursion, just multiple replacement of tag <T>, should be successful:
+ self.assertEqual(CommandAction.substituteRecursiveTags( OrderedDict(
+ (('X', 'x=x<T>'), ('T', '1'), ('Z', '<X> <T> <Y>'), ('Y', 'y=y<T>')))
+ ), {'X': 'x=x1', 'T': '1', 'Y': 'y=y1', 'Z': 'x=x1 1 y=y1'}
+ )
+ # No cyclic recursion, just multiple replacement of tag <T> in composite tags, should be successful:
+ self.assertEqual(CommandAction.substituteRecursiveTags( OrderedDict(
+ (('X', 'x=x<T> <Z> <<R1>> <<R2>>'), ('R1', 'Z'), ('R2', 'Y'), ('T', '1'), ('Z', '<T> <Y>'), ('Y', 'y=y<T>')))
+ ), {'X': 'x=x1 1 y=y1 1 y=y1 y=y1', 'R1': 'Z', 'R2': 'Y', 'T': '1', 'Z': '1 y=y1', 'Y': 'y=y1'}
+ )
+ # No cyclic recursion, just multiple replacement of same tags, should be successful:
+ self.assertEqual(CommandAction.substituteRecursiveTags( OrderedDict((
+ ('actionstart', 'ipset create <ipmset> hash:ip timeout <bantime> family <ipsetfamily>\n<iptables> -I <chain> <actiontype>'),
+ ('ipmset', 'f2b-<name>'),
+ ('name', 'any'),
+ ('bantime', '600'),
+ ('ipsetfamily', 'inet'),
+ ('iptables', 'iptables <lockingopt>'),
+ ('lockingopt', '-w'),
+ ('chain', 'INPUT'),
+ ('actiontype', '<multiport>'),
+ ('multiport', '-p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>'),
+ ('protocol', 'tcp'),
+ ('port', 'ssh'),
+ ('blocktype', 'REJECT',),
+ ))
+ ), OrderedDict((
+ ('actionstart', 'ipset create f2b-any hash:ip timeout 600 family inet\niptables -w -I INPUT -p tcp -m multiport --dports ssh -m set --match-set f2b-any src -j REJECT'),
+ ('ipmset', 'f2b-any'),
+ ('name', 'any'),
+ ('bantime', '600'),
+ ('ipsetfamily', 'inet'),
+ ('iptables', 'iptables -w'),
+ ('lockingopt', '-w'),
+ ('chain', 'INPUT'),
+ ('actiontype', '-p tcp -m multiport --dports ssh -m set --match-set f2b-any src -j REJECT'),
+ ('multiport', '-p tcp -m multiport --dports ssh -m set --match-set f2b-any src -j REJECT'),
+ ('protocol', 'tcp'),
+ ('port', 'ssh'),
+ ('blocktype', 'REJECT')
+ ))
+ )
+ # Cyclic recursion by composite tag creation, tags "create" another tag, that closes cycle:
+ self.assertRaises(ValueError, lambda: CommandAction.substituteRecursiveTags( OrderedDict((
+ ('A', '<<B><C>>'),
+ ('B', 'D'), ('C', 'E'),
+ ('DE', 'cycle <A>'),
+ )) ))
+ self.assertRaises(ValueError, lambda: CommandAction.substituteRecursiveTags( OrderedDict((
+ ('DE', 'cycle <A>'),
+ ('A', '<<B><C>>'),
+ ('B', 'D'), ('C', 'E'),
+ )) ))
+
# missing tags are ok
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C>'}), {'A': '<C>'})
self.assertEqual(CommandAction.substituteRecursiveTags({'A': '<C> <D> <X>','X':'fun'}), {'A': '<C> <D> fun', 'X':'fun'})
diff --git a/fail2ban/tests/clientreadertestcase.py b/fail2ban/tests/clientreadertestcase.py
index 0edbc69e..521fda2e 100644
--- a/fail2ban/tests/clientreadertestcase.py
+++ b/fail2ban/tests/clientreadertestcase.py
@@ -45,7 +45,7 @@ TEST_FILES_DIR_SHARE_CFG = {}
from .utils import CONFIG_DIR
CONFIG_DIR_SHARE_CFG = unittest.F2B.share_config
-STOCK = os.path.exists(os.path.join('config','fail2ban.conf'))
+STOCK = os.path.exists(os.path.join('config', 'fail2ban.conf'))
IMPERFECT_CONFIG = os.path.join(os.path.dirname(__file__), 'config')
IMPERFECT_CONFIG_SHARE_CFG = {}
@@ -255,6 +255,13 @@ class JailReaderTest(LogCaptureTestCase):
result = JailReader.extractOptions(option)
self.assertEqual(expected, result)
+ # And multiple groups (`][` instead of `,`)
+ result = JailReader.extractOptions(option.replace(',', ']['))
+ expected2 = (expected[0],
+ dict((k, v.replace(',', '][')) for k, v in expected[1].iteritems())
+ )
+ self.assertEqual(expected2, result)
+
def testVersionAgent(self):
jail = JailReader('blocklisttest', force_enable=True, basedir=CONFIG_DIR)
# emulate jail.read(), because such jail not exists:
diff --git a/fail2ban/tests/config/filter.d/zzz-generic-example.conf b/fail2ban/tests/config/filter.d/zzz-generic-example.conf
new file mode 100644
index 00000000..a59ccb1e
--- /dev/null
+++ b/fail2ban/tests/config/filter.d/zzz-generic-example.conf
@@ -0,0 +1,17 @@
+# Fail2Ban generic example resp. test filter
+#
+# Author: Serg G. Brester (sebres)
+#
+
+[INCLUDES]
+
+# Read common prefixes. If any customizations available -- read them from
+# common.local
+before = ../../../../config/filter.d/common.conf
+
+[Definition]
+
+_daemon = test-demo
+
+failregex = ^%(__prefix_line)sF2B: failure from <HOST>$
+ignoreregex =
diff --git a/fail2ban/tests/fail2banregextestcase.py b/fail2ban/tests/fail2banregextestcase.py
index d9f4081f..de66e185 100644
--- a/fail2ban/tests/fail2banregextestcase.py
+++ b/fail2ban/tests/fail2banregextestcase.py
@@ -120,6 +120,15 @@ class Fail2banRegexTest(LogCaptureTestCase):
self.assertLogged('Dez 31 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 193.168.0.128')
self.assertLogged('Dec 31 11:59:59 [sshd] error: PAM: Authentication failure for kevin from 87.142.124.10')
+ def testDirectRE_1raw(self):
+ (opts, args, fail2banRegex) = _Fail2banRegex(
+ "--print-all-matched", "--raw",
+ Fail2banRegexTest.FILENAME_01,
+ Fail2banRegexTest.RE_00
+ )
+ self.assertTrue(fail2banRegex.start(opts, args))
+ self.assertLogged('Lines: 19 lines, 0 ignored, 16 matched, 3 missed')
+
def testDirectRE_2(self):
(opts, args, fail2banRegex) = _Fail2banRegex(
"--print-all-matched",
diff --git a/fail2ban/tests/files/config/apache-auth/digest.py b/fail2ban/tests/files/config/apache-auth/digest.py
index 875ebffe..f4fcfcb9 100755
--- a/fail2ban/tests/files/config/apache-auth/digest.py
+++ b/fail2ban/tests/files/config/apache-auth/digest.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
import requests
try:
diff --git a/fail2ban/tests/files/ignorecommand.py b/fail2ban/tests/files/ignorecommand.py
index dd6b5aab..473980ec 100755
--- a/fail2ban/tests/files/ignorecommand.py
+++ b/fail2ban/tests/files/ignorecommand.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
import sys
if sys.argv[1] == "10.0.0.1":
exit(0)
diff --git a/fail2ban/tests/files/logs/asterisk b/fail2ban/tests/files/logs/asterisk
index aa32a290..3f49beec 100644
--- a/fail2ban/tests/files/logs/asterisk
+++ b/fail2ban/tests/files/logs/asterisk
@@ -67,3 +67,7 @@ Nov 4 18:30:40 localhost asterisk[32229]: NOTICE[32257]: chan_sip.c:23417 in han
[2016-01-28 10:34:31] NOTICE[3477][C-000003c3] chan_sip.c: Call from '' (1.2.3.4:10836) to extension '0+441772285407' rejected because extension not found in context 'default'.
# failJSON: { "time": "2016-01-28T10:34:33", "match": true , "host": "1.2.3.4" }
[2016-01-28 10:34:33] NOTICE[3477][C-000003c3] chan_sip.c: Call from '' (1.2.3.4:10836) to extension '' rejected because extension not found in context 'my-context'.
+
+# Failed authentication with pjsip on Asterisk 13+
+# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" }
+[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" <sip:1000@10.0.0.1>' failed for '1.2.3.4:48336' (callid: 276666022) - No matching endpoint found \ No newline at end of file
diff --git a/fail2ban/tests/files/logs/courier-smtp b/fail2ban/tests/files/logs/courier-smtp
index 7beaf856..ab99d322 100644
--- a/fail2ban/tests/files/logs/courier-smtp
+++ b/fail2ban/tests/files/logs/courier-smtp
@@ -10,3 +10,5 @@ Jul 6 03:42:28 whistler courieresmtpd: error,relay=::ffff:1.2.3.4,from=<>,to=<a
Nov 21 23:16:17 server courieresmtpd: error,relay=::ffff:1.2.3.4,from=<>,to=<>: 550 User unknown.
# failJSON: { "time": "2004-08-14T12:51:04", "match": true , "host": "1.2.3.4" }
Aug 14 12:51:04 HOSTNAME courieresmtpd: error,relay=::ffff:1.2.3.4,from=<firozquarl@aclunc.org>,to=<BOGUSUSER@HOSTEDDOMAIN.org>: 550 User unknown.
+# failJSON: { "time": "2004-08-14T12:51:04", "match": true , "host": "1.2.3.4" }
+Aug 14 12:51:04 mail.server courieresmtpd[26762]: error,relay=::ffff:1.2.3.4,msg="535 Authentication failed.",cmd: AUTH PLAIN AAAAABBBBCCCCWxlZA== admin
diff --git a/fail2ban/tests/files/logs/exim b/fail2ban/tests/files/logs/exim
index a3b287d4..9053bf8d 100644
--- a/fail2ban/tests/files/logs/exim
+++ b/fail2ban/tests/files/logs/exim
@@ -48,10 +48,14 @@
2016-03-18 00:34:06 [7513] SMTP protocol error in "AUTH LOGIN" H=(ylmf-pc) [45.32.34.167]:60723 I=[172.89.0.6]:587 AUTH command used when not advertised
# failJSON: { "time": "2016-03-19T18:40:44", "match": true , "host": "92.45.204.170" }
2016-03-19 18:40:44 [26221] SMTP protocol error in "AUTH LOGIN aW5mb0BtYW5iYXQub3Jn" H=([127.0.0.1]) [92.45.204.170]:14243 I=[172.89.0.6]:587 AUTH command used when not advertised
+# failJSON: { "time": "2016-05-17T06:25:27", "match": true , "host": "69.10.61.61", "desc": "from gh-1430" }
+2016-05-17 06:25:27 SMTP protocol error in "AUTH LOGIN" H=(ylmf-pc) [69.10.61.61] AUTH command used when not advertised
# failJSON: { "time": "2016-03-21T06:38:05", "match": true , "host": "49.212.207.15" }
2016-03-21 06:38:05 [5718] no MAIL in SMTP connection from www3005.sakura.ne.jp [49.212.207.15]:28890 I=[172.89.0.6]:25 D=21s C=EHLO,STARTTLS
# failJSON: { "time": "2016-03-21T06:57:36", "match": true , "host": "122.165.71.116" }
2016-03-21 06:57:36 [5908] no MAIL in SMTP connection from [122.165.71.116]:2056 I=[172.89.0.6]:25 D=10s
+# failJSON: { "time": "2016-03-21T06:57:36", "match": true , "host": "122.165.71.116" }
+2016-03-21 06:57:36 [5908] no MAIL in SMTP connection from [122.165.71.116] I=[172.89.0.6]:25 D=10s
# failJSON: { "time": "2016-03-21T04:07:49", "match": true , "host": "174.137.147.204" }
2016-03-21 04:07:49 [25874] 1ahr79-0006jK-G9 SMTP connection from (voyeur.webair.com) [174.137.147.204]:44884 I=[172.89.0.6]:25 closed by DROP in ACL
# failJSON: { "time": "2016-03-21T04:33:13", "match": true , "host": "206.214.71.53" }
diff --git a/fail2ban/tests/files/logs/zzz-generic-example b/fail2ban/tests/files/logs/zzz-generic-example
new file mode 100644
index 00000000..e3480b63
--- /dev/null
+++ b/fail2ban/tests/files/logs/zzz-generic-example
@@ -0,0 +1,31 @@
+# -- _daemon with __pid_re, without __hostname --
+# failJSON: { "time": "2005-06-21T16:47:46", "match": true , "host": "192.0.2.1" }
+Jun 21 16:47:46 machine test-demo[13709]: F2B: failure from 192.0.2.1
+# -- _daemon with __pid_re --
+# failJSON: { "time": "2005-06-21T16:47:48", "match": true , "host": "192.0.2.1" }
+Jun 21 16:47:48 test-demo[13709]: F2B: failure from 192.0.2.1
+
+# -- __kernel_prefix --
+# failJSON: { "time": "2005-06-21T16:47:50", "match": true , "host": "192.0.2.2" }
+Jun 21 16:47:50 machine kernel: [ 970.699396] F2B: failure from 192.0.2.2
+
+# -- _daemon_re with and without __pid_re --
+# failJSON: { "time": "2005-06-21T16:47:52", "match": true , "host": "192.0.2.3" }
+Jun 21 16:47:52 machine [test-demo] F2B: failure from 192.0.2.3
+# failJSON: { "time": "2005-06-21T16:47:53", "match": true , "host": "192.0.2.3" }
+Jun 21 16:47:53 machine [test-demo][13709] F2B: failure from 192.0.2.3
+# failJSON: { "time": "2005-06-21T16:50:00", "match": true , "host": "192.0.2.3" }
+Jun 21 16:50:00 machine test-demo(pam_unix) F2B: failure from 192.0.2.3
+# failJSON: { "time": "2005-06-21T16:50:02", "match": true , "host": "192.0.2.3" }
+Jun 21 16:50:02 machine test-demo(pam_unix)[13709] F2B: failure from 192.0.2.3
+
+
+# -- all common definitions together (bsdverbose hostname kernel_prefix vserver tag daemon_id space) --
+# failJSON: { "time": "2005-06-21T16:55:01", "match": true , "host": "192.0.2.3" }
+Jun 21 16:55:01 <auth.info> machine kernel: [ 970.699396] @vserver_demo test-demo(pam_unix)[13709] [ID 255 test] F2B: failure from 192.0.2.3
+# -- the same as above with additional spaces around --
+# failJSON: { "time": "2005-06-21T16:55:02", "match": true , "host": "192.0.2.3" }
+Jun 21 16:55:02 <auth.info> machine kernel: [ 970.699396] @vserver_demo test-demo(pam_unix)[13709] [ID 255 test] F2B: failure from 192.0.2.3
+# -- the same as above with brackets as date ambit --
+# failJSON: { "time": "2005-06-21T16:55:03", "match": true , "host": "192.0.2.3" }
+[Jun 21 16:55:03] <auth.info> machine kernel: [ 970.699396] @vserver_demo test-demo(pam_unix)[13709] [ID 255 test] F2B: failure from 192.0.2.3
diff --git a/fail2ban/tests/filtertestcase.py b/fail2ban/tests/filtertestcase.py
index 5e493f83..dd3468ed 100644
--- a/fail2ban/tests/filtertestcase.py
+++ b/fail2ban/tests/filtertestcase.py
@@ -1353,13 +1353,51 @@ class DNSUtilsNetworkTests(unittest.TestCase):
"""Call before every test case."""
unittest.F2B.SkipIfNoNetwork()
+ def test_IPAddr(self):
+ self.assertTrue(IPAddr('192.0.2.1').isIPv4)
+ self.assertTrue(IPAddr('2001:DB8::').isIPv6)
+
+ def test_IPAddr_Raw(self):
+ # raw string:
+ r = IPAddr('xxx', IPAddr.CIDR_RAW)
+ self.assertFalse(r.isIPv4)
+ self.assertFalse(r.isIPv6)
+ self.assertTrue(r.isValid)
+ self.assertEqual(r, 'xxx')
+ self.assertEqual('xxx', str(r))
+ self.assertNotEqual(r, IPAddr('xxx'))
+ # raw (not IP, for example host:port as string):
+ r = IPAddr('1:2', IPAddr.CIDR_RAW)
+ self.assertFalse(r.isIPv4)
+ self.assertFalse(r.isIPv6)
+ self.assertTrue(r.isValid)
+ self.assertEqual(r, '1:2')
+ self.assertEqual('1:2', str(r))
+ self.assertNotEqual(r, IPAddr('1:2'))
+ # raw vs ip4 (raw is not an ip):
+ r = IPAddr('93.184.0.1', IPAddr.CIDR_RAW)
+ ip4 = IPAddr('93.184.0.1')
+ self.assertNotEqual(ip4, r)
+ self.assertNotEqual(r, ip4)
+ self.assertTrue(r < ip4)
+ self.assertTrue(r < ip4)
+ # raw vs ip6 (raw is not an ip):
+ r = IPAddr('1::2', IPAddr.CIDR_RAW)
+ ip6 = IPAddr('1::2')
+ self.assertNotEqual(ip6, r)
+ self.assertNotEqual(r, ip6)
+ self.assertTrue(r < ip6)
+ self.assertTrue(r < ip6)
+
def testUseDns(self):
res = DNSUtils.textToIp('www.example.com', 'no')
self.assertEqual(res, [])
res = DNSUtils.textToIp('www.example.com', 'warn')
- self.assertEqual(res, ['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'])
+ # sort ipaddr, IPv4 is always smaller as IPv6
+ self.assertEqual(sorted(res), ['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'])
res = DNSUtils.textToIp('www.example.com', 'yes')
- self.assertEqual(res, ['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'])
+ # sort ipaddr, IPv4 is always smaller as IPv6
+ self.assertEqual(sorted(res), ['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'])
def testTextToIp(self):
# Test hostnames
@@ -1371,17 +1409,29 @@ class DNSUtilsNetworkTests(unittest.TestCase):
for s in hostnames:
res = DNSUtils.textToIp(s, 'yes')
if s == 'www.example.com':
- self.assertEqual(res, ['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'])
+ # sort ipaddr, IPv4 is always smaller as IPv6
+ self.assertEqual(sorted(res), ['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'])
else:
self.assertEqual(res, [])
+ # pure ips:
+ for s in ('93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'):
+ ips = DNSUtils.textToIp(s, 'yes')
+ self.assertEqual(ips, [s])
+ self.assertTrue(isinstance(ips[0], IPAddr))
def testIpToName(self):
unittest.F2B.SkipIfNoNetwork()
res = DNSUtils.ipToName('8.8.4.4')
self.assertEqual(res, 'google-public-dns-b.google.com')
+ # same as above, but with IPAddr:
+ res = DNSUtils.ipToName(IPAddr('8.8.4.4'))
+ self.assertEqual(res, 'google-public-dns-b.google.com')
# invalid ip (TEST-NET-1 according to RFC 5737)
res = DNSUtils.ipToName('192.0.2.0')
self.assertEqual(res, None)
+ # invalid ip:
+ res = DNSUtils.ipToName('192.0.2.888')
+ self.assertEqual(res, None)
def testAddr2bin(self):
res = IPAddr('10.0.0.0')
@@ -1395,11 +1445,44 @@ class DNSUtilsNetworkTests(unittest.TestCase):
res = IPAddr('10.0.0.1', cidr=31L)
self.assertEqual(res.addr, 167772160L)
+ self.assertEqual(IPAddr('10.0.0.0').hexdump, '0a000000')
+ self.assertEqual(IPAddr('1::2').hexdump, '00010000000000000000000000000002')
+ self.assertEqual(IPAddr('xxx').hexdump, '')
+
+ self.assertEqual(IPAddr('192.0.2.0').getPTR(), '0.2.0.192.in-addr.arpa.')
+ self.assertEqual(IPAddr('192.0.2.1').getPTR(), '1.2.0.192.in-addr.arpa.')
+ self.assertEqual(IPAddr('2606:2800:220:1:248:1893:25c8:1946').getPTR(),
+ '6.4.9.1.8.c.5.2.3.9.8.1.8.4.2.0.1.0.0.0.0.2.2.0.0.0.8.2.6.0.6.2.ip6.arpa.')
+
def testIPAddr_Equal6(self):
self.assertEqual(
IPAddr('2606:2800:220:1:248:1893::'),
IPAddr('2606:2800:220:1:248:1893:0:0')
)
+ # special case IPv6 in brackets:
+ self.assertEqual(
+ IPAddr('[2606:2800:220:1:248:1893::]'),
+ IPAddr('2606:2800:220:1:248:1893:0:0')
+ )
+
+ def testIPAddr_InInet(self):
+ ip4net = IPAddr('93.184.0.1/24')
+ ip6net = IPAddr('2606:2800:220:1:248:1893:25c8:0/120')
+ # ip4:
+ self.assertTrue(IPAddr('93.184.0.1').isInNet(ip4net))
+ self.assertTrue(IPAddr('93.184.0.255').isInNet(ip4net))
+ self.assertFalse(IPAddr('93.184.1.0').isInNet(ip4net))
+ self.assertFalse(IPAddr('93.184.0.1').isInNet(ip6net))
+ # ip6:
+ self.assertTrue(IPAddr('2606:2800:220:1:248:1893:25c8:1').isInNet(ip6net))
+ self.assertTrue(IPAddr('2606:2800:220:1:248:1893:25c8:ff').isInNet(ip6net))
+ self.assertFalse(IPAddr('2606:2800:220:1:248:1893:25c8:100').isInNet(ip6net))
+ self.assertFalse(IPAddr('2606:2800:220:1:248:1893:25c8:100').isInNet(ip4net))
+ # raw not in net:
+ self.assertFalse(IPAddr('93.184.0.1', IPAddr.CIDR_RAW).isInNet(ip4net))
+ self.assertFalse(IPAddr('2606:2800:220:1:248:1893:25c8:1', IPAddr.CIDR_RAW).isInNet(ip6net))
+ # invalid not in net:
+ self.assertFalse(IPAddr('xxx').isInNet(ip4net))
def testIPAddr_Compare(self):
ip4 = [
@@ -1452,11 +1535,28 @@ class DNSUtilsNetworkTests(unittest.TestCase):
self.assertEqual(IPAddr('93.184.0.1', 24).ntoa, '93.184.0.0/24')
self.assertEqual(IPAddr('192.168.1.0/255.255.255.128').ntoa, '192.168.1.0/25')
+ self.assertEqual(IPAddr('93.184.0.1/32').ntoa, '93.184.0.1')
+ self.assertEqual(IPAddr('93.184.0.1/255.255.255.255').ntoa, '93.184.0.1')
+
self.assertEqual(str(IPAddr('2606:2800:220:1:248:1893:25c8::', 120)), '2606:2800:220:1:248:1893:25c8:0/120')
self.assertEqual(IPAddr('2606:2800:220:1:248:1893:25c8::', 120).ntoa, '2606:2800:220:1:248:1893:25c8:0/120')
self.assertEqual(str(IPAddr('2606:2800:220:1:248:1893:25c8:0/120')), '2606:2800:220:1:248:1893:25c8:0/120')
self.assertEqual(IPAddr('2606:2800:220:1:248:1893:25c8:0/120').ntoa, '2606:2800:220:1:248:1893:25c8:0/120')
+ self.assertEqual(str(IPAddr('2606:28ff:220:1:248:1893:25c8::', 25)), '2606:2880::/25')
+ self.assertEqual(str(IPAddr('2606:28ff:220:1:248:1893:25c8::/ffff:ff80::')), '2606:2880::/25')
+ self.assertEqual(str(IPAddr('2606:28ff:220:1:248:1893:25c8::/ffff:ffff:ffff:ffff:ffff:ffff:ffff::')),
+ '2606:28ff:220:1:248:1893:25c8:0/112')
+
+ self.assertEqual(str(IPAddr('2606:28ff:220:1:248:1893:25c8::/128')),
+ '2606:28ff:220:1:248:1893:25c8:0')
+ self.assertEqual(str(IPAddr('2606:28ff:220:1:248:1893:25c8::/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')),
+ '2606:28ff:220:1:248:1893:25c8:0')
+
+ def testIPAddr_CIDR_Wrong(self):
+ # too many plen representations:
+ self.assertRaises(ValueError, IPAddr, '2606:28ff:220:1:248:1893:25c8::/ffff::/::1')
+
def testIPAddr_CIDR_Repr(self):
self.assertEqual(["127.0.0.0/8", "::/32", "2001:db8::/32"],
[IPAddr("127.0.0.0", 8), IPAddr("::1", 32), IPAddr("2001:db8::", 32)]
diff --git a/fail2ban/tests/misctestcase.py b/fail2ban/tests/misctestcase.py
index c9eddca1..f72d9316 100644
--- a/fail2ban/tests/misctestcase.py
+++ b/fail2ban/tests/misctestcase.py
@@ -33,7 +33,7 @@ from glob import glob
from StringIO import StringIO
from ..helpers import formatExceptionInfo, mbasename, TraceBack, FormatterWithTraceBack, getLogger
-from ..helpers import splitcommaspace
+from ..helpers import splitwords
from ..server.datetemplate import DatePatternRegex
from ..server.mytime import MyTime
@@ -57,13 +57,15 @@ class HelpersTest(unittest.TestCase):
# might be fragile due to ' vs "
self.assertEqual(args, "('Very bad', None)")
- def testsplitcommaspace(self):
- self.assertEqual(splitcommaspace(None), [])
- self.assertEqual(splitcommaspace(''), [])
- self.assertEqual(splitcommaspace(' '), [])
- self.assertEqual(splitcommaspace('1'), ['1'])
- self.assertEqual(splitcommaspace(' 1 2 '), ['1', '2'])
- self.assertEqual(splitcommaspace(' 1, 2 , '), ['1', '2'])
+ def testsplitwords(self):
+ self.assertEqual(splitwords(None), [])
+ self.assertEqual(splitwords(''), [])
+ self.assertEqual(splitwords(' '), [])
+ self.assertEqual(splitwords('1'), ['1'])
+ self.assertEqual(splitwords(' 1 2 '), ['1', '2'])
+ self.assertEqual(splitwords(' 1, 2 , '), ['1', '2'])
+ self.assertEqual(splitwords(' 1\n 2'), ['1', '2'])
+ self.assertEqual(splitwords(' 1\n 2, 3'), ['1', '2', '3'])
def _getSysPythonVersion():
diff --git a/fail2ban/tests/samplestestcase.py b/fail2ban/tests/samplestestcase.py
index 074ba24c..7af12f13 100644
--- a/fail2ban/tests/samplestestcase.py
+++ b/fail2ban/tests/samplestestcase.py
@@ -35,6 +35,7 @@ from ..server.filter import Filter
from ..client.filterreader import FilterReader
from .utils import setUpMyTime, tearDownMyTime, CONFIG_DIR
+TEST_CONFIG_DIR = os.path.join(os.path.dirname(__file__), "config")
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
@@ -60,12 +61,12 @@ class FilterSamplesRegex(unittest.TestCase):
"Expected more FilterSampleRegexs tests")
-def testSampleRegexsFactory(name):
+def testSampleRegexsFactory(name, basedir):
def testFilter(self):
# Check filter exists
filterConf = FilterReader(name, "jail", {},
- basedir=CONFIG_DIR, share_config=unittest.F2B.share_config)
+ basedir=basedir, share_config=unittest.F2B.share_config)
self.assertEqual(filterConf.getFile(), name)
self.assertEqual(filterConf.getJailName(), "jail")
filterConf.read()
@@ -126,7 +127,7 @@ def testSampleRegexsFactory(name):
(map(lambda x: x[0], ret),logFile.filename(), logFile.filelineno()))
# Verify timestamp and host as expected
- failregex, host, fail2banTime, lines = ret[0]
+ failregex, host, fail2banTime, lines, fail = ret[0]
self.assertEqual(host, faildata.get("host", None))
t = faildata.get("time", None)
@@ -155,11 +156,15 @@ def testSampleRegexsFactory(name):
return testFilter
-for filter_ in filter(lambda x: not x.endswith('common.conf') and x.endswith('.conf'),
- os.listdir(os.path.join(CONFIG_DIR, "filter.d"))):
- filterName = filter_.rpartition(".")[0]
- if not filterName.startswith('.'):
- setattr(
- FilterSamplesRegex,
- "testSampleRegexs%s" % filterName.upper(),
- testSampleRegexsFactory(filterName))
+for basedir_, filter_ in (
+ (CONFIG_DIR, lambda x: not x.endswith('common.conf') and x.endswith('.conf')),
+ (TEST_CONFIG_DIR, lambda x: x.startswith('zzz-') and x.endswith('.conf')),
+):
+ for filter_ in filter(filter_,
+ os.listdir(os.path.join(basedir_, "filter.d"))):
+ filterName = filter_.rpartition(".")[0]
+ if not filterName.startswith('.'):
+ setattr(
+ FilterSamplesRegex,
+ "testSampleRegexs%s" % filterName.upper(),
+ testSampleRegexsFactory(filterName, basedir_))
diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py
index e253ae84..9fc62acd 100644
--- a/fail2ban/tests/servertestcase.py
+++ b/fail2ban/tests/servertestcase.py
@@ -631,11 +631,11 @@ class Transmitter(TransmitterBase):
self.assertEqual(
self.transm.proceed(
["set", self.jailName, "action", action, "timeout", "10"]),
- (0, 10))
+ (0, "10"))
self.assertEqual(
self.transm.proceed(
["get", self.jailName, "action", action, "timeout"]),
- (0, 10))
+ (0, "10"))
self.assertEqual(
self.transm.proceed(["set", self.jailName, "delaction", action]),
(0, None))
@@ -837,7 +837,7 @@ class TransmitterLogging(TransmitterBase):
outCode=1,
outValue=Exception('Failed to change log target'),
repr_=True # Exceptions are not comparable apparently
- )
+ )
}[platform.system() in ('Linux',) and os.path.exists('/dev/log')]
)
@@ -926,6 +926,14 @@ class RegexTests(unittest.TestCase):
def testHost(self):
self.assertRaises(RegexException, FailRegex, '')
+ self.assertRaises(RegexException, FailRegex, '^test no group$')
+ self.assertTrue(FailRegex('^test <HOST> group$'))
+ self.assertTrue(FailRegex('^test <IP4> group$'))
+ self.assertTrue(FailRegex('^test <IP6> group$'))
+ self.assertTrue(FailRegex('^test <DNS> group$'))
+ self.assertTrue(FailRegex('^test id group: ip:port = <F-ID><IP4>(?::<F-PORT/>)?</F-ID>$'))
+ self.assertTrue(FailRegex('^test id group: user:\(<F-ID>[^\)]+</F-ID>\)$'))
+ self.assertTrue(FailRegex('^test id group: anything = <F-ID/>$'))
# Testing obscure case when host group might be missing in the matched pattern,
# e.g. if we made it optional.
fr = FailRegex('%%<HOST>?')
@@ -933,6 +941,30 @@ class RegexTests(unittest.TestCase):
fr.search([('%%',"","")])
self.assertTrue(fr.hasMatched())
self.assertRaises(RegexException, fr.getHost)
+ # The same as above but using separated IPv4/IPv6 expressions
+ fr = FailRegex('%%inet(?:=<F-IP4/>|inet6=<F-IP6/>)?')
+ self.assertFalse(fr.hasMatched())
+ fr.search([('%%inet=test',"","")])
+ self.assertTrue(fr.hasMatched())
+ self.assertRaises(RegexException, fr.getHost)
+ # Success case: using separated IPv4/IPv6 expressions (no HOST)
+ fr = FailRegex('%%(?:inet(?:=<IP4>|6=<IP6>)?|dns=<DNS>?)')
+ self.assertFalse(fr.hasMatched())
+ fr.search([('%%inet=192.0.2.1',"","")])
+ self.assertTrue(fr.hasMatched())
+ self.assertEqual(fr.getHost(), '192.0.2.1')
+ fr.search([('%%inet6=2001:DB8::',"","")])
+ self.assertTrue(fr.hasMatched())
+ self.assertEqual(fr.getHost(), '2001:DB8::')
+ fr.search([('%%dns=example.com',"","")])
+ self.assertTrue(fr.hasMatched())
+ self.assertEqual(fr.getHost(), 'example.com')
+ # Success case: using user as failure-id
+ fr = FailRegex('^test id group: user:\(<F-ID>[^\)]+</F-ID>\)$')
+ self.assertFalse(fr.hasMatched())
+ fr.search([('test id group: user:(test login name)',"","")])
+ self.assertTrue(fr.hasMatched())
+ self.assertEqual(fr.getFailID(), 'test login name')
class _BadThread(JailThread):
@@ -1005,14 +1037,43 @@ class ServerConfigReaderTests(LogCaptureTestCase):
logSys.debug(l)
return True
- def test_IPAddr(self):
- self.assertTrue(IPAddr('192.0.2.1').isIPv4)
- self.assertTrue(IPAddr('2001:DB8::').isIPv6)
+ def _testExecActions(self, server):
+ jails = server._Server__jails
+ for jail in jails:
+ # print(jail, jails[jail])
+ for a in jails[jail].actions:
+ action = jails[jail].actions[a]
+ logSys.debug('# ' + ('=' * 50))
+ logSys.debug('# == %-44s ==', jail + ' - ' + action._name)
+ logSys.debug('# ' + ('=' * 50))
+ # we can currently test only command actions:
+ if not isinstance(action, _actions.CommandAction): continue
+ # wrap default command processor, just log if (heavy)debug:
+ action.executeCmd = self._executeCmd
+ # test start :
+ logSys.debug('# === start ==='); self.pruneLog()
+ action.start()
+ # test ban ip4 :
+ logSys.debug('# === ban-ipv4 ==='); self.pruneLog()
+ action.ban({'ip': IPAddr('192.0.2.1')})
+ # test unban ip4 :
+ logSys.debug('# === unban ipv4 ==='); self.pruneLog()
+ action.unban({'ip': IPAddr('192.0.2.1')})
+ # test ban ip6 :
+ logSys.debug('# === ban ipv6 ==='); self.pruneLog()
+ action.ban({'ip': IPAddr('2001:DB8::')})
+ # test unban ip6 :
+ logSys.debug('# === unban ipv6 ==='); self.pruneLog()
+ action.unban({'ip': IPAddr('2001:DB8::')})
+ # test stop :
+ logSys.debug('# === stop ==='); self.pruneLog()
+ action.stop()
if STOCK:
def testCheckStockJailActions(self):
- jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=self.__share_cfg) # we are running tests from root project dir atm
+ # we are running tests from root project dir atm
+ jails = JailsReader(basedir=CONFIG_DIR, force_enable=True, share_config=self.__share_cfg)
self.assertTrue(jails.read()) # opens fine
self.assertTrue(jails.getOptions()) # reads fine
stream = jails.convert(allow_no_files=True)
@@ -1030,10 +1091,17 @@ class ServerConfigReaderTests(LogCaptureTestCase):
# change to the fast init backend:
if cmd[0] == 'add':
cmd[2] = 'polling'
- # change log path to test log of jail (to prevent "Permission denied" on /var/logs/ for test-user):
+ # change log path to test log of the jail
+ # (to prevent "Permission denied" on /var/logs/ for test-user):
elif len(cmd) > 3 and cmd[0] == 'set' and cmd[2] == 'addlogpath':
- cmd[3] = os.path.join(TEST_FILES_DIR, 'logs', cmd[1])
- # if fast add dummy regex to prevent too long compile of all regexp (we don't use it in this test at all):
+ fn = os.path.join(TEST_FILES_DIR, 'logs', cmd[1])
+ # fallback to testcase01 if jail has no its own test log-file
+ # (should not matter really):
+ if not os.path.exists(fn): # pragma: no cover
+ fn = os.path.join(TEST_FILES_DIR, 'testcase01.log')
+ cmd[3] = fn
+ # if fast add dummy regex to prevent too long compile of all regexp
+ # (we don't use it in this test at all):
elif unittest.F2B.fast and (
len(cmd) > 3 and cmd[0] in ('set', 'multi-set') and cmd[2] == 'addfailregex'
):
@@ -1042,13 +1110,17 @@ class ServerConfigReaderTests(LogCaptureTestCase):
# command to server, use cmdHandler direct instead of `transm.proceed(cmd)`:
try:
cmdHandler(cmd)
- except Exception, e: # pragma: no cover
+ except Exception, e: # pragma: no cover
self.fail("Command %r has failed. Received %r" % (cmd, e))
# jails = server._Server__jails
# for j in jails:
# print(j, jails[j])
+ # test default stock actions sepecified in all stock jails:
+ if not unittest.F2B.fast:
+ self._testExecActions(server)
+
def getDefaultJailStream(self, jail, act):
act = act.replace('%(__name__)s', jail)
actName, actOpt = JailReader.extractOptions(act)
@@ -1064,6 +1136,25 @@ class ServerConfigReaderTests(LogCaptureTestCase):
stream.extend(action.convert())
return stream
+ def testCheckStockAllActions(self):
+ unittest.F2B.SkipIfFast()
+ import glob
+
+ server = TestServer()
+ transm = server._Server__transm
+
+ for actCfg in glob.glob(os.path.join(CONFIG_DIR, 'action.d', '*.conf')):
+ act = os.path.basename(actCfg).replace('.conf', '')
+ # transmit artifical jail with each action to the server:
+ stream = self.getDefaultJailStream('j-'+act, act)
+ for cmd in stream:
+ # command to server:
+ ret, res = transm.proceed(cmd)
+ self.assertEqual(ret, 0)
+ # test executing action commands:
+ self._testExecActions(server)
+
+
def testCheckStockCommandActions(self):
# test cases to check valid ipv4/ipv6 action definition, tuple with (('jail', 'action[params]', 'tests', ...)
# where tests is a dictionary contains:
@@ -1073,7 +1164,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
# etc.
testJailsActions = (
# iptables-multiport --
- ('j-w-iptables-mp', 'iptables-multiport[name=%(__name__)s, bantime="600", port="http,https", protocol="tcp", chain="INPUT"]', {
+ ('j-w-iptables-mp', 'iptables-multiport[name=%(__name__)s, bantime="10m", port="http,https", protocol="tcp", chain="INPUT"]', {
'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'),
'start': (
"`iptables -w -N f2b-j-w-iptables-mp`",
@@ -1111,7 +1202,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
),
}),
# iptables-allports --
- ('j-w-iptables-ap', 'iptables-allports[name=%(__name__)s, bantime="600", protocol="tcp", chain="INPUT"]', {
+ ('j-w-iptables-ap', 'iptables-allports[name=%(__name__)s, bantime="10m", protocol="tcp", chain="INPUT"]', {
'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'),
'start': (
"`iptables -w -N f2b-j-w-iptables-ap`",
@@ -1149,7 +1240,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
),
}),
# iptables-ipset-proto6 --
- ('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, bantime="600", port="http", protocol="tcp", chain="INPUT"]', {
+ ('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain="INPUT"]', {
'ip4': (' f2b-j-w-iptables-ipset ',), 'ip6': (' f2b-j-w-iptables-ipset6 ',),
'start': (
"`ipset create f2b-j-w-iptables-ipset hash:ip timeout 600`",
@@ -1181,7 +1272,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
),
}),
# iptables-ipset-proto6-allports --
- ('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, bantime="600", chain="INPUT"]', {
+ ('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, bantime="10m", chain="INPUT"]', {
'ip4': (' f2b-j-w-iptables-ipset-ap ',), 'ip6': (' f2b-j-w-iptables-ipset-ap6 ',),
'start': (
"`ipset create f2b-j-w-iptables-ipset-ap hash:ip timeout 600`",
@@ -1213,7 +1304,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
),
}),
# iptables --
- ('j-w-iptables', 'iptables[name=%(__name__)s, bantime="600", port="http", protocol="tcp", chain="INPUT"]', {
+ ('j-w-iptables', 'iptables[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain="INPUT"]', {
'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'),
'start': (
"`iptables -w -N f2b-j-w-iptables`",
@@ -1251,7 +1342,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
),
}),
# iptables-new --
- ('j-w-iptables-new', 'iptables-new[name=%(__name__)s, bantime="600", port="http", protocol="tcp", chain="INPUT"]', {
+ ('j-w-iptables-new', 'iptables-new[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain="INPUT"]', {
'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'),
'start': (
"`iptables -w -N f2b-j-w-iptables-new`",
@@ -1289,7 +1380,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
),
}),
# iptables-xt_recent-echo --
- ('j-w-iptables-xtre', 'iptables-xt_recent-echo[name=%(__name__)s, bantime="600", chain="INPUT"]', {
+ ('j-w-iptables-xtre', 'iptables-xt_recent-echo[name=%(__name__)s, bantime="10m", chain="INPUT"]', {
'ip4': ('`iptables ', '/f2b-j-w-iptables-xtre`'), 'ip6': ('`ip6tables ', '/f2b-j-w-iptables-xtre6`'),
'start': (
"`if [ `id -u` -eq 0 ];then iptables -w -I INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre -j REJECT --reject-with icmp-port-unreachable;fi`",
@@ -1318,14 +1409,14 @@ class ServerConfigReaderTests(LogCaptureTestCase):
),
'ip6-unban': (
r"`echo -2001:db8:: > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`",
- ),
+ ),
}),
- # pf --
+ # pf default -- multiport on default port (tag <port> set in jail.conf, but not in this test case)
('j-w-pf', 'pf[name=%(__name__)s]', {
'ip4': (), 'ip6': (),
'start': (
'`echo "table <f2b-j-w-pf> persist counters" | pfctl -f-`',
- '`echo "block proto tcp from <f2b-j-w-pf> to any port any" | pfctl -f-`',
+ '`echo "block proto tcp from <f2b-j-w-pf> to any port <port>" | pfctl -f-`',
),
'stop': (
'`pfctl -sr 2>/dev/null | grep -v f2b-j-w-pf | pfctl -f-`',
@@ -1339,6 +1430,152 @@ class ServerConfigReaderTests(LogCaptureTestCase):
'ip6-ban': ("`pfctl -t f2b-j-w-pf -T add 2001:db8::`",),
'ip6-unban': ("`pfctl -t f2b-j-w-pf -T delete 2001:db8::`",),
}),
+ # pf multiport with custom port --
+ ('j-w-pf-mp', 'pf[actiontype=<multiport>][name=%(__name__)s, port=http]', {
+ 'ip4': (), 'ip6': (),
+ 'start': (
+ '`echo "table <f2b-j-w-pf-mp> persist counters" | pfctl -f-`',
+ '`echo "block proto tcp from <f2b-j-w-pf-mp> to any port http" | pfctl -f-`',
+ ),
+ 'stop': (
+ '`pfctl -sr 2>/dev/null | grep -v f2b-j-w-pf-mp | pfctl -f-`',
+ '`pfctl -t f2b-j-w-pf-mp -T flush`',
+ '`pfctl -t f2b-j-w-pf-mp -T kill`',
+ ),
+ 'ip4-check': ("`pfctl -sr | grep -q f2b-j-w-pf-mp`",),
+ 'ip6-check': ("`pfctl -sr | grep -q f2b-j-w-pf-mp`",),
+ 'ip4-ban': ("`pfctl -t f2b-j-w-pf-mp -T add 192.0.2.1`",),
+ 'ip4-unban': ("`pfctl -t f2b-j-w-pf-mp -T delete 192.0.2.1`",),
+ 'ip6-ban': ("`pfctl -t f2b-j-w-pf-mp -T add 2001:db8::`",),
+ 'ip6-unban': ("`pfctl -t f2b-j-w-pf-mp -T delete 2001:db8::`",),
+ }),
+ # pf allports --
+ ('j-w-pf-ap', 'pf[actiontype=<allports>][name=%(__name__)s]', {
+ 'ip4': (), 'ip6': (),
+ 'start': (
+ '`echo "table <f2b-j-w-pf-ap> persist counters" | pfctl -f-`',
+ '`echo "block proto tcp from <f2b-j-w-pf-ap> to any" | pfctl -f-`',
+ ),
+ 'stop': (
+ '`pfctl -sr 2>/dev/null | grep -v f2b-j-w-pf-ap | pfctl -f-`',
+ '`pfctl -t f2b-j-w-pf-ap -T flush`',
+ '`pfctl -t f2b-j-w-pf-ap -T kill`',
+ ),
+ 'ip4-check': ("`pfctl -sr | grep -q f2b-j-w-pf-ap`",),
+ 'ip6-check': ("`pfctl -sr | grep -q f2b-j-w-pf-ap`",),
+ 'ip4-ban': ("`pfctl -t f2b-j-w-pf-ap -T add 192.0.2.1`",),
+ 'ip4-unban': ("`pfctl -t f2b-j-w-pf-ap -T delete 192.0.2.1`",),
+ 'ip6-ban': ("`pfctl -t f2b-j-w-pf-ap -T add 2001:db8::`",),
+ 'ip6-unban': ("`pfctl -t f2b-j-w-pf-ap -T delete 2001:db8::`",),
+ }),
+ # firewallcmd-multiport --
+ ('j-w-fwcmd-mp', 'firewallcmd-multiport[name=%(__name__)s, bantime="10m", port="http,https", protocol="tcp", chain="INPUT"]', {
+ 'ip4': (' ipv4 ', 'icmp-port-unreachable'), 'ip6': (' ipv6 ', 'icmp6-port-unreachable'),
+ 'start': (
+ "`firewall-cmd --direct --add-chain ipv4 filter f2b-j-w-fwcmd-mp`",
+ "`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-mp 1000 -j RETURN`",
+ "`firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`",
+ "`firewall-cmd --direct --add-chain ipv6 filter f2b-j-w-fwcmd-mp`",
+ "`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-mp 1000 -j RETURN`",
+ "`firewall-cmd --direct --add-rule ipv6 filter INPUT 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`",
+ ),
+ 'stop': (
+ "`firewall-cmd --direct --remove-rule ipv4 filter INPUT 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`",
+ "`firewall-cmd --direct --remove-rules ipv4 filter f2b-j-w-fwcmd-mp`",
+ "`firewall-cmd --direct --remove-chain ipv4 filter f2b-j-w-fwcmd-mp`",
+ "`firewall-cmd --direct --remove-rule ipv6 filter INPUT 0 -m conntrack --ctstate NEW -p tcp -m multiport --dports http,https -j f2b-j-w-fwcmd-mp`",
+ "`firewall-cmd --direct --remove-rules ipv6 filter f2b-j-w-fwcmd-mp`",
+ "`firewall-cmd --direct --remove-chain ipv6 filter f2b-j-w-fwcmd-mp`",
+ ),
+ 'ip4-check': (
+ r"`firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-mp$'`",
+ ),
+ 'ip6-check': (
+ r"`firewall-cmd --direct --get-chains ipv6 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-mp$'`",
+ ),
+ 'ip4-ban': (
+ r"`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-mp 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
+ ),
+ 'ip4-unban': (
+ r"`firewall-cmd --direct --remove-rule ipv4 filter f2b-j-w-fwcmd-mp 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
+ ),
+ 'ip6-ban': (
+ r"`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-mp 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
+ ),
+ 'ip6-unban': (
+ r"`firewall-cmd --direct --remove-rule ipv6 filter f2b-j-w-fwcmd-mp 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
+ ),
+ }),
+ # firewallcmd-allports --
+ ('j-w-fwcmd-ap', 'firewallcmd-allports[name=%(__name__)s, bantime="10m", protocol="tcp", chain="INPUT"]', {
+ 'ip4': (' ipv4 ', 'icmp-port-unreachable'), 'ip6': (' ipv6 ', 'icmp6-port-unreachable'),
+ 'start': (
+ "`firewall-cmd --direct --add-chain ipv4 filter f2b-j-w-fwcmd-ap`",
+ "`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-ap 1000 -j RETURN`",
+ "`firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -j f2b-j-w-fwcmd-ap`",
+ "`firewall-cmd --direct --add-chain ipv6 filter f2b-j-w-fwcmd-ap`",
+ "`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-ap 1000 -j RETURN`",
+ "`firewall-cmd --direct --add-rule ipv6 filter INPUT 0 -j f2b-j-w-fwcmd-ap`",
+ ),
+ 'stop': (
+ "`firewall-cmd --direct --remove-rule ipv4 filter INPUT 0 -j f2b-j-w-fwcmd-ap`",
+ "`firewall-cmd --direct --remove-rules ipv4 filter f2b-j-w-fwcmd-ap`",
+ "`firewall-cmd --direct --remove-chain ipv4 filter f2b-j-w-fwcmd-ap`",
+ "`firewall-cmd --direct --remove-rule ipv6 filter INPUT 0 -j f2b-j-w-fwcmd-ap`",
+ "`firewall-cmd --direct --remove-rules ipv6 filter f2b-j-w-fwcmd-ap`",
+ "`firewall-cmd --direct --remove-chain ipv6 filter f2b-j-w-fwcmd-ap`",
+ ),
+ 'ip4-check': (
+ r"`firewall-cmd --direct --get-chains ipv4 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-ap$'`",
+ ),
+ 'ip6-check': (
+ r"`firewall-cmd --direct --get-chains ipv6 filter | sed -e 's, ,\n,g' | grep -q '^f2b-j-w-fwcmd-ap$'`",
+ ),
+ 'ip4-ban': (
+ r"`firewall-cmd --direct --add-rule ipv4 filter f2b-j-w-fwcmd-ap 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
+ ),
+ 'ip4-unban': (
+ r"`firewall-cmd --direct --remove-rule ipv4 filter f2b-j-w-fwcmd-ap 0 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
+ ),
+ 'ip6-ban': (
+ r"`firewall-cmd --direct --add-rule ipv6 filter f2b-j-w-fwcmd-ap 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
+ ),
+ 'ip6-unban': (
+ r"`firewall-cmd --direct --remove-rule ipv6 filter f2b-j-w-fwcmd-ap 0 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
+ ),
+ }),
+ # firewallcmd-ipset --
+ ('j-w-fwcmd-ipset', 'firewallcmd-ipset[name=%(__name__)s, bantime="10m", port="http", protocol="tcp", chain="INPUT"]', {
+ 'ip4': (' f2b-j-w-fwcmd-ipset ',), 'ip6': (' f2b-j-w-fwcmd-ipset6 ',),
+ 'start': (
+ "`ipset create f2b-j-w-fwcmd-ipset hash:ip timeout 600`",
+ "`firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`",
+ "`ipset create f2b-j-w-fwcmd-ipset6 hash:ip timeout 600`",
+ "`firewall-cmd --direct --add-rule ipv6 filter INPUT 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
+ ),
+ 'stop': (
+ "`firewall-cmd --direct --remove-rule ipv4 filter INPUT 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`",
+ "`ipset flush f2b-j-w-fwcmd-ipset`",
+ "`ipset destroy f2b-j-w-fwcmd-ipset`",
+ "`firewall-cmd --direct --remove-rule ipv6 filter INPUT 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
+ "`ipset flush f2b-j-w-fwcmd-ipset6`",
+ "`ipset destroy f2b-j-w-fwcmd-ipset6`",
+ ),
+ 'ip4-check': (),
+ 'ip6-check': (),
+ 'ip4-ban': (
+ r"`ipset add f2b-j-w-fwcmd-ipset 192.0.2.1 timeout 600 -exist`",
+ ),
+ 'ip4-unban': (
+ r"`ipset del f2b-j-w-fwcmd-ipset 192.0.2.1 -exist`",
+ ),
+ 'ip6-ban': (
+ r"`ipset add f2b-j-w-fwcmd-ipset6 2001:db8:: timeout 600 -exist`",
+ ),
+ 'ip6-unban': (
+ r"`ipset del f2b-j-w-fwcmd-ipset6 2001:db8:: -exist`",
+ ),
+ }),
)
server = TestServer()
transm = server._Server__transm
@@ -1350,7 +1587,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
# for cmd in stream:
# print(cmd)
- # filter all start commands (we want not start all jails):
+ # transmit jail to the server:
for cmd in stream:
# command to server:
ret, res = transm.proceed(cmd)
diff --git a/fail2ban/version.py b/fail2ban/version.py
index 140ca959..db4daa97 100644
--- a/fail2ban/version.py
+++ b/fail2ban/version.py
@@ -24,4 +24,4 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko, Steven Hiscocks, Daniel Black"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2016 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black"
__license__ = "GPL-v2+"
-version = "0.9.4.dev0"
+version = "0.10.0a1"
diff --git a/files/gentoo-initd b/files/gentoo-initd
index b7e1067b..c5b5b702 100755
--- a/files/gentoo-initd
+++ b/files/gentoo-initd
@@ -34,15 +34,15 @@ start() {
# remove stalled sock file after system crash
# bug 347477
rm -f /var/run/fail2ban/fail2ban.sock || return 1
- start-stop-daemon --start --exec ${FAIL2BAN} start \
- --pidfile /var/run/fail2ban/fail2ban.pid
+ start-stop-daemon --start --pidfile /var/run/fail2ban/fail2ban.pid \
+ -- ${FAIL2BAN} start
eend $? "Failed to start fail2ban"
}
stop() {
ebegin "Stopping fail2ban"
- start-stop-daemon --stop --exec ${FAIL2BAN} stop \
- --pidfile /var/run/fail2ban/fail2ban.pid
+ start-stop-daemon --stop --pidfile /var/run/fail2ban/fail2ban.pid \
+ -- ${FAIL2BAN} stop
eend $? "Failed to stop fail2ban"
}
diff --git a/setup.py b/setup.py
index e3c499d2..5579c981 100755
--- a/setup.py
+++ b/setup.py
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!/usr/bin/env python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :