From 685f4aad997ec75ceed31d43df66d36ebb79c311 Mon Sep 17 00:00:00 2001 From: Cyril Jaquier Date: Tue, 5 Jul 2005 12:57:20 +0000 Subject: [PATCH] - Initial 0.5 commit git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/branches/FAIL2BAN-0_5@115 a942ae1a-1317-0410-a47c-b1dcaea8d605 --- CHANGELOG | 2 + MANIFEST | 5 +- README | 3 +- config/fail2ban.conf.default | 40 ++++++- confreader/configreader.py | 14 ++- fail2ban.py | 224 ++++++++--------------------------- firewall/firewall.py | 40 +++++-- firewall/ipfw.py | 72 ----------- firewall/ipfwadm.py | 48 -------- firewall/iptables.py | 48 -------- log-test/apache | 10 +- log-test/current | 6 + logreader/logreader.py | 44 +++---- version.py | 2 +- 14 files changed, 165 insertions(+), 393 deletions(-) delete mode 100644 firewall/ipfw.py delete mode 100644 firewall/ipfwadm.py delete mode 100644 firewall/iptables.py diff --git a/CHANGELOG b/CHANGELOG index a05b6cbd..7969fd7a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,8 @@ Fail2Ban (version 0.?.?) ??/??/2005 ver. 0.?.? (??/??/2005) - ??? ---------- +- Added firewall rules definition in the configuration file +- Cleaned a bit fail2ban.py - Added an initd script for RedHat/Fedora. Thanks to Andrey G. Grozin diff --git a/MANIFEST b/MANIFEST index 330728f8..3da375cc 100644 --- a/MANIFEST +++ b/MANIFEST @@ -7,15 +7,14 @@ version.py fail2ban.py firewall/__init__.py firewall/firewall.py -firewall/iptables.py -firewall/ipfw.py -firewall/ipfwadm.py logreader/__init__.py logreader/logreader.py confreader/__init__.py confreader/configreader.py utils/__init__.py utils/dns.py +utils/process.py config/fail2ban.conf.default config/gentoo-initd config/gentoo-confd +config/redhat-initd diff --git a/README b/README index 7882c60d..b88f1731 100644 --- a/README +++ b/README @@ -121,8 +121,7 @@ Thanks: ------- Kévin Drapel, Marvin Rouge, Sireyessire, Robert Edeker, -Tom Pike, Iain Lea - +Tom Pike, Iain Lea, Andrey G. Grozin License: -------- diff --git a/config/fail2ban.conf.default b/config/fail2ban.conf.default index 0e547bb4..cf00c4aa 100644 --- a/config/fail2ban.conf.default +++ b/config/fail2ban.conf.default @@ -87,7 +87,25 @@ enabled = false # Notes.: logfile to monitor. # Values: FILE Default: /var/log/httpd/access_log # -logfile = /var/log/httpd/access_log +logfile = /home/cyril/workspace/fail2ban/log-test/apache + +# Option: fwbanrule +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# interface name +# IP address +# Values: CMD +# Default iptables -I INPUT 1 -i -s -j DROP +fwbanrule = iptables -I INPUT 1 -i -s -j DROP + +# Option: fwunbanrule +# Notes.: command executed when unbanning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# interface name +# IP address +# Values: CMD +# Default iptables -D INPUT -i -s -j DROP +fwunbanrule = iptables -D INPUT -i -s -j DROP # Option: timeregex # Notes.: regex to match timestamp in Apache logfile. @@ -120,7 +138,25 @@ enabled = true # Notes.: logfile to monitor. # Values: FILE Default: /var/log/secure # -logfile = /var/log/secure +logfile = /home/cyril/workspace/fail2ban/log-test/test + +# Option: fwbanrule +# Notes.: command executed when banning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# interface name +# IP address +# Values: CMD +# Default iptables -I INPUT 1 -i -s -j DROP +fwbanrule = iptables -I INPUT 1 -i -s -j DROP + +# Option: fwunbanrule +# Notes.: command executed when unbanning an IP. Take care that the +# command is executed with Fail2Ban user rights. +# interface name +# IP address +# Values: CMD +# Default iptables -D INPUT -i -s -j DROP +fwunbanrule = iptables -D INPUT -i -s -j DROP # Option: timeregex # Notes.: regex to match timestamp in SSH logfile. diff --git a/confreader/configreader.py b/confreader/configreader.py index c3eb271b..9c64a0aa 100644 --- a/confreader/configreader.py +++ b/confreader/configreader.py @@ -24,8 +24,13 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" +import log4py + from ConfigParser import * +# Gets the instance of log4py. +logSys = log4py.Logger().get_instance() + class ConfigReader: """ This class allow the handling of the configuration options. The DEFAULT section contains the global information about @@ -40,12 +45,13 @@ class ConfigReader: ["str", "logfile", "/dev/null"], ["str", "timeregex", ""], ["str", "timepattern", ""], - ["str", "failregex", ""]) + ["str", "failregex", ""], + ["str", "fwbanrule", ""], + ["str", "fwunbanrule", ""]) - def __init__(self, logSys, confPath): + def __init__(self, confPath): self.confPath = confPath self.configParser = SafeConfigParser() - self.logSys = logSys def openConf(self): """ Opens the configuration file. @@ -74,7 +80,7 @@ class ConfigReader: values[option[1]] = v except NoOptionError: - self.logSys.warn("No '"+option[1]+"' defined in '"+sec+"'") + logSys.warn("No '" + option[1] + "' defined in '" + sec + "'") values[option[1]] = option[2] return values \ No newline at end of file diff --git a/fail2ban.py b/fail2ban.py index 5fdfe698..301975b5 100755 --- a/fail2ban.py +++ b/fail2ban.py @@ -26,7 +26,7 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import time, sys, getopt, os, signal, string +import time, sys, getopt, os, string, signal from ConfigParser import * # Checks if log4py is present. @@ -39,13 +39,15 @@ except: # Appends our own modules path sys.path.append('/usr/lib/fail2ban') -from firewall.iptables import Iptables -from firewall.ipfw import Ipfw -from firewall.ipfwadm import Ipfwadm +from firewall.firewall import Firewall from logreader.logreader import LogReader from confreader.configreader import ConfigReader +from utils.process import * from version import version +# Gets the instance of log4py. +logSys = log4py.Logger().get_instance() + def usage(): print "Usage: fail2ban.py [OPTIONS]" print @@ -79,141 +81,26 @@ def checkForRoot(): else: return False -def createDaemon(): - """Detach a process from the controlling terminal and run it in the - background as a daemon. - - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731 - """ - - try: - # Fork a child process so the parent can exit. This will return control - # to the command line or shell. This is required so that the new process - # is guaranteed not to be a process group leader. We have this guarantee - # because the process GID of the parent is inherited by the child, but - # the child gets a new PID, making it impossible for its PID to equal its - # PGID. - pid = os.fork() - except OSError, e: - return((e.errno, e.strerror)) # ERROR (return a tuple) - - if (pid == 0): # The first child. - - # Next we call os.setsid() to become the session leader of this new - # session. The process also becomes the process group leader of the - # new process group. Since a controlling terminal is associated with a - # session, and this new session has not yet acquired a controlling - # terminal our process now has no controlling terminal. This shouldn't - # fail, since we're guaranteed that the child is not a process group - # leader. - os.setsid() - - # When the first child terminates, all processes in the second child - # are sent a SIGHUP, so it's ignored. - signal.signal(signal.SIGHUP, signal.SIG_IGN) - - try: - # Fork a second child to prevent zombies. Since the first child is - # a session leader without a controlling terminal, it's possible for - # it to acquire one by opening a terminal in the future. This second - # fork guarantees that the child is no longer a session leader, thus - # preventing the daemon from ever acquiring a controlling terminal. - pid = os.fork() # Fork a second child. - except OSError, e: - return((e.errno, e.strerror)) # ERROR (return a tuple) - - if (pid == 0): # The second child. - # Ensure that the daemon doesn't keep any directory in use. Failure - # to do this could make a filesystem unmountable. - os.chdir("/") - # Give the child complete control over permissions. - os.umask(0) - else: - os._exit(0) # Exit parent (the first child) of the second child. - else: - os._exit(0) # Exit parent of the first child. - - # Close all open files. Try the system configuration variable, SC_OPEN_MAX, - # for the maximum number of open files to close. If it doesn't exist, use - # the default value (configurable). - try: - maxfd = os.sysconf("SC_OPEN_MAX") - except (AttributeError, ValueError): - maxfd = 256 # default maximum - - for fd in range(0, maxfd): - try: - os.close(fd) - except OSError: # ERROR (ignore) - pass - - # Redirect the standard file descriptors to /dev/null. - os.open("/dev/null", os.O_RDONLY) # standard input (0) - os.open("/dev/null", os.O_RDWR) # standard output (1) - os.open("/dev/null", os.O_RDWR) # standard error (2) - - return True - def sigTERMhandler(signum, frame): """ Handles the TERM signal when in daemon mode in order to exit properly. """ logSys.debug("Signal handler called with sig "+`signum`) - killApp() + killApp() def killApp(): """ Flush the ban list, remove the PID lock file and exit nicely. """ logSys.warn("Restoring firewall rules...") - fireWall.flushBanList(conf["debug"]) + for element in logFwList: + element[2].flushBanList(conf["debug"]) removePID(conf["pidlock"]) logSys.info("Exiting...") sys.exit(0) - -def checkForPID(lockfile): - """ Checks for running Fail2Ban. - - Returns the current PID if Fail2Ban is running or False - if no instance found. - """ - try: - fileHandler = open(lockfile) - pid = fileHandler.readline() - return pid - except IOError: - return False - -def createPID(lockfile): - """ Creates a PID lock file with the current PID. - """ - fileHandler = open(lockfile, mode='w') - pid = os.getpid() - fileHandler.write(`pid`+'\n') - fileHandler.close() - logSys.debug("Created PID lock ("+`pid`+") in "+lockfile) - -def removePID(lockfile): - """ Remove PID lock. - """ - os.remove(lockfile) - logSys.debug("Removed PID lock "+lockfile) - -def killPID(pid): - """ Kills the process with the given PID using the - INT signal (same effect as +). - """ - try: - return os.kill(pid, 2) - except OSError: - logSys.error("Can not kill process " + `pid` + ". Please check that " + - "Fail2Ban is not running and remove the file " + - "'/tmp/fail2ban.pid'") if __name__ == "__main__": - # Gets an instance of log4py. - logSys = log4py.Logger().get_instance() logSys.set_formatstring("%T %L %M") conf = dict() @@ -453,38 +340,32 @@ if __name__ == "__main__": # Reads the config file and create a LogReader instance for # each log file to check. - confReader = ConfigReader(logSys, conf["conffile"]); + confReader = ConfigReader(conf["conffile"]); confReader.openConf() - logList = list() + logFwList = list() for t in confReader.getSections(): l = confReader.getLogOptions(t) if l["enabled"]: - lObj = LogReader(logSys, l["logfile"], l["timeregex"], - l["timepattern"], l["failregex"], conf["bantime"]) - lObj.setName(t) - logList.append(lObj) - - # Creates one instance of Iptables (thanks to Pyhton dynamic - # features). - fireWallObj = eval(fireWallName) - fireWall = fireWallObj(conf["bantime"], logSys, conf["interface"]) - - # IPFW needs rules number. The configuration option "ipfw-start-rule" - # defines the first rule number used by Fail2Ban. - if fireWallName == "Ipfw": - fireWall.setCrtRuleNbr(conf["ipfw-start-rule"]) + # Creates a logreader object + lObj = LogReader(l["logfile"], l["timeregex"], l["timepattern"], + l["failregex"], conf["bantime"]) + # Creates a firewall object + fObj = Firewall(l["fwbanrule"], l["fwunbanrule"], conf["bantime"], + conf["interface"]) + # Links them into a list. I'm not really happy + # with this :/ + logFwList.append([t, lObj, fObj, dict()]) # We add 127.0.0.1 to the ignore list has we do not want # to be ban ourself. - for element in logList: - element.addIgnoreIP("127.0.0.1") + for element in logFwList: + element[1].addIgnoreIP("127.0.0.1") while len(ignoreIPList) > 0: ip = ignoreIPList.pop() - for element in logList: - element.addIgnoreIP(ip) + for element in logFwList: + element[1].addIgnoreIP(ip) logSys.info("Fail2Ban v"+version+" is running") - failListFull = dict() # Main loop while True: try: @@ -493,14 +374,15 @@ if __name__ == "__main__": # Checks if some IP have to be remove from ban # list. - fireWall.checkForUnBan(conf["debug"]) - + for element in logFwList: + element[2].checkForUnBan(conf["debug"]) + # 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. modList = list() - for element in logList: - if element.isModified(): + for element in logFwList: + if element[1].isModified(): modList.append(element) if len(modList) == 0: @@ -509,42 +391,32 @@ if __name__ == "__main__": # Gets the failure list from the log file. For a given IP, # takes only the service which has the most password failures. - failList = dict() for element in modList: - e = element.getFailures() + e = element[1].getFailures() for key in e.iterkeys(): - if failList.has_key(key): - if failList[key][0] < e[key][0]: - failList[key] = (e[key][0], e[key][1], element) + if element[3].has_key(key): + element[3][key] = (element[3][key][0] + e[key][0], + e[key][1]) else: - failList[key] = (e[key][0], e[key][1], element) - - # Add the last log failures to the global failure list. - for key in failList.iterkeys(): - if failListFull.has_key(key): - failListFull[key] = (failListFull[key][0] + 1, - failList[key][1], failList[key][2]) - else: - failListFull[key] = failList[key] + element[3][key] = (e[key][0], e[key][1]) # Remove the oldest failure attempts from the global list. - unixTime = time.time() - failListFullTemp = failListFull.copy() - for key in failListFullTemp.iterkeys(): - failTime = failListFullTemp[key][2].getFindTime() - if failListFullTemp[key][1] < unixTime - failTime: - del failListFull[key] - # We iterate the failure list and ban IP that make # *retryAllowed* login failures. - failListFullTemp = failListFull.copy() - for key in failListFullTemp.iterkeys(): - element = failListFullTemp[key] - if element[0] >= conf["maxretry"]: - logSys.info(element[2].getName()+": "+key+" has "+ - `element[0]`+" login failure(s). Banned.") - fireWall.addBanIP(key, conf["debug"]) - del failListFull[key] + unixTime = time.time() + for element in logFwList: + fails = element[3].copy() + findTime = element[1].getFindTime() + for attempt in fails: + failTime = fails[attempt][1] + if failTime < unixTime - failTime: + del element[3][attempt] + elif fails[attempt][0] >= conf["maxretry"]: + logSys.info(element[0] + ": " + attempt + " has " + + `element[3][attempt][0]` + + " login failure(s). Banned.") + element[2].addBanIP(attempt, conf["debug"]) + del element[3][attempt] except KeyboardInterrupt: # When the user press + we exit nicely. diff --git a/firewall/firewall.py b/firewall/firewall.py index a62a85ff..38351863 100644 --- a/firewall/firewall.py +++ b/firewall/firewall.py @@ -24,7 +24,10 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import time, os +import time, os, log4py, re + +# Gets the instance of log4py. +logSys = log4py.Logger().get_instance() class Firewall: """ Manages the ban list and executes the command that ban @@ -33,30 +36,31 @@ class Firewall: banList = dict() - def __init__(self, banTime, logSys, interface): + def __init__(self, banRule, unBanRule, banTime, interface): + self.banRule = banRule + self.unBanRule = unBanRule self.banTime = banTime - self.logSys = logSys self.interface = interface def addBanIP(self, ip, debug): """ Bans an IP. """ if not self.inBanList(ip): - self.logSys.warn("Ban "+ip) + logSys.warn("Ban "+ip) self.banList[ip] = time.time() self.__executeCmd(self.banIP(ip), debug) else: - self.logSys.error(ip+" already in ban list") + logSys.error(ip+" already in ban list") def delBanIP(self, ip, debug): """ Unban an IP. """ if self.inBanList(ip): - self.logSys.warn("Unban "+ip) + logSys.warn("Unban "+ip) del self.banList[ip] self.__executeCmd(self.unBanIP(ip), debug) else: - self.logSys.error(ip+" not in ban list") + logSys.error(ip+" not in ban list") def inBanList(self, ip): """ Checks if IP is in ban list. @@ -85,12 +89,30 @@ class Firewall: def __executeCmd(self, cmd, debug): """ Executes an OS command. """ - self.logSys.debug(cmd) + logSys.debug(cmd) if not debug: return os.system(cmd) else: return None - + + def banIP(self, ip): + """ Returns query to ban IP. + """ + query = self.replaceTag(self.banRule, ip, self.interface) + return query + + def unBanIP(self, ip): + """ Returns query to unban IP. + """ + query = self.replaceTag(self.unBanRule, ip, self.interface) + return query + + def replaceTag(self, query, ip, interface): + string = query + string = string.replace("", ip) + string = string.replace("", interface) + return string + def viewBanList(self): """ Prints the ban list on screen. Usefull for debugging. """ diff --git a/firewall/ipfw.py b/firewall/ipfw.py deleted file mode 100644 index 41fbc29b..00000000 --- a/firewall/ipfw.py +++ /dev/null @@ -1,72 +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 os - -from firewall import Firewall - -class Ipfw(Firewall): - """ This class contains specific methods and variables for the - iptables firewall. Must implements the 'abstracts' methods - banIP(ip) and unBanIP(ip). - - Must adds abstract methods definition: - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266468 - """ - - crtRuleNbr = 0 - - def getCrtRuleNbr(self): - """ Gets the current rule number. - """ - return self.crtRuleNbr - - def setCrtRuleNbr(self, value): - """ Sets the current rule number. - """ - self.crtRuleNbr = value - - def banIP(self, ip): - """ Returns query to ban IP. - """ - query = "ipfw -q add "+`self.crtRuleNbr`+" deny ip from "+ip+" to any" - self.crtRuleNbr = self.crtRuleNbr + 1 - return query - - def unBanIP(self, ip): - """ Returns query to unban IP. - """ - ruleNbr = str(self.__findRuleNumber(ip)) - query = "ipfw -q delete "+ruleNbr - return query - - def __findRuleNumber(self, ip): - """ Uses shell commands in order to find the rule - number we want to delete. - """ - output = os.popen("ipfw list|grep \"from "+ip+" to\"|awk '{print $1}'", - "r"); - return output.read() diff --git a/firewall/ipfwadm.py b/firewall/ipfwadm.py deleted file mode 100644 index cfdccbd8..00000000 --- a/firewall/ipfwadm.py +++ /dev/null @@ -1,48 +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" - -from firewall import Firewall - -class Ipfwadm(Firewall): - """ This class contains specific methods and variables for the - iptables firewall. Must implements the 'abstracts' methods - banIP(ip) and unBanIP(ip). - - Must adds abstract methods definition: - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266468 - """ - - def banIP(self, ip): - """ Returns query to ban IP. - """ - query = "ipfwadm -I -a deny -W "+self.interface+" -S "+ip - return query - - def unBanIP(self, ip): - """ Returns query to unban IP. - """ - query = "ipfwadm -I -d deny -W "+self.interface+" -S "+ip - return query diff --git a/firewall/iptables.py b/firewall/iptables.py deleted file mode 100644 index 7b78c69c..00000000 --- a/firewall/iptables.py +++ /dev/null @@ -1,48 +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" - -from firewall import Firewall - -class Iptables(Firewall): - """ This class contains specific methods and variables for the - iptables firewall. Must implements the 'abstracts' methods - banIP(ip) and unBanIP(ip). - - Must adds abstract methods definition: - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266468 - """ - - def banIP(self, ip): - """ Returns query to ban IP. - """ - query = "iptables -I INPUT 1 -i "+self.interface+" -s "+ip+" -j DROP" - return query - - def unBanIP(self, ip): - """ Returns query to unban IP. - """ - query = "iptables -D INPUT -i "+self.interface+" -s "+ip+" -j DROP" - return query diff --git a/log-test/apache b/log-test/apache index 35248fd2..227f8df9 100644 --- a/log-test/apache +++ b/log-test/apache @@ -1,3 +1,4 @@ +[Wed Mar 31 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch [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 @@ -98,5 +99,10 @@ [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 +[Wed Mar 10 15:08:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch +[Wed Mar 10 15:09:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch +[Wed Mar 10 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch +[Wed Mar 30 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch +[Wed Mar 30 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch +[Wed Mar 30 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch +[Wed Mar 30 15:11:28 2005] [error] [client www.google.ch] 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 d4b9f00a..18258689 100644 --- a/log-test/current +++ b/log-test/current @@ -3,3 +3,9 @@ Jan 7 17:53:26 [sshd] (pam_unix) authentication failure; logname= uid=0 euid=0 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 +Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 192.168.0.128 +Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 192.168.0.128 +Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 192.168.0.128 +Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 192.168.0.128 +Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from www.google.ch +Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from www.google.ch diff --git a/logreader/logreader.py b/logreader/logreader.py index 4778b3ba..79f5e7db 100644 --- a/logreader/logreader.py +++ b/logreader/logreader.py @@ -24,18 +24,21 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -import os, sys, time, re +import os, sys, time, re, log4py from utils.dns import * +# Gets the instance of log4py. +logSys = log4py.Logger().get_instance() + class LogReader: """ 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. """ - def __init__(self, logSys, logPath, timeregex, timepattern, failregex, - findTime = 3600): + def __init__(self, logPath, timeregex, timepattern, failregex, + findTime = 3600): self.logPath = logPath self.timeregex = timeregex self.timepattern = timepattern @@ -43,20 +46,9 @@ class LogReader: self.findTime = findTime self.ignoreIpList = [] self.lastModTime = 0 - self.logSys = logSys self.lastPos = 0 self.lastDate = 0 self.logStats = None - - def setName(self, name): - """ Sets the name of the log reader. - """ - self.name = name - - def getName(self): - """ Gets the name of the log reader. - """ - return self.name def getFindTime(self): """ Gets the find time. @@ -66,7 +58,7 @@ class LogReader: def addIgnoreIP(self, ip): """ Adds an IP to the ignore list. """ - self.logSys.debug("Add "+ip+" to ignore list") + logSys.debug("Add "+ip+" to ignore list") self.ignoreIpList.append(ip) def inIgnoreIPList(self, ip): @@ -80,7 +72,7 @@ class LogReader: try: fileHandler = open(self.logPath) except OSError: - self.logSys.error("Unable to open "+self.logPath) + logSys.error("Unable to open "+self.logPath) sys.exit(-1) return fileHandler @@ -90,13 +82,13 @@ class LogReader: try: self.logStats = os.stat(self.logPath) except OSError: - self.logSys.error("Unable to get stat on "+self.logPath) + logSys.error("Unable to get stat on "+self.logPath) sys.exit(-1) if self.lastModTime == self.logStats.st_mtime: return False else: - self.logSys.debug(self.logPath+" has been modified") + logSys.debug(self.logPath+" has been modified") self.lastModTime = self.logStats.st_mtime return True @@ -107,13 +99,13 @@ class LogReader: """ line = file.readline() if self.lastDate < self.getTime(line): - self.logSys.debug("Date " + `self.lastDate` + " is " + - "smaller than " + `self.getTime(line)`) - self.logSys.debug("Log rotation detected for " + self.logPath) + logSys.debug("Date " + `self.lastDate` + " is " + "smaller than " + + `self.getTime(line)`) + logSys.debug("Log rotation detected for " + self.logPath) self.lastPos = 0 - self.logSys.debug("Setting file position to " + `self.lastPos` + " for " - + self.logPath) + logSys.debug("Setting file position to " + `self.lastPos` + " for " + + self.logPath) file.seek(self.lastPos) def getFailures(self): @@ -124,7 +116,7 @@ class LogReader: and the latest failure time. """ ipList = dict() - self.logSys.debug(self.logPath) + logSys.debug(self.logPath) logFile = self.openLogFile() self.setFilePos(logFile) lastLine = '' @@ -137,9 +129,9 @@ class LogReader: if unixTime < time.time()-self.findTime: break if self.inIgnoreIPList(ip): - self.logSys.debug("Ignore "+ip) + logSys.debug("Ignore "+ip) continue - self.logSys.debug("Found "+ip) + logSys.debug("Found "+ip) if ipList.has_key(ip): ipList[ip] = (ipList[ip][0]+1, unixTime) else: diff --git a/version.py b/version.py index 3d2fe563..8f21d385 100644 --- a/version.py +++ b/version.py @@ -24,4 +24,4 @@ __date__ = "$Date$" __copyright__ = "Copyright (c) 2004 Cyril Jaquier" __license__ = "GPL" -version = "0.4.1" +version = "0.5.0-CVS"