Compare commits
29
Commits
debian
...
ipv6-support
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57820eeda2 | ||
|
|
1f2e507135 | ||
|
|
2c5ba07f60 | ||
|
|
c892d83315 | ||
|
|
b43dfc9383 | ||
|
|
42144b05a0 | ||
|
|
8f035c13ad | ||
|
|
55c0fc3ba0 | ||
|
|
4196b8d801 | ||
|
|
6e7b4a8221 | ||
|
|
fd36287354 | ||
|
|
caa4521169 | ||
|
|
1494b14b37 | ||
|
|
ce54db79bc | ||
|
|
dcfa8c5aa4 | ||
|
|
a9d691b0f5 | ||
|
|
b65a9e54a1 | ||
|
|
3d32099144 | ||
|
|
ad2235f66b | ||
|
|
3b8078d0e1 | ||
|
|
8724bbba93 | ||
|
|
4295ded9b6 | ||
|
|
13ae078797 | ||
|
|
5e9fd58c45 | ||
|
|
cedb4d5f85 | ||
|
|
5fe2912c03 | ||
|
|
674e15c851 | ||
|
|
7e689d20e7 | ||
|
|
18ae5503d3 |
@@ -78,6 +78,14 @@ ver. 0.9.4 (2016/03/08) - for-you-ladies
|
||||
* sshd filter got new failregex to match "maximum authentication
|
||||
attempts exceeded" (introduced in openssh 6.8)
|
||||
* Added filter for Mac OS screen sharing (VNC) daemon
|
||||
* IPv6 support:
|
||||
- IP addresses are now handled as objects rather than strings capable for
|
||||
handling both address types IPv4 and IPv6
|
||||
- iptables related actions have been amended to support IPv6 specific actions
|
||||
additionally
|
||||
- hostsdeny and route actions have been tested to be aware of v4 and v6 already
|
||||
- pf action for *BSD systems has been improved and supports now also v4 and v6
|
||||
- Name resolution is now working for either address type
|
||||
|
||||
- Enhancements:
|
||||
* Do not rotate empty log files
|
||||
|
||||
@@ -163,6 +163,7 @@ fail2ban/client/__init__.py
|
||||
fail2ban/client/jailreader.py
|
||||
fail2ban/client/jailsreader.py
|
||||
fail2ban/exceptions.py
|
||||
fail2ban/ipaddr.py
|
||||
fail2ban/helpers.py
|
||||
fail2ban/__init__.py
|
||||
fail2ban/protocol.py
|
||||
@@ -200,6 +201,7 @@ fail2ban/tests/actionstestcase.py
|
||||
fail2ban/tests/actiontestcase.py
|
||||
fail2ban/tests/banmanagertestcase.py
|
||||
fail2ban/tests/clientreadertestcase.py
|
||||
fail2ban/tests/clientbeautifiertestcase.py
|
||||
fail2ban/tests/config/action.d/brokenaction.conf
|
||||
fail2ban/tests/config/fail2ban.conf
|
||||
fail2ban/tests/config/filter.d/simple.conf
|
||||
|
||||
@@ -12,6 +12,7 @@ Adrien Clerc
|
||||
ache
|
||||
ag4ve (Shawn)
|
||||
Alasdair D. Campbell
|
||||
Alexander Koeppe (IPv6 support)
|
||||
Alexandre Perrin (kAworu)
|
||||
Amir Caspi
|
||||
Amy
|
||||
|
||||
@@ -32,6 +32,7 @@ else:
|
||||
from urllib import urlencode
|
||||
|
||||
from fail2ban.server.actions import ActionBase
|
||||
from fail2ban.server.filter import IPAddr
|
||||
|
||||
|
||||
class BadIPsAction(ActionBase):
|
||||
@@ -259,7 +260,7 @@ class BadIPsAction(ActionBase):
|
||||
for ip in ips:
|
||||
try:
|
||||
self._jail.actions[self.banaction].ban({
|
||||
'ip': ip,
|
||||
'ip': IPAddr(ip),
|
||||
'failures': 0,
|
||||
'matches': "",
|
||||
'ipmatches': "",
|
||||
@@ -280,7 +281,7 @@ class BadIPsAction(ActionBase):
|
||||
for ip in ips:
|
||||
try:
|
||||
self._jail.actions[self.banaction].unban({
|
||||
'ip': ip,
|
||||
'ip': IPAddr(ip),
|
||||
'failures': 0,
|
||||
'matches': "",
|
||||
'ipmatches': "",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
# Author: Cyril Jaquier
|
||||
# Modified: Yaroslav O. Halchenko <debian@onerussian.com>
|
||||
# made active on all ports from original iptables.conf
|
||||
# Modified: Alexander Koeppe <format_c@online.de>
|
||||
# make config file IPv6 capable
|
||||
#
|
||||
#
|
||||
|
||||
@@ -21,6 +23,10 @@ actionstart = <iptables> -N f2b-<name>
|
||||
<iptables> -A f2b-<name> -j <returntype>
|
||||
<iptables> -I <chain> -p <protocol> -j f2b-<name>
|
||||
|
||||
actionstart6 = <ip6tables> -N f2b-<name>
|
||||
<ip6tables> -A f2b-<name> -j <returntype>
|
||||
<ip6tables> -I <chain> -p <protocol> -j f2b-<name>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
@@ -29,11 +35,16 @@ actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
|
||||
<iptables> -F f2b-<name>
|
||||
<iptables> -X f2b-<name>
|
||||
|
||||
actionstop6 = <ip6tables> -D <chain> -p <protocol> -j f2b-<name>
|
||||
<ip6tables> -F f2b-<name>
|
||||
<ip6tables> -X f2b-<name>
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
actioncheck6 = <ip6tables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
@@ -42,6 +53,7 @@ actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
actionban6 = <ip6tables> -I f2b-<name> 1 -s <ip> -j <blocktype6>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
@@ -50,6 +62,8 @@ actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||
actionunban6 = <ip6tables> -D f2b-<name> -s <ip> -j <blocktype6>
|
||||
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
# used in all iptables based actions by default.
|
||||
#
|
||||
# The user can override the defaults in iptables-common.local
|
||||
#
|
||||
# Modified: Alexander Koeppe <format_c@online.de>
|
||||
# made config file IPv6 capable
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
@@ -43,6 +46,7 @@ protocol = tcp
|
||||
# REJECT, REJECT --reject-with icmp-port-unreachable
|
||||
# Values: STRING
|
||||
blocktype = REJECT --reject-with icmp-port-unreachable
|
||||
blocktype6 = REJECT --reject-with icmp6-port-unreachable
|
||||
|
||||
# Option: returntype
|
||||
# Note: This is the default rule on "actionstart". This should be RETURN
|
||||
@@ -62,3 +66,8 @@ lockingopt = -w
|
||||
# Notes.: Actual command to be executed, including common to all calls options
|
||||
# Values: STRING
|
||||
iptables = iptables <lockingopt>
|
||||
|
||||
# Option: ip6tables
|
||||
# Notes.: Actual command to be executed, including common to all calls options
|
||||
# Values: STRING
|
||||
ip6tables = ip6tables <lockingopt>
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#
|
||||
# If you are running on an older kernel you make need to patch in external
|
||||
# modules which probably won't be protocol version 6.
|
||||
#
|
||||
# Modified: Alexander Koeppe <format_c@online.de>
|
||||
# made config file IPv6 capable
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
@@ -26,6 +29,9 @@ before = iptables-common.conf
|
||||
actionstart = ipset create f2b-<name> hash:ip timeout <bantime>
|
||||
<iptables> -I <chain> -m set --match-set f2b-<name> src -j <blocktype>
|
||||
|
||||
actionstart6 = ipset create f2b-<name>6 hash:ip timeout <bantime> family inet6
|
||||
<ip6tables> -I <chain> -m set --match-set f2b-<name>6 src -j <blocktype6>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
@@ -34,6 +40,10 @@ actionstop = <iptables> -D <chain> -m set --match-set f2b-<name> src -j <blockty
|
||||
ipset flush f2b-<name>
|
||||
ipset destroy f2b-<name>
|
||||
|
||||
actionstop6 = <ip6tables> -D <chain> -m set --match-set f2b-<name>6 src -j <blocktype6>
|
||||
ipset flush f2b-<name>6
|
||||
ipset destroy f2b-<name>6
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
@@ -41,6 +51,7 @@ actionstop = <iptables> -D <chain> -m set --match-set f2b-<name> src -j <blockty
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
||||
actionban6 = ipset add f2b-<name>6 <ip> timeout <bantime> -exist
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
@@ -49,6 +60,7 @@ actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = ipset del f2b-<name> <ip> -exist
|
||||
actionunban6 = ipset del f2b-<name>6 <ip> -exist
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#
|
||||
# If you are running on an older kernel you make need to patch in external
|
||||
# modules.
|
||||
#
|
||||
# Modified: Alexander Koeppe <format_c@online.de>
|
||||
# made config file IPv6 capable
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
@@ -26,6 +29,9 @@ before = iptables-common.conf
|
||||
actionstart = ipset create f2b-<name> hash:ip timeout <bantime>
|
||||
<iptables> -I <chain> -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name> src -j <blocktype>
|
||||
|
||||
actionstart6 = ipset create f2b-<name>6 hash:ip timeout <bantime> family inet6
|
||||
<ip6tables> -I <chain> -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name>6 src -j <blocktype6>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
@@ -34,6 +40,10 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m
|
||||
ipset flush f2b-<name>
|
||||
ipset destroy f2b-<name>
|
||||
|
||||
actionstop6 = <ip6tables> -D <chain> -p <protocol> -m multiport --dports <port> -m set --match-set f2b-<name>6 src -j <blocktype6>
|
||||
ipset flush f2b-<name>6
|
||||
ipset destroy f2b-<name>6
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
@@ -41,6 +51,7 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -m
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
||||
actionban6 = ipset add f2b-<name>6 <ip> timeout <bantime> -exist
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
@@ -49,6 +60,7 @@ actionban = ipset add f2b-<name> <ip> timeout <bantime> -exist
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = ipset del f2b-<name> <ip> -exist
|
||||
actionunban6 = ipset del f2b-<name>6 <ip> -exist
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
# make "f2b-<name>-log" chain to log and drop
|
||||
# insert a jump to f2b-<name> from -I <chain> if proto/port match
|
||||
#
|
||||
# Modified: Alexander Koeppe <format_c@online.de>
|
||||
# made config file IPv6 capable
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
@@ -26,6 +28,13 @@ actionstart = <iptables> -N f2b-<name>
|
||||
<iptables> -I f2b-<name>-log -j LOG --log-prefix "$(expr f2b-<name> : '\(.\{1,23\}\)'):DROP " --log-level warning -m limit --limit 6/m --limit-burst 2
|
||||
<iptables> -A f2b-<name>-log -j <blocktype>
|
||||
|
||||
actionstart6 = <ip6tables> -N f2b-<name>
|
||||
<ip6tables> -A f2b-<name> -j <returntype>
|
||||
<ip6tables> -I <chain> 1 -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
<ip6tables> -N f2b-<name>-log
|
||||
<ip6tables> -I f2b-<name>-log -j LOG --log-prefix "$(expr f2b-<name> : '\(.\{1,23\}\)'):DROP " --log-level warning -m limit --limit 6/m --limit-burst 2
|
||||
<ip6tables> -A f2b-<name>-log -j <blocktype6>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
@@ -36,11 +45,18 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -j
|
||||
<iptables> -X f2b-<name>
|
||||
<iptables> -X f2b-<name>-log
|
||||
|
||||
actionstop6 = <ip6tables> -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
<ip6tables> -F f2b-<name>
|
||||
<ip6tables> -F f2b-<name>-log
|
||||
<ip6tables> -X f2b-<name>
|
||||
<ip6tables> -X f2b-<name>-log
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = <iptables> -n -L f2b-<name>-log >/dev/null
|
||||
actioncheck6 = <ip6tables> -n -L f2b-<name>-log >/dev/null
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
@@ -49,6 +65,7 @@ actioncheck = <iptables> -n -L f2b-<name>-log >/dev/null
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j f2b-<name>-log
|
||||
actionban6 = <ip6tables> -I f2b-<name> 1 -s <ip> -j f2b-<name>-log
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
@@ -57,6 +74,7 @@ actionban = <iptables> -I f2b-<name> 1 -s <ip> -j f2b-<name>-log
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = <iptables> -D f2b-<name> -s <ip> -j f2b-<name>-log
|
||||
actionunban6 = <ip6tables> -D f2b-<name> -s <ip> -j f2b-<name>-log
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#
|
||||
# Author: Cyril Jaquier
|
||||
# Modified by Yaroslav Halchenko for multiport banning
|
||||
# Modified: Alexander Koeppe <format_c@online.de>
|
||||
# made config file IPv6 capable
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
@@ -18,6 +20,10 @@ actionstart = <iptables> -N f2b-<name>
|
||||
<iptables> -A f2b-<name> -j <returntype>
|
||||
<iptables> -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
|
||||
actionstart6 = <ip6tables> -N f2b-<name>
|
||||
<ip6tables> -A f2b-<name> -j <returntype>
|
||||
<ip6tables> -I <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
@@ -26,11 +32,16 @@ actionstop = <iptables> -D <chain> -p <protocol> -m multiport --dports <port> -j
|
||||
<iptables> -F f2b-<name>
|
||||
<iptables> -X f2b-<name>
|
||||
|
||||
actionstop6 = <ip6tables> -D <chain> -p <protocol> -m multiport --dports <port> -j f2b-<name>
|
||||
<ip6tables> -F f2b-<name>
|
||||
<ip6tables> -X f2b-<name>
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
actioncheck6 = <ip6tables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
@@ -39,6 +50,7 @@ actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
actionban6 = <ip6tables> -I f2b-<name> 1 -s <ip> -j <blocktype6>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
@@ -47,6 +59,7 @@ actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||
actionunban6 = <ip6tables> -D f2b-<name> -s <ip> -j <blocktype6>
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
# Copied from iptables.conf and modified by Yaroslav Halchenko
|
||||
# to fulfill the needs of bugreporter dbts#350746.
|
||||
#
|
||||
# Modified: Alexander Koeppe <format_c@online.de>
|
||||
# made config file IPv6 capable
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
@@ -20,6 +22,10 @@ actionstart = <iptables> -N f2b-<name>
|
||||
<iptables> -A f2b-<name> -j <returntype>
|
||||
<iptables> -I <chain> -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||
|
||||
actionstart6 = <ip6tables> -N f2b-<name>
|
||||
<ip6tables> -A f2b-<name> -j <returntype>
|
||||
<ip6tables> -I <chain> -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
@@ -28,11 +34,16 @@ actionstop = <iptables> -D <chain> -m state --state NEW -p <protocol> --dport <p
|
||||
<iptables> -F f2b-<name>
|
||||
<iptables> -X f2b-<name>
|
||||
|
||||
actionstop6 = <ip6tables> -D <chain> -m state --state NEW -p <protocol> --dport <port> -j f2b-<name>
|
||||
<ip6tables> -F f2b-<name>
|
||||
<ip6tables> -X f2b-<name>
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
actioncheck6 = <ip6tables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
@@ -41,6 +52,7 @@ actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
actionban6 = <ip6tables> -I f2b-<name> 1 -s <ip> -j <blocktype6>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
@@ -49,6 +61,7 @@ actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||
actionunban6 = <ip6tables> -D f2b-<name> -s <ip> -j <blocktype6>
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#
|
||||
# Author: Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
|
||||
#
|
||||
# Modified: Alexander Koeppe <format_c@online.de>
|
||||
# made config file IPv6 capable
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
@@ -33,6 +35,7 @@ before = iptables-common.conf
|
||||
# safeguard in case the fail2ban process dies unexpectedly. The
|
||||
# shorter of the two timeouts actually matters.
|
||||
actionstart = if [ `id -u` -eq 0 ];then <iptables> -I <chain> -m recent --update --seconds 3600 --name f2b-<name> -j <blocktype>;fi
|
||||
actionstart6 = if [ `id -u` -eq 0 ];then <ip6tables> -I <chain> -m recent --update --seconds 3600 --name f2b-<name>6 -j <blocktype6>;fi
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
@@ -40,12 +43,15 @@ actionstart = if [ `id -u` -eq 0 ];then <iptables> -I <chain> -m recent --update
|
||||
#
|
||||
actionstop = echo / > /proc/net/xt_recent/f2b-<name>
|
||||
if [ `id -u` -eq 0 ];then <iptables> -D <chain> -m recent --update --seconds 3600 --name f2b-<name> -j <blocktype>;fi
|
||||
actionstop6 = echo / > /proc/net/xt_recent/f2b-<name>6
|
||||
if [ `id -u` -eq 0 ];then <ip6tables> -D <chain> -m recent --update --seconds 3600 --name f2b-<name>6 -j <blocktype6>;fi
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = test -e /proc/net/xt_recent/f2b-<name>
|
||||
actioncheck6 = test -e /proc/net/xt_recent/f2b-<name>6
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
@@ -54,6 +60,7 @@ actioncheck = test -e /proc/net/xt_recent/f2b-<name>
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = echo +<ip> > /proc/net/xt_recent/f2b-<name>
|
||||
actionban6 = echo +<ip> > /proc/net/xt_recent/f2b-<name>6
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
@@ -62,6 +69,7 @@ actionban = echo +<ip> > /proc/net/xt_recent/f2b-<name>
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = echo -<ip> > /proc/net/xt_recent/f2b-<name>
|
||||
actionunban6 = echo -<ip> > /proc/net/xt_recent/f2b-<name>6
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#
|
||||
# Author: Cyril Jaquier
|
||||
#
|
||||
# Modified: Alexander Koeppe <format_c@online.de>
|
||||
# made config file IPv6 capable
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
@@ -18,6 +20,10 @@ actionstart = <iptables> -N f2b-<name>
|
||||
<iptables> -A f2b-<name> -j <returntype>
|
||||
<iptables> -I <chain> -p <protocol> --dport <port> -j f2b-<name>
|
||||
|
||||
actionstart6 = <ip6tables> -N f2b-<name>
|
||||
<ip6tables> -A f2b-<name> -j <returntype>
|
||||
<ip6tables> -I <chain> -p <protocol> --dport <port> -j f2b-<name>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
@@ -26,11 +32,16 @@ actionstop = <iptables> -D <chain> -p <protocol> --dport <port> -j f2b-<name>
|
||||
<iptables> -F f2b-<name>
|
||||
<iptables> -X f2b-<name>
|
||||
|
||||
actionstop6 = <ip6tables> -D <chain> -p <protocol> --dport <port> -j f2b-<name>
|
||||
<ip6tables> -F f2b-<name>
|
||||
<ip6tables> -X f2b-<name>
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
actioncheck6 = <ip6tables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
@@ -39,6 +50,7 @@ actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
actionban6 = <ip6tables> -I f2b-<name> 1 -s <ip> -j <blocktype6>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
@@ -47,6 +59,7 @@ actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||
actionunban6 = <ip6tables> -D f2b-<name> -s <ip> -j <blocktype6>
|
||||
|
||||
[Init]
|
||||
|
||||
|
||||
+25
-8
@@ -3,6 +3,7 @@
|
||||
# OpenBSD pf ban/unban
|
||||
#
|
||||
# Author: Nick Hilliard <nick@foobar.org>
|
||||
# Modified by: Alexander Koeppe making PF work seamless and with IPv4 and IPv6
|
||||
#
|
||||
#
|
||||
|
||||
@@ -12,23 +13,27 @@
|
||||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
# we don't enable PF automatically, as it will be enabled elsewhere
|
||||
actionstart =
|
||||
# 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-
|
||||
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
# we don't disable PF automatically either
|
||||
actionstop =
|
||||
# we only disable PF rules we've installed prior
|
||||
actionstop = pfctl -sr 2>/dev/null | grep -v <tablename>-<name> | pfctl -f-
|
||||
pfctl -t <tablename>-<name> -T flush
|
||||
pfctl -t <tablename>-<name> -T kill
|
||||
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck =
|
||||
actioncheck = pfctl -sr | grep -q <tablename>-<name>
|
||||
|
||||
|
||||
# Option: actionban
|
||||
@@ -39,7 +44,7 @@ actioncheck =
|
||||
# <time> unix timestamp of the ban time
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = /sbin/pfctl -t <tablename> -T add <ip>/32
|
||||
actionban = pfctl -t <tablename>-<name> -T add <ip>
|
||||
|
||||
|
||||
# Option: actionunban
|
||||
@@ -51,12 +56,24 @@ actionban = /sbin/pfctl -t <tablename> -T add <ip>/32
|
||||
# Values: CMD
|
||||
#
|
||||
# note -r option used to remove matching rule
|
||||
actionunban = /sbin/pfctl -t <tablename> -T delete <ip>/32
|
||||
actionunban = pfctl -t <tablename>-<name> -T delete <ip>
|
||||
|
||||
[Init]
|
||||
# Option: tablename
|
||||
# Notes.: The pf table name.
|
||||
# Values: [ STRING ]
|
||||
#
|
||||
tablename = fail2ban
|
||||
tablename = f2b
|
||||
|
||||
# Option: protocol
|
||||
# Notes.: internally used by config reader for interpolations.
|
||||
# Values: [ tcp | udp | icmp | ipv6-icmp ] Default: tcp
|
||||
#
|
||||
protocol = tcp
|
||||
|
||||
# Option: port
|
||||
# Notes.: the port to block, defaults to any
|
||||
# Values: [ STRING ]
|
||||
#
|
||||
port = any
|
||||
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ before = paths-debian.conf
|
||||
# "ignoreip" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not
|
||||
# ban a host which matches an address in this list. Several addresses can be
|
||||
# defined using space (and/or comma) separator.
|
||||
ignoreip = 127.0.0.1/8
|
||||
ignoreip = 127.0.0.1/8 ::1
|
||||
|
||||
# External command that will take an tagged arguments to ignore, e.g. <ip>,
|
||||
# and return true if the IP is to be ignored. False otherwise.
|
||||
|
||||
@@ -40,7 +40,8 @@ lighttpd_error_log = /var/log/lighttpd/error.log
|
||||
# http://www.hardened-php.net/suhosin/configuration.html#suhosin.log.syslog.facility
|
||||
# syslog_user is the default. Lighttpd also hooks errors into its log.
|
||||
|
||||
suhosin_log = %(syslog_user)s %(lighttpd_error_log)s
|
||||
suhosin_log = %(syslog_user)s
|
||||
%(lighttpd_error_log)s
|
||||
|
||||
# defaults to ftp or local2 if ftp doesn't exist
|
||||
proftpd_log = %(syslog_ftp)s
|
||||
|
||||
@@ -35,13 +35,18 @@ logSys = getLogger(__name__)
|
||||
|
||||
class ActionReader(DefinitionInitConfigReader):
|
||||
|
||||
_configOpts = [
|
||||
["string", "actionstart", None],
|
||||
["string", "actionstop", None],
|
||||
["string", "actioncheck", None],
|
||||
["string", "actionban", None],
|
||||
["string", "actionunban", None],
|
||||
]
|
||||
_configOpts = {
|
||||
"actionstart": ["string", None],
|
||||
"actionstart6": ["string", None],
|
||||
"actionstop": ["string", None],
|
||||
"actionstop6": ["string", None],
|
||||
"actioncheck": ["string", None],
|
||||
"actioncheck6": ["string", None],
|
||||
"actionban": ["string", None],
|
||||
"actionban6": ["string", None],
|
||||
"actionunban": ["string", None],
|
||||
"actionunban6": ["string", None],
|
||||
}
|
||||
|
||||
def __init__(self, file_, jailName, initOpts, **kwargs):
|
||||
self._name = initOpts.get("actname", file_)
|
||||
@@ -65,20 +70,16 @@ class ActionReader(DefinitionInitConfigReader):
|
||||
head = ["set", self._jailName]
|
||||
stream = list()
|
||||
stream.append(head + ["addaction", self._name])
|
||||
head.extend(["action", self._name])
|
||||
for opt in self._opts:
|
||||
if opt == "actionstart":
|
||||
stream.append(head + ["actionstart", self._opts[opt]])
|
||||
elif opt == "actionstop":
|
||||
stream.append(head + ["actionstop", self._opts[opt]])
|
||||
elif opt == "actioncheck":
|
||||
stream.append(head + ["actioncheck", self._opts[opt]])
|
||||
elif opt == "actionban":
|
||||
stream.append(head + ["actionban", self._opts[opt]])
|
||||
elif opt == "actionunban":
|
||||
stream.append(head + ["actionunban", self._opts[opt]])
|
||||
multi = []
|
||||
for opt, optval in self._opts.iteritems():
|
||||
if opt in self._configOpts:
|
||||
multi.append([opt, optval])
|
||||
if self._initOpts:
|
||||
for p in self._initOpts:
|
||||
stream.append(head + [p, self._initOpts[p]])
|
||||
for opt, optval in self._initOpts.iteritems():
|
||||
multi.append([opt, optval])
|
||||
if len(multi) > 1:
|
||||
stream.append(["multi-set", self._jailName, "action", self._name, multi])
|
||||
elif len(multi):
|
||||
stream.append(["set", self._jailName, "action", self._name] + multi[0])
|
||||
|
||||
return stream
|
||||
|
||||
@@ -78,13 +78,13 @@ class Beautifier:
|
||||
prefix1 = " " if n == len(response) - 1 else "| "
|
||||
for m, res2 in enumerate(res1[1]):
|
||||
prefix2 = prefix1 + ("`-" if m == len(res1[1]) - 1 else "|-")
|
||||
val = " ".join(res2[1]) if isinstance(res2[1], list) else res2[1]
|
||||
val = " ".join(map(str, res2[1])) if isinstance(res2[1], list) else res2[1]
|
||||
msg.append("%s %s:\t%s" % (prefix2, res2[0], val))
|
||||
else:
|
||||
msg = ["Status"]
|
||||
for n, res1 in enumerate(response):
|
||||
prefix1 = "`-" if n == len(response) - 1 else "|-"
|
||||
val = " ".join(res1[1]) if isinstance(res1[1], list) else res1[1]
|
||||
val = " ".join(map(str, res1[1])) if isinstance(res1[1], list) else res1[1]
|
||||
msg.append("%s %s:\t%s" % (prefix1, res1[0], val))
|
||||
msg = "\n".join(msg)
|
||||
elif inC[1] == "syslogsocket":
|
||||
|
||||
@@ -231,7 +231,7 @@ after = 1.conf
|
||||
# save previous known values, for possible using in local interpolations later:
|
||||
sk = {}
|
||||
for k, v in s2.iteritems():
|
||||
if not k.startswith('known/'):
|
||||
if not k.startswith('known/') and k != '__name__':
|
||||
sk['known/'+k] = v
|
||||
s2.update(sk)
|
||||
# merge section
|
||||
@@ -256,7 +256,7 @@ after = 1.conf
|
||||
alls = self.get_sections()
|
||||
sk = {}
|
||||
for k, v in options.iteritems():
|
||||
if pref == '' or not k.startswith(pref):
|
||||
if pref == '' or (not k.startswith(pref) and k != '__name__'):
|
||||
sk[pref+k] = v
|
||||
alls[section].update(sk)
|
||||
|
||||
|
||||
@@ -203,40 +203,47 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
|
||||
#
|
||||
# Read the given option in the configuration file. Default values
|
||||
# are used...
|
||||
# Each optionValues entry is composed of an array with:
|
||||
# 0 -> the type of the option
|
||||
# 1 -> the name of the option
|
||||
# 2 -> the default value for the option
|
||||
# Each options entry is composed of an array with:
|
||||
# [[type, name, default], ...]
|
||||
# Or it is a dict:
|
||||
# {name: [type, default], ...}
|
||||
|
||||
def getOptions(self, sec, options, pOptions=None):
|
||||
values = dict()
|
||||
for option in options:
|
||||
try:
|
||||
if option[0] == "bool":
|
||||
v = self.getboolean(sec, option[1])
|
||||
elif option[0] == "int":
|
||||
v = self.getint(sec, option[1])
|
||||
for optname in options:
|
||||
if isinstance(options, (list,tuple)):
|
||||
if len(optname) > 2:
|
||||
opttype, optname, optvalue = optname
|
||||
else:
|
||||
v = self.get(sec, option[1])
|
||||
if not pOptions is None and option[1] in pOptions:
|
||||
(opttype, optname), optvalue = optname, None
|
||||
else:
|
||||
opttype, optvalue = options[optname]
|
||||
try:
|
||||
if opttype == "bool":
|
||||
v = self.getboolean(sec, optname)
|
||||
elif opttype == "int":
|
||||
v = self.getint(sec, optname)
|
||||
else:
|
||||
v = self.get(sec, optname)
|
||||
if not pOptions is None and optname in pOptions:
|
||||
continue
|
||||
values[option[1]] = v
|
||||
values[optname] = v
|
||||
except NoSectionError, e:
|
||||
# No "Definition" section or wrong basedir
|
||||
logSys.error(e)
|
||||
values[option[1]] = option[2]
|
||||
values[optname] = optvalue
|
||||
# TODO: validate error handling here.
|
||||
except NoOptionError:
|
||||
if not option[2] is None:
|
||||
if not optvalue is None:
|
||||
logSys.warning("'%s' not defined in '%s'. Using default one: %r"
|
||||
% (option[1], sec, option[2]))
|
||||
values[option[1]] = option[2]
|
||||
% (optname, sec, optvalue))
|
||||
values[optname] = optvalue
|
||||
elif logSys.getEffectiveLevel() <= logLevel:
|
||||
logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", option[1], sec)
|
||||
logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", optname, sec)
|
||||
except ValueError:
|
||||
logSys.warning("Wrong value for '" + option[1] + "' in '" + sec +
|
||||
"'. Using default one: '" + repr(option[2]) + "'")
|
||||
values[option[1]] = option[2]
|
||||
logSys.warning("Wrong value for '" + optname + "' in '" + sec +
|
||||
"'. Using default one: '" + repr(optvalue) + "'")
|
||||
values[optname] = optvalue
|
||||
return values
|
||||
|
||||
|
||||
@@ -286,7 +293,8 @@ class DefinitionInitConfigReader(ConfigReader):
|
||||
if self.has_section("Init"):
|
||||
for opt in self.options("Init"):
|
||||
v = self.get("Init", opt)
|
||||
self._initOpts['known/'+opt] = v
|
||||
if not opt.startswith('known/') and opt != '__name__':
|
||||
self._initOpts['known/'+opt] = v
|
||||
if not opt in self._initOpts:
|
||||
self._initOpts[opt] = v
|
||||
|
||||
|
||||
@@ -291,7 +291,14 @@ class Fail2banRegex(object):
|
||||
RegexStat(m[3])
|
||||
for m in filter(
|
||||
lambda x: x[0] == 'set' and x[2] == "add%sregex" % regextype,
|
||||
readercommands)]
|
||||
readercommands)
|
||||
] + [
|
||||
RegexStat(m)
|
||||
for mm in filter(
|
||||
lambda x: x[0] == 'multi-set' and x[2] == "add%sregex" % regextype,
|
||||
readercommands)
|
||||
for m in mm[3]
|
||||
]
|
||||
# Read out and set possible value of maxlines
|
||||
for command in readercommands:
|
||||
if command[2] == "maxlines":
|
||||
|
||||
@@ -37,10 +37,10 @@ logSys = getLogger(__name__)
|
||||
|
||||
class FilterReader(DefinitionInitConfigReader):
|
||||
|
||||
_configOpts = [
|
||||
["string", "ignoreregex", None],
|
||||
["string", "failregex", ""],
|
||||
]
|
||||
_configOpts = {
|
||||
"ignoreregex": ["string", None],
|
||||
"failregex": ["string", ""],
|
||||
}
|
||||
|
||||
def setFile(self, fileName):
|
||||
self.__file = fileName
|
||||
@@ -64,16 +64,16 @@ class FilterReader(DefinitionInitConfigReader):
|
||||
if not len(opts):
|
||||
return stream
|
||||
for opt, value in opts.iteritems():
|
||||
if opt == "failregex":
|
||||
if opt in ("failregex", "ignoreregex"):
|
||||
multi = []
|
||||
for regex in value.split('\n'):
|
||||
# Do not send a command if the rule is empty.
|
||||
if regex != '':
|
||||
stream.append(["set", self._jailName, "addfailregex", regex])
|
||||
elif opt == "ignoreregex":
|
||||
for regex in value.split('\n'):
|
||||
# Do not send a command if the rule is empty.
|
||||
if regex != '':
|
||||
stream.append(["set", self._jailName, "addignoreregex", regex])
|
||||
multi.append(regex)
|
||||
if len(multi) > 1:
|
||||
stream.append(["multi-set", self._jailName, "add" + opt, multi])
|
||||
elif len(multi):
|
||||
stream.append(["set", self._jailName, "add" + opt, multi[0]])
|
||||
if self._initOpts:
|
||||
if 'maxlines' in self._initOpts:
|
||||
# We warn when multiline regex is used without maxlines > 1
|
||||
|
||||
@@ -190,11 +190,11 @@ class JailReader(ConfigReader):
|
||||
"""
|
||||
|
||||
stream = []
|
||||
for opt in self.__opts:
|
||||
for opt, value in self.__opts.iteritems():
|
||||
if opt == "logpath" and \
|
||||
self.__opts.get('backend', None) != "systemd":
|
||||
found_files = 0
|
||||
for path in self.__opts[opt].split("\n"):
|
||||
for path in value.split("\n"):
|
||||
path = path.rsplit(" ", 1)
|
||||
path, tail = path if len(path) > 1 else (path[0], "head")
|
||||
pathList = JailReader._glob(path)
|
||||
@@ -208,32 +208,32 @@ class JailReader(ConfigReader):
|
||||
raise ValueError(
|
||||
"Have not found any log file for %s jail" % self.__name)
|
||||
elif opt == "logencoding":
|
||||
stream.append(["set", self.__name, "logencoding", self.__opts[opt]])
|
||||
stream.append(["set", self.__name, "logencoding", value])
|
||||
elif opt == "backend":
|
||||
backend = self.__opts[opt]
|
||||
backend = value
|
||||
elif opt == "maxretry":
|
||||
stream.append(["set", self.__name, "maxretry", self.__opts[opt]])
|
||||
stream.append(["set", self.__name, "maxretry", value])
|
||||
elif opt == "ignoreip":
|
||||
for ip in splitcommaspace(self.__opts[opt]):
|
||||
for ip in splitcommaspace(value):
|
||||
stream.append(["set", self.__name, "addignoreip", ip])
|
||||
elif opt == "findtime":
|
||||
stream.append(["set", self.__name, "findtime", self.__opts[opt]])
|
||||
stream.append(["set", self.__name, "findtime", value])
|
||||
elif opt == "bantime":
|
||||
stream.append(["set", self.__name, "bantime", self.__opts[opt]])
|
||||
stream.append(["set", self.__name, "bantime", value])
|
||||
elif opt == "usedns":
|
||||
stream.append(["set", self.__name, "usedns", self.__opts[opt]])
|
||||
elif opt == "failregex":
|
||||
for regex in self.__opts[opt].split('\n'):
|
||||
stream.append(["set", self.__name, "usedns", value])
|
||||
elif opt in ("failregex", "ignoreregex"):
|
||||
multi = []
|
||||
for regex in value.split('\n'):
|
||||
# Do not send a command if the rule is empty.
|
||||
if regex != '':
|
||||
stream.append(["set", self.__name, "addfailregex", regex])
|
||||
multi.append(regex)
|
||||
if len(multi) > 1:
|
||||
stream.append(["multi-set", self.__name, "add" + opt, multi])
|
||||
elif len(multi):
|
||||
stream.append(["set", self.__name, "add" + opt, multi[0]])
|
||||
elif opt == "ignorecommand":
|
||||
stream.append(["set", self.__name, "ignorecommand", self.__opts[opt]])
|
||||
elif opt == "ignoreregex":
|
||||
for regex in self.__opts[opt].split('\n'):
|
||||
# Do not send a command if the rule is empty.
|
||||
if regex != '':
|
||||
stream.append(["set", self.__name, "addignoreregex", regex])
|
||||
stream.append(["set", self.__name, "ignorecommand", value])
|
||||
if self.__filter:
|
||||
stream.extend(self.__filter.convert())
|
||||
for action in self.__actions:
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
|
||||
# vi: set ft=python sts=4 ts=4 sw=4 noet :
|
||||
|
||||
# This file is part of Fail2Ban.
|
||||
#
|
||||
# Fail2Ban is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fail2Ban is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Fail2Ban; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
# Author: Alexander Koeppe
|
||||
#
|
||||
|
||||
__author__ = "Alexander Koeppe"
|
||||
__copyright__ = "Copyright (c) 2016 Alexander Koeppe"
|
||||
__license__ = "GPL"
|
||||
|
||||
import inspect
|
||||
import socket
|
||||
import struct
|
||||
import re
|
||||
|
||||
from functools import wraps
|
||||
|
||||
|
||||
##
|
||||
# Helper functions / decorator
|
||||
# Thanks to Yaroslav Halchenko (yarikoptic)
|
||||
#
|
||||
def asip(ip):
|
||||
"""A little helper to guarantee ip being an IPAddr instance"""
|
||||
return ip if isinstance(ip, IPAddr) or ip is None else IPAddr(ip)
|
||||
|
||||
|
||||
def iparg(f):
|
||||
"""A helper decorator to simplify use of asip throughout the code"""
|
||||
args = inspect.getargspec(f).args
|
||||
# I consider clarity better than trying to avoid any duplication here
|
||||
# also better to make a decision at code parsing stage, not within the
|
||||
# actual decorator function (i.e. checkip)
|
||||
if args and args[0] == 'self':
|
||||
# method -- just above simpler version
|
||||
@wraps(f)
|
||||
def checkip(self, ip=None, *argv, **kwargs):
|
||||
return f(self, asip(ip), *argv, **kwargs)
|
||||
else:
|
||||
@wraps(f)
|
||||
def checkip(ip=None, *argv, **kwargs):
|
||||
return f(asip(ip), *argv, **kwargs)
|
||||
|
||||
return checkip
|
||||
|
||||
|
||||
##
|
||||
# Class for IP address handling.
|
||||
#
|
||||
# This class contains methods for handling IPv4 and IPv6 addresses.
|
||||
|
||||
class IPAddr:
|
||||
"""Encapsulate functionality for IPv4 and IPv6 addresses
|
||||
"""
|
||||
|
||||
IP_CRE = re.compile("^(?:\d{1,3}\.){3}\d{1,3}$")
|
||||
IP6_CRE = re.compile("^[0-9a-fA-F]{4}[0-9a-fA-F:]+:[0-9a-fA-F]{1,4}|::1$")
|
||||
|
||||
# object attributes
|
||||
_addr = 0
|
||||
_family = socket.AF_UNSPEC
|
||||
_plen = 0
|
||||
_isValid = False
|
||||
_raw = ""
|
||||
|
||||
# object methods
|
||||
def __init__(self, ipstring, cidr=-1):
|
||||
""" initialize IP object by converting IP address string
|
||||
to binary to integer
|
||||
"""
|
||||
for family in [socket.AF_INET, socket.AF_INET6]:
|
||||
try:
|
||||
binary = socket.inet_pton(family, ipstring)
|
||||
except socket.error:
|
||||
continue
|
||||
else:
|
||||
self._isValid = True
|
||||
break
|
||||
|
||||
if self.isValid and family == socket.AF_INET:
|
||||
# convert host to network byte order
|
||||
self._addr, = struct.unpack("!L", binary)
|
||||
self._family = family
|
||||
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.isValid and family == socket.AF_INET6:
|
||||
# convert host to network byte order
|
||||
hi, lo = struct.unpack("!QQ", binary)
|
||||
self._addr = (hi << 64) | lo
|
||||
self._family = family
|
||||
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(_IPv6_v4COMPAT):
|
||||
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 = ipstring
|
||||
|
||||
def __repr__(self):
|
||||
return self.ntoa
|
||||
|
||||
def __str__(self):
|
||||
return self.ntoa
|
||||
|
||||
@property
|
||||
def addr(self):
|
||||
return self._addr
|
||||
|
||||
@property
|
||||
def family(self):
|
||||
return self._family
|
||||
|
||||
@property
|
||||
def plen(self):
|
||||
return self._plen
|
||||
|
||||
@property
|
||||
def raw(self):
|
||||
"""The raw address
|
||||
|
||||
Should only be set to a non-empty string if prior address
|
||||
conversion wasn't possible
|
||||
"""
|
||||
return self._raw
|
||||
|
||||
@property
|
||||
def isValid(self):
|
||||
"""Either the object corresponds to a valid IP address
|
||||
"""
|
||||
return self._isValid
|
||||
|
||||
@iparg
|
||||
def __eq__(self, other):
|
||||
if not (self.isValid or other.isValid):
|
||||
return self.raw == other.raw
|
||||
return (
|
||||
(self.isValid and other.isValid) and
|
||||
(self.addr == other.addr) and
|
||||
(self.family == other.family) and
|
||||
(self.plen == other.plen)
|
||||
)
|
||||
|
||||
@iparg
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
@iparg
|
||||
def __lt__(self, other):
|
||||
return self.family < other.family or self.addr < other.addr
|
||||
|
||||
@iparg
|
||||
def __add__(self, other):
|
||||
return "%s%s" % (self, other)
|
||||
|
||||
@iparg
|
||||
def __radd__(self, other):
|
||||
return "%s%s" % (other, self)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.addr) ^ hash((self.plen << 16) | self.family)
|
||||
|
||||
@property
|
||||
def hexdump(self):
|
||||
"""Hex representation of the IP address (for debug purposes)
|
||||
"""
|
||||
if self.family == socket.AF_INET:
|
||||
return "%08x" % self.addr
|
||||
elif self.family == socket.AF_INET6:
|
||||
return "%032x" % self.addr
|
||||
else:
|
||||
return ""
|
||||
|
||||
# TODO: could be lazily evaluated
|
||||
@property
|
||||
def ntoa(self):
|
||||
""" represent IP object as text like the deprecated
|
||||
C pendant inet.ntoa but address family independent
|
||||
"""
|
||||
if self.isIPv4:
|
||||
# convert network to host byte order
|
||||
binary = struct.pack("!L", self._addr)
|
||||
elif self.isIPv6:
|
||||
# convert network to host byte order
|
||||
hi = self.addr >> 64
|
||||
lo = self.addr & 0xFFFFFFFFFFFFFFFFL
|
||||
binary = struct.pack("!QQ", hi, lo)
|
||||
else:
|
||||
return self.raw
|
||||
|
||||
return socket.inet_ntop(self.family, binary)
|
||||
|
||||
def getPTR(self, suffix=""):
|
||||
""" return the DNS PTR string of the provided IP address object
|
||||
|
||||
If "suffix" is provided it will be appended as the second and top
|
||||
level reverse domain.
|
||||
If omitted it is implicitly set to the second and top level reverse
|
||||
domain of the according IP address family
|
||||
"""
|
||||
if self.isIPv4:
|
||||
exploded_ip = self.ntoa.split(".")
|
||||
if not suffix:
|
||||
suffix = "in-addr.arpa."
|
||||
elif self.isIPv6:
|
||||
exploded_ip = self.hexdump()
|
||||
if not suffix:
|
||||
suffix = "ip6.arpa."
|
||||
else:
|
||||
return ""
|
||||
|
||||
return "%s.%s" % (".".join(reversed(exploded_ip)), suffix)
|
||||
|
||||
@property
|
||||
def isIPv4(self):
|
||||
"""Either the IP object is of address family AF_INET
|
||||
"""
|
||||
return self.family == socket.AF_INET
|
||||
|
||||
@property
|
||||
def isIPv6(self):
|
||||
"""Either the IP object is of address family AF_INET6
|
||||
"""
|
||||
return self.family == socket.AF_INET6
|
||||
|
||||
@iparg
|
||||
def isInNet(self, net):
|
||||
"""Return either the IP object is in the provided network
|
||||
"""
|
||||
if self.family != net.family:
|
||||
return False
|
||||
if self.isIPv4:
|
||||
mask = ~(0xFFFFFFFFL >> net.plen)
|
||||
elif self.isIPv6:
|
||||
mask = ~(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL >> net.plen)
|
||||
else:
|
||||
return False
|
||||
|
||||
return self.addr & mask == net.addr
|
||||
|
||||
@staticmethod
|
||||
@iparg
|
||||
def masktoplen(mask):
|
||||
"""Convert mask string to prefix length
|
||||
|
||||
To be used only for IPv4 masks
|
||||
"""
|
||||
mask = mask.addr # to avoid side-effect within original mask
|
||||
plen = 0
|
||||
while mask:
|
||||
mask = (mask << 1) & 0xFFFFFFFFL
|
||||
plen += 1
|
||||
return plen
|
||||
|
||||
@staticmethod
|
||||
def searchIP(text):
|
||||
"""Search if text is an IP address, and return it if so, else None
|
||||
"""
|
||||
match = IPAddr.IP_CRE.match(text)
|
||||
if not match:
|
||||
match = IPAddr.IP6_CRE.match(text)
|
||||
return match if match else None
|
||||
|
||||
# An IPv4 compatible IPv6 to be reused
|
||||
_IPv6_v4COMPAT = IPAddr("::ffff:0:0", 96)
|
||||
@@ -203,9 +203,13 @@ class CommandAction(ActionBase):
|
||||
Attributes
|
||||
----------
|
||||
actionban
|
||||
actionban6
|
||||
actionstart
|
||||
actionstart6
|
||||
actionstop
|
||||
actionstop6
|
||||
actionunban
|
||||
actionunban6
|
||||
timeout
|
||||
"""
|
||||
|
||||
@@ -216,14 +220,24 @@ class CommandAction(ActionBase):
|
||||
self.timeout = 60
|
||||
## Command executed in order to initialize the system.
|
||||
self.actionstart = ''
|
||||
## Command executed in order to initialize the system.
|
||||
self.actionstart6 = ''
|
||||
## Command executed when an IP address gets banned.
|
||||
self.actionban = ''
|
||||
## Command executed when an IPv6 address gets banned.
|
||||
self.actionban6 = ''
|
||||
## Command executed when an IP address gets removed.
|
||||
self.actionunban = ''
|
||||
## Command executed when an IPv6 address gets removed.
|
||||
self.actionunban6 = ''
|
||||
## Command executed in order to check requirements.
|
||||
self.actioncheck = ''
|
||||
## Command executed in order to check requirements.
|
||||
self.actioncheck6 = ''
|
||||
## Command executed in order to stop the system.
|
||||
self.actionstop = ''
|
||||
## Command executed in order to stop the system.
|
||||
self.actionstop6 = ''
|
||||
self._logSys.debug("Created %s" % self.__class__)
|
||||
|
||||
@classmethod
|
||||
@@ -264,6 +278,17 @@ class CommandAction(ActionBase):
|
||||
self._actionstart = value
|
||||
self._logSys.debug("Set actionstart = %s" % value)
|
||||
|
||||
@property
|
||||
def actionstart6(self):
|
||||
"""The command executed on start of the jail/action.
|
||||
"""
|
||||
return self._actionstart6
|
||||
|
||||
@actionstart6.setter
|
||||
def actionstart6(self, value):
|
||||
self._actionstart6 = value
|
||||
self._logSys.debug("Set actionstart6 = %s" % value)
|
||||
|
||||
def start(self):
|
||||
"""Executes the "actionstart" command.
|
||||
|
||||
@@ -279,10 +304,16 @@ class CommandAction(ActionBase):
|
||||
startCmd = self.replaceTag(self.actionstart, self._properties)
|
||||
if not self.executeCmd(startCmd, self.timeout):
|
||||
raise RuntimeError("Error starting action")
|
||||
# Start actionstart6 if available
|
||||
if self.actionstart6:
|
||||
startCmd = self.replaceTag(self.actionstart6, self._properties)
|
||||
if not self.executeCmd(startCmd, self.timeout):
|
||||
raise RuntimeError("Error starting action")
|
||||
|
||||
|
||||
@property
|
||||
def actionban(self):
|
||||
"""The command used when a ban occurs.
|
||||
"""The command used when a ban on IPv4 address occurs.
|
||||
"""
|
||||
return self._actionban
|
||||
|
||||
@@ -291,6 +322,17 @@ class CommandAction(ActionBase):
|
||||
self._actionban = value
|
||||
self._logSys.debug("Set actionban = %s" % value)
|
||||
|
||||
@property
|
||||
def actionban6(self):
|
||||
"""The command used when a ban on IPv6 address occurs.
|
||||
"""
|
||||
return self._actionban6
|
||||
|
||||
@actionban6.setter
|
||||
def actionban6(self, value):
|
||||
self._actionban6 = value
|
||||
self._logSys.debug("Set actionban6 = %s" % value)
|
||||
|
||||
def ban(self, aInfo):
|
||||
"""Executes the "actionban" command.
|
||||
|
||||
@@ -303,7 +345,10 @@ class CommandAction(ActionBase):
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
"""
|
||||
if not self._processCmd(self.actionban, aInfo):
|
||||
banaction = self.actionban
|
||||
if "ip" in aInfo and aInfo["ip"] and aInfo["ip"].isIPv6 and self.actionban6:
|
||||
banaction = self.actionban6
|
||||
if not self._processCmd(banaction, aInfo):
|
||||
raise RuntimeError("Error banning %(ip)s" % aInfo)
|
||||
|
||||
@property
|
||||
@@ -317,6 +362,17 @@ class CommandAction(ActionBase):
|
||||
self._actionunban = value
|
||||
self._logSys.debug("Set actionunban = %s" % value)
|
||||
|
||||
@property
|
||||
def actionunban6(self):
|
||||
"""The command used when an unban occurs.
|
||||
"""
|
||||
return self._actionunban6
|
||||
|
||||
@actionunban6.setter
|
||||
def actionunban6(self, value):
|
||||
self._actionunban6 = value
|
||||
self._logSys.debug("Set actionunban6 = %s" % value)
|
||||
|
||||
def unban(self, aInfo):
|
||||
"""Executes the "actionunban" command.
|
||||
|
||||
@@ -329,7 +385,11 @@ class CommandAction(ActionBase):
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
"""
|
||||
if not self._processCmd(self.actionunban, aInfo):
|
||||
unbanaction = self.actionunban
|
||||
if "ip" in aInfo and aInfo["ip"] and aInfo["ip"].isIPv6 and self.actionunban6:
|
||||
unbanaction = self.actionunban6
|
||||
|
||||
if not self._processCmd(unbanaction, aInfo):
|
||||
raise RuntimeError("Error unbanning %(ip)s" % aInfo)
|
||||
|
||||
@property
|
||||
@@ -347,6 +407,21 @@ class CommandAction(ActionBase):
|
||||
self._actioncheck = value
|
||||
self._logSys.debug("Set actioncheck = %s" % value)
|
||||
|
||||
@property
|
||||
def actioncheck6(self):
|
||||
"""The command used to check the environment.
|
||||
|
||||
This is used prior to a ban taking place to ensure the
|
||||
environment is appropriate. If this check fails, `stop` and
|
||||
`start` is executed prior to the check being called again.
|
||||
"""
|
||||
return self._actioncheck6
|
||||
|
||||
@actioncheck6.setter
|
||||
def actioncheck6(self, value):
|
||||
self._actioncheck6 = value
|
||||
self._logSys.debug("Set actioncheck6 = %s" % value)
|
||||
|
||||
@property
|
||||
def actionstop(self):
|
||||
"""The command executed when the jail/actions stops.
|
||||
@@ -358,8 +433,19 @@ class CommandAction(ActionBase):
|
||||
self._actionstop = value
|
||||
self._logSys.debug("Set actionstop = %s" % value)
|
||||
|
||||
@property
|
||||
def actionstop6(self):
|
||||
"""The command executed when the jail/actions stops.
|
||||
"""
|
||||
return self._actionstop6
|
||||
|
||||
@actionstop6.setter
|
||||
def actionstop6(self, value):
|
||||
self._actionstop6 = value
|
||||
self._logSys.debug("Set actionstop6 = %s" % value)
|
||||
|
||||
def stop(self):
|
||||
"""Executes the "actionstop" command.
|
||||
"""Executes the "actionstop6" command.
|
||||
|
||||
Replaces the tags in the action command with actions properties
|
||||
and executes the resulting command.
|
||||
@@ -367,6 +453,11 @@ class CommandAction(ActionBase):
|
||||
stopCmd = self.replaceTag(self.actionstop, self._properties)
|
||||
if not self.executeCmd(stopCmd, self.timeout):
|
||||
raise RuntimeError("Error stopping action")
|
||||
# Start actionstop6 if available
|
||||
if self.actionstop6:
|
||||
stopCmd = self.replaceTag(self.actionstop6, self._properties)
|
||||
if not self.executeCmd(stopCmd, self.timeout):
|
||||
raise RuntimeError("Error stopping action")
|
||||
|
||||
@classmethod
|
||||
def substituteRecursiveTags(cls, tags):
|
||||
@@ -510,7 +601,10 @@ class CommandAction(ActionBase):
|
||||
self._logSys.debug("Nothing to do")
|
||||
return True
|
||||
|
||||
checkCmd = self.replaceTag(self.actioncheck, self._properties)
|
||||
actioncheck = self.actioncheck
|
||||
if "ip" in aInfo and aInfo["ip"] and aInfo["ip"].isIPv6 and self.actioncheck6:
|
||||
actioncheck = self.actioncheck6
|
||||
checkCmd = self.replaceTag(actioncheck, self._properties)
|
||||
if not self.executeCmd(checkCmd, self.timeout):
|
||||
self._logSys.error(
|
||||
"Invariant check failed. Trying to restore a sane environment")
|
||||
|
||||
@@ -43,6 +43,7 @@ from .jailthread import JailThread
|
||||
from .action import ActionBase, CommandAction, CallingMap
|
||||
from .mytime import MyTime
|
||||
from ..helpers import getLogger
|
||||
from ..ipaddr import iparg
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
@@ -178,6 +179,7 @@ class Actions(JailThread, Mapping):
|
||||
def getBanTime(self):
|
||||
return self.__banManager.getBanTime()
|
||||
|
||||
@iparg
|
||||
def removeBannedIP(self, ip):
|
||||
"""Removes banned IP calling actions' unban method
|
||||
|
||||
|
||||
@@ -152,9 +152,10 @@ class BanManager:
|
||||
for banData in self.__banList:
|
||||
ip = banData.getIP()
|
||||
# Reference: http://www.team-cymru.org/Services/ip-to-asn.html#dns
|
||||
# TODO: IPv6 compatibility
|
||||
reversed_ip = ".".join(reversed(ip.split(".")))
|
||||
question = "%s.origin.asn.cymru.com" % reversed_ip
|
||||
question = ip.getPTR(
|
||||
"origin.asn.cymru.com" if ip.isIPv4
|
||||
else "origin6.asn.cymru.com"
|
||||
)
|
||||
try:
|
||||
answers = dns.resolver.query(question, "TXT")
|
||||
for rdata in answers:
|
||||
|
||||
@@ -32,6 +32,7 @@ from threading import RLock
|
||||
|
||||
from .mytime import MyTime
|
||||
from .ticket import FailTicket
|
||||
from ..ipaddr import IPAddr, iparg
|
||||
from ..helpers import getLogger
|
||||
|
||||
# Gets the instance of the logger.
|
||||
@@ -413,7 +414,7 @@ class Fail2BanDb(object):
|
||||
#TODO: Implement data parts once arbitrary match keys completed
|
||||
cur.execute(
|
||||
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
|
||||
(jail.name, ticket.getIP(), int(round(ticket.getTime())),
|
||||
(jail.name, ticket.getIP().ntoa, int(round(ticket.getTime())),
|
||||
{"matches": ticket.getMatches(),
|
||||
"failures": ticket.getAttempt()}))
|
||||
|
||||
@@ -428,7 +429,7 @@ class Fail2BanDb(object):
|
||||
ip : str
|
||||
IP to be removed.
|
||||
"""
|
||||
queryArgs = (jail.name, ip);
|
||||
queryArgs = (jail.name, ip.ntoa);
|
||||
cur.execute(
|
||||
"DELETE FROM bans WHERE jail = ? AND ip = ?",
|
||||
queryArgs);
|
||||
@@ -446,7 +447,7 @@ class Fail2BanDb(object):
|
||||
queryArgs.append(MyTime.time() - bantime)
|
||||
if ip is not None:
|
||||
query += " AND ip=?"
|
||||
queryArgs.append(ip)
|
||||
queryArgs.append(ip.ntoa)
|
||||
query += " ORDER BY ip, timeofban"
|
||||
|
||||
return cur.execute(query, queryArgs)
|
||||
@@ -462,7 +463,7 @@ class Fail2BanDb(object):
|
||||
Ban time in seconds, such that bans returned would still be
|
||||
valid now. Negative values are equivalent to `None`.
|
||||
Default `None`; no limit.
|
||||
ip : str
|
||||
ip : IPAddr object
|
||||
IP Address to filter bans by. Default `None`; all IPs.
|
||||
|
||||
Returns
|
||||
@@ -471,12 +472,14 @@ class Fail2BanDb(object):
|
||||
List of `Ticket`s for bans stored in database.
|
||||
"""
|
||||
tickets = []
|
||||
for ip, timeofban, data in self._getBans(**kwargs):
|
||||
for ipstr, timeofban, data in self._getBans(**kwargs):
|
||||
ip = IPAddr(ipstr)
|
||||
#TODO: Implement data parts once arbitrary match keys completed
|
||||
tickets.append(FailTicket(ip, timeofban, data.get('matches')))
|
||||
tickets[-1].setAttempt(data.get('failures', 1))
|
||||
return tickets
|
||||
|
||||
@iparg
|
||||
def getBansMerged(self, ip=None, jail=None, bantime=None):
|
||||
"""Get bans from the database, merged into single ticket.
|
||||
|
||||
@@ -491,7 +494,7 @@ class Fail2BanDb(object):
|
||||
Ban time in seconds, such that bans returned would still be
|
||||
valid now. Negative values are equivalent to `None`.
|
||||
Default `None`; no limit.
|
||||
ip : str
|
||||
ip : IPAddr object
|
||||
IP Address to filter bans by. Default `None`; all IPs.
|
||||
|
||||
Returns
|
||||
@@ -512,6 +515,8 @@ class Fail2BanDb(object):
|
||||
ticket = None
|
||||
|
||||
results = list(self._getBans(ip=ip, jail=jail, bantime=bantime))
|
||||
# Convert IP strings to IPAddr objects
|
||||
results = map(lambda i:(IPAddr(i[0]),)+i[1:], results)
|
||||
if results:
|
||||
prev_banip = results[0][0]
|
||||
matches = []
|
||||
|
||||
@@ -30,6 +30,7 @@ import logging
|
||||
from .faildata import FailData
|
||||
from .ticket import FailTicket
|
||||
from ..helpers import getLogger
|
||||
from ..ipaddr import iparg
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
@@ -136,6 +137,7 @@ class FailManager:
|
||||
finally:
|
||||
self.__lock.release()
|
||||
|
||||
@iparg
|
||||
def __delFailure(self, ip):
|
||||
if ip in self.__failList:
|
||||
del self.__failList[ip]
|
||||
|
||||
+61
-81
@@ -28,6 +28,7 @@ import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
from .failmanager import FailManagerEmpty, FailManager
|
||||
from .ticket import FailTicket
|
||||
from .jailthread import JailThread
|
||||
@@ -37,6 +38,7 @@ from .mytime import MyTime
|
||||
from .failregex import FailRegex, Regex, RegexException
|
||||
from .action import CommandAction
|
||||
from ..helpers import getLogger
|
||||
from ..ipaddr import IPAddr, iparg
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
@@ -303,12 +305,19 @@ class Filter(JailThread):
|
||||
def getIgnoreCommand(self):
|
||||
return self.__ignoreCommand
|
||||
|
||||
##
|
||||
# create new IPAddr object from IP address string
|
||||
@iparg
|
||||
def newIP(self, ip):
|
||||
return ip
|
||||
|
||||
##
|
||||
# Ban an IP - http://blogs.buanzo.com.ar/2009/04/fail2ban-patch-ban-ip-address-manually.html
|
||||
# Arturo 'Buanzo' Busleiman <buanzo@buanzo.com.ar>
|
||||
#
|
||||
# to enable banip fail2ban-client BAN command
|
||||
|
||||
@iparg
|
||||
def addBannedIP(self, ip):
|
||||
if self.inIgnoreIPList(ip):
|
||||
logSys.warning('Requested to manually ban an ignored IP %s. User knows best. Proceeding to ban it.' % ip)
|
||||
@@ -334,14 +343,31 @@ class Filter(JailThread):
|
||||
# when finding failures. CIDR mask and DNS are also accepted.
|
||||
# @param ip IP address to ignore
|
||||
|
||||
def addIgnoreIP(self, ip):
|
||||
def addIgnoreIP(self, ipstr):
|
||||
# An empty string is always false
|
||||
if ipstr == "":
|
||||
return
|
||||
s = ipstr.split('/', 1)
|
||||
# IP address without CIDR mask
|
||||
if len(s) == 1:
|
||||
s.insert(1, -1) # <0 means no CIDR
|
||||
elif "." in s[1]: # 255.255.255.0 style mask
|
||||
s[1] = IPAddr.masktoplen(s[1])
|
||||
s[1] = long(s[1])
|
||||
|
||||
# Create IP address object
|
||||
ip = IPAddr(s[0], s[1])
|
||||
|
||||
# log and append to ignore list
|
||||
logSys.debug("Add " + ip + " to ignore list")
|
||||
self.__ignoreIpList.append(ip)
|
||||
|
||||
@iparg
|
||||
def delIgnoreIP(self, ip):
|
||||
logSys.debug("Remove " + ip + " from ignore list")
|
||||
self.__ignoreIpList.remove(ip)
|
||||
|
||||
@iparg
|
||||
def logIgnoreIp(self, ip, log_ignore, ignore_source="unknown source"):
|
||||
if log_ignore:
|
||||
logSys.info("[%s] Ignore %s by %s" % (self.jail.name, ip, ignore_source))
|
||||
@@ -354,34 +380,23 @@ class Filter(JailThread):
|
||||
#
|
||||
# Check if the given IP address matches an IP address/DNS or a CIDR
|
||||
# mask in the ignore list.
|
||||
# @param ip IP address
|
||||
# @param ip IP address object
|
||||
# @return True if IP address is in ignore list
|
||||
|
||||
@iparg
|
||||
def inIgnoreIPList(self, ip, log_ignore=False):
|
||||
for i in self.__ignoreIpList:
|
||||
# An empty string is always false
|
||||
if i == "":
|
||||
continue
|
||||
s = i.split('/', 1)
|
||||
# IP address without CIDR mask
|
||||
if len(s) == 1:
|
||||
s.insert(1, '32')
|
||||
elif "." in s[1]: # 255.255.255.0 style mask
|
||||
s[1] = len(re.search(
|
||||
"(?<=b)1+", bin(DNSUtils.addr2bin(s[1]))).group())
|
||||
s[1] = long(s[1])
|
||||
try:
|
||||
a = DNSUtils.addr2bin(s[0], cidr=s[1])
|
||||
b = DNSUtils.addr2bin(ip, cidr=s[1])
|
||||
except Exception:
|
||||
for net in self.__ignoreIpList:
|
||||
# if it isn't a valid IP address, try DNS resolution
|
||||
if not net.isValid and net.raw != "":
|
||||
# Check if IP in DNS
|
||||
ips = DNSUtils.dnsToIp(i)
|
||||
ips = DNSUtils.dnsToIp(net.raw)
|
||||
if ip in ips:
|
||||
self.logIgnoreIp(ip, log_ignore, ignore_source="dns")
|
||||
return True
|
||||
else:
|
||||
continue
|
||||
if a == b:
|
||||
# check if the IP is covered by ignore IP
|
||||
if ip.isInNet(net):
|
||||
self.logIgnoreIp(ip, log_ignore, ignore_source="ip")
|
||||
return True
|
||||
|
||||
@@ -389,6 +404,7 @@ class Filter(JailThread):
|
||||
command = CommandAction.replaceTag(self.__ignoreCommand, { 'ip': ip } )
|
||||
logSys.debug('ignore command: ' + command)
|
||||
ret_ignore = CommandAction.executeCmd(command)
|
||||
|
||||
self.logIgnoreIp(ip, log_ignore and ret_ignore, ignore_source="command")
|
||||
return ret_ignore
|
||||
|
||||
@@ -524,16 +540,17 @@ class Filter(JailThread):
|
||||
try:
|
||||
host = failRegex.getHost()
|
||||
if returnRawHost:
|
||||
failList.append([failRegexIndex, host, date,
|
||||
ip = IPAddr(host)
|
||||
failList.append([failRegexIndex, ip, date,
|
||||
failRegex.getMatchedLines()])
|
||||
if not checkAllRegex:
|
||||
break
|
||||
else:
|
||||
ipMatch = DNSUtils.textToIp(host, self.__useDns)
|
||||
if ipMatch:
|
||||
for ip in ipMatch:
|
||||
failList.append([failRegexIndex, ip, date,
|
||||
failRegex.getMatchedLines()])
|
||||
ips = DNSUtils.textToIp(host, self.__useDns)
|
||||
if ips:
|
||||
for ip in ips:
|
||||
failList.append([failRegexIndex, ip,
|
||||
date, failRegex.getMatchedLines()])
|
||||
if not checkAllRegex:
|
||||
break
|
||||
except RegexException, e: # pragma: no cover - unsure if reachable
|
||||
@@ -837,10 +854,10 @@ class JournalFilter(Filter): # pragma: systemd no cover
|
||||
return []
|
||||
|
||||
##
|
||||
# Utils class for DNS and IP handling.
|
||||
# Utils class for DNS handling.
|
||||
#
|
||||
# This class contains only static methods used to handle DNS
|
||||
#
|
||||
# This class contains only static methods used to handle DNS and IP
|
||||
# addresses.
|
||||
|
||||
import socket
|
||||
import struct
|
||||
@@ -848,66 +865,45 @@ import struct
|
||||
|
||||
class DNSUtils:
|
||||
|
||||
IP_CRE = re.compile("^(?:\d{1,3}\.){3}\d{1,3}$")
|
||||
|
||||
@staticmethod
|
||||
def dnsToIp(dns):
|
||||
""" Convert a DNS into an IP address using the Python socket module.
|
||||
Thanks to Kevin Drapel.
|
||||
"""
|
||||
# retrieve ip (todo: use AF_INET6 for IPv6)
|
||||
try:
|
||||
return set([i[4][0] for i in socket.getaddrinfo(dns, None, socket.AF_INET, 0, socket.IPPROTO_TCP)])
|
||||
ips = list()
|
||||
for result in socket.getaddrinfo(dns, None, 0, 0,
|
||||
socket.IPPROTO_TCP):
|
||||
ip = IPAddr(result[4][0])
|
||||
if ip.isValid:
|
||||
ips.append(ip)
|
||||
|
||||
return ips
|
||||
except socket.error, e:
|
||||
logSys.warning("Unable to find a corresponding IP address for %s: %s"
|
||||
% (dns, e))
|
||||
return list()
|
||||
except socket.error, e:
|
||||
logSys.warning("Socket error raised trying to resolve hostname %s: %s"
|
||||
% (dns, e))
|
||||
return list()
|
||||
|
||||
@staticmethod
|
||||
@iparg
|
||||
def ipToName(ip):
|
||||
try:
|
||||
return socket.gethostbyaddr(ip)[0]
|
||||
return socket.gethostbyaddr(ip.ntoa)[0]
|
||||
except socket.error, e:
|
||||
logSys.debug("Unable to find a name for the IP %s: %s" % (ip, e))
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def searchIP(text):
|
||||
""" Search if an IP address if directly available and return
|
||||
it.
|
||||
"""
|
||||
match = DNSUtils.IP_CRE.match(text)
|
||||
if match:
|
||||
return match
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def isValidIP(string):
|
||||
""" Return true if str is a valid IP
|
||||
"""
|
||||
s = string.split('/', 1)
|
||||
try:
|
||||
socket.inet_aton(s[0])
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def textToIp(text, useDns):
|
||||
""" Return the IP of DNS found in a given text.
|
||||
"""
|
||||
ipList = list()
|
||||
# Search for plain IP
|
||||
plainIP = DNSUtils.searchIP(text)
|
||||
if not plainIP is None:
|
||||
plainIPStr = plainIP.group(0)
|
||||
if DNSUtils.isValidIP(plainIPStr):
|
||||
ipList.append(plainIPStr)
|
||||
plainIP = IPAddr.searchIP(text)
|
||||
if plainIP is not None:
|
||||
ip = IPAddr(plainIP.group(0))
|
||||
if ip.isValid:
|
||||
ipList.append(ip)
|
||||
|
||||
# If we are allowed to resolve -- give it a try if nothing was found
|
||||
if useDns in ("yes", "warn") and not ipList:
|
||||
@@ -920,19 +916,3 @@ class DNSUtils:
|
||||
|
||||
return ipList
|
||||
|
||||
@staticmethod
|
||||
def addr2bin(ipstring, cidr=None):
|
||||
""" Convert a string IPv4 address into binary form.
|
||||
If cidr is supplied, return the network address for the given block
|
||||
"""
|
||||
if cidr is None:
|
||||
return struct.unpack("!L", socket.inet_aton(ipstring))[0]
|
||||
else:
|
||||
MASK = 0xFFFFFFFFL
|
||||
return ~(MASK >> cidr) & MASK & DNSUtils.addr2bin(ipstring)
|
||||
|
||||
@staticmethod
|
||||
def bin2addr(ipbin):
|
||||
""" Convert a binary IPv4 address into string n.n.n.n form.
|
||||
"""
|
||||
return socket.inet_ntoa(struct.pack("!L", ipbin))
|
||||
|
||||
@@ -263,8 +263,13 @@ class Server:
|
||||
def getIgnoreCommand(self, name):
|
||||
return self.__jails[name].filter.getIgnoreCommand()
|
||||
|
||||
def addFailRegex(self, name, value):
|
||||
self.__jails[name].filter.addFailRegex(value)
|
||||
def addFailRegex(self, name, value, multiple=False):
|
||||
flt = self.__jails[name].filter
|
||||
if multiple:
|
||||
for value in value:
|
||||
flt.addFailRegex(value)
|
||||
else:
|
||||
flt.addFailRegex(value)
|
||||
|
||||
def delFailRegex(self, name, index):
|
||||
self.__jails[name].filter.delFailRegex(index)
|
||||
@@ -272,8 +277,13 @@ class Server:
|
||||
def getFailRegex(self, name):
|
||||
return self.__jails[name].filter.getFailRegex()
|
||||
|
||||
def addIgnoreRegex(self, name, value):
|
||||
self.__jails[name].filter.addIgnoreRegex(value)
|
||||
def addIgnoreRegex(self, name, value, multiple=False):
|
||||
flt = self.__jails[name].filter
|
||||
if multiple:
|
||||
for value in value:
|
||||
flt.addIgnoreRegex(value)
|
||||
else:
|
||||
flt.addIgnoreRegex(value)
|
||||
|
||||
def delIgnoreRegex(self, name, index):
|
||||
self.__jails[name].filter.delIgnoreRegex(index)
|
||||
|
||||
@@ -25,6 +25,7 @@ __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||
__license__ = "GPL"
|
||||
|
||||
from ..helpers import getLogger
|
||||
from ..ipaddr import iparg
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
@@ -32,6 +33,7 @@ logSys = getLogger(__name__)
|
||||
|
||||
class Ticket:
|
||||
|
||||
@iparg
|
||||
def __init__(self, ip, time, matches=None):
|
||||
"""Ticket constructor
|
||||
|
||||
@@ -48,7 +50,9 @@ class Ticket:
|
||||
|
||||
def __str__(self):
|
||||
return "%s: ip=%s time=%s #attempts=%d matches=%r" % \
|
||||
(self.__class__.__name__.split('.')[-1], self.__ip, self.__time, self.__attempt, self.__matches)
|
||||
(self.__class__.__name__.split('.')[-1],
|
||||
self.__ip.ntoa, self.__time, self.__attempt,
|
||||
self.__matches)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
@@ -63,9 +67,6 @@ class Ticket:
|
||||
return False
|
||||
|
||||
def setIP(self, value):
|
||||
if isinstance(value, basestring):
|
||||
# guarantee using regular str instead of unicode for the IP
|
||||
value = str(value)
|
||||
self.__ip = value
|
||||
|
||||
def getIP(self):
|
||||
|
||||
@@ -99,6 +99,8 @@ class Transmitter:
|
||||
return None
|
||||
elif command[0] == "flushlogs":
|
||||
return self.__server.flushLogs()
|
||||
elif command[0] == "multi-set":
|
||||
return self.__commandSet(command[1:], True)
|
||||
elif command[0] == "set":
|
||||
return self.__commandSet(command[1:])
|
||||
elif command[0] == "get":
|
||||
@@ -109,7 +111,7 @@ class Transmitter:
|
||||
return version.version
|
||||
raise Exception("Invalid command")
|
||||
|
||||
def __commandSet(self, command):
|
||||
def __commandSet(self, command, multiple=False):
|
||||
name = command[0]
|
||||
# Logging
|
||||
if name == "loglevel":
|
||||
@@ -196,7 +198,9 @@ class Transmitter:
|
||||
return self.__server.getJournalMatch(name)
|
||||
elif command[1] == "addfailregex":
|
||||
value = command[2]
|
||||
self.__server.addFailRegex(name, value)
|
||||
self.__server.addFailRegex(name, value, multiple=multiple)
|
||||
if multiple:
|
||||
return True
|
||||
return self.__server.getFailRegex(name)
|
||||
elif command[1] == "delfailregex":
|
||||
value = int(command[2])
|
||||
@@ -204,7 +208,9 @@ class Transmitter:
|
||||
return self.__server.getFailRegex(name)
|
||||
elif command[1] == "addignoreregex":
|
||||
value = command[2]
|
||||
self.__server.addIgnoreRegex(name, value)
|
||||
self.__server.addIgnoreRegex(name, value, multiple=multiple)
|
||||
if multiple:
|
||||
return True
|
||||
return self.__server.getIgnoreRegex(name)
|
||||
elif command[1] == "delignoreregex":
|
||||
value = int(command[2])
|
||||
@@ -254,15 +260,26 @@ class Transmitter:
|
||||
return None
|
||||
elif command[1] == "action":
|
||||
actionname = command[2]
|
||||
actionkey = command[3]
|
||||
action = self.__server.getAction(name, actionname)
|
||||
if callable(getattr(action, actionkey, None)):
|
||||
actionvalue = json.loads(command[4]) if len(command)>4 else {}
|
||||
return getattr(action, actionkey)(**actionvalue)
|
||||
if multiple:
|
||||
for cmd in command[3]:
|
||||
actionkey = cmd[0]
|
||||
if callable(getattr(action, actionkey, None)):
|
||||
actionvalue = json.loads(cmd[1]) if len(cmd)>1 else {}
|
||||
getattr(action, actionkey)(**actionvalue)
|
||||
else:
|
||||
actionvalue = cmd[1]
|
||||
setattr(action, actionkey, actionvalue)
|
||||
return True
|
||||
else:
|
||||
actionvalue = command[4]
|
||||
setattr(action, actionkey, actionvalue)
|
||||
return getattr(action, actionkey)
|
||||
actionkey = command[3]
|
||||
if callable(getattr(action, actionkey, None)):
|
||||
actionvalue = json.loads(command[4]) if len(command)>4 else {}
|
||||
return getattr(action, actionkey)(**actionvalue)
|
||||
else:
|
||||
actionvalue = command[4]
|
||||
setattr(action, actionkey, actionvalue)
|
||||
return getattr(action, actionkey)
|
||||
raise Exception("Invalid command (no set action or not yet implemented)")
|
||||
|
||||
def __commandGet(self, command):
|
||||
|
||||
@@ -29,6 +29,8 @@ import unittest
|
||||
from ..server.banmanager import BanManager
|
||||
from ..server.ticket import BanTicket
|
||||
from .utils import assert_dict_equal
|
||||
from ..ipaddr import IPAddr
|
||||
|
||||
|
||||
class AddFailure(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
|
||||
# vi: set ft=python sts=4 ts=4 sw=4 noet :
|
||||
|
||||
# This file is part of Fail2Ban.
|
||||
#
|
||||
# Fail2Ban is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fail2Ban is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Fail2Ban; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
__author__ = "Alexander Koeppe"
|
||||
__copyright__ = "Copyright (c) 2016 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
|
||||
__license__ = "GPL"
|
||||
|
||||
import unittest
|
||||
|
||||
from ..client.beautifier import Beautifier
|
||||
from ..version import version
|
||||
from ..ipaddr import IPAddr
|
||||
|
||||
class BeautifierTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
""" Call before every test case """
|
||||
self.b = Beautifier()
|
||||
|
||||
def tearDown(self):
|
||||
""" Call after every test case """
|
||||
|
||||
def testGetInputCmd(self):
|
||||
cmd = ["test"]
|
||||
self.b.setInputCmd(cmd)
|
||||
self.assertEqual(self.b.getInputCmd(), cmd)
|
||||
|
||||
def testPing(self):
|
||||
self.b.setInputCmd(["ping"])
|
||||
self.assertEqual(self.b.beautify("pong"), "Server replied: pong")
|
||||
|
||||
def testVersion(self):
|
||||
self.b.setInputCmd(["version"])
|
||||
self.assertEqual(self.b.beautify(version), version)
|
||||
|
||||
def testAddJail(self):
|
||||
self.b.setInputCmd(["add"])
|
||||
self.assertEqual(self.b.beautify("ssh"), "Added jail ssh")
|
||||
|
||||
def testStartJail(self):
|
||||
self.b.setInputCmd(["start"])
|
||||
self.assertEqual(self.b.beautify(None), "Jail started")
|
||||
|
||||
def testFlushLogs(self):
|
||||
self.b.setInputCmd(["flushlogs"])
|
||||
self.assertEqual(self.b.beautify("rolled over"), "logs: rolled over")
|
||||
|
||||
def testStopJail(self):
|
||||
self.b.setInputCmd(["stop", "ssh"])
|
||||
self.assertEqual(self.b.beautify(None), "Jail stopped")
|
||||
|
||||
def testShutdown(self):
|
||||
self.b.setInputCmd(["stop"])
|
||||
self.assertEqual(self.b.beautify(None), "Shutdown successful")
|
||||
|
||||
def testStatus(self):
|
||||
self.b.setInputCmd(["status"])
|
||||
response = (("Number of jails", 0), ("Jail list", ["ssh", "exim4"]))
|
||||
output = "Status\n|- Number of jails:\t0\n`- Jail list:\tssh exim4"
|
||||
self.assertEqual(self.b.beautify(response), output)
|
||||
|
||||
self.b.setInputCmd(["status", "ssh"])
|
||||
response = (
|
||||
("Filter", [
|
||||
("Currently failed", 0),
|
||||
("Total failed", 0),
|
||||
("File list", "/var/log/auth.log")
|
||||
]
|
||||
),
|
||||
("Actions", [
|
||||
("Currently banned", 3),
|
||||
("Total banned", 3),
|
||||
("Banned IP list", [
|
||||
IPAddr("192.168.0.1"),
|
||||
IPAddr("::ffff:10.2.2.1"),
|
||||
IPAddr("2001:db8::1")
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
output = """Status for the jail: ssh
|
||||
|- Filter
|
||||
| |- Currently failed: 0
|
||||
| |- Total failed: 0
|
||||
| `- File list: /var/log/auth.log
|
||||
`- Actions
|
||||
|- Currently banned: 3
|
||||
|- Total banned: 3
|
||||
`- Banned IP list: 192.168.0.1 10.2.2.1 2001:db8::1"""
|
||||
|
||||
self.assertEqual(self.b.beautify(response), output)
|
||||
|
||||
@@ -273,7 +273,15 @@ class JailReaderTest(LogCaptureTestCase):
|
||||
# convert and get stream
|
||||
stream = jail.convert()
|
||||
# get action and retrieve agent from it, compare with agent saved in version:
|
||||
act = [o for o in stream if len(o) > 4 and (o[4] == 'agent' or o[4].endswith('badips.py'))]
|
||||
act = []
|
||||
for cmd in stream:
|
||||
if len(cmd) <= 4:
|
||||
continue
|
||||
# differentiate between set and multi-set (wrop it here to single set):
|
||||
if cmd[0] == 'set' and (cmd[4] == 'agent' or cmd[4].endswith('badips.py')):
|
||||
act.append(cmd)
|
||||
elif cmd[0] == 'multi-set':
|
||||
act.extend([['set'] + cmd[1:4] + o for o in cmd[4] if o[0] == 'agent'])
|
||||
useragent = 'Fail2Ban/%s' % version
|
||||
self.assertEqual(len(act), 4)
|
||||
self.assertEqual(act[0], ['set', 'blocklisttest', 'action', 'blocklist_de', 'agent', useragent])
|
||||
@@ -309,23 +317,21 @@ class FilterReaderTest(unittest.TestCase):
|
||||
self.__share_cfg = {}
|
||||
|
||||
def testConvert(self):
|
||||
output = [['set', 'testcase01', 'addfailregex',
|
||||
output = [['multi-set', 'testcase01', 'addfailregex', [
|
||||
"^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )"
|
||||
"?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|"
|
||||
"[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:"
|
||||
"error: PAM: )?Authentication failure for .* from <HOST>\\s*$"],
|
||||
['set', 'testcase01', 'addfailregex',
|
||||
"error: PAM: )?Authentication failure for .* from <HOST>\\s*$",
|
||||
"^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )"
|
||||
"?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|"
|
||||
"[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:"
|
||||
"error: PAM: )?User not known to the underlying authentication mo"
|
||||
"dule for .* from <HOST>\\s*$"],
|
||||
['set', 'testcase01', 'addfailregex',
|
||||
"dule for .* from <HOST>\\s*$",
|
||||
"^\\s*(?:\\S+ )?(?:kernel: \\[\\d+\\.\\d+\\] )?(?:@vserver_\\S+ )"
|
||||
"?(?:(?:\\[\\d+\\])?:\\s+[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?|"
|
||||
"[\\[\\(]?sshd(?:\\(\\S+\\))?[\\]\\)]?:?(?:\\[\\d+\\])?:)?\\s*(?:"
|
||||
"error: PAM: )?User not known to the\\nunderlying authentication."
|
||||
"+$<SKIPLINES>^.+ module for .* from <HOST>\\s*$"],
|
||||
"+$<SKIPLINES>^.+ module for .* from <HOST>\\s*$"]],
|
||||
['set', 'testcase01', 'addignoreregex',
|
||||
"^.+ john from host 192.168.1.1\\s*$"],
|
||||
['set', 'testcase01', 'addjournalmatch',
|
||||
@@ -493,9 +499,11 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||
self.assertEqual(sorted(comm_commands),
|
||||
sorted([['add', 'emptyaction', 'auto'],
|
||||
['add', 'test-known-interp', 'auto'],
|
||||
['set', 'test-known-interp', 'addfailregex', 'failure test 1 (filter.d/test.conf) <HOST>'],
|
||||
['set', 'test-known-interp', 'addfailregex', 'failure test 2 (filter.d/test.local) <HOST>'],
|
||||
['set', 'test-known-interp', 'addfailregex', 'failure test 3 (jail.local) <HOST>'],
|
||||
['multi-set', 'test-known-interp', 'addfailregex', [
|
||||
'failure test 1 (filter.d/test.conf) <HOST>',
|
||||
'failure test 2 (filter.d/test.local) <HOST>',
|
||||
'failure test 3 (jail.local) <HOST>'
|
||||
]],
|
||||
['start', 'test-known-interp'],
|
||||
['add', 'missinglogfiles', 'auto'],
|
||||
['set', 'missinglogfiles', 'addfailregex', '<IP>'],
|
||||
@@ -658,12 +666,16 @@ class JailsReaderTest(LogCaptureTestCase):
|
||||
self.assertTrue('blocktype' in action._initOpts)
|
||||
# Verify that we have a call to set it up
|
||||
blocktype_present = False
|
||||
target_command = ['set', jail_name, 'action', action_name, 'blocktype']
|
||||
target_command = [jail_name, 'action', action_name]
|
||||
for command in commands:
|
||||
if (len(command) > 5 and
|
||||
command[:5] == target_command):
|
||||
blocktype_present = True
|
||||
continue
|
||||
if (len(command) > 4 and command[0] == 'multi-set' and
|
||||
command[1:4] == target_command):
|
||||
blocktype_present = ('blocktype' in [cmd[0] for cmd in command[4]])
|
||||
elif (len(command) > 5 and command[0] == 'set' and
|
||||
command[1:4] == target_command and command[4] == 'blocktype'): # pragma: no cover - because of multi-set
|
||||
blocktype_present = True
|
||||
if blocktype_present:
|
||||
break
|
||||
self.assertTrue(
|
||||
blocktype_present,
|
||||
msg="Found no %s command among %s"
|
||||
|
||||
@@ -28,6 +28,7 @@ import unittest
|
||||
|
||||
from ..server.failmanager import FailManager, FailManagerEmpty
|
||||
from ..server.ticket import FailTicket
|
||||
from ..server.filter import IPAddr
|
||||
|
||||
|
||||
class AddFailure(unittest.TestCase):
|
||||
@@ -89,7 +90,7 @@ class AddFailure(unittest.TestCase):
|
||||
#ticket = FailTicket('193.168.0.128', None)
|
||||
ticket = self.__failManager.toBan()
|
||||
self.assertEqual(ticket.getIP(), "193.168.0.128")
|
||||
self.assertTrue(isinstance(ticket.getIP(), str))
|
||||
self.assertTrue(isinstance(ticket.getIP(), IPAddr))
|
||||
|
||||
# finish with rudimentary tests of the ticket
|
||||
# verify consistent str
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2
|
||||
Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2
|
||||
Aug 14 11:56:59 i60p295 sshd[12365]: Failed publickey for roehl from 172.31.0.34 port 51332 ssh2
|
||||
Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:172.31.0.34 port 51332 ssh2
|
||||
|
||||
@@ -41,6 +41,7 @@ from ..server.filterpoll import FilterPoll
|
||||
from ..server.filter import Filter, FileFilter, DNSUtils
|
||||
from ..server.failmanager import FailManagerEmpty
|
||||
from ..server.mytime import MyTime
|
||||
from ..ipaddr import IPAddr
|
||||
from .utils import setUpMyTime, tearDownMyTime, mtimesleep, LogCaptureTestCase
|
||||
from .dummyjail import DummyJail
|
||||
|
||||
@@ -85,8 +86,14 @@ def _assert_equal_entries(utest, found, output, count=None):
|
||||
|
||||
and report helpful failure reports instead of millions of seconds ;)
|
||||
"""
|
||||
utest.assertEqual(found[0], output[0]) # IP
|
||||
utest.assertEqual(found[1], count or output[1]) # count
|
||||
# if DNS is involved, multiple addresses may be returned
|
||||
if isinstance(output[0], list):
|
||||
utest.assertIn(found[0], output[0]) # IP
|
||||
utest.assertEqual(found[1], count or output[1]) # count
|
||||
else:
|
||||
utest.assertEqual(found[0], output[0]) # IP
|
||||
utest.assertEqual(found[1], count or output[1]) # count
|
||||
|
||||
found_time, output_time = \
|
||||
MyTime.localtime(found[2]),\
|
||||
MyTime.localtime(output[2])
|
||||
@@ -963,12 +970,12 @@ class GetFailures(LogCaptureTestCase):
|
||||
|
||||
def testGetFailuresUseDNS(self):
|
||||
# We should still catch failures with usedns = no ;-)
|
||||
output_yes = ('93.184.216.34', 2, 1124013539.0,
|
||||
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2',
|
||||
u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2'])
|
||||
output_yes = (['93.184.216.34', '2606:2800:220:1:248:1893:25c8:1946'], 1, 1124013299.0,
|
||||
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2'])
|
||||
|
||||
output_no = ('93.184.216.34', 1, 1124013539.0,
|
||||
[u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2'])
|
||||
output_no = ('172.31.0.34', 2, 1124013539.0,
|
||||
[u'Aug 14 11:56:59 i60p295 sshd[12365]: Failed publickey for roehl from 172.31.0.34 port 51332 ssh2',
|
||||
u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:172.31.0.34 port 51332 ssh2'])
|
||||
|
||||
# Actually no exception would be raised -- it will be just set to 'no'
|
||||
#self.assertRaises(ValueError,
|
||||
@@ -1067,9 +1074,11 @@ class DNSUtilsTests(unittest.TestCase):
|
||||
res = DNSUtils.textToIp('www.example.com', 'no')
|
||||
self.assertEqual(res, [])
|
||||
res = DNSUtils.textToIp('www.example.com', 'warn')
|
||||
self.assertEqual(res, ['93.184.216.34'])
|
||||
self.assertIn('93.184.216.34', res)
|
||||
self.assertIn('2606:2800:220:1:248:1893:25c8:1946', res)
|
||||
res = DNSUtils.textToIp('www.example.com', 'yes')
|
||||
self.assertEqual(res, ['93.184.216.34'])
|
||||
self.assertIn('93.184.216.34', res)
|
||||
self.assertIn('2606:2800:220:1:248:1893:25c8:1946', res)
|
||||
|
||||
def testTextToIp(self):
|
||||
# Test hostnames
|
||||
@@ -1081,32 +1090,31 @@ class DNSUtilsTests(unittest.TestCase):
|
||||
for s in hostnames:
|
||||
res = DNSUtils.textToIp(s, 'yes')
|
||||
if s == 'www.example.com':
|
||||
self.assertEqual(res, ['93.184.216.34'])
|
||||
self.assertIn('93.184.216.34', res)
|
||||
self.assertIn('2606:2800:220:1:248:1893:25c8:1946', res)
|
||||
else:
|
||||
self.assertEqual(res, [])
|
||||
|
||||
def testIpToName(self):
|
||||
res = DNSUtils.ipToName('8.8.4.4')
|
||||
self.assertEqual(res, 'google-public-dns-b.google.com')
|
||||
res = DNSUtils.ipToName('2001:4860:4860::8844')
|
||||
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)
|
||||
|
||||
def testAddr2bin(self):
|
||||
res = DNSUtils.addr2bin('10.0.0.0')
|
||||
self.assertEqual(res, 167772160L)
|
||||
res = DNSUtils.addr2bin('10.0.0.0', cidr=None)
|
||||
self.assertEqual(res, 167772160L)
|
||||
res = DNSUtils.addr2bin('10.0.0.0', cidr=32L)
|
||||
self.assertEqual(res, 167772160L)
|
||||
res = DNSUtils.addr2bin('10.0.0.1', cidr=32L)
|
||||
self.assertEqual(res, 167772161L)
|
||||
res = DNSUtils.addr2bin('10.0.0.1', cidr=31L)
|
||||
self.assertEqual(res, 167772160L)
|
||||
|
||||
def testBin2addr(self):
|
||||
res = DNSUtils.bin2addr(167772160L)
|
||||
self.assertEqual(res, '10.0.0.0')
|
||||
res = IPAddr('10.0.0.0')
|
||||
self.assertEqual(res.addr, 167772160L)
|
||||
res = IPAddr('10.0.0.0', cidr=None)
|
||||
self.assertEqual(res.addr, 167772160L)
|
||||
res = IPAddr('10.0.0.0', cidr=32L)
|
||||
self.assertEqual(res.addr, 167772160L)
|
||||
res = IPAddr('10.0.0.1', cidr=32L)
|
||||
self.assertEqual(res.addr, 167772161L)
|
||||
res = IPAddr('10.0.0.1', cidr=31L)
|
||||
self.assertEqual(res.addr, 167772160L)
|
||||
|
||||
|
||||
class JailTests(unittest.TestCase):
|
||||
|
||||
@@ -71,14 +71,21 @@ def testSampleRegexsFactory(name):
|
||||
filterConf.getOptions({})
|
||||
|
||||
for opt in filterConf.convert():
|
||||
if opt[2] == "addfailregex":
|
||||
self.filter.addFailRegex(opt[3])
|
||||
elif opt[2] == "maxlines":
|
||||
self.filter.setMaxLines(opt[3])
|
||||
elif opt[2] == "addignoreregex":
|
||||
self.filter.addIgnoreRegex(opt[3])
|
||||
elif opt[2] == "datepattern":
|
||||
self.filter.setDatePattern(opt[3])
|
||||
if opt[0] == 'multi-set':
|
||||
optval = opt[3]
|
||||
elif opt[0] == 'set':
|
||||
optval = [opt[3]]
|
||||
else:
|
||||
continue
|
||||
for optval in optval:
|
||||
if opt[2] == "addfailregex":
|
||||
self.filter.addFailRegex(optval)
|
||||
elif opt[2] == "addignoreregex":
|
||||
self.filter.addIgnoreRegex(optval)
|
||||
elif opt[2] == "maxlines":
|
||||
self.filter.setMaxLines(optval)
|
||||
elif opt[2] == "datepattern":
|
||||
self.filter.setDatePattern(optval)
|
||||
|
||||
self.assertTrue(
|
||||
os.path.isfile(os.path.join(TEST_FILES_DIR, "logs", name)),
|
||||
|
||||
@@ -33,9 +33,11 @@ import sys
|
||||
import platform
|
||||
|
||||
from ..server.failregex import Regex, FailRegex, RegexException
|
||||
from ..server import actions as _actions
|
||||
from ..server.server import Server
|
||||
from ..server.jail import Jail
|
||||
from ..server.jailthread import JailThread
|
||||
from ..ipaddr import IPAddr
|
||||
from .utils import LogCaptureTestCase
|
||||
from ..helpers import getLogger
|
||||
from .. import version
|
||||
@@ -47,6 +49,8 @@ except ImportError: # pragma: no cover
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
|
||||
logSys = getLogger("fail2ban")
|
||||
|
||||
|
||||
class TestServer(Server):
|
||||
def setLogLevel(self, *args, **kwargs):
|
||||
@@ -110,18 +114,23 @@ class TransmitterBase(unittest.TestCase):
|
||||
cmdAdd = "add" + cmd
|
||||
cmdDel = "del" + cmd
|
||||
|
||||
# sorting IPAddr objects and strings differs so that the comparism values
|
||||
# must also be sorted as IPAddr objects
|
||||
# convert to IPAddr objects if values look like a list of IP address strings
|
||||
ips = map(lambda x: IPAddr(x) if IPAddr.searchIP(x) else x , values)
|
||||
|
||||
self.assertEqual(
|
||||
self.transm.proceed(["get", jail, cmd]), (0, []))
|
||||
for n, value in enumerate(values):
|
||||
ret = self.transm.proceed(["set", jail, cmdAdd, value])
|
||||
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(values[:n+1])))
|
||||
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(ips[:n+1])))
|
||||
ret = self.transm.proceed(["get", jail, cmd])
|
||||
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(values[:n+1])))
|
||||
for n, value in enumerate(values):
|
||||
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(ips[:n+1])))
|
||||
for n, value in enumerate(ips):
|
||||
ret = self.transm.proceed(["set", jail, cmdDel, value])
|
||||
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(values[n+1:])))
|
||||
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(ips[n+1:])))
|
||||
ret = self.transm.proceed(["get", jail, cmd])
|
||||
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(values[n+1:])))
|
||||
self.assertEqual((ret[0], sorted(ret[1])), (0, sorted(ips[n+1:])))
|
||||
|
||||
def jailAddDelRegexTest(self, cmd, inValues, outValues, jail):
|
||||
cmdAdd = "add" + cmd
|
||||
@@ -936,3 +945,423 @@ class LoggingTests(LogCaptureTestCase):
|
||||
sys.__excepthook__ = prev_exchook
|
||||
self.assertEqual(len(x), 1)
|
||||
self.assertEqual(x[0][0], RuntimeError)
|
||||
|
||||
|
||||
from clientreadertestcase import ActionReader, JailReader, JailsReader, CONFIG_DIR, STOCK
|
||||
|
||||
class ServerConfigReaderTests(LogCaptureTestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ServerConfigReaderTests, self).__init__(*args, **kwargs)
|
||||
self.__share_cfg = {}
|
||||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
super(ServerConfigReaderTests, self).setUp()
|
||||
self._execCmdLst = []
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
super(ServerConfigReaderTests, self).tearDown()
|
||||
|
||||
def _executeCmd(self, realCmd, timeout=60):
|
||||
for l in realCmd.split('\n'):
|
||||
if not l.startswith('#'):
|
||||
logSys.debug('exec-cmd: `%s`', l)
|
||||
else:
|
||||
logSys.debug(l)
|
||||
return True
|
||||
|
||||
def test_IPAddr(self):
|
||||
self.assertTrue(IPAddr('192.0.2.1').isIPv4)
|
||||
self.assertTrue(IPAddr('2001:DB8::').isIPv6)
|
||||
|
||||
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
|
||||
self.assertTrue(jails.read()) # opens fine
|
||||
self.assertTrue(jails.getOptions()) # reads fine
|
||||
stream = jails.convert(allow_no_files=True)
|
||||
|
||||
server = TestServer()
|
||||
transm = server._Server__transm
|
||||
cmdHandler = transm._Transmitter__commandHandler
|
||||
|
||||
# for cmd in stream:
|
||||
# print(cmd)
|
||||
|
||||
# filter all start commands (we want not start all jails):
|
||||
for cmd in stream:
|
||||
if cmd[0] != 'start':
|
||||
# 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):
|
||||
elif len(cmd) > 3 and cmd[0] == 'set' and cmd[2] == 'addlogpath':
|
||||
cmd[3] = os.path.join(TEST_FILES_DIR, 'logs', cmd[1])
|
||||
# add dummy regex to prevent too long compile of all regexp (we don't use it in this test at all):
|
||||
# [todo sebres] remove `not hasattr(unittest, 'F2B') or `, after merge with "f2b-perfom-prepare-716" ...
|
||||
elif (not hasattr(unittest, 'F2B') or unittest.F2B.fast) and (
|
||||
len(cmd) > 3 and cmd[0] in ('set', 'multi-set') and cmd[2] == 'addfailregex'
|
||||
):
|
||||
cmd[0] = "set"
|
||||
cmd[3] = "DUMMY-REGEX <HOST>"
|
||||
# command to server, use cmdHandler direct instead of `transm.proceed(cmd)`:
|
||||
try:
|
||||
cmdHandler(cmd)
|
||||
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])
|
||||
|
||||
def getDefaultJailStream(self, jail, act):
|
||||
act = act.replace('%(__name__)s', jail)
|
||||
actName, actOpt = JailReader.extractOptions(act)
|
||||
stream = [
|
||||
['add', jail, 'polling'],
|
||||
# ['set', jail, 'addfailregex', 'DUMMY-REGEX <HOST>'],
|
||||
]
|
||||
action = ActionReader(
|
||||
actName, jail, actOpt,
|
||||
share_config=self.__share_cfg, basedir=CONFIG_DIR)
|
||||
self.assertTrue(action.read())
|
||||
action.getOptions({})
|
||||
stream.extend(action.convert())
|
||||
return stream
|
||||
|
||||
def testCheckStockCommandActions(self):
|
||||
# test cases to check valid ipv4/ipv6 action definition, tuple with (('jail', 'action[params]', 'tests', ...)
|
||||
# where tests is a dictionary contains:
|
||||
# 'ip4' - should not be found (logged) on ban/unban of IPv6 (negative test),
|
||||
# 'ip6' - should not be found (logged) on ban/unban of IPv4 (negative test),
|
||||
# 'start', 'stop' - should be found (logged) on action start/stop,
|
||||
# etc.
|
||||
testJailsActions = (
|
||||
# iptables-multiport --
|
||||
('j-w-iptables-mp', 'iptables-multiport[name=%(__name__)s, bantime="600", 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`",
|
||||
"`iptables -w -A f2b-j-w-iptables-mp -j RETURN`",
|
||||
"`iptables -w -I INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`",
|
||||
"`ip6tables -w -N f2b-j-w-iptables-mp`",
|
||||
"`ip6tables -w -A f2b-j-w-iptables-mp -j RETURN`",
|
||||
"`ip6tables -w -I INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`",
|
||||
),
|
||||
'stop': (
|
||||
"`iptables -w -D INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`",
|
||||
"`iptables -w -F f2b-j-w-iptables-mp`",
|
||||
"`iptables -w -X f2b-j-w-iptables-mp`",
|
||||
"`ip6tables -w -D INPUT -p tcp -m multiport --dports http,https -j f2b-j-w-iptables-mp`",
|
||||
"`ip6tables -w -F f2b-j-w-iptables-mp`",
|
||||
"`ip6tables -w -X f2b-j-w-iptables-mp`",
|
||||
),
|
||||
'ip4-check': (
|
||||
r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-mp[ \t]'`""",
|
||||
),
|
||||
'ip6-check': (
|
||||
r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-mp[ \t]'`""",
|
||||
),
|
||||
'ip4-ban': (
|
||||
r"`iptables -w -I f2b-j-w-iptables-mp 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
|
||||
),
|
||||
'ip4-unban': (
|
||||
r"`iptables -w -D f2b-j-w-iptables-mp -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
|
||||
),
|
||||
'ip6-ban': (
|
||||
r"`ip6tables -w -I f2b-j-w-iptables-mp 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
),
|
||||
'ip6-unban': (
|
||||
r"`ip6tables -w -D f2b-j-w-iptables-mp -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
),
|
||||
}),
|
||||
# iptables-allports --
|
||||
('j-w-iptables-ap', 'iptables-allports[name=%(__name__)s, bantime="600", protocol="tcp", chain="INPUT"]', {
|
||||
'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'),
|
||||
'start': (
|
||||
"`iptables -w -N f2b-j-w-iptables-ap`",
|
||||
"`iptables -w -A f2b-j-w-iptables-ap -j RETURN`",
|
||||
"`iptables -w -I INPUT -p tcp -j f2b-j-w-iptables-ap`",
|
||||
"`ip6tables -w -N f2b-j-w-iptables-ap`",
|
||||
"`ip6tables -w -A f2b-j-w-iptables-ap -j RETURN`",
|
||||
"`ip6tables -w -I INPUT -p tcp -j f2b-j-w-iptables-ap`",
|
||||
),
|
||||
'stop': (
|
||||
"`iptables -w -D INPUT -p tcp -j f2b-j-w-iptables-ap`",
|
||||
"`iptables -w -F f2b-j-w-iptables-ap`",
|
||||
"`iptables -w -X f2b-j-w-iptables-ap`",
|
||||
"`ip6tables -w -D INPUT -p tcp -j f2b-j-w-iptables-ap`",
|
||||
"`ip6tables -w -F f2b-j-w-iptables-ap`",
|
||||
"`ip6tables -w -X f2b-j-w-iptables-ap`",
|
||||
),
|
||||
'ip4-check': (
|
||||
r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-ap[ \t]'`""",
|
||||
),
|
||||
'ip6-check': (
|
||||
r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-ap[ \t]'`""",
|
||||
),
|
||||
'ip4-ban': (
|
||||
r"`iptables -w -I f2b-j-w-iptables-ap 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
|
||||
),
|
||||
'ip4-unban': (
|
||||
r"`iptables -w -D f2b-j-w-iptables-ap -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
|
||||
),
|
||||
'ip6-ban': (
|
||||
r"`ip6tables -w -I f2b-j-w-iptables-ap 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
),
|
||||
'ip6-unban': (
|
||||
r"`ip6tables -w -D f2b-j-w-iptables-ap -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
),
|
||||
}),
|
||||
# iptables-ipset-proto6 --
|
||||
('j-w-iptables-ipset', 'iptables-ipset-proto6[name=%(__name__)s, bantime="600", 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`",
|
||||
"`iptables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`",
|
||||
"`ipset create f2b-j-w-iptables-ipset6 hash:ip timeout 600 family inet6`",
|
||||
"`ip6tables -w -I INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
),
|
||||
'stop': (
|
||||
"`iptables -w -D INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset src -j REJECT --reject-with icmp-port-unreachable`",
|
||||
"`ipset flush f2b-j-w-iptables-ipset`",
|
||||
"`ipset destroy f2b-j-w-iptables-ipset`",
|
||||
"`ip6tables -w -D INPUT -p tcp -m multiport --dports http -m set --match-set f2b-j-w-iptables-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
"`ipset flush f2b-j-w-iptables-ipset6`",
|
||||
"`ipset destroy f2b-j-w-iptables-ipset6`",
|
||||
),
|
||||
'ip4-check': (),
|
||||
'ip6-check': (),
|
||||
'ip4-ban': (
|
||||
r"`ipset add f2b-j-w-iptables-ipset 192.0.2.1 timeout 600 -exist`",
|
||||
),
|
||||
'ip4-unban': (
|
||||
r"`ipset del f2b-j-w-iptables-ipset 192.0.2.1 -exist`",
|
||||
),
|
||||
'ip6-ban': (
|
||||
r"`ipset add f2b-j-w-iptables-ipset6 2001:db8:: timeout 600 -exist`",
|
||||
),
|
||||
'ip6-unban': (
|
||||
r"`ipset del f2b-j-w-iptables-ipset6 2001:db8:: -exist`",
|
||||
),
|
||||
}),
|
||||
# iptables-ipset-proto6-allports --
|
||||
('j-w-iptables-ipset-ap', 'iptables-ipset-proto6-allports[name=%(__name__)s, bantime="600", 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`",
|
||||
"`iptables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`",
|
||||
"`ipset create f2b-j-w-iptables-ipset-ap6 hash:ip timeout 600 family inet6`",
|
||||
"`ip6tables -w -I INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
),
|
||||
'stop': (
|
||||
"`iptables -w -D INPUT -m set --match-set f2b-j-w-iptables-ipset-ap src -j REJECT --reject-with icmp-port-unreachable`",
|
||||
"`ipset flush f2b-j-w-iptables-ipset-ap`",
|
||||
"`ipset destroy f2b-j-w-iptables-ipset-ap`",
|
||||
"`ip6tables -w -D INPUT -m set --match-set f2b-j-w-iptables-ipset-ap6 src -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
"`ipset flush f2b-j-w-iptables-ipset-ap6`",
|
||||
"`ipset destroy f2b-j-w-iptables-ipset-ap6`",
|
||||
),
|
||||
'ip4-check': (),
|
||||
'ip6-check': (),
|
||||
'ip4-ban': (
|
||||
r"`ipset add f2b-j-w-iptables-ipset-ap 192.0.2.1 timeout 600 -exist`",
|
||||
),
|
||||
'ip4-unban': (
|
||||
r"`ipset del f2b-j-w-iptables-ipset-ap 192.0.2.1 -exist`",
|
||||
),
|
||||
'ip6-ban': (
|
||||
r"`ipset add f2b-j-w-iptables-ipset-ap6 2001:db8:: timeout 600 -exist`",
|
||||
),
|
||||
'ip6-unban': (
|
||||
r"`ipset del f2b-j-w-iptables-ipset-ap6 2001:db8:: -exist`",
|
||||
),
|
||||
}),
|
||||
# iptables --
|
||||
('j-w-iptables', 'iptables[name=%(__name__)s, bantime="600", port="http", protocol="tcp", chain="INPUT"]', {
|
||||
'ip4': ('`iptables ', 'icmp-port-unreachable'), 'ip6': ('`ip6tables ', 'icmp6-port-unreachable'),
|
||||
'start': (
|
||||
"`iptables -w -N f2b-j-w-iptables`",
|
||||
"`iptables -w -A f2b-j-w-iptables -j RETURN`",
|
||||
"`iptables -w -I INPUT -p tcp --dport http -j f2b-j-w-iptables`",
|
||||
"`ip6tables -w -N f2b-j-w-iptables`",
|
||||
"`ip6tables -w -A f2b-j-w-iptables -j RETURN`",
|
||||
"`ip6tables -w -I INPUT -p tcp --dport http -j f2b-j-w-iptables`",
|
||||
),
|
||||
'stop': (
|
||||
"`iptables -w -D INPUT -p tcp --dport http -j f2b-j-w-iptables`",
|
||||
"`iptables -w -F f2b-j-w-iptables`",
|
||||
"`iptables -w -X f2b-j-w-iptables`",
|
||||
"`ip6tables -w -D INPUT -p tcp --dport http -j f2b-j-w-iptables`",
|
||||
"`ip6tables -w -F f2b-j-w-iptables`",
|
||||
"`ip6tables -w -X f2b-j-w-iptables`",
|
||||
),
|
||||
'ip4-check': (
|
||||
r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables[ \t]'`""",
|
||||
),
|
||||
'ip6-check': (
|
||||
r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables[ \t]'`""",
|
||||
),
|
||||
'ip4-ban': (
|
||||
r"`iptables -w -I f2b-j-w-iptables 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
|
||||
),
|
||||
'ip4-unban': (
|
||||
r"`iptables -w -D f2b-j-w-iptables -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
|
||||
),
|
||||
'ip6-ban': (
|
||||
r"`ip6tables -w -I f2b-j-w-iptables 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
),
|
||||
'ip6-unban': (
|
||||
r"`ip6tables -w -D f2b-j-w-iptables -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
),
|
||||
}),
|
||||
# iptables-new --
|
||||
('j-w-iptables-new', 'iptables-new[name=%(__name__)s, bantime="600", 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`",
|
||||
"`iptables -w -A f2b-j-w-iptables-new -j RETURN`",
|
||||
"`iptables -w -I INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`",
|
||||
"`ip6tables -w -N f2b-j-w-iptables-new`",
|
||||
"`ip6tables -w -A f2b-j-w-iptables-new -j RETURN`",
|
||||
"`ip6tables -w -I INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`",
|
||||
),
|
||||
'stop': (
|
||||
"`iptables -w -D INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`",
|
||||
"`iptables -w -F f2b-j-w-iptables-new`",
|
||||
"`iptables -w -X f2b-j-w-iptables-new`",
|
||||
"`ip6tables -w -D INPUT -m state --state NEW -p tcp --dport http -j f2b-j-w-iptables-new`",
|
||||
"`ip6tables -w -F f2b-j-w-iptables-new`",
|
||||
"`ip6tables -w -X f2b-j-w-iptables-new`",
|
||||
),
|
||||
'ip4-check': (
|
||||
r"""`iptables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-new[ \t]'`""",
|
||||
),
|
||||
'ip6-check': (
|
||||
r"""`ip6tables -w -n -L INPUT | grep -q 'f2b-j-w-iptables-new[ \t]'`""",
|
||||
),
|
||||
'ip4-ban': (
|
||||
r"`iptables -w -I f2b-j-w-iptables-new 1 -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
|
||||
),
|
||||
'ip4-unban': (
|
||||
r"`iptables -w -D f2b-j-w-iptables-new -s 192.0.2.1 -j REJECT --reject-with icmp-port-unreachable`",
|
||||
),
|
||||
'ip6-ban': (
|
||||
r"`ip6tables -w -I f2b-j-w-iptables-new 1 -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
),
|
||||
'ip6-unban': (
|
||||
r"`ip6tables -w -D f2b-j-w-iptables-new -s 2001:db8:: -j REJECT --reject-with icmp6-port-unreachable`",
|
||||
),
|
||||
}),
|
||||
# iptables-xt_recent-echo --
|
||||
('j-w-iptables-xtre', 'iptables-xt_recent-echo[name=%(__name__)s, bantime="600", 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`",
|
||||
"`if [ `id -u` -eq 0 ];then ip6tables -w -I INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre6 -j REJECT --reject-with icmp6-port-unreachable;fi`",
|
||||
),
|
||||
'stop': (
|
||||
"`echo / > /proc/net/xt_recent/f2b-j-w-iptables-xtre`",
|
||||
"`if [ `id -u` -eq 0 ];then iptables -w -D INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre -j REJECT --reject-with icmp-port-unreachable;fi`",
|
||||
"`echo / > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`",
|
||||
"`if [ `id -u` -eq 0 ];then ip6tables -w -D INPUT -m recent --update --seconds 3600 --name f2b-j-w-iptables-xtre6 -j REJECT --reject-with icmp6-port-unreachable;fi`",
|
||||
),
|
||||
'ip4-check': (
|
||||
r"`test -e /proc/net/xt_recent/f2b-j-w-iptables-xtre`",
|
||||
),
|
||||
'ip6-check': (
|
||||
r"`test -e /proc/net/xt_recent/f2b-j-w-iptables-xtre6`",
|
||||
),
|
||||
'ip4-ban': (
|
||||
r"`echo +192.0.2.1 > /proc/net/xt_recent/f2b-j-w-iptables-xtre`",
|
||||
),
|
||||
'ip4-unban': (
|
||||
r"`echo -192.0.2.1 > /proc/net/xt_recent/f2b-j-w-iptables-xtre`",
|
||||
),
|
||||
'ip6-ban': (
|
||||
r"`echo +2001:db8:: > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`",
|
||||
),
|
||||
'ip6-unban': (
|
||||
r"`echo -2001:db8:: > /proc/net/xt_recent/f2b-j-w-iptables-xtre6`",
|
||||
),
|
||||
}),
|
||||
# pf --
|
||||
('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-`',
|
||||
),
|
||||
'stop': (
|
||||
'`pfctl -sr 2>/dev/null | grep -v f2b-j-w-pf | pfctl -f-`',
|
||||
'`pfctl -t f2b-j-w-pf -T flush`',
|
||||
'`pfctl -t f2b-j-w-pf -T kill`',
|
||||
),
|
||||
'ip4-check': ("`pfctl -sr | grep -q f2b-j-w-pf`",),
|
||||
'ip6-check': ("`pfctl -sr | grep -q f2b-j-w-pf`",),
|
||||
'ip4-ban': ("`pfctl -t f2b-j-w-pf -T add 192.0.2.1`",),
|
||||
'ip4-unban': ("`pfctl -t f2b-j-w-pf -T delete 192.0.2.1`",),
|
||||
'ip6-ban': ("`pfctl -t f2b-j-w-pf -T add 2001:db8::`",),
|
||||
'ip6-unban': ("`pfctl -t f2b-j-w-pf -T delete 2001:db8::`",),
|
||||
}),
|
||||
)
|
||||
server = TestServer()
|
||||
transm = server._Server__transm
|
||||
cmdHandler = transm._Transmitter__commandHandler
|
||||
|
||||
for jail, act, tests in testJailsActions:
|
||||
stream = self.getDefaultJailStream(jail, act)
|
||||
|
||||
# for cmd in stream:
|
||||
# print(cmd)
|
||||
|
||||
# filter all start commands (we want not start all jails):
|
||||
for cmd in stream:
|
||||
# command to server:
|
||||
ret, res = transm.proceed(cmd)
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
jails = server._Server__jails
|
||||
|
||||
for jail, act, tests in testJailsActions:
|
||||
# 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))
|
||||
self.assertTrue(isinstance(action, _actions.CommandAction))
|
||||
# wrap default command processor:
|
||||
action.executeCmd = self._executeCmd
|
||||
# test start :
|
||||
logSys.debug('# === start ==='); self.pruneLog()
|
||||
action.start()
|
||||
self.assertLogged(*tests['start'], all=True)
|
||||
# test ban ip4 :
|
||||
logSys.debug('# === ban-ipv4 ==='); self.pruneLog()
|
||||
action.ban({'ip': IPAddr('192.0.2.1')})
|
||||
self.assertLogged(*tests['ip4-check']+tests['ip4-ban'], all=True)
|
||||
self.assertNotLogged(*tests['ip6'], all=True)
|
||||
# test unban ip4 :
|
||||
logSys.debug('# === unban ipv4 ==='); self.pruneLog()
|
||||
action.unban({'ip': IPAddr('192.0.2.1')})
|
||||
self.assertLogged(*tests['ip4-check']+tests['ip4-unban'], all=True)
|
||||
self.assertNotLogged(*tests['ip6'], all=True)
|
||||
# test ban ip6 :
|
||||
logSys.debug('# === ban ipv6 ==='); self.pruneLog()
|
||||
action.ban({'ip': IPAddr('2001:DB8::')})
|
||||
self.assertLogged(*tests['ip6-check']+tests['ip6-ban'], all=True)
|
||||
self.assertNotLogged(*tests['ip4'], all=True)
|
||||
# test unban ip6 :
|
||||
logSys.debug('# === unban ipv6 ==='); self.pruneLog()
|
||||
action.unban({'ip': IPAddr('2001:DB8::')})
|
||||
self.assertLogged(*tests['ip6-check']+tests['ip6-unban'], all=True)
|
||||
self.assertNotLogged(*tests['ip4'], all=True)
|
||||
# test stop :
|
||||
logSys.debug('# === stop ==='); self.pruneLog()
|
||||
action.stop()
|
||||
self.assertLogged(*tests['stop'], all=True)
|
||||
|
||||
|
||||
+34
-10
@@ -74,6 +74,7 @@ def gatherTests(regexps=None, no_network=False):
|
||||
# Import all the test cases here instead of a module level to
|
||||
# avoid circular imports
|
||||
from . import banmanagertestcase
|
||||
from . import clientbeautifiertestcase
|
||||
from . import clientreadertestcase
|
||||
from . import failmanagertestcase
|
||||
from . import filtertestcase
|
||||
@@ -108,6 +109,7 @@ def gatherTests(regexps=None, no_network=False):
|
||||
tests.addTest(unittest.makeSuite(servertestcase.JailTests))
|
||||
tests.addTest(unittest.makeSuite(servertestcase.RegexTests))
|
||||
tests.addTest(unittest.makeSuite(servertestcase.LoggingTests))
|
||||
tests.addTest(unittest.makeSuite(servertestcase.ServerConfigReaderTests))
|
||||
tests.addTest(unittest.makeSuite(actiontestcase.CommandActionTest))
|
||||
tests.addTest(unittest.makeSuite(actionstestcase.ExecuteActions))
|
||||
# FailManager
|
||||
@@ -119,6 +121,10 @@ def gatherTests(regexps=None, no_network=False):
|
||||
tests.addTest(unittest.makeSuite(banmanagertestcase.StatusExtendedCymruInfo))
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# ClientBeautifier
|
||||
tests.addTest(unittest.makeSuite(clientbeautifiertestcase.BeautifierTest))
|
||||
|
||||
# ClientReaders
|
||||
tests.addTest(unittest.makeSuite(clientreadertestcase.ConfigReaderTest))
|
||||
tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest))
|
||||
@@ -248,7 +254,7 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||
def _is_logged(self, s):
|
||||
return s in self._log.getvalue()
|
||||
|
||||
def assertLogged(self, *s):
|
||||
def assertLogged(self, *s, **kwargs):
|
||||
"""Assert that one of the strings was logged
|
||||
|
||||
Preferable to assertTrue(self._is_logged(..)))
|
||||
@@ -258,14 +264,23 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||
----------
|
||||
s : string or list/set/tuple of strings
|
||||
Test should succeed if string (or any of the listed) is present in the log
|
||||
all : boolean (default False) if True should fail if any of s not logged
|
||||
"""
|
||||
logged = self._log.getvalue()
|
||||
for s_ in s:
|
||||
if s_ in logged:
|
||||
return
|
||||
raise AssertionError("None among %r was found in the log: %r" % (s, logged))
|
||||
if not kwargs.get('all', False):
|
||||
# at least one entry should be found:
|
||||
for s_ in s:
|
||||
if s_ in logged:
|
||||
return
|
||||
if True: # pragma: no cover
|
||||
self.fail("None among %r was found in the log: ===\n%s===" % (s, logged))
|
||||
else:
|
||||
# each entry should be found:
|
||||
for s_ in s:
|
||||
if s_ not in logged: # pragma: no cover
|
||||
self.fail("%r was not found in the log: ===\n%s===" % (s_, logged))
|
||||
|
||||
def assertNotLogged(self, *s):
|
||||
def assertNotLogged(self, *s, **kwargs):
|
||||
"""Assert that strings were not logged
|
||||
|
||||
Parameters
|
||||
@@ -273,13 +288,22 @@ class LogCaptureTestCase(unittest.TestCase):
|
||||
s : string or list/set/tuple of strings
|
||||
Test should succeed if the string (or at least one of the listed) is not
|
||||
present in the log
|
||||
all : boolean (default False) if True should fail if any of s logged
|
||||
"""
|
||||
logged = self._log.getvalue()
|
||||
for s_ in s:
|
||||
if s_ not in logged:
|
||||
return
|
||||
raise AssertionError("All of the %r were found present in the log: %r" % (s, logged))
|
||||
if not kwargs.get('all', False):
|
||||
for s_ in s:
|
||||
if s_ not in logged:
|
||||
return
|
||||
if True: # pragma: no cover
|
||||
self.fail("All of the %r were found present in the log: ===\n%s===" % (s, logged))
|
||||
else:
|
||||
for s_ in s:
|
||||
if s_ in logged: # pragma: no cover
|
||||
self.fail("%r was found in the log: ===\n%s===" % (s_, logged))
|
||||
|
||||
def pruneLog(self):
|
||||
self._log.truncate(0)
|
||||
|
||||
def getLog(self):
|
||||
return self._log.getvalue()
|
||||
|
||||
@@ -269,17 +269,32 @@ The following commands can be present in the [Definition] section.
|
||||
.B actionstart
|
||||
command(s) executed when the jail starts.
|
||||
.TP
|
||||
.B actionstart6
|
||||
IPv6 specific equivalent for actionstart
|
||||
.TP
|
||||
.B actionstop
|
||||
command(s) executed when the jail stops.
|
||||
.TP
|
||||
.B actionstop6
|
||||
IPv6 specific equivalent for actionstop
|
||||
.TP
|
||||
.B actioncheck
|
||||
command(s) ran before any other action. It aims to verify if the environment is still ok.
|
||||
.TP
|
||||
.B actioncheck6
|
||||
IPv6 specific equivalent for actioncheck
|
||||
.TP
|
||||
.B actionban
|
||||
command(s) that bans the IP address after \fBmaxretry\fR log lines matches within last \fBfindtime\fR seconds.
|
||||
.TP
|
||||
.B actionban6
|
||||
IPv6 specific equivalent for actionban
|
||||
.TP
|
||||
.B actionunban
|
||||
command(s) that unbans the IP address after \fBbantime\fR.
|
||||
.TP
|
||||
.B actionunban6
|
||||
IPv6 specific equivalent for actionunban
|
||||
.PP
|
||||
The [Init] section allows for action-specific settings. In \fIjail.conf/jail.local\fR these can be overwritten for a particular jail as options to the jail. The following are special tags which can be set in the [Init] section:
|
||||
.TP
|
||||
|
||||
Reference in New Issue
Block a user