From ab10664b57630fcf012ee5cf8cc1f00c2eadf7b4 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sun, 14 Jul 2013 16:20:21 +1000 Subject: [PATCH 01/27] ENH: action.d/hostsdeny to take daemon_list arguement as suggested in README.Solaris --- ChangeLog | 2 +- README.Solaris | 6 ++---- config/action.d/hostsdeny.conf | 11 +++++++++-- config/jail.conf | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5b9ec79e..2fba9401 100644 --- a/ChangeLog +++ b/ChangeLog @@ -29,7 +29,7 @@ ver. 0.8.11 (2013/XX/XXX) - loves-unittests Daniel Black * action.d/hostsdeny -- NOTE: new dependancy 'ed'. Switched to use 'ed' across all platforms to ensure permissions are the same before and after a ban - - closes gh-266 + closes gh-266. hostsdeny supports daemon_list now too. - New Features: Daniel Black & ykimon * filter.d/3proxy.conf -- filter added diff --git a/README.Solaris b/README.Solaris index 10a5f88c..6a3335d7 100644 --- a/README.Solaris +++ b/README.Solaris @@ -99,7 +99,7 @@ REQ: Create /etc/fail2ban/jail.local containing: enabled = true filter = sshd -action = hostsdeny +action = hostsdeny[sshd] sendmail-whois[name=SSH, dest=you@example.com] ignoreregex = for myuser from logpath = /var/adm/auth.log @@ -136,6 +136,4 @@ GOTCHAS AND FIXMES * Fail2ban adds lines like these to /etc/hosts.deny: - ALL: 1.2.3.4 - - wouldn't it be better to just block sshd? + sshd: 1.2.3.4 diff --git a/config/action.d/hostsdeny.conf b/config/action.d/hostsdeny.conf index 36e34948..d74f498d 100644 --- a/config/action.d/hostsdeny.conf +++ b/config/action.d/hostsdeny.conf @@ -1,6 +1,7 @@ # Fail2Ban configuration file # # Author: Cyril Jaquier +# Edited for cross platform by: James Stout, Yaroslav Halchenko and Daniel Black # # @@ -31,7 +32,7 @@ actioncheck = # Values: CMD # actionban = IP= && - printf %%b "ALL: $IP\n" >> + printf %%b ": $IP\n" >> # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -39,7 +40,7 @@ actionban = IP= && # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = echo "/ALL: $/
d
w
q" | ed +actionunban = echo "/^: $/
d
w
q" | ed [Init] @@ -48,3 +49,9 @@ actionunban = echo "/ALL: $/
d
w
q" | ed # Values: STR Default: /etc/hosts.deny # file = /etc/hosts.deny + +# Option: daemon_list +# Notes: The list of services that this action will deny. See the man page +# for hosts.deny/hosts_access. Default is all services. +# Values: STR Default: ALL +daemon_list = ALL diff --git a/config/jail.conf b/config/jail.conf index e3b92038..ccc1ae9a 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -103,7 +103,7 @@ logpath = /root/path/to/assp/logs/maillog.txt enabled = false filter = sshd -action = hostsdeny +action = hostsdeny[daemon_list=sshd] sendmail-whois[name=SSH, dest=you@example.com] ignoreregex = for myuser from logpath = /var/log/sshd.log From 6e4b1441644c2399a30c6cc311a52d79a5f22d83 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sun, 14 Jul 2013 21:43:14 +1000 Subject: [PATCH 02/27] DOC: fix for README.Solaris. Thanks Steven. --- README.Solaris | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.Solaris b/README.Solaris index 6a3335d7..5157c099 100644 --- a/README.Solaris +++ b/README.Solaris @@ -99,7 +99,7 @@ REQ: Create /etc/fail2ban/jail.local containing: enabled = true filter = sshd -action = hostsdeny[sshd] +action = hostsdeny[daemon_list=sshd] sendmail-whois[name=SSH, dest=you@example.com] ignoreregex = for myuser from logpath = /var/adm/auth.log From fcf79b475f26884a584617f3fd5ff66afa6df371 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Fri, 19 Jul 2013 20:14:53 +1000 Subject: [PATCH 03/27] ENH: new filter perdition.conf --- ChangeLog | 2 ++ THANKS | 1 + config/filter.d/perdition.conf | 16 ++++++++++++++++ testcases/files/logs/perdition | 4 ++++ 4 files changed, 23 insertions(+) create mode 100644 config/filter.d/perdition.conf create mode 100644 testcases/files/logs/perdition diff --git a/ChangeLog b/ChangeLog index a6a6ab23..4139dfe1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -36,6 +36,8 @@ ver. 0.8.11 (2013/XX/XXX) - loves-unittests Daniel Black * filter.d/exim-spam.conf -- a splitout of exim's spam regexes with additions for greater control over filtering spam. + Christophe Carles & Daniel Black + * filter.d/perdition.conf -- filter added - Enhancements: Daniel Black * filter.d/{asterisk,assp,dovecot,proftpd}.conf -- regex hardening diff --git a/THANKS b/THANKS index af790f67..b853c0dc 100644 --- a/THANKS +++ b/THANKS @@ -11,6 +11,7 @@ Axel Thimm Bill Heaton Carlos Alberto Lopez Perez Christian Rauch +Christophe Carles Christoph Haas Christos Psonis Daniel B. Cid diff --git a/config/filter.d/perdition.conf b/config/filter.d/perdition.conf new file mode 100644 index 00000000..e7cb0f7d --- /dev/null +++ b/config/filter.d/perdition.conf @@ -0,0 +1,16 @@ +# Fail2Ban configuration file +# +# Author: Christophe Carles and Daniel Black +# +# + +[INCLUDES] + +before = common.conf + +[Definition] + +_daemon=perdition.imaps + +failregex = ^%(__prefix_line)sAuth: :\d+->(\d{1,3}\.){3}\d{1,3}:\d+ client-secure=\S+ authorisation_id=NONE authentication_id="\S+" server="\S+" protocol=\S+ server-secure=\S+ status="failed: Re-Authentication Failure"$ + ^%(__prefix_line)sFatal Error reading authentication information from client :\d+->(\d{1,3}\.){3}\d{1,3}:\d+: Exiting child$ diff --git a/testcases/files/logs/perdition b/testcases/files/logs/perdition new file mode 100644 index 00000000..2304e372 --- /dev/null +++ b/testcases/files/logs/perdition @@ -0,0 +1,4 @@ +# failJSON: { "time": "2013-07-18T16:07:18", "match": true , "host": "192.168.8.100" } +Jul 18 16:07:18 ares perdition.imaps[3194]: Auth: 192.168.8.100:2274->193.48.191.9:993 client-secure=ssl authorisation_id=NONE authentication_id="carles" server="imap.biotoul.fr:993" protocol=IMAP4S server-secure=ssl status="failed: Re-Authentication Failure" +# failJSON: { "time": "2013-07-18T16:08:58", "match": true , "host": "192.168.8.100" } +Jul 18 16:08:58 ares perdition.imaps[3194]: Fatal Error reading authentication information from client 192.168.8.100:2274->193.48.191.9:993: Exiting child From eea5b071e61ba5cd5f4943100dc4bc0a278cf358 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Fri, 19 Jul 2013 20:27:15 +1000 Subject: [PATCH 04/27] ENH: jail for perdition --- config/jail.conf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config/jail.conf b/config/jail.conf index e3b92038..735f028c 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -419,3 +419,10 @@ enabled = false filter = exim-spam action = iptables-multiport[name=exim-spam,port="25,465,587"] logpath = /var/log/exim/mainlog + +[perdition] +enabled = false +filter = perdition +action = iptables-multiport[name=perdition="110,143,993,995"] +logpath = /var/log/maillog + From 6fdfd8d356838512bf8582b8bc2cff0cfb5fade9 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 20 Jul 2013 15:09:25 +1000 Subject: [PATCH 05/27] BF: fix port --- config/jail.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/jail.conf b/config/jail.conf index 735f028c..07f9cacc 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -423,6 +423,6 @@ logpath = /var/log/exim/mainlog [perdition] enabled = false filter = perdition -action = iptables-multiport[name=perdition="110,143,993,995"] +action = iptables-multiport[name=perdition,port="110,143,993,995"] logpath = /var/log/maillog From bdcde678d199a360f39e03fa27719fc0fa68fbc6 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sat, 20 Jul 2013 15:15:02 +1000 Subject: [PATCH 06/27] TST: fix year --- testcases/files/logs/perdition | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testcases/files/logs/perdition b/testcases/files/logs/perdition index 2304e372..24848e6f 100644 --- a/testcases/files/logs/perdition +++ b/testcases/files/logs/perdition @@ -1,4 +1,4 @@ -# failJSON: { "time": "2013-07-18T16:07:18", "match": true , "host": "192.168.8.100" } +# failJSON: { "time": "2005-07-18T16:07:18", "match": true , "host": "192.168.8.100" } Jul 18 16:07:18 ares perdition.imaps[3194]: Auth: 192.168.8.100:2274->193.48.191.9:993 client-secure=ssl authorisation_id=NONE authentication_id="carles" server="imap.biotoul.fr:993" protocol=IMAP4S server-secure=ssl status="failed: Re-Authentication Failure" -# failJSON: { "time": "2013-07-18T16:08:58", "match": true , "host": "192.168.8.100" } +# failJSON: { "time": "2005-07-18T16:08:58", "match": true , "host": "192.168.8.100" } Jul 18 16:08:58 ares perdition.imaps[3194]: Fatal Error reading authentication information from client 192.168.8.100:2274->193.48.191.9:993: Exiting child From b92316ff17024ef1c0f0fa48298b5b51f60026b6 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 22 Jul 2013 11:47:36 -0400 Subject: [PATCH 07/27] RF(ENH): JailsReader.getOptions -- avoid code duplication when asking for 1 jail or all --- client/jailsreader.py | 45 +++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/client/jailsreader.py b/client/jailsreader.py index 408a40bf..00c63e3c 100644 --- a/client/jailsreader.py +++ b/client/jailsreader.py @@ -18,7 +18,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Author: Cyril Jaquier -# +# __author__ = "Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" @@ -32,7 +32,7 @@ from jailreader import JailReader logSys = logging.getLogger("fail2ban.client.config") class JailsReader(ConfigReader): - + def __init__(self, force_enable=False, **kwargs): """ Parameters @@ -44,17 +44,25 @@ class JailsReader(ConfigReader): ConfigReader.__init__(self, **kwargs) self.__jails = list() self.__force_enable = force_enable - + def read(self): return ConfigReader.read(self, "jail") - - def getOptions(self, section = None): + + def getOptions(self, section=None): + """Reads configuration for jail(s) and adds enabled jails to __jails + """ opts = [] self.__opts = ConfigReader.getOptions(self, "Definition", opts) - if section: - # Get the options of a specific jail. - jail = JailReader(section, basedir=self.getBaseDir(), force_enable=self.__force_enable) + if section is None: + sections = self.sections() + else: + sections = [ section ] + + # Get the options of all jails. + for sec in sections: + jail = JailReader(sec, basedir=self.getBaseDir(), + force_enable=self.__force_enable) jail.read() ret = jail.getOptions() if ret: @@ -62,23 +70,10 @@ class JailsReader(ConfigReader): # We only add enabled jails self.__jails.append(jail) else: - logSys.error("Errors in jail '%s'. Skipping..." % section) + logSys.error("Errors in jail %r. Skipping..." % sec) return False - else: - # Get the options of all jails. - for sec in self.sections(): - jail = JailReader(sec, basedir=self.getBaseDir(), force_enable=self.__force_enable) - jail.read() - ret = jail.getOptions() - if ret: - if jail.isEnabled(): - # We only add enabled jails - self.__jails.append(jail) - else: - logSys.error("Errors in jail '" + sec + "'. Skipping...") - return False return True - + def convert(self, allow_no_files=False): """Convert read before __opts and jails to the commands stream @@ -99,6 +94,6 @@ class JailsReader(ConfigReader): # Start jails for jail in self.__jails: stream.append(["start", jail.getName()]) - + return stream - + From 149a83545fe45d1b66d0ee525ee29ccce5c2288d Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 22 Jul 2013 11:52:51 -0400 Subject: [PATCH 08/27] TST: basic test for reading of a bogus jail --- testcases/clientreadertestcase.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/testcases/clientreadertestcase.py b/testcases/clientreadertestcase.py index 695b3877..4e8cdd9d 100644 --- a/testcases/clientreadertestcase.py +++ b/testcases/clientreadertestcase.py @@ -126,6 +126,13 @@ class JailsReaderTest(unittest.TestCase): # commands to communicate to the server self.assertEqual(comm_commands, []) + # We should not "read" some bogus jail + old_comm_commands = comm_commands[:] # make a copy + self.assertFalse(jails.getOptions("BOGUS")) + # and there should be no side-effects + self.assertEqual(jails.convert(), old_comm_commands) + + def testReadStockJailConfForceEnabled(self): # more of a smoke test to make sure that no obvious surprises # on users' systems when enabling shipped jails From 3b52eca608d858ce42a6b6109ad5d1cc7a01d3b9 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 22 Jul 2013 12:09:33 -0400 Subject: [PATCH 09/27] ENH+TST: Ticket -- drop unused/bogus get|setFile + enh __str__ + basic testing --- server/ticket.py | 8 +------- testcases/failmanagertestcase.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/server/ticket.py b/server/ticket.py index 8826f26b..e0b2cb2e 100644 --- a/server/ticket.py +++ b/server/ticket.py @@ -47,7 +47,7 @@ class Ticket: def __str__(self): return "%s: ip=%s time=%s #attempts=%d" % \ - (self.__class__, self.__ip, self.__time, self.__attempt) + (self.__class__.__name__.split('.')[-1], self.__ip, self.__time, self.__attempt) def setIP(self, value): @@ -59,12 +59,6 @@ class Ticket: def getIP(self): return self.__ip - def setFile(self, value): - self.__file = value - - def getFile(self): - return self.__file - def setTime(self, value): self.__time = value diff --git a/testcases/failmanagertestcase.py b/testcases/failmanagertestcase.py index 1bfab339..bd34e25f 100644 --- a/testcases/failmanagertestcase.py +++ b/testcases/failmanagertestcase.py @@ -78,6 +78,20 @@ class AddFailure(unittest.TestCase): ticket = self.__failManager.toBan() self.assertEqual(ticket.getIP(), "193.168.0.128") self.assertTrue(isinstance(ticket.getIP(), str)) + + # finish with rudimentary tests of the ticket + # verify consistent str + ticket_str = str(ticket) + self.assertEqual( + ticket_str, + 'FailTicket: ip=193.168.0.128 time=1167605999.0 #attempts=5') + # and some get/set-ers otherwise not tested + ticket.setTime(1000002000.0) + self.assertEqual(ticket.getTime(), 1000002000.0) + # and str() adjusted correspondingly + self.assertEqual( + str(ticket), + 'FailTicket: ip=193.168.0.128 time=1000002000.0 #attempts=5') def testbanNOK(self): self.__failManager.setMaxRetry(10) From abc41460799fb10b710b2684c0408240ef90d1ec Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 24 Jul 2013 10:27:12 +1000 Subject: [PATCH 10/27] ENH: perdition proxies other types hence daemon can include (perdidtion.(imap|pop)s?|managesieve). Also support local authentication resulting in the log message: local authentication failure --- config/filter.d/perdition.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/filter.d/perdition.conf b/config/filter.d/perdition.conf index e7cb0f7d..41980cb1 100644 --- a/config/filter.d/perdition.conf +++ b/config/filter.d/perdition.conf @@ -10,7 +10,7 @@ before = common.conf [Definition] -_daemon=perdition.imaps +_daemon=perdition.\S+ -failregex = ^%(__prefix_line)sAuth: :\d+->(\d{1,3}\.){3}\d{1,3}:\d+ client-secure=\S+ authorisation_id=NONE authentication_id="\S+" server="\S+" protocol=\S+ server-secure=\S+ status="failed: Re-Authentication Failure"$ +failregex = ^%(__prefix_line)sAuth: :\d+->(\d{1,3}\.){3}\d{1,3}:\d+ client-secure=\S+ authorisation_id=NONE authentication_id="\S+" server="\S+" protocol=\S+ server-secure=\S+ status="failed: (local authentication failure|Re-Authentication Failure)"$ ^%(__prefix_line)sFatal Error reading authentication information from client :\d+->(\d{1,3}\.){3}\d{1,3}:\d+: Exiting child$ From 7d7ef081457f7c55137e18f3235b3c36c356ee3b Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 24 Jul 2013 10:44:52 +1000 Subject: [PATCH 11/27] ENH: authentication_id can be an imap4 quoted string, whatever that is, so using .+ as its id --- config/filter.d/perdition.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/filter.d/perdition.conf b/config/filter.d/perdition.conf index 41980cb1..7fdca14b 100644 --- a/config/filter.d/perdition.conf +++ b/config/filter.d/perdition.conf @@ -12,5 +12,5 @@ before = common.conf _daemon=perdition.\S+ -failregex = ^%(__prefix_line)sAuth: :\d+->(\d{1,3}\.){3}\d{1,3}:\d+ client-secure=\S+ authorisation_id=NONE authentication_id="\S+" server="\S+" protocol=\S+ server-secure=\S+ status="failed: (local authentication failure|Re-Authentication Failure)"$ +failregex = ^%(__prefix_line)sAuth: :\d+->(\d{1,3}\.){3}\d{1,3}:\d+ client-secure=\S+ authorisation_id=NONE authentication_id=".+" server="\S+" protocol=\S+ server-secure=\S+ status="failed: (local authentication failure|Re-Authentication Failure)"$ ^%(__prefix_line)sFatal Error reading authentication information from client :\d+->(\d{1,3}\.){3}\d{1,3}:\d+: Exiting child$ From 8f532f9148a79a4caea77275660aa7fe18e3d14e Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Wed, 24 Jul 2013 11:29:58 +1000 Subject: [PATCH 12/27] NIT: space remove --- config/jail.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/jail.conf b/config/jail.conf index 07f9cacc..8d086da4 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -424,5 +424,5 @@ logpath = /var/log/exim/mainlog enabled = false filter = perdition action = iptables-multiport[name=perdition,port="110,143,993,995"] -logpath = /var/log/maillog +logpath = /var/log/maillog From 8936f2cd02c476cac0581b87c65c0e739cd2de68 Mon Sep 17 00:00:00 2001 From: Jamyn Shanley Date: Thu, 25 Jul 2013 07:36:10 -0500 Subject: [PATCH 13/27] fail2ban-users: Sebastian Arcus - Detect device auth failures on Asterisk 11 --- config/filter.d/asterisk.conf | 2 +- testcases/files/logs/asterisk | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/filter.d/asterisk.conf b/config/filter.d/asterisk.conf index e4e5de5f..fef43693 100644 --- a/config/filter.d/asterisk.conf +++ b/config/filter.d/asterisk.conf @@ -30,7 +30,7 @@ failregex = ^%(log_prefix)s Registration from '[^']*' failed for '(:\d+)?' ^%(log_prefix)s Host failed to authenticate as '[^']*'$ ^%(log_prefix)s No registration for peer '[^']*' \(from \)$ ^%(log_prefix)s Host failed MD5 authentication for '[^']*' \([^)]+\)$ - ^%(log_prefix)s Failed to authenticate user [^@]+@\S*$ + ^%(log_prefix)s Failed to authenticate (user|device) [^@]+@\S*$ ^%(log_prefix)s (?:handle_request_subscribe: )?Sending fake auth rejection for (device|user) \d*>;tag=\w+\S*$ ^%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="[\d-]+",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="\d+",SessionID="0x[\da-f]+",LocalAddress="IPV[46]/(UD|TC)P/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UD|TC)P//\d+"(,Challenge="\w+",ReceivedChallenge="\w+")?(,ReceivedHash="[\da-f]+")?$ diff --git a/testcases/files/logs/asterisk b/testcases/files/logs/asterisk index ec2fec7c..b2eb7738 100644 --- a/testcases/files/logs/asterisk +++ b/testcases/files/logs/asterisk @@ -1,4 +1,6 @@ # Sample log files for asterisk +# failJSON: { "time": "2013-07-25T07:26:43", "match": true , "host": "1.2.3.4" } +[2013-07-25 07:26:43] NOTICE[26015][C-000006b2] chan_sip.c: Failed to authenticate device 101;tag=deadbeef # failJSON: { "time": "2012-02-13T17:21:54", "match": true , "host": "1.2.3.4" } [2012-02-13 17:21:54] NOTICE[1638] chan_sip.c: Registration from '' failed for '1.2.3.4' - Wrong password # failJSON: { "time": "2012-02-13T17:18:22", "match": true , "host": "1.2.3.4" } From a355fab91b87b0843dfa5c804db93de8e0157361 Mon Sep 17 00:00:00 2001 From: Jamyn Shanley Date: Sat, 27 Jul 2013 03:31:00 +0000 Subject: [PATCH 14/27] https://github.com/fail2ban/fail2ban/issues/306 Fix regex for latest dropbear (keep backwards compatibility). Add test case logfiles. Signed-off-by: Jamyn Shanley --- config/filter.d/dropbear.conf | 5 +++-- testcases/files/logs/dropbear | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 testcases/files/logs/dropbear diff --git a/config/filter.d/dropbear.conf b/config/filter.d/dropbear.conf index c822d08c..b48615e6 100644 --- a/config/filter.d/dropbear.conf +++ b/config/filter.d/dropbear.conf @@ -27,8 +27,9 @@ _daemon = dropbear # These match the unmodified dropbear messages. It isn't possible to # match the source of the 'exit before auth' messages from dropbear. # -failregex = ^%(__prefix_line)slogin attempt for nonexistent user ('.*' )?from :.*\s*$ - ^%(__prefix_line)sbad password attempt for .+ from :.*\s*$ +failregex = ^%(__prefix_line)s(L|l)ogin attempt for nonexistent user ('.*' )?from :.*\s*$ + ^%(__prefix_line)s(B|b)ad password attempt for .+ from :.*\s*$ + ^%(__prefix_line)sExit before auth \(user .+, \d+ fails\): Max auth tries reached - user .+ from :.*\s*$ # The only line we need to match with the modified dropbear. diff --git a/testcases/files/logs/dropbear b/testcases/files/logs/dropbear new file mode 100644 index 00000000..414c3c7a --- /dev/null +++ b/testcases/files/logs/dropbear @@ -0,0 +1,6 @@ +# failJSON: { "time": "2005-07-27T01:04:12", "match": true , "host": "1.2.3.4" } +Jul 27 01:04:12 fail2ban-test dropbear[1335]: Bad password attempt for 'root' from 1.2.3.4:60588 +# failJSON: { "time": "2005-07-27T01:04:22", "match": true , "host": "1.2.3.4" } +Jul 27 01:04:22 fail2ban-test dropbear[1335]: Exit before auth (user 'root', 10 fails): Max auth tries reached - user 'root' from 1.2.3.4:60588 +# failJSON: { "time": "2005-07-27T01:18:59", "match": true , "host": "1.2.3.4" } +Jul 27 01:18:59 fail2ban-test dropbear[1477]: Login attempt for nonexistent user from 1.2.3.4:60794 From af46cd0e4f6ed194f2661b70ea83e5c7ab5c6e77 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 27 Jul 2013 11:34:10 +0100 Subject: [PATCH 15/27] ENH: fail2ban-regex now sorts date templates, similar to actual filters Sort occurs every 10 lines, as to not overly effect performance --- fail2ban-regex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fail2ban-regex b/fail2ban-regex index e19b1bc9..ff4bbd10 100755 --- a/fail2ban-regex +++ b/fail2ban-regex @@ -240,7 +240,7 @@ class Fail2banRegex(object): def process(self, test_lines): - for line in test_lines: + for line_no, line in enumerate(test_lines): if line.startswith('#') or not line.strip(): # skip comment and empty lines continue @@ -256,6 +256,9 @@ class Fail2banRegex(object): self._line_stats.missed_lines.append(line) self._line_stats.tested += 1 + if line_no % 10 == 0: + self._filter.dateDetector.sortTemplate() + def printLines(self, ltype): lstats = self._line_stats assert(len(lstats.missed_lines) == lstats.tested - (lstats.matched + lstats.ignored)) From 879c1a32f7e1d5ea72c72c950d97c72dc6c464ef Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 27 Jul 2013 11:36:01 +0100 Subject: [PATCH 16/27] ENH: fail2ban-regex now uses iteraable for log file Previosuly, the whole file was read in first --- fail2ban-regex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fail2ban-regex b/fail2ban-regex index ff4bbd10..4c5a0a11 100755 --- a/fail2ban-regex +++ b/fail2ban-regex @@ -377,7 +377,7 @@ if __name__ == "__main__": try: hdlr = open(cmd_log) print "Use log file : %s" % cmd_log - test_lines = hdlr.readlines() + test_lines = hdlr # Iterable except IOError, e: print e sys.exit(-1) From 1e270078b47ed8051f12b99f33857af2508f1899 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 27 Jul 2013 20:21:05 +0100 Subject: [PATCH 17/27] TST: Warn if date templates overlap in default detectors --- server/datetemplate.py | 3 +++ testcases/datedetectortestcase.py | 41 ++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/server/datetemplate.py b/server/datetemplate.py index 86eeee8e..0754391b 100644 --- a/server/datetemplate.py +++ b/server/datetemplate.py @@ -62,6 +62,9 @@ class DateTemplate: def incHits(self): self.__hits += 1 + + def resetHits(self): + self.__hits = 0 def matchDate(self, line): dateMatch = self.__cRegex.search(line) diff --git a/testcases/datedetectortestcase.py b/testcases/datedetectortestcase.py index 8a838700..218abba9 100644 --- a/testcases/datedetectortestcase.py +++ b/testcases/datedetectortestcase.py @@ -24,7 +24,7 @@ __author__ = "Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import unittest +import unittest, calendar, datetime, re, pprint from server.datedetector import DateDetector from server.datetemplate import DateTemplate @@ -122,6 +122,45 @@ class DateDetectorTest(unittest.TestCase): self.__datedetector.getTime('2012/10/11 02:37:17 [error] 18434#0')[:6], m1) + def testDateDetectorTemplateOverlap(self): + patterns = [template.getPattern() + for template in self.__datedetector.getTemplates() + if hasattr(template, "getPattern")] + + year = 2008 # Leap year, 08 for %y can be confused with both %d and %m + def iterDates(year): + for month in xrange(1, 13): + for day in xrange(2, calendar.monthrange(year, month)[1]+1, 9): + for hour in xrange(0, 24, 6): + for minute in xrange(0, 60, 15): + for second in xrange(0, 60, 15): # Far enough? + yield datetime.datetime( + year, month, day, hour, minute, second) + + overlapedTemplates = set() + for date in iterDates(year): + for pattern in patterns: + datestr = date.strftime(pattern) + datestrs = set([ + datestr, + re.sub(r"(\s)0", r"\1 ", datestr), + re.sub(r"(\s)0", r"\1", datestr)]) + for template in self.__datedetector.getTemplates(): + template.resetHits() + for datestr in datestrs: + if template.matchDate(datestr): # or getDate? + template.incHits() + + matchedTemplates = [template + for template in self.__datedetector.getTemplates() + if template.getHits() > 0] + assert matchedTemplates != [] # Should match at least one + if len(matchedTemplates) > 1: + overlapedTemplates.add((pattern, tuple(sorted(template.getName() + for template in matchedTemplates)))) + if overlapedTemplates: + print "WARNING: The following date templates overlap:" + pprint.pprint(overlapedTemplates) # def testDefaultTempate(self): # self.__datedetector.setDefaultRegex("^\S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}") From 04ff9450b51f21177fb6c34a57601cd5f9e136dc Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Mon, 29 Jul 2013 10:45:15 +1000 Subject: [PATCH 18/27] BF: testcases is a package that needs to be installed. Thanks to Michael Danko on irc --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1b6268bf..b61ecce4 100755 --- a/setup.py +++ b/setup.py @@ -53,7 +53,8 @@ setup( packages = [ 'common', 'client', - 'server' + 'server', + 'testcases' ], data_files = [ ('/etc/fail2ban', From 547c123cfb5dd66ecaa8390896e71ad03893dcf4 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Wed, 7 Aug 2013 22:56:57 -0400 Subject: [PATCH 19/27] BF: example.com is pointing to another IP now. Closes #313 This is a permanent change according to private correspondence with David Closson @ IANN, thus replaced 192.0.43.10 with updated IP 93.184.216.119, while leaving 192.0.43.10 as is in the sample log files (it is still within IANN dedicated testing network). --- testcases/files/testcase-usedns.log | 2 +- testcases/filtertestcase.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/testcases/files/testcase-usedns.log b/testcases/files/testcase-usedns.log index 87c99fdb..a91fd7ac 100644 --- a/testcases/files/testcase-usedns.log +++ b/testcases/files/testcase-usedns.log @@ -1,2 +1,2 @@ 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:192.0.43.10 port 51332 ssh2 +Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2 diff --git a/testcases/filtertestcase.py b/testcases/filtertestcase.py index d918bbf7..b961d0dc 100644 --- a/testcases/filtertestcase.py +++ b/testcases/filtertestcase.py @@ -632,12 +632,12 @@ class GetFailures(unittest.TestCase): def testGetFailuresUseDNS(self): # We should still catch failures with usedns = no ;-) - output_yes = ('192.0.43.10', 2, 1124013539.0, + output_yes = ('93.184.216.119', 2, 1124013539.0, ['Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2\n', - 'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:192.0.43.10 port 51332 ssh2\n']) + 'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2\n']) - output_no = ('192.0.43.10', 1, 1124013539.0, - ['Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:192.0.43.10 port 51332 ssh2\n']) + output_no = ('93.184.216.119', 1, 1124013539.0, + ['Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.119 port 51332 ssh2\n']) # Actually no exception would be raised -- it will be just set to 'no' #self.assertRaises(ValueError, @@ -684,9 +684,9 @@ class DNSUtilsTests(unittest.TestCase): res = DNSUtils.textToIp('www.example.com', 'no') self.assertEqual(res, []) res = DNSUtils.textToIp('www.example.com', 'warn') - self.assertEqual(res, ['192.0.43.10']) + self.assertEqual(res, ['93.184.216.119']) res = DNSUtils.textToIp('www.example.com', 'yes') - self.assertEqual(res, ['192.0.43.10']) + self.assertEqual(res, ['93.184.216.119']) def testTextToIp(self): # Test hostnames @@ -698,7 +698,7 @@ class DNSUtilsTests(unittest.TestCase): for s in hostnames: res = DNSUtils.textToIp(s, 'yes') if s == 'www.example.com': - self.assertEqual(res, ['192.0.43.10']) + self.assertEqual(res, ['93.184.216.119']) else: self.assertEqual(res, []) From c0456fd835f3ba8989ff207ff26e5d464ed620c4 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 8 Aug 2013 09:47:47 -0400 Subject: [PATCH 20/27] BF: Fixing a name for MySQL date pattern which misplaced Year (should not effect functionality) Closes #312 --- server/datedetector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/datedetector.py b/server/datedetector.py index 0ed9e00a..0bbbc089 100644 --- a/server/datedetector.py +++ b/server/datedetector.py @@ -151,7 +151,7 @@ class DateDetector: self._appendTemplate(template) # MySQL: 130322 11:46:11 template = DateStrptime() - template.setName("MonthDayYear Hour:Minute:Second") + template.setName("YearMonthDay Hour:Minute:Second") template.setRegex("^\d{2}\d{2}\d{2} +\d{1,2}:\d{2}:\d{2}") template.setPattern("%y%m%d %H:%M:%S") self._appendTemplate(template) From 9487ee55629068f8e0dd716485b9844b0be64518 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 8 Aug 2013 09:54:15 -0400 Subject: [PATCH 21/27] minor: consistent indentation in dropbear.conf --- config/filter.d/dropbear.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/filter.d/dropbear.conf b/config/filter.d/dropbear.conf index b48615e6..6f34c1ba 100644 --- a/config/filter.d/dropbear.conf +++ b/config/filter.d/dropbear.conf @@ -29,7 +29,7 @@ _daemon = dropbear # failregex = ^%(__prefix_line)s(L|l)ogin attempt for nonexistent user ('.*' )?from :.*\s*$ ^%(__prefix_line)s(B|b)ad password attempt for .+ from :.*\s*$ - ^%(__prefix_line)sExit before auth \(user .+, \d+ fails\): Max auth tries reached - user .+ from :.*\s*$ + ^%(__prefix_line)sExit before auth \(user .+, \d+ fails\): Max auth tries reached - user .+ from :.*\s*$ # The only line we need to match with the modified dropbear. From 4e0ddc5f67df99e91bf27220fd275277b9efcff1 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 8 Aug 2013 09:58:36 -0400 Subject: [PATCH 22/27] ENH: hardened added dropbear failregex to avoid trailing .* and enclose username in '' --- config/filter.d/dropbear.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/filter.d/dropbear.conf b/config/filter.d/dropbear.conf index 6f34c1ba..350747c6 100644 --- a/config/filter.d/dropbear.conf +++ b/config/filter.d/dropbear.conf @@ -29,7 +29,7 @@ _daemon = dropbear # failregex = ^%(__prefix_line)s(L|l)ogin attempt for nonexistent user ('.*' )?from :.*\s*$ ^%(__prefix_line)s(B|b)ad password attempt for .+ from :.*\s*$ - ^%(__prefix_line)sExit before auth \(user .+, \d+ fails\): Max auth tries reached - user .+ from :.*\s*$ + ^%(__prefix_line)sExit before auth \(user '.+', \d+ fails\): Max auth tries reached - user '.+' from :\d+\s*$ # The only line we need to match with the modified dropbear. From e4dad8dfc945d8c04c7831dd329f3cbfa297c938 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 8 Aug 2013 22:00:17 -0400 Subject: [PATCH 23/27] TST: SYSLOG present only on Linuxes thus do not test if not Linux --- testcases/servertestcase.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/testcases/servertestcase.py b/testcases/servertestcase.py index 07cc373c..0af8f920 100644 --- a/testcases/servertestcase.py +++ b/testcases/servertestcase.py @@ -24,7 +24,7 @@ __author__ = "Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import unittest, socket, time, tempfile, os +import unittest, socket, time, tempfile, os, sys from server.server import Server from server.jail import Jail from common.exceptions import UnknownJailException @@ -498,7 +498,8 @@ class TransmitterLogging(TransmitterBase): self.setGetTest("logtarget", "STDOUT") self.setGetTest("logtarget", "STDERR") - self.setGetTest("logtarget", "SYSLOG") + if sys.platform.lower().startswith('linux'): + self.setGetTest("logtarget", "SYSLOG") def testLogLevel(self): self.setGetTest("loglevel", "4", 4) From 67497db6e56130792d12c87b32d536f47e84e071 Mon Sep 17 00:00:00 2001 From: Orion Poplawski Date: Thu, 8 Aug 2013 20:28:55 -0600 Subject: [PATCH 24/27] Change /tmp/fail2ban.sock to /var/run/fail2ban/fail2ban.sock --- client/fail2banreader.py | 2 +- files/nagios/README | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/fail2banreader.py b/client/fail2banreader.py index ada88084..0171b457 100644 --- a/client/fail2banreader.py +++ b/client/fail2banreader.py @@ -39,7 +39,7 @@ class Fail2banReader(ConfigReader): ConfigReader.read(self, "fail2ban") def getEarlyOptions(self): - opts = [["string", "socket", "/tmp/fail2ban.sock"], + opts = [["string", "socket", "/var/run/fail2ban/fail2ban.sock"], ["string", "pidfile", "/var/run/fail2ban/fail2ban.pid"]] return ConfigReader.getOptions(self, "Definition", opts) diff --git a/files/nagios/README b/files/nagios/README index 28e84495..2b855d83 100644 --- a/files/nagios/README +++ b/files/nagios/README @@ -35,7 +35,7 @@ HELP: /etc/init.d/fail2ban stop 2.) delete the socket if available -rm /tmp/fail2ban.sock +rm /var/run/fail2ban/fail2ban.sock 3.) start the Service /etc/init.d/fail2ban start From 511e0ace2e6081ef9b0378db961e232af13591eb Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 8 Aug 2013 22:25:14 -0400 Subject: [PATCH 25/27] TST: Even more of conditioning of tests for cygwin --- testcases/clientreadertestcase.py | 9 +++++++-- testcases/filtertestcase.py | 8 +++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/testcases/clientreadertestcase.py b/testcases/clientreadertestcase.py index 4e8cdd9d..b52ad618 100644 --- a/testcases/clientreadertestcase.py +++ b/testcases/clientreadertestcase.py @@ -21,7 +21,7 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko" __license__ = "GPL" -import os, shutil, tempfile, unittest +import os, tempfile, shutil, unittest from client.configreader import ConfigReader from client.jailreader import JailReader from client.jailsreader import JailsReader @@ -65,7 +65,12 @@ option = %s self._write('d.conf', 0) self.assertEqual(self._getoption('d'), 0) os.chmod(f, 0) - self.assertFalse(self.c.read('d')) # should not be readable BUT present + # fragile test and known to fail e.g. under Cygwin where permissions + # seems to be not enforced, thus condition + if not os.access('d.conf', os.R_OK): + self.assertFalse(self.c.read('d')) # should not be readable BUT present + else: + raise unittest.SkipTest("Skipping on %s -- access rights are not enforced" % platform) def testOptionalDotDDir(self): diff --git a/testcases/filtertestcase.py b/testcases/filtertestcase.py index b961d0dc..ecdc7bde 100644 --- a/testcases/filtertestcase.py +++ b/testcases/filtertestcase.py @@ -83,7 +83,13 @@ def _assert_equal_entries(utest, found, output, count=None): utest.assertEqual(found_time, output_time) if len(output) > 3 and count is None: # match matches # do not check if custom count (e.g. going through them twice) - utest.assertEqual(repr(found[3]), repr(output[3])) + if os.linesep != '\n' or sys.platform.startswith('cygwin'): + # on those where text file lines end with '\r\n', override + # repr for this test to disregard trailing CR LF + srepr = lambda x: repr(str(x).rstrip('\n\r')) + else: + srepr = repr + utest.assertEqual(srepr(found[3]), srepr(output[3])) def _assert_correct_last_attempt(utest, filter_, output, count=None): """Additional helper to wrap most common test case From 31a78b271143cb652aca70579b75bab78efb85eb Mon Sep 17 00:00:00 2001 From: Orion Poplawski Date: Thu, 8 Aug 2013 20:41:44 -0600 Subject: [PATCH 26/27] Use /var/run/fail2ban in config/action.d/dummy.conf --- config/action.d/dummy.conf | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/action.d/dummy.conf b/config/action.d/dummy.conf index 20507c0b..dc4e1dbf 100644 --- a/config/action.d/dummy.conf +++ b/config/action.d/dummy.conf @@ -10,14 +10,14 @@ # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # -actionstart = touch /tmp/fail2ban.dummy - printf %%b "\n" >> /tmp/fail2ban.dummy +actionstart = touch /var/run/fail2ban/fail2ban.dummy + printf %%b "\n" >> /var/run/fail2ban/fail2ban.dummy # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # -actionstop = rm -f /tmp/fail2ban.dummy +actionstop = rm -f /var/run/fail2ban/fail2ban.dummy # Option: actioncheck # Notes.: command executed once before each actionban command @@ -31,7 +31,7 @@ actioncheck = # Tags: See jail.conf(5) man page # Values: CMD # -actionban = printf %%b "+\n" >> /tmp/fail2ban.dummy +actionban = printf %%b "+\n" >> /var/run/fail2ban/fail2ban.dummy # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the @@ -39,7 +39,7 @@ actionban = printf %%b "+\n" >> /tmp/fail2ban.dummy # Tags: See jail.conf(5) man page # Values: CMD # -actionunban = printf %%b "-\n" >> /tmp/fail2ban.dummy +actionunban = printf %%b "-\n" >> /var/run/fail2ban/fail2ban.dummy [Init] From 2aa8ddea4d6f60e74a33765a5e6068374c566e4f Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 8 Aug 2013 22:58:06 -0400 Subject: [PATCH 27/27] BF: fixed up conditioning of tests under cygwin (still 3 fail) --- testcases/clientreadertestcase.py | 6 ++++-- testcases/filtertestcase.py | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/testcases/clientreadertestcase.py b/testcases/clientreadertestcase.py index b52ad618..65c3bbc2 100644 --- a/testcases/clientreadertestcase.py +++ b/testcases/clientreadertestcase.py @@ -67,10 +67,12 @@ option = %s os.chmod(f, 0) # fragile test and known to fail e.g. under Cygwin where permissions # seems to be not enforced, thus condition - if not os.access('d.conf', os.R_OK): + if not os.access(f, os.R_OK): self.assertFalse(self.c.read('d')) # should not be readable BUT present else: - raise unittest.SkipTest("Skipping on %s -- access rights are not enforced" % platform) + # SkipTest introduced only in 2.7 thus can't yet use generally + # raise unittest.SkipTest("Skipping on %s -- access rights are not enforced" % platform) + pass def testOptionalDotDDir(self): diff --git a/testcases/filtertestcase.py b/testcases/filtertestcase.py index ecdc7bde..2061327a 100644 --- a/testcases/filtertestcase.py +++ b/testcases/filtertestcase.py @@ -84,9 +84,8 @@ def _assert_equal_entries(utest, found, output, count=None): if len(output) > 3 and count is None: # match matches # do not check if custom count (e.g. going through them twice) if os.linesep != '\n' or sys.platform.startswith('cygwin'): - # on those where text file lines end with '\r\n', override - # repr for this test to disregard trailing CR LF - srepr = lambda x: repr(str(x).rstrip('\n\r')) + # on those where text file lines end with '\r\n', remove '\r' + srepr = lambda x: repr(x).replace(r'\r', '') else: srepr = repr utest.assertEqual(srepr(found[3]), srepr(output[3]))