From 2e5bfe5bb640c3aaefe7310f68c9144baf935b97 Mon Sep 17 00:00:00 2001 From: Cyril Jaquier Date: Fri, 18 Feb 2005 13:30:54 +0000 Subject: [PATCH] - Changed Fail2Ban in order to handle several log files git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/trunk@50 a942ae1a-1317-0410-a47c-b1dcaea8d605 --- CHANGELOG | 8 +- MANIFEST | 3 +- README | 2 +- config/fail2ban.conf.default | 41 ++++++- confreader/__init__.py | 25 +++++ .../sshd.py => confreader/configreader.py | 50 ++++----- fail2ban.py | 77 +++++++------ log-test/apache | 102 ++++++++++++++++++ log-test/current | 9 +- log-test/test | 9 +- logreader/logreader.py | 70 ++++++------ logreader/parser.py | 67 ------------ setup.py | 2 +- version.py | 2 +- 14 files changed, 298 insertions(+), 169 deletions(-) create mode 100644 confreader/__init__.py rename logreader/sshd.py => confreader/configreader.py (53%) create mode 100644 log-test/apache delete mode 100644 logreader/parser.py diff --git a/CHANGELOG b/CHANGELOG index c71f9a0e..d954dec9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,9 +4,15 @@ |_| \__,_|_|_/___|_.__/\__,_|_||_| ============================================================= -Fail2Ban (version 0.1.2) 11/21/2004 +Fail2Ban (version 0.3.0) 02/??/2005 ============================================================= +ver. 0.3.0 (02/??/2005) - alpha +---------- +- Re-writting of parts of the code in order to handle several + log files with different rules +- Removed sshd.py because it is no more needed + ver. 0.1.2 (11/21/2004) - beta ---------- - Add ipfw and ipfwadm support. The rules are taken from diff --git a/MANIFEST b/MANIFEST index f52e1848..d31f45f5 100644 --- a/MANIFEST +++ b/MANIFEST @@ -12,5 +12,6 @@ firewall/ipfwadm.py logreader/__init__.py logreader/logreader.py logreader/parser.py -logreader/sshd.py +confreader/__init__.py +confreader/configreader.py config/fail2ban.conf.default diff --git a/README b/README index 14a23b02..30b27060 100644 --- a/README +++ b/README @@ -79,7 +79,7 @@ options: -b start fail2ban in background -d start fail2ban in debug mode -e ban IP on the INTF interface - -f read password failure from FILE + -c read configuration file FILE -h display this help message -i IP(s) to ignore -l log message in FILE diff --git a/config/fail2ban.conf.default b/config/fail2ban.conf.default index c1bb16f7..3a4e79cc 100644 --- a/config/fail2ban.conf.default +++ b/config/fail2ban.conf.default @@ -16,10 +16,6 @@ background = false # and bypass root user test. debug = false -# pwdfailfile: the path of the file which contains the -# password failure log. -pwdfailfile = /var/log/pwdfail/current - # logfile: the path of the file for logging messages of # fail2ban. logfile = /var/log/fail2ban.log @@ -44,3 +40,40 @@ interface = eth0 # log file). 1 is a good value. polltime = 1 +# You can define a new section for each log file to check for +# password failure. + +[Apache] +# logfile: file to monitor. +logfile = log-test/apache + +# timeregex: regular expression which have to match the +# timestamp of an Apache log event. +# [Wed Jan 05 15:08:01 2005] +timeregex = \S{3} \S{3} \d{2} \d{2}:\d{2}:\d{2} \d{4} + +# timepattern: indicates the "timeregex" fields signification. +# See syntax here: http://rgruet.free.fr/PQR2.3.html#timeModule +timepattern = %%a %%b %%d %%H:%%M:%%S %%Y + +# failregex: regular expression which have to match the +# message written in the log file in case of password failure. +failregex = authentication failure + +[SSH] +# logfile: file to monitor. +logfile = log-test/current + +# timeregex: regular expression which have to match the +# timestamp of an Apache log event. +# Mar 7 17:53:28 +timeregex = \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2} + +# timepattern: indicates the "timeregex" fields signification. +# See syntax here: http://rgruet.free.fr/PQR2.3.html#timeModule +timepattern = %%b %%d %%H:%%M:%%S + +# failregex: regular expression which have to match the +# message written in the log file in case of password failure. +failregex = Authentication failure + diff --git a/confreader/__init__.py b/confreader/__init__.py new file mode 100644 index 00000000..76dba873 --- /dev/null +++ b/confreader/__init__.py @@ -0,0 +1,25 @@ +# 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: Cyril Jaquier +# +# $Revision$ + +__author__ = "Cyril Jaquier" +__version__ = "$Revision$" +__date__ = "$Date$" +__copyright__ = "Copyright (c) 2004 Cyril Jaquier" +__license__ = "GPL" \ No newline at end of file diff --git a/logreader/sshd.py b/confreader/configreader.py similarity index 53% rename from logreader/sshd.py rename to confreader/configreader.py index 2adc87f3..5f8679dd 100644 --- a/logreader/sshd.py +++ b/confreader/configreader.py @@ -24,32 +24,34 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -from parser import Parser +import os, sys, time -class Sshd(Parser): - """ OpenSSH daemon log parser. Contains specific code for sshd. +from ConfigParser import * + +class ConfigReader: + """ Reads a log file and reports information about IP that make password + failure, bad user or anything else that is considered as doubtful login + attempt. """ - _instance = None - # This is the pattern to look for. - pattern = "Failed password|Illegal user" + optionValues = ("logfile", "timeregex", "timepattern", "failregex") + + def __init__(self, confPath): + self.confPath = confPath + self.configParser = SafeConfigParser() + + def openConf(self): + self.configParser.read(self.confPath) + + def getSections(self): + return self.configParser.sections() + + def getLogOptions(self, sec): + values = dict() + for option in self.optionValues: + v = self.configParser.get(sec, option) + values[option] = v + return values + - def getInstance(): - """ We use a singleton. - """ - if not Sshd._instance: - Sshd._instance = Sshd() - return Sshd._instance - - getInstance = staticmethod(getInstance) - def parseLogLine(self, line): - """ Matches sshd bad login attempt. Returns the IP and the - log time. - """ - if self.getLogMatch(self.pattern, line): - matchIP = self.getLogIP(line) - if matchIP: - return [matchIP, self.getLogTime(line)] - else: - return False diff --git a/fail2ban.py b/fail2ban.py index 9ed5738a..a7adfd2d 100755 --- a/fail2ban.py +++ b/fail2ban.py @@ -44,6 +44,7 @@ from firewall.ipfw import Ipfw from firewall.ipfwadm import Ipfwadm from logreader.logreader import LogReader from version import version +from confreader.configreader import ConfigReader def usage(): print "Usage: fail2ban.py [OPTIONS]" @@ -54,7 +55,7 @@ def usage(): print " -b start fail2ban in background" print " -d start fail2ban in debug mode" print " -e ban IP on the INTF interface" - print " -f read password failure from FILE" + print " -c read configuration file FILE" print " -h display this help message" print " -i IP(s) to ignore" print " -l log message in FILE" @@ -167,15 +168,12 @@ if __name__ == "__main__": logSys = log4py.Logger().get_instance() logSys.set_formatstring("%T %L %M") - # Config file - configParser = SafeConfigParser() - configParser.read("/etc/fail2ban.conf") - conf = dict() conf["verbose"] = False conf["background"] = False conf["debug"] = False - conf["pwdfailfile"] = "/var/log/pwdfail/current" + conf["conffile"] = "/etc/fail2ban.conf" + conf["apachefile"] = "log-test/current" conf["logging"] = False conf["logfile"] = "/var/log/fail2ban.log" conf["maxretry"] = 3 @@ -185,6 +183,21 @@ if __name__ == "__main__": conf["firewall"] = "iptables" conf["polltime"] = 1 + # Reads the command line options. + try: + optList, args = getopt.getopt(sys.argv[1:], 'hvbdc:l:t:i:r:e:w:') + except getopt.GetoptError: + usage() + + # Pre-parsing of command line options for the -c option + for opt in optList: + if opt[0] == "-c": + conf["conffile"] = opt[1] + + # Config file + configParser = SafeConfigParser() + configParser.read(conf["conffile"]) + # background try: conf["background"] = configParser.getboolean("DEFAULT", "background") @@ -204,16 +217,6 @@ if __name__ == "__main__": except NoOptionError: logSys.warn("debug option not in config file") logSys.warn("Using default value") - - # pwdfailfile - try: - conf["pwdfailfile"] = configParser.get("DEFAULT", "pwdfailfile") - except ValueError: - logSys.warn("pwdfailfile option should be a string") - logSys.warn("Using default value") - except NoOptionError: - logSys.warn("pwdfailfile option not in config file") - logSys.warn("Using default value") # logfile try: @@ -265,7 +268,7 @@ if __name__ == "__main__": logSys.warn("interface option not in config file") logSys.warn("Using default value") - # interface + # firewall try: conf["firewall"] = configParser.get("DEFAULT", "firewall") except ValueError: @@ -285,12 +288,6 @@ if __name__ == "__main__": logSys.warn("polltime option not in config file") logSys.warn("Using default value") - # Reads the command line options. - try: - optList, args = getopt.getopt(sys.argv[1:], 'hvbdf:l:t:i:r:e:w:') - except getopt.GetoptError: - usage() - for opt in optList: if opt[0] == "-h": usage() @@ -302,8 +299,6 @@ if __name__ == "__main__": conf["debug"] = True if opt[0] == "-e": conf["interface"] = opt[1] - if opt[0] == "-f": - conf["pwdfailfile"] = opt[1] if opt[0] == "-l": conf["logging"] = True conf["logfile"] = opt[1] @@ -359,22 +354,35 @@ if __name__ == "__main__": if not conf["debug"]: sys.exit(-1) - logSys.debug("logFilePath is "+conf["pwdfailfile"]) + logSys.debug("ConfFile is "+conf["conffile"]) logSys.debug("BanTime is "+`conf["bantime"]`) logSys.debug("retryAllowed is "+`conf["maxretry"]`) + # Reads the config file and create a LogReader instance for + # each log file to check. + confReader = ConfigReader(conf["conffile"]); + confReader.openConf() + logList = list() + for t in confReader.getSections(): + l = confReader.getLogOptions(t) + lObj = LogReader(logSys, l["logfile"], l["timeregex"], + l["timepattern"], l["failregex"]) + lObj.openLogFile() + logList.append(lObj) + # Creates one instance of Iptables (thanks to Pyhton dynamic # features) and one of LogReader. fireWallObj = eval(fireWallName) fireWall = fireWallObj(conf["bantime"], logSys, conf["interface"]) - logFile = LogReader(conf["pwdfailfile"], logSys, conf["bantime"]) # We add 127.0.0.1 to the ignore list has we do not want # to be ban ourself. - logFile.addIgnoreIP("127.0.0.1") + for element in logList: + element.addIgnoreIP("127.0.0.1") while len(ignoreIPList) > 0: ip = ignoreIPList.pop() - logFile.addIgnoreIP(ip) + for element in logList: + element.addIgnoreIP(ip) logSys.info("Fail2Ban v"+version+" is running") # Main loop @@ -390,12 +398,19 @@ if __name__ == "__main__": # If the log file has not been modified since the # last time, we sleep for 1 second. This is active # polling so not very effective. - if not logFile.isModified(): + isModified = False + for element in logList: + if element.isModified(): + isModified = True + + if not isModified: time.sleep(conf["polltime"]) continue # Gets the failure list from the log file. - failList = logFile.getPwdFailure() + failList = dict() + for element in logList: + failList.update(element.getFailures()) # We iterate the failure list and ban IP that make # *retryAllowed* login failures. diff --git a/log-test/apache b/log-test/apache new file mode 100644 index 00000000..35248fd2 --- /dev/null +++ b/log-test/apache @@ -0,0 +1,102 @@ +[Mon Jan 03 05:02:15 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc +[Mon Jan 03 05:02:20 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc +[Mon Jan 03 05:02:20 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc +[Mon Jan 03 05:02:21 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc +[Mon Jan 03 05:02:22 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc +[Mon Jan 03 05:02:22 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc +[Mon Jan 03 05:02:22 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc +[Mon Jan 03 05:02:22 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/\xe0 +[Mon Jan 03 05:02:23 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msdac +[Mon Jan 03 05:02:23 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msdac +[Mon Jan 03 05:02:24 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/PBServer +[Mon Jan 03 05:02:24 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/PBServer +[Mon Jan 03 05:02:27 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/Rpc +[Mon Jan 03 05:02:27 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/Rpc +[Mon Jan 03 05:02:27 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/samples +[Mon Jan 03 05:02:27 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/samples +[Mon Jan 03 05:02:28 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts..\xc1\x9c.. +[Mon Jan 03 05:02:28 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:28 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:28 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:32 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:32 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:33 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:33 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:33 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:34 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:34 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:38 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:38 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:38 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:38 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:39 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 05:02:39 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/scripts +[Mon Jan 03 11:08:29 2005] [error] [client 128.178.150.127] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Mon Jan 03 20:08:52 2005] [error] [client 83.76.202.195] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:19:50 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:19:55 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:20:01 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:20:05 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:20:08 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:20:12 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:20:16 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:20:22 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:20:26 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:20:28 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 15:20:38 2005] [error] [client 213.221.138.70] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 20:54:59 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 20:55:04 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 20:55:29 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 21:34:29 2005] [error] [client 81.63.51.202] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Tue Jan 04 21:34:32 2005] [error] [client 81.63.51.202] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 00:17:41 2005] [error] [client 217.251.126.37] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 00:18:03 2005] [error] [client 217.251.126.37] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 00:18:12 2005] [error] [client 217.251.126.37] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 01:24:38 2005] [error] [client 81.63.51.202] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 01:24:40 2005] [error] [client 81.63.51.202] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 01:24:46 2005] [error] [client 81.63.51.202] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 01:24:48 2005] [error] [client 81.63.51.202] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 01:25:58 2005] [error] [client 81.63.51.202] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 01:26:34 2005] [error] [client 81.63.51.202] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 01:26:37 2005] [error] [client 81.63.51.202] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 10:13:02 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 10:13:07 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 10:13:10 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 10:17:07 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 14:41:40 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 14:41:45 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 14:41:47 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 14:41:51 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 14:55:30 2005] [error] [client 212.101.4.200] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:03:44 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:03:48 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:03:52 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:03:57 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:06:45 2005] [error] [client 212.101.4.200] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Mar 05 15:07:28 2005] [error] [client 192.168.0.128] user cyril: authentication failure for "/phpinfo": Password Mismatch +[Wed Jan 05 15:08:01 2005] [error] [client 192.168.0.128] user not found: /phpinfo +[Wed Jan 05 15:10:45 2005] [crit] [client 192.168.0.128] (13)Permission denied: /var/www/jaquier.dyndns.org/htdocs/css/.htaccess pcfg_openfile: unable to check htaccess file, ensure it is readable, referer: http://earth/phpinfo +[Wed Jan 05 15:10:45 2005] [crit] [client 192.168.0.128] (13)Permission denied: /var/www/jaquier.dyndns.org/htdocs/images/.htaccess pcfg_openfile: unable to check htaccess file, ensure it is readable, referer: http://earth/phpinfo +[Wed Jan 05 15:10:45 2005] [error] [client 192.168.0.128] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:11:09 2005] [error] [client 192.168.0.128] user test not found: /phpinfo +[Wed Jan 05 15:11:10 2005] [error] [client 192.168.0.128] user test not found: /phpinfo +[Wed Jan 06 15:11:11 2005] [error] [client 192.168.0.128] user test not found: /phpinfo +[Wed Jan 06 15:11:13 2005] [error] [client 192.168.0.128] user test not found: /phpinfo +[Wed Jan 06 15:11:14 2005] [error] [client 192.168.0.128] user test not found: /phpinfo +[Wed Jan 05 15:11:15 2005] [crit] [client 192.168.0.128] (13)Permission denied: /var/www/jaquier.dyndns.org/htdocs/css/.htaccess pcfg_openfile: unable to check htaccess file, ensure it is readable, referer: http://earth/phpinfo +[Wed Jan 05 15:11:15 2005] [crit] [client 192.168.0.128] (13)Permission denied: /var/www/jaquier.dyndns.org/htdocs/images/.htaccess pcfg_openfile: unable to check htaccess file, ensure it is readable, referer: http://earth/phpinfo +[Wed Jan 05 15:11:15 2005] [error] [client 192.168.0.128] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:12:32 2005] [error] [client 212.101.4.200] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:13:48 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:13:51 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:13:52 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:13:52 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:13:54 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:13:56 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:13:59 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:14:20 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:14:24 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:14:29 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Jan 05 15:14:34 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico +[Wed Mar 05 15:08:28 2005] [error] [client 192.168.0.128] user cyril: authentication failure for "/phpinfo": Password Mismatch +[Wed Mar 05 15:09:28 2005] [error] [client 192.168.0.128] user cyril: authentication failure for "/phpinfo": Password Mismatch \ No newline at end of file diff --git a/log-test/current b/log-test/current index 6537a6aa..d4b9f00a 100644 --- a/log-test/current +++ b/log-test/current @@ -1,4 +1,5 @@ - - Last output repeated 2 times - -Oct 7 11:47:08 [sshd] Failed password for illegal user test from 69.182.27.122 port 34015 ssh2 -Oct 7 11:47:09 [sshd] Failed password for illegal user guest from 69.182.27.122 port 34068 ssh2 -Oct 7 11:47:11 [sshd] Failed password for illegal user admin from 69.182.27.122 port 34127 ssh2 +Jan 7 17:53:15 [sshd] (pam_unix) 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=62.220.137.36 user=kevin +Jan 7 17:53:26 [sshd] (pam_unix) authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=62.220.137.36 user=kevin +Mar 7 17:53:28 [sshd] error: PAM: Authentication failure for kevin from 62.220.137.36 +Mar 7 17:55:28 [sshd] error: PAM: Authentication failure for kevin from 62.220.137.36 +Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 62.220.137.36 diff --git a/log-test/test b/log-test/test index 30fc4868..d370ca46 100644 --- a/log-test/test +++ b/log-test/test @@ -425,7 +425,8 @@ Oct 7 01:03:12 [sshd] Failed password for illegal user tata from 128.178.164.52 - Last output repeated 2 times - Oct 7 01:03:20 [sshd] Failed password for illegal user tata from 128.178.164.52 port 37187 ssh2 - Last output repeated 2 times - -Oct 11 11:47:08 [sshd] Failed password for illegal user test from 69.182.27.122 port 34015 ssh2 -Oct 11 11:47:09 [sshd] Failed password for illegal user guest from 69.182.27.122 port 34068 ssh2 -Oct 11 11:47:11 [sshd] Failed password for illegal user admin from 69.182.27.122 port 34127 ssh2 -Oct 12 21:54:11 yellow sshd[16069]: Failed password for cyril from 212.41.79.210 port 29404 ssh2 +Nov 14 11:47:08 [sshd] Failed password for illegal user test from 69.182.27.122 port 34015 ssh2 +Nov 14 11:47:09 [sshd] Failed password for illegal user guest from 69.182.27.122 port 34068 ssh2 +Nov 14 11:47:11 [sshd] Failed password for illegal user admin from 69.182.27.122 port 34127 ssh2 +Nov 15 11:12:11 yellow sshd[16069]: Failed password for cyril from 212.41.79.210 port 29404 ssh2 +Nov 15 21:54:11 yellow sshd[16069]: Illegal user for cyril from 212.41.79.210 port 29404 ssh2 diff --git a/logreader/logreader.py b/logreader/logreader.py index 12a24d85..24b8458f 100644 --- a/logreader/logreader.py +++ b/logreader/logreader.py @@ -24,9 +24,7 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import os, sys, time - -from sshd import Sshd +import os, sys, time, re class LogReader: """ Reads a log file and reports information about IP that make password @@ -34,13 +32,15 @@ class LogReader: attempt. """ - def __init__(self, logPath, logSys, findTime = 3600): + def __init__(self, logSys, logPath, timeregex, timepattern, failregex, findTime = 3600): self.logPath = logPath + self.timeregex = timeregex + self.timepattern = timepattern + self.failregex = failregex self.findTime = findTime self.ignoreIpList = [] self.lastModTime = 0 self.logSys = logSys - self.parserList = ["Sshd"] def addIgnoreIP(self, ip): """ Adds an IP to the ignore list. @@ -79,30 +79,14 @@ class LogReader: self.lastModTime = logStats.st_mtime return True - def matchLine(self, line): - """ Checks if the line contains a pattern. It does this for all - classes specified in *parserList*. We use a singleton to avoid - creating/destroying objects too much. - - Return a dict with the IP and number of retries. - """ - for i in self.parserList: - match = eval(i).getInstance().parseLogLine(line) - if match: - return match - return None - - def getFailInfo(self, findTime): - """ Gets the failed login attempt. Returns a dict() which contains - IP and the number of retries. - """ + def getFailures(self): ipList = dict() logFile = self.openLogFile() for line in logFile.readlines(): - match = self.matchLine(line) - if match: - ip = match[0] - unixTime = match[1] + value = self.findFailure(line) + if value: + ip = value[0] + unixTime = value[1] if unixTime < time.time()-self.findTime: continue if self.inIgnoreIPList(ip): @@ -115,9 +99,35 @@ class LogReader: ipList[ip] = (1, unixTime) logFile.close() return ipList + + def findFailure(self, line): + match = self.matchLine(line, self.failregex) + if match: + timeMatch = self.matchLine(match.string, self.timeregex) + if timeMatch: + date = self.getUnixTime(timeMatch.group(), self.timepattern) + ipMatch = self.matchAddress(match.string) + if ipMatch: + ip = ipMatch.group() + return [ip, date] + return None + + def getUnixTime(self, value, pattern): + date = list(time.strptime(value, pattern)) + if date[0] < 2000: + date[0] = time.gmtime()[0] + unixTime = time.mktime(date) + return unixTime - def getPwdFailure(self): - """ Executes the getFailInfo method. Not very usefull... + def matchLine(self, line, pattern): + """ Checks if the line contains a pattern. It does this for all + classes specified in *parserList*. We use a singleton to avoid + creating/destroying objects too much. + + Return a dict with the IP and number of retries. """ - failList = self.getFailInfo(self.findTime) - return failList + return re.search(pattern, line) + + def matchAddress(self, line): + return re.search("(?:\d{1,3}\.){3}\d{1,3}", line) + \ No newline at end of file diff --git a/logreader/parser.py b/logreader/parser.py deleted file mode 100644 index d3067543..00000000 --- a/logreader/parser.py +++ /dev/null @@ -1,67 +0,0 @@ -# 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: Cyril Jaquier -# -# $Revision$ - -__author__ = "Cyril Jaquier" -__version__ = "$Revision$" -__date__ = "$Date$" -__copyright__ = "Copyright (c) 2004 Cyril Jaquier" -__license__ = "GPL" - -import time, re - -class Parser: - """ This class is the main log parser class. It should be inherited - by all the service specific classes. - """ - - def getLogMatch(self, pattern, line): - """ Returns a match if pattern is found in line. - """ - return re.search(pattern, line) - - def getLogIPv4(self, line): - """ Returns IP if one is found in line. Match IPv4 string. - """ - matchIP = re.search("(?:\d{1,3}\.){3}\d{1,3}", line) - if matchIP: - return matchIP.group() - else: - return None - - def getLogIP(self, line): - """ Returns IP if one is found in line. - """ - return self.getLogIPv4(line) - - def getLogTimeStandard(self, line): - """ Returns the log time of line using a standard log format. - - Format: Oct 14 11:47:08 - """ - date = list(time.strptime(line[0:15], "%b %d %H:%M:%S")) - date[0] = time.gmtime()[0] - unixTime = time.mktime(date) - return unixTime - - def getLogTime(self, line): - """ Returns the log time of line. - """ - return self.getLogTimeStandard(line) - \ No newline at end of file diff --git a/setup.py b/setup.py index b1eafeda..1e05ff47 100755 --- a/setup.py +++ b/setup.py @@ -38,5 +38,5 @@ setup( url = "http://www.sourceforge.net/projects/fail2ban", scripts = ['fail2ban.py'], py_modules = ['version'], - packages = ['firewall', 'logreader'] + packages = ['firewall', 'logreader', 'confreader'] ) \ No newline at end of file diff --git a/version.py b/version.py index cbfd96b8..cb074256 100644 --- a/version.py +++ b/version.py @@ -24,4 +24,4 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -version = "0.1.2" +version = "0.3.0-CVS"