You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
225 lines
6.8 KiB
225 lines
6.8 KiB
@@ -, +, @@
|
|
config/jail.conf | 9 ++-
|
|
server/filterinotify.py | 157 +++++++++++++++++++++++++++++++++++++++++++++++
|
|
server/jail.py | 18 +++++-
|
|
3 files changed, 179 insertions(+), 5 deletions(-)
|
|
create mode 100644 server/filterinotify.py
|
|
--- a/config/jail.conf
|
|
+++ a/config/jail.conf
|
|
@@ -26,13 +26,16 @@ findtime = 600
|
|
maxretry = 3
|
|
|
|
# "backend" specifies the backend used to get files modification. Available
|
|
-# options are "gamin", "polling" and "auto". This option can be overridden in
|
|
-# each jail too (use "gamin" for a jail and "polling" for another).
|
|
+# options are "inotify", "gamin", "polling" and "auto". This option can be
|
|
+# overridden in each jail too (use "gamin" for a jail and "polling" for
|
|
+# another).
|
|
#
|
|
+# inotify: requires pyinotify and the a kernel supporting Inotify
|
|
# gamin: requires Gamin (a file alteration monitor) to be installed. If Gamin
|
|
# is not installed, Fail2ban will use polling.
|
|
# polling: uses a polling algorithm which does not require external libraries.
|
|
-# auto: will choose Gamin if available and polling otherwise.
|
|
+# auto: will choose Inotify if pyinotify is present, if not then it will
|
|
+# try Gamin and use that if available, and polling otherwise.
|
|
backend = auto
|
|
|
|
|
|
--- a/server/filterinotify.py
|
|
+++ a/server/filterinotify.py
|
|
@@ -0,0 +1,157 @@
|
|
+# This file is part of Fail2Ban.
|
|
+#
|
|
+# Fail2Ban is free software; you can redistribute it and/or modify
|
|
+# it under the terms of the GNU General Public License as published by
|
|
+# the Free Software Foundation; either version 2 of the License, or
|
|
+# (at your option) any later version.
|
|
+#
|
|
+# Fail2Ban is distributed in the hope that it will be useful,
|
|
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+# GNU General Public License for more details.
|
|
+#
|
|
+# You should have received a copy of the GNU General Public License
|
|
+# along with Fail2Ban; if not, write to the Free Software
|
|
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
+
|
|
+# Author: Jonathan G. Underwood
|
|
+#
|
|
+# $Revision$
|
|
+
|
|
+__author__ = "Jonathan G. Underwood"
|
|
+__version__ = "$Revision$"
|
|
+__date__ = "$Date$"
|
|
+__copyright__ = "Copyright (c) 2010 Jonathan G. Underwood"
|
|
+__license__ = "GPL"
|
|
+
|
|
+from failmanager import FailManagerEmpty
|
|
+from filter import FileFilter
|
|
+from mytime import MyTime
|
|
+
|
|
+import time, logging
|
|
+
|
|
+import pyinotify
|
|
+from pyinotify import ProcessEvent, WatchManager, Notifier
|
|
+
|
|
+# Gets the instance of the logger.
|
|
+logSys = logging.getLogger("fail2ban.filter")
|
|
+
|
|
+##
|
|
+# Log reader class.
|
|
+#
|
|
+# This class reads a log file and detects login failures or anything else
|
|
+# that matches a given regular expression. This class is instanciated by
|
|
+# a Jail object.
|
|
+
|
|
+class FilterInotify(ProcessEvent, FileFilter):
|
|
+
|
|
+ ##
|
|
+ # Constructor.
|
|
+ #
|
|
+ # Initialize the filter object with default values.
|
|
+ # @param jail the jail object
|
|
+
|
|
+ # Note that according to the pyinotify documentation we shouldn't
|
|
+ # define an __init__ function, but define a my_init function which is
|
|
+ # called by ProcessEvent.__init__. However, that approach appears not
|
|
+ # to work here and so we define __init__ and call
|
|
+ # ProcessEvent.__init__ from here.
|
|
+ def __init__(self, jail):
|
|
+ FileFilter.__init__(self, jail)
|
|
+ ProcessEvent.__init__(self)
|
|
+ self.__monitor = WatchManager()
|
|
+ self.__notifier = Notifier(self.__monitor, self)
|
|
+ self.__mask = pyinotify.IN_MODIFY | pyinotify.IN_CREATE
|
|
+
|
|
+ ##
|
|
+ # Event handling functions used by pyinotify.ProcessEvent
|
|
+ # instance. These simply call the __handleMod method.
|
|
+ # @event an event object
|
|
+
|
|
+ def process_IN_MODIFY(self, event):
|
|
+ logSys.debug("process_IN_MODIFY called")
|
|
+ self.__handleMod(event)
|
|
+
|
|
+ def process_IN_CREATE(self, event):
|
|
+ logSys.debug("process_IN_CREATE called")
|
|
+ self.__handleMod(event)
|
|
+
|
|
+ ##
|
|
+ # This method handles all modified file events
|
|
+ # @event an event object
|
|
+
|
|
+ def __handleMod(self, event):
|
|
+ self.getFailures(event.path)
|
|
+ try:
|
|
+ while True:
|
|
+ ticket = self.failManager.toBan()
|
|
+ self.jail.putFailTicket(ticket)
|
|
+ except FailManagerEmpty:
|
|
+ self.failManager.cleanup(MyTime.time())
|
|
+ self.dateDetector.sortTemplate()
|
|
+
|
|
+ ##
|
|
+ # Add a log file path
|
|
+ #
|
|
+ # @param path log file path
|
|
+
|
|
+ def addLogPath(self, path, tail = False):
|
|
+ if self.containsLogPath(path):
|
|
+ logSys.error(path + " already exists")
|
|
+ else:
|
|
+ wd = self.__monitor.add_watch(path, self.__mask)
|
|
+ if wd[path] > 0:
|
|
+ FileFilter.addLogPath(self, path, tail)
|
|
+ logSys.info("Added logfile = %s" % path)
|
|
+ else:
|
|
+ logSys.error("Failed to add an inotify watch for logfile = %s" % path)
|
|
+
|
|
+ ##
|
|
+ # Delete a log path
|
|
+ #
|
|
+ # @param path the log file to delete
|
|
+
|
|
+ def delLogPath(self, path):
|
|
+ if not self.containsLogPath(path):
|
|
+ logSys.error(path + " is not monitored")
|
|
+ else:
|
|
+ rd = self.__monitor.rm_watch(self.__monitor.get_wd(path))
|
|
+ if rd[path]:
|
|
+ FileFilter.delLogPath(self, path)
|
|
+ logSys.info("Removed logfile = %s" % path)
|
|
+ else:
|
|
+ logSys.error("Failed to remove inotify watch for logfile = %s" % path)
|
|
+
|
|
+ ##
|
|
+ # Main loop.
|
|
+ #
|
|
+ # This function is the main loop of the thread. It checks if the
|
|
+ # file has been modified and looks for failures.
|
|
+ # @return True when the thread exits nicely
|
|
+
|
|
+ def run(self):
|
|
+ self.setActive(True)
|
|
+ while self._isActive():
|
|
+ if not self.getIdle():
|
|
+ # We cannot block here because we want to be able to
|
|
+ # exit. __notifier.check_events will block for
|
|
+ # timeout milliseconds.
|
|
+ if self.__notifier.check_events(timeout=10):
|
|
+ self.__notifier.read_events()
|
|
+ self.__notifier.process_events()
|
|
+ time.sleep(self.getSleepTime())
|
|
+ else:
|
|
+ time.sleep(self.getSleepTime())
|
|
+
|
|
+ # Cleanup when shutting down
|
|
+ for wd in self.watchd.keys():
|
|
+ self.__monitor.rm_watch(wd)
|
|
+ del self.__monitor
|
|
+ self.__notifier.stop()
|
|
+ del self.__notifier
|
|
+
|
|
+ logSys.debug(self.jail.getName() + ": filter terminated")
|
|
+ return True
|
|
+
|
|
+
|
|
+
|
|
--- a/server/jail.py
|
|
+++ a/server/jail.py
|
|
@@ -40,17 +40,31 @@ class Jail:
|
|
logSys.info("Creating new jail '%s'" % self.__name)
|
|
if backend == "polling":
|
|
self.__initPoller()
|
|
+ elif backend == "inotify":
|
|
+ self.__initInotify()
|
|
+ elif backend == "gamin":
|
|
+ self.__initGamin()
|
|
else:
|
|
try:
|
|
- self.__initGamin()
|
|
+ self.__initInotify()
|
|
except ImportError:
|
|
- self.__initPoller()
|
|
+ try:
|
|
+ self.__initGamin()
|
|
+ except ImportError:
|
|
+ self.__initPoller()
|
|
self.__action = Actions(self)
|
|
|
|
def __initPoller(self):
|
|
logSys.info("Jail '%s' uses poller" % self.__name)
|
|
from filterpoll import FilterPoll
|
|
self.__filter = FilterPoll(self)
|
|
+
|
|
+ def __initInotify(self):
|
|
+ # Try to import pyinotify
|
|
+ import pyinotify
|
|
+ logSys.info("Jail '%s' uses Inotify" % self.__name)
|
|
+ from filterinotify import FilterInotify
|
|
+ self.__filter = FilterInotify(self)
|
|
|
|
def __initGamin(self):
|
|
# Try to import gamin
|