diff --git a/pyinotify.patch b/pyinotify.patch index f76ee0a..8a16c65 100644 --- a/pyinotify.patch +++ b/pyinotify.patch @@ -1,1379 +1,224 @@ - - - - -Attachment #381327 for bug #551895 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - View - | Details - | Raw Unified - | Return to bug 551895 -
- - -Collapse All | -Expand All - - -

+@@ -, +, @@ + 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 - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(-)a/config/jail.conf - (-3 / +6 lines) -
- -
-  Lines 26-38 -   findtime = 600 - - Link Here  -
-
26
maxretry = 3
26
maxretry = 3
27
27
28
# "backend" specifies the backend used to get files modification. Available
28
# "backend" specifies the backend used to get files modification. Available
29
# options are "gamin", "polling" and "auto". This option can be overridden in
29
# options are "inotify", "gamin", "polling" and "auto". This option can be
30
# each jail too (use "gamin" for a jail and "polling" for another).
30
# overridden in each jail too (use "gamin" for a jail and "polling" for
31
# another).
31
#
32
#
33
# inotify: requires pyinotify and the a kernel supporting Inotify
32
# gamin:   requires Gamin (a file alteration monitor) to be installed. If Gamin
34
# gamin:   requires Gamin (a file alteration monitor) to be installed. If Gamin
33
#          is not installed, Fail2ban will use polling.
35
#          is not installed, Fail2ban will use polling.
34
# polling: uses a polling algorithm which does not require external libraries.
36
# polling: uses a polling algorithm which does not require external libraries.
35
# auto:    will choose Gamin if available and polling otherwise.
37
# auto:    will choose Inotify if pyinotify is present, if not then it will
38
# 	   try Gamin and use that if available, and polling otherwise.
36
backend = auto
39
backend = auto
37
40
38
41
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(-)a/server/filterinotify.py - (+157 lines) -
- -
- Line 0 -    - - Link Here  -
-
1
# This file is part of Fail2Ban.
2
#
3
# Fail2Ban is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# Fail2Ban is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with Fail2Ban; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
# Author: Jonathan G. Underwood
18
# 
19
# $Revision$
20
21
__author__ = "Jonathan G. Underwood"
22
__version__ = "$Revision$"
23
__date__ = "$Date$"
24
__copyright__ = "Copyright (c) 2010 Jonathan G. Underwood"
25
__license__ = "GPL"
26
27
from failmanager import FailManagerEmpty
28
from filter import FileFilter
29
from mytime import MyTime
30
31
import time, logging
32
33
import pyinotify
34
from pyinotify import ProcessEvent, WatchManager, Notifier
35
36
# Gets the instance of the logger.
37
logSys = logging.getLogger("fail2ban.filter")
38
39
##
40
# Log reader class.
41
#
42
# This class reads a log file and detects login failures or anything else
43
# that matches a given regular expression. This class is instanciated by
44
# a Jail object.
45
46
class FilterInotify(ProcessEvent, FileFilter):
47
48
	##
49
	# Constructor.
50
	#
51
	# Initialize the filter object with default values.
52
	# @param jail the jail object
53
	
54
	# Note that according to the pyinotify documentation we shouldn't
55
	# define an __init__ function, but define a my_init function which is
56
	# called by ProcessEvent.__init__. However, that approach appears not
57
	# to work here and so we define __init__ and call
58
	# ProcessEvent.__init__ from here.
59
	def __init__(self, jail):
60
		FileFilter.__init__(self, jail)
61
		ProcessEvent.__init__(self)
62
		self.__monitor = WatchManager()
63
		self.__notifier = Notifier(self.__monitor, self)
64
		self.__mask = pyinotify.IN_MODIFY | pyinotify.IN_CREATE
65
		
66
	##
67
	# Event handling functions used by pyinotify.ProcessEvent
68
	# instance. These simply call the __handleMod method.
69
	# @event an event object
70
71
	def process_IN_MODIFY(self, event):
72
		logSys.debug("process_IN_MODIFY called")
73
		self.__handleMod(event)
74
75
	def process_IN_CREATE(self, event):
76
		logSys.debug("process_IN_CREATE called")
77
		self.__handleMod(event)
78
79
	##
80
	# This method handles all modified file events
81
	# @event an event object
82
83
	def __handleMod(self, event):
84
		self.getFailures(event.path)
85
		try:
86
			while True:
87
				ticket = self.failManager.toBan()
88
				self.jail.putFailTicket(ticket)
89
		except FailManagerEmpty:
90
			self.failManager.cleanup(MyTime.time())
91
		self.dateDetector.sortTemplate()
92
			
93
	##
94
	# Add a log file path
95
	#
96
	# @param path log file path
97
98
	def addLogPath(self, path, tail = False):
99
		if self.containsLogPath(path):
100
			logSys.error(path + " already exists")
101
		else:
102
			wd = self.__monitor.add_watch(path, self.__mask)
103
			if wd[path] > 0:
104
				FileFilter.addLogPath(self, path, tail)
105
				logSys.info("Added logfile = %s" % path)
106
			else:
107
				logSys.error("Failed to add an inotify watch for logfile = %s" % path)
108
	
109
	##
110
	# Delete a log path
111
	#
112
	# @param path the log file to delete
113
	
114
	def delLogPath(self, path):
115
		if not self.containsLogPath(path):
116
			logSys.error(path + " is not monitored")
117
		else:
118
			rd = self.__monitor.rm_watch(self.__monitor.get_wd(path))
119
			if rd[path]:
120
				FileFilter.delLogPath(self, path)
121
				logSys.info("Removed logfile = %s" % path)
122
			else:
123
				logSys.error("Failed to remove inotify watch for logfile = %s" % path)
124
		
125
	##
126
	# Main loop.
127
	#
128
	# This function is the main loop of the thread. It checks if the
129
	# file has been modified and looks for failures.
130
	# @return True when the thread exits nicely
131
132
	def run(self):
133
		self.setActive(True)
134
		while self._isActive():
135
			if not self.getIdle():
136
				# We cannot block here because we want to be able to
137
				# exit. __notifier.check_events will block for
138
				# timeout milliseconds.
139
				if self.__notifier.check_events(timeout=10):
140
					self.__notifier.read_events()
141
					self.__notifier.process_events()
142
				time.sleep(self.getSleepTime())
143
			else:
144
				time.sleep(self.getSleepTime())
145
146
		# Cleanup when shutting down
147
		for wd in self.watchd.keys():
148
			self.__monitor.rm_watch(wd)
149
		del self.__monitor
150
		self.__notifier.stop()
151
		del self.__notifier
152
153
		logSys.debug(self.jail.getName() + ": filter terminated")
154
		return True
155
156
157
				
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(-)a/server/jail.py - (-2 / +16 lines) -
- -
-  Lines 40-56 -   class Jail: - - Link Here  -
-
40
		logSys.info("Creating new jail '%s'" % self.__name)
40
		logSys.info("Creating new jail '%s'" % self.__name)
41
		if backend == "polling":
41
		if backend == "polling":
42
			self.__initPoller()
42
			self.__initPoller()
43
		elif backend == "inotify":
44
			self.__initInotify()
45
		elif backend == "gamin":
46
			self.__initGamin()
43
		else:
47
		else:
44
			try:
48
			try:
45
				self.__initGamin()
49
				self.__initInotify()
46
			except ImportError:
50
			except ImportError:
47
				self.__initPoller()
51
				try:
52
					self.__initGamin()
53
				except ImportError:
54
					self.__initPoller()
48
		self.__action = Actions(self)
55
		self.__action = Actions(self)
49
	
56
	
50
	def __initPoller(self):
57
	def __initPoller(self):
51
		logSys.info("Jail '%s' uses poller" % self.__name)
58
		logSys.info("Jail '%s' uses poller" % self.__name)
52
		from filterpoll import FilterPoll
59
		from filterpoll import FilterPoll
53
		self.__filter = FilterPoll(self)
60
		self.__filter = FilterPoll(self)
61
62
	def __initInotify(self):
63
		# Try to import pyinotify
64
		import pyinotify
65
		logSys.info("Jail '%s' uses Inotify" % self.__name)
66
		from filterinotify import FilterInotify
67
		self.__filter = FilterInotify(self)
54
	
68
	
55
	def __initGamin(self):
69
	def __initGamin(self):
56
		# Try to import gamin
70
		# Try to import gamin
- - -
- - Return to bug 551895 -
- - - - - - - - \ No newline at end of file + # "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