mirror of https://github.com/fail2ban/fail2ban
				
				
				
			Initial changes and test for multi-line filtering
							parent
							
								
									51a3be2d79
								
							
						
					
					
						commit
						aec709f4c1
					
				|  | @ -63,6 +63,7 @@ class JailReader(ConfigReader): | ||||||
| 				["string", "logpath", "/var/log/messages"], | 				["string", "logpath", "/var/log/messages"], | ||||||
| 				["string", "backend", "auto"], | 				["string", "backend", "auto"], | ||||||
| 				["int", "maxretry", 3], | 				["int", "maxretry", 3], | ||||||
|  | 				["int", "maxlines", 1], | ||||||
| 				["int", "findtime", 600], | 				["int", "findtime", 600], | ||||||
| 				["int", "bantime", 600], | 				["int", "bantime", 600], | ||||||
| 				["string", "usedns", "warn"], | 				["string", "usedns", "warn"], | ||||||
|  | @ -114,6 +115,8 @@ class JailReader(ConfigReader): | ||||||
| 				backend = self.__opts[opt] | 				backend = self.__opts[opt] | ||||||
| 			elif opt == "maxretry": | 			elif opt == "maxretry": | ||||||
| 				stream.append(["set", self.__name, "maxretry", self.__opts[opt]]) | 				stream.append(["set", self.__name, "maxretry", self.__opts[opt]]) | ||||||
|  | 			elif opt == "maxlines": | ||||||
|  | 				stream.append(["set", self.__name, "maxlines", self.__opts[opt]]) | ||||||
| 			elif opt == "ignoreip": | 			elif opt == "ignoreip": | ||||||
| 				for ip in self.__opts[opt].split(): | 				for ip in self.__opts[opt].split(): | ||||||
| 					# Do not send a command if the rule is empty. | 					# Do not send a command if the rule is empty. | ||||||
|  |  | ||||||
|  | @ -66,6 +66,7 @@ protocol = [ | ||||||
| ["set <JAIL> banip <IP>", "manually Ban <IP> for <JAIL>"],  | ["set <JAIL> banip <IP>", "manually Ban <IP> for <JAIL>"],  | ||||||
| ["set <JAIL> unbanip <IP>", "manually Unban <IP> in <JAIL>"],  | ["set <JAIL> unbanip <IP>", "manually Unban <IP> in <JAIL>"],  | ||||||
| ["set <JAIL> maxretry <RETRY>", "sets the number of failures <RETRY> before banning the host for <JAIL>"],  | ["set <JAIL> maxretry <RETRY>", "sets the number of failures <RETRY> before banning the host for <JAIL>"],  | ||||||
|  | ["set <JAIL> maxlines <LINES>", "sets the number of <LINES> to buffer for regex search for <JAIL>"],  | ||||||
| ["set <JAIL> addaction <ACT>", "adds a new action named <NAME> for <JAIL>"],  | ["set <JAIL> addaction <ACT>", "adds a new action named <NAME> for <JAIL>"],  | ||||||
| ["set <JAIL> delaction <ACT>", "removes the action <NAME> from <JAIL>"],  | ["set <JAIL> delaction <ACT>", "removes the action <NAME> from <JAIL>"],  | ||||||
| ["set <JAIL> setcinfo <ACT> <KEY> <VALUE>", "sets <VALUE> for <KEY> of the action <NAME> for <JAIL>"],  | ["set <JAIL> setcinfo <ACT> <KEY> <VALUE>", "sets <VALUE> for <KEY> of the action <NAME> for <JAIL>"],  | ||||||
|  | @ -84,6 +85,7 @@ protocol = [ | ||||||
| ["get <JAIL> bantime", "gets the time a host is banned for <JAIL>"], | ["get <JAIL> bantime", "gets the time a host is banned for <JAIL>"], | ||||||
| ["get <JAIL> usedns", "gets the usedns setting for <JAIL>"], | ["get <JAIL> usedns", "gets the usedns setting for <JAIL>"], | ||||||
| ["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"], | ["get <JAIL> maxretry", "gets the number of failures allowed for <JAIL>"], | ||||||
|  | ["get <JAIL> maxlines", "gets the number of lines to buffer for <JAIL>"], | ||||||
| ["get <JAIL> addaction", "gets the last action which has been added for <JAIL>"], | ["get <JAIL> addaction", "gets the last action which has been added for <JAIL>"], | ||||||
| ["get <JAIL> actionstart <ACT>", "gets the start command for the action <ACT> for <JAIL>"], | ["get <JAIL> actionstart <ACT>", "gets the start command for the action <ACT> for <JAIL>"], | ||||||
| ["get <JAIL> actionstop <ACT>", "gets the stop command for the action <ACT> for <JAIL>"], | ["get <JAIL> actionstop <ACT>", "gets the stop command for the action <ACT> for <JAIL>"], | ||||||
|  |  | ||||||
|  | @ -145,6 +145,11 @@ sets the number of failures | ||||||
| <RETRY> before banning the host | <RETRY> before banning the host | ||||||
| for <JAIL> | for <JAIL> | ||||||
| .TP | .TP | ||||||
|  | \fBset <JAIL> maxlines <LINES>\fR | ||||||
|  | sets the number of <LINES> to | ||||||
|  | buffer for regex search for | ||||||
|  | <JAIL> | ||||||
|  | .TP | ||||||
| \fBset <JAIL> addaction <ACT>\fR | \fBset <JAIL> addaction <ACT>\fR | ||||||
| adds a new action named <NAME> for | adds a new action named <NAME> for | ||||||
| <JAIL> | <JAIL> | ||||||
|  | @ -222,6 +227,10 @@ gets the time a host is banned for | ||||||
| gets the number of failures | gets the number of failures | ||||||
| allowed for <JAIL> | allowed for <JAIL> | ||||||
| .TP | .TP | ||||||
|  | \fBget <JAIL> maxlines\fR | ||||||
|  | gets the number lines to | ||||||
|  | buffer for <JAIL> | ||||||
|  | .TP | ||||||
| \fBget <JAIL> addaction\fR | \fBget <JAIL> addaction\fR | ||||||
| gets the last action which has | gets the last action which has | ||||||
| been added for <JAIL> | been added for <JAIL> | ||||||
|  |  | ||||||
|  | @ -51,7 +51,7 @@ class Regex: | ||||||
| 		if regex.lstrip() == '': | 		if regex.lstrip() == '': | ||||||
| 			raise RegexException("Cannot add empty regex") | 			raise RegexException("Cannot add empty regex") | ||||||
| 		try: | 		try: | ||||||
| 			self._regexObj = re.compile(regex) | 			self._regexObj = re.compile(regex, re.MULTILINE) | ||||||
| 			self._regex = regex | 			self._regex = regex | ||||||
| 		except sre_constants.error: | 		except sre_constants.error: | ||||||
| 			raise RegexException("Unable to compile regular expression '%s'" % | 			raise RegexException("Unable to compile regular expression '%s'" % | ||||||
|  |  | ||||||
|  | @ -36,6 +36,7 @@ from mytime import MyTime | ||||||
| from failregex import FailRegex, Regex, RegexException | from failregex import FailRegex, Regex, RegexException | ||||||
| 
 | 
 | ||||||
| import logging, re, os, fcntl, time | import logging, re, os, fcntl, time | ||||||
|  | from collections import deque | ||||||
| 
 | 
 | ||||||
| # Gets the instance of the logger. | # Gets the instance of the logger. | ||||||
| logSys = logging.getLogger("fail2ban.filter") | logSys = logging.getLogger("fail2ban.filter") | ||||||
|  | @ -71,6 +72,10 @@ class Filter(JailThread): | ||||||
| 		self.__findTime = 6000 | 		self.__findTime = 6000 | ||||||
| 		## The ignore IP list. | 		## The ignore IP list. | ||||||
| 		self.__ignoreIpList = [] | 		self.__ignoreIpList = [] | ||||||
|  | 		## Size of line buffer | ||||||
|  | 		self.__line_buffer_size = 1 | ||||||
|  | 		## Line buffer | ||||||
|  | 		self.__line_buffer = deque() | ||||||
| 
 | 
 | ||||||
| 		self.dateDetector = DateDetector() | 		self.dateDetector = DateDetector() | ||||||
| 		self.dateDetector.addDefaultTemplate() | 		self.dateDetector.addDefaultTemplate() | ||||||
|  | @ -204,6 +209,25 @@ class Filter(JailThread): | ||||||
| 	def getMaxRetry(self): | 	def getMaxRetry(self): | ||||||
| 		return self.failManager.getMaxRetry() | 		return self.failManager.getMaxRetry() | ||||||
| 
 | 
 | ||||||
|  | 	## | ||||||
|  | 	# Set the maximum line buffer size. | ||||||
|  | 	# | ||||||
|  | 	# @param value the line buffer size | ||||||
|  | 
 | ||||||
|  | 	def setMaxLines(self, value): | ||||||
|  | 		if value < 1: | ||||||
|  | 			value = 1 | ||||||
|  | 		self.__line_buffer_size = value | ||||||
|  | 		logSys.info("Set maxLines = %s" % value) | ||||||
|  | 
 | ||||||
|  | 	## | ||||||
|  | 	# Get the maximum line buffer size. | ||||||
|  | 	# | ||||||
|  | 	# @return the line buffer size | ||||||
|  | 
 | ||||||
|  | 	def getMaxLines(self): | ||||||
|  | 		return self.__line_buffer_size | ||||||
|  | 
 | ||||||
| 	## | 	## | ||||||
| 	# Main loop. | 	# Main loop. | ||||||
| 	# | 	# | ||||||
|  | @ -305,7 +329,10 @@ class Filter(JailThread): | ||||||
| 		else: | 		else: | ||||||
| 			timeLine = l | 			timeLine = l | ||||||
| 			logLine = l | 			logLine = l | ||||||
| 		return self.findFailure(timeLine, logLine) | 		self.__line_buffer.append(logLine) | ||||||
|  | 		while len(self.__line_buffer) > self.__line_buffer_size: | ||||||
|  | 			self.__line_buffer.popleft() | ||||||
|  | 		return self.findFailure(timeLine, "".join(self.__line_buffer)) | ||||||
| 
 | 
 | ||||||
| 	def processLineAndAdd(self, line): | 	def processLineAndAdd(self, line): | ||||||
| 		"""Processes the line for failures and populates failManager | 		"""Processes the line for failures and populates failManager | ||||||
|  | @ -365,6 +392,7 @@ class Filter(JailThread): | ||||||
| 								 "in order to get support for this format." | 								 "in order to get support for this format." | ||||||
| 								 % (logLine, timeLine)) | 								 % (logLine, timeLine)) | ||||||
| 				else: | 				else: | ||||||
|  | 					self.__line_buffer.clear() | ||||||
| 					try: | 					try: | ||||||
| 						host = failRegex.getHost() | 						host = failRegex.getHost() | ||||||
| 						ipMatch = DNSUtils.textToIp(host, self.__useDns) | 						ipMatch = DNSUtils.textToIp(host, self.__useDns) | ||||||
|  |  | ||||||
|  | @ -216,6 +216,12 @@ class Server: | ||||||
| 	def getMaxRetry(self, name): | 	def getMaxRetry(self, name): | ||||||
| 		return self.__jails.getFilter(name).getMaxRetry() | 		return self.__jails.getFilter(name).getMaxRetry() | ||||||
| 	 | 	 | ||||||
|  | 	def setMaxLines(self, name, value): | ||||||
|  | 		self.__jails.getFilter(name).setMaxLines(value) | ||||||
|  | 	 | ||||||
|  | 	def getMaxLines(self, name): | ||||||
|  | 		return self.__jails.getFilter(name).getMaxLines() | ||||||
|  | 	 | ||||||
| 	# Action | 	# Action | ||||||
| 	def addAction(self, name, value): | 	def addAction(self, name, value): | ||||||
| 		self.__jails.getAction(name).addAction(value) | 		self.__jails.getAction(name).addAction(value) | ||||||
|  |  | ||||||
|  | @ -167,6 +167,10 @@ class Transmitter: | ||||||
| 			value = command[2] | 			value = command[2] | ||||||
| 			self.__server.setMaxRetry(name, int(value)) | 			self.__server.setMaxRetry(name, int(value)) | ||||||
| 			return self.__server.getMaxRetry(name) | 			return self.__server.getMaxRetry(name) | ||||||
|  | 		elif command[1] == "maxlines": | ||||||
|  | 			value = command[2] | ||||||
|  | 			self.__server.setMaxLines(name, int(value)) | ||||||
|  | 			return self.__server.getMaxLines(name) | ||||||
| 		# command | 		# command | ||||||
| 		elif command[1] == "bantime": | 		elif command[1] == "bantime": | ||||||
| 			value = command[2] | 			value = command[2] | ||||||
|  | @ -245,6 +249,8 @@ class Transmitter: | ||||||
| 			return self.__server.getFindTime(name) | 			return self.__server.getFindTime(name) | ||||||
| 		elif command[1] == "maxretry": | 		elif command[1] == "maxretry": | ||||||
| 			return self.__server.getMaxRetry(name) | 			return self.__server.getMaxRetry(name) | ||||||
|  | 		elif command[1] == "maxlines": | ||||||
|  | 			return self.__server.getMaxLines(name) | ||||||
| 		# Action | 		# Action | ||||||
| 		elif command[1] == "bantime": | 		elif command[1] == "bantime": | ||||||
| 			return self.__server.getBanTime(name) | 			return self.__server.getBanTime(name) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | Aug 14 11:59:58 [sshd] Invalid user toto... | ||||||
|  | Aug 14 11:59:58 [sshd] from 212.41.96.185 | ||||||
|  | Aug 14 11:59:58 [sshd] Invalid user toto... | ||||||
|  | Aug 14 11:59:58 [sshd] from 212.41.96.185 | ||||||
|  | Aug 14 11:59:58 [sshd] Invalid user fuck... | ||||||
|  | Aug 14 11:59:58 [sshd] from 212.41.96.185 | ||||||
|  | Aug 14 11:59:58 [sshd] Invalid user toto... | ||||||
|  | Aug 14 11:59:58 [sshd] from 212.41.96.185 | ||||||
|  | Aug 14 11:59:58 [sshd] Invalid user fuck... | ||||||
|  | Aug 14 11:59:58 [sshd] from 212.41.96.185 | ||||||
|  | Aug 14 11:59:58 [sshd] Invalid user fuck... | ||||||
|  | Aug 14 11:59:58 [sshd] from 212.41.96.185 | ||||||
|  | @ -499,6 +499,7 @@ class GetFailures(unittest.TestCase): | ||||||
| 	FILENAME_03 = "testcases/files/testcase03.log" | 	FILENAME_03 = "testcases/files/testcase03.log" | ||||||
| 	FILENAME_04 = "testcases/files/testcase04.log" | 	FILENAME_04 = "testcases/files/testcase04.log" | ||||||
| 	FILENAME_USEDNS = "testcases/files/testcase-usedns.log" | 	FILENAME_USEDNS = "testcases/files/testcase-usedns.log" | ||||||
|  | 	FILENAME_MULTILINE = "testcases/files/testcase-multiline.log" | ||||||
| 
 | 
 | ||||||
| 	# so that they could be reused by other tests | 	# so that they could be reused by other tests | ||||||
| 	FAILURES_01 = ('193.168.0.128', 3, 1124013599.0, | 	FAILURES_01 = ('193.168.0.128', 3, 1124013599.0, | ||||||
|  | @ -604,6 +605,20 @@ class GetFailures(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
| 		self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan) | 		self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan) | ||||||
| 
 | 
 | ||||||
|  | 	def testGetFailuresMultiLine(self): | ||||||
|  | 		output = ("212.41.96.185", 3, 1124013598.0) | ||||||
|  | 		self.filter.addLogPath(GetFailures.FILENAME_MULTILINE) | ||||||
|  | 		self.filter.addFailRegex("Invalid user .+\n.+ from <HOST>$") | ||||||
|  | 		self.filter.addIgnoreRegex("user fuck") | ||||||
|  | 
 | ||||||
|  | 		self.filter.setMaxLines(2) | ||||||
|  | 
 | ||||||
|  | 		self.filter.getFailures(GetFailures.FILENAME_MULTILINE) | ||||||
|  | 
 | ||||||
|  | 		_assert_correct_last_attempt(self, self.filter, output) | ||||||
|  | 
 | ||||||
|  | 		self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan) | ||||||
|  | 
 | ||||||
| class DNSUtilsTests(unittest.TestCase): | class DNSUtilsTests(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
| 	def testUseDns(self): | 	def testUseDns(self): | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Steven Hiscocks
						Steven Hiscocks