Merge remote-tracking branch 'remotes/upstream/master' into sebres:ban-time-incr;

normalize code to python >= 2.6;
pull/716/head
sebres 2014-10-25 19:05:53 +02:00
commit 361c220846
16 changed files with 111 additions and 92 deletions

View File

@ -95,6 +95,7 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger
not affect SYSLOG output not affect SYSLOG output
* Log unhandled exceptions * Log unhandled exceptions
* cyrus-imap: catch "user not found" attempts * cyrus-imap: catch "user not found" attempts
* Add support for Portsentry
ver. 0.9.0 (2014/03/14) - beta ver. 0.9.0 (2014/03/14) - beta
---------- ----------

12
DEVELOP
View File

@ -81,6 +81,18 @@ some quick commands::
status test status test
Testing with vagrant
--------------------
Testing can now be done inside a vagrant VM. Vagrantfile provided in
source code repository established two VMs:
- VM "secure" which can be used for testing fail2ban code.
- VM "attacker" which hcan be used to perform attack against our "secure" VM.
Both VMs are sharing the 192.168.200/24 network. If you are using this network
take a look into the Vagrantfile and change the IP.
Coding Standards Coding Standards
================ ================

1
THANKS
View File

@ -26,6 +26,7 @@ Christian Rauch
Christophe Carles Christophe Carles
Christoph Haas Christoph Haas
Christos Psonis Christos Psonis
craneworks
Cyril Jaquier Cyril Jaquier
Daniel B. Cid Daniel B. Cid
Daniel B. Daniel B.

View File

@ -419,12 +419,11 @@ class Fail2banClient:
ret = False ret = False
return ret return ret
#@staticmethod @staticmethod
def dumpConfig(cmd): def dumpConfig(cmd):
for c in cmd: for c in cmd:
print c print c
return True return True
dumpConfig = staticmethod(dumpConfig)
class ServerExecutionException(Exception): class ServerExecutionException(Exception):

View File

@ -0,0 +1,10 @@
# Fail2Ban filter for failure attempts in Counter Strike-1.6
#
#
[Definition]
failregex = \/<HOST> Port\: [0-9]+ (TCP|UDP) Blocked$
# Author: Pacop <pacoparu@gmail.com>

View File

@ -757,3 +757,8 @@ banaction = iptables-allports
enabled = false enabled = false
logpath = /var/log/directadmin/login.log logpath = /var/log/directadmin/login.log
port = 2222 port = 2222
[portsentry]
enabled = false
logpath = /var/lib/portsentry/portsentry.history
maxretry = 1

View File

@ -77,7 +77,7 @@ class ConfigReader():
""" """
# already shared ? # already shared ?
if not self._cfg: if not self._cfg:
self.touch(name) self._create_unshared(name)
# performance feature - read once if using shared config reader: # performance feature - read once if using shared config reader:
if once and self._cfg.read_cfg_files is not None: if once and self._cfg.read_cfg_files is not None:
return self._cfg.read_cfg_files return self._cfg.read_cfg_files
@ -90,7 +90,7 @@ class ConfigReader():
self._cfg.read_cfg_files = ret self._cfg.read_cfg_files = ret
return ret return ret
def touch(self, name=''): def _create_unshared(self, name=''):
""" Allocates and share a config file by it name. """ Allocates and share a config file by it name.
Automatically allocates unshared or reuses shared handle by given 'name' and Automatically allocates unshared or reuses shared handle by given 'name' and
@ -268,7 +268,7 @@ class DefinitionInitConfigReader(ConfigReader):
# needed for fail2ban-regex that doesn't need fancy directories # needed for fail2ban-regex that doesn't need fancy directories
def readexplicit(self): def readexplicit(self):
if not self._cfg: if not self._cfg:
self.touch(self._file) self._create_unshared(self._file)
return SafeConfigParserWithIncludes.read(self._cfg, self._file) return SafeConfigParserWithIncludes.read(self._cfg, self._file)
def getOptions(self, pOpts): def getOptions(self, pOpts):

View File

@ -57,7 +57,7 @@ class CSocket:
self.__csock.close() self.__csock.close()
return ret return ret
#@staticmethod @staticmethod
def receive(sock): def receive(sock):
msg = EMPTY_BYTES msg = EMPTY_BYTES
while msg.rfind(CSocket.END_STRING) == -1: while msg.rfind(CSocket.END_STRING) == -1:
@ -66,4 +66,3 @@ class CSocket:
raise RuntimeError, "socket connection broken" raise RuntimeError, "socket connection broken"
msg = msg + chunk msg = msg + chunk
return loads(msg) return loads(msg)
receive = staticmethod(receive)

View File

@ -229,7 +229,7 @@ class JailReader(ConfigReader):
stream.insert(0, ["add", self.__name, backend]) stream.insert(0, ["add", self.__name, backend])
return stream return stream
#@staticmethod @staticmethod
def extractOptions(option): def extractOptions(option):
match = JailReader.optionCRE.match(option) match = JailReader.optionCRE.match(option)
if not match: if not match:
@ -244,4 +244,3 @@ class JailReader(ConfigReader):
val for val in optmatch.group(2,3,4) if val is not None][0] val for val in optmatch.group(2,3,4) if val is not None][0]
option_opts[opt.strip()] = value.strip() option_opts[opt.strip()] = value.strip()
return option_name, option_opts return option_name, option_opts
extractOptions = staticmethod(extractOptions)

View File

@ -149,12 +149,8 @@ class AsyncServer(asyncore.dispatcher):
self.__init = True self.__init = True
# TODO Add try..catch # TODO Add try..catch
# There's a bug report for Python 2.6/3.0 that use_poll=True yields some 2.5 incompatibilities: # There's a bug report for Python 2.6/3.0 that use_poll=True yields some 2.5 incompatibilities:
if sys.version_info >= (2, 6): # if python 2.6 or greater... logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll")
logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll") asyncore.loop(use_poll=False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
asyncore.loop(use_poll = False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
else: # pragma: no cover
logSys.debug("NOT Python 2.6/3.* - asyncore.loop() using poll")
asyncore.loop(use_poll = True)
## ##
# Stops the communication server. # Stops the communication server.
@ -175,12 +171,11 @@ class AsyncServer(asyncore.dispatcher):
# @param sock: socket file. # @param sock: socket file.
#@staticmethod @staticmethod
def __markCloseOnExec(sock): def __markCloseOnExec(sock):
fd = sock.fileno() fd = sock.fileno()
flags = fcntl.fcntl(fd, fcntl.F_GETFD) flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC) fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC)
__markCloseOnExec = staticmethod(__markCloseOnExec)
## ##
# AsyncServerException is used to wrap communication exceptions. # AsyncServerException is used to wrap communication exceptions.

View File

@ -126,7 +126,7 @@ class BanManager:
# @param ticket the FailTicket # @param ticket the FailTicket
# @return a BanTicket # @return a BanTicket
#@staticmethod @staticmethod
def createBanTicket(ticket): def createBanTicket(ticket):
ip = ticket.getIP() ip = ticket.getIP()
# if ticked was restored from database - set time of original restored ticket: # if ticked was restored from database - set time of original restored ticket:
@ -138,7 +138,6 @@ class BanManager:
banTicket = BanTicket(ip, lastTime, ticket.getMatches()) banTicket = BanTicket(ip, lastTime, ticket.getMatches())
banTicket.setAttempt(ticket.getAttempt()) banTicket.setAttempt(ticket.getAttempt())
return banTicket return banTicket
createBanTicket = staticmethod(createBanTicket)
## ##
# Add a ban ticket. # Add a ban ticket.

View File

@ -932,7 +932,7 @@ class DNSUtils:
IP_CRE = re.compile("^(?:\d{1,3}\.){3}\d{1,3}$") IP_CRE = re.compile("^(?:\d{1,3}\.){3}\d{1,3}$")
#@staticmethod @staticmethod
def dnsToIp(dns): def dnsToIp(dns):
""" Convert a DNS into an IP address using the Python socket module. """ Convert a DNS into an IP address using the Python socket module.
Thanks to Kevin Drapel. Thanks to Kevin Drapel.
@ -947,9 +947,8 @@ class DNSUtils:
logSys.warning("Socket error raised trying to resolve hostname %s: %s" logSys.warning("Socket error raised trying to resolve hostname %s: %s"
% (dns, e)) % (dns, e))
return list() return list()
dnsToIp = staticmethod(dnsToIp)
#@staticmethod @staticmethod
def searchIP(text): def searchIP(text):
""" Search if an IP address if directly available and return """ Search if an IP address if directly available and return
it. it.
@ -959,9 +958,8 @@ class DNSUtils:
return match return match
else: else:
return None return None
searchIP = staticmethod(searchIP)
#@staticmethod @staticmethod
def isValidIP(string): def isValidIP(string):
""" Return true if str is a valid IP """ Return true if str is a valid IP
""" """
@ -971,9 +969,8 @@ class DNSUtils:
return True return True
except socket.error: except socket.error:
return False return False
isValidIP = staticmethod(isValidIP)
#@staticmethod @staticmethod
def textToIp(text, useDns): def textToIp(text, useDns):
""" Return the IP of DNS found in a given text. """ Return the IP of DNS found in a given text.
""" """
@ -995,9 +992,8 @@ class DNSUtils:
text, ipList) text, ipList)
return ipList return ipList
textToIp = staticmethod(textToIp)
#@staticmethod @staticmethod
def cidr(i, n): def cidr(i, n):
""" Convert an IP address string with a CIDR mask into a 32-bit """ Convert an IP address string with a CIDR mask into a 32-bit
integer. integer.
@ -1005,18 +1001,15 @@ class DNSUtils:
# 32-bit IPv4 address mask # 32-bit IPv4 address mask
MASK = 0xFFFFFFFFL MASK = 0xFFFFFFFFL
return ~(MASK >> n) & MASK & DNSUtils.addr2bin(i) return ~(MASK >> n) & MASK & DNSUtils.addr2bin(i)
cidr = staticmethod(cidr)
#@staticmethod @staticmethod
def addr2bin(string): def addr2bin(string):
""" Convert a string IPv4 address into an unsigned integer. """ Convert a string IPv4 address into an unsigned integer.
""" """
return struct.unpack("!L", socket.inet_aton(string))[0] return struct.unpack("!L", socket.inet_aton(string))[0]
addr2bin = staticmethod(addr2bin)
#@staticmethod @staticmethod
def bin2addr(addr): def bin2addr(addr):
""" Convert a numeric IPv4 address into string n.n.n.n form. """ Convert a numeric IPv4 address into string n.n.n.n form.
""" """
return socket.inet_ntoa(struct.pack("!L", addr)) return socket.inet_ntoa(struct.pack("!L", addr))
bin2addr = staticmethod(bin2addr)

View File

@ -26,89 +26,95 @@ import time, datetime, re
## ##
# MyTime class. # MyTime class.
# #
# This class is a wrapper around time.time() and time.gmtime(). When
# performing unit test, it is very useful to get a fixed value from these
# functions.
# Thus, time.time() and time.gmtime() should never be called directly.
# This wrapper should be called instead. The API are equivalent.
class MyTime: class MyTime:
"""A wrapper around time module primarily for testing purposes
This class is a wrapper around time.time() and time.gmtime(). When
performing unit test, it is very useful to get a fixed value from
these functions. Thus, time.time() and time.gmtime() should never
be called directly. This wrapper should be called instead. The API
are equivalent.
"""
myTime = None myTime = None
## @staticmethod
# Sets the current time.
#
# Use None in order to always get the real current time.
#
# @param t the time to set or None
#@staticmethod
def setTime(t): def setTime(t):
"""Set current time.
Use None in order to always get the real current time.
@param t the time to set or None
"""
MyTime.myTime = t MyTime.myTime = t
setTime = staticmethod(setTime)
@staticmethod
##
# Equivalent to time.time()
#
# @return time.time() if setTime was called with None
#@staticmethod
def time(): def time():
"""Decorate time.time() for the purpose of testing mocking
@return time.time() if setTime was called with None
"""
if MyTime.myTime is None: if MyTime.myTime is None:
return time.time() return time.time()
else: else:
return MyTime.myTime return MyTime.myTime
time = staticmethod(time)
@staticmethod
##
# Equivalent to time.gmtime()
#
# @return time.gmtime() if setTime was called with None
#@staticmethod
def gmtime(): def gmtime():
"""Decorate time.gmtime() for the purpose of testing mocking
@return time.gmtime() if setTime was called with None
"""
if MyTime.myTime is None: if MyTime.myTime is None:
return time.gmtime() return time.gmtime()
else: else:
return time.gmtime(MyTime.myTime) return time.gmtime(MyTime.myTime)
gmtime = staticmethod(gmtime)
#@staticmethod @staticmethod
def now(): def now():
"""Decorate datetime.now() for the purpose of testing mocking
@return datetime.now() if setTime was called with None
"""
if MyTime.myTime is None: if MyTime.myTime is None:
return datetime.datetime.now() return datetime.datetime.now()
else: else:
return datetime.datetime.fromtimestamp(MyTime.myTime) return datetime.datetime.fromtimestamp(MyTime.myTime)
now = staticmethod(now)
@staticmethod
def localtime(x=None): def localtime(x=None):
"""Decorate time.localtime() for the purpose of testing mocking
@return time.localtime() if setTime was called with None
"""
if MyTime.myTime is None or x is not None: if MyTime.myTime is None or x is not None:
return time.localtime(x) return time.localtime(x)
else: else:
return time.localtime(MyTime.myTime) return time.localtime(MyTime.myTime)
localtime = staticmethod(localtime)
##
# Wraps string expression like "1h 2m 3s" into number contains seconds (3723).
# The string expression will be evaluated as mathematical expression, spaces between each groups
# will be wrapped to "+" operand (only if any operand does not specified between).
# Because of case insensitivity and overwriting with minutes ("m" or "mm"), the short replacement for month
# are "mo" or "mon".
# Ex: 1hour+30min = 5400
# 0d 1h 30m = 5400
# 1year-6mo = 15778800
# 6 months = 15778800
# warn: month is not 30 days, it is a year in seconds / 12, the leap years will be respected also:
# >>>> float(Test.str2seconds("1month")) / 60 / 60 / 24
# 30.4375
# >>>> float(Test.str2seconds("1year")) / 60 / 60 / 24
# 365.25
#
# @returns number (calculated seconds from expression "val")
#@staticmethod @staticmethod
def str2seconds(val): def str2seconds(val):
"""Wraps string expression like "1h 2m 3s" into number contains seconds (3723).
The string expression will be evaluated as mathematical expression, spaces between each groups
will be wrapped to "+" operand (only if any operand does not specified between).
Because of case insensitivity and overwriting with minutes ("m" or "mm"), the short replacement for month
are "mo" or "mon".
Ex: 1hour+30min = 5400
0d 1h 30m = 5400
1year-6mo = 15778800
6 months = 15778800
warn: month is not 30 days, it is a year in seconds / 12, the leap years will be respected also:
>>>> float(Test.str2seconds("1month")) / 60 / 60 / 24
30.4375
>>>> float(Test.str2seconds("1year")) / 60 / 60 / 24
365.25
@returns number (calculated seconds from expression "val")
"""
if isinstance(val, (int, long, float, complex)): if isinstance(val, (int, long, float, complex)):
return val return val
# replace together standing abbreviations, example '1d12h' -> '1d 12h': # replace together standing abbreviations, example '1d12h' -> '1d 12h':
@ -122,4 +128,3 @@ class MyTime:
val = re.sub(r"(?i)(?<=[\d\s])(%s)\b" % rexp, "*"+str(rpl), val) val = re.sub(r"(?i)(?<=[\d\s])(%s)\b" % rexp, "*"+str(rpl), val)
val = re.sub(r"(\d)\s+(\d)", r"\1+\2", val); val = re.sub(r"(\d)\s+(\d)", r"\1+\2", val);
return eval(val) return eval(val)
str2seconds = staticmethod(str2seconds)

View File

@ -86,6 +86,7 @@ class DateDetectorTest(unittest.TestCase):
(False, "23-Jan-2005 21:59:59.02"), (False, "23-Jan-2005 21:59:59.02"),
(False, "23-Jan-2005 21:59:59 +0100"), (False, "23-Jan-2005 21:59:59 +0100"),
(False, "23-01-2005 21:59:59"), (False, "23-01-2005 21:59:59"),
(True, "1106513999"), # Portsetry
(False, "01-23-2005 21:59:59.252"), # reported on f2b, causes Feb29 fix to break (False, "01-23-2005 21:59:59.252"), # reported on f2b, causes Feb29 fix to break
(False, "@4000000041f4104f00000000"), # TAI64N (False, "@4000000041f4104f00000000"), # TAI64N
(False, "2005-01-23T20:59:59.252Z"), #ISO 8601 (UTC) (False, "2005-01-23T20:59:59.252Z"), #ISO 8601 (UTC)

View File

@ -0,0 +1,4 @@
# failJSON: { "time": "2014-06-27T17:51:19", "match": true , "host": "192.168.56.1" }
1403884279 - 06/27/2014 17:51:19 Host: 192.168.56.1/192.168.56.1 Port: 1 TCP Blocked
# failJSON: { "time": "2014-06-27T17:51:19", "match": true , "host": "192.168.56.1" }
1403884279 - 06/27/2014 17:51:19 Host: 192.168.56.1/192.168.56.1 Port: 1 UDP Blocked

View File

@ -24,11 +24,7 @@ __license__ = "GPL"
import unittest, sys, os, fileinput, re, time, datetime, inspect import unittest, sys, os, fileinput, re, time, datetime, inspect
if sys.version_info >= (2, 6): import json
import json
else:
import simplejson as json
next = lambda x: x.next()
from ..server.filter import Filter from ..server.filter import Filter
from ..client.filterreader import FilterReader from ..client.filterreader import FilterReader