diff --git a/ChangeLog b/ChangeLog index 3ab1b197..243a4dee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -63,13 +63,15 @@ 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 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/DEVELOP b/DEVELOP index 996a9552..f9cac432 100644 --- a/DEVELOP +++ b/DEVELOP @@ -253,6 +253,10 @@ Use the text "closes #333"/"resolves #333 "/"fixes #333" where 333 represents an issue that is closed. Other text and details in link below. See: https://help.github.com/articles/closing-issues-via-commit-messages +If merge resulted in conflicts, clarify what changes were done to +corresponding files in the 'Conflicts:' section of the merge commit +message. See e.g. https://github.com/fail2ban/fail2ban/commit/f5a8a8ac + Adding Actions -------------- diff --git a/README.Solaris b/README.Solaris index 86f56241..e41e3811 100644 --- a/README.Solaris +++ b/README.Solaris @@ -82,7 +82,7 @@ REQ: Create /etc/fail2ban/jail.local containing: enabled = true filter = sshd -action = hostsdeny +action = hostsdeny[daemon_list=sshd] sendmail-whois[name=SSH, dest=you@example.com] ignoreregex = for myuser from logpath = /var/adm/auth.log @@ -119,6 +119,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/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/bin/fail2ban-regex b/bin/fail2ban-regex index 5fb18584..b7d477c4 100755 --- a/bin/fail2ban-regex +++ b/bin/fail2ban-regex @@ -341,7 +341,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 @@ -357,6 +357,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)) 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/filter.d/perdition.conf b/config/filter.d/perdition.conf new file mode 100644 index 00000000..7fdca14b --- /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.\S+ + +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$ diff --git a/config/jail.conf b/config/jail.conf index 6f4d637e..53565225 100644 --- a/config/jail.conf +++ b/config/jail.conf @@ -198,7 +198,7 @@ logpath = /root/path/to/assp/logs/maillog.txt [sshd-tcpwrapper] 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 @@ -540,3 +540,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,port="110,143,993,995"] +logpath = /var/log/maillog + diff --git a/fail2ban/client/jailsreader.py b/fail2ban/client/jailsreader.py index 46e924fd..15d56c1c 100644 --- a/fail2ban/client/jailsreader.py +++ b/fail2ban/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(__name__) 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 - + diff --git a/fail2ban/server/datetemplate.py b/fail2ban/server/datetemplate.py index e445a343..25e5db44 100644 --- a/fail2ban/server/datetemplate.py +++ b/fail2ban/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/fail2ban/server/ticket.py b/fail2ban/server/ticket.py index 02576d29..3883f0c8 100644 --- a/fail2ban/server/ticket.py +++ b/fail2ban/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/fail2ban/tests/datedetectortestcase.py b/fail2ban/tests/datedetectortestcase.py index 071060ca..846d853f 100644 --- a/fail2ban/tests/datedetectortestcase.py +++ b/fail2ban/tests/datedetectortestcase.py @@ -24,8 +24,7 @@ __author__ = "Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import unittest - +import unittest, calendar, datetime, re, pprint from fail2ban.server.datedetector import DateDetector from fail2ban.server.datetemplate import DateTemplate from fail2ban.tests.utils import setUpMyTime, tearDownMyTime @@ -127,6 +126,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}") diff --git a/fail2ban/tests/failmanagertestcase.py b/fail2ban/tests/failmanagertestcase.py index c2fea4fa..80242b42 100644 --- a/fail2ban/tests/failmanagertestcase.py +++ b/fail2ban/tests/failmanagertestcase.py @@ -79,6 +79,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) diff --git a/fail2ban/tests/files/logs/postfix b/fail2ban/tests/files/logs/postfix index 50e3a360..e4d07b58 100644 --- a/fail2ban/tests/files/logs/postfix +++ b/fail2ban/tests/files/logs/postfix @@ -2,3 +2,5 @@ # and https://github.com/fail2ban/fail2ban/issues/126 # failJSON: { "time": "2005-02-21T09:21:54", "match": true , "host": "192.0.43.10" } Feb 21 09:21:54 xxx postfix/smtpd[14398]: NOQUEUE: reject: RCPT from example.com[192.0.43.10]: 450 4.7.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= +# failJSON: { "time": "2005-07-12T07:47:48", "match": true , "host": "1.2.3.4" } +Jul 12 07:47:48 saturn postfix/smtpd[8738]: NOQUEUE: reject: RCPT from 1-2-3-4-example.com[1.2.3.4]: 554 5.7.1 : Relay access denied; from= to= proto=SMTP helo=<198.51.100.17> diff --git a/testcases/files/logs/perdition b/testcases/files/logs/perdition new file mode 100644 index 00000000..24848e6f --- /dev/null +++ b/testcases/files/logs/perdition @@ -0,0 +1,4 @@ +# 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": "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 diff --git a/testcases/files/logs/sieve b/testcases/files/logs/sieve new file mode 100644 index 00000000..5cc19673 --- /dev/null +++ b/testcases/files/logs/sieve @@ -0,0 +1,2 @@ +# failJSON: { "time": "2004-12-01T20:36:56", "match": true , "host": "1.2.3.4" } +Dec 1 20:36:56 mail sieve[23713]: badlogin: example.com[1.2.3.4] PLAIN authentication failure