Sophie

Sophie

sources > fail2ban > pyinotify.patch > bc125ded03f0185e6a307ffe2242488d
Prev Next
@@ -, +, @@ 
 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