From 0bcff771b8239498541ab95dc365e6500805d72b Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Fri, 13 Dec 2013 22:35:35 +0000 Subject: [PATCH 1/5] ENH: Add and tags Example use filter also added for sendmail-whois with ipmatches rather than grepped lines --- MANIFEST | 1 + config/action.d/sendmail-whois-ipmatches.conf | 77 +++++++++++++++++++ fail2ban/server/action.py | 17 ++-- fail2ban/server/actions.py | 9 ++- fail2ban/tests/actiontestcase.py | 24 +++++- 5 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 config/action.d/sendmail-whois-ipmatches.conf diff --git a/MANIFEST b/MANIFEST index 25c5d824..e60b2685 100644 --- a/MANIFEST +++ b/MANIFEST @@ -228,6 +228,7 @@ config/action.d/mynetwatchman.conf config/action.d/pf.conf config/action.d/sendmail.conf config/action.d/sendmail-buffered.conf +config/action.d/sendmail-whois-ipmatches.conf config/action.d/sendmail-whois.conf config/action.d/sendmail-whois-lines.conf config/action.d/shorewall.conf diff --git a/config/action.d/sendmail-whois-ipmatches.conf b/config/action.d/sendmail-whois-ipmatches.conf new file mode 100644 index 00000000..3dabf093 --- /dev/null +++ b/config/action.d/sendmail-whois-ipmatches.conf @@ -0,0 +1,77 @@ +# Fail2Ban configuration file +# +# Author: Cyril Jaquier +# +# + +[INCLUDES] + +before = sendmail-common.conf + +[Definition] + +# Option: actionstart +# Notes.: command executed once at the start of Fail2Ban. +# Values: CMD +# +actionstart = printf %%b "Subject: [Fail2Ban] : started on `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The jail has been started successfully.\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +# Option: actionstop +# Notes.: command executed once at the end of Fail2Ban +# Values: CMD +# +actionstop = printf %%b "Subject: [Fail2Ban] : stopped on `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The jail has been stopped.\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +# Option: actioncheck +# Notes.: command executed once before each actionban command +# Values: CMD +# +actioncheck = + +# Option: actionban +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The IP has just been banned by Fail2Ban after + attempts against .\n\n + Here are more information about :\n + `/usr/bin/whois `\n\n + Matches for IP:\n + \n\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +# Option: actionunban +# Notes.: command executed when unbanning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionunban = + +[Init] + +# Default name of the chain +# +name = default diff --git a/fail2ban/server/action.py b/fail2ban/server/action.py index 7e16901c..17f33a6f 100644 --- a/fail2ban/server/action.py +++ b/fail2ban/server/action.py @@ -295,7 +295,7 @@ class Action: #@staticmethod def escapeTag(tag): - for c in '\\#&;`|*?~<>^()[]{}$\n\'"': + for c in '\\#&;`|*?~<>^()[]{}$\'"': if c in tag: tag = tag.replace(c, '\\' + c) return tag @@ -314,12 +314,15 @@ class Action: """ string = query for tag, value in aInfo.iteritems(): - value = str(value) # assure string - if tag == 'matches': - # That one needs to be escaped since its content is - # out of our control - value = Action.escapeTag(value) - string = string.replace('<' + tag + '>', value) + if "<%s>" % tag in query: + if callable(value): + value = value() + value = str(value) # assure string + if tag.endswith('matches'): + # That one needs to be escaped since its content is + # out of our control + value = Action.escapeTag(value) + string = string.replace('<' + tag + '>', value) # New line string = string.replace("
", '\n') return string diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index 520886af..96c1b3c1 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -183,7 +183,14 @@ class Actions(JailThread): aInfo["ip"] = bTicket.getIP() aInfo["failures"] = bTicket.getAttempt() aInfo["time"] = bTicket.getTime() - aInfo["matches"] = "".join(bTicket.getMatches()) + aInfo["matches"] = "\n".join(bTicket.getMatches()) + if self.jail.getDatabase() is not None: + aInfo["ipmatches"] = lambda: "\n".join( + self.jail.getDatabase().getBansMerged( + bTicket.getIP()).getMatches()) + aInfo["ipjailmatches"] = lambda: "\n".join( + self.jail.getDatabase().getBansMerged( + bTicket.getIP(), jail=self.jail).getMatches()) if self.__banManager.addBanTicket(bTicket): logSys.warning("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"])) for action in self.__actions: diff --git a/fail2ban/tests/actiontestcase.py b/fail2ban/tests/actiontestcase.py index e25f4cb0..ad22d2a2 100644 --- a/fail2ban/tests/actiontestcase.py +++ b/fail2ban/tests/actiontestcase.py @@ -102,8 +102,28 @@ class ExecuteAction(unittest.TestCase): "Text 890 text 123 ABC") self.assertEqual( self.__action.replaceTag("", - {'matches': "some >char< should \< be[ escap}ed&"}), - r"some \>char\< should \\\< be\[ escap\}ed\&") + {'matches': "some >char< should \< be[ escap}ed&\n"}), + "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n") + self.assertEqual( + self.__action.replaceTag("", + {'ipmatches': "some >char< should \< be[ escap}ed&\n"}), + "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n") + self.assertEqual( + self.__action.replaceTag("", + {'ipjailmatches': "some >char< should \< be[ escap}ed&\n"}), + "some \\>char\\< should \\\\\\< be\\[ escap\\}ed\\&\n") + + # Callable + self.assertEqual( + self.__action.replaceTag("09 11", + {'callable': lambda: str(10)}), + "09 10 11") + + # As tag not present, therefore callable should not be called + # Will raise ValueError if it is + self.assertEqual( + self.__action.replaceTag("abc", + {'callable': lambda: int("a")}), "abc") def testExecuteActionBan(self): self.__action.setActionStart("touch /tmp/fail2ban.test") From d6cbc05e358d2fccc995cb7f78edf0169a64eabd Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sun, 15 Dec 2013 21:10:11 +0000 Subject: [PATCH 2/5] ENH: Make use of functools.wraps for server.database decorators --- fail2ban/server/database.py | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/fail2ban/server/database.py b/fail2ban/server/database.py index 6b22334e..e3dc8214 100644 --- a/fail2ban/server/database.py +++ b/fail2ban/server/database.py @@ -26,6 +26,7 @@ import sys import sqlite3 import json import locale +from functools import wraps from fail2ban.server.mytime import MyTime from fail2ban.server.ticket import FailTicket @@ -46,13 +47,12 @@ else: sqlite3.register_adapter(dict, json.dumps) sqlite3.register_converter("JSON", json.loads) -def commitandrollback(): - def wrap(f): - def func(self, *args, **kw): - with self._db: # Auto commit and rollback on exception - return f(self, self._db.cursor(), *args, **kw) - return func - return wrap +def commitandrollback(f): + @wraps(f) + def wrapper(self, *args, **kwargs): + with self._db: # Auto commit and rollback on exception + return f(self, self._db.cursor(), *args, **kwargs) + return wrapper class Fail2BanDb(object): __version__ = 1 @@ -97,7 +97,7 @@ class Fail2BanDb(object): def setPurgeAge(self, value): self._purgeAge = int(value) - @commitandrollback() + @commitandrollback def createDb(self, cur): # Version info cur.execute("CREATE TABLE fail2banDb(version INTEGER)") @@ -143,14 +143,14 @@ class Fail2BanDb(object): cur.execute("SELECT version FROM fail2banDb LIMIT 1") return cur.fetchone()[0] - @commitandrollback() + @commitandrollback def updateDb(self, cur, version): raise NotImplementedError( "Only single version of database exists...how did you get here??") cur.execute("SELECT version FROM fail2banDb LIMIT 1") return cur.fetchone()[0] - @commitandrollback() + @commitandrollback def addJail(self, cur, jail): cur.execute( "INSERT OR REPLACE INTO jails(name, enabled) VALUES(?, 1)", @@ -159,23 +159,23 @@ class Fail2BanDb(object): def delJail(self, jail): return self.delJailName(jail.getName()) - @commitandrollback() + @commitandrollback def delJailName(self, cur, name): # Will be deleted by purge as appropriate cur.execute( "UPDATE jails SET enabled=0 WHERE name=?", (name, )) - @commitandrollback() + @commitandrollback def delAllJails(self, cur): # Will be deleted by purge as appropriate cur.execute("UPDATE jails SET enabled=0") - @commitandrollback() + @commitandrollback def getJailNames(self, cur): cur.execute("SELECT name FROM jails") return set(row[0] for row in cur.fetchmany()) - @commitandrollback() + @commitandrollback def addLog(self, cur, jail, container): lastLinePos = None cur.execute( @@ -196,7 +196,7 @@ class Fail2BanDb(object): lastLinePos = None return lastLinePos - @commitandrollback() + @commitandrollback def getLogPaths(self, cur, jail=None): query = "SELECT path FROM logs" queryArgs = [] @@ -206,7 +206,7 @@ class Fail2BanDb(object): cur.execute(query, queryArgs) return set(row[0] for row in cur.fetchmany()) - @commitandrollback() + @commitandrollback def updateLog(self, cur, *args, **kwargs): self._updateLog(cur, *args, **kwargs) @@ -217,7 +217,7 @@ class Fail2BanDb(object): (container.getHash(), container.getPos(), jail.getName(), container.getFileName())) - @commitandrollback() + @commitandrollback def addBan(self, cur, jail, ticket): #TODO: Implement data parts once arbitrary match keys completed cur.execute( @@ -226,7 +226,7 @@ class Fail2BanDb(object): {"matches": ticket.getMatches(), "failures": ticket.getAttempt()})) - @commitandrollback() + @commitandrollback def _getBans(self, cur, jail=None, bantime=None, ip=None): query = "SELECT ip, timeofban, data FROM bans WHERE 1" queryArgs = [] @@ -263,7 +263,7 @@ class Fail2BanDb(object): ticket.setAttempt(failures) return ticket - @commitandrollback() + @commitandrollback def purge(self, cur): cur.execute( "DELETE FROM bans WHERE timeofban < ?", From 40007abc1d1f2aa2ec704bf6bc0dc6680467537f Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sun, 15 Dec 2013 21:41:43 +0000 Subject: [PATCH 3/5] ENH: Refactor and add database matches and failures for sendmail actions --- config/action.d/sendmail-common.conf | 50 +++++++++++++++++++ .../sendmail-whois-ipjailmatches.conf | 37 ++++++++++++++ config/action.d/sendmail-whois-ipmatches.conf | 42 +--------------- config/action.d/sendmail-whois-lines.conf | 40 --------------- config/action.d/sendmail-whois-matches.conf | 37 ++++++++++++++ config/action.d/sendmail-whois.conf | 40 --------------- config/action.d/sendmail.conf | 40 --------------- fail2ban/server/actions.py | 10 +++- fail2ban/server/database.py | 8 +-- fail2ban/tests/databasetestcase.py | 2 +- 10 files changed, 138 insertions(+), 168 deletions(-) create mode 100644 config/action.d/sendmail-whois-ipjailmatches.conf create mode 100644 config/action.d/sendmail-whois-matches.conf diff --git a/config/action.d/sendmail-common.conf b/config/action.d/sendmail-common.conf index e2820470..26dcb4c8 100644 --- a/config/action.d/sendmail-common.conf +++ b/config/action.d/sendmail-common.conf @@ -8,6 +8,56 @@ after = sendmail-common.local +[Definition] + +# Option: actionstart +# Notes.: command executed once at the start of Fail2Ban. +# Values: CMD +# +actionstart = printf %%b "Subject: [Fail2Ban] : started on `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The jail has been started successfully.\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +# Option: actionstop +# Notes.: command executed once at the end of Fail2Ban +# Values: CMD +# +actionstop = printf %%b "Subject: [Fail2Ban] : stopped on `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The jail has been stopped.\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +# Option: actioncheck +# Notes.: command executed once before each actionban command +# Values: CMD +# +actioncheck = + +# Option: actionban +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionban = + +# Option: actionunban +# Notes.: command executed when unbanning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionunban = + [Init] # Recipient mail address diff --git a/config/action.d/sendmail-whois-ipjailmatches.conf b/config/action.d/sendmail-whois-ipjailmatches.conf new file mode 100644 index 00000000..45b1f312 --- /dev/null +++ b/config/action.d/sendmail-whois-ipjailmatches.conf @@ -0,0 +1,37 @@ +# Fail2Ban configuration file +# +# Author: Cyril Jaquier +# +# + +[INCLUDES] + +before = sendmail-common.conf + +[Definition] + +# Option: actionban +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The IP has just been banned by Fail2Ban after + attempts against .\n\n + Here are more information about :\n + `/usr/bin/whois `\n\n + Matches for with failures IP:\n + \n\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +[Init] + +# Default name of the chain +# +name = default diff --git a/config/action.d/sendmail-whois-ipmatches.conf b/config/action.d/sendmail-whois-ipmatches.conf index 3dabf093..8193fb04 100644 --- a/config/action.d/sendmail-whois-ipmatches.conf +++ b/config/action.d/sendmail-whois-ipmatches.conf @@ -10,38 +10,6 @@ before = sendmail-common.conf [Definition] -# Option: actionstart -# Notes.: command executed once at the start of Fail2Ban. -# Values: CMD -# -actionstart = printf %%b "Subject: [Fail2Ban] : started on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been started successfully.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actionstop -# Notes.: command executed once at the end of Fail2Ban -# Values: CMD -# -actionstop = printf %%b "Subject: [Fail2Ban] : stopped on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been stopped.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actioncheck -# Notes.: command executed once before each actionban command -# Values: CMD -# -actioncheck = - # Option: actionban # Notes.: command executed when banning an IP. Take care that the # command is executed with Fail2Ban user rights. @@ -57,19 +25,11 @@ actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` attempts against .\n\n Here are more information about :\n `/usr/bin/whois `\n\n - Matches for IP:\n + Matches with failures IP:\n \n\n Regards,\n Fail2Ban" | /usr/sbin/sendmail -f -# Option: actionunban -# Notes.: command executed when unbanning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionunban = - [Init] # Default name of the chain diff --git a/config/action.d/sendmail-whois-lines.conf b/config/action.d/sendmail-whois-lines.conf index 5a331e24..2ec71aa5 100644 --- a/config/action.d/sendmail-whois-lines.conf +++ b/config/action.d/sendmail-whois-lines.conf @@ -10,38 +10,6 @@ before = sendmail-common.conf [Definition] -# Option: actionstart -# Notes.: command executed once at the start of Fail2Ban. -# Values: CMD -# -actionstart = printf %%b "Subject: [Fail2Ban] : started on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been started successfully.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actionstop -# Notes.: command executed once at the end of Fail2Ban -# Values: CMD -# -actionstop = printf %%b "Subject: [Fail2Ban] : stopped on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been stopped.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actioncheck -# Notes.: command executed once before each actionban command -# Values: CMD -# -actioncheck = - # Option: actionban # Notes.: command executed when banning an IP. Take care that the # command is executed with Fail2Ban user rights. @@ -62,14 +30,6 @@ actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` Regards,\n Fail2Ban" | /usr/sbin/sendmail -f -# Option: actionunban -# Notes.: command executed when unbanning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionunban = - [Init] # Default name of the chain diff --git a/config/action.d/sendmail-whois-matches.conf b/config/action.d/sendmail-whois-matches.conf new file mode 100644 index 00000000..ed664766 --- /dev/null +++ b/config/action.d/sendmail-whois-matches.conf @@ -0,0 +1,37 @@ +# Fail2Ban configuration file +# +# Author: Cyril Jaquier +# +# + +[INCLUDES] + +before = sendmail-common.conf + +[Definition] + +# Option: actionban +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# Tags: See jail.conf(5) man page +# Values: CMD +# +actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` + Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` + From: <> + To: \n + Hi,\n + The IP has just been banned by Fail2Ban after + attempts against .\n\n + Here are more information about :\n + `/usr/bin/whois `\n\n + Matches:\n + \n\n + Regards,\n + Fail2Ban" | /usr/sbin/sendmail -f + +[Init] + +# Default name of the chain +# +name = default diff --git a/config/action.d/sendmail-whois.conf b/config/action.d/sendmail-whois.conf index a65f9875..d6a7c3c1 100644 --- a/config/action.d/sendmail-whois.conf +++ b/config/action.d/sendmail-whois.conf @@ -10,38 +10,6 @@ before = sendmail-common.conf [Definition] -# Option: actionstart -# Notes.: command executed once at the start of Fail2Ban. -# Values: CMD -# -actionstart = printf %%b "Subject: [Fail2Ban] : started on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been started successfully.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actionstop -# Notes.: command executed once at the end of Fail2Ban -# Values: CMD -# -actionstop = printf %%b "Subject: [Fail2Ban] : stopped on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been stopped.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actioncheck -# Notes.: command executed once before each actionban command -# Values: CMD -# -actioncheck = - # Option: actionban # Notes.: command executed when banning an IP. Take care that the # command is executed with Fail2Ban user rights. @@ -60,14 +28,6 @@ actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` Regards,\n Fail2Ban" | /usr/sbin/sendmail -f -# Option: actionunban -# Notes.: command executed when unbanning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionunban = - [Init] # Default name of the chain diff --git a/config/action.d/sendmail.conf b/config/action.d/sendmail.conf index 70f38329..46050e11 100644 --- a/config/action.d/sendmail.conf +++ b/config/action.d/sendmail.conf @@ -10,38 +10,6 @@ before = sendmail-common.conf [Definition] -# Option: actionstart -# Notes.: command executed once at the start of Fail2Ban. -# Values: CMD -# -actionstart = printf %%b "Subject: [Fail2Ban] : started on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been started successfully.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actionstop -# Notes.: command executed once at the end of Fail2Ban -# Values: CMD -# -actionstop = printf %%b "Subject: [Fail2Ban] : stopped on `uname -n` - Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"` - From: <> - To: \n - Hi,\n - The jail has been stopped.\n - Regards,\n - Fail2Ban" | /usr/sbin/sendmail -f - -# Option: actioncheck -# Notes.: command executed once before each actionban command -# Values: CMD -# -actioncheck = - # Option: actionban # Notes.: command executed when banning an IP. Take care that the # command is executed with Fail2Ban user rights. @@ -58,14 +26,6 @@ actionban = printf %%b "Subject: [Fail2Ban] : banned from `uname -n` Regards,\n Fail2Ban" | /usr/sbin/sendmail -f -# Option: actionunban -# Notes.: command executed when unbanning an IP. Take care that the -# command is executed with Fail2Ban user rights. -# Tags: See jail.conf(5) man page -# Values: CMD -# -actionunban = - [Init] # Default name of the chain diff --git a/fail2ban/server/actions.py b/fail2ban/server/actions.py index 96c1b3c1..af2b4670 100644 --- a/fail2ban/server/actions.py +++ b/fail2ban/server/actions.py @@ -187,10 +187,16 @@ class Actions(JailThread): if self.jail.getDatabase() is not None: aInfo["ipmatches"] = lambda: "\n".join( self.jail.getDatabase().getBansMerged( - bTicket.getIP()).getMatches()) + ip=bTicket.getIP()).getMatches()) aInfo["ipjailmatches"] = lambda: "\n".join( self.jail.getDatabase().getBansMerged( - bTicket.getIP(), jail=self.jail).getMatches()) + ip=bTicket.getIP(), jail=self.jail).getMatches()) + aInfo["ipfailures"] = lambda: "\n".join( + self.jail.getDatabase().getBansMerged( + ip=bTicket.getIP()).getAttempt()) + aInfo["ipjailfailures"] = lambda: "\n".join( + self.jail.getDatabase().getBansMerged( + ip=bTicket.getIP(), jail=self.jail).getAttempt()) if self.__banManager.addBanTicket(bTicket): logSys.warning("[%s] Ban %s" % (self.jail.getName(), aInfo["ip"])) for action in self.__actions: diff --git a/fail2ban/server/database.py b/fail2ban/server/database.py index e3dc8214..997f1c21 100644 --- a/fail2ban/server/database.py +++ b/fail2ban/server/database.py @@ -244,18 +244,18 @@ class Fail2BanDb(object): return cur.execute(query, queryArgs) - def getBans(self, *args, **kwargs): + def getBans(self, **kwargs): tickets = [] - for ip, timeofban, data in self._getBans(*args, **kwargs): + for ip, timeofban, data in self._getBans(**kwargs): #TODO: Implement data parts once arbitrary match keys completed tickets.append(FailTicket(ip, timeofban, data['matches'])) tickets[-1].setAttempt(data['failures']) return tickets - def getBansMerged(self, ip, *args, **kwargs): + def getBansMerged(self, ip, jail=None, **kwargs): matches = [] failures = 0 - for ip, timeofban, data in self._getBans(*args, ip=ip, **kwargs): + for ip, timeofban, data in self._getBans(ip=ip, jail=jail, **kwargs): #TODO: Implement data parts once arbitrary match keys completed matches.extend(data['matches']) failures += data['failures'] diff --git a/fail2ban/tests/databasetestcase.py b/fail2ban/tests/databasetestcase.py index 2c7422b2..44f7a59e 100644 --- a/fail2ban/tests/databasetestcase.py +++ b/fail2ban/tests/databasetestcase.py @@ -127,7 +127,7 @@ class DatabaseTest(unittest.TestCase): ticket = FailTicket("127.0.0.1", 0, ["abc\n"]) self.db.addBan(self.jail, ticket) - self.assertEquals(len(self.db.getBans(self.jail)), 1) + self.assertEquals(len(self.db.getBans(jail=self.jail)), 1) self.assertTrue( isinstance(self.db.getBans(jail=self.jail)[0], FailTicket)) From fb7511fdea47862f52ffb1d33a1ea49852e7e685 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sun, 15 Dec 2013 21:52:50 +0000 Subject: [PATCH 4/5] ENH: Add cache for database getBansMerged This is avoids duplicate queries when using the ip(jail)matches and ip(jail)failures in actions --- fail2ban/server/database.py | 8 ++++++++ fail2ban/tests/databasetestcase.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/fail2ban/server/database.py b/fail2ban/server/database.py index 997f1c21..b9c2e12d 100644 --- a/fail2ban/server/database.py +++ b/fail2ban/server/database.py @@ -64,6 +64,8 @@ class Fail2BanDb(object): self._dbFilename = filename self._purgeAge = purgeAge + self._bansMergedCache = {} + logSys.info( "Connected to fail2ban persistent database '%s'", filename) except sqlite3.OperationalError, e: @@ -219,6 +221,7 @@ class Fail2BanDb(object): @commitandrollback def addBan(self, cur, jail, ticket): + self._bansMergedCache = {} #TODO: Implement data parts once arbitrary match keys completed cur.execute( "INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)", @@ -253,6 +256,9 @@ class Fail2BanDb(object): return tickets def getBansMerged(self, ip, jail=None, **kwargs): + cacheKey = ip if jail is None else "%s|%s" % (ip, jail.getName()) + if cacheKey in self._bansMergedCache: + return self._bansMergedCache[cacheKey] matches = [] failures = 0 for ip, timeofban, data in self._getBans(ip=ip, jail=jail, **kwargs): @@ -261,10 +267,12 @@ class Fail2BanDb(object): failures += data['failures'] ticket = FailTicket(ip, timeofban, matches) ticket.setAttempt(failures) + self._bansMergedCache[cacheKey] = ticket return ticket @commitandrollback def purge(self, cur): + self._bansMergedCache = {} cur.execute( "DELETE FROM bans WHERE timeofban < ?", (MyTime.time() - self._purgeAge, )) diff --git a/fail2ban/tests/databasetestcase.py b/fail2ban/tests/databasetestcase.py index 44f7a59e..969ea6ff 100644 --- a/fail2ban/tests/databasetestcase.py +++ b/fail2ban/tests/databasetestcase.py @@ -162,6 +162,20 @@ class DatabaseTest(unittest.TestCase): self.assertEqual(ticket.getAttempt(), 30) self.assertEqual(ticket.getMatches(), ["abc\n", "123\n"]) + # Should cache result if no extra bans added + ticketID = id(ticket) + self.assertEqual( + ticketID, + id(self.db.getBansMerged("127.0.0.1", jail=self.jail))) + + ticket = FailTicket("127.0.0.1", 40, ["ABC\n"]) + ticket.setAttempt(40) + self.db.addBan(jail2, ticket) + # Added ticket, so cache should have been cleared + self.assertNotEqual( + ticketID, + id(self.db.getBansMerged("127.0.0.1", jail=self.jail))) + def testPurge(self): self.testAddJail() # Add jail From 802029d83a307f4ac39d7f3d0e34b6b7979270ef Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sun, 15 Dec 2013 22:20:48 +0000 Subject: [PATCH 5/5] BF: Database test keep ticket present in memory so address is reused This bug only seemed to effect python2.6 which seemed hasty to reuse the memory id that was assigned to the ticket which was being used for reference --- fail2ban/tests/databasetestcase.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/fail2ban/tests/databasetestcase.py b/fail2ban/tests/databasetestcase.py index 969ea6ff..c4c5f53e 100644 --- a/fail2ban/tests/databasetestcase.py +++ b/fail2ban/tests/databasetestcase.py @@ -163,17 +163,16 @@ class DatabaseTest(unittest.TestCase): self.assertEqual(ticket.getMatches(), ["abc\n", "123\n"]) # Should cache result if no extra bans added - ticketID = id(ticket) self.assertEqual( - ticketID, + id(ticket), id(self.db.getBansMerged("127.0.0.1", jail=self.jail))) - ticket = FailTicket("127.0.0.1", 40, ["ABC\n"]) + newTicket = FailTicket("127.0.0.1", 40, ["ABC\n"]) ticket.setAttempt(40) - self.db.addBan(jail2, ticket) + self.db.addBan(self.jail, newTicket) # Added ticket, so cache should have been cleared self.assertNotEqual( - ticketID, + id(ticket), id(self.db.getBansMerged("127.0.0.1", jail=self.jail))) def testPurge(self):