From 7bcfd2ace98ed74bfb8b1e601b5c38ad6572633e Mon Sep 17 00:00:00 2001
From: Cyril Jaquier <cyril.jaquier@fail2ban.org>
Date: Sun, 21 Jan 2007 22:21:13 +0000
Subject: [PATCH] - Added file support to fail2ban-regex. Benchmark feature has
 been removed

git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/trunk@523 a942ae1a-1317-0410-a47c-b1dcaea8d605
---
 fail2ban-regex        | 232 ++++++++++++++++++++++++++++++------------
 man/fail2ban-client.1 |   9 +-
 man/fail2ban-regex.1  |  30 +++++-
 man/fail2ban-server.1 |   4 +-
 man/generate-man      |  28 ++++-
 5 files changed, 225 insertions(+), 78 deletions(-)

diff --git a/fail2ban-regex b/fail2ban-regex
index 8074a853..261f742b 100755
--- a/fail2ban-regex
+++ b/fail2ban-regex
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/python -O
 # This file is part of Fail2Ban.
 #
 # Fail2Ban is free software; you can redistribute it and/or modify
@@ -25,12 +25,14 @@ __date__ = "$Date$"
 __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
 __license__ = "GPL"
 
-import locale, getopt, sys, time, logging, gc
+import getopt, sys, time, logging, os
 
 # Inserts our own modules path first in the list
 # fix for bug #343821
 sys.path.insert(1, "/usr/share/fail2ban")
 
+from ConfigParser import SafeConfigParser
+from ConfigParser import NoOptionError, NoSectionError, MissingSectionHeaderError
 from common.version import version
 from server.filter import Filter
 from server.regex import RegexException
@@ -38,13 +40,38 @@ from server.regex import RegexException
 # Gets the instance of the logger.
 logSys = logging.getLogger("fail2ban.regex")
 
+class RegexStat:
+
+	def __init__(self, failregex):
+		self.__stats = 0
+		self.__failregex = failregex
+		self.__ipList = list()
+	
+	def inc(self):
+		self.__stats += 1
+	
+	def getStats(self):
+		return self.__stats
+
+	def getFailRegex(self):
+		return self.__failregex
+	
+	def appendIP(self, value):
+		self.__ipList.extend(value)
+	
+	def getIPList(self):
+		return self.__ipList
+
 class Fail2banRegex:
 	
+	test = None
+	
 	def __init__(self):
 		self.__filter = Filter(None)
+		self.__failregex = list()
 		# Setup logging
 		logging.getLogger("fail2ban").handlers = []
-		self.__hdlr = logging.StreamHandler(sys.stdout)
+		self.__hdlr = logging.StreamHandler(Fail2banRegex.test)
 		# set a format which is simpler for console use
 		formatter = logging.Formatter("%(message)s")
 		# tell the handler to use this format
@@ -52,7 +79,8 @@ class Fail2banRegex:
 		logging.getLogger("fail2ban").addHandler(self.__hdlr)
 		logging.getLogger("fail2ban").setLevel(logging.ERROR)
 	
-	def dispVersion(self):
+	@staticmethod
+	def dispVersion():
 		print "Fail2Ban v" + version
 		print
 		print "Copyright (c) 2004-2006 Cyril Jaquier"
@@ -62,14 +90,26 @@ class Fail2banRegex:
 		print "Written by Cyril Jaquier <lostcontrol@users.sourceforge.net>."
 		print "Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>."
 	
-	def dispUsage(self):
-		print "Usage: "+sys.argv[0]+" <logline> <failregex>"
+	@staticmethod
+	def dispUsage():
+		print "Usage: "+sys.argv[0]+" [OPTIONS] <LOG> <REGEX>"
 		print
 		print "Fail2Ban v" + version + " reads log file that contains password failure report"
 		print "and bans the corresponding IP addresses using firewall rules."
 		print
-		print "This tools can test and benchmark your regular expressions for the \"failregex\""
-		print "option."
+		print "This tools can test regular expressions for \"fail2ban\"."
+		print
+		print "Options:"
+		print "    -h, --help              display this help message"
+		print "    -V, --version           print the version"
+		print
+		print "Log:"
+		print "    string                  a string representing a log line"
+		print "    filename                path to a log file (/var/log/auth.log)"
+		print
+		print "Regex:"
+		print "    string                  a string representing a 'failregex'"
+		print "    filename                path to a filter file (filter.d/sshd.conf)"
 		print
 		print "Report bugs to <lostcontrol@users.sourceforge.net>"
 	
@@ -78,87 +118,149 @@ class Fail2banRegex:
 		"""
 		for opt in optList:
 			if opt[0] in ["-h", "--help"]:
-	 			self.dispUsage()
-	 			sys.exit(0)
-	 		elif opt[0] in ["-V", "--version"]:
-	 			self.dispVersion()
-	 			sys.exit(0)
+				self.dispUsage()
+				sys.exit(0)
+			elif opt[0] in ["-V", "--version"]:
+				self.dispVersion()
+				sys.exit(0)
+
+	@staticmethod
+	def logIsFile(value):
+		return os.path.isfile(value)
+
+	def readRegex(self, value):
+		if os.path.isfile(value):
+			reader = SafeConfigParser()
+			try:
+				reader.read(value)
+				self.__failregex = [RegexStat(m)
+									for m in reader.get("Definition", "failregex").split('\n')]
+			except NoSectionError:
+				print "No [Definition] section in " + value
+				return False
+			except NoOptionError:
+				print "No failregex option in " + value
+				return False
+			except MissingSectionHeaderError:
+				print "No section headers in " + value
+				return False
+		else:
+			self.__failregex = [RegexStat(value)]
+		return True
 	
-	def testRegex(self, line, regex):
-		print
-		try:
+	def testRegex(self, line):
+		for regex in self.__failregex:
 			logging.getLogger("fail2ban").setLevel(logging.DEBUG)
-			self.__filter.addFailRegex(regex)
-			ret = self.__filter.findFailure(line)
-			print
-			logging.getLogger("fail2ban").setLevel(logging.CRITICAL)
-		except RegexException, e:
-			print e
-			return False
-		except IndexError:
-			print "Sorry, but no <host> found in regex"
-			return False
-		if len(ret) == 0:
+			try:
+				self.__filter.addFailRegex(regex.getFailRegex())
+				try:
+					ret = self.__filter.findFailure(line)
+					if not len(ret) == 0:
+						regex.inc()
+						regex.appendIP(ret)
+				except RegexException, e:
+					print e
+					return False
+				except IndexError:
+					print "Sorry, but no <host> found in regex"
+					return False
+			finally:
+				self.__filter.delFailRegex(0)
+				logging.getLogger("fail2ban").setLevel(logging.CRITICAL)
+	
+	def printStats(self):
+		print
+		print "Results"
+		print "======="
+		print
+		
+		# Print title
+		cnt = 1
+		print "Failregex:"
+		for failregex in self.__failregex:
+			print "[" + str(cnt) + "] " + failregex.getFailRegex()
+			cnt += 1
+		
+		print
+		
+		# Print stats
+		cnt = 1
+		total = 0
+		print "Number of matches:"
+		for failregex in self.__failregex:
+			match = failregex.getStats()
+			total += match
+			print "[" + str(cnt) + "] " + str(match) + " match(es)"
+			cnt += 1
+		
+		print
+		
+		if total == 0:
 			print "Sorry, no match"
+			print
+			print "Look at the above section 'Running tests' which could contain important"
+			print "information."
 			return False
 		else:
-			print "Success, the following data were found:"
-			timeTuple = time.localtime(ret[0][1])
-			print "Date: " + time.strftime("%a %b %d %H:%M:%S %Y", timeTuple)
-			ipList = ""
-			for i in ret:
-				ipList = ipList + " " + i[0]
-			print "IP  :" + ipList
+			# Print stats
+			cnt = 1
+			print "Addresses found:"
+			for failregex in self.__failregex:
+				print "[" + str(cnt) + "]"
+				for ip in failregex.getIPList():
+					timeTuple = time.localtime(ip[1])
+					timeString = time.strftime("%a %b %d %H:%M:%S %Y", timeTuple)
+					print "    " + ip[0] + " (" + timeString + ")"
+				cnt += 1
+			
 			print
+			
 			print "Date template hits:"
 			for template in self.__filter.dateDetector.getTemplates():
 				print `template.getHits()` + " hit: " + template.getName()
+			
 			print
-			print "Benchmark. Executing 1000..."
-			gc.disable()
-			total = 0
-			maxValue = 0
-			maxPos = 0
-			minValue = 99999999
-			minPos = 0
-			for i in range(1000):
-				start = time.time()
-				ret = self.__filter.findFailure(line)
-				end = time.time()
-				diff = (end - start) * 1000
-				total = total + diff
-				minValue = min(minValue, diff)
-				if minValue == diff:
-					minPos = i
-				maxValue = max(maxValue, diff)
-				if maxValue == diff:
-					maxPos = i
-			gc.enable()
-			print "Performance"
-			print "Avg: " + `total / 1000` + " ms"
-			print "Max: " + `maxValue` + " ms (Run " + `maxPos` + ")"
-			print "Min: " + `minValue` + " ms (Run " + `minPos` + ")"
+			
+			print "Success, the total number of match is " + str(total)
+			print
+			print "However, look at the above section 'Running tests' which could contain important"
+			print "information."
 			return True
+
 			
 if __name__ == "__main__":
-	regex = Fail2banRegex()
+	fail2banRegex = Fail2banRegex()
 	# Reads the command line options.
 	try:
 		cmdOpts = 'hV'
 		cmdLongOpts = ['help', 'version']
 		optList, args = getopt.getopt(sys.argv[1:], cmdOpts, cmdLongOpts)
 	except getopt.GetoptError:
-		regex.dispUsage()
+		fail2banRegex.dispUsage()
 		sys.exit(-1)
 	# Process command line
-	regex.getCmdLineOptions(optList)
+	fail2banRegex.getCmdLineOptions(optList)
 	# We need exactly 3 parameters
-	if len(sys.argv) <> 3:
-		regex.dispUsage()
+	if not len(sys.argv) == 3:
+		fail2banRegex.dispUsage()
 		sys.exit(-1)
 	else:
-		ret = regex.testRegex(sys.argv[1], sys.argv[2])
-		if ret:
+		if fail2banRegex.readRegex(sys.argv[2]) == False:
+			sys.exit(-1)
+
+		print
+		print "Running tests"
+		print "============="
+		print
+
+		if fail2banRegex.logIsFile(sys.argv[1]):
+			hdlr = open(sys.argv[1])
+			for line in hdlr:
+				fail2banRegex.testRegex(line)
+		else:
+			fail2banRegex.testRegex(sys.argv[1])
+		
+		if fail2banRegex.printStats():
 			sys.exit(0)
 		else:
 			sys.exit(-1)
diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1
index 1ee203c7..b99c5ada 100644
--- a/man/fail2ban-client.1
+++ b/man/fail2ban-client.1
@@ -1,12 +1,11 @@
 .\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.36.
-.TH FAIL2BAN-CLIENT "1" "January 2007" "fail2ban-client v0.7.6" "User Commands"
+.TH FAIL2BAN-CLIENT "1" "January 2007" "fail2ban-client v0.7.6-SVN" "User Commands"
 .SH NAME
 fail2ban-client \- configure and control the server
-.SH SYNOPSIS
-.B fail2ban-client
-[\fIOPTIONS\fR]... \fI<COMMAND>\fR
 .SH DESCRIPTION
-Fail2Ban v0.7.6 reads log file that contains password failure report
+[?1034hUsage: ../fail2ban\-client [OPTIONS]... <COMMAND>
+.PP
+Fail2Ban v0.7.6\-SVN reads log file that contains password failure report
 and bans the corresponding IP addresses using firewall rules.
 .SH OPTIONS
 .TP
diff --git a/man/fail2ban-regex.1 b/man/fail2ban-regex.1
index 16e3fa5a..499c1c96 100644
--- a/man/fail2ban-regex.1
+++ b/man/fail2ban-regex.1
@@ -1,16 +1,36 @@
 .\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.36.
-.TH FAIL2BAN-REGEX "1" "January 2007" "fail2ban-regex v0.7.6" "User Commands"
+.TH FAIL2BAN-REGEX "1" "January 2007" "fail2ban-regex v0.7.6-SVN" "User Commands"
 .SH NAME
 fail2ban-regex \- test Fail2ban "failregex" option
 .SH SYNOPSIS
 .B fail2ban-regex
-\fI<logline> <failregex>\fR
+[\fIOPTIONS\fR] \fI<LOG> <REGEX>\fR
 .SH DESCRIPTION
-Fail2Ban v0.7.6 reads log file that contains password failure report
+Fail2Ban v0.7.6\-SVN reads log file that contains password failure report
 and bans the corresponding IP addresses using firewall rules.
 .PP
-This tools can test and benchmark your regular expressions for the "failregex"
-option.
+This tools can test regular expressions for "fail2ban".
+.SH OPTIONS
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+display this help message
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+print the version
+.SH LOG
+.TP
+\fBstring\fR
+a string representing a log line
+.TP
+\fBfilename\fR
+path to a log file (/var/log/auth.log)
+.SH REGEX
+.TP
+\fBstring\fR
+a string representing a 'failregex'
+.TP
+\fBfilename\fR
+path to a filter file (filter.d/sshd.conf)
 .SH AUTHOR
 Written by Cyril Jaquier <lostcontrol@users.sourceforge.net>.
 Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>.
diff --git a/man/fail2ban-server.1 b/man/fail2ban-server.1
index 85780a23..211cbabf 100644
--- a/man/fail2ban-server.1
+++ b/man/fail2ban-server.1
@@ -1,12 +1,12 @@
 .\" DO NOT MODIFY THIS FILE!  It was generated by help2man 1.36.
-.TH FAIL2BAN-SERVER "1" "January 2007" "fail2ban-server v0.7.6" "User Commands"
+.TH FAIL2BAN-SERVER "1" "January 2007" "fail2ban-server v0.7.6-SVN" "User Commands"
 .SH NAME
 fail2ban-server \- start the server
 .SH SYNOPSIS
 .B fail2ban-server
 [\fIOPTIONS\fR]
 .SH DESCRIPTION
-Fail2Ban v0.7.6 reads log file that contains password failure report
+Fail2Ban v0.7.6\-SVN reads log file that contains password failure report
 and bans the corresponding IP addresses using firewall rules.
 .PP
 Only use this command for debugging purpose. Start the server with
diff --git a/man/generate-man b/man/generate-man
index bf4f651e..86ad5c12 100755
--- a/man/generate-man
+++ b/man/generate-man
@@ -40,4 +40,30 @@ echo "[done]"
 echo -n "Generating fail2ban-regex  "
 help2man --section=1 --no-info --include=fail2ban-regex.h2m --output fail2ban-regex.1 ../fail2ban-regex
 echo "[done]"
-
+echo -n "Patching fail2ban-regex    "
+# Changes the title.
+sed -i -e 's/.SS "Log:"/.SH LOG/' fail2ban-regex.1
+sed -i -e 's/.SS "Regex:"/.SH REGEX/' fail2ban-regex.1
+# Sets bold font for commands.
+IFS="
+"
+NEXT=0
+FOUND=0
+LINES=$( cat fail2ban-regex.1 )
+echo -n "" > fail2ban-regex.1
+for LINE in $LINES; do
+	if [ "$LINE" = ".SH LOG" ]; then
+		FOUND=1
+	fi
+	if [ $NEXT -eq 1 ] && [ $FOUND -eq 1 ]; then
+		echo "\fB$LINE\fR" >> fail2ban-regex.1
+	else
+		echo "$LINE" >> fail2ban-regex.1
+	fi
+	if [ "$LINE" = ".TP" ]; then
+		NEXT=1
+	else
+		NEXT=0
+	fi
+done
+echo "[done]"