Merged with 0.5.0 upstream release

debian-releases/etch
Yaroslav Halchenko 2005-07-13 10:01:01 +00:00
parent 5930368ed9
commit 61e23f45f7
22 changed files with 884 additions and 674 deletions

View File

@ -4,10 +4,25 @@
|_| \__,_|_|_/___|_.__/\__,_|_||_|
=============================================================
Fail2Ban (version 0.4.1) 06/30/2005
Fail2Ban (version 0.5.0) 2005/07/12
=============================================================
ver. 0.4.1 (06/30/2005) - stable
ver. 0.5.0 (2005/07/12) - beta
----------
- Added support for CIDR mask in ignoreip
- Added mail notification support
- Fixed bug #1234699
- Added tags replacement in rules definition. Should allow a
clean solution for Feature Request #1229479
- Removed "interface" and "firewall" options
- Added start and end commands in the configuration file.
Thanks to Yaroslav Halchenko
- Added firewall rules definition in the configuration file
- Cleaned fail2ban.py
- Added an initd script for RedHat/Fedora. Thanks to Andrey
G. Grozin
ver. 0.4.1 (2005/06/30) - stable
----------
- Fixed textToDNS method which generated wrong matches for
"rhost=12-xyz...". Thanks to Tom Pike
@ -16,19 +31,19 @@ ver. 0.4.1 (06/30/2005) - stable
- Changed default PID lock file location from /tmp to
/var/run
ver. 0.4.0 (04/24/2005) - stable
ver. 0.4.0 (2005/04/24) - stable
----------
- Fixed textToDNS which did not recognize strings like
"12-345-67-890.abcd.mnopqr.xyz"
ver. 0.3.1 (03/31/2005) - beta
ver. 0.3.1 (2005/03/31) - beta
----------
- Corrected level of messages
- Added DNS lookup support
- Improved parsing speed. Only parse the new log messages
- Added a second verbose level (-vv)
ver. 0.3.0 (02/24/2005) - beta
ver. 0.3.0 (2005/02/24) - beta
----------
- Re-writting of parts of the code in order to handle several
log files with different rules
@ -39,7 +54,7 @@ ver. 0.3.0 (02/24/2005) - beta
- Added ipfw-start-rule option (thanks to Robert Edeker)
- Added -k option which kills a currently running Fail2Ban
ver. 0.1.2 (11/21/2004) - beta
ver. 0.1.2 (2004/11/21) - beta
----------
- Add ipfw and ipfwadm support. The rules are taken from
BlockIt. Thanks to Robert Edeker
@ -47,7 +62,7 @@ ver. 0.1.2 (11/21/2004) - beta
Robert Edeker who reminded me this
- Small code cleaning
ver. 0.1.1 (10/23/2004) - beta
ver. 0.1.1 (2004/10/23) - beta
----------
- Add SIGTERM handler in order to exit nicely when in daemon
mode
@ -61,6 +76,6 @@ ver. 0.1.1 (10/23/2004) - beta
- Code documentation
ver. 0.1.0 (10/12/2004) - alpha
ver. 0.1.0 (2004/10/12) - alpha
----------
- Initial release

View File

@ -2,15 +2,11 @@
#
DESTDIR=debian/fail2ban
all:: fail2ban fail2ban.1x
all:: fail2ban.1x
fail2ban.1x: fail2ban fail2ban.h2m
help2man --include fail2ban.h2m --section=1x --no-info --output $@ ./fail2ban
fail2ban: fail2ban.py
cp fail2ban.py fail2ban
install:: all
mkdir -p $(DESTDIR)/etc/default
@ -18,7 +14,7 @@ install:: all
cp config/fail2ban.conf.default $(DESTDIR)/etc/fail2ban.conf
cp config/gentoo-confd $(DESTDIR)/etc/default/fail2ban
mkdir -p $(DESTDIR)/usr/lib/fail2ban/
cp log4py.py $(DESTDIR)/usr/lib/fail2ban/
# cp log4py.py $(DESTDIR)/usr/lib/fail2ban/
clean::
rm -rf changelog.gz fail2ban{,.1x} build* `find -iname '*.pyc' `

View File

@ -1,8 +1,8 @@
Metadata-Version: 1.0
Name: fail2ban
Version: 0.4.1
Version: 0.5.0
Summary: Ban IPs that make too many password failure
Home-page: http://www.sourceforge.net/projects/fail2ban
Home-page: http://fail2ban.sourceforge.net
Author: Cyril Jaquier
Author-email: lostcontrol@users.sourceforge.net
License: UNKNOWN

39
README
View File

@ -4,14 +4,14 @@
|_| \__,_|_|_/___|_.__/\__,_|_||_|
=============================================================
Fail2Ban (version 0.4.1) 06/30/2005
Fail2Ban (version 0.5.0) 2005/07/12
=============================================================
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. Currently iptables, ipfw and
ipfwadm are supported. Fail2Ban can read multiple log files
such as sshd or Apache web server ones. It needs log4py.
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.
This is my first Python program. Moreover, English is not my
mother tongue...
@ -36,18 +36,19 @@ tries to find lines which match the failregex. Then it
retrieves the message time using timeregex and timepattern.
It finally gets the ip and if it has already done 3 or more
password failures in the last banTime, the ip is banned for
banTime using a firewall rule. After banTime, the rule is
deleted. Notice that if no "plain" ip is available, Fail2Ban
try to do DNS lookup in order to found one or several ip's to
ban.
banTime using a firewall rule. This rule is set by the user
in the configuration file. Thus, Fail2Ban can be adapted for
lots of firewall. After banTime, the rule is deleted. Notice
that if no "plain" ip is available, Fail2Ban try to do DNS
lookup in order to found one or several ip's to ban.
Sections can be freely added so it is possible to monitor
several daemons at the same time.
Runs on my server and does its job rather well :-) The idea
is to make fail2ban usable with daemons and services that
require a login (sshd, telnetd, ...). It should also support
others firewalls than iptables.
require a login (sshd, telnetd, ...) and with different
firewalls.
Installation:
@ -58,14 +59,15 @@ Require: python-2.3 (http://www.python.org)
To install, just do:
> tar xvfj fail2ban-0.4.1.tar.bz2
> cd fail2ban-0.4.1
> tar xvfj fail2ban-0.5.0.tar.bz2
> cd fail2ban-0.5.0
> python setup.py install
This will install Fail2Ban into /usr/lib/fail2ban. The
fail2ban.py executable is placed into /usr/bin.
For Gentoo users, an ebuild is available on the website.
Gentoo: an ebuild is available on the website.
Debian: a package is available on the website.
Fail2Ban should now be correctly installed. Just type:
@ -93,18 +95,16 @@ options:
-b start fail2ban in background
-d start fail2ban in debug mode
-e <INTF> ban IP on the INTF interface
-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
-l <FILE> log message in FILE
-l <FILE> log messages in FILE
-r <VALUE> allow a max of VALUE password failure
-t <TIME> ban IP for TIME seconds
-v verbose. Use twice for greater effect
-w <FIWA> select the firewall to use. Can be iptables,
ipfwadm or ipfw
-V print software version
Contact:
--------
@ -112,7 +112,7 @@ Contact:
You need some new features, you found bugs or you just
appreciate this program, you can contact me at :
Website: http://www.sourceforge.net/projects/fail2ban
Website: http://fail2ban.sourceforge.net
Cyril Jaquier: <lostcontrol@users.sourceforge.net>
@ -121,8 +121,7 @@ Thanks:
-------
Kévin Drapel, Marvin Rouge, Sireyessire, Robert Edeker,
Tom Pike, Iain Lea
Tom Pike, Iain Lea, Andrey G. Grozin, Yaroslav Halchenko
License:
--------

View File

@ -1,22 +1,10 @@
# Fail2Ban configuration file
#
# $Revision: 1.8 $
# $Revision: 1.8.2.5 $
#
# 2005.06.21 modified for readability Iain Lea iain@bricbrac.de
[DEFAULT]
# Option: firewall
# Notes.: select the firewall system to use.
# Values: [iptables | ipfwadm | ipfw] Default: iptables
#
firewall = iptables
# Option: ipfw-start-rule
# Notes.: set first firewall rule number used (only used if firewall = ipfw).
# Values: NUM Default: 100
#
ipfw-start-rule = 100
# Option: background
# Notes.: start fail2ban as a daemon. Output is redirect to logfile.
# Values: [true | false] Default: false
@ -54,17 +42,24 @@ maxretry = 5
bantime = 600
# Option: ignoreip
# Notes.: space separated list of IP's to be ignored by fail2ban
# Example: ignoreip = 192.168.0.1 123.45.235.65
# Values: IP Default:
# Notes.: space separated list of IP's to be ignored by fail2ban.
# You can use CIDR mask in order to specify a range.
# Example: ignoreip = 192.168.0.1/24 123.45.235.65
# Values: IP Default: 192.168.0.0/24
#
ignoreip =
ignoreip = 192.168.0.0/24
# Option: interface
# Notes.: interface name on which the IP will be banned.
# Values: INT Default: eth0
# Option: cmdstart
# Notes.: command executed once at the start of Fail2Ban
# Values: CMD Default:
#
interface = eth0
cmdstart =
# Option: cmdend
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD Default:
#
cmdend =
# Option: polltime
# Notes.: number of seconds fail2ban sleeps between iterations.
@ -72,9 +67,64 @@ interface = eth0
#
polltime = 1
[MAIL]
# Option: enabled
# Notes.: enable mail notification when banning an IP address.
# Values: [true | false] Default: false
#
enabled = false
# Option: host
# Notes.: host running the mail server.
# Values: STR Default: localhost
#
host = localhost
# Option: port
# Notes.: port of the mail server.
# Values: INT Default: 25
#
port = 25
# Option: from
# Notes.: e-mail address of the sender.
# Values: MAIL Default: fail2ban
#
from = fail2ban
# Option: to
# Notes.: e-mail address of the receiver.
# Values: MAIL Default: root
#
to = root
# Option: subject
# Notes.: subject of the e-mail.
# Tags: <ip> IP address
# <failures> number of failures
# <failtime> unix timestamp of the last failure
# Values: TEXT Default: [Fail2Ban] Banned <ip>
#
subject = [Fail2Ban] Banned <ip>
# Option: message
# Notes.: message of the e-mail.
# Tags: <ip> IP address
# <failures> number of failures
# <failtime> unix timestamp of the last failure
# <br> new line
# Values: TEXT Default:
#
message = Hi,<br>
The IP <ip> has just been banned by Fail2Ban after
<failures> attempts.<br>
Regards,<br>
Fail2Ban
# You can define a new section for each log file to check for
# password failure. Each section has to define the following
# options: logfile, timeregex, timepattern, failregex.
# options: logfile, fwban, fwunban, timeregex, timepattern,
# failregex.
[Apache]
# Option: enabled
@ -89,10 +139,45 @@ enabled = false
#
logfile = /var/log/apache/access.log
# Option: fwstart
# Notes.: command executed once at the start of Fail2Ban
# Values: CMD Default:
#
fwstart =
# Option: fwend
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD Default:
#
fwend =
# Option: fwban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# <failures> number of failures
# <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
#
fwban = iptables -I INPUT 1 -i eth0 -s <ip> -j DROP
# Option: fwunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# <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
#
fwunban = iptables -D INPUT -i eth0 -s <ip> -j DROP
# Option: timeregex
# Notes.: regex to match timestamp in Apache logfile.
# Values: [Wed Jan 05 15:08:01 2005]
# Default \S{3} \S{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}
# Default: \S{3} \S{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}
#
timeregex = \S{3} \S{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}
@ -122,10 +207,45 @@ enabled = true
#
logfile = /var/log/auth.log
# Option: fwstart
# Notes.: command executed once at the start of Fail2Ban
# Values: CMD Default:
#
fwstart =
# Option: fwend
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD Default:
#
fwend =
# Option: fwbanrule
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# <failures> number of failures
# <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
#
fwban = iptables -I INPUT 1 -i eth0 -s <ip> -j DROP
# Option: fwunbanrule
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# <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
#
fwunban = iptables -D INPUT -i eth0 -s <ip> -j DROP
# Option: timeregex
# Notes.: regex to match timestamp in SSH logfile.
# Values: [Mar 7 17:53:28]
# Default \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}
# Default: \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}
#
timeregex = \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}

View File

@ -17,11 +17,11 @@
#
# Author: Sireyessire, Cyril Jaquier
#
# $Revision: 1.1 $
# $Revision: 1.1.2.1 $
opts="start stop restart showlog"
FAIL2BAN="/usr/bin/fail2ban.py"
FAIL2BAN="/usr/bin/fail2ban"
depend() {
need net

78
config/redhat-initd Normal file
View File

@ -0,0 +1,78 @@
#!/bin/bash
#
# fail2ban
#
# chkconfig: 345 91 9
# description: if many unsuccessfull login attempts from some ip address \
# during a short period happen, this address is banned \
# by the firewall
#
# Author: Andrey G. Grozin
#
# $Revision: 1.1.2.1 $
# Source function library.
. /etc/init.d/functions
# Get config.
. /etc/sysconfig/network
# Check that networking is up.
[ "${NETWORKING}" = "no" ] && exit 0
[ -f /etc/fail2ban.conf ] || exit 0
FAIL2BAN="/usr/bin/fail2ban"
PIDFILE="/var/run/fail2ban.pid"
RETVAL=0
start() {
echo -n $"Starting fail2ban: "
"${FAIL2BAN}" -b
RETVAL=$?
echo
}
stop() {
if [ -f "${PIDFILE}" ]; then
echo -n $"Stopping fail2ban: "
"${FAIL2BAN}" -k
echo
fi
}
restart() {
stop
start
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status fail2ban
RETVAL=$?
;;
reload)
restart
;;
restart)
restart
;;
condrestart)
if [ -f "${PIDFILE}" ]; then
restart
fi
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart}"
exit 1
;;
esac
exit $RETVAL

View File

@ -16,36 +16,30 @@
# Author: Cyril Jaquier
#
# $Revision: 1.5 $
# $Revision: 1.5.2.3 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.5 $"
__date__ = "$Date: 2005/03/06 17:45:55 $"
__version__ = "$Revision: 1.5.2.3 $"
__date__ = "$Date: 2005/07/12 13:07:35 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import log4py
from ConfigParser import *
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
class ConfigReader:
""" This class allow the handling of the configuration options.
The DEFAULT section contains the global information about
Fail2Ban. Each other section is for a different log file.
"""
# Each optionValues entry is composed of an array with:
# 0 -> the type of the option
# 1 -> the name of the option
# 2 -> the default value for the option
optionValues = (["bool", "enabled", True],
["str", "logfile", "/dev/null"],
["str", "timeregex", ""],
["str", "timepattern", ""],
["str", "failregex", ""])
def __init__(self, logSys, confPath):
def __init__(self, confPath):
self.confPath = confPath
self.configParser = SafeConfigParser()
self.logSys = logSys
def openConf(self):
""" Opens the configuration file.
@ -54,16 +48,23 @@ class ConfigReader:
def getSections(self):
""" Returns all the sections present in the configuration
file except the DEFAULT section.
file except the DEFAULT and MAIL sections.
"""
return self.configParser.sections()
def getLogOptions(self, sec):
sections = self.configParser.sections()
sections.remove("MAIL")
logSys.debug("Found sections: " + `sections`)
return sections
# Each optionValues entry is composed of an array with:
# 0 -> the type of the option
# 1 -> the name of the option
# 2 -> the default value for the option
def getLogOptions(self, sec, options):
""" Gets all the options of a given section. The options
are defined in the optionValues list.
"""
values = dict()
for option in self.optionValues:
for option in options:
try:
if option[0] == "bool":
v = self.configParser.getboolean(sec, option[1])
@ -74,7 +75,7 @@ class ConfigReader:
values[option[1]] = v
except NoOptionError:
self.logSys.warn("No '"+option[1]+"' defined in '"+sec+"'")
logSys.warn("No '" + option[1] + "' defined in '" + sec + "'")
values[option[1]] = option[2]
return values

11
debian/README.Debian vendored
View File

@ -1,12 +1,13 @@
fail2ban for Debian
-------------------
This package is nearly 100% identical to the upstream version. It was merely
packaged to be installed on a Debian system.
This package is nearly 100% identical to the upstream version. It was
merely packaged to be installed on a Debian system.
Module log4py installed into lib/fail2ban directory because there is no package for not-developed-in-a-long-time fail2ban
Module log4py installed into lib/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.
See the file TODO.Debian for more details, as well as the Debian Bug
Tracking system.
-- Yaroslav Halchenko <debian@onerussian.com>, Tue, 4 Jul 2005 00:00:00 -1000

5
debian/TODO vendored
View File

@ -1,3 +1,4 @@
Because this is just a quick hack to package fail2ban it might be missing some crucial parts...
Because this is just a quick hack to package fail2ban it might be missing
some crucial parts...
-- yoh@onerussian.com
-- debian@onerussian.com

7
debian/changelog vendored
View File

@ -1,3 +1,10 @@
fail2ban (0.5.0-1) unstable; urgency=low
* New upstream release
* Corrections to the description of the package
-- Yaroslav Halchenko <debian@onerussian.com> Tue, 12 Jul 2005 23:33:20 -1000
fail2ban (0.4.1-1) unstable; urgency=low
* First upstream release of a Debian package

48
firewall/iptables.py → fail2ban Normal file → Executable file
View File

@ -1,3 +1,5 @@
#!/usr/bin/env python
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
@ -16,33 +18,31 @@
# Author: Cyril Jaquier
#
# $Revision: 1.5 $
# $Revision: 1.4.2.2 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.5 $"
__date__ = "$Date: 2004/11/06 14:02:07 $"
__version__ = "$Revision: 1.4.2.2 $"
__date__ = "$Date: 2005/07/08 10:21:52 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from firewall import Firewall
from sys import exit, path
class Iptables(Firewall):
""" This class contains specific methods and variables for the
iptables firewall. Must implements the 'abstracts' methods
banIP(ip) and unBanIP(ip).
Must adds abstract methods definition:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266468
"""
def banIP(self, ip):
""" Returns query to ban IP.
"""
query = "iptables -I INPUT 1 -i "+self.interface+" -s "+ip+" -j DROP"
return query
def unBanIP(self, ip):
""" Returns query to unban IP.
"""
query = "iptables -D INPUT -i "+self.interface+" -s "+ip+" -j DROP"
return query
#yoh: We do need to load this path first if we ship log4py with fail2ban
# Appends our own modules path
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
import fail2ban
# Start the application
fail2ban.main()

View File

@ -1,5 +1,3 @@
#!/usr/bin/env python
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
@ -18,37 +16,34 @@
# Author: Cyril Jaquier
#
# $Revision: 1.20 $
# $Revision: 1.20.2.5 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.20 $"
__date__ = "$Date: 2005/06/30 09:26:38 $"
__version__ = "$Revision: 1.20.2.5 $"
__date__ = "$Date: 2005/07/12 13:11:58 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import time, sys, getopt, os, signal, string
import time, sys, getopt, os, string, signal, log4py
from ConfigParser import *
# Appends our own modules path
# you: moved before loading log4py so we add path to it
sys.path.append('/usr/lib/fail2ban')
# Checks if log4py is present.
try:
import log4py
except:
print "log4py is needed (see README)"
sys.exit(-1)
from firewall.iptables import Iptables
from firewall.ipfw import Ipfw
from firewall.ipfwadm import Ipfwadm
from firewall.firewall import Firewall
from logreader.logreader import LogReader
from confreader.configreader import ConfigReader
from utils.process import *
from utils.mail import Mail
from version import version
def usage():
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
# Global variables
logFwList = list()
conf = dict()
def dispUsage():
""" Prints Fail2Ban command line options and exits
"""
print "Usage: "+sys.argv[0]+" [OPTIONS]"
print
print "Fail2Ban v"+version+" reads log file that contains password failure report"
@ -56,22 +51,26 @@ def usage():
print
print " -b start fail2ban in background"
print " -d start fail2ban in debug mode"
print " -e <INTF> ban IP on the INTF interface"
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 " -l <FILE> log message in FILE"
print " -l <FILE> log messages in FILE"
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"
print " -w <FIWA> select the firewall to use. Can be iptables,"
print " ipfwadm or ipfw"
print " -V print software version"
print
print "Report bugs to <lostcontrol@users.sourceforge.net>"
sys.exit(0)
def dispVersion():
""" Prints Fail2Ban version and exits
"""
print sys.argv[0]+" "+version
sys.exit(0)
def checkForRoot():
""" Check for root user.
"""
@ -81,304 +80,46 @@ def checkForRoot():
else:
return False
def createDaemon():
"""Detach a process from the controlling terminal and run it in the
background as a daemon.
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
"""
try:
# Fork a child process so the parent can exit. This will return control
# to the command line or shell. This is required so that the new process
# is guaranteed not to be a process group leader. We have this guarantee
# because the process GID of the parent is inherited by the child, but
# the child gets a new PID, making it impossible for its PID to equal its
# PGID.
pid = os.fork()
except OSError, e:
return((e.errno, e.strerror)) # ERROR (return a tuple)
if (pid == 0): # The first child.
# Next we call os.setsid() to become the session leader of this new
# session. The process also becomes the process group leader of the
# new process group. Since a controlling terminal is associated with a
# session, and this new session has not yet acquired a controlling
# terminal our process now has no controlling terminal. This shouldn't
# fail, since we're guaranteed that the child is not a process group
# leader.
os.setsid()
# When the first child terminates, all processes in the second child
# are sent a SIGHUP, so it's ignored.
signal.signal(signal.SIGHUP, signal.SIG_IGN)
try:
# Fork a second child to prevent zombies. Since the first child is
# a session leader without a controlling terminal, it's possible for
# it to acquire one by opening a terminal in the future. This second
# fork guarantees that the child is no longer a session leader, thus
# preventing the daemon from ever acquiring a controlling terminal.
pid = os.fork() # Fork a second child.
except OSError, e:
return((e.errno, e.strerror)) # ERROR (return a tuple)
if (pid == 0): # The second child.
# Ensure that the daemon doesn't keep any directory in use. Failure
# to do this could make a filesystem unmountable.
os.chdir("/")
# Give the child complete control over permissions.
# yoh: BAD BAD BAD IDEA - generated files are writable by everybody
# changing to restrictive umask
os.umask(0022)
else:
os._exit(0) # Exit parent (the first child) of the second child.
else:
os._exit(0) # Exit parent of the first child.
# Close all open files. Try the system configuration variable, SC_OPEN_MAX,
# for the maximum number of open files to close. If it doesn't exist, use
# the default value (configurable).
try:
maxfd = os.sysconf("SC_OPEN_MAX")
except (AttributeError, ValueError):
maxfd = 256 # default maximum
for fd in range(0, maxfd):
try:
os.close(fd)
except OSError: # ERROR (ignore)
pass
# Redirect the standard file descriptors to /dev/null.
os.open("/dev/null", os.O_RDONLY) # standard input (0)
os.open("/dev/null", os.O_RDWR) # standard output (1)
os.open("/dev/null", os.O_RDWR) # standard error (2)
return True
def sigTERMhandler(signum, frame):
""" Handles the TERM signal when in daemon mode in order to
exit properly.
"""
logSys.debug("Signal handler called with sig "+`signum`)
killApp()
killApp()
def killApp():
""" Flush the ban list, remove the PID lock file and exit
nicely.
"""
logSys.warn("Restoring firewall rules...")
fireWall.flushBanList(conf["debug"])
for element in logFwList:
element[2].flushBanList(conf["debug"])
# Execute end command of each section
for element in logFwList:
l = element[4]
executeCmd(l["fwend"], conf["debug"])
# Execute global start command
executeCmd(conf["cmdend"], conf["debug"])
# Remove the PID lock
removePID(conf["pidlock"])
logSys.info("Exiting...")
sys.exit(0)
def checkForPID(lockfile):
""" Checks for running Fail2Ban.
Returns the current PID if Fail2Ban is running or False
if no instance found.
"""
try:
fileHandler = open(lockfile)
pid = fileHandler.readline()
return pid
except IOError:
return False
def createPID(lockfile):
""" Creates a PID lock file with the current PID.
"""
fileHandler = open(lockfile, mode='w')
pid = os.getpid()
fileHandler.write(`pid`+'\n')
fileHandler.close()
logSys.debug("Created PID lock ("+`pid`+") in "+lockfile)
def removePID(lockfile):
""" Remove PID lock.
"""
os.remove(lockfile)
logSys.debug("Removed PID lock "+lockfile)
def killPID(pid):
""" Kills the process with the given PID using the
INT signal (same effect as <ctrl>+<c>).
def getCmdLineOptions(optList):
""" Gets the command line options
"""
try:
return os.kill(pid, 2)
except OSError:
logSys.error("Can not kill process " + `pid` + ". Please check that " +
"Fail2Ban is not running and remove the file " +
"'/tmp/fail2ban.pid'")
if __name__ == "__main__":
# Gets an instance of log4py.
logSys = log4py.Logger().get_instance()
logSys.set_formatstring("%T %L %M")
conf = dict()
conf["verbose"] = 0
conf["background"] = False
conf["debug"] = False
conf["conffile"] = "/etc/fail2ban.conf"
conf["pidlock"] = "/var/run/fail2ban.pid"
conf["logging"] = False
conf["logfile"] = "/var/log/fail2ban.log"
conf["maxretry"] = 3
conf["bantime"] = 600
conf["ignoreip"] = ''
conf["interface"] = "eth0"
conf["firewall"] = "iptables"
conf["ipfw-start-rule"] = 0
conf["polltime"] = 1
# Reads the command line options.
try:
optList, args = getopt.getopt(sys.argv[1:], 'hvbdkc:l:t:i:r:e:w:p:', ['help','version'])
except getopt.GetoptError:
sys.stderr.write("Error during parsing of command line parameters\n");
usage()
# Pre-parsing of command line options for the -c option
for opt in optList:
if opt[0] == "-c":
conf["conffile"] = opt[1]
# Config file
configParser = SafeConfigParser()
configParser.read(conf["conffile"])
# background
try:
conf["background"] = configParser.getboolean("DEFAULT", "background")
except ValueError:
logSys.warn("background option should be a boolean")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("background option not in config file")
logSys.warn("Using default value")
# debug
try:
conf["debug"] = configParser.getboolean("DEFAULT", "debug")
except ValueError:
logSys.warn("debug option should be a boolean")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("debug option not in config file")
logSys.warn("Using default value")
# logfile
try:
conf["logfile"] = configParser.get("DEFAULT", "logfile")
except ValueError:
logSys.warn("logfile option should be a string")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("logfile option not in config file")
logSys.warn("Using default value")
# pidlock
try:
conf["pidlock"] = configParser.get("DEFAULT", "pidlock")
except ValueError:
logSys.warn("pidlock option should be a string")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("pidlock option not in config file")
logSys.warn("Using default value")
# maxretry
try:
conf["maxretry"] = configParser.getint("DEFAULT", "maxretry")
except ValueError:
logSys.warn("maxretry option should be an integer")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("maxretry option not in config file")
logSys.warn("Using default value")
# bantime
try:
conf["bantime"] = configParser.getint("DEFAULT", "bantime")
except ValueError:
logSys.warn("bantime option should be an integer")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("bantime option not in config file")
logSys.warn("Using default value")
# ignoreip
try:
conf["ignoreip"] = configParser.get("DEFAULT", "ignoreip")
except ValueError:
logSys.warn("ignoreip option should be a string")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("ignoreip option not in config file")
logSys.warn("Using default value")
# interface
try:
conf["interface"] = configParser.get("DEFAULT", "interface")
except ValueError:
logSys.warn("interface option should be a string")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("interface option not in config file")
logSys.warn("Using default value")
# firewall
try:
conf["firewall"] = configParser.get("DEFAULT", "firewall")
except ValueError:
logSys.warn("firewall option should be a string")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("firewall option not in config file")
logSys.warn("Using default value")
# ipfw-start-rule
try:
conf["ipfw-start-rule"] = configParser.getint("DEFAULT",
"ipfw-start-rule")
except ValueError:
logSys.warn("ipfw-start-rule option should be an integer")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("ipfw-start-rule option not in config file")
logSys.warn("Using default value")
# polltime
try:
conf["polltime"] = configParser.getint("DEFAULT", "polltime")
except ValueError:
logSys.warn("polltime option should be an integer")
logSys.warn("Using default value")
except NoOptionError:
logSys.warn("polltime option not in config file")
logSys.warn("Using default value")
for opt in optList:
if opt[0] in ["-h","--help"]:
usage()
if opt[0] in ["--version"]:
print version
sys.exit(0)
if opt[0] in ["-h", "--help"]:
dispUsage()
if opt[0] in ["-V", "--version"]:
dispVersion()
if opt[0] == "-v":
conf["verbose"] = conf["verbose"] + 1
if opt[0] == "-b":
conf["background"] = True
if opt[0] == "-d":
conf["debug"] = True
if opt[0] == "-e":
conf["interface"] = opt[1]
if opt[0] == "-l":
conf["logging"] = True
conf["logfile"] = opt[1]
if opt[0] == "-t":
try:
@ -390,8 +131,6 @@ if __name__ == "__main__":
conf["ignoreip"] = opt[1]
if opt[0] == "-r":
conf["retrymax"] = int(opt[1])
if opt[0] == "-w":
conf["firewall"] = opt[1]
if opt[0] == "-p":
conf["pidlock"] = opt[1]
if opt[0] == "-k":
@ -404,41 +143,84 @@ if __name__ == "__main__":
logSys.error("No running Fail2Ban found")
sys.exit(-1)
def main():
""" Fail2Ban main function
"""
logSys.set_formatstring("%T %L %M")
conf["verbose"] = 0
conf["conffile"] = "/etc/fail2ban.conf"
# Reads the command line options.
try:
cmdOpts = 'hvVbdkc:l:t:i:r:p:'
cmdLongOpts = ['help','version']
optList, args = getopt.getopt(sys.argv[1:], cmdOpts, cmdLongOpts)
except getopt.GetoptError:
dispUsage()
# Pre-parsing of command line options for the -c option
for opt in optList:
if opt[0] == "-c":
conf["conffile"] = opt[1]
# Reads the config file and create a LogReader instance for
# each log file to check.
confReader = ConfigReader(conf["conffile"]);
confReader.openConf()
# Options
optionValues = (["bool", "background", False],
["bool", "debug", False],
["str", "logfile", "/var/log/fail2ban.log"],
["str", "pidlock", "/var/run/fail2ban.pid"],
["int", "maxretry", 3],
["int", "bantime", 600],
["str", "ignoreip", ""],
["int", "polltime", 1],
["str", "cmdstart", ""],
["str", "cmdend", ""])
# Gets global configuration options
conf.update(confReader.getLogOptions("DEFAULT", optionValues))
# Gets command line options
getCmdLineOptions(optList)
# Process some options
for c in conf:
if c == "verbose":
logSys.warn("Verbose level is "+`conf[c]`)
if conf[c] == 1:
logSys.set_loglevel(log4py.LOGLEVEL_VERBOSE)
elif conf[c] > 1:
logSys.set_loglevel(log4py.LOGLEVEL_DEBUG)
elif c == "debug" and conf[c]:
# 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)
logSys.set_formatstring(log4py.FMT_DEBUG)
elif c == "background" and conf[c]:
retCode = createDaemon()
signal.signal(signal.SIGTERM, sigTERMhandler)
# 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
if conf["background"]:
retCode = createDaemon()
signal.signal(signal.SIGTERM, sigTERMhandler)
if not retCode:
logSys.error("Unable to start daemon")
sys.exit(-1)
# Bug fix for #1234699
os.umask(0077)
try:
open(conf["logfile"], "a")
logSys.set_target(conf["logfile"])
if not retCode:
logSys.error("Unable to start daemon")
sys.exit(-1)
elif c == "logging" and conf[c]:
try:
open(conf["logfile"], "a")
logSys.set_target(conf["logfile"])
except IOError:
logSys.warn("Unable to log to "+conf["logfile"])
logSys.warn("Using default output for logging")
elif c == "ignoreip":
ignoreIPList = conf[c].split(' ')
elif c == "firewall":
conf[c] = string.lower(conf[c])
if conf[c] == "ipfw":
fireWallName = "Ipfw"
elif conf[c] == "ipfwadm":
fireWallName = "Ipfwadm"
else:
fireWallName = "Iptables"
except IOError:
logSys.error("Unable to log to " + conf["logfile"])
logSys.warn("Using default output for logging")
# Ignores IP list
ignoreIPList = conf["ignoreip"].split(' ')
# Checks for root user. This is necessary because log files
# are owned by root and firewall needs root access.
@ -459,40 +241,66 @@ if __name__ == "__main__":
logSys.debug("BanTime is "+`conf["bantime"]`)
logSys.debug("retryAllowed is "+`conf["maxretry"]`)
# Reads the config file and create a LogReader instance for
# each log file to check.
confReader = ConfigReader(logSys, conf["conffile"]);
confReader.openConf()
logList = list()
# Options
optionValues = (["bool", "enabled", False],
["str", "host", "localhost"],
["int", "port", "25"],
["str", "from", "root"],
["str", "to", "root"],
["str", "subject", "[Fail2Ban] Banned <ip>"],
["str", "message", "Fail2Ban notification"])
# Gets global configuration options
mailConf = confReader.getLogOptions("MAIL", optionValues)
# Create mailer if enabled
if mailConf["enabled"]:
logSys.debug("Mail enabled")
mail = Mail(mailConf["host"], mailConf["port"])
mail.setFromAddr(mailConf["from"])
mail.setToAddr(mailConf["to"])
logSys.debug("to: " + mailConf["to"] + " from: " + mailConf["from"])
# Options
optionValues = (["bool", "enabled", True],
["str", "logfile", "/dev/null"],
["str", "timeregex", ""],
["str", "timepattern", ""],
["str", "failregex", ""],
["str", "fwstart", ""],
["str", "fwend", ""],
["str", "fwban", ""],
["str", "fwunban", ""])
# Gets the options of each sections
for t in confReader.getSections():
l = confReader.getLogOptions(t)
l = confReader.getLogOptions(t, optionValues)
if l["enabled"]:
lObj = LogReader(logSys, l["logfile"], l["timeregex"],
l["timepattern"], l["failregex"], conf["bantime"])
lObj.setName(t)
logList.append(lObj)
# Creates one instance of Iptables (thanks to Pyhton dynamic
# features).
fireWallObj = eval(fireWallName)
fireWall = fireWallObj(conf["bantime"], logSys, conf["interface"])
# IPFW needs rules number. The configuration option "ipfw-start-rule"
# defines the first rule number used by Fail2Ban.
if fireWallName == "Ipfw":
fireWall.setCrtRuleNbr(conf["ipfw-start-rule"])
# Creates a logreader object
lObj = LogReader(l["logfile"], l["timeregex"], l["timepattern"],
l["failregex"], conf["bantime"])
# Creates a firewall object
fObj = Firewall(l["fwban"], l["fwunban"], conf["bantime"])
# Links them into a list. I'm not really happy
# with this :/
logFwList.append([t, lObj, fObj, dict(), l])
# We add 127.0.0.1 to the ignore list has we do not want
# to be ban ourself.
for element in logList:
element.addIgnoreIP("127.0.0.1")
for element in logFwList:
element[1].addIgnoreIP("127.0.0.1")
while len(ignoreIPList) > 0:
ip = ignoreIPList.pop()
for element in logList:
element.addIgnoreIP(ip)
for element in logFwList:
element[1].addIgnoreIP(ip)
logSys.info("Fail2Ban v"+version+" is running")
failListFull = dict()
# Execute global start command
executeCmd(conf["cmdstart"], conf["debug"])
# Execute start command of each section
for element in logFwList:
l = element[4]
executeCmd(l["fwstart"], conf["debug"])
# Main loop
while True:
try:
@ -501,14 +309,15 @@ if __name__ == "__main__":
# Checks if some IP have to be remove from ban
# list.
fireWall.checkForUnBan(conf["debug"])
for element in logFwList:
element[2].checkForUnBan(conf["debug"])
# If the log file has not been modified since the
# last time, we sleep for 1 second. This is active
# polling so not very effective.
modList = list()
for element in logList:
if element.isModified():
for element in logFwList:
if element[1].isModified():
modList.append(element)
if len(modList) == 0:
@ -517,42 +326,39 @@ if __name__ == "__main__":
# Gets the failure list from the log file. For a given IP,
# takes only the service which has the most password failures.
failList = dict()
for element in modList:
e = element.getFailures()
e = element[1].getFailures()
for key in e.iterkeys():
if failList.has_key(key):
if failList[key][0] < e[key][0]:
failList[key] = (e[key][0], e[key][1], element)
if element[3].has_key(key):
element[3][key] = (element[3][key][0] + e[key][0],
e[key][1])
else:
failList[key] = (e[key][0], e[key][1], element)
# Add the last log failures to the global failure list.
for key in failList.iterkeys():
if failListFull.has_key(key):
failListFull[key] = (failListFull[key][0] + 1,
failList[key][1], failList[key][2])
else:
failListFull[key] = failList[key]
element[3][key] = (e[key][0], e[key][1])
# Remove the oldest failure attempts from the global list.
unixTime = time.time()
failListFullTemp = failListFull.copy()
for key in failListFullTemp.iterkeys():
failTime = failListFullTemp[key][2].getFindTime()
if failListFullTemp[key][1] < unixTime - failTime:
del failListFull[key]
# We iterate the failure list and ban IP that make
# *retryAllowed* login failures.
failListFullTemp = failListFull.copy()
for key in failListFullTemp.iterkeys():
element = failListFullTemp[key]
if element[0] >= conf["maxretry"]:
logSys.info(element[2].getName()+": "+key+" has "+
`element[0]`+" login failure(s). Banned.")
fireWall.addBanIP(key, conf["debug"])
del failListFull[key]
unixTime = time.time()
for element in logFwList:
fails = element[3].copy()
findTime = element[1].getFindTime()
for attempt in fails:
failTime = fails[attempt][1]
if failTime < unixTime - findTime:
del element[3][attempt]
elif fails[attempt][0] >= conf["maxretry"]:
aInfo = {"ip": attempt,
"failures": element[3][attempt][0],
"failtime": failTime}
logSys.info(element[0] + ": " + aInfo["ip"] +
" has " + `aInfo["failures"]` +
" login failure(s). Banned.")
element[2].addBanIP(aInfo, conf["debug"])
# Send a mail notification
if 'mail' in locals():
mail.sendmail(mailConf["subject"],
mailConf["message"], aInfo)
del element[3][attempt]
except KeyboardInterrupt:
# When the user press <ctrl>+<c> we exit nicely.

View File

@ -16,15 +16,21 @@
# Author: Cyril Jaquier
#
# $Revision: 1.8 $
# $Revision: 1.8.2.4 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.8 $"
__date__ = "$Date: 2005/03/06 17:46:56 $"
__version__ = "$Revision: 1.8.2.4 $"
__date__ = "$Date: 2005/07/12 13:08:24 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import time, os
import time, os, log4py, re
from utils.process import executeCmd
from utils.strings import replaceTag
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
class Firewall:
""" Manages the ban list and executes the command that ban
@ -33,30 +39,34 @@ class Firewall:
banList = dict()
def __init__(self, banTime, logSys, interface):
def __init__(self, banRule, unBanRule, banTime):
self.banRule = banRule
self.unBanRule = unBanRule
self.banTime = banTime
self.logSys = logSys
self.interface = interface
def addBanIP(self, ip, debug):
def addBanIP(self, aInfo, debug):
""" Bans an IP.
"""
ip = aInfo["ip"]
if not self.inBanList(ip):
self.logSys.warn("Ban "+ip)
self.banList[ip] = time.time()
self.__executeCmd(self.banIP(ip), debug)
crtTime = time.time()
logSys.warn("Ban " + ip)
self.banList[ip] = crtTime
aInfo["bantime"] = crtTime
executeCmd(self.banIP(aInfo), debug)
else:
self.logSys.error(ip+" already in ban list")
logSys.error(ip+" already in ban list")
def delBanIP(self, ip, debug):
def delBanIP(self, aInfo, debug):
""" Unban an IP.
"""
ip = aInfo["ip"]
if self.inBanList(ip):
self.logSys.warn("Unban "+ip)
logSys.warn("Unban "+ip)
del self.banList[ip]
self.__executeCmd(self.unBanIP(ip), debug)
executeCmd(self.unBanIP(aInfo), debug)
else:
self.logSys.error(ip+" not in ban list")
logSys.error(ip+" not in ban list")
def inBanList(self, ip):
""" Checks if IP is in ban list.
@ -68,10 +78,12 @@ class Firewall:
"""
banListTemp = self.banList.copy()
for element in banListTemp.iteritems():
ip = element[0]
btime = element[1]
if btime < time.time()-self.banTime:
self.delBanIP(ip, debug)
aInfo = {"ip": element[0],
"bantime": btime,
"unbantime": time.time()}
self.delBanIP(aInfo, debug)
def flushBanList(self, debug):
""" Flushes the ban list and of course the firewall rules.
@ -79,18 +91,23 @@ class Firewall:
"""
banListTemp = self.banList.copy()
for element in banListTemp.iteritems():
ip = element[0]
self.delBanIP(ip, debug)
def __executeCmd(self, cmd, debug):
""" Executes an OS command.
aInfo = {"ip": element[0],
"bantime": element[1],
"unbantime": time.time()}
self.delBanIP(aInfo, debug)
def banIP(self, aInfo):
""" Returns query to ban IP.
"""
self.logSys.debug(cmd)
if not debug:
return os.system(cmd)
else:
return None
query = replaceTag(self.banRule, aInfo)
return query
def unBanIP(self, aInfo):
""" Returns query to unban IP.
"""
query = replaceTag(self.unBanRule, aInfo)
return query
def viewBanList(self):
""" Prints the ban list on screen. Usefull for debugging.
"""

View File

@ -1,72 +0,0 @@
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Author: Cyril Jaquier
#
# $Revision: 1.4 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.4 $"
__date__ = "$Date: 2005/03/06 17:47:51 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os
from firewall import Firewall
class Ipfw(Firewall):
""" This class contains specific methods and variables for the
iptables firewall. Must implements the 'abstracts' methods
banIP(ip) and unBanIP(ip).
Must adds abstract methods definition:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266468
"""
crtRuleNbr = 0
def getCrtRuleNbr(self):
""" Gets the current rule number.
"""
return self.crtRuleNbr
def setCrtRuleNbr(self, value):
""" Sets the current rule number.
"""
self.crtRuleNbr = value
def banIP(self, ip):
""" Returns query to ban IP.
"""
query = "ipfw -q add "+`self.crtRuleNbr`+" deny ip from "+ip+" to any"
self.crtRuleNbr = self.crtRuleNbr + 1
return query
def unBanIP(self, ip):
""" Returns query to unban IP.
"""
ruleNbr = str(self.__findRuleNumber(ip))
query = "ipfw -q delete "+ruleNbr
return query
def __findRuleNumber(self, ip):
""" Uses shell commands in order to find the rule
number we want to delete.
"""
output = os.popen("ipfw list|grep \"from "+ip+" to\"|awk '{print $1}'",
"r");
return output.read()

View File

@ -16,26 +16,29 @@
# Author: Cyril Jaquier
#
# $Revision: 1.13 $
# $Revision: 1.13.2.2 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.13 $"
__date__ = "$Date: 2005/03/31 15:45:24 $"
__version__ = "$Revision: 1.13.2.2 $"
__date__ = "$Date: 2005/07/12 13:09:09 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os, sys, time, re
import os, sys, time, re, log4py
from utils.dns import *
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
class LogReader:
""" Reads a log file and reports information about IP that make password
failure, bad user or anything else that is considered as doubtful login
attempt.
"""
def __init__(self, logSys, logPath, timeregex, timepattern, failregex,
findTime = 3600):
def __init__(self, logPath, timeregex, timepattern, failregex,
findTime = 3600):
self.logPath = logPath
self.timeregex = timeregex
self.timepattern = timepattern
@ -43,20 +46,9 @@ class LogReader:
self.findTime = findTime
self.ignoreIpList = []
self.lastModTime = 0
self.logSys = logSys
self.lastPos = 0
self.lastDate = 0
self.logStats = None
def setName(self, name):
""" Sets the name of the log reader.
"""
self.name = name
def getName(self):
""" Gets the name of the log reader.
"""
return self.name
def getFindTime(self):
""" Gets the find time.
@ -66,13 +58,23 @@ class LogReader:
def addIgnoreIP(self, ip):
""" Adds an IP to the ignore list.
"""
self.logSys.debug("Add "+ip+" to ignore list")
logSys.debug("Add "+ip+" to ignore list")
self.ignoreIpList.append(ip)
def inIgnoreIPList(self, ip):
""" Checks if IP is in the ignore list.
"""
return ip in self.ignoreIpList
for i in self.ignoreIpList:
s = i.split('/', 1)
# IP address without CIDR mask
if len(s) == 1:
s.insert(1, '32')
s[1] = long(s[1])
a = cidr(s[0], s[1])
b = cidr(ip, s[1])
if a == b:
return True
return False
def openLogFile(self):
""" Opens the log file specified on init.
@ -80,7 +82,7 @@ class LogReader:
try:
fileHandler = open(self.logPath)
except OSError:
self.logSys.error("Unable to open "+self.logPath)
logSys.error("Unable to open "+self.logPath)
sys.exit(-1)
return fileHandler
@ -90,13 +92,13 @@ class LogReader:
try:
self.logStats = os.stat(self.logPath)
except OSError:
self.logSys.error("Unable to get stat on "+self.logPath)
logSys.error("Unable to get stat on "+self.logPath)
sys.exit(-1)
if self.lastModTime == self.logStats.st_mtime:
return False
else:
self.logSys.debug(self.logPath+" has been modified")
logSys.debug(self.logPath+" has been modified")
self.lastModTime = self.logStats.st_mtime
return True
@ -107,13 +109,13 @@ class LogReader:
"""
line = file.readline()
if self.lastDate < self.getTime(line):
self.logSys.debug("Date " + `self.lastDate` + " is " +
"smaller than " + `self.getTime(line)`)
self.logSys.debug("Log rotation detected for " + self.logPath)
logSys.debug("Date " + `self.lastDate` + " is " + "smaller than " +
`self.getTime(line)`)
logSys.debug("Log rotation detected for " + self.logPath)
self.lastPos = 0
self.logSys.debug("Setting file position to " + `self.lastPos` + " for "
+ self.logPath)
logSys.debug("Setting file position to " + `self.lastPos` + " for " +
self.logPath)
file.seek(self.lastPos)
def getFailures(self):
@ -124,7 +126,7 @@ class LogReader:
and the latest failure time.
"""
ipList = dict()
self.logSys.debug(self.logPath)
logSys.debug(self.logPath)
logFile = self.openLogFile()
self.setFilePos(logFile)
lastLine = ''
@ -137,9 +139,9 @@ class LogReader:
if unixTime < time.time()-self.findTime:
break
if self.inIgnoreIPList(ip):
self.logSys.debug("Ignore "+ip)
logSys.debug("Ignore "+ip)
continue
self.logSys.debug("Found "+ip)
logSys.debug("Found "+ip)
if ipList.has_key(ip):
ipList[ip] = (ipList[ip][0]+1, unixTime)
else:

View File

@ -18,11 +18,11 @@
# Author: Cyril Jaquier
#
# $Revision: 1.4 $
# $Revision: 1.4.2.1 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.4 $"
__date__ = "$Date: 2005/03/31 15:52:02 $"
__version__ = "$Revision: 1.4.2.1 $"
__date__ = "$Date: 2005/07/07 16:57:05 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
@ -35,8 +35,8 @@ setup(
description = "Ban IPs that make too many password failure",
author = "Cyril Jaquier",
author_email = "lostcontrol@users.sourceforge.net",
url = "http://www.sourceforge.net/projects/fail2ban",
url = "http://fail2ban.sourceforge.net",
scripts = ['fail2ban'],
py_modules = ['version'],
py_modules = ['fail2ban', 'version','log4py'],
packages = ['firewall', 'logreader', 'confreader', 'utils']
)

View File

@ -16,15 +16,15 @@
# Author: Cyril Jaquier
#
# $Revision: 1.7 $
# $Revision: 1.7.2.1 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.7 $"
__date__ = "$Date: 2005/05/28 19:46:18 $"
__version__ = "$Revision: 1.7.2.1 $"
__date__ = "$Date: 2005/07/12 13:10:14 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os, re, socket
import os, re, socket, struct
def dnsToIp(dns):
""" Convert a DNS into an IP address using the Python socket module.
@ -71,3 +71,21 @@ def textToIp(text):
for e in dns:
ipList.append(e)
return ipList
def cidr(i, n):
""" Convert an IP address string with a CIDR mask into a 32-bit
integer.
"""
# 32-bit IPv4 address mask
MASK = 0xFFFFFFFFL
return ~(MASK >> n) & MASK & addr2bin(i)
def addr2bin(str):
""" Convert a string IPv4 address into an unsigned integer.
"""
return struct.unpack("!L", socket.inet_aton(str))[0]
def bin2addr(addr):
""" Convert a numeric IPv4 address into string n.n.n.n form.
"""
return socket.inet_ntoa(struct.pack("!L", addr))

71
utils/mail.py Normal file
View File

@ -0,0 +1,71 @@
# 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/07/12 13:09:47 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import log4py, smtplib
from utils.strings import replaceTag
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
class Mail:
""" Mailer class
"""
def __init__(self, host, port = 25):
self.host = host
self.port = port
def setFromAddr(self, fromAddr):
""" Set from: address
"""
self.fromAddr = fromAddr
def setToAddr(self, toAddr):
""" Set to: address
"""
self.toAddr = toAddr.split()
def sendmail(self, subject, message, aInfo):
""" Send an email using smtplib
"""
subj = replaceTag(subject, aInfo)
msg = replaceTag(message, aInfo)
mail = ("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" %
(self.fromAddr, ", ".join(self.toAddr), subj)) + msg
try:
server = smtplib.SMTP(self.host, self.port)
#server.set_debuglevel(1)
server.sendmail(self.fromAddr, self.toAddr, mail)
logSys.debug("Email sent to " + `self.toAddr`)
server.quit()
except Exception:
logSys.error("Unable to send mail to " + self.host + ":" +
`self.port` + " from " + self.fromAddr + " to " +
`self.toAddr`)

158
utils/process.py Normal file
View File

@ -0,0 +1,158 @@
# 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/07/07 16:53:47 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os, log4py, signal
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
def createDaemon():
"""Detach a process from the controlling terminal and run it in the
background as a daemon.
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
"""
try:
# Fork a child process so the parent can exit. This will return control
# to the command line or shell. This is required so that the new process
# is guaranteed not to be a process group leader. We have this guarantee
# because the process GID of the parent is inherited by the child, but
# the child gets a new PID, making it impossible for its PID to equal its
# PGID.
pid = os.fork()
except OSError, e:
return((e.errno, e.strerror)) # ERROR (return a tuple)
if (pid == 0): # The first child.
# Next we call os.setsid() to become the session leader of this new
# session. The process also becomes the process group leader of the
# new process group. Since a controlling terminal is associated with a
# session, and this new session has not yet acquired a controlling
# terminal our process now has no controlling terminal. This shouldn't
# fail, since we're guaranteed that the child is not a process group
# leader.
os.setsid()
# When the first child terminates, all processes in the second child
# are sent a SIGHUP, so it's ignored.
signal.signal(signal.SIGHUP, signal.SIG_IGN)
try:
# Fork a second child to prevent zombies. Since the first child is
# a session leader without a controlling terminal, it's possible for
# it to acquire one by opening a terminal in the future. This second
# fork guarantees that the child is no longer a session leader, thus
# preventing the daemon from ever acquiring a controlling terminal.
pid = os.fork() # Fork a second child.
except OSError, e:
return((e.errno, e.strerror)) # ERROR (return a tuple)
if (pid == 0): # The second child.
# Ensure that the daemon doesn't keep any directory in use. Failure
# to do this could make a filesystem unmountable.
os.chdir("/")
# Give the child complete control over permissions.
os.umask(0)
else:
os._exit(0) # Exit parent (the first child) of the second child.
else:
os._exit(0) # Exit parent of the first child.
# Close all open files. Try the system configuration variable, SC_OPEN_MAX,
# for the maximum number of open files to close. If it doesn't exist, use
# the default value (configurable).
try:
maxfd = os.sysconf("SC_OPEN_MAX")
except (AttributeError, ValueError):
maxfd = 256 # default maximum
for fd in range(0, maxfd):
try:
os.close(fd)
except OSError: # ERROR (ignore)
pass
# Redirect the standard file descriptors to /dev/null.
os.open("/dev/null", os.O_RDONLY) # standard input (0)
os.open("/dev/null", os.O_RDWR) # standard output (1)
os.open("/dev/null", os.O_RDWR) # standard error (2)
return True
def checkForPID(lockfile):
""" Checks for running Fail2Ban.
Returns the current PID if Fail2Ban is running or False
if no instance found.
"""
try:
fileHandler = open(lockfile)
pid = fileHandler.readline()
return pid
except IOError:
return False
def createPID(lockfile):
""" Creates a PID lock file with the current PID.
"""
fileHandler = open(lockfile, mode='w')
pid = os.getpid()
fileHandler.write(`pid`+'\n')
fileHandler.close()
logSys.debug("Created PID lock ("+`pid`+") in "+lockfile)
def removePID(lockfile):
""" Remove PID lock.
"""
os.remove(lockfile)
logSys.debug("Removed PID lock "+lockfile)
def killPID(pid):
""" Kills the process with the given PID using the
INT signal (same effect as <ctrl>+<c>).
"""
try:
return os.kill(pid, 2)
except OSError:
logSys.error("Can not kill process " + `pid` + ". Please check that " +
"Fail2Ban is not running and remove the file " +
"'/tmp/fail2ban.pid'")
return False
def executeCmd(cmd, debug):
""" Executes an OS command.
"""
if cmd == "":
logSys.debug("Nothing to do")
return None
logSys.debug(cmd)
if not debug:
return os.system(cmd)
else:
return None

View File

@ -16,33 +16,25 @@
# Author: Cyril Jaquier
#
# $Revision: 1.1 $
# $Revision: 1.1.2.1 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.1 $"
__date__ = "$Date: 2004/11/06 14:02:27 $"
__version__ = "$Revision: 1.1.2.1 $"
__date__ = "$Date: 2005/07/12 13:09:47 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from firewall import Firewall
import log4py
class Ipfwadm(Firewall):
""" This class contains specific methods and variables for the
iptables firewall. Must implements the 'abstracts' methods
banIP(ip) and unBanIP(ip).
Must adds abstract methods definition:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266468
# Gets the instance of log4py.
logSys = log4py.Logger().get_instance()
def replaceTag(query, aInfo):
""" Replace tags in query
"""
def banIP(self, ip):
""" Returns query to ban IP.
"""
query = "ipfwadm -I -a deny -W "+self.interface+" -S "+ip
return query
def unBanIP(self, ip):
""" Returns query to unban IP.
"""
query = "ipfwadm -I -d deny -W "+self.interface+" -S "+ip
return query
string = query
for tag in aInfo:
string = string.replace('<'+tag+'>', `aInfo[tag]`)
# New line
string = string.replace('<br>', '\n')
return string

View File

@ -16,12 +16,12 @@
# Author: Cyril Jaquier
#
# $Revision: 1.12 $
# $Revision: 1.12.2.2 $
__author__ = "Cyril Jaquier"
__version__ = "$Revision: 1.12 $"
__date__ = "$Date: 2005/06/30 09:30:59 $"
__version__ = "$Revision: 1.12.2.2 $"
__date__ = "$Date: 2005/07/12 13:12:40 $"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
version = "0.4.1"
version = "0.5.0"