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 ver. 0.5.1 (2005/07/23) - beta
---------- ----------
- Fixed bugs #1241756, #1239557 - Fixed bugs #1241756, #1239557

View File

@ -1,10 +1,15 @@
Metadata-Version: 1.0 Metadata-Version: 1.0
Name: fail2ban Name: fail2ban
Version: 0.5.1 Version: 0.5.2
Summary: Ban IPs that make too many password failure Summary: Ban IPs that make too many password failure
Home-page: http://fail2ban.sourceforge.net Home-page: http://fail2ban.sourceforge.net
Author: Cyril Jaquier Author: Cyril Jaquier
Author-email: lostcontrol@users.sourceforge.net Author-email: lostcontrol@users.sourceforge.net
License: UNKNOWN License: GPL
Description: UNKNOWN Description:
Platform: UNKNOWN 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 Fail2Ban scans log files like /var/log/pwdfail and bans IP
that makes too many password failures. It updates firewall that makes too many password failures. It updates firewall
rules to reject the IP address. These rules can be defined by rules to reject the IP address. These rules can be defined by
the user. Fail2Ban can read multiple log files such as sshd 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 This is my first Python program. Moreover, English is not my
mother tongue... mother tongue...
@ -55,12 +55,11 @@ Installation:
------------- -------------
Require: python-2.3 (http://www.python.org) Require: python-2.3 (http://www.python.org)
log4py-1.3 (http://sourceforge.net/projects/log4py)
To install, just do: To install, just do:
> tar xvfj fail2ban-0.5.1.tar.bz2 > tar xvfj fail2ban-0.5.2.tar.bz2
> cd fail2ban-0.5.1 > cd fail2ban-0.5.2
> python setup.py install > python setup.py install
This will install Fail2Ban into /usr/lib/fail2ban. The fail2ban 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 the value stored in fail2ban.conf. Here are the command line
options: options:
-b start fail2ban in background -b start in background
-d start fail2ban in debug mode -d start in debug mode
-c <FILE> read configuration file FILE -c <FILE> read configuration file FILE
-p <FILE> create PID lock in FILE -p <FILE> create PID lock in FILE
-h display this help message -h display this help message
-i <IP(s)> IP(s) to ignore -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 -r <VALUE> allow a max of VALUE password failure
-t <TIME> ban IP for TIME seconds -t <TIME> ban IP for TIME seconds
-v verbose. Use twice for greater effect -v verbose. Use twice for greater effect

1
TODO
View File

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

View File

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

View File

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

View File

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

View File

@ -16,20 +16,20 @@
# Author: Cyril Jaquier # Author: Cyril Jaquier
# #
# $Revision: 1.5.2.3 $ # $Revision: 1.5.2.5 $
__author__ = "Cyril Jaquier" __author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.5.2.3 $" __version__ = "$Revision: 1.5.2.5 $"
__date__ = "$Date: 2005/07/12 13:07:35 $" __date__ = "$Date: 2005/08/01 16:31:13 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import log4py import logging
from ConfigParser import * from ConfigParser import *
# Gets the instance of log4py. # Gets the instance of the logger.
logSys = log4py.Logger().get_instance() logSys = logging.getLogger("fail2ban")
class ConfigReader: class ConfigReader:
""" This class allow the handling of the configuration options. """ This class allow the handling of the configuration options.
@ -77,5 +77,9 @@ class ConfigReader:
except NoOptionError: except NoOptionError:
logSys.warn("No '" + option[1] + "' defined in '" + sec + "'") logSys.warn("No '" + option[1] + "' defined in '" + sec + "'")
values[option[1]] = option[2] 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 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 placed under /usr/share/fail2ban insteadh of /usr/lib/fail2ban to
comply with policy regarding architecture independent resources. 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 See the file TODO.Debian for more details, as well as the Debian Bug
Tracking system. 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 fail2ban (0.5.1-1) unstable; urgency=low
* New upstream release * 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. 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_installinit
# dh_installcron # dh_installcron
# dh_installinfo # dh_installinfo
dh_installman fail2ban.1x dh_installman fail2ban.1x man/fail2ban.conf.5
dh_link dh_link
dh_strip dh_strip
dh_compress dh_compress

View File

@ -18,31 +18,43 @@
# Author: Cyril Jaquier # Author: Cyril Jaquier
# #
# $Revision: 1.4.2.3 $ # $Revision: 1.4.2.5 $
__author__ = "Cyril Jaquier" __author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.4.2.3 $" __version__ = "$Revision: 1.4.2.5 $"
__date__ = "$Date: 2005/07/15 14:11:21 $" __date__ = "$Date: 2005/08/04 20:51:14 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __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.
# Appends our own modules path. Added before log4py import sys.path.append("/usr/lib/fail2ban")
# because log4py could be distributed with Fail2Ban.
path.append('/usr/share/fail2ban')
# Checks for required libs # Now we can import our modules.
# Checks if log4py is present.
try:
import log4py
except:
print "log4py is needed (see README)"
exit(-1)
# Now we can import our module
import fail2ban import fail2ban
from utils.pidlock import PIDLock
# Start the application # Get the instance of the logger.
fail2ban.main() 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 # Author: Cyril Jaquier
# #
# $Revision: 1.20.2.8 $ # $Revision: 1.20.2.13 $
__author__ = "Cyril Jaquier" __author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.20.2.8 $" __version__ = "$Revision: 1.20.2.13 $"
__date__ = "$Date: 2005/07/22 21:13:19 $" __date__ = "$Date: 2005/08/06 18:44:06 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import time, sys, getopt, os, string, signal, log4py import time, sys, getopt, os, string, signal, logging, logging.handlers
from ConfigParser import * from ConfigParser import *
from version import version from version import version
@ -32,11 +32,15 @@ from firewall.firewall import Firewall
from logreader.logreader import LogReader from logreader.logreader import LogReader
from confreader.configreader import ConfigReader from confreader.configreader import ConfigReader
from utils.mail import Mail from utils.mail import Mail
from utils.pidlock import PIDLock
from utils.dns import * from utils.dns import *
from utils.process import * from utils.process import *
# Gets the instance of log4py. # Get the instance of the logger.
logSys = log4py.Logger().get_instance() logSys = logging.getLogger("fail2ban")
# Get PID lock file instance
pidLock = PIDLock()
# Global variables # Global variables
logFwList = list() logFwList = list()
@ -50,13 +54,13 @@ def dispUsage():
print "Fail2Ban v"+version+" reads log file that contains password failure report" print "Fail2Ban v"+version+" reads log file that contains password failure report"
print "and bans the corresponding IP addresses using firewall rules." print "and bans the corresponding IP addresses using firewall rules."
print print
print " -b start fail2ban in background" print " -b start in background"
print " -d start fail2ban in debug mode" print " -d start in debug mode"
print " -c <FILE> read configuration file FILE" print " -c <FILE> read configuration file FILE"
print " -p <FILE> create PID lock in FILE" print " -p <FILE> create PID lock in FILE"
print " -h display this help message" print " -h display this help message"
print " -i <IP(s)> IP(s) to ignore" 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 " -r <VALUE> allow a max of VALUE password failure"
print " -t <TIME> ban IP for TIME seconds" print " -t <TIME> ban IP for TIME seconds"
print " -v verbose. Use twice for greater effect" print " -v verbose. Use twice for greater effect"
@ -101,8 +105,9 @@ def killApp():
# Execute global start command # Execute global start command
executeCmd(conf["cmdend"], conf["debug"]) executeCmd(conf["cmdend"], conf["debug"])
# Remove the PID lock # Remove the PID lock
removePID(conf["pidlock"]) pidLock.remove()
logSys.info("Exiting...") logSys.info("Exiting...")
logging.shutdown()
sys.exit(0) sys.exit(0)
def getCmdLineOptions(optList): def getCmdLineOptions(optList):
@ -128,23 +133,23 @@ def getCmdLineOptions(optList):
if opt[0] == "-i": if opt[0] == "-i":
conf["ignoreip"] = opt[1] conf["ignoreip"] = opt[1]
if opt[0] == "-r": if opt[0] == "-r":
conf["retrymax"] = int(opt[1]) conf["maxretry"] = int(opt[1])
if opt[0] == "-p": if opt[0] == "-p":
conf["pidlock"] = opt[1] conf["pidlock"] = opt[1]
if opt[0] == "-k": if opt[0] == "-k":
pid = checkForPID(conf["pidlock"]) conf["kill"] = True
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)
def main(): def main():
""" Fail2Ban main function """ 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["verbose"] = 0
conf["conffile"] = "/etc/fail2ban.conf" conf["conffile"] = "/etc/fail2ban.conf"
@ -169,7 +174,7 @@ def main():
# Options # Options
optionValues = (["bool", "background", False], optionValues = (["bool", "background", False],
["str", "logtargets", "STDOUT /var/log/fail2ban.log"], ["str", "logtargets", "/var/log/fail2ban.log"],
["bool", "debug", False], ["bool", "debug", False],
["str", "pidlock", "/var/run/fail2ban.pid"], ["str", "pidlock", "/var/run/fail2ban.pid"],
["int", "maxretry", 3], ["int", "maxretry", 3],
@ -185,46 +190,22 @@ def main():
# Gets command line options # Gets command line options
getCmdLineOptions(optList) getCmdLineOptions(optList)
# Process some options # PID lock
# Log targets pidLock.setPath(conf["pidlock"])
# Bug fix for #1234699
os.umask(0077) # Now we can kill properly a running instance if needed
# 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)
else:
# Target should be a file
try: try:
open(target, "a") conf["kill"]
logSys.add_target(target) pid = pidLock.exists()
except IOError: if pid:
logSys.error("Unable to log to " + target) killPID(int(pid))
logSys.warn("Killed Fail2Ban with PID "+pid)
# Check if at least one target exists sys.exit(0)
if len(logSys.get_targets()) == 0: else:
logSys.add_target(log4py.TARGET_SYS_STDOUT) logSys.error("No running Fail2Ban found")
logSys.error("No valid logging target found. Logging to STDOUT") sys.exit(-1)
except KeyError:
# Verbose level pass
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")
# Start Fail2Ban in daemon mode # Start Fail2Ban in daemon mode
if conf["background"]: if conf["background"]:
@ -234,9 +215,54 @@ def main():
logSys.error("Unable to start daemon") logSys.error("Unable to start daemon")
sys.exit(-1) 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 # Ignores IP list
ignoreIPList = conf["ignoreip"].split(' ') ignoreIPList = conf["ignoreip"].split(' ')
# maxretry option
maxRetry = conf["maxretry"]
# bantime option
banTime = conf["bantime"]
# Checks for root user. This is necessary because log files # Checks for root user. This is necessary because log files
# are owned by root and firewall needs root access. # are owned by root and firewall needs root access.
if not checkForRoot(): if not checkForRoot():
@ -245,16 +271,16 @@ def main():
sys.exit(-1) sys.exit(-1)
# Checks that no instance of Fail2Ban is currently running. # Checks that no instance of Fail2Ban is currently running.
pid = checkForPID(conf["pidlock"]) pid = pidLock.exists()
if pid: if pid:
logSys.error("Fail2Ban already running with PID "+pid) logSys.error("Fail2Ban already running with PID "+pid)
sys.exit(-1) sys.exit(-1)
else: else:
createPID(conf["pidlock"]) pidLock.create()
logSys.debug("ConfFile is "+conf["conffile"]) logSys.debug("ConfFile is " + conf["conffile"])
logSys.debug("BanTime is "+`conf["bantime"]`) logSys.debug("BanTime is " + `conf["bantime"]`)
logSys.debug("retryAllowed is "+`conf["maxretry"]`) logSys.debug("retryAllowed is " + `conf["maxretry"]`)
# Options # Options
optionValues = (["bool", "enabled", False], optionValues = (["bool", "enabled", False],
@ -277,8 +303,10 @@ def main():
logSys.debug("to: " + mailConf["to"] + " from: " + mailConf["from"]) logSys.debug("to: " + mailConf["to"] + " from: " + mailConf["from"])
# Options # Options
optionValues = (["bool", "enabled", True], optionValues = (["bool", "enabled", False],
["str", "logfile", "/dev/null"], ["str", "logfile", "/dev/null"],
["int", "maxretry", None],
["int", "bantime", None],
["str", "timeregex", ""], ["str", "timeregex", ""],
["str", "timepattern", ""], ["str", "timepattern", ""],
["str", "failregex", ""], ["str", "failregex", ""],
@ -291,11 +319,19 @@ def main():
for t in confReader.getSections(): for t in confReader.getSections():
l = confReader.getLogOptions(t, optionValues) l = confReader.getLogOptions(t, optionValues)
if l["enabled"]: 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 # Creates a logreader object
lObj = LogReader(l["logfile"], l["timeregex"], l["timepattern"], lObj = LogReader(l["logfile"], l["timeregex"], l["timepattern"],
l["failregex"], conf["bantime"]) l["failregex"], maxRetry, banTime)
# Creates a firewall object # 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 # Links them into a list. I'm not really happy
# with this :/ # with this :/
logFwList.append([t, lObj, fObj, dict(), l]) logFwList.append([t, lObj, fObj, dict(), l])
@ -311,7 +347,7 @@ def main():
for element in logFwList: for element in logFwList:
element[1].addIgnoreIP(ip) element[1].addIgnoreIP(ip)
logSys.info("Fail2Ban v"+version+" is running") logSys.info("Fail2Ban v" + version + " is running")
# Execute global start command # Execute global start command
executeCmd(conf["cmdstart"], conf["debug"]) executeCmd(conf["cmdstart"], conf["debug"])
# Execute start command of each section # Execute start command of each section
@ -363,7 +399,7 @@ def main():
failTime = fails[attempt][1] failTime = fails[attempt][1]
if failTime < unixTime - findTime: if failTime < unixTime - findTime:
del element[3][attempt] del element[3][attempt]
elif fails[attempt][0] >= conf["maxretry"]: elif fails[attempt][0] >= element[1].getMaxRetry():
aInfo = {"ip": attempt, aInfo = {"ip": attempt,
"failures": element[3][attempt][0], "failures": element[3][attempt][0],
"failtime": failTime} "failtime": failTime}

View File

@ -16,21 +16,21 @@
# Author: Cyril Jaquier # Author: Cyril Jaquier
# #
# $Revision: 1.8.2.5 $ # $Revision: 1.8.2.6 $
__author__ = "Cyril Jaquier" __author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.8.2.5 $" __version__ = "$Revision: 1.8.2.6 $"
__date__ = "$Date: 2005/07/15 14:07:08 $" __date__ = "$Date: 2005/08/01 16:31:42 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import time, os, log4py, re import time, os, logging, re
from utils.process import executeCmd from utils.process import executeCmd
from utils.strings import replaceTag from utils.strings import replaceTag
# Gets the instance of log4py. # Gets the instance of the logger.
logSys = log4py.Logger().get_instance() logSys = logging.getLogger("fail2ban")
class Firewall: class Firewall:
""" Manages the ban list and executes the command that ban """ 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 # Author: Cyril Jaquier
# #
# $Revision: 1.13.2.4 $ # $Revision: 1.13.2.7 $
__author__ = "Cyril Jaquier" __author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.13.2.4 $" __version__ = "$Revision: 1.13.2.7 $"
__date__ = "$Date: 2005/07/23 09:07:53 $" __date__ = "$Date: 2005/08/06 18:43:11 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import os, sys, time, re, log4py import os, sys, time, re, logging
from utils.dns import * from utils.dns import *
# Gets the instance of log4py. # Gets the instance of the logger.
logSys = log4py.Logger().get_instance() logSys = logging.getLogger("fail2ban")
class LogReader: class LogReader:
""" Reads a log file and reports information about IP that make password """ 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, def __init__(self, logPath, timeregex, timepattern, failregex,
findTime = 3600): maxRetry, findTime):
self.logPath = logPath self.logPath = logPath
self.maxRetry = maxRetry
self.timeregex = timeregex self.timeregex = timeregex
self.timepattern = timepattern self.timepattern = timepattern
self.failregex = failregex self.failregex = failregex
@ -50,6 +51,11 @@ class LogReader:
self.lastDate = 0 self.lastDate = 0
self.logStats = None self.logStats = None
def getMaxRetry(self):
""" Gets the maximum number of failures
"""
return self.maxRetry
def getFindTime(self): def getFindTime(self):
""" Gets the find time. """ Gets the find time.
""" """
@ -83,7 +89,7 @@ class LogReader:
fileHandler = open(self.logPath) fileHandler = open(self.logPath)
except OSError: except OSError:
logSys.error("Unable to open "+self.logPath) logSys.error("Unable to open "+self.logPath)
sys.exit(-1)
return fileHandler return fileHandler
def isModified(self): def isModified(self):
@ -93,7 +99,6 @@ class LogReader:
self.logStats = os.stat(self.logPath) self.logStats = os.stat(self.logPath)
except OSError: except OSError:
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: if self.lastModTime == self.logStats.st_mtime:
return False return False
@ -130,7 +135,7 @@ class LogReader:
logFile = self.openLogFile() logFile = self.openLogFile()
self.setFilePos(logFile) self.setFilePos(logFile)
lastLine = '' lastLine = ''
for line in logFile.readlines(): for line in logFile:
lastLine = line lastLine = line
failList = self.findFailure(line) failList = self.findFailure(line)
for element in failList: 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 # Author: Cyril Jaquier
# #
# $Revision: 1.4.2.2 $ # $Revision: 1.4.2.3 $
__author__ = "Cyril Jaquier" __author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.4.2.2 $" __version__ = "$Revision: 1.4.2.3 $"
__date__ = "$Date: 2005/07/15 14:14:12 $" __date__ = "$Date: 2005/07/28 20:30:34 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
@ -31,15 +31,25 @@ from version import version
from os.path import isfile, join from os.path import isfile, join
from sys import exit, argv 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( setup(
name = "fail2ban", name = "fail2ban",
version = version, version = version,
description = "Ban IPs that make too many password failure", description = "Ban IPs that make too many password failure",
long_description = longdesc,
author = "Cyril Jaquier", author = "Cyril Jaquier",
author_email = "lostcontrol@users.sourceforge.net", author_email = "lostcontrol@users.sourceforge.net",
url = "http://fail2ban.sourceforge.net", url = "http://fail2ban.sourceforge.net",
license = "GPL",
platforms = "Posix",
scripts = ['fail2ban'], scripts = ['fail2ban'],
py_modules = ['fail2ban', 'version', 'log4py'], py_modules = ['fail2ban', 'version'],
packages = ['firewall', 'logreader', 'confreader', 'utils'] packages = ['firewall', 'logreader', 'confreader', 'utils']
) )

View File

@ -16,20 +16,20 @@
# Author: Cyril Jaquier # Author: Cyril Jaquier
# #
# $Revision: 1.1.2.1 $ # $Revision: 1.1.2.2 $
__author__ = "Cyril Jaquier" __author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.1.2.1 $" __version__ = "$Revision: 1.1.2.2 $"
__date__ = "$Date: 2005/07/12 13:09:47 $" __date__ = "$Date: 2005/08/01 16:35:18 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import log4py, smtplib import logging, smtplib
from utils.strings import replaceTag from utils.strings import replaceTag
# Gets the instance of log4py. # Gets the instance of the logger.
logSys = log4py.Logger().get_instance() logSys = logging.getLogger("fail2ban")
class Mail: class Mail:
""" Mailer class """ 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,21 +16,21 @@
# Author: Cyril Jaquier # Author: Cyril Jaquier
# #
# $Revision: 1.1.2.2 $ # $Revision: 1.1.2.4 $
__author__ = "Cyril Jaquier" __author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.1.2.2 $" __version__ = "$Revision: 1.1.2.4 $"
__date__ = "$Date: 2005/07/15 14:08:17 $" __date__ = "$Date: 2005/08/04 20:48:30 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import os, log4py, signal import os, logging, signal
# Gets the instance of log4py. # Gets the instance of the logger.
logSys = log4py.Logger().get_instance() logSys = logging.getLogger("fail2ban")
def createDaemon(): def createDaemon():
"""Detach a process from the controlling terminal and run it in the """ Detach a process from the controlling terminal and run it in the
background as a daemon. background as a daemon.
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
@ -102,34 +102,6 @@ def createDaemon():
return True 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): def killPID(pid):
""" Kills the process with the given PID using the """ Kills the process with the given PID using the
INT signal (same effect as <ctrl>+<c>). INT signal (same effect as <ctrl>+<c>).
@ -151,6 +123,9 @@ def executeCmd(cmd, debug):
logSys.debug(cmd) logSys.debug(cmd)
if not debug: if not debug:
return os.system(cmd) retval = os.system(cmd)
if not retval == 0:
logSys.error("'" + cmd + "' returned " + `retval`)
return retval
else: else:
return None return None

View File

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

View File

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