summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog1
-rw-r--r--DEVELOP12
-rw-r--r--THANKS1
-rwxr-xr-xbin/fail2ban-client3
-rw-r--r--config/filter.d/portsentry.conf10
-rw-r--r--config/jail.conf5
-rw-r--r--fail2ban/client/configreader.py6
-rw-r--r--fail2ban/client/csocket.py3
-rw-r--r--fail2ban/client/jailreader.py3
-rw-r--r--fail2ban/server/asyncserver.py11
-rw-r--r--fail2ban/server/banmanager.py3
-rw-r--r--fail2ban/server/filter.py21
-rw-r--r--fail2ban/server/mytime.py117
-rw-r--r--fail2ban/tests/datedetectortestcase.py1
-rw-r--r--fail2ban/tests/files/logs/portsentry4
-rw-r--r--fail2ban/tests/samplestestcase.py6
16 files changed, 113 insertions, 94 deletions
diff --git a/ChangeLog b/ChangeLog
index 6473f47a..dd15a238 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -95,6 +95,7 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger
not affect SYSLOG output
* Log unhandled exceptions
* cyrus-imap: catch "user not found" attempts
+ * Add support for Portsentry
ver. 0.9.0 (2014/03/14) - beta
----------
diff --git a/DEVELOP b/DEVELOP
index f1426561..1384a6ac 100644
--- a/DEVELOP
+++ b/DEVELOP
@@ -81,6 +81,18 @@ some quick commands::
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
================
diff --git a/THANKS b/THANKS
index e8f628b8..33f7ea16 100644
--- a/THANKS
+++ b/THANKS
@@ -26,6 +26,7 @@ Christian Rauch
Christophe Carles
Christoph Haas
Christos Psonis
+craneworks
Cyril Jaquier
Daniel B. Cid
Daniel B.
diff --git a/bin/fail2ban-client b/bin/fail2ban-client
index 0c6999c1..866a5287 100755
--- a/bin/fail2ban-client
+++ b/bin/fail2ban-client
@@ -419,12 +419,11 @@ class Fail2banClient:
ret = False
return ret
- #@staticmethod
+ @staticmethod
def dumpConfig(cmd):
for c in cmd:
print c
return True
- dumpConfig = staticmethod(dumpConfig)
class ServerExecutionException(Exception):
diff --git a/config/filter.d/portsentry.conf b/config/filter.d/portsentry.conf
new file mode 100644
index 00000000..1ee9531c
--- /dev/null
+++ b/config/filter.d/portsentry.conf
@@ -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>
+
diff --git a/config/jail.conf b/config/jail.conf
index 955ba44a..6255d7f1 100644
--- a/config/jail.conf
+++ b/config/jail.conf
@@ -757,3 +757,8 @@ banaction = iptables-allports
enabled = false
logpath = /var/log/directadmin/login.log
port = 2222
+
+[portsentry]
+enabled = false
+logpath = /var/lib/portsentry/portsentry.history
+maxretry = 1
diff --git a/fail2ban/client/configreader.py b/fail2ban/client/configreader.py
index d9bfb271..b6c39628 100644
--- a/fail2ban/client/configreader.py
+++ b/fail2ban/client/configreader.py
@@ -77,7 +77,7 @@ class ConfigReader():
"""
# already shared ?
if not self._cfg:
- self.touch(name)
+ self._create_unshared(name)
# performance feature - read once if using shared config reader:
if once and self._cfg.read_cfg_files is not None:
return self._cfg.read_cfg_files
@@ -90,7 +90,7 @@ class ConfigReader():
self._cfg.read_cfg_files = ret
return ret
- def touch(self, name=''):
+ def _create_unshared(self, name=''):
""" Allocates and share a config file by it name.
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
def readexplicit(self):
if not self._cfg:
- self.touch(self._file)
+ self._create_unshared(self._file)
return SafeConfigParserWithIncludes.read(self._cfg, self._file)
def getOptions(self, pOpts):
diff --git a/fail2ban/client/csocket.py b/fail2ban/client/csocket.py
index 1d522f6c..921b0de5 100644
--- a/fail2ban/client/csocket.py
+++ b/fail2ban/client/csocket.py
@@ -57,7 +57,7 @@ class CSocket:
self.__csock.close()
return ret
- #@staticmethod
+ @staticmethod
def receive(sock):
msg = EMPTY_BYTES
while msg.rfind(CSocket.END_STRING) == -1:
@@ -66,4 +66,3 @@ class CSocket:
raise RuntimeError, "socket connection broken"
msg = msg + chunk
return loads(msg)
- receive = staticmethod(receive)
diff --git a/fail2ban/client/jailreader.py b/fail2ban/client/jailreader.py
index 356db607..eee6cd7c 100644
--- a/fail2ban/client/jailreader.py
+++ b/fail2ban/client/jailreader.py
@@ -229,7 +229,7 @@ class JailReader(ConfigReader):
stream.insert(0, ["add", self.__name, backend])
return stream
- #@staticmethod
+ @staticmethod
def extractOptions(option):
match = JailReader.optionCRE.match(option)
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]
option_opts[opt.strip()] = value.strip()
return option_name, option_opts
- extractOptions = staticmethod(extractOptions)
diff --git a/fail2ban/server/asyncserver.py b/fail2ban/server/asyncserver.py
index 14673a99..a54d41a1 100644
--- a/fail2ban/server/asyncserver.py
+++ b/fail2ban/server/asyncserver.py
@@ -149,12 +149,8 @@ class AsyncServer(asyncore.dispatcher):
self.__init = True
# TODO Add try..catch
# 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")
- 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)
+ 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
##
# Stops the communication server.
@@ -175,12 +171,11 @@ class AsyncServer(asyncore.dispatcher):
# @param sock: socket file.
- #@staticmethod
+ @staticmethod
def __markCloseOnExec(sock):
fd = sock.fileno()
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC)
- __markCloseOnExec = staticmethod(__markCloseOnExec)
##
# AsyncServerException is used to wrap communication exceptions.
diff --git a/fail2ban/server/banmanager.py b/fail2ban/server/banmanager.py
index 021d235e..36653c7e 100644
--- a/fail2ban/server/banmanager.py
+++ b/fail2ban/server/banmanager.py
@@ -126,7 +126,7 @@ class BanManager:
# @param ticket the FailTicket
# @return a BanTicket
- #@staticmethod
+ @staticmethod
def createBanTicket(ticket):
ip = ticket.getIP()
# 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.setAttempt(ticket.getAttempt())
return banTicket
- createBanTicket = staticmethod(createBanTicket)
##
# Add a ban ticket.
diff --git a/fail2ban/server/filter.py b/fail2ban/server/filter.py
index deada0e0..9428e750 100644
--- a/fail2ban/server/filter.py
+++ b/fail2ban/server/filter.py
@@ -932,7 +932,7 @@ class DNSUtils:
IP_CRE = re.compile("^(?:\d{1,3}\.){3}\d{1,3}$")
- #@staticmethod
+ @staticmethod
def dnsToIp(dns):
""" Convert a DNS into an IP address using the Python socket module.
Thanks to Kevin Drapel.
@@ -947,9 +947,8 @@ class DNSUtils:
logSys.warning("Socket error raised trying to resolve hostname %s: %s"
% (dns, e))
return list()
- dnsToIp = staticmethod(dnsToIp)
- #@staticmethod
+ @staticmethod
def searchIP(text):
""" Search if an IP address if directly available and return
it.
@@ -959,9 +958,8 @@ class DNSUtils:
return match
else:
return None
- searchIP = staticmethod(searchIP)
- #@staticmethod
+ @staticmethod
def isValidIP(string):
""" Return true if str is a valid IP
"""
@@ -971,9 +969,8 @@ class DNSUtils:
return True
except socket.error:
return False
- isValidIP = staticmethod(isValidIP)
- #@staticmethod
+ @staticmethod
def textToIp(text, useDns):
""" Return the IP of DNS found in a given text.
"""
@@ -995,9 +992,8 @@ class DNSUtils:
text, ipList)
return ipList
- textToIp = staticmethod(textToIp)
- #@staticmethod
+ @staticmethod
def cidr(i, n):
""" Convert an IP address string with a CIDR mask into a 32-bit
integer.
@@ -1005,18 +1001,15 @@ class DNSUtils:
# 32-bit IPv4 address mask
MASK = 0xFFFFFFFFL
return ~(MASK >> n) & MASK & DNSUtils.addr2bin(i)
- cidr = staticmethod(cidr)
- #@staticmethod
+ @staticmethod
def addr2bin(string):
""" Convert a string IPv4 address into an unsigned integer.
"""
return struct.unpack("!L", socket.inet_aton(string))[0]
- addr2bin = staticmethod(addr2bin)
- #@staticmethod
+ @staticmethod
def bin2addr(addr):
""" Convert a numeric IPv4 address into string n.n.n.n form.
"""
return socket.inet_ntoa(struct.pack("!L", addr))
- bin2addr = staticmethod(bin2addr)
diff --git a/fail2ban/server/mytime.py b/fail2ban/server/mytime.py
index 55142188..f675bcf4 100644
--- a/fail2ban/server/mytime.py
+++ b/fail2ban/server/mytime.py
@@ -26,89 +26,95 @@ import time, datetime, re
##
# 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:
-
+ """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
-
- ##
- # Sets the current time.
- #
- # Use None in order to always get the real current time.
- #
- # @param t the time to set or None
-
- #@staticmethod
+
+ @staticmethod
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
- setTime = staticmethod(setTime)
-
- ##
- # Equivalent to time.time()
- #
- # @return time.time() if setTime was called with None
-
- #@staticmethod
+
+ @staticmethod
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:
return time.time()
else:
return MyTime.myTime
- time = staticmethod(time)
-
- ##
- # Equivalent to time.gmtime()
- #
- # @return time.gmtime() if setTime was called with None
-
- #@staticmethod
+
+ @staticmethod
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:
return time.gmtime()
else:
return time.gmtime(MyTime.myTime)
- gmtime = staticmethod(gmtime)
- #@staticmethod
+ @staticmethod
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:
return datetime.datetime.now()
else:
return datetime.datetime.fromtimestamp(MyTime.myTime)
- now = staticmethod(now)
+ @staticmethod
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:
return time.localtime(x)
else:
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):
+ """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)):
return val
# 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"(\d)\s+(\d)", r"\1+\2", val);
return eval(val)
- str2seconds = staticmethod(str2seconds) \ No newline at end of file
diff --git a/fail2ban/tests/datedetectortestcase.py b/fail2ban/tests/datedetectortestcase.py
index 6f1fe35a..fe5448d4 100644
--- a/fail2ban/tests/datedetectortestcase.py
+++ b/fail2ban/tests/datedetectortestcase.py
@@ -86,6 +86,7 @@ class DateDetectorTest(unittest.TestCase):
(False, "23-Jan-2005 21:59:59.02"),
(False, "23-Jan-2005 21:59:59 +0100"),
(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, "@4000000041f4104f00000000"), # TAI64N
(False, "2005-01-23T20:59:59.252Z"), #ISO 8601 (UTC)
diff --git a/fail2ban/tests/files/logs/portsentry b/fail2ban/tests/files/logs/portsentry
new file mode 100644
index 00000000..19c917a0
--- /dev/null
+++ b/fail2ban/tests/files/logs/portsentry
@@ -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 \ No newline at end of file
diff --git a/fail2ban/tests/samplestestcase.py b/fail2ban/tests/samplestestcase.py
index e0831184..2c18a504 100644
--- a/fail2ban/tests/samplestestcase.py
+++ b/fail2ban/tests/samplestestcase.py
@@ -24,11 +24,7 @@ __license__ = "GPL"
import unittest, sys, os, fileinput, re, time, datetime, inspect
-if sys.version_info >= (2, 6):
- import json
-else:
- import simplejson as json
- next = lambda x: x.next()
+import json
from ..server.filter import Filter
from ..client.filterreader import FilterReader