New upstream release, removed log4py, fresh man file for conf

debian-releases/etch
Yaroslav Halchenko 2005-08-06 19:43:43 +00:00
parent e965ab1504
commit e46b5f6665
25 changed files with 454 additions and 834 deletions

View File

@ -4,9 +4,19 @@
|_| \__,_|_|_/___|_.__/\__,_|_||_|
=============================================================
Fail2Ban (version 0.5.1) 2005/07/23
Fail2Ban (version 0.5.2) 2005/08/06
=============================================================
ver. 0.5.2 (2005/08/06) - beta
----------
- Better PID lock file handling. Should close #1239562
- Added man pages
- Removed log4py dependency. Use logging module instead
- "maxretry" and "bantime" can be overridden in each section
- Fixed bug #1246278 (excessive memory usage)
- Fixed crash on wrong option value in configuration file
- Changed custom chains to lowercase
ver. 0.5.1 (2005/07/23) - beta
----------
- Fixed bugs #1241756, #1239557

View File

@ -1,10 +1,15 @@
Metadata-Version: 1.0
Name: fail2ban
Version: 0.5.1
Version: 0.5.2
Summary: Ban IPs that make too many password failure
Home-page: http://fail2ban.sourceforge.net
Author: Cyril Jaquier
Author-email: lostcontrol@users.sourceforge.net
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
License: GPL
Description:
Fail2Ban scans log files like /var/log/pwdfail or
/var/log/apache/error_log and bans IP that makes
too many password failures. It updates firewall rules
to reject the IP address or executes user defined
commands. It needs log4py.
Platform: Posix

15
README
View File

@ -4,14 +4,14 @@
|_| \__,_|_|_/___|_.__/\__,_|_||_|
=============================================================
Fail2Ban (version 0.5.1) 2005/07/23
Fail2Ban (version 0.5.2) 2005/08/06
=============================================================
Fail2Ban scans log files like /var/log/pwdfail and bans IP
that makes too many password failures. It updates firewall
rules to reject the IP address. These rules can be defined by
the user. Fail2Ban can read multiple log files such as sshd
or Apache web server ones. It needs log4py.
or Apache web server ones.
This is my first Python program. Moreover, English is not my
mother tongue...
@ -55,12 +55,11 @@ Installation:
-------------
Require: python-2.3 (http://www.python.org)
log4py-1.3 (http://sourceforge.net/projects/log4py)
To install, just do:
> tar xvfj fail2ban-0.5.1.tar.bz2
> cd fail2ban-0.5.1
> tar xvfj fail2ban-0.5.2.tar.bz2
> cd fail2ban-0.5.2
> python setup.py install
This will install Fail2Ban into /usr/lib/fail2ban. The fail2ban
@ -95,13 +94,13 @@ or using command line options. Command line options override
the value stored in fail2ban.conf. Here are the command line
options:
-b start fail2ban in background
-d start fail2ban in debug mode
-b start in background
-d start in debug mode
-c <FILE> read configuration file FILE
-p <FILE> create PID lock in FILE
-h display this help message
-i <IP(s)> IP(s) to ignore
-k kill a currently running Fail2Ban instance
-k kill a currently running instance
-r <VALUE> allow a max of VALUE password failure
-t <TIME> ban IP for TIME seconds
-v verbose. Use twice for greater effect

1
TODO
View File

@ -10,4 +10,3 @@ ToDo
See Feature Request Tracking System at SourceForge.net
- improve installation process (better prefix support)
- add better documentation (man page)

View File

@ -1,6 +1,6 @@
# Fail2Ban configuration file
#
# $Revision: 1.8.2.7 $
# $Revision: 1.8.2.9 $
#
# 2005.06.21 modified for readability Iain Lea iain@bricbrac.de
@ -19,9 +19,9 @@ debug = false
# Option: logtargets
# Notes.: log targets. Space separated list of logging targets.
# Values: STDOUT STDERR SYSLOG file Default: STDOUT /var/log/fail2ban.log
# Values: STDERR SYSLOG file Default: /var/log/fail2ban.log
#
logtargets = STDOUT /var/log/fail2ban.log
logtargets = /var/log/fail2ban.log
# Option: pidlock
# Notes.: path of the PID lock file (must be able to write to file).
@ -49,7 +49,7 @@ bantime = 600
# Examples
# ignoreip = 192.168.0.0/24
#
ignoreip =
ignoreip = 192.168.0.0/16
# Option: cmdstart
@ -147,17 +147,17 @@ logfile = /var/log/apache/access.log
# Notes.: command executed once at the start of Fail2Ban
# Values: CMD Default:
#
fwstart = iptables -N fail2ban-HTTP
iptables -I INPUT -i eth0 -p tcp --dport http -j fail2ban-HTTP
iptables -A fail2ban-HTTP -j RETURN
fwstart = iptables -N fail2ban-http
iptables -I INPUT -p tcp --dport http -j fail2ban-http
iptables -A fail2ban-http -j RETURN
# Option: fwend
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD Default:
#
fwend = iptables -D INPUT -i eth0 -p tcp --dport http -j fail2ban-HTTP
iptables -D fail2ban-HTTP -j RETURN
iptables -X fail2ban-HTTP
fwend = iptables -D INPUT -p tcp --dport http -j fail2ban-http
iptables -D fail2ban-http -j RETURN
iptables -X fail2ban-http
# Option: fwban
# Notes.: command executed when banning an IP. Take care that the
@ -167,9 +167,9 @@ fwend = iptables -D INPUT -i eth0 -p tcp --dport http -j fail2ban-HTTP
# <failtime> unix timestamp of the last failure
# <bantime> unix timestamp of the ban time
# Values: CMD
# Default: iptables -I INPUT 1 -i eth0 -s <ip> -j DROP
# Default: iptables -I INPUT 1 -s <ip> -j DROP
#
fwban = iptables -I fail2ban-HTTP 1 -i eth0 -s <ip> -j DROP
fwban = iptables -I fail2ban-http 1 -s <ip> -j DROP
# Option: fwunban
# Notes.: command executed when unbanning an IP. Take care that the
@ -178,9 +178,9 @@ fwban = iptables -I fail2ban-HTTP 1 -i eth0 -s <ip> -j DROP
# <bantime> unix timestamp of the ban time
# <unbantime> unix timestamp of the unban time
# Values: CMD
# Default: iptables -D INPUT -i eth0 -s <ip> -j DROP
# Default: iptables -D INPUT -s <ip> -j DROP
#
fwunban = iptables -D fail2ban-HTTP -i eth0 -s <ip> -j DROP
fwunban = iptables -D fail2ban-http -s <ip> -j DROP
# Option: timeregex
# Notes.: regex to match timestamp in Apache logfile.
@ -219,17 +219,17 @@ logfile = /var/log/auth.log
# Notes.: command executed once at the start of Fail2Ban
# Values: CMD Default:
#
fwstart = iptables -N fail2ban-SSH
iptables -I INPUT -i eth0 -p tcp --dport ssh -j fail2ban-SSH
iptables -A fail2ban-SSH -j RETURN
fwstart = iptables -N fail2ban-ssh
iptables -I INPUT -p tcp --dport ssh -j fail2ban-ssh
iptables -A fail2ban-ssh -j RETURN
# Option: fwend
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD Default:
#
fwend = iptables -D INPUT -i eth0 -p tcp --dport ssh -j fail2ban-SSH
iptables -D fail2ban-SSH -j RETURN
iptables -X fail2ban-SSH
fwend = iptables -D INPUT -p tcp --dport ssh -j fail2ban-ssh
iptables -D fail2ban-ssh -j RETURN
iptables -X fail2ban-ssh
# Option: fwbanrule
# Notes.: command executed when banning an IP. Take care that the
@ -239,9 +239,9 @@ fwend = iptables -D INPUT -i eth0 -p tcp --dport ssh -j fail2ban-SSH
# <failtime> unix timestamp of the last failure
# <bantime> unix timestamp of the ban time
# Values: CMD
# Default: iptables -I INPUT 1 -i eth0 -s <ip> -j DROP
# Default: iptables -I INPUT 1 -s <ip> -j DROP
#
fwban = iptables -I fail2ban-SSH 1 -i eth0 -s <ip> -j DROP
fwban = iptables -I fail2ban-ssh 1 -s <ip> -j DROP
# Option: fwunbanrule
# Notes.: command executed when unbanning an IP. Take care that the
@ -250,9 +250,9 @@ fwban = iptables -I fail2ban-SSH 1 -i eth0 -s <ip> -j DROP
# <bantime> unix timestamp of the ban time
# <unbantime> unix timestamp of the unban time
# Values: CMD
# Default: iptables -D INPUT -i eth0 -s <ip> -j DROP
# Default: iptables -D INPUT -s <ip> -j DROP
#
fwunban = iptables -D fail2ban-SSH -i eth0 -s <ip> -j DROP
fwunban = iptables -D fail2ban-ssh -s <ip> -j DROP
# Option: timeregex
# Notes.: regex to match timestamp in SSH logfile.

View File

@ -17,7 +17,7 @@
#
# Author: Sireyessire, Cyril Jaquier
#
# $Revision: 1.1.2.1 $
# $Revision: 1.1.2.2 $
opts="start stop restart showlog"
@ -31,13 +31,13 @@ depend() {
start() {
ebegin "Starting fail2ban"
${FAIL2BAN} -b ${FAIL2BAN_OPTS}
${FAIL2BAN} -b ${FAIL2BAN_OPTS} > /dev/null
eend $? "Failed to start fail2ban"
}
stop() {
ebegin "Stopping fail2ban"
${FAIL2BAN} -k
${FAIL2BAN} -k > /dev/null
eend $? "Failed to stop fail2ban"
}

View File

@ -9,7 +9,7 @@
#
# Author: Andrey G. Grozin
#
# $Revision: 1.1.2.1 $
# $Revision: 1.1.2.2 $
# Source function library.
. /etc/init.d/functions
@ -28,7 +28,7 @@ RETVAL=0
start() {
echo -n $"Starting fail2ban: "
"${FAIL2BAN}" -b
"${FAIL2BAN}" -b > /dev/null
RETVAL=$?
echo
}
@ -36,7 +36,7 @@ start() {
stop() {
if [ -f "${PIDFILE}" ]; then
echo -n $"Stopping fail2ban: "
"${FAIL2BAN}" -k
"${FAIL2BAN}" -k > /dev/null
echo
fi
}

View File

@ -16,20 +16,20 @@
# Author: Cyril Jaquier
#
# $Revision: 1.5.2.3 $
# $Revision: 1.5.2.5 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.5.2.3 $"
__date__ = "$Date: 2005/07/12 13:07:35 $"
__version__ = "$Revision: 1.5.2.5 $"
__date__ = "$Date: 2005/08/01 16:31:13 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import log4py
import logging
from ConfigParser import *
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban")
class ConfigReader:
""" This class allow the handling of the configuration options.
@ -77,5 +77,9 @@ class ConfigReader:
except NoOptionError:
logSys.warn("No '" + option[1] + "' defined in '" + sec + "'")
values[option[1]] = option[2]
except ValueError:
logSys.warn("Wrong value for '" + option[1] + "' in '" + sec +
"'. Using default one: '" + `option[2]` + "'")
values[option[1]] = option[2]
return values

View File

@ -10,9 +10,6 @@ Currently the main difference with upstream: python libraries are
placed under /usr/share/fail2ban insteadh of /usr/lib/fail2ban to
comply with policy regarding architecture independent resources.
Module log4py installed along into fail2ban directory because there is
no package for not-developed-in-a-long-time fail2ban
See the file TODO.Debian for more details, as well as the Debian Bug
Tracking system.

8
debian/changelog vendored
View File

@ -1,3 +1,11 @@
fail2ban (0.5.2-1) unstable; urgency=low
* New upstream release
* No log4py any more
* removed -i eth0 from config
-- Yaroslav Halchenko <debian@onerussian.com> Sat, 6 Aug 2005 09:21:07 -1000
fail2ban (0.5.1-1) unstable; urgency=low
* New upstream release

23
debian/copyright vendored
View File

@ -25,26 +25,3 @@ Boston, MA 02111-1307, USA.
See /usr/share/common-licenses/GPL for the full license.
log4py.py module included in this package is distributed in complience
with MIT License:
Copyright (c) 2001 Martin Preishuber
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

2
debian/rules vendored
View File

@ -87,7 +87,7 @@ binary-arch: build install copy-inits
dh_installinit
# dh_installcron
# dh_installinfo
dh_installman fail2ban.1x
dh_installman fail2ban.1x man/fail2ban.conf.5
dh_link
dh_strip
dh_compress

View File

@ -18,31 +18,43 @@
# Author: Cyril Jaquier
#
# $Revision: 1.4.2.3 $
# $Revision: 1.4.2.5 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.4.2.3 $"
__date__ = "$Date: 2005/07/15 14:11:21 $"
__version__ = "$Revision: 1.4.2.5 $"
__date__ = "$Date: 2005/08/04 20:51:14 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from sys import exit, path
import sys, traceback, logging
#yoh: We do need to load this path first if we ship log4py with fail2ban
# Appends our own modules path. Added before log4py import
# because log4py could be distributed with Fail2Ban.
path.append('/usr/share/fail2ban')
# Appends our own modules path.
sys.path.append("/usr/lib/fail2ban")
# Checks for required libs
# Checks if log4py is present.
try:
import log4py
except:
print "log4py is needed (see README)"
exit(-1)
# Now we can import our module
# Now we can import our modules.
import fail2ban
from utils.pidlock import PIDLock
# Start the application
fail2ban.main()
# Get the instance of the logger.
logSys = logging.getLogger("fail2ban")
# Get PID lock file instance
pidLock = PIDLock()
# Start the application. Handle all the unhandled exceptions
try:
fail2ban.main()
except SystemExit:
# We called sys.exit(). Nothing wrong so just pass
pass
except Exception, e:
# Print the exception data
(type, value, tb) = sys.exc_info()
tbStack = traceback.extract_tb(tb)
logSys.error("Fail2Ban got an unhandled exception and died.")
logSys.error("Type: " + `type.__name__` + "\n" +
"Value: " + `e.args` + "\n" +
"TB: " + `tbStack`)
# Remove the PID lock file. Should close #1239562
pidLock.remove()
logging.shutdown()

View File

@ -16,15 +16,15 @@
# Author: Cyril Jaquier
#
# $Revision: 1.20.2.8 $
# $Revision: 1.20.2.13 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.20.2.8 $"
__date__ = "$Date: 2005/07/22 21:13:19 $"
__version__ = "$Revision: 1.20.2.13 $"
__date__ = "$Date: 2005/08/06 18:44:06 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import time, sys, getopt, os, string, signal, log4py
import time, sys, getopt, os, string, signal, logging, logging.handlers
from ConfigParser import *
from version import version
@ -32,11 +32,15 @@ from firewall.firewall import Firewall
from logreader.logreader import LogReader
from confreader.configreader import ConfigReader
from utils.mail import Mail
from utils.pidlock import PIDLock
from utils.dns import *
from utils.process import *
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
# Get the instance of the logger.
logSys = logging.getLogger("fail2ban")
# Get PID lock file instance
pidLock = PIDLock()
# Global variables
logFwList = list()
@ -50,13 +54,13 @@ def dispUsage():
print "Fail2Ban v"+version+" reads log file that contains password failure report"
print "and bans the corresponding IP addresses using firewall rules."
print
print " -b start fail2ban in background"
print " -d start fail2ban in debug mode"
print " -b start in background"
print " -d start in debug mode"
print " -c <FILE> read configuration file FILE"
print " -p <FILE> create PID lock in FILE"
print " -h display this help message"
print " -i <IP(s)> IP(s) to ignore"
print " -k kill a currently running Fail2Ban instance"
print " -k kill a currently running instance"
print " -r <VALUE> allow a max of VALUE password failure"
print " -t <TIME> ban IP for TIME seconds"
print " -v verbose. Use twice for greater effect"
@ -101,8 +105,9 @@ def killApp():
# Execute global start command
executeCmd(conf["cmdend"], conf["debug"])
# Remove the PID lock
removePID(conf["pidlock"])
pidLock.remove()
logSys.info("Exiting...")
logging.shutdown()
sys.exit(0)
def getCmdLineOptions(optList):
@ -128,23 +133,23 @@ def getCmdLineOptions(optList):
if opt[0] == "-i":
conf["ignoreip"] = opt[1]
if opt[0] == "-r":
conf["retrymax"] = int(opt[1])
conf["maxretry"] = int(opt[1])
if opt[0] == "-p":
conf["pidlock"] = opt[1]
if opt[0] == "-k":
pid = checkForPID(conf["pidlock"])
if pid:
killPID(int(pid))
logSys.warn("Killed Fail2Ban with PID "+pid)
sys.exit(0)
else:
logSys.error("No running Fail2Ban found")
sys.exit(-1)
conf["kill"] = True
def main():
""" Fail2Ban main function
"""
logSys.set_formatstring("%T %L %M")
# Add the default logging handler
stdout = logging.StreamHandler(sys.stdout)
logSys.addHandler(stdout)
# Default formatter
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
stdout.setFormatter(formatter)
conf["verbose"] = 0
conf["conffile"] = "/etc/fail2ban.conf"
@ -169,7 +174,7 @@ def main():
# Options
optionValues = (["bool", "background", False],
["str", "logtargets", "STDOUT /var/log/fail2ban.log"],
["str", "logtargets", "/var/log/fail2ban.log"],
["bool", "debug", False],
["str", "pidlock", "/var/run/fail2ban.pid"],
["int", "maxretry", 3],
@ -184,47 +189,23 @@ def main():
# Gets command line options
getCmdLineOptions(optList)
# Process some options
# Log targets
# Bug fix for #1234699
os.umask(0077)
# Remove all the targets before setting our own
logSys.remove_all_targets()
for target in conf["logtargets"].split():
if target == "STDOUT":
logSys.add_target(log4py.TARGET_SYS_STDOUT)
elif target == "STDERR":
logSys.add_target(log4py.TARGET_SYS_STDERR)
elif target == "SYSLOG":
logSys.add_target(log4py.TARGET_SYSLOG)
# PID lock
pidLock.setPath(conf["pidlock"])
# Now we can kill properly a running instance if needed
try:
conf["kill"]
pid = pidLock.exists()
if pid:
killPID(int(pid))
logSys.warn("Killed Fail2Ban with PID "+pid)
sys.exit(0)
else:
# Target should be a file
try:
open(target, "a")
logSys.add_target(target)
except IOError:
logSys.error("Unable to log to " + target)
# Check if at least one target exists
if len(logSys.get_targets()) == 0:
logSys.add_target(log4py.TARGET_SYS_STDOUT)
logSys.error("No valid logging target found. Logging to STDOUT")
# Verbose level
if conf["verbose"]:
logSys.warn("Verbose level is "+`conf["verbose"]`)
if conf["verbose"] == 1:
logSys.set_loglevel(log4py.LOGLEVEL_VERBOSE)
elif conf["verbose"] > 1:
logSys.set_loglevel(log4py.LOGLEVEL_DEBUG)
# Set debug log level
if conf["debug"]:
logSys.set_loglevel(log4py.LOGLEVEL_DEBUG)
logSys.set_formatstring(log4py.FMT_DEBUG)
logSys.warn("DEBUG MODE: FIREWALL COMMANDS ARE _NOT_ EXECUTED BUT " +
"ONLY DISPLAYED IN THE LOG MESSAGES")
logSys.error("No running Fail2Ban found")
sys.exit(-1)
except KeyError:
pass
# Start Fail2Ban in daemon mode
if conf["background"]:
@ -233,10 +214,55 @@ def main():
if not retCode:
logSys.error("Unable to start daemon")
sys.exit(-1)
# Verbose level
if conf["verbose"]:
logSys.warn("Verbose level is "+`conf["verbose"]`)
if conf["verbose"] == 1:
logSys.setLevel(logging.INFO)
elif conf["verbose"] > 1:
logSys.setLevel(logging.DEBUG)
# Set debug log level
if conf["debug"]:
logSys.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s %(levelname)s " +
"[%(filename)s (%(lineno)d)] " +
"%(message)s")
stdout.setFormatter(formatter)
logSys.warn("DEBUG MODE: FIREWALL COMMANDS ARE _NOT_ EXECUTED BUT " +
"ONLY DISPLAYED IN THE LOG MESSAGES")
# Process some options
# Log targets
# Bug fix for #1234699
os.umask(0077)
for target in conf["logtargets"].split():
if target == "STDERR":
hdlr = logging.StreamHandler(sys.stderr)
elif target == "SYSLOG":
hdlr = logging.handlers.SysLogHandler()
else:
# Target should be a file
try:
open(target, "a")
hdlr = logging.FileHandler(target)
except IOError:
logSys.error("Unable to log to " + target)
continue
# Set formatter and add handler to logger
hdlr.setFormatter(formatter)
logSys.addHandler(hdlr)
# Ignores IP list
ignoreIPList = conf["ignoreip"].split(' ')
# maxretry option
maxRetry = conf["maxretry"]
# bantime option
banTime = conf["bantime"]
# Checks for root user. This is necessary because log files
# are owned by root and firewall needs root access.
if not checkForRoot():
@ -245,16 +271,16 @@ def main():
sys.exit(-1)
# Checks that no instance of Fail2Ban is currently running.
pid = checkForPID(conf["pidlock"])
pid = pidLock.exists()
if pid:
logSys.error("Fail2Ban already running with PID "+pid)
sys.exit(-1)
else:
createPID(conf["pidlock"])
pidLock.create()
logSys.debug("ConfFile is "+conf["conffile"])
logSys.debug("BanTime is "+`conf["bantime"]`)
logSys.debug("retryAllowed is "+`conf["maxretry"]`)
logSys.debug("ConfFile is " + conf["conffile"])
logSys.debug("BanTime is " + `conf["bantime"]`)
logSys.debug("retryAllowed is " + `conf["maxretry"]`)
# Options
optionValues = (["bool", "enabled", False],
@ -277,8 +303,10 @@ def main():
logSys.debug("to: " + mailConf["to"] + " from: " + mailConf["from"])
# Options
optionValues = (["bool", "enabled", True],
optionValues = (["bool", "enabled", False],
["str", "logfile", "/dev/null"],
["int", "maxretry", None],
["int", "bantime", None],
["str", "timeregex", ""],
["str", "timepattern", ""],
["str", "failregex", ""],
@ -291,11 +319,19 @@ def main():
for t in confReader.getSections():
l = confReader.getLogOptions(t, optionValues)
if l["enabled"]:
# Override maxretry option
if not l["maxretry"] == None:
maxRetry = l["maxretry"]
# Override bantime option
if not l["bantime"] == None:
banTime = l["bantime"]
# Creates a logreader object
lObj = LogReader(l["logfile"], l["timeregex"], l["timepattern"],
l["failregex"], conf["bantime"])
l["failregex"], maxRetry, banTime)
# Creates a firewall object
fObj = Firewall(l["fwban"], l["fwunban"], conf["bantime"])
fObj = Firewall(l["fwban"], l["fwunban"], banTime)
# Links them into a list. I'm not really happy
# with this :/
logFwList.append([t, lObj, fObj, dict(), l])
@ -311,7 +347,7 @@ def main():
for element in logFwList:
element[1].addIgnoreIP(ip)
logSys.info("Fail2Ban v"+version+" is running")
logSys.info("Fail2Ban v" + version + " is running")
# Execute global start command
executeCmd(conf["cmdstart"], conf["debug"])
# Execute start command of each section
@ -363,7 +399,7 @@ def main():
failTime = fails[attempt][1]
if failTime < unixTime - findTime:
del element[3][attempt]
elif fails[attempt][0] >= conf["maxretry"]:
elif fails[attempt][0] >= element[1].getMaxRetry():
aInfo = {"ip": attempt,
"failures": element[3][attempt][0],
"failtime": failTime}

View File

@ -16,21 +16,21 @@
# Author: Cyril Jaquier
#
# $Revision: 1.8.2.5 $
# $Revision: 1.8.2.6 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.8.2.5 $"
__date__ = "$Date: 2005/07/15 14:07:08 $"
__version__ = "$Revision: 1.8.2.6 $"
__date__ = "$Date: 2005/08/01 16:31:42 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import time, os, log4py, re
import time, os, logging, re
from utils.process import executeCmd
from utils.strings import replaceTag
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban")
class Firewall:
""" Manages the ban list and executes the command that ban

593
log4py.py
View File

@ -1,593 +0,0 @@
"""
Python logging module - Version 1.3
Loglevels:
LOGLEVEL_NONE, LOGLEVEL_ERROR, LOGLEVEL_NORMAL, LOGLEVEL_VERBOSE, LOGLEVEL_DEBUG
Format-Parameters:
%C -- The name of the current class.
%D -- Program duration since program start.
%d -- Program duration for the last step (last output).
%F -- The name of the current function.
%f -- Current filename
%L -- Log type (Error, Warning, Debug or Info)
%M -- The actual message.
%N -- The current line number.
%T -- Current time (human readable).
%t -- Current time (machine readable)
%U -- Current fully qualified module/file.
%u -- Current module/file.
%x -- NDC (nested diagnostic contexts).
Pre-defined Formats:
FMT_SHORT -- %M
FMT_MEDIUM -- [ %C.%F ] %D: %M
FMT_LONG -- %T %L %C [%F] %x%M
FMT_DEBUG -- %T [%D (%d)] %L %C [%F (%N)] %x%M
"""
# Logging levels
LOGLEVEL_NONE = 1 << 0
LOGLEVEL_ERROR = 1 << 1
LOGLEVEL_NORMAL = 1 << 2
LOGLEVEL_VERBOSE = 1 << 3
LOGLEVEL_DEBUG = 1 << 4
# Pre-defined format strings
FMT_SHORT = "%M"
FMT_MEDIUM = "[ %C.%F ] %D: %M"
FMT_LONG = "%T %L %C [%F] %x%M"
FMT_DEBUG = "%T [%D (%d)] %L %C [%F (%N)] %x%M"
# Special logging targets
TARGET_MYSQL = "MySQL"
TARGET_POSTGRES = "Postgres"
TARGET_SYSLOG = "Syslog"
TARGET_SYS_STDOUT = "sys.stdout"
TARGET_SYS_STDERR = "sys.stderr"
TARGET_SYS_STDOUT_ALIAS = "stdout"
TARGET_SYS_STDERR_ALIAS = "stderr"
SPECIAL_TARGETS = [ TARGET_MYSQL, TARGET_POSTGRES, TARGET_SYSLOG, TARGET_SYS_STDOUT, TARGET_SYS_STDERR, TARGET_SYS_STDOUT_ALIAS, TARGET_SYS_STDERR_ALIAS ]
# Configuration files
CONFIGURATION_FILES = {}
CONFIGURATION_FILES[1] = "log4py.conf" # local directory
CONFIGURATION_FILES[2] = "$HOME/.log4py.conf" # hidden file in the home directory
CONFIGURATION_FILES[3] = "/etc/log4py.conf" # system wide file
# Constants for the FileAppender
ROTATE_NONE = 0
ROTATE_DAILY = 1
ROTATE_WEEKLY = 2
ROTATE_MONTHLY = 3
# The following constants are of internal interest only
# Message constants (used for ansi colors and for logtype %L)
MSG_DEBUG = 1 << 0
MSG_WARN = 1 << 1
MSG_ERROR = 1 << 2
MSG_INFO = 1 << 3
# Boolean constants
TRUE = "TRUE"
FALSE = "FALSE"
# Color constants
BLACK = 30
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
PURPLE = 35
AQUA = 36
WHITE = 37
LOG_MSG = { MSG_DEBUG: "DEBUG", MSG_WARN: "WARNING", MSG_ERROR: "ERROR", MSG_INFO: "INFO"}
LOG_COLORS = { MSG_DEBUG: [WHITE, BLACK, FALSE], MSG_WARN: [WHITE, BLACK, FALSE], MSG_ERROR: [WHITE, BLACK, TRUE], MSG_INFO: [WHITE, BLACK, FALSE]}
LOG_LEVELS = { "DEBUG": LOGLEVEL_DEBUG, "VERBOSE": LOGLEVEL_VERBOSE, "NORMAL": LOGLEVEL_NORMAL, "NONE": LOGLEVEL_NONE, "ERROR": LOGLEVEL_ERROR }
SECTION_DEFAULT = "Default"
from time import time, strftime, localtime
from types import StringType, ClassType, InstanceType, FileType, TupleType
from string import zfill, atoi, lower, upper, join, replace, split, strip
from re import sub
from ConfigParser import ConfigParser, NoOptionError
from os import stat, rename
import sys
import traceback
import os
import copy
import socket
import locale
if (os.name == "posix"):
import syslog
try:
import MySQLdb
mysql_available = TRUE
except:
mysql_available = FALSE
def get_homedirectory():
if (sys.platform == "win32"):
if (os.environ.has_key("USERPROFILE")):
return os.environ["USERPROFILE"]
else:
return "C:\\"
else:
if (os.environ.has_key("HOME")):
return os.environ["HOME"]
else:
# No home directory set
return ""
# This is the main class for the logging module
class Logger:
cache = {}
instance = None
configfiles = []
hostname = socket.gethostname()
def __init__(self, useconfigfiles = TRUE, customconfigfiles = None):
""" **(private)** Class initalization & customization. """
if (customconfigfiles):
if (type(customconfigfiles) == StringType):
customconfigfiles = [customconfigfiles]
Logger.configfiles = customconfigfiles
if (not Logger.instance):
self.__Logger_setdefaults()
if (useconfigfiles == TRUE):
self.__Logger_appendconfigfiles(Logger.configfiles)
# read the default options
self.__Logger_parse_options()
self.__Logger_timeinit = time()
self.__Logger_timelaststep = self.__Logger_timeinit
Logger.instance = self
if (useconfigfiles == TRUE):
# read and pre-cache settings for named classids
self.__Logger_cache_options()
def get_root(self):
""" Provides a way to change the base logger object's properties. """
return Logger.instance
def get_instance(self, classid = "Main", use_cache = TRUE):
""" Either get the cached logger instance or create a new one
Note that this is safe, even if you have your target set to sys.stdout
or sys.stderr
"""
cache = Logger.cache
if (type(classid) == ClassType):
classid = classid.__name__
elif (type(classid) == InstanceType):
classid = classid.__class__.__name__
# classid has to be lowercase, because the ConfigParser returns sections lowercase
classid = lower(classid)
if ((cache.has_key(classid)) and (use_cache == TRUE)):
cat = Logger.cache[classid]
else:
instance = Logger.instance
# test for targets which won't deep copy
targets = instance.__Logger_targets
deepcopyable = TRUE
for i in range(len(targets)):
if (type(targets[i]) == FileType):
deepcopyable = FALSE
if (deepcopyable == FALSE):
# swap the non-copyable target out for a moment
del instance.__Logger_targets
cat = copy.deepcopy(instance)
instance.__Logger_targets = targets
cat.__Logger_targets = targets
else:
cat = copy.deepcopy(instance)
cat.__Logger_classname = classid
# new categories have their own private Nested Diagnostic Contexts
self.__Logger_ndc = []
self.__Logger_classid = classid
cat.debug("Class %s instantiated" % classid)
if (use_cache == TRUE):
cache[classid] = cat
return cat
# Log-target handling (add, remove, set, remove_all)
def add_target(self, target, *args):
""" Add a target to the logger targets. """
if (not target in self.__Logger_targets):
if (target == TARGET_MYSQL):
if (mysql_available == TRUE):
# Required parameters: dbhost, dbname, dbuser, dbpass, dbtable
try:
self.__Logger_mysql_connection = MySQLdb.connect(host=args[0], db=args[1], user=args[2], passwd=args[3])
self.__Logger_mysql_cursor = self.__Logger_mysql_connection.cursor()
self.__Logger_mysql_tablename = args[4]
self.__Logger_targets.append(target)
except MySQLdb.OperationalError, detail:
self.error("MySQL connection failed: %s" % detail)
else:
self.error("MySQL target not added - Python-mysql not available")
else:
if (type(target) == StringType):
if (target not in SPECIAL_TARGETS):
# This is a filename
target = FileAppender(target, self.__Logger_rotation)
if ((target == TARGET_SYSLOG) and (os.name != "posix")):
self.warn("TARGET_SYSLOG is not available on non-posix platforms!")
else:
self.__Logger_targets.append(target)
def remove_target(self, target):
""" Remove a target from the logger targets. """
if (target in self.__Logger_targets):
if (target == TARGET_MYSQL):
self.__Logger_mysql_connection.close()
self.__Logger_targets.remove(target)
def set_target(self, target):
""" Set a single target. """
if (type(target) == StringType):
if (target not in SPECIAL_TARGETS):
# File target
target = FileAppender(target, self.__Logger_rotation)
self.__Logger_targets = [ target ]
def remove_all_targets(self):
""" Remove all targets from the logger targets. """
self.__Logger_targets=[]
def get_targets(self):
""" Returns all defined targets. """
return self.__Logger_targets
# Methods to set properties
def set_loglevel(self, loglevel):
""" Set the loglevel for the current instance. """
self.__Logger_loglevel = loglevel
def set_formatstring(self, formatstring):
""" Set a format string. """
self.__Logger_formatstring = formatstring
def set_use_ansi_codes(self, useansicodes):
""" Use ansi codes for output to the console (TRUE or FALSE). """
self.__Logger_useansicodes = useansicodes
def set_time_format(self, timeformat):
""" Set the time format (default: loaded from the system locale). """
self.__Logger_timeformat = timeformat
def set_rotation(self, rotation):
""" Set the file rotation mode to one of ROTATE_NONE, ROTATE_DAILY, ROTATE_WEEKLY, ROTATE_MONTHLY """
self.__Logger_rotation = rotation
for i in range(len(self.__Logger_targets)):
target = self.__Logger_targets[i]
if (isinstance(target, FileAppender)):
target.set_rotation(rotation)
# Method to get properties
def get_loglevel(self):
""" Returns the current loglevel. """
return self.__Logger_loglevel
def get_formatstring(self):
""" Returns the current format string. """
return self.__Logger_formatstring
def get_use_ansi_codes(self):
""" Returns, wether ansi codes are being used or not. """
return self.__Logger_useansicodes
def get_time_format(self):
""" Returns the current time format. """
return self.__Logger_timeformat
def get_rotation(self):
""" Returns the current rotation setting. """
return self.__Logger_rotation
# Methods to push and pop trace messages for nested contexts
def push(self, message):
""" Add a trace message. """
self.__Logger_ndc.append(message)
def pop(self):
""" Remove the topmost trace message. """
ct = len(self.__Logger_ndc)
if (ct):
del(self.__Logger_ndc[ct-1])
def clear_ndc(self):
""" Clears all NDC messages. """
self.__Logger_ndc = []
# Methods to actually print messages
def debug(self, *messages):
""" Write a debug message. """
if (self.__Logger_loglevel >= LOGLEVEL_DEBUG):
message = self.__Logger_collate_messages(messages)
self.__Logger_showmessage(message, MSG_DEBUG)
def warn(self, *messages):
""" Write a warning message. """
if (self.__Logger_loglevel >= LOGLEVEL_VERBOSE):
message = self.__Logger_collate_messages(messages)
self.__Logger_showmessage(message, MSG_WARN)
def error(self, *messages):
""" Write a error message. """
if (self.__Logger_loglevel >= LOGLEVEL_ERROR):
message = self.__Logger_collate_messages(messages)
self.__Logger_showmessage(message, MSG_ERROR)
def info(self, *messages):
""" Write a info message. """
if (self.__Logger_loglevel >= LOGLEVEL_NORMAL):
message = self.__Logger_collate_messages(messages)
self.__Logger_showmessage(message, MSG_INFO)
# Private methods of the Logger class - you never have to use those directly
def __Logger_collate_messages(self, messages):
""" **(private)** Create a single string from a number of messages. """
return strip(reduce(lambda x, y: "%s%s" % (x, y), messages))
def __Logger_tracestack(self):
""" **(private)** Analyze traceback stack and set linenumber and functionname. """
stack = traceback.extract_stack()
self.__Logger_module = stack[-4][0]
self.__Logger_linenumber = stack[-4][1]
self.__Logger_functionname = stack[-4][2]
self.__Logger_filename = stack[-4][0]
if (self.__Logger_functionname == "?"):
self.__Logger_functionname = "Main"
def __Logger_setdefaults(self):
""" **(private)** Set default values for internal variables. """
locale.setlocale(locale.LC_ALL)
self.__Logger_classid = None
self.__Logger_targets = [ TARGET_SYS_STDOUT ] # default target = sys.stdout
self.__Logger_formatstring = FMT_LONG
self.__Logger_loglevel = LOGLEVEL_NORMAL
self.__Logger_rotation = ROTATE_NONE
self.__Logger_useansicodes = FALSE
self.__Logger_functionname = ""
self.__Logger_filename = ""
self.__Logger_linenumber = -1
try:
self.__Logger_timeformat = "%s %s" % (locale.nl_langinfo(locale.D_FMT), locale.nl_langinfo(locale.T_FMT))
except (AttributeError):
self.__Logger_timeformat = "%d.%m.%Y %H:%M:%S"
self.__Logger_classname = None
self.__Logger_configfilename = ""
self.__Logger_module = ""
self.__Logger_ndc = [] # ndc = Nested Diagnostic Context
def __Logger_find_config(self):
""" **(private)** Search for configuration files. """
if (not self.__Logger_configfilename):
priorities = CONFIGURATION_FILES.keys()
priorities.sort()
configfilename = ""
for i in range(len(priorities)):
filename = CONFIGURATION_FILES[priorities[i]]
home_directory = get_homedirectory()
if (os.sep == "\\"):
home_directory = replace(home_directory, "\\", "\\\\")
filename = sub("\$HOME", home_directory, filename)
if (os.path.exists(filename)):
configfilename = filename
break
self.__Logger_configfilename = configfilename
return self.__Logger_configfilename
def __Logger_parse_options(self, section = SECTION_DEFAULT):
""" **(private)** Parse main options from config file. """
configfilename = self.__Logger_find_config()
if (configfilename != ""):
parser = ConfigParser()
parser.read(configfilename)
self.__Logger_set_instance_options(parser, section, self)
return TRUE
def __Logger_set_instance_options(self, parser, section, instance):
""" **(private)** Set the options for a given instance from the parser section """
for i in range(len(parser.options(section))):
option = lower(parser.options(section)[i])
value = parser.get(section, option)
if (option == "format"):
instance.set_formatstring(value)
elif (option == "timeformat"):
instance.set_time_format(value)
elif (option == "ansi"):
instance.set_use_ansi_codes(upper(value))
elif (option == "loglevel"):
instance.set_loglevel(LOG_LEVELS[upper(value)])
elif (option == "target"):
splitted = split(value, ",")
instance.remove_all_targets()
for i in range(len(splitted)):
instance.add_target(strip(splitted[i]))
def __Logger_cache_options(self):
""" **(private)** Read and cache debug levels for categories from config file. """
configfilename = self.__Logger_find_config()
if (configfilename != ""):
parser = ConfigParser()
parser.read(configfilename)
for i in range(len(parser.sections())):
section = parser.sections()[i]
if (section != SECTION_DEFAULT):
instance = self.get_instance(section)
self.__Logger_set_instance_options(parser, section, instance)
return TRUE
def __Logger_appendconfigfiles(self, filenames):
""" **(private)** Append a filename to the list of configuration files. """
filenames.reverse()
for i in range(len(filenames)):
keys = CONFIGURATION_FILES.keys()
CONFIGURATION_FILES[min(keys) - 1] = filenames[i]
def __Logger_get_ndc(self):
""" **(private)** Returns the NDC (nested diagnostic context) joined with single-spaces. """
if (len(self.__Logger_ndc)):
return join(self.__Logger_ndc)
else:
return ""
def __Logger_showmessage(self, message, messagesource):
""" **(private)** Writes a message to all targets set. """
if (isinstance(message, Exception)):
(exc_type, exc_value, tb) = sys.exc_info()
exception_summary = traceback.format_exception(exc_type, exc_value, tb)
message = 'Exception caught:\n'
for line in exception_summary:
message = "%s%s" % (message, line)
currenttime = time()
self.__Logger_tracestack()
timedifference = "%.3f" % (currenttime - self.__Logger_timeinit)
timedifflaststep = "%.3f" % (currenttime - self.__Logger_timelaststep)
self.__Logger_timelaststep = currenttime
milliseconds = int(round((currenttime - long(currenttime)) * 1000))
timeformat = sub("%S", "%S." + (zfill(milliseconds, 3)), self.__Logger_timeformat)
currentformattedtime = strftime(timeformat, localtime(currenttime))
line = self.__Logger_formatstring
line = sub("%C", str(self.__Logger_classname), line)
line = sub("%D", timedifference, line)
line = sub("%d", timedifflaststep, line)
line = sub("%F", self.__Logger_functionname, line)
line = sub("%f", self.__Logger_filename, line)
line = sub("%U", self.__Logger_module, line)
line = sub("%u", os.path.split(self.__Logger_module)[-1], line)
ndc = self.__Logger_get_ndc()
if (ndc != ""):
line = sub("%x", "%s - " % ndc, line)
else:
line = sub("%x", "", line)
message = replace(message, "\\", "\\\\")
if (self.__Logger_useansicodes == TRUE):
line = sub("%L", self.__Logger_ansi(LOG_MSG[messagesource], messagesource), line)
line = sub("%M", self.__Logger_ansi(message, messagesource), line)
else:
line = sub("%L", LOG_MSG[messagesource], line)
line = sub("%M", message, line)
line = sub("%N", str(self.__Logger_linenumber), line)
line = sub("%T", currentformattedtime, line)
line = sub("%t", `currenttime`, line)
for i in range(len(self.__Logger_targets)):
target = self.__Logger_targets[i]
if (target == TARGET_MYSQL):
sqltime = strftime("'%Y-%m-%d', '%H:%M:%S'", localtime(currenttime))
sqlStatement = "INSERT INTO %s (host, facility, level, date, time, program, msg) VALUES ('%s', '%s', '%s', %s, '%s', '%s')" % (self.__Logger_mysql_tablename, self.hostname, self.__Logger_functionname, LOG_MSG[messagesource], sqltime, str(self.__Logger_classname), sub("'", "`", message + " " + ndc))
self.__Logger_mysql_cursor.execute(sqlStatement)
elif (target == TARGET_SYSLOG):
# We don't need time and stuff here
syslog.syslog(message)
elif (isinstance(target, FileAppender)):
target.writeline(line)
elif (target == sys.stdout) or (lower(target) == TARGET_SYS_STDOUT) or (lower(target) == TARGET_SYS_STDOUT_ALIAS):
sys.stdout.write("%s\n" % line)
elif (target == sys.stderr) or (lower(target) == TARGET_SYS_STDERR) or (lower(target) == TARGET_SYS_STDERR_ALIAS):
sys.stderr.write("%s\n" % line)
else:
target.write("%s\n" % line)
def __Logger_ansi(self, text, messagesource):
""" **(private)** Converts plain text to ansi text. """
bold = LOG_COLORS[messagesource][2]
fg = str(LOG_COLORS[messagesource][0])
bg = LOG_COLORS[messagesource][1]
if (bold == TRUE):
fg = "%s;1" % fg
bg = bg + 10
text = "\033[%d;%sm%s\033[0m" % (bg, fg, text)
return text
class FileAppender:
def __init__(self, filename, rotation = ROTATE_NONE):
""" **(private)** Class initalization & customization. """
self.__FileAppender_filename = sub("\$HOME", get_homedirectory(), filename)
self.__FileAppender_filename = os.path.expanduser(self.__FileAppender_filename)
self.__FileAppender_filename = os.path.expandvars(self.__FileAppender_filename)
self.__FileAppender_rotation = rotation
def __FileAppender_rotate(self, modification_time):
""" **(private)** Check, wether the file has to be rotated yet or not. """
if (self.__FileAppender_rotation == ROTATE_DAILY):
strftime_mask = "%Y%j"
elif (self.__FileAppender_rotation == ROTATE_WEEKLY):
strftime_mask = "%Y%W"
elif (self.__FileAppender_rotation == ROTATE_MONTHLY):
strftime_mask = "%Y%m"
return (strftime(strftime_mask, localtime(time())) != strftime(strftime_mask, localtime(modification_time)))
def __FileAppender_date_string(self, modification_time):
""" **(private)** Returns a new filename for the rotated file with the appropriate time included. """
if (self.__FileAppender_rotation == ROTATE_DAILY):
return strftime("%Y-%m-%d", localtime(modification_time))
elif (self.__FileAppender_rotation == ROTATE_WEEKLY):
return strftime("%Y-Week %W", localtime(modification_time))
elif (self.__FileAppender_rotation == ROTATE_MONTHLY):
return strftime("%Y-Month %m", localtime(modification_time))
def get_rotation(self):
""" Returns the current rotation setting. """
return self.__FileAppender_rotation
def set_rotation(self, rotation):
""" Set the file rotation mode to one of ROTATE_NONE, ROTATE_DAILY, ROTATE_WEEKLY, ROTATE_MONTHLY """
self.__FileAppender_rotation = rotation
def write(self, text):
""" Write some text to the file appender. """
if ((os.path.exists(self.__FileAppender_filename)) and (self.__FileAppender_rotation != ROTATE_NONE)):
statinfo = stat(self.__FileAppender_filename)
if (self.__FileAppender_rotate(statinfo[8])):
splitted = os.path.splitext(self.__FileAppender_filename)
target_file = "%s-%s%s" % (splitted[0], self.__FileAppender_date_string(statinfo[8]), splitted[1])
rename(self.__FileAppender_filename, target_file)
file = open(self.__FileAppender_filename, "a")
file.write(text)
file.close()
def writeline(self, text):
""" Write some text including newline to the file appender. """
self.write("%s\n" % text)

View File

@ -16,20 +16,20 @@
# Author: Cyril Jaquier
#
# $Revision: 1.13.2.4 $
# $Revision: 1.13.2.7 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.13.2.4 $"
__date__ = "$Date: 2005/07/23 09:07:53 $"
__version__ = "$Revision: 1.13.2.7 $"
__date__ = "$Date: 2005/08/06 18:43:11 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os, sys, time, re, log4py
import os, sys, time, re, logging
from utils.dns import *
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban")
class LogReader:
""" Reads a log file and reports information about IP that make password
@ -38,8 +38,9 @@ class LogReader:
"""
def __init__(self, logPath, timeregex, timepattern, failregex,
findTime = 3600):
maxRetry, findTime):
self.logPath = logPath
self.maxRetry = maxRetry
self.timeregex = timeregex
self.timepattern = timepattern
self.failregex = failregex
@ -50,6 +51,11 @@ class LogReader:
self.lastDate = 0
self.logStats = None
def getMaxRetry(self):
""" Gets the maximum number of failures
"""
return self.maxRetry
def getFindTime(self):
""" Gets the find time.
"""
@ -83,7 +89,7 @@ class LogReader:
fileHandler = open(self.logPath)
except OSError:
logSys.error("Unable to open "+self.logPath)
sys.exit(-1)
return fileHandler
def isModified(self):
@ -93,7 +99,6 @@ class LogReader:
self.logStats = os.stat(self.logPath)
except OSError:
logSys.error("Unable to get stat on "+self.logPath)
sys.exit(-1)
if self.lastModTime == self.logStats.st_mtime:
return False
@ -130,7 +135,7 @@ class LogReader:
logFile = self.openLogFile()
self.setFilePos(logFile)
lastLine = ''
for line in logFile.readlines():
for line in logFile:
lastLine = line
failList = self.findFailure(line)
for element in failList:

58
man/fail2ban.8 Normal file
View File

@ -0,0 +1,58 @@
.\"
.TH "FAIL2BAN" "8" "July 2005" "Cyril Jaquier" "System administration tools"
.SH "NAME"
fail2ban \- bans IP that makes too many password failures
.SH "SYNOPSIS"
.B fail2ban
[\fIOPTIONS\fR]
.SH "DESCRIPTION"
\fBFail2Ban\fR reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. It updates
firewall rules to reject the IP address.
.SH "OPTIONS"
.TP
\fB\-b\fR
start in background
.TP
\fB\-d\fR
start in debug mode. Commands are NOT executed but only displayed
.TP
\fB\-c\fR \fIFILE\fR
read configuration file \fIFILE\fR
.TP
\fB\-p\fR \fIFILE\fR
create PID lock in \fIFILE\fR
.TP
\fB\-h, \-\-help\fR
display this help message
.TP
\fB\-i\fR \fIIP\fR
\fIIP\fR(s) to ignore
.TP
\fB\-k\fR
kill a currently running Fail2Ban instance
.TP
\fB\-r\fR \fIVALUE\fR
allow a max of \fIVALUE\fR password failure
.TP
\fB\-t\fR \fITIME\fR
ban IP for \fITIME\fR seconds
.TP
\fB\-v\fR
verbose. Use twice for greater effect
.TP
\fB\-V, \-\-version\fR
print software version
.SH "FILES"
.I /etc/fail2ban.conf
.RS
The configuration file. See \fBfail2ban.conf\fR(5) for further details.
.SH "REPORTING BUGS"
Please report bugs at http://sourceforge.net/projects/fail2ban/
via bug tracker
.SH "AUTHOR"
Cyril Jaquier <lostcontrol@users.sourceforge.net>
.SH "SEE ALSO"
.TP
See
.BR "http://fail2ban.sourceforge.net/".

20
man/fail2ban.conf.5 Normal file
View File

@ -0,0 +1,20 @@
.\"
.TH "FAIL2BAN.CONF" "5" "July 2005" "Cyril Jaquier" "System administration tools"
.SH "NAME"
fail2ban.conf \- configuration data for fail2ban
.SH "DESCRIPTION"
\fB/etc/fail2ban.conf\fR contains data about the general configuration of fail2ban, the mail notification and services to monitor.
.SH "VARIABLES"
Please look at the file itself
.SH "FILES"
.I /etc/fail2ban.conf
.SH "REPORTING BUGS"
Please report bugs at http://sourceforge.net/projects/fail2ban/
via bug tracker
.SH "AUTHOR"
Cyril Jaquier <lostcontrol@users.sourceforge.net>
.SH "SEE ALSO"
.BR fail2ban (8)
.TP
See
.BR "http://fail2ban.sourceforge.net/".

View File

@ -18,11 +18,11 @@
# Author: Cyril Jaquier
#
# $Revision: 1.4.2.2 $
# $Revision: 1.4.2.3 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.4.2.2 $"
__date__ = "$Date: 2005/07/15 14:14:12 $"
__version__ = "$Revision: 1.4.2.3 $"
__date__ = "$Date: 2005/07/28 20:30:34 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
@ -31,15 +31,25 @@ from version import version
from os.path import isfile, join
from sys import exit, argv
longdesc = '''
Fail2Ban scans log files like /var/log/pwdfail or
/var/log/apache/error_log and bans IP that makes
too many password failures. It updates firewall rules
to reject the IP address or executes user defined
commands.'''
setup(
name = "fail2ban",
version = version,
description = "Ban IPs that make too many password failure",
long_description = longdesc,
author = "Cyril Jaquier",
author_email = "lostcontrol@users.sourceforge.net",
url = "http://fail2ban.sourceforge.net",
license = "GPL",
platforms = "Posix",
scripts = ['fail2ban'],
py_modules = ['fail2ban', 'version', 'log4py'],
py_modules = ['fail2ban', 'version'],
packages = ['firewall', 'logreader', 'confreader', 'utils']
)

View File

@ -16,20 +16,20 @@
# Author: Cyril Jaquier
#
# $Revision: 1.1.2.1 $
# $Revision: 1.1.2.2 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.1.2.1 $"
__date__ = "$Date: 2005/07/12 13:09:47 $"
__version__ = "$Revision: 1.1.2.2 $"
__date__ = "$Date: 2005/08/01 16:35:18 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import log4py, smtplib
import logging, smtplib
from utils.strings import replaceTag
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban")
class Mail:
""" Mailer class

98
utils/pidlock.py Normal file
View File

@ -0,0 +1,98 @@
# 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: 1.1.2.1 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.1.2.1 $"
__date__ = "$Date: 2005/08/04 20:48:30 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os, logging
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban")
class PIDLock:
""" Manages the PID lock file.
The following class shows how to implement the singleton pattern[1] in
Python. A singleton is a class that makes sure only one instance of it
is ever created. Typically such classes are used to manage resources
that by their very nature can only exist once.
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52558
"""
class __impl:
""" Implementation of the singleton interface """
def setPath(self, path):
""" Set PID lock file path.
"""
self.path = path
def create(self):
""" Create PID lock.
"""
fileHandler = open(self.path, mode='w')
pid = os.getpid()
fileHandler.write(`pid` + '\n')
fileHandler.close()
logSys.debug("Created PID lock (" + `pid` + ") in " + self.path)
def remove(self):
""" Remove PID lock.
"""
os.remove(self.path)
logSys.debug("Removed PID lock " + self.path)
def exists(self):
""" Returns the current PID if Fail2Ban is running or False
if no instance found.
"""
try:
fileHandler = open(self.path)
pid = fileHandler.readline()
fileHandler.close()
return pid
except IOError:
return False
# storage for the instance reference
__instance = None
def __init__(self):
""" Create singleton instance """
# Check whether we already have an instance
if PIDLock.__instance is None:
# Create and remember instance
PIDLock.__instance = PIDLock.__impl()
# Store instance reference as the only member in the handle
self.__dict__['_PIDLock__instance'] = PIDLock.__instance
def __getattr__(self, attr):
""" Delegate access to implementation """
return getattr(self.__instance, attr)
def __setattr__(self, attr, value):
""" Delegate access to implementation """
return setattr(self.__instance, attr, value)

View File

@ -16,24 +16,24 @@
# Author: Cyril Jaquier
#
# $Revision: 1.1.2.2 $
# $Revision: 1.1.2.4 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.1.2.2 $"
__date__ = "$Date: 2005/07/15 14:08:17 $"
__version__ = "$Revision: 1.1.2.4 $"
__date__ = "$Date: 2005/08/04 20:48:30 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os, log4py, signal
import os, logging, signal
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban")
def createDaemon():
"""Detach a process from the controlling terminal and run it in the
background as a daemon.
""" 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
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
"""
try:
@ -101,34 +101,6 @@ def createDaemon():
os.open("/dev/null", os.O_RDWR) # standard error (2)
return True
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
@ -151,6 +123,9 @@ def executeCmd(cmd, debug):
logSys.debug(cmd)
if not debug:
return os.system(cmd)
retval = os.system(cmd)
if not retval == 0:
logSys.error("'" + cmd + "' returned " + `retval`)
return retval
else:
return None

View File

@ -16,18 +16,18 @@
# Author: Cyril Jaquier
#
# $Revision: 1.1.2.1 $
# $Revision: 1.1.2.2 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.1.2.1 $"
__date__ = "$Date: 2005/07/12 13:09:47 $"
__version__ = "$Revision: 1.1.2.2 $"
__date__ = "$Date: 2005/08/01 16:35:18 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import log4py
import logging
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban")
def replaceTag(query, aInfo):
""" Replace tags in query

View File

@ -16,12 +16,12 @@
# Author: Cyril Jaquier
#
# $Revision: 1.12.2.4 $
# $Revision: 1.12.2.6 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.12.2.4 $"
__date__ = "$Date: 2005/07/23 09:31:12 $"
__version__ = "$Revision: 1.12.2.6 $"
__date__ = "$Date: 2005/08/06 15:07:11 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
version = "0.5.1"
version = "0.5.2"