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.
fail2ban/pyinotify.patch

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