diff options
-rw-r--r-- | ChangeLog | 1 | ||||
-rw-r--r-- | DEVELOP | 12 | ||||
-rw-r--r-- | THANKS | 1 | ||||
-rwxr-xr-x | bin/fail2ban-client | 3 | ||||
-rw-r--r-- | config/filter.d/portsentry.conf | 10 | ||||
-rw-r--r-- | config/jail.conf | 5 | ||||
-rw-r--r-- | fail2ban/client/configreader.py | 6 | ||||
-rw-r--r-- | fail2ban/client/csocket.py | 3 | ||||
-rw-r--r-- | fail2ban/client/jailreader.py | 3 | ||||
-rw-r--r-- | fail2ban/server/asyncserver.py | 11 | ||||
-rw-r--r-- | fail2ban/server/banmanager.py | 3 | ||||
-rw-r--r-- | fail2ban/server/filter.py | 21 | ||||
-rw-r--r-- | fail2ban/server/mytime.py | 117 | ||||
-rw-r--r-- | fail2ban/tests/datedetectortestcase.py | 1 | ||||
-rw-r--r-- | fail2ban/tests/files/logs/portsentry | 4 | ||||
-rw-r--r-- | fail2ban/tests/samplestestcase.py | 6 |
16 files changed, 113 insertions, 94 deletions
@@ -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 ---------- @@ -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 ================ @@ -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 |