mirror of https://github.com/fail2ban/fail2ban
- Initial 0.5 commit
git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/branches/FAIL2BAN-0_5@115 a942ae1a-1317-0410-a47c-b1dcaea8d6050.5
parent
ea9a671e88
commit
685f4aad99
|
@ -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
|
||||
|
||||
|
|
5
MANIFEST
5
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
|
||||
|
|
3
README
3
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:
|
||||
--------
|
||||
|
|
|
@ -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.
|
||||
# <if> interface name
|
||||
# <ip> IP address
|
||||
# Values: CMD
|
||||
# Default iptables -I INPUT 1 -i <if> -s <ip> -j DROP
|
||||
fwbanrule = iptables -I INPUT 1 -i <if> -s <ip> -j DROP
|
||||
|
||||
# Option: fwunbanrule
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# <if> interface name
|
||||
# <ip> IP address
|
||||
# Values: CMD
|
||||
# Default iptables -D INPUT -i <if> -s <ip> -j DROP
|
||||
fwunbanrule = iptables -D INPUT -i <if> -s <ip> -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.
|
||||
# <if> interface name
|
||||
# <ip> IP address
|
||||
# Values: CMD
|
||||
# Default iptables -I INPUT 1 -i <if> -s <ip> -j DROP
|
||||
fwbanrule = iptables -I INPUT 1 -i <if> -s <ip> -j DROP
|
||||
|
||||
# Option: fwunbanrule
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# <if> interface name
|
||||
# <ip> IP address
|
||||
# Values: CMD
|
||||
# Default iptables -D INPUT -i <if> -s <ip> -j DROP
|
||||
fwunbanrule = iptables -D INPUT -i <if> -s <ip> -j DROP
|
||||
|
||||
# Option: timeregex
|
||||
# Notes.: regex to match timestamp in SSH logfile.
|
||||
|
|
|
@ -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
|
||||
|
224
fail2ban.py
224
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 <ctrl>+<c>).
|
||||
"""
|
||||
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 <ctrl>+<c> we exit nicely.
|
||||
|
|
|
@ -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>", ip)
|
||||
string = string.replace("<if>", interface)
|
||||
return string
|
||||
|
||||
def viewBanList(self):
|
||||
""" Prints the ban list on screen. Usefull for debugging.
|
||||
"""
|
||||
|
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
[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
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -24,4 +24,4 @@ __date__ = "$Date$"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
|
||||
__license__ = "GPL"
|
||||
|
||||
version = "0.4.1"
|
||||
version = "0.5.0-CVS"
|
||||
|
|
Loading…
Reference in New Issue