- Merged FAIL2BAN-0_5 with HEAD

git-svn-id: https://fail2ban.svn.sourceforge.net/svnroot/fail2ban/trunk@216 a942ae1a-1317-0410-a47c-b1dcaea8d605
0.6 0.6.0
Cyril Jaquier 2005-11-20 17:07:47 +00:00
parent 07d127bebd
commit 840f6cd052
27 changed files with 1619 additions and 747 deletions

106
CHANGELOG
View File

@ -4,15 +4,103 @@
|_| \__,_|_|_/___|_.__/\__,_|_||_| |_| \__,_|_|_/___|_.__/\__,_|_||_|
============================================================= =============================================================
Fail2Ban (version 0.?.?) ??/??/2005 Fail2Ban (version 0.6.0) 2005/11/20
============================================================= =============================================================
ver. 0.?.? (??/??/2005) - ??? ver. 0.6.0 (2005/11/20) - stable
---------- ----------
- Propagated patches introduced by Debian maintainer
(Yaroslav Halchenko):
* Added an option to report local time (including timezone)
or GMT in mail notification.
ver. 0.5.5 (2005/10/26) - beta
----------
- Propagated patches introduced by Debian maintainer
(Yaroslav Halchenko):
* Introduced fwcheck option to verify consistency of the
chains. Implemented automatic restart of fail2ban main
function in case check of fwban or fwunban command failed
(closes: #329163, #331695). (Introduced patch was further
adjusted by upstream author).
* Added -f command line parameter for [findtime].
* Added a cleanup of firewall rules on emergency shutdown
when unknown exception is catched.
* Fail2ban should not crash now if a wrong file name is
specified in config.
* reordered code a bit so that log targets are setup right
after background and then only loglevel (verbose, debug)
is processed, so the warning could be seen in the logs
* Added a keyword <section> in parsing of the subject and
the body of an email sent out by fail2ban (closes:
#330311)
ver. 0.5.4 (2005/09/13) - beta
----------
- Fixed bug #1286222.
- Propagated patches introduced by Debian maintainer
(Yaroslav Halchenko):
* Fixed handling of SYSLOG logging target. Now it can log
to any SYSLOG target and facility as directed by the
config
* Format of SYSLOG entries fixed to look closer to standard
* Fixed errata in config/gentoo-confd
* Introduced findtime configuration variable to control the
lifetime of caught "failed" log entries
ver. 0.5.3 (2005/09/08) - beta
----------
- Fixed a bug when overriding "maxfailures" or "bantime".
Thanks to Yaroslav Halchenko
- Added more debug output if an error occurs when sending
mail. Thanks to Stephen Gildea
- Renamed "maxretry" to "maxfailures" and changed default
value to 5. Thanks to Stephen Gildea
- Hopefully fixed bug #1256075
- Fixed bug #1262345
- Fixed exception handling in PIDLock
- Removed warning when using "-V" or "-h" with no config
file. Thanks to Yaroslav Halchenko
- Removed "-i eth0" from config file. Thanks to Yaroslav
Halchenko
ver. 0.5.2 (2005/08/06) - beta
----------
- Better PID lock file handling. Should close #1239562
- Added man pages
- Removed log4py dependency. Use logging module instead
- "maxretry" and "bantime" can be overridden in each section
- Fixed bug #1246278 (excessive memory usage)
- Fixed crash on wrong option value in configuration file
- Changed custom chains to lowercase
ver. 0.5.1 (2005/07/23) - beta
----------
- Fixed bugs #1241756, #1239557
- Added log targets in configuration file. Removed -l option
- Changed iptables rules in order to create a separated chain
for each section
- Fixed static banList in firewall.py
- Added an initd script for Debian. Thanks to Yaroslav
Halchenko
- Check for obsolete files after install
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 - Added an initd script for RedHat/Fedora. Thanks to Andrey
G. Grozin G. Grozin
ver. 0.4.1 (06/30/2005) - stable ver. 0.4.1 (2005/06/30) - stable
---------- ----------
- Fixed textToDNS method which generated wrong matches for - Fixed textToDNS method which generated wrong matches for
"rhost=12-xyz...". Thanks to Tom Pike "rhost=12-xyz...". Thanks to Tom Pike
@ -21,19 +109,19 @@ ver. 0.4.1 (06/30/2005) - stable
- Changed default PID lock file location from /tmp to - Changed default PID lock file location from /tmp to
/var/run /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 - Fixed textToDNS which did not recognize strings like
"12-345-67-890.abcd.mnopqr.xyz" "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 - Corrected level of messages
- Added DNS lookup support - Added DNS lookup support
- Improved parsing speed. Only parse the new log messages - Improved parsing speed. Only parse the new log messages
- Added a second verbose level (-vv) - 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 - Re-writting of parts of the code in order to handle several
log files with different rules log files with different rules
@ -44,7 +132,7 @@ ver. 0.3.0 (02/24/2005) - beta
- Added ipfw-start-rule option (thanks to Robert Edeker) - Added ipfw-start-rule option (thanks to Robert Edeker)
- Added -k option which kills a currently running Fail2Ban - 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 - Add ipfw and ipfwadm support. The rules are taken from
BlockIt. Thanks to Robert Edeker BlockIt. Thanks to Robert Edeker
@ -52,7 +140,7 @@ ver. 0.1.2 (11/21/2004) - beta
Robert Edeker who reminded me this Robert Edeker who reminded me this
- Small code cleaning - 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 - Add SIGTERM handler in order to exit nicely when in daemon
mode mode
@ -66,6 +154,6 @@ ver. 0.1.1 (10/23/2004) - beta
- Code documentation - Code documentation
ver. 0.1.0 (10/12/2004) - alpha ver. 0.1.0 (2004/10/12) - alpha
---------- ----------
- Initial release - Initial release

View File

@ -4,18 +4,24 @@ TODO
setup.cfg setup.cfg
setup.py setup.py
version.py version.py
fail2ban
fail2ban.py fail2ban.py
firewall/__init__.py firewall/__init__.py
firewall/firewall.py firewall/firewall.py
firewall/iptables.py
firewall/ipfw.py
firewall/ipfwadm.py
logreader/__init__.py logreader/__init__.py
logreader/logreader.py logreader/logreader.py
confreader/__init__.py confreader/__init__.py
confreader/configreader.py confreader/configreader.py
utils/__init__.py utils/__init__.py
utils/dns.py utils/dns.py
utils/process.py
utils/mail.py
utils/strings.py
utils/pidlock.py
config/fail2ban.conf.default config/fail2ban.conf.default
config/gentoo-initd config/gentoo-initd
config/gentoo-confd config/gentoo-confd
config/redhat-initd
config/debian-initd
man/fail2ban.8
man/fail2ban.conf.5

70
README
View File

@ -4,14 +4,14 @@
|_| \__,_|_|_/___|_.__/\__,_|_||_| |_| \__,_|_|_/___|_.__/\__,_|_||_|
============================================================= =============================================================
Fail2Ban (version 0.4.1) 06/30/2005 Fail2Ban (version 0.6.0) 2005/11/20
============================================================= =============================================================
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. Currently iptables, ipfw and rules to reject the IP address. These rules can be defined by
ipfwadm are supported. Fail2Ban can read multiple log files the user. Fail2Ban can read multiple log files such as sshd
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...
@ -36,51 +36,55 @@ tries to find lines which match the failregex. Then it
retrieves the message time using timeregex and timepattern. retrieves the message time using timeregex and timepattern.
It finally gets the ip and if it has already done 3 or more 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 password failures in the last banTime, the ip is banned for
banTime using a firewall rule. After banTime, the rule is banTime using a firewall rule. This rule is set by the user
deleted. Notice that if no "plain" ip is available, Fail2Ban in the configuration file. Thus, Fail2Ban can be adapted for
try to do DNS lookup in order to found one or several ip's to lots of firewall. After banTime, the rule is deleted. Notice
ban. 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 Sections can be freely added so it is possible to monitor
several daemons at the same time. several daemons at the same time.
Runs on my server and does its job rather well :-) The idea Runs on my server and does its job rather well :-) The idea
is to make fail2ban usable with daemons and services that is to make fail2ban usable with daemons and services that
require a login (sshd, telnetd, ...). It should also support require a login (sshd, telnetd, ...) and with different
others firewalls than iptables. firewalls.
Installation: Installation:
------------- -------------
Require: python-2.3 (http://www.python.org) Require: python-2.4 (http://www.python.org)
log4py-1.1 (http://sourceforge.net/projects/log4py)
To install, just do: To install, just do:
> tar xvfj fail2ban-0.4.1.tar.bz2 > tar xvfj fail2ban-0.6.0.tar.bz2
> cd fail2ban-0.4.1 > cd fail2ban-0.6.0
> python setup.py install > python setup.py install
This will install Fail2Ban into /usr/lib/fail2ban. The This will install Fail2Ban into /usr/lib/fail2ban. The fail2ban
fail2ban.py executable is placed into /usr/bin. executable is placed into /usr/bin.
For Gentoo users, an ebuild is available on the website. Gentoo: ebuilds are available on the website.
Debian: Fail2Ban is in Debian unstable.
RedHat: packages are available on the website.
Fail2Ban should now be correctly installed. Just type: Fail2Ban should now be correctly installed. Just type:
> fail2ban.py -h > fail2ban -h
to see if everything is alright. You can configure fail2ban to see if everything is alright. You can configure fail2ban
with a config file. Copy config/fail2ban.conf.default to with a config file. Copy config/fail2ban.conf.default to
/etc/fail2ban.conf. /etc/fail2ban.conf.
Gentoo users can use the initd script available in config/. You can use the initd script available in config/. Copy
Copy gentoo-initd to /etc/init.d/fail2ban and gentoo-confd <dist>-initd to /etc/init.d/fail2ban. Gentoo users must copy
to /etc/conf.d/fail2ban. You can start fail2ban and add it gentoo-confd to /etc/conf.d/fail2ban. You can start fail2ban:
to your default runlevel:
> /etc/init.d/fail2ban start > /etc/init.d/fail2ban start
Gentoo users can add it to the default runlevel:
> rc-update add fail2ban default > rc-update add fail2ban default
Configuration: Configuration:
@ -91,20 +95,18 @@ 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
-e <INTF> ban IP on the INTF interface
-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
-l <FILE> log message in FILE -r <VALUE> allow a max of VALUE password failure [maxfailures]
-r <VALUE> allow a max of VALUE password failure -t <TIME> ban IP for TIME seconds [bantime]
-t <TIME> ban IP for TIME seconds -f <TIME> lifetime in seconds of failed entry [findtime]
-v verbose. Use twice for greater effect -v verbose. Use twice for greater effect
-w <FIWA> select the firewall to use. Can be iptables, -V print software version
ipfwadm or ipfw
Contact: Contact:
-------- --------
@ -112,7 +114,7 @@ Contact:
You need some new features, you found bugs or you just You need some new features, you found bugs or you just
appreciate this program, you can contact me at : 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> Cyril Jaquier: <lostcontrol@users.sourceforge.net>
@ -121,8 +123,8 @@ Thanks:
------- -------
Kévin Drapel, Marvin Rouge, Sireyessire, Robert Edeker, Kévin Drapel, Marvin Rouge, Sireyessire, Robert Edeker,
Tom Pike, Iain Lea Tom Pike, Iain Lea, Andrey G. Grozin, Yaroslav Halchenko,
Jonathan Kamens, Stephen Gildea
License: License:
-------- --------

22
TODO
View File

@ -4,12 +4,20 @@
|_| \__,_|_|_/___|_.__/\__,_|_||_| |_| \__,_|_|_/___|_.__/\__,_|_||_|
============================================================= =============================================================
ToDo ToDo $Revision$
============================================================= =============================================================
- cleanup fail2ban.py See Feature Request Tracking System at SourceForge.net
- improve configuration file and command line options
handling - improve installation process (better prefix support)
- improve installation process - install Fail2ban into /usr/share
- add init script - better configuration files
- use FAM (inotify, gamin, ...) - add a check to see if the time of the log messages is
correctly detected (valid regexp)
- split configuration files in /etc/fail2ban/services.d
Example: /etc/fail2ban/services.d/apache
- template for common services in /etc/fail2ban/scripts.d
Example: /etc/fail2ban/scripts.d/apache
- remove debug mode (root check)
- better return values in function
- use more email.Utils in mail.py

73
config/debian-initd Normal file
View File

@ -0,0 +1,73 @@
#! /bin/sh
#
# Fail2Ban init.d file - to be launched on boot
#
# Written by Miquel van Smoorenburg <miquels@cistron.nl>.
# Modified for Debian
# by Ian Murdock <imurdock@gnu.ai.mit.edu>.
# Adjusted for Fail2Ban
# by Yaroslav Halchenko <debian@onerussian.com>.
#
# Version: $Id$
#
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/bin/fail2ban
NAME=fail2ban
DESC=fail2ban
PIDFILE=/var/run/$NAME.pid
test -x $DAEMON || exit 0
# Include fail2ban defaults if available
if [ -f /etc/default/fail2ban ] ; then
. /etc/default/fail2ban
fi
DAEMON_OPTS=$FAIL2BAN_OPTS
set -e
case "$1" in
start)
echo -n "Starting $DESC: "
[ -f $PIDFILE ] && [ ! -d /proc/`cat $PIDFILE` ] && rm -f $PIDFILE
start-stop-daemon --start --quiet --pidfile $PIDFILE \
-b --exec $DAEMON -- $DAEMON_OPTS
echo "$NAME."
;;
stop)
echo -n "Stopping $DESC: "
start-stop-daemon --stop --quiet --pidfile $PIDFILE
echo "$NAME."
;;
restart|force-reload)
echo -n "Restarting $DESC: "
( $0 stop )
sleep 1
$0 start
;;
status)
echo -n "Status of $DESC: "
if [ ! -e "$PIDFILE" ]; then
echo "$NAME is not running."
exit 3
fi
if [ ! -r "$PIDFILE" ]; then
echo "$PIDFILE not readable, status of $NAME unknown."
exit 4
fi
if [ -d /proc/`cat "$PIDFILE"` ]; then
echo "$NAME is running."
exit 0
else
echo "$NAME is not running but $PIDFILE exists."
exit 1
fi
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
exit 1
;;
esac
exit 0

View File

@ -5,18 +5,6 @@
# 2005.06.21 modified for readability Iain Lea iain@bricbrac.de # 2005.06.21 modified for readability Iain Lea iain@bricbrac.de
[DEFAULT] [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 # Option: background
# Notes.: start fail2ban as a daemon. Output is redirect to logfile. # Notes.: start fail2ban as a daemon. Output is redirect to logfile.
# Values: [true | false] Default: false # Values: [true | false] Default: false
@ -29,23 +17,35 @@ background = false
# #
debug = false debug = false
# Option: logtargets
# Notes.: log targets. Space separated list of logging targets.
# Values: STDERR SYSLOG file Default: /var/log/fail2ban.log
#
logtargets = /var/log/fail2ban.log
# Option: syslog-target
# Notes.: where to find syslog facility if logtarget SYSLOG.
# Values: SOCKET HOST HOST:PORT Default: /dev/log
#
syslog-target = /dev/log
# Option: syslog-facility
# Notes.: which syslog facility to use if logtarget SYSLOG.
# Values: NUM Default: 1
#
syslog-facility = 1
# 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).
# Values: FILE Default: /var/run/fail2ban.pid # Values: FILE Default: /var/run/fail2ban.pid
# #
pidlock = /var/run/fail2ban.pid pidlock = /var/run/fail2ban.pid
# Option: logfile # Option: maxfailures
# Notes.: logfile for logging fail2ban messages. # Notes.: number of failures before IP gets banned.
# Values: FILE Default: /var/log/fail2ban.log # Values: NUM Default: 5
# #
logfile = /var/log/fail2ban.log maxfailures = 5
# Option: maxretry
# Notes.: number of retrys before IP gets banned.
# Values: NUM Default: 3
#
maxretry = 3
# Option: bantime # Option: bantime
# Notes.: number of seconds an IP will be banned. # Notes.: number of seconds an IP will be banned.
@ -53,18 +53,31 @@ maxretry = 3
# #
bantime = 600 bantime = 600
# Option: ignoreip # Option: findtime
# Notes.: space separated list of IP's to be ignored by fail2ban # Notes.: lifetime in seconds of a "failed" log entry.
# Example: ignoreip = 192.168.0.1 123.45.235.65 # Values: NUM Default: 600
# Values: IP Default:
# #
ignoreip = findtime = 600
# Option: interface # Option: ignoreip
# Notes.: interface name on which the IP will be banned. # Notes.: space separated list of IP's to be ignored by fail2ban.
# Values: INT Default: eth0 # 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/16
# #
interface = eth0 ignoreip = 192.168.0.0/16
# Option: cmdstart
# Notes.: command executed once at the start of Fail2Ban
# Values: CMD Default:
#
cmdstart =
# Option: cmdend
# Notes.: command executed once at the end of Fail2Ban.
# Values: CMD Default:
#
cmdend =
# Option: polltime # Option: polltime
# Notes.: number of seconds fail2ban sleeps between iterations. # Notes.: number of seconds fail2ban sleeps between iterations.
@ -72,9 +85,89 @@ interface = eth0
# #
polltime = 1 polltime = 1
# Option: reinittime
# Notes.: minimal number of seconds between the re-initialization of
# firewalls due to external changes in their rules (see fwcheck)
# Values: NUM Default: 100
#
reinittime = 10
# Option: maxreinits
# Notes.: maximal number of re-initialization of firewalls due to external
# changes. -1 stays for infinite, so only reinittime is of importance
# Values: NUM Default: -1
#
maxreinits = -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 addresses of the receiver. Addresses are space
# separated.
# Values: MAIL Default: root
#
to = root
# Option: localtime
# Notes.: report local time (including timezone) or GMT
# Values: [true | false] Default: false
#
localtime = true
# Option: subject
# Notes.: subject of the e-mail.
# Tags: <section> active section (eg ssh, apache, etc)
# <ip> IP address
# <failures> number of failures
# <failtime> unix timestamp of the last failure
# Values: TEXT Default: [Fail2Ban] <section>: Banned <ip>
#
subject = [Fail2Ban] <section>: Banned <ip>
# Option: message
# Notes.: message of the e-mail.
# Tags: <section> active section (eg ssh, apache, etc)
# <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 against <section>.<br>
Regards,<br>
Fail2Ban
# You can define a new section for each log file to check for # You can define a new section for each log file to check for
# password failure. Each section has to define the following # password failure. Each section has to define the following
# options: logfile, timeregex, timepattern, failregex. # options: logfile, fwban, fwunban, timeregex, timepattern,
# failregex.
[Apache] [Apache]
# Option: enabled # Option: enabled
@ -89,10 +182,55 @@ enabled = false
# #
logfile = /var/log/httpd/access_log logfile = /var/log/httpd/access_log
# Option: fwstart
# Notes.: command executed once at the start of Fail2Ban
# Values: CMD Default:
#
fwstart = iptables -N fail2ban-http
iptables -I INPUT -p tcp --dport http -j fail2ban-http
iptables -A fail2ban-http -j RETURN
# Option: fwend
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD Default:
#
fwend = iptables -D INPUT -p tcp --dport http -j fail2ban-http
iptables -F fail2ban-http
iptables -X fail2ban-http
# Option: fwcheck
# Notes.: command executed once before each fwban command
# Values: CMD Default:
#
fwcheck = iptables -L INPUT | grep -q fail2ban-http
# 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 -s <ip> -j DROP
#
fwban = iptables -I fail2ban-http 1 -s <ip> -j DROP
# Option: fwunban
# Notes.: command executed when unbanning an IP. Take care that the
# 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 -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.
# Values: [Wed Jan 05 15:08:01 2005] # 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} timeregex = \S{3} \S{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}
@ -109,6 +247,7 @@ timepattern = %%a %%b %%d %%H:%%M:%%S %%Y
# #
failregex = authentication failure|user .* not found failregex = authentication failure|user .* not found
[SSH] [SSH]
# Option: enabled # Option: enabled
# Notes.: enable monitoring for this section. # Notes.: enable monitoring for this section.
@ -122,10 +261,55 @@ enabled = true
# #
logfile = /var/log/secure logfile = /var/log/secure
# Option: fwstart
# Notes.: command executed once at the start of Fail2Ban
# Values: CMD Default:
#
fwstart = iptables -N fail2ban-ssh
iptables -I INPUT -p tcp --dport ssh -j fail2ban-ssh
iptables -A fail2ban-ssh -j RETURN
# Option: fwend
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD Default:
#
fwend = iptables -D INPUT -p tcp --dport ssh -j fail2ban-ssh
iptables -F fail2ban-ssh
iptables -X fail2ban-ssh
# Option: fwcheck
# Notes.: command executed once before each fwban command
# Values: CMD Default:
#
fwcheck = iptables -L INPUT | grep -q fail2ban-ssh
# 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 -s <ip> -j DROP
#
fwban = iptables -I fail2ban-ssh 1 -s <ip> -j DROP
# Option: fwunbanrule
# Notes.: command executed when unbanning an IP. Take care that the
# 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 -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.
# Values: [Mar 7 17:53:28] # 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} timeregex = \S{3}\s{1,2}\d{1,2} \d{2}:\d{2}:\d{2}

View File

@ -18,6 +18,6 @@
# #
# $Revision$ # $Revision$
# Command line options for Fail2Ban. Refer to "fail2ban.py -h" for # Command line options for Fail2Ban. Refer to "fail2ban -h" for
# valid options. # valid options.
FAIL2BAN_OPTS="-v" FAIL2BAN_OPTS="-v"

View File

@ -21,7 +21,7 @@
opts="start stop restart showlog" opts="start stop restart showlog"
FAIL2BAN="/usr/bin/fail2ban.py" FAIL2BAN="/usr/bin/fail2ban"
depend() { depend() {
need net need net
@ -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

@ -21,14 +21,14 @@
[ "${NETWORKING}" = "no" ] && exit 0 [ "${NETWORKING}" = "no" ] && exit 0
[ -f /etc/fail2ban.conf ] || exit 0 [ -f /etc/fail2ban.conf ] || exit 0
FAIL2BAN="/usr/bin/fail2ban.py" FAIL2BAN="/usr/bin/fail2ban"
PIDFILE="/var/run/fail2ban.pid" PIDFILE="/var/run/fail2ban.pid"
RETVAL=0 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
} }
@ -55,7 +55,7 @@ case "$1" in
stop stop
;; ;;
status) status)
status fail2ban.py status fail2ban
RETVAL=$? RETVAL=$?
;; ;;
reload) reload)

View File

@ -24,28 +24,22 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import logging
from ConfigParser import * from ConfigParser import *
# Gets the instance of the logger.
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.
The DEFAULT section contains the global information about The DEFAULT section contains the global information about
Fail2Ban. Each other section is for a different log file. Fail2Ban. Each other section is for a different log file.
""" """
# Each optionValues entry is composed of an array with: def __init__(self, confPath):
# 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):
self.confPath = confPath self.confPath = confPath
self.configParser = SafeConfigParser() self.configParser = SafeConfigParser()
self.logSys = logSys
def openConf(self): def openConf(self):
""" Opens the configuration file. """ Opens the configuration file.
@ -54,16 +48,23 @@ class ConfigReader:
def getSections(self): def getSections(self):
""" Returns all the sections present in the configuration """ Returns all the sections present in the configuration
file except the DEFAULT section. file except the DEFAULT and MAIL sections.
""" """
return self.configParser.sections() sections = self.configParser.sections()
sections.remove("MAIL")
logSys.debug("Found sections: " + `sections`)
return sections
def getLogOptions(self, sec): # 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 """ Gets all the options of a given section. The options
are defined in the optionValues list. are defined in the optionValues list.
""" """
values = dict() values = dict()
for option in self.optionValues: for option in options:
try: try:
if option[0] == "bool": if option[0] == "bool":
v = self.configParser.getboolean(sec, option[1]) v = self.configParser.getboolean(sec, option[1])
@ -74,7 +75,11 @@ class ConfigReader:
values[option[1]] = v values[option[1]] = v
except NoOptionError: except NoOptionError:
self.logSys.warn("No '"+option[1]+"' defined in '"+sec+"'") logSys.warn("No '" + option[1] + "' defined in '" + sec + "'")
values[option[1]] = option[2]
except ValueError:
logSys.warn("Wrong value for '" + option[1] + "' in '" + sec +
"'. Using default one: '" + `option[2]` + "'")
values[option[1]] = option[2] values[option[1]] = option[2]
return values return values

66
fail2ban Executable file
View File

@ -0,0 +1,66 @@
#!/usr/bin/env python
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Author: Cyril Jaquier
#
# $Revision$
__author__ = "Cyril Jaquier"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import sys, traceback, logging
# Appends our own modules path.
sys.path.append("/usr/lib/fail2ban")
# Now we can import our modules.
import fail2ban
from utils.pidlock import PIDLock
# Get the instance of the logger.
logSys = logging.getLogger("fail2ban")
# Get PID lock file instance
pidLock = PIDLock()
# Start the application. Handle all the unhandled exceptions
try:
fail2ban.main()
except SystemExit:
# We called sys.exit(). Nothing wrong so just pass
pass
except Exception, e:
# Print the exception data
(type, value, tb) = sys.exc_info()
tbStack = traceback.extract_tb(tb)
logSys.error("Fail2Ban got an unhandled exception and died.")
logSys.error("Type: " + `type.__name__` + "\n" +
"Value: " + `e.args` + "\n" +
"TB: " + `tbStack`)
# Try to clean up after ourselves
# just for extreme caution - wrapping with try
try:
fail2ban.restoreFwRules()
except Exception:
pass
# Remove the PID lock file. Should close #1239562
pidLock.remove()
logging.shutdown()

View File

@ -1,5 +1,3 @@
#!/usr/bin/env python
# This file is part of Fail2Ban. # This file is part of Fail2Ban.
# #
# Fail2Ban is free software; you can redistribute it and/or modify # Fail2Ban is free software; you can redistribute it and/or modify
@ -17,6 +15,7 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Author: Cyril Jaquier # Author: Cyril Jaquier
# Modified by: Yaroslav Halchenko (SYSLOG, findtime)
# #
# $Revision$ # $Revision$
@ -26,50 +25,58 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import time, sys, getopt, os, signal, string import time, sys, getopt, os, string, signal, logging, logging.handlers, copy
from ConfigParser import * from ConfigParser import *
# Checks if log4py is present. from version import version
try: from firewall.firewall import Firewall
import log4py
except:
print "log4py is needed (see README)"
sys.exit(-1)
# Appends our own modules path
sys.path.append('/usr/lib/fail2ban')
from firewall.iptables import Iptables
from firewall.ipfw import Ipfw
from firewall.ipfwadm import Ipfwadm
from logreader.logreader import LogReader from logreader.logreader import LogReader
from confreader.configreader import ConfigReader from confreader.configreader import ConfigReader
from version import version from utils.mail import Mail
from utils.pidlock import PIDLock
from utils.dns import *
from utils.process import *
def usage(): # Get the instance of the logger.
print "Usage: fail2ban.py [OPTIONS]" logSys = logging.getLogger("fail2ban")
# Get PID lock file instance
pidLock = PIDLock()
# Global variables
logFwList = list()
conf = dict()
def dispUsage():
""" Prints Fail2Ban command line options and exits
"""
print "Usage: "+sys.argv[0]+" [OPTIONS]"
print print
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 address using iptables." 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 " -e <INTF> ban IP on the INTF interface"
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 " -l <FILE> log message in FILE" print " -r <VALUE> allow a max of VALUE password failure [maxfailures]"
print " -r <VALUE> allow a max of VALUE password failure" print " -t <TIME> ban IP for TIME seconds [bantime]"
print " -t <TIME> ban IP for TIME seconds" print " -f <TIME> lifetime in seconds of failed entry [findtime]"
print " -v verbose. Use twice for greater effect" print " -v verbose. Use twice for greater effect"
print " -w <FIWA> select the firewall to use. Can be iptables," print " -V print software version"
print " ipfwadm or ipfw"
print print
print "Report bugs to <lostcontrol@users.sourceforge.net>" print "Report bugs to <lostcontrol@users.sourceforge.net>"
sys.exit(0) sys.exit(0)
def dispVersion():
""" Prints Fail2Ban version and exits
"""
print sys.argv[0]+" "+version
sys.exit(0)
def checkForRoot(): def checkForRoot():
""" Check for root user. """ Check for root user.
""" """
@ -79,81 +86,6 @@ def checkForRoot():
else: else:
return False return False
def createDaemon():
"""Detach a process from the controlling terminal and run it in the
background as a daemon.
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
"""
try:
# Fork a child process so the parent can exit. This will return control
# to the command line or shell. This is required so that the new process
# is guaranteed not to be a process group leader. We have this guarantee
# because the process GID of the parent is inherited by the child, but
# the child gets a new PID, making it impossible for its PID to equal its
# PGID.
pid = os.fork()
except OSError, e:
return((e.errno, e.strerror)) # ERROR (return a tuple)
if (pid == 0): # The first child.
# Next we call os.setsid() to become the session leader of this new
# session. The process also becomes the process group leader of the
# new process group. Since a controlling terminal is associated with a
# session, and this new session has not yet acquired a controlling
# terminal our process now has no controlling terminal. This shouldn't
# fail, since we're guaranteed that the child is not a process group
# leader.
os.setsid()
# When the first child terminates, all processes in the second child
# are sent a SIGHUP, so it's ignored.
signal.signal(signal.SIGHUP, signal.SIG_IGN)
try:
# Fork a second child to prevent zombies. Since the first child is
# a session leader without a controlling terminal, it's possible for
# it to acquire one by opening a terminal in the future. This second
# fork guarantees that the child is no longer a session leader, thus
# preventing the daemon from ever acquiring a controlling terminal.
pid = os.fork() # Fork a second child.
except OSError, e:
return((e.errno, e.strerror)) # ERROR (return a tuple)
if (pid == 0): # The second child.
# Ensure that the daemon doesn't keep any directory in use. Failure
# to do this could make a filesystem unmountable.
os.chdir("/")
# Give the child complete control over permissions.
os.umask(0)
else:
os._exit(0) # Exit parent (the first child) of the second child.
else:
os._exit(0) # Exit parent of the first child.
# Close all open files. Try the system configuration variable, SC_OPEN_MAX,
# for the maximum number of open files to close. If it doesn't exist, use
# the default value (configurable).
try:
maxfd = os.sysconf("SC_OPEN_MAX")
except (AttributeError, ValueError):
maxfd = 256 # default maximum
for fd in range(0, maxfd):
try:
os.close(fd)
except OSError: # ERROR (ignore)
pass
# Redirect the standard file descriptors to /dev/null.
os.open("/dev/null", os.O_RDONLY) # standard input (0)
os.open("/dev/null", os.O_RDWR) # standard output (1)
os.open("/dev/null", os.O_RDWR) # standard error (2)
return True
def sigTERMhandler(signum, frame): def sigTERMhandler(signum, frame):
""" Handles the TERM signal when in daemon mode in order to """ Handles the TERM signal when in daemon mode in order to
exit properly. exit properly.
@ -161,233 +93,153 @@ def sigTERMhandler(signum, frame):
logSys.debug("Signal handler called with sig "+`signum`) logSys.debug("Signal handler called with sig "+`signum`)
killApp() killApp()
def setFwMustCheck(value):
""" Set the mustCheck value of the firewalls (True/False)
"""
for element in logFwList:
element[2].setMustCheck(value)
def initializeFwRules():
""" Initializes firewalls by running cmdstart and then
fwstart for each section
"""
# Execute global start command
executeCmd(conf["cmdstart"], conf["debug"])
# Execute start command of each section
for element in logFwList:
element[2].initialize(conf["debug"])
def reBan():
""" For each section asks the Firewall to reban known IPs
"""
for element in logFwList:
element[2].reBan(conf["debug"])
def restoreFwRules():
""" Flush the ban list
"""
logSys.warn("Restoring firewall rules...")
try:
for element in logFwList:
# Execute end command of each section
element[2].restore(conf["debug"])
# Execute global end command
executeCmd(conf["cmdend"], conf["debug"])
except ExternalError:
# nothing bad really - we can survive :-)
pass
def killApp(): def killApp():
""" Flush the ban list, remove the PID lock file and exit """ Flush the ban list, remove the PID lock file and exit
nicely. nicely.
""" """
logSys.warn("Restoring firewall rules...") # Restore Fw rules
fireWall.flushBanList(conf["debug"]) restoreFwRules()
removePID(conf["pidlock"]) # Remove the PID lock
pidLock.remove()
logSys.info("Exiting...") logSys.info("Exiting...")
logging.shutdown()
sys.exit(0) sys.exit(0)
def checkForPID(lockfile): def getCmdLineOptions(optList):
""" Checks for running Fail2Ban. """ Gets the command line options
Returns the current PID if Fail2Ban is running or False
if no instance found.
""" """
try:
fileHandler = open(lockfile)
pid = fileHandler.readline()
return pid
except IOError:
return False
def createPID(lockfile):
""" Creates a PID lock file with the current PID.
"""
fileHandler = open(lockfile, mode='w')
pid = os.getpid()
fileHandler.write(`pid`+'\n')
fileHandler.close()
logSys.debug("Created PID lock ("+`pid`+") in "+lockfile)
def removePID(lockfile):
""" Remove PID lock.
"""
os.remove(lockfile)
logSys.debug("Removed PID lock "+lockfile)
def killPID(pid):
""" Kills the process with the given PID using the
INT signal (same effect as <ctrl>+<c>).
"""
try:
return os.kill(pid, 2)
except OSError:
logSys.error("Can not kill process " + `pid` + ". Please check that " +
"Fail2Ban is not running and remove the file " +
"'/tmp/fail2ban.pid'")
if __name__ == "__main__":
# Gets an instance of log4py.
logSys = log4py.Logger().get_instance()
logSys.set_formatstring("%T %L %M")
conf = dict()
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:')
except getopt.GetoptError:
usage()
# Pre-parsing of command line options for the -c option
for opt in optList: 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] == "-h":
usage()
if opt[0] == "-v": if opt[0] == "-v":
conf["verbose"] = conf["verbose"] + 1 conf["verbose"] = conf["verbose"] + 1
if opt[0] == "-b": if opt[0] == "-b":
conf["background"] = True conf["background"] = True
if opt[0] == "-d": if opt[0] == "-d":
conf["debug"] = True 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": if opt[0] == "-t":
try: try:
conf["bantime"] = int(opt[1]) conf["bantime"] = int(opt[1])
except ValueError: except ValueError:
logSys.warn("banTime must be an integer") logSys.warn("banTime must be an integer")
logSys.warn("Using default value") logSys.warn("Using default value")
if opt[0] == "-f":
try:
conf["findtime"] = int(opt[1])
except ValueError:
logSys.warn("findTime must be an integer")
logSys.warn("Using default value")
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["maxfailures"] = int(opt[1])
if opt[0] == "-w":
conf["firewall"] = 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
def main():
""" Fail2Ban main function
"""
# Add the default logging handler
stdout = logging.StreamHandler(sys.stdout)
logSys.addHandler(stdout)
# Default formatter
formatterstring='%(levelname)s: %(message)s'
formatter = logging.Formatter('%(asctime)s ' + formatterstring)
stdout.setFormatter(formatter)
conf["kill"] = False
conf["verbose"] = 0
conf["conffile"] = "/etc/fail2ban.conf"
# Reads the command line options.
try:
cmdOpts = 'hvVbdkc: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]
if opt[0] in ["-h", "--help"]:
dispUsage()
if opt[0] in ["-V", "--version"]:
dispVersion()
# 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],
["str", "logtargets", "/var/log/fail2ban.log"],
["str", "syslog-target", "/dev/log"],
["int", "syslog-facility", 1],
["bool", "debug", False],
["str", "pidlock", "/var/run/fail2ban.pid"],
["int", "maxfailures", 5],
["int", "bantime", 600],
["int", "findtime", 600],
["str", "ignoreip", ""],
["int", "polltime", 1],
["str", "cmdstart", ""],
["str", "cmdend", ""],
["int", "reinittime", 100],
["int", "maxreinits", 100])
# Gets global configuration options
conf.update(confReader.getLogOptions("DEFAULT", optionValues))
# Gets command line options
getCmdLineOptions(optList)
# PID lock
pidLock.setPath(conf["pidlock"])
# Now we can kill properly a running instance if needed
if conf["kill"]:
pid = pidLock.exists()
if pid: if pid:
killPID(int(pid)) killPID(int(pid))
logSys.warn("Killed Fail2Ban with PID "+pid) logSys.warn("Killed Fail2Ban with PID "+pid)
@ -396,41 +248,86 @@ if __name__ == "__main__":
logSys.error("No running Fail2Ban found") logSys.error("No running Fail2Ban found")
sys.exit(-1) sys.exit(-1)
# Process some options # Start Fail2Ban in daemon mode
for c in conf: if conf["background"]:
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]:
logSys.set_loglevel(log4py.LOGLEVEL_DEBUG)
logSys.set_formatstring(log4py.FMT_DEBUG)
elif c == "background" and conf[c]:
retCode = createDaemon() retCode = createDaemon()
signal.signal(signal.SIGTERM, sigTERMhandler) signal.signal(signal.SIGTERM, sigTERMhandler)
logSys.set_target(conf["logfile"])
if not retCode: if not retCode:
logSys.error("Unable to start daemon") logSys.error("Unable to start daemon")
sys.exit(-1) sys.exit(-1)
elif c == "logging" and conf[c]:
try: # Process some options
open(conf["logfile"], "a") # First setup Log targets
logSys.set_target(conf["logfile"]) # Bug fix for #1234699
except IOError: os.umask(0077)
logSys.warn("Unable to log to "+conf["logfile"]) for target in conf["logtargets"].split():
logSys.warn("Using default output for logging") # target formatter
elif c == "ignoreip": # By default global formatter is taken. Is different for SYSLOG
ignoreIPList = conf[c].split(' ') tformatter = formatter
elif c == "firewall": if target == "STDERR":
conf[c] = string.lower(conf[c]) hdlr = logging.StreamHandler(sys.stderr)
if conf[c] == "ipfw": elif target == "SYSLOG":
fireWallName = "Ipfw" # SYSLOG target can be either
elif conf[c] == "ipfwadm": # a socket (file, so it starts with /)
fireWallName = "Ipfwadm" # or hostname
# or hostname:port
syslogtargets = re.findall("(/[\w/]*)|([^/ ][^: ]*)(:(\d+)){,1}",
conf["syslog-target"])
# we are waiting for a single match
syslogtargets = syslogtargets[0]
# assign facility if it was defined
if conf["syslog-facility"] < 0:
facility = handlers.SysLogHandler.LOG_USER
else: else:
fireWallName = "Iptables" facility = conf["syslog-facility"]
if len(syslogtargets) == 0: # everything default
hdlr = logging.handlers.SysLogHandler()
else:
if not ( syslogtargets[0] == "" ): # got socket
syslogtarget = syslogtargets[0]
else: # got hostname and maybe a port
if syslogtargets[3] == "": # no port specified
port = 514
else:
port = int(syslogtargets[3])
syslogtarget = (syslogtargets[1], port)
hdlr = logging.handlers.SysLogHandler(syslogtarget, facility)
tformatter = logging.Formatter("fail2ban[%(process)d]: " +
formatterstring);
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(tformatter)
logSys.addHandler(hdlr)
# 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)
formatterstring = ('%(levelname)s: [%(filename)s (%(lineno)d)] ' +
'%(message)s')
formatter = logging.Formatter("%(asctime)s " + formatterstring)
stdout.setFormatter(formatter)
logSys.warn("DEBUG MODE: FIREWALL COMMANDS ARE _NOT_ EXECUTED BUT " +
"ONLY DISPLAYED IN THE LOG MESSAGES")
# Ignores IP list
ignoreIPList = conf["ignoreip"].split(' ')
# 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.
@ -440,51 +337,93 @@ if __name__ == "__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"]) ret = pidLock.create()
if not ret:
# Unable to create PID lock. Exit
sys.exit(-1)
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("FindTime is " + `conf["findtime"]`)
logSys.debug("MaxFailure is " + `conf["maxfailures"]`)
# Reads the config file and create a LogReader instance for # Options
# each log file to check. optionValues = (["bool", "enabled", False],
confReader = ConfigReader(logSys, conf["conffile"]); ["str", "host", "localhost"],
confReader.openConf() ["int", "port", "25"],
logList = list() ["str", "from", "root"],
["str", "to", "root"],
["bool", "localtime", False],
["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"])
mail.setLocalTimeFlag(mailConf["localtime"])
logSys.debug("to: " + mailConf["to"] + " from: " + mailConf["from"])
# Options
optionValues = (["bool", "enabled", False],
["str", "logfile", "/dev/null"],
["int", "maxfailures", conf["maxfailures"]],
["int", "bantime", conf["bantime"]],
["int", "findtime", conf["findtime"]],
["str", "timeregex", ""],
["str", "timepattern", ""],
["str", "failregex", ""],
["str", "fwstart", ""],
["str", "fwend", ""],
["str", "fwban", ""],
["str", "fwunban", ""],
["str", "fwcheck", ""])
logSys.info("Fail2Ban v" + version + " is running")
# Gets the options of each sections
for t in confReader.getSections(): for t in confReader.getSections():
l = confReader.getLogOptions(t) l = confReader.getLogOptions(t, optionValues)
if l["enabled"]: if l["enabled"]:
lObj = LogReader(logSys, l["logfile"], l["timeregex"], # Creates a logreader object
l["timepattern"], l["failregex"], conf["bantime"]) lObj = LogReader(l["logfile"], l["timeregex"], l["timepattern"],
lObj.setName(t) l["failregex"], l["maxfailures"], l["findtime"])
logList.append(lObj) # Creates a firewall object
fObj = Firewall(l["fwstart"], l["fwend"], l["fwban"], l["fwunban"],
# Creates one instance of Iptables (thanks to Pyhton dynamic l["fwcheck"], l["bantime"])
# features). # "Name" the firewall
fireWallObj = eval(fireWallName) fObj.setSection(t)
fireWall = fireWallObj(conf["bantime"], logSys, conf["interface"]) # Links them into a list. I'm not really happy
# with this :/
# IPFW needs rules number. The configuration option "ipfw-start-rule" logFwList.append([t, lObj, fObj, dict(), l])
# defines the first rule number used by Fail2Ban.
if fireWallName == "Ipfw":
fireWall.setCrtRuleNbr(conf["ipfw-start-rule"])
# We add 127.0.0.1 to the ignore list has we do not want # We add 127.0.0.1 to the ignore list has we do not want
# to be ban ourself. # to be ban ourself.
for element in logList: for element in logFwList:
element.addIgnoreIP("127.0.0.1") element[1].addIgnoreIP("127.0.0.1")
while len(ignoreIPList) > 0: while len(ignoreIPList) > 0:
ip = ignoreIPList.pop() ip = ignoreIPList.pop()
for element in logList: # Bug fix for #1239557
element.addIgnoreIP(ip) if isValidIP(ip):
for element in logFwList:
element[1].addIgnoreIP(ip)
else:
logSys.warn(ip + " is not a valid IP address")
logSys.info("Fail2Ban v"+version+" is running") initializeFwRules()
failListFull = dict() # try to reinit once if it fails immediately
lastReinitTime = time.time() - conf["reinittime"] - 1
reinits = 0
# Main loop # Main loop
while True: while True:
try: try:
@ -493,14 +432,15 @@ if __name__ == "__main__":
# Checks if some IP have to be remove from ban # Checks if some IP have to be remove from ban
# list. # list.
fireWall.checkForUnBan(conf["debug"]) for element in logFwList:
element[2].checkForUnBan(conf["debug"])
# If the log file has not been modified since the # If the log file has not been modified since the
# last time, we sleep for 1 second. This is active # last time, we sleep for 1 second. This is active
# polling so not very effective. # polling so not very effective.
modList = list() modList = list()
for element in logList: for element in logFwList:
if element.isModified(): if element[1].isModified():
modList.append(element) modList.append(element)
if len(modList) == 0: if len(modList) == 0:
@ -509,43 +449,73 @@ if __name__ == "__main__":
# Gets the failure list from the log file. For a given IP, # Gets the failure list from the log file. For a given IP,
# takes only the service which has the most password failures. # takes only the service which has the most password failures.
failList = dict()
for element in modList: for element in modList:
e = element.getFailures() e = element[1].getFailures()
for key in e.iterkeys(): for key in e.iterkeys():
if failList.has_key(key): if element[3].has_key(key):
if failList[key][0] < e[key][0]: element[3][key] = (element[3][key][0] + e[key][0],
failList[key] = (e[key][0], e[key][1], element) e[key][1])
else: else:
failList[key] = (e[key][0], e[key][1], element) element[3][key] = (e[key][0], e[key][1])
# 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]
# Remove the oldest failure attempts from the global list. # 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 # We iterate the failure list and ban IP that make
# *retryAllowed* login failures. # *retryAllowed* login failures.
failListFullTemp = failListFull.copy() unixTime = time.time()
for key in failListFullTemp.iterkeys(): for element in logFwList:
element = failListFullTemp[key] fails = element[3].copy()
if element[0] >= conf["maxretry"]: findTime = element[1].getFindTime()
logSys.info(element[2].getName()+": "+key+" has "+ for attempt in fails:
`element[0]`+" login failure(s). Banned.") failTime = fails[attempt][1]
fireWall.addBanIP(key, conf["debug"]) if failTime < unixTime - findTime:
del failListFull[key] del element[3][attempt]
elif fails[attempt][0] >= element[1].getMaxRetry():
aInfo = {"section": element[0],
"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 ExternalError, e:
# Something wrong while dealing with Iptables.
# May be chain got removed?
reinits += 1
logSys.error(e)
if ((unixTime - lastReinitTime > conf["reinittime"]) and
((conf["maxreinits"] < 0) or (reinits < conf["maxreinits"]))):
logSys.warn("#%d reinitialization of firewalls"%reinits)
lastReinitTime = unixTime
else:
logSys.error("Exiting: reinits follow too often, or too many " +
"reinit attempts")
killApp()
# We already failed runCheck so disable it until
# restoring a safe state
setFwMustCheck(False)
# save firewalls to keep a list of IPs for rebanning
logFwListCopy = copy.deepcopy(logFwList)
try:
# restore as much as possible
restoreFwRules()
# reinitialize all the chains
initializeFwRules()
# restore the lists of baned IPs
logFwList.__init__(logFwListCopy)
# reBan known IPs
reBan()
# Now we can enable the runCheck test again
setFwMustCheck(True)
except ExternalError:
raise ExternalError("Big Oops happened: situation is out of " +
"control. Something is wrong with your " +
"setup. Please check your settings")
except KeyboardInterrupt: except KeyboardInterrupt:
# When the user press <ctrl>+<c> we exit nicely. # When the user press <ctrl>+<c> we exit nicely.
killApp() killApp()

View File

@ -24,54 +24,130 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import time, os import time, os, logging, re
from utils.process import executeCmd
from utils.strings import replaceTag
# unfortunately but I have to bring ExternalError in especially for
# flushBanList: if one of IPs got flushed manually outside or something, we
# might endup with not "full" flush unless we handle exception within the loop
from utils.process import ExternalError
# Gets the instance of the logger.
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
the IP. the IP.
""" """
banList = dict() def __init__(self, startRule, endRule, banRule, unBanRule, checkRule,
banTime):
def __init__(self, banTime, logSys, interface): self.banRule = banRule
self.unBanRule = unBanRule
self.checkRule = checkRule
self.startRule = startRule
self.endRule = endRule
self.banTime = banTime self.banTime = banTime
self.logSys = logSys self.banList = dict()
self.interface = interface self.section = ""
self.mustCheck = True
def addBanIP(self, ip, debug): def setSection(self, section):
""" Set optional section name for clarify of logging
"""
self.section = section
def getMustCheck(self):
""" Return true if the runCheck test is executed
"""
return self.mustCheck
def setMustCheck(self, value):
""" Enable or disable the execution of runCheck test
"""
self.mustCheck = value
def initialize(self, debug):
logSys.debug("%s: Initialize firewall rules"%self.section)
executeCmd(self.startRule, debug)
def restore(self, debug):
logSys.debug("%s: Restore firewall rules"%self.section)
try:
self.flushBanList(debug)
executeCmd(self.endRule, debug)
except ExternalError:
pass
def addBanIP(self, aInfo, debug):
""" Bans an IP. """ Bans an IP.
""" """
ip = aInfo["ip"]
if not self.inBanList(ip): if not self.inBanList(ip):
self.logSys.warn("Ban "+ip) crtTime = time.time()
self.banList[ip] = time.time() logSys.warn("%s: Ban "%self.section + ip)
self.__executeCmd(self.banIP(ip), debug) self.banList[ip] = crtTime
aInfo["bantime"] = crtTime
self.runCheck(debug)
cmd = self.banIP(aInfo)
if executeCmd(cmd, debug):
raise ExternalError("Firewall: execution of fwban command " +
"'%s' failed"%cmd)
else: else:
self.logSys.error(ip+" already in ban list") self.runCheck(debug)
logSys.error("%s: "%self.section+ip+" already in ban list")
def delBanIP(self, ip, debug): def delBanIP(self, aInfo, debug):
""" Unban an IP. """ Unban an IP.
""" """
ip = aInfo["ip"]
if self.inBanList(ip): if self.inBanList(ip):
self.logSys.warn("Unban "+ip) logSys.warn("%s: Unban "%self.section + ip)
del self.banList[ip] del self.banList[ip]
self.__executeCmd(self.unBanIP(ip), debug) self.runCheck(debug)
executeCmd(self.unBanIP(aInfo), debug)
else: else:
self.logSys.error(ip+" not in ban list") logSys.error("%s: "%self.section+ip+" not in ban list")
def reBan(self, debug):
""" Re-Bans known IPs.
TODO: implement "failures" and "failtime"
"""
for ip in self.banList:
aInfo = {"ip": ip,
"bantime":self.banList[ip]}
logSys.warn("%s: ReBan "%self.section + ip)
# next piece is similar to the on in addBanIp
# so might be one more function will not hurt
self.runCheck(debug)
executeCmd(self.banIP(aInfo), debug)
def inBanList(self, ip): def inBanList(self, ip):
""" Checks if IP is in ban list. """ Checks if IP is in ban list.
""" """
return self.banList.has_key(ip) return self.banList.has_key(ip)
def runCheck(self, debug):
""" Runs fwcheck command and throws an exception if it returns non-0
result
"""
if self.mustCheck:
executeCmd(self.checkRule, debug)
else:
return None
def checkForUnBan(self, debug): def checkForUnBan(self, debug):
""" Check for IP to remove from ban list. """ Check for IP to remove from ban list.
""" """
banListTemp = self.banList.copy() banListTemp = self.banList.copy()
for element in banListTemp.iteritems(): for element in banListTemp.iteritems():
ip = element[0]
btime = element[1] btime = element[1]
if btime < time.time()-self.banTime: 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): def flushBanList(self, debug):
""" Flushes the ban list and of course the firewall rules. """ Flushes the ban list and of course the firewall rules.
@ -79,17 +155,27 @@ class Firewall:
""" """
banListTemp = self.banList.copy() banListTemp = self.banList.copy()
for element in banListTemp.iteritems(): for element in banListTemp.iteritems():
ip = element[0] aInfo = {"ip": element[0],
self.delBanIP(ip, debug) "bantime": element[1],
"unbantime": time.time()}
try:
self.delBanIP(aInfo, debug)
except ExternalError:
# we must let it fail here in the loop, or we don't
# flush properly
pass
def __executeCmd(self, cmd, debug): def banIP(self, aInfo):
""" Executes an OS command. """ Returns query to ban IP.
""" """
self.logSys.debug(cmd) query = replaceTag(self.banRule, aInfo)
if not debug: return query
return os.system(cmd)
else: def unBanIP(self, aInfo):
return None """ Returns query to unban IP.
"""
query = replaceTag(self.unBanRule, aInfo)
return query
def viewBanList(self): def viewBanList(self):
""" Prints the ban list on screen. Usefull for debugging. """ 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$
__author__ = "Cyril Jaquier"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os
from firewall import Firewall
class Ipfw(Firewall):
""" This class contains specific methods and variables for the
iptables firewall. Must implements the 'abstracts' methods
banIP(ip) and unBanIP(ip).
Must adds abstract methods definition:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266468
"""
crtRuleNbr = 0
def getCrtRuleNbr(self):
""" Gets the current rule number.
"""
return self.crtRuleNbr
def setCrtRuleNbr(self, value):
""" Sets the current rule number.
"""
self.crtRuleNbr = value
def banIP(self, ip):
""" Returns query to ban IP.
"""
query = "ipfw -q add "+`self.crtRuleNbr`+" deny ip from "+ip+" to any"
self.crtRuleNbr = self.crtRuleNbr + 1
return query
def unBanIP(self, ip):
""" Returns query to unban IP.
"""
ruleNbr = str(self.__findRuleNumber(ip))
query = "ipfw -q delete "+ruleNbr
return query
def __findRuleNumber(self, ip):
""" Uses shell commands in order to find the rule
number we want to delete.
"""
output = os.popen("ipfw list|grep \"from "+ip+" to\"|awk '{print $1}'",
"r");
return output.read()

View File

@ -1,48 +0,0 @@
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Author: Cyril Jaquier
#
# $Revision$
__author__ = "Cyril Jaquier"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from firewall import Firewall
class Iptables(Firewall):
""" This class contains specific methods and variables for the
iptables firewall. Must implements the 'abstracts' methods
banIP(ip) and unBanIP(ip).
Must adds abstract methods definition:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266468
"""
def banIP(self, ip):
""" Returns query to ban IP.
"""
query = "iptables -I INPUT 1 -i "+self.interface+" -s "+ip+" -j DROP"
return query
def unBanIP(self, ip):
""" Returns query to unban IP.
"""
query = "iptables -D INPUT -i "+self.interface+" -s "+ip+" -j DROP"
return query

View File

@ -1,3 +1,4 @@
[Wed Mar 31 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch
[Mon Jan 03 05:02:15 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc [Mon Jan 03 05:02:15 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc
[Mon Jan 03 05:02:20 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc [Mon Jan 03 05:02:20 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc
[Mon Jan 03 05:02:20 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc [Mon Jan 03 05:02:20 2005] [error] [client 81.83.248.17] File does not exist: /var/www/jaquier.dyndns.org/htdocs/msadc
@ -98,5 +99,10 @@
[Wed Jan 05 15:14:24 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico [Wed Jan 05 15:14:24 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico
[Wed Jan 05 15:14:29 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico [Wed Jan 05 15:14:29 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico
[Wed Jan 05 15:14:34 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico [Wed Jan 05 15:14:34 2005] [error] [client 192.168.0.129] File does not exist: /var/www/jaquier.dyndns.org/htdocs/favicon.ico
[Wed Mar 05 15:08:28 2005] [error] [client 192.168.0.128] user cyril: authentication failure for "/phpinfo": Password Mismatch [Wed Mar 10 15:08:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch
[Wed Mar 05 15:09:28 2005] [error] [client 192.168.0.128] user cyril: authentication failure for "/phpinfo": Password Mismatch [Wed Mar 10 15:09:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch
[Wed Mar 10 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch
[Wed Mar 30 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch
[Wed Mar 30 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch
[Wed Mar 30 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch
[Wed Mar 30 15:11:28 2005] [error] [client www.google.ch] user cyril: authentication failure for "/phpinfo": Password Mismatch

View File

@ -3,3 +3,9 @@ Jan 7 17:53:26 [sshd] (pam_unix) authentication failure; logname= uid=0 euid=0
Mar 7 17:53:28 [sshd] error: PAM: Authentication failure for kevin from 62.220.137.36 Mar 7 17:53:28 [sshd] error: PAM: Authentication failure for kevin from 62.220.137.36
Mar 7 17:55:28 [sshd] error: PAM: Authentication failure for kevin from 62.220.137.36 Mar 7 17:55:28 [sshd] error: PAM: Authentication failure for kevin from 62.220.137.36
Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 62.220.137.36 Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 62.220.137.36
Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 192.168.0.128
Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 192.168.0.128
Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 192.168.0.128
Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from 192.168.0.128
Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from www.google.ch
Mar 7 17:57:28 [sshd] error: PAM: Authentication failure for kevin from www.google.ch

View File

@ -24,39 +24,37 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import os, sys, time, re import os, sys, time, re, logging
from utils.dns import * from utils.dns import *
# Gets the instance of the logger.
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
failure, bad user or anything else that is considered as doubtful login failure, bad user or anything else that is considered as doubtful login
attempt. attempt.
""" """
def __init__(self, logSys, 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
self.findTime = findTime self.findTime = findTime
self.ignoreIpList = [] self.ignoreIpList = []
self.lastModTime = 0 self.lastModTime = 0
self.logSys = logSys
self.lastPos = 0 self.lastPos = 0
self.lastDate = 0 self.lastDate = 0
self.logStats = None self.logStats = None
def setName(self, name): def getMaxRetry(self):
""" Sets the name of the log reader. """ Gets the maximum number of failures
""" """
self.name = name return self.maxRetry
def getName(self):
""" Gets the name of the log reader.
"""
return self.name
def getFindTime(self): def getFindTime(self):
""" Gets the find time. """ Gets the find time.
@ -66,13 +64,23 @@ class LogReader:
def addIgnoreIP(self, ip): def addIgnoreIP(self, ip):
""" Adds an IP to the ignore list. """ 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) self.ignoreIpList.append(ip)
def inIgnoreIPList(self, ip): def inIgnoreIPList(self, ip):
""" Checks if IP is in the ignore list. """ 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): def openLogFile(self):
""" Opens the log file specified on init. """ Opens the log file specified on init.
@ -80,8 +88,8 @@ class LogReader:
try: try:
fileHandler = open(self.logPath) fileHandler = open(self.logPath)
except OSError: except OSError:
self.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):
@ -89,16 +97,15 @@ class LogReader:
""" """
try: try:
self.logStats = os.stat(self.logPath) self.logStats = os.stat(self.logPath)
except OSError:
self.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
else: else:
self.logSys.debug(self.logPath+" has been modified") logSys.debug(self.logPath+" has been modified")
self.lastModTime = self.logStats.st_mtime self.lastModTime = self.logStats.st_mtime
return True return True
except OSError:
logSys.error("Unable to get stat on "+self.logPath)
return False
def setFilePos(self, file): def setFilePos(self, file):
""" Sets the file position. We must take care of log file rotation """ Sets the file position. We must take care of log file rotation
@ -107,13 +114,13 @@ class LogReader:
""" """
line = file.readline() line = file.readline()
if self.lastDate < self.getTime(line): if self.lastDate < self.getTime(line):
self.logSys.debug("Date " + `self.lastDate` + " is " + logSys.debug("Date " + `self.lastDate` + " is " + "smaller than " +
"smaller than " + `self.getTime(line)`) `self.getTime(line)`)
self.logSys.debug("Log rotation detected for " + self.logPath) logSys.debug("Log rotation detected for " + self.logPath)
self.lastPos = 0 self.lastPos = 0
self.logSys.debug("Setting file position to " + `self.lastPos` + " for " logSys.debug("Setting file position to " + `self.lastPos` + " for " +
+ self.logPath) self.logPath)
file.seek(self.lastPos) file.seek(self.lastPos)
def getFailures(self): def getFailures(self):
@ -124,27 +131,30 @@ class LogReader:
and the latest failure time. and the latest failure time.
""" """
ipList = dict() ipList = dict()
self.logSys.debug(self.logPath) logSys.debug(self.logPath)
logFile = self.openLogFile() logFile = self.openLogFile()
self.setFilePos(logFile) self.setFilePos(logFile)
lastLine = '' lastLine = None
for line in logFile.readlines(): for line in logFile:
if not self.hasTime(line):
# There is no valid time in this line
continue
lastLine = line lastLine = line
failList = self.findFailure(line) for element in self.findFailure(line):
for element in failList:
ip = element[0] ip = element[0]
unixTime = element[1] unixTime = element[1]
if unixTime < time.time()-self.findTime: if unixTime < time.time()-self.findTime:
break break
if self.inIgnoreIPList(ip): if self.inIgnoreIPList(ip):
self.logSys.debug("Ignore "+ip) logSys.debug("Ignore "+ip)
continue continue
self.logSys.debug("Found "+ip) logSys.debug("Found "+ip)
if ipList.has_key(ip): if ipList.has_key(ip):
ipList[ip] = (ipList[ip][0]+1, unixTime) ipList[ip] = (ipList[ip][0]+1, unixTime)
else: else:
ipList[ip] = (1, unixTime) ipList[ip] = (1, unixTime)
self.lastPos = logFile.tell() self.lastPos = logFile.tell()
if lastLine:
self.lastDate = self.getTime(lastLine) self.lastDate = self.getTime(lastLine)
logFile.close() logFile.close()
return ipList return ipList
@ -168,6 +178,15 @@ class LogReader:
failList.append([ip, date]) failList.append([ip, date])
return failList return failList
def hasTime(self, line):
""" Return true if the line contains a date
"""
timeMatch = re.search(self.timeregex, line)
if timeMatch:
return True
else:
return False
def getTime(self, line): def getTime(self, line):
""" Gets the time of a log message. """ Gets the time of a log message.
""" """
@ -184,6 +203,12 @@ class LogReader:
""" """
date = list(time.strptime(value, self.timepattern)) date = list(time.strptime(value, self.timepattern))
if date[0] < 2000: if date[0] < 2000:
# There is probably no year field in the logs
date[0] = time.gmtime()[0] date[0] = time.gmtime()[0]
# Bug fix for #1241756
# If the date is greater than the current time, we suppose
# that the log is not from this year but from the year before
if time.mktime(date) > time.time():
date[0] -= 1
unixTime = time.mktime(date) unixTime = time.mktime(date)
return unixTime return unixTime

61
man/fail2ban.8 Normal file
View File

@ -0,0 +1,61 @@
.\"
.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 [maxfailures]
.TP
\fB\-t\fR \fITIME\fR
ban IP for \fITIME\fR seconds [bantime]
.TP
\fB\-f\fR \fITIME\fR
lifetime in seconds of failed entry [findtime]
.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

@ -28,15 +28,55 @@ __license__ = "GPL"
from distutils.core import setup from distutils.core import setup
from version import version from version import version
from os.path import isfile, join
from sys import exit, argv
longdesc = '''
Fail2Ban scans log files like /var/log/pwdfail or
/var/log/apache/error_log and bans IP that makes
too many password failures. It updates firewall rules
to reject the IP address or executes user defined
commands.'''
setup( 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://www.sourceforge.net/projects/fail2ban", url = "http://fail2ban.sourceforge.net",
scripts = ['fail2ban.py'], license = "GPL",
py_modules = ['version'], platforms = "Posix",
scripts = ['fail2ban'],
py_modules = ['fail2ban', 'version'],
packages = ['firewall', 'logreader', 'confreader', 'utils'] packages = ['firewall', 'logreader', 'confreader', 'utils']
) )
# Do some checks after installation
# Search for obsolete files.
obsoleteFiles = []
elements = {"/usr/bin/": ["fail2ban.py"],
"/usr/lib/fail2ban/firewall/": ["iptables.py", "ipfwadm.py",
"ipfw.py"]}
for dir in elements:
for f in elements[dir]:
path = join(dir, f)
if isfile(path):
obsoleteFiles.append(path)
if obsoleteFiles:
print
print "Obsolete files from previous Fail2Ban versions were found on " \
"your system."
print "Please delete them:"
print
for f in obsoleteFiles:
print "\t" + f
print
# Update config file
if argv[1] == "install":
print
print "Please do not forget to update your configuration file."
print "Use config/fail2ban.conf.default as example."
print

View File

@ -24,7 +24,7 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import os, re, socket import os, re, socket, struct
def dnsToIp(dns): def dnsToIp(dns):
""" Convert a DNS into an IP address using the Python socket module. """ Convert a DNS into an IP address using the Python socket module.
@ -55,6 +55,16 @@ def searchIP(text):
else: else:
return [] return []
def isValidIP(str):
""" Return true if str is a valid IP
"""
s = str.split('/', 1)
try:
socket.inet_aton(s[0])
return True
except socket.error:
return False
def textToIp(text): def textToIp(text):
""" Return the IP of DNS found in a given text. """ Return the IP of DNS found in a given text.
""" """
@ -62,6 +72,7 @@ def textToIp(text):
# Search for plain IP # Search for plain IP
plainIP = searchIP(text) plainIP = searchIP(text)
for element in plainIP: for element in plainIP:
if isValidIP(element):
ipList.append(element) ipList.append(element)
if not ipList: if not ipList:
# Try to get IP from possible DNS # Try to get IP from possible DNS
@ -71,3 +82,21 @@ def textToIp(text):
for e in dns: for e in dns:
ipList.append(e) ipList.append(e)
return ipList 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))

79
utils/mail.py Normal file
View File

@ -0,0 +1,79 @@
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Author: Cyril Jaquier
#
# $Revision$
__author__ = "Cyril Jaquier"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import logging, smtplib, email.Utils
from utils.strings import replaceTag
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban")
class Mail:
""" Mailer class
"""
def __init__(self, host, port = 25):
self.host = host
self.port = port
self.localTimeFlag = False
def setFromAddr(self, fromAddr):
""" Set from: address
"""
self.fromAddr = fromAddr
def setToAddr(self, toAddr):
""" Set to: address
"""
self.toAddr = toAddr.split()
def setLocalTimeFlag(self, localTimeFlag):
""" Set to: address
"""
self.localTimeFlag = localTimeFlag
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\nDate: %s\r\nSubject: %s\r\n\r\n" %
(self.fromAddr, ", ".join(self.toAddr),
email.Utils.formatdate(localtime = self.localTimeFlag),
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, e:
logSys.error("Unable to send mail to " + self.host + ":" +
`self.port` + " from " + self.fromAddr + " to " +
`self.toAddr` + ": " + `e` + ": " + `e.args`)

109
utils/pidlock.py Normal file
View File

@ -0,0 +1,109 @@
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Author: Cyril Jaquier
#
# $Revision$
__author__ = "Cyril Jaquier"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os, 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.
"""
try:
fileHandler = open(self.path, mode='w')
pid = os.getpid()
fileHandler.write(`pid` + '\n')
fileHandler.close()
logSys.debug("Created PID lock (" + `pid` + ") in " + self.path)
return True
except:
logSys.error("Unable to create PID lock " + self.path)
return False
def remove(self):
""" Remove PID lock.
"""
try:
os.remove(self.path)
logSys.debug("Removed PID lock " + self.path)
except OSError:
logSys.error("Unable to remove PID lock " + self.path)
except AttributeError:
# AttributeError if self.path wasn't specified yet
logSys.debug("PID lock not removed because not defined yet")
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)

137
utils/process.py Normal file
View File

@ -0,0 +1,137 @@
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Author: Cyril Jaquier
#
# $Revision$
__author__ = "Cyril Jaquier"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
import os, logging, signal
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban")
class ExternalError(UserWarning):
""" Exception to warn about failed fwcheck or fwban command
"""
pass
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("/")
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 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:
retval = os.system(cmd)
if not retval == 0:
logSys.error("'" + cmd + "' returned " + `retval`)
raise ExternalError("Execution of command '%s' failed" % cmd)
return retval
else:
return None

View File

@ -24,25 +24,17 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
from firewall import Firewall import logging
class Ipfwadm(Firewall): # Gets the instance of the logger.
""" This class contains specific methods and variables for the logSys = logging.getLogger("fail2ban")
iptables firewall. Must implements the 'abstracts' methods
banIP(ip) and unBanIP(ip).
Must adds abstract methods definition: def replaceTag(query, aInfo):
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266468 """ Replace tags in query
""" """
string = query
def banIP(self, ip): for tag in aInfo:
""" Returns query to ban IP. string = string.replace('<'+tag+'>', `aInfo[tag]`)
""" # New line
query = "ipfwadm -I -a deny -W "+self.interface+" -S "+ip string = string.replace('<br>', '\n')
return query return string
def unBanIP(self, ip):
""" Returns query to unban IP.
"""
query = "ipfwadm -I -d deny -W "+self.interface+" -S "+ip
return query

View File

@ -24,4 +24,4 @@ __date__ = "$Date$"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier" __copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
version = "0.4.1" version = "0.6.0"