mirror of https://github.com/fail2ban/fail2ban
merge master
commit
e3a037ee3f
|
@ -8,3 +8,4 @@ htmlcov
|
|||
*.rej
|
||||
*.bak
|
||||
__pycache__
|
||||
.vagrant/
|
||||
|
|
|
@ -16,6 +16,8 @@ install:
|
|||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cd ..; pip install -q coveralls; cd -; fi
|
||||
script:
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then coverage run --rcfile=.travis_coveragerc setup.py test; else python setup.py test; fi
|
||||
# test installation
|
||||
- sudo python setup.py install
|
||||
after_success:
|
||||
# Coverage config file must be .coveragerc for coveralls
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then cp -v .travis_coveragerc .coveragerc; fi
|
||||
|
|
35
ChangeLog
35
ChangeLog
|
@ -16,6 +16,9 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger
|
|||
provides defaults for the chain, port, protocol and name tags
|
||||
|
||||
- Fixes:
|
||||
* start of file2ban aborted (on slow hosts, systemd considers the server has
|
||||
been timed out and kills him), see gh-824
|
||||
* UTF-8 fixes in pure-ftp thanks to Johannes Weberhofer. Closes gh-806.
|
||||
* systemd backend error on bad utf-8 in python3
|
||||
* badips.py action error when logging HTTP error raised with badips request
|
||||
* fail2ban-regex failed to work in python3 due to space/tab mix
|
||||
|
@ -36,18 +39,48 @@ ver. 0.9.1 (2014/xx/xx) - better, faster, stronger
|
|||
Thanks Serg G. Brester
|
||||
* Correct times for non-timezone date times formats during DST
|
||||
* Pass a copy of, not original, aInfo into actions to avoid side-effects
|
||||
* Per-distribution paths to the exim's main log
|
||||
* Ignored IPs are no longer banned when being restored from persistent
|
||||
database
|
||||
* Manually unbanned IPs are now removed from persistent database, such they
|
||||
wont be banned again when Fail2Ban is restarted
|
||||
* Pass "bantime" parameter to the actions in default jail's action
|
||||
definition(s)
|
||||
* filters.d/sieve.conf - fixed typo in _daemon. Thanks Jisoo Park
|
||||
* cyrus-imap -- also catch also failed logins via secured (imaps/pop3s).
|
||||
Regression was introduced while strengthening failregex in 0.8.11 (bd175f)
|
||||
Debian bug #755173
|
||||
* postfix-sasl - added journalmatch. Thanks Luc Maisonobe
|
||||
* postfix* - match with a new daemon string (postfix/submission/smtpd).
|
||||
Closes gh-804 . Thanks Paul Traina
|
||||
* apache - added filter for AH01630 client denied by server configuration.
|
||||
|
||||
- New features:
|
||||
- Added monit filter thanks Jason H Martin.
|
||||
- New filters:
|
||||
- monit Thanks Jason H Martin
|
||||
- directadmin Thanks niorg
|
||||
- apache-shellshock Thanks Eugene Hopkinson (SlowRiot)
|
||||
- New actions:
|
||||
- symbiosis-blacklist-allports for Bytemark symbiosis firewall
|
||||
- fail2ban-client can fetch the running server version
|
||||
- Added Cloudflare API action
|
||||
|
||||
- Enhancements
|
||||
* Start performance of fail2ban-client (and tests) increased, start time
|
||||
and cpu usage rapidly reduced. Introduced a shared storage logic, to
|
||||
bypass reading lots of config files (see gh-824).
|
||||
Thanks to Joost Molenaar for good catch (reported gh-820).
|
||||
* Fail2ban-regex - add print-all-matched option. Closes gh-652
|
||||
* Suppress fail2ban-client warnings for non-critical config options
|
||||
* Match non "Bye Bye" disconnect messages for sshd locked account regex
|
||||
* courier-smtp filter:
|
||||
- match lines with user names
|
||||
- match lines containing "535 Authentication failed" attempts
|
||||
* Add <chain> tag to iptables-ipsets
|
||||
* Realign fail2ban log output with white space to improve readability. Does
|
||||
not affect SYSLOG output
|
||||
* Log unhandled exceptions
|
||||
* cyrus-imap: catch "user not found" attempts
|
||||
|
||||
ver. 0.9.0 (2014/03/14) - beta
|
||||
----------
|
||||
|
|
12
DEVELOP
12
DEVELOP
|
@ -81,6 +81,18 @@ some quick commands::
|
|||
status test
|
||||
|
||||
|
||||
Testing with vagrant
|
||||
--------------------
|
||||
|
||||
Testing can now be done inside a vagrant VM. Vagrantfile provided in
|
||||
source code repository established two VMs:
|
||||
|
||||
- VM "secure" which can be used for testing fail2ban code.
|
||||
- VM "attacker" which hcan be used to perform attack against our "secure" VM.
|
||||
|
||||
Both VMs are sharing the 192.168.200/24 network. If you are using this network
|
||||
take a look into the Vagrantfile and change the IP.
|
||||
|
||||
|
||||
Coding Standards
|
||||
================
|
||||
|
|
10
THANKS
10
THANKS
|
@ -26,6 +26,7 @@ Christian Rauch
|
|||
Christophe Carles
|
||||
Christoph Haas
|
||||
Christos Psonis
|
||||
craneworks
|
||||
Cyril Jaquier
|
||||
Daniel B. Cid
|
||||
Daniel B.
|
||||
|
@ -34,6 +35,7 @@ David Nutter
|
|||
Derek Atkins
|
||||
Eric Gerbier
|
||||
Enrico Labedzki
|
||||
Eugene Hopkinson (SlowRiot)
|
||||
ftoppi
|
||||
François Boulogne
|
||||
Frédéric
|
||||
|
@ -44,11 +46,13 @@ Hank Leininger
|
|||
Hanno 'Rince' Wagner
|
||||
Helmut Grohne
|
||||
Iain Lea
|
||||
Ioan Indreias
|
||||
Ivo Truxa
|
||||
John Thoe
|
||||
Jacques Lav!gnotte
|
||||
Ioan Indreias
|
||||
Johannes Weberhofer
|
||||
Jason H Martin
|
||||
Jisoo Park
|
||||
Joel M Snyder
|
||||
Jonathan Kamens
|
||||
Jonathan Lanning
|
||||
|
@ -62,6 +66,7 @@ kjohnsonecl
|
|||
kojiro
|
||||
Lars Kneschke
|
||||
Lee Clemens
|
||||
leftyfb (Mike Rushton)
|
||||
Manuel Arostegui Ramirez
|
||||
Marcel Dopita
|
||||
Mark Edgington
|
||||
|
@ -78,8 +83,10 @@ Mika (mkl)
|
|||
Nick Munger
|
||||
onorua
|
||||
Paul Marrapese
|
||||
Paul Traina
|
||||
Noel Butler
|
||||
Patrick Börjesson
|
||||
Pressy
|
||||
Raphaël Marichez
|
||||
RealRancor
|
||||
René Berber
|
||||
|
@ -88,6 +95,7 @@ Rolf Fokkens
|
|||
Roman Gelfand
|
||||
Russell Odom
|
||||
SATO Kentaro
|
||||
Sean DuBois
|
||||
Sebastian Arcus
|
||||
Serg G. Brester
|
||||
Sireyessire
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
Vagrant.configure("2") do |config|
|
||||
|
||||
config.vm.define "secure" do |secure|
|
||||
secure.vm.box = "ubuntu/trusty64"
|
||||
secure.vm.hostname = "secure.dev.fail2ban.org"
|
||||
secure.vm.network "private_network", ip: "192.168.200.100"
|
||||
|
||||
# secure.vm.synced_folder 'salt/roots', '/srv/salt'
|
||||
|
||||
# secure.vm.provision :salt do |salt|
|
||||
# salt.minion_config = 'salt/minion'
|
||||
# salt.run_highstate = true
|
||||
# salt.verbose = true
|
||||
# end
|
||||
end
|
||||
|
||||
config.vm.define "attacker" do |attacker|
|
||||
attacker.vm.box = "ubuntu/trusty64"
|
||||
attacker.vm.hostname = "attacker.dev.fail2ban.org"
|
||||
attacker.vm.network "private_network", ip: "192.168.200.150"
|
||||
|
||||
# attacker.vm.synced_folder 'salt/roots', '/srv/salt'
|
||||
|
||||
# attacker.vm.provision :salt do |salt|
|
||||
# salt.minion_config = 'salt/minion'
|
||||
# salt.run_highstate = true
|
||||
# salt.verbose = true
|
||||
# end
|
||||
end
|
||||
end
|
|
@ -409,6 +409,7 @@ class Fail2banClient:
|
|||
# TODO: get away from stew of return codes and exception
|
||||
# handling -- handle via exceptions
|
||||
try:
|
||||
self.__configurator.Reload()
|
||||
self.__configurator.readAll()
|
||||
ret = self.__configurator.getOptions(jail)
|
||||
self.__configurator.convertToProtocol()
|
||||
|
@ -418,12 +419,11 @@ class Fail2banClient:
|
|||
ret = False
|
||||
return ret
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def dumpConfig(cmd):
|
||||
for c in cmd:
|
||||
print c
|
||||
return True
|
||||
dumpConfig = staticmethod(dumpConfig)
|
||||
|
||||
|
||||
class ServerExecutionException(Exception):
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
#
|
||||
# Author: Mike Rushton
|
||||
#
|
||||
# Referenced from from http://www.normyee.net/blog/2012/02/02/adding-cloudflare-support-to-fail2ban by NORM YEE
|
||||
#
|
||||
# To get your Cloudflare API key: https://www.cloudflare.com/my-account
|
||||
#
|
||||
|
||||
[Definition]
|
||||
|
||||
# Option: actionstart
|
||||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart =
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop =
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck =
|
||||
|
||||
# Option: actionban
|
||||
# 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
|
||||
# <time> unix timestamp of the ban time
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = curl https://www.cloudflare.com/api_json.html -d 'a=ban' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# Tags: <ip> IP address
|
||||
# <failures> number of failures
|
||||
# <time> unix timestamp of the ban time
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = curl https://www.cloudflare.com/api_json.html -d 'a=nul' -d 'tkn=<cftoken>' -d 'email=<cfuser>' -d 'key=<ip>'
|
||||
|
||||
|
||||
[Init]
|
||||
|
||||
# Default Cloudflare API token
|
||||
cftoken =
|
||||
|
||||
# Default Cloudflare username
|
||||
cfuser =
|
|
@ -0,0 +1,52 @@
|
|||
# Fail2Ban configuration file for Bytemark Symbiosis firewall
|
||||
#
|
||||
# Author: Yaroslav Halchenko
|
||||
#
|
||||
|
||||
|
||||
[Definition]
|
||||
|
||||
# Option: actionstart
|
||||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart =
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop =
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck = iptables -n -L <chain>
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP.
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = echo 'all' >| /etc/symbiosis/firewall/blacklist.d/<ip>.auto
|
||||
iptables -I <chain> 1 -s <ip> -j <blocktype>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP.
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = rm -f /etc/symbiosis/firewall/blacklist.d/<ip>.auto
|
||||
iptables -D <chain> -s <ip> -j <blocktype> || :
|
||||
|
||||
[Init]
|
||||
|
||||
# Option: chain
|
||||
# Notes specifies the iptables chain to which the fail2ban rules should be
|
||||
# added to. blacklist is a chain initiated by symbiosis firewall.
|
||||
# Values: STRING Default: blacklist
|
||||
chain = blacklist
|
||||
|
||||
# Option: blocktype
|
||||
# Note: This is to match default symbiosis firewall type for blacklisted IPs
|
||||
# Values: STRING
|
||||
blocktype = DROP
|
|
@ -1,9 +1,9 @@
|
|||
# Fail2Ban action configuration file for ufw
|
||||
#
|
||||
# You are required to run "ufw enable" before this will have an effect.
|
||||
# You are required to run "ufw enable" before this will have any effect.
|
||||
#
|
||||
# The insert position should be approprate to block the required traffic.
|
||||
# A number after an allow rule to the application won't be much use.
|
||||
# The insert position should be appropriate to block the required traffic.
|
||||
# A number after an allow rule to the application won't be of much use.
|
||||
|
||||
[Definition]
|
||||
|
||||
|
@ -19,7 +19,7 @@ actionunban = [ -n "<application>" ] && app="app <application>" ; ufw delete <bl
|
|||
|
||||
[Init]
|
||||
# Option: insertpos
|
||||
# Notes.: The postition number in the firewall list to insert the block rule
|
||||
# Notes.: The position number in the firewall list to insert the block rule
|
||||
insertpos = 1
|
||||
|
||||
# Option: blocktype
|
||||
|
|
|
@ -10,7 +10,7 @@ before = apache-common.conf
|
|||
[Definition]
|
||||
|
||||
|
||||
failregex = ^%(_apache_error_client)s (AH01797: )?client denied by server configuration: (uri )?\S*(, referer: \S+)?\s*$
|
||||
failregex = ^%(_apache_error_client)s (AH(01797|01630): )?client denied by server configuration: (uri )?\S*(, referer: \S+)?\s*$
|
||||
^%(_apache_error_client)s (AH01617: )?user .*? authentication failure for "\S*": Password Mismatch(, referer: \S+)?$
|
||||
^%(_apache_error_client)s (AH01618: )?user .*? not found(: )?\S*(, referer: \S+)?\s*$
|
||||
^%(_apache_error_client)s (AH01614: )?client used wrong authentication scheme: \S*(, referer: \S+)?\s*$
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# Fail2Ban filter to block web requests containing custom headers attempting to exploit the shellshock bug
|
||||
#
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
# overwrite with apache-common.local if _apache_error_client is incorrect.
|
||||
before = apache-common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^%(_apache_error_client)s (AH01215: )?/bin/(ba)?sh: warning: HTTP_.*?: ignoring function definition attempt(, referer: \S+)?\s*$
|
||||
^%(_apache_error_client)s (AH01215: )?/bin/(ba)?sh: error importing function definition for `HTTP_.*?'(, referer: \S+)?\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
||||
# DEV Notes:
|
||||
#
|
||||
# https://wiki.apache.org/httpd/ListOfErrors for apache error IDs
|
||||
#
|
||||
# example log lines:
|
||||
# [Thu Sep 25 09:27:18.813902 2014] [cgi:error] [pid 16860] [client 89.207.132.76:59635] AH01215: /bin/bash: warning: HTTP_TEST: ignoring function definition attempt
|
||||
# [Thu Sep 25 09:29:56.141832 2014] [cgi:error] [pid 16864] [client 162.247.73.206:41273] AH01215: /bin/bash: error importing function definition for `HTTP_TEST'
|
||||
#
|
||||
# Author: Eugene Hopkinson (riot@riot.so)
|
|
@ -12,7 +12,8 @@ before = common.conf
|
|||
|
||||
_daemon = courieresmtpd
|
||||
|
||||
failregex = ^%(__prefix_line)serror,relay=<HOST>,.*: 550 User unknown\.$
|
||||
failregex = ^%(__prefix_line)serror,relay=<HOST>,.*: 550 User (<.*> )?unknown\.?$
|
||||
^%(__prefix_line)serror,relay=<HOST>,msg="535 Authentication failed\.",cmd:( AUTH \S+)?( [0-9a-zA-Z\+/=]+)?$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
|
|
@ -11,9 +11,9 @@ before = common.conf
|
|||
|
||||
[Definition]
|
||||
|
||||
_daemon = (?:cyrus/)?(?:imapd?|pop3d?)
|
||||
_daemon = (?:cyrus/)?(?:imap(d|s)?|pop3(d|s)?)
|
||||
|
||||
failregex = ^%(__prefix_line)sbadlogin: \S+ ?\[<HOST>\] \S+ .*?\[?SASL\(-13\): authentication failure: .*\]?$
|
||||
failregex = ^%(__prefix_line)sbadlogin: \S+ ?\[<HOST>\] \S+ .*?\[?SASL\(-13\): (authentication failure|user not found): .*\]?$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Fail2Ban configuration file for Directadmin
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^: \'<HOST>\' \d{1,3} failed login attempt(s)?. \s*
|
||||
|
||||
ignoreregex =
|
||||
|
||||
[Init]
|
||||
datepattern = ^%%Y:%%m:%%d-%%H:%%M:%%S
|
||||
|
||||
#
|
||||
# Requires Directadmin v1.45.3 or higher. http://www.directadmin.com/features.php?id=1590
|
||||
#
|
||||
# Author: Cyril Roos
|
||||
|
|
@ -7,8 +7,15 @@ before = common.conf
|
|||
|
||||
[Definition]
|
||||
|
||||
_daemon = postfix/smtpd
|
||||
_daemon = postfix/(submission/)?smtp(d|s)
|
||||
|
||||
failregex = ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/]*={0,2})?\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
[Init]
|
||||
|
||||
journalmatch = _SYSTEMD_UNIT=postfix.service
|
||||
|
||||
|
||||
# Author: Yaroslav Halchenko
|
||||
|
|
|
@ -10,7 +10,7 @@ before = common.conf
|
|||
|
||||
[Definition]
|
||||
|
||||
_daemon = postfix/smtpd
|
||||
_daemon = postfix/(submission/)?smtp(d|s)
|
||||
|
||||
failregex = ^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 554 5\.7\.1 .*$
|
||||
^%(__prefix_line)sNOQUEUE: reject: RCPT from \S+\[<HOST>\]: 450 4\.7\.1 : Helo command rejected: Host not found; from=<> to=<> proto=ESMTP helo= *$
|
||||
|
|
|
@ -15,7 +15,7 @@ before = common.conf
|
|||
_daemon = pure-ftpd
|
||||
|
||||
# Error message specified in multiple languages
|
||||
__errmsg = (?:<EFBFBD>ϥΪ<EFBFBD>\[.*\]<5D><><EFBFBD>ҥ<EFBFBD><D2A5><EFBFBD>|ʹ<><CAB9><EFBFBD><EFBFBD>\[.*\]<5D><>֤ʧ<D6A4><CAA7>|\[.*\] kullan<61>c<EFBFBD>s<EFBFBD> i<>in giri<72> hatal<61>|<7C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> \[.*\]|Godkjennelse mislyktes for \[.*\]|Beh<65>righetskontroll misslyckas f<>r anv<6E>ndare \[.*\]|Autentifikacia uzivatela zlyhala \[.*\]|Autentificare esuata pentru utilizatorul \[.*\]|Autentica<63><61>o falhou para usu<73>rio \[.*\]|Autentyfikacja nie powiod<6F>a si<73> dla u<>ytkownika \[.*\]|Autorisatie faalde voor gebruiker \[.*\]|\[.*\] <20><><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>|Autenticazione falita per l'utente \[.*\]|Azonos<6F>t<EFBFBD>s sikertelen \[.*\] felhaszn<7A>l<EFBFBD>nak|\[.*\] c'est un batard, il connait pas son code|Erreur d'authentification pour l'utilisateur \[.*\]|Autentificaci<63>n fallida para el usuario \[.*\]|Authentication failed for user \[.*\]|Authentifizierung fehlgeschlagen f<>r Benutzer \[.*\].|Godkendelse mislykkedes for \[.*\]|Autentifikace u<>ivatele selhala \[.*\])
|
||||
__errmsg = (?:Godkendelse mislykkedes for \[.*\]|Authentifizierung fehlgeschlagen für Benutzer \[.*\].|Authentication failed for user \[.*\]|Autentificación fallida para el usuario \[.*\]|\[.*\] c'est un batard, il connait pas son code|Erreur d'authentification pour l'utilisateur \[.*\]|Azonosítás sikertelen \[.*\] felhasználónak|Autenticazione falita per l'utente \[.*\]|Autorisatie faalde voor gebruiker \[.*\]|Godkjennelse mislyktes for \[.*\]|\[.*\] kullanýcýsý için giriþ hatalý|Autenticação falhou para usuário \[.*\]|Autentificare esuata pentru utilizatorul \[.*\]|Autentifikace uživatele selhala \[.*\]|Autentyfikacja nie powiodła się dla użytkownika \[.*\]|Autentifikacia uzivatela zlyhala \[.*\]|Behörighetskontroll misslyckas för användare \[.*\]|Авторизация не удалась пользователю \[.*\]|\[.*\] 嶸盪 檣隸 褒ぬ|妏蚚氪\[.*\]桄痐囮啖|使用者\[.*\]驗證失敗)
|
||||
|
||||
failregex = ^%(__prefix_line)s\(.+?@<HOST>\) \[WARNING\] %(__errmsg)s\s*$
|
||||
|
||||
|
@ -24,7 +24,13 @@ ignoreregex =
|
|||
# Author: Cyril Jaquier
|
||||
# Modified: Yaroslav Halchenko for pure-ftpd
|
||||
# Documentation thanks to Blake on http://www.fail2ban.org/wiki/index.php?title=Fail2ban:Community_Portal
|
||||
# UTF-8 editing and mechanism thanks to Johannes Weberhofer
|
||||
#
|
||||
# Only logs to syslog though facility can be changed configuration file/command line
|
||||
#
|
||||
# fgrep -r MSG_AUTH_FAILED_LOG pure-ftpd-1.0.36/src
|
||||
# To get messages in the right encoding:
|
||||
# grep MSG_AUTH_FAILED_LOG pure-ftpd-1.0.36/src/messages_[defhint]* | grep -Po '".?"' | recode latin1..utf-8 | tr -d '"' > messages
|
||||
# grep MSG_AUTH_FAILED_LOG pure-ftpd-1.0.36/src/messages_[pr][to] | grep -Po '".?"' | recode latin1..utf-8 | tr -d '"' >> messages
|
||||
# grep MSG_AUTH_FAILED_LOG pure-ftpd-1.0.36/src/messages_[cps][slkv] | grep -Po '".?"' | recode latin2..utf-8 | tr -d '"' >> messages
|
||||
# grep MSG_AUTH_FAILED_LOG pure-ftpd-1.0.36/src/messages_ru | grep -Po '".?"' | recode KOI8-R..utf-8 | tr -d '"' >> messages
|
||||
# grep MSG_AUTH_FAILED_LOG pure-ftpd-1.0.36/src/messages_[kz] | grep -Po '".*?"' | tr -d '"' | recode big5..utf-8 >> messages
|
||||
|
|
|
@ -9,7 +9,7 @@ before = common.conf
|
|||
|
||||
[Definition]
|
||||
|
||||
_deamon = (?:cyrus/)?(?:tim)?sieved?
|
||||
_daemon = (?:cyrus/)?(?:tim)?sieved?
|
||||
|
||||
failregex = ^%(__prefix_line)sbadlogin: \S+ ?\[<HOST>\] \S+ authentication failure$
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# YOU SHOULD NOT MODIFY THIS FILE.
|
||||
#
|
||||
# It will probably be overwitten or improved in a distribution update.
|
||||
# It will probably be overwritten or improved in a distribution update.
|
||||
#
|
||||
# Provide customizations in a jail.local file or a jail.d/customisation.local.
|
||||
# For example to change the default bantime for all jails and to enable the
|
||||
|
@ -151,22 +151,22 @@ port = 0:65535
|
|||
banaction = iptables-multiport
|
||||
|
||||
# The simplest action to take: ban only
|
||||
action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||
action_ = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||
|
||||
# ban & send an e-mail with whois report to the destemail.
|
||||
action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||
action_mw = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||
|
||||
# ban & send an e-mail with whois report and relevant log lines
|
||||
# to the destemail.
|
||||
action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||
action_mwl = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||
%(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]
|
||||
|
||||
# See the IMPORTANT note in action.d/xarf-login-attack for when to use this action
|
||||
#
|
||||
# ban & send a xarf e-mail to abuse contact of IP address and include relevant log lines
|
||||
# to the destemail.
|
||||
action_xarf = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||
action_xarf = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
||||
xarf-login-attack[service=%(__name__)s, sender="%(sender)s", logpath=%(logpath)s, port="%(port)s"]
|
||||
|
||||
|
||||
|
@ -283,6 +283,11 @@ port = http,https
|
|||
logpath = %(apache_error_log)s
|
||||
maxretry = 2
|
||||
|
||||
[apache-shellshock]
|
||||
|
||||
port = http,https
|
||||
logpath = $(apache_error_log)s
|
||||
maxretry = 1
|
||||
|
||||
[nginx-http-auth]
|
||||
|
||||
|
@ -376,7 +381,7 @@ logpath = /var/log/monit
|
|||
[webmin-auth]
|
||||
|
||||
port = 10000
|
||||
logpath = /var/log/auth.log
|
||||
logpath = %(syslog_authpriv)s
|
||||
|
||||
|
||||
#
|
||||
|
@ -429,7 +434,7 @@ maxretry = 6
|
|||
|
||||
[vsftpd]
|
||||
# or overwrite it in jails.local to be
|
||||
# logpath = /var/log/auth.log
|
||||
# logpath = %(syslog_authpriv)s
|
||||
# if you want to rely on PAM failed login attempts
|
||||
# vsftpd's failregex should match both of those formats
|
||||
port = ftp,ftp-data,ftps,ftps-data
|
||||
|
@ -501,13 +506,13 @@ logpath = %(solidpop3d_log)s
|
|||
[exim]
|
||||
|
||||
port = smtp,465,submission
|
||||
logpath = /var/log/exim/mainlog
|
||||
logpath = %(exim_main_log)s
|
||||
|
||||
|
||||
[exim-spam]
|
||||
|
||||
port = smtp,465,submission
|
||||
logpath = /var/log/exim/mainlog
|
||||
logpath = %(exim_main_log)s
|
||||
|
||||
|
||||
[kerio]
|
||||
|
@ -539,7 +544,7 @@ logpath = %(postfix_log)s
|
|||
[perdition]
|
||||
|
||||
port = imap3,imaps,pop3,pop3s
|
||||
logpath = /var/log/maillog
|
||||
logpath = %(syslog_mail)s
|
||||
|
||||
|
||||
[squirrelmail]
|
||||
|
@ -663,13 +668,13 @@ maxretry = 5
|
|||
[pam-generic]
|
||||
# pam-generic filter can be customized to monitor specific subset of 'tty's
|
||||
banaction = iptables-allports
|
||||
logpath = /var/log/auth.log
|
||||
logpath = %(syslog_authpriv)s
|
||||
|
||||
|
||||
[xinetd-fail]
|
||||
|
||||
banaction = iptables-multiport-log
|
||||
logpath = /var/log/daemon.log
|
||||
logpath = %(syslog_daemon)s
|
||||
maxretry = 2
|
||||
|
||||
|
||||
|
@ -699,7 +704,7 @@ action = %(banaction)s[name=%(__name__)s-tcp, port="%(tcpport)s", protocol="tcp
|
|||
[nagios]
|
||||
|
||||
enabled = false
|
||||
logpath = /var/log/messages ; nrpe.cfg may define a different log_facility
|
||||
logpath = %(syslog_daemon)s ; nrpe.cfg may define a different log_facility
|
||||
maxretry = 1
|
||||
|
||||
|
||||
|
@ -710,7 +715,12 @@ logpath = /opt/sun/comms/messaging64/log/mail.log_current
|
|||
maxretry = 6
|
||||
banaction = iptables-allports
|
||||
|
||||
[directadmin]
|
||||
enabled = false
|
||||
logpath = /var/log/directadmin/login.log
|
||||
port = 2222
|
||||
|
||||
[portsentry]
|
||||
enabled = false
|
||||
logpath = /var/lib/portsentry/portsentry.history
|
||||
maxretry = 1
|
||||
maxretry = 1
|
|
@ -12,7 +12,7 @@ sshd_log = %(syslog_authpriv)s
|
|||
dropbear_log = %(syslog_authpriv)s
|
||||
|
||||
# There is no sensible generic defaults for syslog log targets, thus
|
||||
# leaving them empty here so that no errors while parsing/interpollatin configs
|
||||
# leaving them empty here so that no errors while parsing/interpolating configs
|
||||
syslog_daemon =
|
||||
syslog_ftp =
|
||||
syslog_local0 =
|
||||
|
@ -22,6 +22,7 @@ syslog_user =
|
|||
# from /etc/audit/auditd.conf
|
||||
auditd_log = /var/log/audit/audit.log
|
||||
|
||||
exim_main_log = /var/log/exim/mainlog
|
||||
|
||||
nginx_error_log = /var/log/nginx/error.log
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ apache_error_log = /var/log/apache2/*error.log
|
|||
|
||||
apache_access_log = /var/log/apache2/*access.log
|
||||
|
||||
exim_main_log = /var/log/exim4/mainlog
|
||||
|
||||
# was in debian squeezy but not in wheezy
|
||||
# /etc/proftpd/proftpd.conf (SystemLog)
|
||||
|
|
|
@ -32,4 +32,6 @@ apache_access_log = /var/log/httpd/*access_log
|
|||
# proftpd_log = /var/log/proftpd/auth.log
|
||||
# Tested and it worked out in /var/log/messages so assuming syslog_ftp for now.
|
||||
|
||||
exim_main_log = /var/log/exim/main.log
|
||||
|
||||
mysql_log = /var/lib/mysql/mysqld.log
|
||||
|
|
|
@ -26,7 +26,7 @@ __license__ = "GPL"
|
|||
|
||||
import os
|
||||
|
||||
from .configreader import ConfigReader, DefinitionInitConfigReader
|
||||
from .configreader import DefinitionInitConfigReader
|
||||
from ..helpers import getLogger
|
||||
|
||||
# Gets the instance of the logger.
|
||||
|
@ -47,15 +47,19 @@ class ActionReader(DefinitionInitConfigReader):
|
|||
DefinitionInitConfigReader.__init__(
|
||||
self, file_, jailName, initOpts, **kwargs)
|
||||
|
||||
def setFile(self, fileName):
|
||||
self.__file = fileName
|
||||
DefinitionInitConfigReader.setFile(self, os.path.join("action.d", fileName))
|
||||
|
||||
def getFile(self):
|
||||
return self.__file
|
||||
|
||||
def setName(self, name):
|
||||
self._name = name
|
||||
|
||||
def getName(self):
|
||||
return self._name
|
||||
|
||||
def read(self):
|
||||
return ConfigReader.read(self, os.path.join("action.d", self._file))
|
||||
|
||||
def convert(self):
|
||||
head = ["set", self._jailName]
|
||||
stream = list()
|
||||
|
|
|
@ -51,6 +51,8 @@ class Beautifier:
|
|||
try:
|
||||
if inC[0] == "ping":
|
||||
msg = "Server replied: " + response
|
||||
elif inC[0] == "version":
|
||||
msg = response
|
||||
elif inC[0] == "start":
|
||||
msg = "Jail started"
|
||||
elif inC[0] == "stop":
|
||||
|
|
|
@ -62,6 +62,7 @@ else: # pragma: no cover
|
|||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
logLevel = 7
|
||||
|
||||
__all__ = ['SafeConfigParserWithIncludes']
|
||||
|
||||
|
@ -98,30 +99,73 @@ after = 1.conf
|
|||
|
||||
if sys.version_info >= (3,2):
|
||||
# overload constructor only for fancy new Python3's
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, share_config=None, *args, **kwargs):
|
||||
kwargs = kwargs.copy()
|
||||
kwargs['interpolation'] = BasicInterpolationWithName()
|
||||
kwargs['inline_comment_prefixes'] = ";"
|
||||
super(SafeConfigParserWithIncludes, self).__init__(
|
||||
*args, **kwargs)
|
||||
self._cfg_share = share_config
|
||||
|
||||
#@staticmethod
|
||||
def getIncludes(resource, seen = []):
|
||||
else:
|
||||
def __init__(self, share_config=None, *args, **kwargs):
|
||||
SafeConfigParser.__init__(self, *args, **kwargs)
|
||||
self._cfg_share = share_config
|
||||
|
||||
@property
|
||||
def share_config(self):
|
||||
return self._cfg_share
|
||||
|
||||
def _getSharedSCPWI(self, filename):
|
||||
SCPWI = SafeConfigParserWithIncludes
|
||||
# read single one, add to return list, use sharing if possible:
|
||||
if self._cfg_share:
|
||||
# cache/share each file as include (ex: filter.d/common could be included in each filter config):
|
||||
hashv = 'inc:'+(filename if not isinstance(filename, list) else '\x01'.join(filename))
|
||||
cfg, i = self._cfg_share.get(hashv, (None, None))
|
||||
if cfg is None:
|
||||
cfg = SCPWI(share_config=self._cfg_share)
|
||||
i = cfg.read(filename, get_includes=False)
|
||||
self._cfg_share[hashv] = (cfg, i)
|
||||
elif logSys.getEffectiveLevel() <= logLevel:
|
||||
logSys.log(logLevel, " Shared file: %s", filename)
|
||||
else:
|
||||
# don't have sharing:
|
||||
cfg = SCPWI()
|
||||
i = cfg.read(filename, get_includes=False)
|
||||
return (cfg, i)
|
||||
|
||||
def _getIncludes(self, filenames, seen=[]):
|
||||
if not isinstance(filenames, list):
|
||||
filenames = [ filenames ]
|
||||
# retrieve or cache include paths:
|
||||
if self._cfg_share:
|
||||
# cache/share include list:
|
||||
hashv = 'inc-path:'+('\x01'.join(filenames))
|
||||
fileNamesFull = self._cfg_share.get(hashv)
|
||||
if fileNamesFull is None:
|
||||
fileNamesFull = []
|
||||
for filename in filenames:
|
||||
fileNamesFull += self.__getIncludesUncached(filename, seen)
|
||||
self._cfg_share[hashv] = fileNamesFull
|
||||
return fileNamesFull
|
||||
# don't have sharing:
|
||||
fileNamesFull = []
|
||||
for filename in filenames:
|
||||
fileNamesFull += self.__getIncludesUncached(filename, seen)
|
||||
return fileNamesFull
|
||||
|
||||
def __getIncludesUncached(self, resource, seen=[]):
|
||||
"""
|
||||
Given 1 config resource returns list of included files
|
||||
(recursively) with the original one as well
|
||||
Simple loops are taken care about
|
||||
"""
|
||||
|
||||
# Use a short class name ;)
|
||||
SCPWI = SafeConfigParserWithIncludes
|
||||
|
||||
parser = SafeConfigParser()
|
||||
try:
|
||||
if sys.version_info >= (3,2): # pragma: no cover
|
||||
parser.read(resource, encoding='utf-8')
|
||||
else:
|
||||
parser.read(resource)
|
||||
parser, i = self._getSharedSCPWI(resource)
|
||||
if not i:
|
||||
return []
|
||||
except UnicodeDecodeError, e:
|
||||
logSys.error("Error decoding config file '%s': %s" % (resource, e))
|
||||
return []
|
||||
|
@ -141,22 +185,60 @@ after = 1.conf
|
|||
if r in seen:
|
||||
continue
|
||||
s = seen + [resource]
|
||||
option_list += SCPWI.getIncludes(r, s)
|
||||
option_list += self._getIncludes(r, s)
|
||||
# combine lists
|
||||
return newFiles[0][1] + [resource] + newFiles[1][1]
|
||||
#print "Includes list for " + resource + " is " + `resources`
|
||||
getIncludes = staticmethod(getIncludes)
|
||||
|
||||
def get_defaults(self):
|
||||
return self._defaults
|
||||
|
||||
def read(self, filenames):
|
||||
fileNamesFull = []
|
||||
def get_sections(self):
|
||||
return self._sections
|
||||
|
||||
def read(self, filenames, get_includes=True):
|
||||
if not isinstance(filenames, list):
|
||||
filenames = [ filenames ]
|
||||
for filename in filenames:
|
||||
fileNamesFull += SafeConfigParserWithIncludes.getIncludes(filename)
|
||||
logSys.debug("Reading files: %s" % fileNamesFull)
|
||||
# retrieve (and cache) includes:
|
||||
fileNamesFull = []
|
||||
if get_includes:
|
||||
fileNamesFull += self._getIncludes(filenames)
|
||||
else:
|
||||
fileNamesFull = filenames
|
||||
|
||||
if not fileNamesFull:
|
||||
return []
|
||||
|
||||
logSys.info(" Loading files: %s", fileNamesFull)
|
||||
|
||||
if get_includes or len(fileNamesFull) > 1:
|
||||
# read multiple configs:
|
||||
ret = []
|
||||
alld = self.get_defaults()
|
||||
alls = self.get_sections()
|
||||
for filename in fileNamesFull:
|
||||
# read single one, add to return list, use sharing if possible:
|
||||
cfg, i = self._getSharedSCPWI(filename)
|
||||
if i:
|
||||
ret += i
|
||||
# merge defaults and all sections to self:
|
||||
alld.update(cfg.get_defaults())
|
||||
for n, s in cfg.get_sections().iteritems():
|
||||
if isinstance(s, dict):
|
||||
s2 = alls.get(n)
|
||||
if isinstance(s2, dict):
|
||||
s2.update(s)
|
||||
else:
|
||||
alls[n] = s.copy()
|
||||
else:
|
||||
alls[n] = s
|
||||
|
||||
return ret
|
||||
|
||||
# read one config :
|
||||
if logSys.getEffectiveLevel() <= logLevel:
|
||||
logSys.log(logLevel, " Reading file: %s", fileNamesFull[0])
|
||||
# read file(s) :
|
||||
if sys.version_info >= (3,2): # pragma: no cover
|
||||
return SafeConfigParser.read(self, fileNamesFull, encoding='utf-8')
|
||||
else:
|
||||
return SafeConfigParser.read(self, fileNamesFull)
|
||||
|
||||
|
|
|
@ -27,24 +27,127 @@ __license__ = "GPL"
|
|||
import glob, os
|
||||
from ConfigParser import NoOptionError, NoSectionError
|
||||
|
||||
from .configparserinc import SafeConfigParserWithIncludes
|
||||
from .configparserinc import SafeConfigParserWithIncludes, logLevel
|
||||
from ..helpers import getLogger
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
|
||||
class ConfigReader(SafeConfigParserWithIncludes):
|
||||
class ConfigReader():
|
||||
"""Generic config reader class.
|
||||
|
||||
A caching adapter which automatically reuses already shared configuration.
|
||||
"""
|
||||
|
||||
def __init__(self, use_config=None, share_config=None, **kwargs):
|
||||
# use given shared config if possible (see read):
|
||||
self._cfg_share = None
|
||||
self._cfg = None
|
||||
if use_config is not None:
|
||||
self._cfg = use_config
|
||||
# share config if possible:
|
||||
if share_config is not None:
|
||||
self._cfg_share = share_config
|
||||
self._cfg_share_kwargs = kwargs
|
||||
self._cfg_share_basedir = None
|
||||
elif self._cfg is None:
|
||||
self._cfg = ConfigReaderUnshared(**kwargs)
|
||||
|
||||
def setBaseDir(self, basedir):
|
||||
if self._cfg:
|
||||
self._cfg.setBaseDir(basedir)
|
||||
else:
|
||||
self._cfg_share_basedir = basedir
|
||||
|
||||
def getBaseDir(self):
|
||||
if self._cfg:
|
||||
return self._cfg.getBaseDir()
|
||||
else:
|
||||
return self._cfg_share_basedir
|
||||
|
||||
@property
|
||||
def share_config(self):
|
||||
return self._cfg_share
|
||||
|
||||
def read(self, name, once=True):
|
||||
""" Overloads a default (not shared) read of config reader.
|
||||
|
||||
To prevent mutiple reads of config files with it includes, reads into
|
||||
the config reader, if it was not yet cached/shared by 'name'.
|
||||
"""
|
||||
# already shared ?
|
||||
if not self._cfg:
|
||||
self.touch(name)
|
||||
# performance feature - read once if using shared config reader:
|
||||
if once and self._cfg.read_cfg_files is not None:
|
||||
return self._cfg.read_cfg_files
|
||||
|
||||
# load:
|
||||
logSys.info("Loading configs for %s under %s ", name, self._cfg.getBaseDir())
|
||||
ret = self._cfg.read(name)
|
||||
|
||||
# save already read and return:
|
||||
self._cfg.read_cfg_files = ret
|
||||
return ret
|
||||
|
||||
def touch(self, name=''):
|
||||
""" Allocates and share a config file by it name.
|
||||
|
||||
Automatically allocates unshared or reuses shared handle by given 'name' and
|
||||
init arguments inside a given shared storage.
|
||||
"""
|
||||
if not self._cfg and self._cfg_share is not None:
|
||||
self._cfg = self._cfg_share.get(name)
|
||||
if not self._cfg:
|
||||
self._cfg = ConfigReaderUnshared(share_config=self._cfg_share, **self._cfg_share_kwargs)
|
||||
if self._cfg_share_basedir is not None:
|
||||
self._cfg.setBaseDir(self._cfg_share_basedir)
|
||||
self._cfg_share[name] = self._cfg
|
||||
else:
|
||||
self._cfg = ConfigReaderUnshared(**self._cfg_share_kwargs)
|
||||
|
||||
def sections(self):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.sections()
|
||||
return []
|
||||
|
||||
def has_section(self, sec):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.has_section(sec)
|
||||
return False
|
||||
|
||||
def options(self, *args):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.options(*args)
|
||||
return {}
|
||||
|
||||
def get(self, sec, opt):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.get(sec, opt)
|
||||
return None
|
||||
|
||||
def getOptions(self, *args, **kwargs):
|
||||
if self._cfg is not None:
|
||||
return self._cfg.getOptions(*args, **kwargs)
|
||||
return {}
|
||||
|
||||
class ConfigReaderUnshared(SafeConfigParserWithIncludes):
|
||||
"""Unshared config reader (previously ConfigReader).
|
||||
|
||||
Do not use this class (internal not shared/cached represenation).
|
||||
Use ConfigReader instead.
|
||||
"""
|
||||
|
||||
DEFAULT_BASEDIR = '/etc/fail2ban'
|
||||
|
||||
def __init__(self, basedir=None):
|
||||
SafeConfigParserWithIncludes.__init__(self)
|
||||
def __init__(self, basedir=None, *args, **kwargs):
|
||||
SafeConfigParserWithIncludes.__init__(self, *args, **kwargs)
|
||||
self.read_cfg_files = None
|
||||
self.setBaseDir(basedir)
|
||||
self.__opts = None
|
||||
|
||||
def setBaseDir(self, basedir):
|
||||
if basedir is None:
|
||||
basedir = ConfigReader.DEFAULT_BASEDIR # stock system location
|
||||
basedir = ConfigReaderUnshared.DEFAULT_BASEDIR # stock system location
|
||||
self._basedir = basedir.rstrip('/')
|
||||
|
||||
def getBaseDir(self):
|
||||
|
@ -55,7 +158,7 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
|||
raise ValueError("Base configuration directory %s does not exist "
|
||||
% self._basedir)
|
||||
basename = os.path.join(self._basedir, filename)
|
||||
logSys.info("Reading configs for %s under %s " % (basename, self._basedir))
|
||||
logSys.debug("Reading configs for %s under %s " , filename, self._basedir)
|
||||
config_files = [ basename + ".conf" ]
|
||||
|
||||
# possible further customizations under a .conf.d directory
|
||||
|
@ -71,14 +174,14 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
|||
|
||||
if len(config_files):
|
||||
# at least one config exists and accessible
|
||||
logSys.debug("Reading config files: " + ', '.join(config_files))
|
||||
logSys.debug("Reading config files: %s", ', '.join(config_files))
|
||||
config_files_read = SafeConfigParserWithIncludes.read(self, config_files)
|
||||
missed = [ cf for cf in config_files if cf not in config_files_read ]
|
||||
if missed:
|
||||
logSys.error("Could not read config files: " + ', '.join(missed))
|
||||
logSys.error("Could not read config files: %s", ', '.join(missed))
|
||||
if config_files_read:
|
||||
return True
|
||||
logSys.error("Found no accessible config files for %r under %s" %
|
||||
logSys.error("Found no accessible config files for %r under %s",
|
||||
( filename, self.getBaseDir() ))
|
||||
return False
|
||||
else:
|
||||
|
@ -98,7 +201,7 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
|||
# 1 -> the name of the option
|
||||
# 2 -> the default value for the option
|
||||
|
||||
def getOptions(self, sec, options, pOptions = None):
|
||||
def getOptions(self, sec, options, pOptions=None):
|
||||
values = dict()
|
||||
for option in options:
|
||||
try:
|
||||
|
@ -121,10 +224,8 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
|||
logSys.warning("'%s' not defined in '%s'. Using default one: %r"
|
||||
% (option[1], sec, option[2]))
|
||||
values[option[1]] = option[2]
|
||||
else:
|
||||
logSys.debug(
|
||||
"Non essential option '%s' not defined in '%s'.",
|
||||
option[1], sec)
|
||||
elif logSys.getEffectiveLevel() <= logLevel:
|
||||
logSys.log(logLevel, "Non essential option '%s' not defined in '%s'.", option[1], sec)
|
||||
except ValueError:
|
||||
logSys.warning("Wrong value for '" + option[1] + "' in '" + sec +
|
||||
"'. Using default one: '" + `option[2]` + "'")
|
||||
|
@ -133,12 +234,12 @@ class ConfigReader(SafeConfigParserWithIncludes):
|
|||
|
||||
class DefinitionInitConfigReader(ConfigReader):
|
||||
"""Config reader for files with options grouped in [Definition] and
|
||||
[Init] sections.
|
||||
[Init] sections.
|
||||
|
||||
Is a base class for readers of filters and actions, where definitions
|
||||
in jails might provide custom values for options defined in [Init]
|
||||
section.
|
||||
"""
|
||||
Is a base class for readers of filters and actions, where definitions
|
||||
in jails might provide custom values for options defined in [Init]
|
||||
section.
|
||||
"""
|
||||
|
||||
_configOpts = []
|
||||
|
||||
|
|
|
@ -33,12 +33,20 @@ logSys = getLogger(__name__)
|
|||
|
||||
class Configurator:
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, force_enable=False, share_config=None):
|
||||
self.__settings = dict()
|
||||
self.__streams = dict()
|
||||
self.__fail2ban = Fail2banReader()
|
||||
self.__jails = JailsReader()
|
||||
|
||||
# always share all config readers:
|
||||
if share_config is None:
|
||||
share_config = dict()
|
||||
self.__share_config = share_config
|
||||
self.__fail2ban = Fail2banReader(share_config=share_config)
|
||||
self.__jails = JailsReader(force_enable=force_enable, share_config=share_config)
|
||||
|
||||
def Reload(self):
|
||||
# clear all shared handlers:
|
||||
self.__share_config.clear()
|
||||
|
||||
def setBaseDir(self, folderName):
|
||||
self.__fail2ban.setBaseDir(folderName)
|
||||
self.__jails.setBaseDir(folderName)
|
||||
|
|
|
@ -57,7 +57,7 @@ class CSocket:
|
|||
self.__csock.close()
|
||||
return ret
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def receive(sock):
|
||||
msg = EMPTY_BYTES
|
||||
while msg.rfind(CSocket.END_STRING) == -1:
|
||||
|
@ -66,4 +66,3 @@ class CSocket:
|
|||
raise RuntimeError, "socket connection broken"
|
||||
msg = msg + chunk
|
||||
return loads(msg)
|
||||
receive = staticmethod(receive)
|
||||
|
|
|
@ -26,7 +26,7 @@ __license__ = "GPL"
|
|||
|
||||
import os, shlex
|
||||
|
||||
from .configreader import ConfigReader, DefinitionInitConfigReader
|
||||
from .configreader import DefinitionInitConfigReader
|
||||
from ..server.action import CommandAction
|
||||
from ..helpers import getLogger
|
||||
|
||||
|
@ -40,8 +40,12 @@ class FilterReader(DefinitionInitConfigReader):
|
|||
["string", "failregex", ""],
|
||||
]
|
||||
|
||||
def read(self):
|
||||
return ConfigReader.read(self, os.path.join("filter.d", self._file))
|
||||
def setFile(self, fileName):
|
||||
self.__file = fileName
|
||||
DefinitionInitConfigReader.setFile(self, os.path.join("filter.d", fileName))
|
||||
|
||||
def getFile(self):
|
||||
return self.__file
|
||||
|
||||
def convert(self):
|
||||
stream = list()
|
||||
|
|
|
@ -27,7 +27,7 @@ __license__ = "GPL"
|
|||
import re, glob, os.path
|
||||
import json
|
||||
|
||||
from .configreader import ConfigReader
|
||||
from .configreader import ConfigReaderUnshared, ConfigReader
|
||||
from .filterreader import FilterReader
|
||||
from .actionreader import ActionReader
|
||||
from ..helpers import getLogger
|
||||
|
@ -111,7 +111,7 @@ class JailReader(ConfigReader):
|
|||
filterName, filterOpt = JailReader.extractOptions(
|
||||
self.__opts["filter"])
|
||||
self.__filter = FilterReader(
|
||||
filterName, self.__name, filterOpt, basedir=self.getBaseDir())
|
||||
filterName, self.__name, filterOpt, share_config=self.share_config, basedir=self.getBaseDir())
|
||||
ret = self.__filter.read()
|
||||
if ret:
|
||||
self.__filter.getOptions(self.__opts)
|
||||
|
@ -141,7 +141,7 @@ class JailReader(ConfigReader):
|
|||
else:
|
||||
action = ActionReader(
|
||||
actName, self.__name, actOpt,
|
||||
basedir=self.getBaseDir())
|
||||
share_config=self.share_config, basedir=self.getBaseDir())
|
||||
ret = action.read()
|
||||
if ret:
|
||||
action.getOptions(self.__opts)
|
||||
|
@ -213,14 +213,14 @@ class JailReader(ConfigReader):
|
|||
if self.__filter:
|
||||
stream.extend(self.__filter.convert())
|
||||
for action in self.__actions:
|
||||
if isinstance(action, ConfigReader):
|
||||
if isinstance(action, (ConfigReaderUnshared, ConfigReader)):
|
||||
stream.extend(action.convert())
|
||||
else:
|
||||
stream.append(action)
|
||||
stream.insert(0, ["add", self.__name, backend])
|
||||
return stream
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def extractOptions(option):
|
||||
match = JailReader.optionCRE.match(option)
|
||||
if not match:
|
||||
|
@ -235,4 +235,3 @@ class JailReader(ConfigReader):
|
|||
val for val in optmatch.group(2,3,4) if val is not None][0]
|
||||
option_opts[opt.strip()] = value.strip()
|
||||
return option_name, option_opts
|
||||
extractOptions = staticmethod(extractOptions)
|
||||
|
|
|
@ -68,9 +68,10 @@ class JailsReader(ConfigReader):
|
|||
for sec in sections:
|
||||
if sec == 'INCLUDES':
|
||||
continue
|
||||
jail = JailReader(sec, basedir=self.getBaseDir(),
|
||||
force_enable=self.__force_enable)
|
||||
jail.read()
|
||||
# use the cfg_share for filter/action caching and the same config for all
|
||||
# jails (use_config=...), therefore don't read it here:
|
||||
jail = JailReader(sec, force_enable=self.__force_enable,
|
||||
share_config=self.share_config, use_config=self._cfg)
|
||||
ret = jail.getOptions()
|
||||
if ret:
|
||||
if jail.isEnabled():
|
||||
|
|
|
@ -38,6 +38,7 @@ protocol = [
|
|||
["status", "gets the current status of the server"],
|
||||
["ping", "tests if the server is alive"],
|
||||
["help", "return this output"],
|
||||
["version", "return the server version"],
|
||||
['', "LOGGING", ""],
|
||||
["set loglevel <LEVEL>", "sets logging level to <LEVEL>. Levels: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG"],
|
||||
["get loglevel", "gets the logging level"],
|
||||
|
|
|
@ -197,6 +197,8 @@ class Actions(JailThread, Mapping):
|
|||
if ticket is not None:
|
||||
# Unban the IP.
|
||||
self.__unBan(ticket)
|
||||
if self._jail.database is not None:
|
||||
self._jail.database.delBan(self._jail, ticket)
|
||||
else:
|
||||
raise ValueError("IP %s is not banned" % ip)
|
||||
|
||||
|
|
|
@ -149,12 +149,8 @@ class AsyncServer(asyncore.dispatcher):
|
|||
self.__init = True
|
||||
# TODO Add try..catch
|
||||
# There's a bug report for Python 2.6/3.0 that use_poll=True yields some 2.5 incompatibilities:
|
||||
if sys.version_info >= (2, 6): # if python 2.6 or greater...
|
||||
logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll")
|
||||
asyncore.loop(use_poll = False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
|
||||
else: # pragma: no cover
|
||||
logSys.debug("NOT Python 2.6/3.* - asyncore.loop() using poll")
|
||||
asyncore.loop(use_poll = True)
|
||||
logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll")
|
||||
asyncore.loop(use_poll=False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
|
||||
|
||||
##
|
||||
# Stops the communication server.
|
||||
|
@ -175,12 +171,11 @@ class AsyncServer(asyncore.dispatcher):
|
|||
|
||||
# @param sock: socket file.
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def __markCloseOnExec(sock):
|
||||
fd = sock.fileno()
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC)
|
||||
__markCloseOnExec = staticmethod(__markCloseOnExec)
|
||||
|
||||
##
|
||||
# AsyncServerException is used to wrap communication exceptions.
|
||||
|
|
|
@ -126,7 +126,7 @@ class BanManager:
|
|||
# @param ticket the FailTicket
|
||||
# @return a BanTicket
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def createBanTicket(ticket):
|
||||
ip = ticket.getIP()
|
||||
#lastTime = ticket.getTime()
|
||||
|
@ -134,7 +134,6 @@ class BanManager:
|
|||
banTicket = BanTicket(ip, lastTime, ticket.getMatches())
|
||||
banTicket.setAttempt(ticket.getAttempt())
|
||||
return banTicket
|
||||
createBanTicket = staticmethod(createBanTicket)
|
||||
|
||||
##
|
||||
# Add a ban ticket.
|
||||
|
|
|
@ -368,10 +368,25 @@ class Fail2BanDb(object):
|
|||
#TODO: Implement data parts once arbitrary match keys completed
|
||||
cur.execute(
|
||||
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, ?, ?)",
|
||||
(jail.name, ticket.getIP(), ticket.getTime(),
|
||||
(jail.name, ticket.getIP(), int(round(ticket.getTime())),
|
||||
{"matches": ticket.getMatches(),
|
||||
"failures": ticket.getAttempt()}))
|
||||
|
||||
@commitandrollback
|
||||
def delBan(self, cur, jail, ticket):
|
||||
"""Delete a ban from the database.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
jail : Jail
|
||||
Jail in which the ban has occurred.
|
||||
ticket : BanTicket
|
||||
Ticket of the ban to be removed.
|
||||
"""
|
||||
cur.execute(
|
||||
"DELETE FROM bans WHERE jail = ? AND ip = ? AND timeofban = ?",
|
||||
(jail.name, ticket.getIP(), int(round(ticket.getTime()))))
|
||||
|
||||
@commitandrollback
|
||||
def _getBans(self, cur, jail=None, bantime=None, ip=None):
|
||||
query = "SELECT ip, timeofban, data FROM bans WHERE 1"
|
||||
|
|
|
@ -838,7 +838,7 @@ class DNSUtils:
|
|||
|
||||
IP_CRE = re.compile("^(?:\d{1,3}\.){3}\d{1,3}$")
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def dnsToIp(dns):
|
||||
""" Convert a DNS into an IP address using the Python socket module.
|
||||
Thanks to Kevin Drapel.
|
||||
|
@ -853,9 +853,8 @@ class DNSUtils:
|
|||
logSys.warning("Socket error raised trying to resolve hostname %s: %s"
|
||||
% (dns, e))
|
||||
return list()
|
||||
dnsToIp = staticmethod(dnsToIp)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def searchIP(text):
|
||||
""" Search if an IP address if directly available and return
|
||||
it.
|
||||
|
@ -865,9 +864,8 @@ class DNSUtils:
|
|||
return match
|
||||
else:
|
||||
return None
|
||||
searchIP = staticmethod(searchIP)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def isValidIP(string):
|
||||
""" Return true if str is a valid IP
|
||||
"""
|
||||
|
@ -877,9 +875,8 @@ class DNSUtils:
|
|||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
isValidIP = staticmethod(isValidIP)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def textToIp(text, useDns):
|
||||
""" Return the IP of DNS found in a given text.
|
||||
"""
|
||||
|
@ -901,9 +898,8 @@ class DNSUtils:
|
|||
text, ipList)
|
||||
|
||||
return ipList
|
||||
textToIp = staticmethod(textToIp)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def cidr(i, n):
|
||||
""" Convert an IP address string with a CIDR mask into a 32-bit
|
||||
integer.
|
||||
|
@ -911,18 +907,15 @@ class DNSUtils:
|
|||
# 32-bit IPv4 address mask
|
||||
MASK = 0xFFFFFFFFL
|
||||
return ~(MASK >> n) & MASK & DNSUtils.addr2bin(i)
|
||||
cidr = staticmethod(cidr)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def addr2bin(string):
|
||||
""" Convert a string IPv4 address into an unsigned integer.
|
||||
"""
|
||||
return struct.unpack("!L", socket.inet_aton(string))[0]
|
||||
addr2bin = staticmethod(addr2bin)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def bin2addr(addr):
|
||||
""" Convert a numeric IPv4 address into string n.n.n.n form.
|
||||
"""
|
||||
return socket.inet_ntoa(struct.pack("!L", addr))
|
||||
bin2addr = staticmethod(bin2addr)
|
||||
|
|
|
@ -26,65 +26,71 @@ import time, datetime
|
|||
##
|
||||
# MyTime class.
|
||||
#
|
||||
# This class is a wrapper around time.time() and time.gmtime(). When
|
||||
# performing unit test, it is very useful to get a fixed value from these
|
||||
# functions.
|
||||
# Thus, time.time() and time.gmtime() should never be called directly.
|
||||
# This wrapper should be called instead. The API are equivalent.
|
||||
|
||||
class MyTime:
|
||||
|
||||
"""A wrapper around time module primarily for testing purposes
|
||||
|
||||
This class is a wrapper around time.time() and time.gmtime(). When
|
||||
performing unit test, it is very useful to get a fixed value from
|
||||
these functions. Thus, time.time() and time.gmtime() should never
|
||||
be called directly. This wrapper should be called instead. The API
|
||||
are equivalent.
|
||||
"""
|
||||
|
||||
myTime = None
|
||||
|
||||
##
|
||||
# Sets the current time.
|
||||
#
|
||||
# Use None in order to always get the real current time.
|
||||
#
|
||||
# @param t the time to set or None
|
||||
|
||||
#@staticmethod
|
||||
|
||||
@staticmethod
|
||||
def setTime(t):
|
||||
"""Set current time.
|
||||
|
||||
Use None in order to always get the real current time.
|
||||
|
||||
@param t the time to set or None
|
||||
"""
|
||||
|
||||
MyTime.myTime = t
|
||||
setTime = staticmethod(setTime)
|
||||
|
||||
##
|
||||
# Equivalent to time.time()
|
||||
#
|
||||
# @return time.time() if setTime was called with None
|
||||
|
||||
#@staticmethod
|
||||
|
||||
@staticmethod
|
||||
def time():
|
||||
"""Decorate time.time() for the purpose of testing mocking
|
||||
|
||||
@return time.time() if setTime was called with None
|
||||
"""
|
||||
|
||||
if MyTime.myTime is None:
|
||||
return time.time()
|
||||
else:
|
||||
return MyTime.myTime
|
||||
time = staticmethod(time)
|
||||
|
||||
##
|
||||
# Equivalent to time.gmtime()
|
||||
#
|
||||
# @return time.gmtime() if setTime was called with None
|
||||
|
||||
#@staticmethod
|
||||
|
||||
@staticmethod
|
||||
def gmtime():
|
||||
"""Decorate time.gmtime() for the purpose of testing mocking
|
||||
|
||||
@return time.gmtime() if setTime was called with None
|
||||
"""
|
||||
if MyTime.myTime is None:
|
||||
return time.gmtime()
|
||||
else:
|
||||
return time.gmtime(MyTime.myTime)
|
||||
gmtime = staticmethod(gmtime)
|
||||
|
||||
#@staticmethod
|
||||
@staticmethod
|
||||
def now():
|
||||
"""Decorate datetime.now() for the purpose of testing mocking
|
||||
|
||||
@return datetime.now() if setTime was called with None
|
||||
"""
|
||||
if MyTime.myTime is None:
|
||||
return datetime.datetime.now()
|
||||
else:
|
||||
return datetime.datetime.fromtimestamp(MyTime.myTime)
|
||||
now = staticmethod(now)
|
||||
|
||||
@staticmethod
|
||||
def localtime(x=None):
|
||||
"""Decorate time.localtime() for the purpose of testing mocking
|
||||
|
||||
@return time.localtime() if setTime was called with None
|
||||
"""
|
||||
if MyTime.myTime is None or x is not None:
|
||||
return time.localtime(x)
|
||||
else:
|
||||
return time.localtime(MyTime.myTime)
|
||||
localtime = staticmethod(localtime)
|
||||
|
|
|
@ -28,6 +28,7 @@ import time
|
|||
import json
|
||||
|
||||
from ..helpers import getLogger
|
||||
from .. import version
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
|
@ -102,7 +103,9 @@ class Transmitter:
|
|||
elif command[0] == "get":
|
||||
return self.__commandGet(command[1:])
|
||||
elif command[0] == "status":
|
||||
return self.status(command[1:])
|
||||
return self.status(command[1:])
|
||||
elif command[0] == "version":
|
||||
return version.version
|
||||
raise Exception("Invalid command")
|
||||
|
||||
def __commandSet(self, command):
|
||||
|
|
|
@ -22,11 +22,7 @@ import unittest
|
|||
import sys
|
||||
|
||||
from ..dummyjail import DummyJail
|
||||
|
||||
if os.path.exists('config/fail2ban.conf'):
|
||||
CONFIG_DIR = "config"
|
||||
else:
|
||||
CONFIG_DIR='/etc/fail2ban'
|
||||
from ..utils import CONFIG_DIR
|
||||
|
||||
if sys.version_info >= (2,7):
|
||||
class BadIPsActionTest(unittest.TestCase):
|
||||
|
|
|
@ -30,10 +30,7 @@ else:
|
|||
|
||||
from ..dummyjail import DummyJail
|
||||
|
||||
if os.path.exists('config/fail2ban.conf'):
|
||||
CONFIG_DIR = "config"
|
||||
else:
|
||||
CONFIG_DIR='/etc/fail2ban'
|
||||
from ..utils import CONFIG_DIR
|
||||
|
||||
class TestSMTPServer(smtpd.SMTPServer):
|
||||
|
||||
|
|
|
@ -21,9 +21,9 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
|||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2011-2013 Yaroslav Halchenko"
|
||||
__license__ = "GPL"
|
||||
|
||||
import os, glob, shutil, tempfile, unittest
|
||||
|
||||
from ..client.configreader import ConfigReader
|
||||
import os, glob, shutil, tempfile, unittest, re, logging
|
||||
from ..client.configreader import ConfigReaderUnshared
|
||||
from ..client import configparserinc
|
||||
from ..client.jailreader import JailReader
|
||||
from ..client.filterreader import FilterReader
|
||||
from ..client.jailsreader import JailsReader
|
||||
|
@ -32,8 +32,10 @@ from ..client.configurator import Configurator
|
|||
from .utils import LogCaptureTestCase
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
|
||||
from .utils import CONFIG_DIR
|
||||
|
||||
STOCK = os.path.exists(os.path.join('config','fail2ban.conf'))
|
||||
CONFIG_DIR='config' if STOCK else '/etc/fail2ban'
|
||||
|
||||
IMPERFECT_CONFIG = os.path.join(os.path.dirname(__file__), 'config')
|
||||
|
||||
|
@ -42,7 +44,7 @@ class ConfigReaderTest(unittest.TestCase):
|
|||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
self.d = tempfile.mkdtemp(prefix="f2b-temp")
|
||||
self.c = ConfigReader(basedir=self.d)
|
||||
self.c = ConfigReaderUnshared(basedir=self.d)
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
|
@ -333,6 +335,64 @@ class FilterReaderTest(unittest.TestCase):
|
|||
self.assertRaises(ValueError, FilterReader.convert, filterReader)
|
||||
|
||||
|
||||
class JailsReaderTestCache(LogCaptureTestCase):
|
||||
|
||||
def _readWholeConf(self, basedir, force_enable=False, share_config=None):
|
||||
# read whole configuration like a file2ban-client ...
|
||||
configurator = Configurator(force_enable=force_enable, share_config=share_config)
|
||||
configurator.setBaseDir(basedir)
|
||||
configurator.readEarly()
|
||||
configurator.getEarlyOptions()
|
||||
configurator.readAll()
|
||||
# from here we test a cache with all includes / before / after :
|
||||
self.assertTrue(configurator.getOptions(None))
|
||||
|
||||
def _getLoggedReadCount(self, filematch):
|
||||
cnt = 0
|
||||
for s in self.getLog().rsplit('\n'):
|
||||
if re.match(r"^\s*Reading files?: .*/"+filematch, s):
|
||||
cnt += 1
|
||||
return cnt
|
||||
|
||||
def testTestJailConfCache(self):
|
||||
saved_ll = configparserinc.logLevel
|
||||
configparserinc.logLevel = logging.DEBUG
|
||||
basedir = tempfile.mkdtemp("fail2ban_conf")
|
||||
try:
|
||||
shutil.rmtree(basedir)
|
||||
shutil.copytree(CONFIG_DIR, basedir)
|
||||
shutil.copy(CONFIG_DIR + '/jail.conf', basedir + '/jail.local')
|
||||
shutil.copy(CONFIG_DIR + '/fail2ban.conf', basedir + '/fail2ban.local')
|
||||
|
||||
# common sharing handle for this test:
|
||||
share_cfg = dict()
|
||||
|
||||
# read whole configuration like a file2ban-client ...
|
||||
self._readWholeConf(basedir, share_config=share_cfg)
|
||||
# how many times jail.local was read:
|
||||
cnt = self._getLoggedReadCount('jail.local')
|
||||
# if cnt > 1:
|
||||
# self.printLog()
|
||||
self.assertTrue(cnt == 1, "Unexpected count by reading of jail files, cnt = %s" % cnt)
|
||||
|
||||
# read whole configuration like a file2ban-client, again ...
|
||||
# but this time force enable all jails, to check filter and action cached also:
|
||||
self._readWholeConf(basedir, force_enable=True, share_config=share_cfg)
|
||||
cnt = self._getLoggedReadCount(r'jail\.local')
|
||||
# still one (no more reads):
|
||||
self.assertTrue(cnt == 1, "Unexpected count by second reading of jail files, cnt = %s" % cnt)
|
||||
|
||||
# same with filter:
|
||||
cnt = self._getLoggedReadCount(r'filter\.d/common\.conf')
|
||||
self.assertTrue(cnt == 1, "Unexpected count by reading of filter files, cnt = %s" % cnt)
|
||||
# same with action:
|
||||
cnt = self._getLoggedReadCount(r'action\.d/iptables-common\.conf')
|
||||
self.assertTrue(cnt == 1, "Unexpected count by reading of action files, cnt = %s" % cnt)
|
||||
finally:
|
||||
shutil.rmtree(basedir)
|
||||
configparserinc.logLevel = saved_ll
|
||||
|
||||
|
||||
class JailsReaderTest(LogCaptureTestCase):
|
||||
|
||||
def testProvidingBadBasedir(self):
|
||||
|
|
|
@ -173,6 +173,12 @@ class DatabaseTest(unittest.TestCase):
|
|||
self.assertTrue(
|
||||
isinstance(self.db.getBans(jail=self.jail)[0], FailTicket))
|
||||
|
||||
def testDelBan(self):
|
||||
self.testAddBan()
|
||||
ticket = self.db.getBans(jail=self.jail)[0]
|
||||
self.db.delBan(self.jail, ticket)
|
||||
self.assertEqual(len(self.db.getBans(jail=self.jail)), 0)
|
||||
|
||||
def testGetBansWithTime(self):
|
||||
if Fail2BanDb is None: # pragma: no cover
|
||||
return
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
#!/bin/env python
|
||||
import requests
|
||||
import md5
|
||||
|
||||
try:
|
||||
import hashlib
|
||||
md5sum = hashlib.md5
|
||||
except ImportError: # pragma: no cover
|
||||
# hashlib was introduced in Python 2.5. For compatibility with those
|
||||
# elderly Pythons, import from md5
|
||||
import md5
|
||||
md5sum = md5.new
|
||||
|
||||
def auth(v):
|
||||
|
||||
ha1 = md5.new(username + ':' + realm + ':' + password).hexdigest()
|
||||
ha2 = md5.new("GET:" + url).hexdigest()
|
||||
ha1 = md5sum(username + ':' + realm + ':' + password).hexdigest()
|
||||
ha2 = md5sum("GET:" + url).hexdigest()
|
||||
|
||||
#response = md5.new(ha1 + ':' + v['nonce'][1:-1] + ':' + v['nc'] + ':' + v['cnonce'][1:-1]
|
||||
#response = md5sum(ha1 + ':' + v['nonce'][1:-1] + ':' + v['nc'] + ':' + v['cnonce'][1:-1]
|
||||
# + ':' + v['qop'][1:-1] + ':' + ha2).hexdigest()
|
||||
|
||||
nonce = v['nonce'][1:-1]
|
||||
|
@ -17,7 +24,7 @@ def auth(v):
|
|||
#opaque = v.get('opaque') or ''
|
||||
qop = v['qop'][1:-1]
|
||||
algorithm = v['algorithm']
|
||||
response = md5.new(ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2).hexdigest()
|
||||
response = md5sum(ha1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2).hexdigest()
|
||||
|
||||
p = requests.Request('GET', host + url).prepare()
|
||||
#p.headers['Authentication-Info'] = response
|
||||
|
@ -33,13 +40,13 @@ def auth(v):
|
|||
response="%s"
|
||||
""" % ( username, algorithm, realm, url, nonce, qop, response )
|
||||
# opaque="%s",
|
||||
print p.method, p.url, p.headers
|
||||
print(p.method, p.url, p.headers)
|
||||
s = requests.Session()
|
||||
return s.send(p)
|
||||
|
||||
def preauth():
|
||||
r = requests.get(host + url)
|
||||
print r
|
||||
print(r)
|
||||
r.headers['www-authenticate'].split(', ')
|
||||
return dict([ a.split('=',1) for a in r.headers['www-authenticate'].split(', ') ])
|
||||
|
||||
|
@ -51,7 +58,7 @@ v = preauth()
|
|||
|
||||
username="username"
|
||||
password = "password"
|
||||
print v
|
||||
print(v)
|
||||
|
||||
realm = 'so far away'
|
||||
r = auth(v)
|
||||
|
@ -67,18 +74,18 @@ r = auth(v)
|
|||
|
||||
# [Sun Jul 28 21:41:20 2013] [error] [client 127.0.0.1] Digest: unknown algorithm `super funky chicken' received: /digest/
|
||||
|
||||
print r.status_code,r.headers, r.text
|
||||
print(r.status_code,r.headers, r.text)
|
||||
v['algorithm'] = algorithm
|
||||
|
||||
|
||||
r = auth(v)
|
||||
print r.status_code,r.headers, r.text
|
||||
print(r.status_code,r.headers, r.text)
|
||||
|
||||
nonce = v['nonce']
|
||||
v['nonce']=v['nonce'][5:-5]
|
||||
|
||||
r = auth(v)
|
||||
print r.status_code,r.headers, r.text
|
||||
print(r.status_code,r.headers, r.text)
|
||||
|
||||
# [Sun Jul 28 21:05:31.178340 2013] [auth_digest:error] [pid 24224:tid 139895539455744] [client 127.0.0.1:56906] AH01793: invalid qop `auth' received: /digest/qop_none/
|
||||
|
||||
|
@ -86,7 +93,7 @@ print r.status_code,r.headers, r.text
|
|||
v['nonce']=nonce[0:11] + 'ZZZ' + nonce[14:]
|
||||
|
||||
r = auth(v)
|
||||
print r.status_code,r.headers, r.text
|
||||
print(r.status_code,r.headers, r.text)
|
||||
|
||||
#[Sun Jul 28 21:18:11.769228 2013] [auth_digest:error] [pid 24752:tid 139895505884928] [client 127.0.0.1:56964] AH01776: invalid nonce b9YAiJDiBAZZZ1b1abe02d20063ea3b16b544ea1b0d981c1bafe received - hash is not d42d824dee7aaf50c3ba0a7c6290bd453e3dd35b
|
||||
|
||||
|
@ -98,7 +105,7 @@ import time
|
|||
time.sleep(1)
|
||||
|
||||
r = auth(v)
|
||||
print r.status_code,r.headers, r.text
|
||||
print(r.status_code,r.headers, r.text)
|
||||
|
||||
# Obtained by putting the following code in modules/aaa/mod_auth_digest.c
|
||||
# in the function initialize_secret
|
||||
|
@ -128,7 +135,7 @@ s = sha.sha(apachesecret)
|
|||
|
||||
v=preauth()
|
||||
|
||||
print v['nonce']
|
||||
print(v['nonce'])
|
||||
realm = v['Digest realm'][1:-1]
|
||||
|
||||
(t,) = struct.unpack('l',base64.b64decode(v['nonce'][1:13]))
|
||||
|
@ -143,17 +150,17 @@ s.update(timepac)
|
|||
|
||||
v['nonce'] = v['nonce'][0] + timepac + s.hexdigest() + v['nonce'][-1]
|
||||
|
||||
print v
|
||||
print(v)
|
||||
|
||||
r = auth(v)
|
||||
#[Mon Jul 29 02:12:55.539813 2013] [auth_digest:error] [pid 9647:tid 139895522670336] [client 127.0.0.1:58474] AH01777: invalid nonce 59QJppTiBAA=b08983fd166ade9840407df1b0f75b9e6e07d88d received - user attempted time travel
|
||||
print r.status_code,r.headers, r.text
|
||||
print(r.status_code,r.headers, r.text)
|
||||
|
||||
url='/digest_onetime/'
|
||||
v=preauth()
|
||||
|
||||
# Need opaque header handling in auth
|
||||
r = auth(v)
|
||||
print r.status_code,r.headers, r.text
|
||||
print(r.status_code,r.headers, r.text)
|
||||
r = auth(v)
|
||||
print r.status_code,r.headers, r.text
|
||||
print(r.status_code,r.headers, r.text)
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
# failJSON: { "time": "2013-07-20T21:34:49", "match": true , "host": "127.0.0.1" }
|
||||
[Sat Jul 20 21:34:49.453232 2013] [access_compat:error] [pid 17512:tid 140123104306944] [client 127.0.0.1:51380] AH01797: client denied by server configuration: /var/www/html/noentry/cant_get_me.html
|
||||
|
||||
# failJSON: { "time": "2014-09-14T21:44:43", "match": true , "host": "192.3.9.178" }
|
||||
[Sun Sep 14 21:44:43.008606 2014] [authz_core:error] [pid 10691] [client 192.3.9.178:44271] AH01630: client denied by server configuration: /var/www/html/noentry/cant_get_me.html
|
||||
|
||||
# wget --http-user='' --http-password='' http://localhost/basic/file/cant_get_me.html -O /dev/null
|
||||
# failJSON: { "time": "2013-07-17T23:14:37", "match": true , "host": "127.0.0.1" }
|
||||
[Wed Jul 17 23:14:37 2013] [error] [client 127.0.0.1] user not found: /basic/anon/cant_get_me.html
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# failJSON: { "time": "2014-09-25T09:27:18", "match": true , "host": "89.207.132.76" }
|
||||
[Thu Sep 25 09:27:18.813902 2014] [cgi:error] [pid 16860] [client 89.207.132.76:59635] AH01215: /bin/bash: warning: HTTP_TEST: ignoring function definition attempt
|
||||
# failJSON: { "time": "2014-09-25T09:29:56", "match": true , "host": "162.247.73.206" }
|
||||
[Thu Sep 25 09:29:56.141832 2014] [cgi:error] [pid 16864] [client 162.247.73.206:41273] AH01215: /bin/bash: error importing function definition for `HTTP_TEST'
|
|
@ -1,5 +1,9 @@
|
|||
# failJSON: { "time": "2005-04-10T03:47:57", "match": true , "host": "1.2.3.4" }
|
||||
Apr 10 03:47:57 web courieresmtpd: error,relay=::ffff:1.2.3.4,ident=tmf,from=<tmf@example.com>,to=<mailman-subscribe@example.com>: 550 User unknown.
|
||||
# failJSON: { "time": "2005-07-03T23:07:20", "match": true , "host": "1.2.3.4" }
|
||||
Jul 3 23:07:20 szerver courieresmtpd: error,relay=::ffff:1.2.3.4,msg="535 Authentication failed.",cmd: YWRvYmVhZG9iZQ==
|
||||
# failJSON: { "time": "2005-07-04T18:39:39", "match": true , "host": "1.2.3.4" }
|
||||
Jul 4 18:39:39 mail courieresmtpd: error,relay=::ffff:1.2.3.4,from=<picaro@astroboymail.com>,to=<user@update.net>: 550 User <benny> unknown
|
||||
# failJSON: { "time": "2005-07-06T03:42:28", "match": true , "host": "1.2.3.4" }
|
||||
Jul 6 03:42:28 whistler courieresmtpd: error,relay=::ffff:1.2.3.4,from=<>,to=<admin at memcpy>: 550 User unknown.
|
||||
# failJSON: { "time": "2004-11-21T23:16:17", "match": true , "host": "1.2.3.4" }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# failJSON: { "time": "2005-01-04T21:51:05", "match": true , "host": "127.0.0.1" }
|
||||
Jan 4 21:51:05 hostname cyrus/imap[5355]: badlogin: localhost.localdomain [127.0.0.1] plaintext cyrus@localdomain SASL(-13): authentication failure: checkpass failed
|
||||
# failJSON: { "time": "2005-01-04T21:51:05", "match": true , "host": "127.0.0.1", "desc": "For secure imaps" }
|
||||
Jan 4 21:51:05 hostname cyrus/imaps[5355]: badlogin: localhost.localdomain [127.0.0.1] plaintext cyrus@localdomain SASL(-13): authentication failure: checkpass failed
|
||||
# failJSON: { "time": "2005-02-20T17:23:32", "match": true , "host": "198.51.100.23" }
|
||||
Feb 20 17:23:32 domain cyrus/pop3[18635]: badlogin: localhost [198.51.100.23] plaintext administrator SASL(-13): authentication failure: checkpass failed
|
||||
# failJSON: { "time": "2005-02-20T17:23:32", "match": true , "host": "1.2.3.4" }
|
||||
|
@ -10,4 +12,7 @@ Jun 8 18:11:13 lampserver imap[4480]: badlogin: example.com [198.51.100.45] DIGE
|
|||
Dec 21 10:01:57 hostname imapd[18454]: badlogin: example.com [198.51.100.57] CRAM-MD5 [SASL(-13): authentication failure: incorrect digest response]
|
||||
# failJSON: { "time": "2004-12-30T16:03:27", "match": true , "host": "1.2.3.4" }
|
||||
Dec 30 16:03:27 somehost imapd[2517]: badlogin: local-somehost[1.2.3.4] OTP [SASL(-13): authentication failure: External SSF not good enough]
|
||||
|
||||
# failJSON: { "time": "2005-07-17T22:55:56", "match": true , "host": "1.2.3.4" }
|
||||
Jul 17 22:55:56 derry cyrus/imaps[7568]: badlogin: serafinat.xxxxxx [1.2.3.4] plain [SASL(-13): user not found: user: pressy@derry property: cmusaslsecretPLAIN not found in sasldb]
|
||||
# failJSON: { "time": "2005-07-18T16:46:42", "match": true , "host": "1.2.3.4" }
|
||||
Jul 18 16:46:42 derry cyrus/imaps[27449]: badlogin: serafinat.xxxxxx [1.2.3.4] PLAIN [SASL(-13): user not found: Password verification failed]
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# failJSON: { "time": "2014-07-02T00:17:45", "match": true , "host": "3.2.1.4" }
|
||||
2014:07:02-00:17:45: '3.2.1.4' 2 failed login attempts. Account 'test'
|
||||
|
||||
# failJSON: { "time": "2014-07-02T13:07:40", "match": true , "host": "40.40.123.231" }
|
||||
2014:07:02-13:07:40: '40.40.123.231' 13 failed login attempts. Account 'admin'
|
||||
|
||||
# failJSON: { "time": "2014-07-02T13:07:50", "match": true , "host": "40.40.123.231" }
|
||||
2014:07:02-13:07:50: '40.40.123.231' 5 failed login attempt. Invalid account 'user%2Ename'
|
||||
|
||||
# failJSON: { "time": "2014-07-02T13:28:39", "match": false , "host": "12.12.123.231" }
|
||||
2014:07:02-13:28:39: '12.12.123.231' successful login to 'nobody' after 1 attempts
|
||||
|
||||
# failJSON: { "time": "2014-07-02T13:29:38", "match": true , "host": "1.2.3.4" }
|
||||
2014:07:02-13:29:38: '1.2.3.4' 2 failed login attempts. Account 'user' via 'admin'
|
|
@ -5,3 +5,6 @@ Dec 2 22:24:22 hel postfix/smtpd[7676]: warning: 114-44-142-233.dynamic.hinet.n
|
|||
# failJSON: { "time": "2005-03-10T13:33:30", "match": true , "host": "1.1.1.1" }
|
||||
Mar 10 13:33:30 gandalf postfix/smtpd[3937]: warning: HOSTNAME[1.1.1.1]: SASL LOGIN authentication failed: authentication failure
|
||||
|
||||
#3 Example from postfix post-debian changes to rename to add "submission" to syslog name
|
||||
# failJSON: { "time": "2004-09-06T00:44:56", "match": true , "host": "82.221.106.233" }
|
||||
Sep 6 00:44:56 trianon postfix/submission/smtpd[11538]: warning: unknown[82.221.106.233]: SASL LOGIN authentication failed: UGFzc3dvcmQ6
|
||||
|
|
|
@ -24,6 +24,7 @@ __license__ = "GPL"
|
|||
|
||||
from __builtin__ import open as fopen
|
||||
import unittest
|
||||
import getpass
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
@ -349,10 +350,20 @@ class LogFileMonitor(LogCaptureTestCase):
|
|||
# shorter wait time for not modified status
|
||||
return not self.isModified(0.4)
|
||||
|
||||
def testNoLogFile(self):
|
||||
def testUnaccessibleLogFile(self):
|
||||
os.chmod(self.name, 0)
|
||||
self.filter.getFailures(self.name)
|
||||
self.assertTrue(self._is_logged('Unable to open %s' % self.name))
|
||||
failure_was_logged = self._is_logged('Unable to open %s' % self.name)
|
||||
is_root = getpass.getuser() == 'root'
|
||||
# If ran as root, those restrictive permissions would not
|
||||
# forbid log to be read.
|
||||
self.assertTrue(failure_was_logged != is_root)
|
||||
|
||||
def testNoLogFile(self):
|
||||
_killfile(self.file, self.name)
|
||||
self.filter.getFailures(self.name)
|
||||
failure_was_logged = self._is_logged('Unable to open %s' % self.name)
|
||||
self.assertTrue(failure_was_logged)
|
||||
|
||||
def testRemovingFailRegex(self):
|
||||
self.filter.delFailRegex(0)
|
||||
|
|
|
@ -24,21 +24,13 @@ __license__ = "GPL"
|
|||
|
||||
import unittest, sys, os, fileinput, re, time, datetime, inspect
|
||||
|
||||
if sys.version_info >= (2, 6):
|
||||
import json
|
||||
else:
|
||||
import simplejson as json
|
||||
next = lambda x: x.next()
|
||||
import json
|
||||
|
||||
from ..server.filter import Filter
|
||||
from ..client.filterreader import FilterReader
|
||||
from .utils import setUpMyTime, tearDownMyTime
|
||||
from .utils import setUpMyTime, tearDownMyTime, CONFIG_DIR
|
||||
|
||||
TEST_FILES_DIR = os.path.join(os.path.dirname(__file__), "files")
|
||||
if os.path.exists('config/fail2ban.conf'):
|
||||
CONFIG_DIR = "config"
|
||||
else:
|
||||
CONFIG_DIR='/etc/fail2ban'
|
||||
|
||||
class FilterSamplesRegex(unittest.TestCase):
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ from ..server.jail import Jail
|
|||
from ..server.jailthread import JailThread
|
||||
from .utils import LogCaptureTestCase
|
||||
from ..helpers import getLogger
|
||||
from .. import version
|
||||
|
||||
try:
|
||||
from ..server import filtersystemd
|
||||
|
@ -148,6 +149,9 @@ class Transmitter(TransmitterBase):
|
|||
def testPing(self):
|
||||
self.assertEqual(self.transm.proceed(["ping"]), (0, "pong"))
|
||||
|
||||
def testVersion(self):
|
||||
self.assertEqual(self.transm.proceed(["version"]), (0, version.version))
|
||||
|
||||
def testSleep(self):
|
||||
t0 = time.time()
|
||||
self.assertEqual(self.transm.proceed(["sleep", "1"]), (0, None))
|
||||
|
@ -800,7 +804,7 @@ class RegexTests(unittest.TestCase):
|
|||
|
||||
class _BadThread(JailThread):
|
||||
def run(self):
|
||||
int("cat")
|
||||
int("ignore this exception -- raised for testing")
|
||||
|
||||
class LoggingTests(LogCaptureTestCase):
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ __license__ = "GPL"
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
from StringIO import StringIO
|
||||
|
@ -34,6 +35,15 @@ from ..helpers import getLogger
|
|||
|
||||
logSys = getLogger(__name__)
|
||||
|
||||
CONFIG_DIR = os.environ.get('FAIL2BAN_CONFIG_DIR', None)
|
||||
|
||||
if not CONFIG_DIR:
|
||||
# Use heuristic to figure out where configuration files are
|
||||
if os.path.exists(os.path.join('config','fail2ban.conf')):
|
||||
CONFIG_DIR = 'config'
|
||||
else:
|
||||
CONFIG_DIR = '/etc/fail2ban'
|
||||
|
||||
def mtimesleep():
|
||||
# no sleep now should be necessary since polling tracks now not only
|
||||
# mtime but also ino and size
|
||||
|
@ -102,6 +112,7 @@ def gatherTests(regexps=None, no_network=False):
|
|||
tests.addTest(unittest.makeSuite(clientreadertestcase.JailReaderTest))
|
||||
tests.addTest(unittest.makeSuite(clientreadertestcase.FilterReaderTest))
|
||||
tests.addTest(unittest.makeSuite(clientreadertestcase.JailsReaderTest))
|
||||
tests.addTest(unittest.makeSuite(clientreadertestcase.JailsReaderTestCache))
|
||||
# CSocket and AsyncServer
|
||||
tests.addTest(unittest.makeSuite(sockettestcase.Socket))
|
||||
# Misc helpers
|
||||
|
@ -195,6 +206,8 @@ class LogCaptureTestCase(unittest.TestCase):
|
|||
# Let's log everything into a string
|
||||
self._log = StringIO()
|
||||
logSys.handlers = [logging.StreamHandler(self._log)]
|
||||
if self._old_level < logging.DEBUG: # so if HEAVYDEBUG etc -- show them!
|
||||
logSys.handlers += self._old_handlers
|
||||
logSys.setLevel(getattr(logging, 'DEBUG'))
|
||||
|
||||
def tearDown(self):
|
||||
|
@ -207,5 +220,8 @@ class LogCaptureTestCase(unittest.TestCase):
|
|||
def _is_logged(self, s):
|
||||
return s in self._log.getvalue()
|
||||
|
||||
def getLog(self):
|
||||
return self._log.getvalue()
|
||||
|
||||
def printLog(self):
|
||||
print(self._log.getvalue())
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
#!/usr/bin/perl
|
||||
##########################################################################
|
||||
# $Id: fail2ban 150 2013-06-18 22:19:38Z mtremaine $
|
||||
##########################################################################
|
||||
# $Log: fail2ban,v $
|
||||
#
|
||||
# Revision 1.6 2014/08/11 16:07:46 yoh
|
||||
# Patches from Yaroslav Halchenko to match adjusted in 0.9.x lines.
|
||||
# Also reports now total number of hits (matches) along with Ban:Unban
|
||||
# and relaxed regular expressions for matching any log level
|
||||
#
|
||||
# Revision 1.5 2008/08/18 16:07:46 mike
|
||||
# Patches from Paul Gear <paul at libertysys.com> -mgt
|
||||
#
|
||||
# Revision 1.4 2008/06/30 23:07:51 kirk
|
||||
# fixed copyright holders for files where I know who they should be
|
||||
#
|
||||
# Revision 1.3 2008/03/24 23:31:26 kirk
|
||||
# added copyright/license notice to each script
|
||||
#
|
||||
# Revision 1.2 2006/12/15 04:53:59 bjorn
|
||||
# Additional filtering, by Willi Mann.
|
||||
#
|
||||
# Revision 1.1 2006/05/30 19:04:26 bjorn
|
||||
# Added fail2ban service, written by Yaroslav Halchenko.
|
||||
#
|
||||
# Written by Yaroslav Halchenko <debian@onerussian.com> for fail2ban
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
########################################################
|
||||
## Copyright (c) 2008 Yaroslav Halchenko
|
||||
## Covered under the included MIT/X-Consortium License:
|
||||
## http://www.opensource.org/licenses/mit-license.php
|
||||
## All modifications and contributions by other persons to
|
||||
## this script are assumed to have been donated to the
|
||||
## Logwatch project and thus assume the above copyright
|
||||
## and licensing terms. If you want to make contributions
|
||||
## under your own copyright or a different license this
|
||||
## must be explicitly stated in the contribution an the
|
||||
## Logwatch project reserves the right to not accept such
|
||||
## contributions. If you have made significant
|
||||
## contributions to this script and want to claim
|
||||
## copyright please contact logwatch-devel@lists.sourceforge.net.
|
||||
#########################################################
|
||||
|
||||
use strict;
|
||||
use Logwatch ':all';
|
||||
|
||||
my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0;
|
||||
my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;
|
||||
my $IgnoreHost = $ENV{'sshd_ignore_host'} || "";
|
||||
my $DebugCounter = 0;
|
||||
my $ReInitializations = 0;
|
||||
my @ActionsErrors = ();
|
||||
my @CommandsErrors = ();
|
||||
my $NotValidIP = 0; # reported invalid IPs number
|
||||
my @OtherList = ();
|
||||
|
||||
my %ServicesBans = ();
|
||||
|
||||
if ( $Debug >= 5 ) {
|
||||
print STDERR "\n\nDEBUG: Inside Fail2Ban Filter \n\n";
|
||||
$DebugCounter = 1;
|
||||
}
|
||||
|
||||
while (defined(my $ThisLine = <STDIN>)) {
|
||||
if ( $Debug >= 5 ) {
|
||||
print STDERR "DEBUG($DebugCounter): $ThisLine";
|
||||
$DebugCounter++;
|
||||
}
|
||||
chomp($ThisLine);
|
||||
if ( ($ThisLine =~ /..,... DEBUG: /) or
|
||||
($ThisLine =~ /..,... \S*\s*: DEBUG /) or # syntax of 0.7.? fail2ban
|
||||
($ThisLine =~ /..,... \S+: (Fail2Ban v.* is running|Exiting|Enabled sections:)/) or
|
||||
($ThisLine =~ /\S+\s+rollover performed on/) or
|
||||
($ThisLine =~ /\S+\s+Connected to .* persistent database/) or
|
||||
($ThisLine =~ /\S+\s+Jail '.*' uses .*/) or
|
||||
($ThisLine =~ /\S+\s+Initiated '.*' backend/) or
|
||||
($ThisLine =~ /\S+\s+Jail .* is not a JournalFilter instance/) or
|
||||
($ThisLine =~ /\S+\s+Log rotation detected for/) or
|
||||
($ThisLine =~ /\S+\s+Jail.+(?:stopped|started|uses poller)/) or
|
||||
($ThisLine =~ /\S+\s+Changed logging target to/) or
|
||||
($ThisLine =~ /\S+\s+Creating new jail/) or
|
||||
($ThisLine =~ /..,... \S+\s*: INFO\s+(Set |Socket|Exiting|Gamin|Created|Added|Using)/) or # syntax of 0.7.? fail2ban
|
||||
($ThisLine =~ /..,... \S+: Verbose level is /) or
|
||||
($ThisLine =~ /..,... \S+: Restoring firewall rules/)
|
||||
)
|
||||
{
|
||||
if ( $Debug >= 6 ) {
|
||||
print STDERR "DEBUG($DebugCounter): line ignored\n";
|
||||
}
|
||||
} elsif ( my ($LogLevel,$Service,$Action,$Host) = ($ThisLine =~ m/(WARNING|NOTICE):?\s+\[?(.*?)[]:]?\s(Ban|Unban)[^\.]* (\S+)/)) {
|
||||
if ( $Debug >= 6 ) {
|
||||
print STDERR "DEBUG($DebugCounter): Found $Action for $Service from $Host\n";
|
||||
}
|
||||
$ServicesBans{$Service}{$Host}{$Action}++;
|
||||
$ServicesBans{$Service}{"(all)"}{$Action}++;
|
||||
} elsif ( my ($LogLevel,$Service,$Host) = ($ThisLine =~ m/(INFO|WARNING|NOTICE):?\s+\[?(.*?)[]:]?\sFound[^\.]* (\S+)/)) {
|
||||
if ( $Debug >= 6 ) {
|
||||
print STDERR "DEBUG($DebugCounter): Found hit for $Service from $Host\n";
|
||||
}
|
||||
$ServicesBans{$Service}{$Host}{"Hit"}++;
|
||||
$ServicesBans{$Service}{"(all)"}{"Hit"}++;
|
||||
} elsif ( my ($Service,$Host,$NumFailures) = ($ThisLine =~ m/\S+:\s+(\S+): (.+) has (\d+) login failure\(s\). Banned./)) {
|
||||
if ($Debug >= 4) {
|
||||
print STDERR "DEBUG: Found host $Host trying to access $Service - failed $NumFailures times\n";
|
||||
}
|
||||
push @{$ServicesBans{$Service}{$Host}{'Failures'}}, $NumFailures;
|
||||
} elsif ( my ($Service,$Host) = ($ThisLine =~ m/ \S+:\s(.*):\s(\S+)\salready in ban list/)) {
|
||||
$ServicesBans{$Service}{$Host}{'AlreadyInTheList'}++;
|
||||
} elsif ( my ($Service,$Host) = ($ThisLine =~ m/\S+:?\s+\[?([^[]*?)[]:]?\s+(\S+)\salready banned/)) {
|
||||
if ( $Debug >= 6 ) {
|
||||
print STDERR "DEBUG($DebugCounter): Found hit for already banned $Host against $Service\n";
|
||||
}
|
||||
$ServicesBans{$Service}{$Host}{'AlreadyInTheList'}++;
|
||||
} elsif ( my ($Service,$Host) = ($ThisLine =~ m/ \S+:\s(.*):\sReBan (\S+)/)) {
|
||||
$ServicesBans{$Service}{$Host}{'ReBan'}++;
|
||||
} elsif ($ThisLine =~ / ERROR:?\s*(Execution of command )?\'?iptables/) {
|
||||
push @ActionsErrors, "$ThisLine\n";
|
||||
} elsif ($ThisLine =~ / ERROR\s*Failed to execute.*action/) {
|
||||
push @ActionsErrors, "$ThisLine\n";
|
||||
} elsif ($ThisLine =~ / WARNING Command \[.*\] has failed. Received/) {
|
||||
push @CommandsErrors, "$ThisLine\n";
|
||||
} elsif ($ThisLine =~ /ERROR.*returned \d+$/) {
|
||||
push @ActionsErrors, "$ThisLine\n";
|
||||
} elsif (($ThisLine =~ /..,... WARNING: \#\S+ reinitialization of firewalls/) or
|
||||
($ThisLine =~ / ERROR\s*Invariant check failed. Trying to restore a sane environment/)) {
|
||||
$ReInitializations++;
|
||||
} elsif ($ThisLine =~ /..,... WARNING: is not a valid IP address/) {
|
||||
# just ignore - this will be fixed within fail2ban and is harmless warning
|
||||
}
|
||||
else
|
||||
{
|
||||
# Report any unmatched entries...
|
||||
push @OtherList, "$ThisLine\n";
|
||||
}
|
||||
}
|
||||
|
||||
###########################################################
|
||||
|
||||
|
||||
if (keys %ServicesBans) {
|
||||
printf("\nBanned services with Fail2Ban: Bans:Unbans:Hits\n");
|
||||
foreach my $service (sort {$a cmp $b} keys %ServicesBans) {
|
||||
printf(" %-55s [%3d:%d:%-3d]\n", "$service:",
|
||||
$ServicesBans{$service}{'(all)'}{'Ban'},
|
||||
$ServicesBans{$service}{'(all)'}{'Unban'},
|
||||
$ServicesBans{$service}{'(all)'}{'Hit'});
|
||||
delete $ServicesBans{$service}{'(all)'};
|
||||
my $totalSort = TotalCountOrder(%{$ServicesBans{$service}}, \&SortIP);
|
||||
if ($Detail >= 5) {
|
||||
foreach my $ip (sort $totalSort keys %{$ServicesBans{$service}}) {
|
||||
my $name = LookupIP($ip);
|
||||
printf(" %-53s %3d:%d:%-3d\n",
|
||||
$name,
|
||||
$ServicesBans{$service}{$ip}{'Ban'},
|
||||
$ServicesBans{$service}{$ip}{'Unban'},
|
||||
$ServicesBans{$service}{$ip}{'Hit'});
|
||||
if (($Detail >= 10) and ($ServicesBans{$service}{$ip}{'Failures'}>0)) {
|
||||
print " Failed ";
|
||||
foreach my $fails (@{$ServicesBans{$service}{$ip}{'Failures'}}) {
|
||||
print " $fails";
|
||||
}
|
||||
print " times\n";
|
||||
}
|
||||
if ($ServicesBans{$service}{$ip}{'AlreadyInTheList'}>0) {
|
||||
printf(" %d Duplicate Ban attempt(s)\n", $ServicesBans{$service}{$ip}{'AlreadyInTheList'}) ;
|
||||
}
|
||||
if ($ServicesBans{$service}{$ip}{'ReBan'}>0) {
|
||||
printf(" %d ReBan(s) due to rules reinitilizations\n", $ServicesBans{$service}{$ip}{'ReBan'}) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($Detail>0) {
|
||||
if ($#ActionsErrors >= 0) {
|
||||
printf("\n%d faulty action invocation(s)", $#ActionsErrors+1);
|
||||
if ($Detail > 5) {
|
||||
print ":\n";
|
||||
print @ActionsErrors ;
|
||||
}
|
||||
}
|
||||
if ($#CommandsErrors >= 0) {
|
||||
printf("\n%d faulty command invocation(s) from client(s)", $#CommandsErrors+1);
|
||||
if ($Detail > 5) {
|
||||
print ":\n";
|
||||
print @CommandsErrors ;
|
||||
}
|
||||
}
|
||||
if ($ReInitializations > 0) {
|
||||
printf("\n%d fail2ban rules reinitialization(s)", $ReInitializations);
|
||||
}
|
||||
if ($#OtherList >= 0) {
|
||||
print "\n**Unmatched Entries**\n";
|
||||
print @OtherList;
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
|
||||
# vi: shiftwidth=3 tabstop=3 syntax=perl et
|
||||
# Local Variables:
|
||||
# mode: perl
|
||||
# perl-indent-level: 3
|
||||
# indent-tabs-mode: nil
|
||||
# End:
|
|
@ -0,0 +1,2 @@
|
|||
2014-08-04 03:06:26,161 fail2ban.actions[4822]: WARNING [apache-badbots] Ban 37.152.91.34
|
||||
2014-08-05 03:06:26,448 fail2ban.actions[4822]: WARNING [apache-badbots] Unban 37.152.91.34
|
|
@ -0,0 +1,52 @@
|
|||
2014-08-08 14:59:35,013 fail2ban.server.server[31122]: INFO Exiting Fail2ban
|
||||
2014-08-08 14:59:36,041 fail2ban.server.server[21667]: INFO Changed logging target to /var/log/fail2ban.log for Fail2ban v0.9.0
|
||||
2014-08-08 14:59:36,043 fail2ban.server.database[21667]: INFO Connected to fail2ban persistent database '/var/lib/fail2ban/fail2ban.sqlite3'
|
||||
2014-08-08 14:59:36,072 fail2ban.server.jail[21667]: INFO Creating new jail 'exim'
|
||||
2014-08-08 14:59:36,137 fail2ban.server.jail[21667]: INFO Jail 'exim' uses pyinotify
|
||||
2014-08-08 14:59:36,172 fail2ban.server.filter[21667]: INFO Set jail log file encoding to UTF-8
|
||||
2014-08-08 14:59:36,172 fail2ban.server.jail[21667]: INFO Initiated 'pyinotify' backend
|
||||
2014-08-08 14:59:36,233 fail2ban.server.filter[21667]: INFO Added logfile = /var/log/exim4/mainlog
|
||||
2014-08-08 14:59:36,249 fail2ban.server.filter[21667]: INFO Set maxRetry = 5
|
||||
2014-08-08 14:59:36,251 fail2ban.server.filter[21667]: INFO Set jail log file encoding to UTF-8
|
||||
2014-08-08 14:59:36,252 fail2ban.server.actions[21667]: INFO Set banTime = 600
|
||||
2014-08-08 14:59:36,254 fail2ban.server.filter[21667]: INFO Set findtime = 600
|
||||
2014-08-08 14:59:36,284 fail2ban.server.jail[21667]: INFO Creating new jail 'sshd'
|
||||
2014-08-08 14:59:36,284 fail2ban.server.jail[21667]: INFO Jail 'sshd' uses pyinotify
|
||||
2014-08-08 14:59:36,286 fail2ban.server.filter[21667]: INFO Set jail log file encoding to UTF-8
|
||||
2014-08-08 14:59:36,286 fail2ban.server.jail[21667]: INFO Initiated 'pyinotify' backend
|
||||
2014-08-08 14:59:36,499 fail2ban.server.filter[21667]: INFO Added logfile = /var/log/auth.log
|
||||
2014-08-08 14:59:36,510 fail2ban.server.filter[21667]: INFO Set maxRetry = 5
|
||||
2014-08-08 14:59:36,512 fail2ban.server.filter[21667]: INFO Set jail log file encoding to UTF-8
|
||||
2014-08-08 14:59:36,513 fail2ban.server.actions[21667]: INFO Set banTime = 600
|
||||
2014-08-08 14:59:36,514 fail2ban.server.filter[21667]: INFO Set findtime = 600
|
||||
2014-08-08 14:59:36,515 fail2ban.server.filter[21667]: INFO Set maxlines = 10
|
||||
2014-08-08 14:59:36,788 fail2ban.server.server[21667]: INFO Jail sshd is not a JournalFilter instance
|
||||
2014-08-08 14:59:36,798 fail2ban.server.jail[21667]: INFO Jail 'exim' started
|
||||
2014-08-08 14:59:36,802 fail2ban.server.jail[21667]: INFO Jail 'sshd' started
|
||||
2014-08-08 15:01:30,120 fail2ban.server.transmitter[21667]: WARNING Command ['status', 'ssh'] has failed. Received UnknownJailException('ssh',)
|
||||
2014-08-08 15:09:36,978 fail2ban.server.actions[21667]: NOTICE [sshd] Unban 116.10.191.199
|
||||
2014-08-08 15:09:37,187 fail2ban.server.action[21667]: ERROR rm -f /etc/symbiosis/firewall/blacklist.d/116.10.191.199.auto
|
||||
iptables -D INPUT -s 116.10.191.199 -j DROP -- stdout: ''
|
||||
2014-08-08 15:09:37,188 fail2ban.server.action[21667]: ERROR rm -f /etc/symbiosis/firewall/blacklist.d/116.10.191.199.auto
|
||||
iptables -D INPUT -s 116.10.191.199 -j DROP -- stderr: 'iptables: Bad rule (does a matching rule exist in that chain?).\n'
|
||||
2014-08-08 15:09:37,188 fail2ban.server.action[21667]: ERROR rm -f /etc/symbiosis/firewall/blacklist.d/116.10.191.199.auto
|
||||
iptables -D INPUT -s 116.10.191.199 -j DROP -- returned 1
|
||||
2014-08-08 15:09:37,188 fail2ban.server.actions[21667]: ERROR Failed to execute unban jail 'sshd' action 'symbiosis-blacklist': Error unbanning 116.10.191.199
|
||||
2014-08-10 02:27:27,235 fail2ban.server.server[21667]: INFO rollover performed on /var/log/fail2ban.log
|
||||
2014-08-10 02:27:28,109 fail2ban.server.filter[21667]: INFO Log rotation detected for /var/log/exim4/mainlog
|
||||
2014-08-10 02:28:01,747 fail2ban.server.filter[21667]: INFO Log rotation detected for /var/log/auth.log
|
||||
2014-08-10 02:33:29,500 fail2ban.server.filter[21667]: INFO [sshd] Found 86.101.234.57
|
||||
2014-08-10 02:46:06,846 fail2ban.server.filter[21667]: INFO [sshd] Found 220.130.163.247
|
||||
2014-08-10 03:10:43,794 fail2ban.server.filter[21667]: INFO [sshd] Found 220.130.163.247
|
||||
2014-08-10 06:49:27,446 fail2ban.server.actions[21667]: NOTICE [sshd] Ban 116.10.191.181
|
||||
2014-08-10 06:59:28,375 fail2ban.server.actions[21667]: NOTICE [sshd] Unban 116.10.191.181
|
||||
2014-08-10 20:06:41,576 fail2ban.server.actions[21667]: NOTICE [sshd] Unban 50.30.34.7
|
||||
2014-08-13 17:55:50,401 fail2ban.server.actions[17436]: NOTICE [sshd] 144.0.0.25 already banned
|
||||
2014-08-10 20:06:41,785 fail2ban.server.action[21667]: ERROR rm -f /etc/symbiosis/firewall/blacklist.d/50.30.34.7.auto
|
||||
iptables -D INPUT -s 50.30.34.7 -j DROP -- stdout: ''
|
||||
2014-08-10 20:06:41,785 fail2ban.server.action[21667]: ERROR rm -f /etc/symbiosis/firewall/blacklist.d/50.30.34.7.auto
|
||||
iptables -D INPUT -s 50.30.34.7 -j DROP -- stderr: 'iptables: Bad rule (does a matching rule exist in that chain?).\n'
|
||||
2014-08-10 20:06:41,786 fail2ban.server.action[21667]: ERROR rm -f /etc/symbiosis/firewall/blacklist.d/50.30.34.7.auto
|
||||
iptables -D INPUT -s 50.30.34.7 -j DROP -- returned 1
|
||||
2014-08-10 20:06:41,786 fail2ban.server.actions[21667]: ERROR Failed to execute unban jail 'sshd' action 'symbiosis-blacklist': Error unbanning 50.30.34.7
|
||||
2014-08-11 02:27:35,433 fail2ban.server.filter[21667]: INFO Log rotation detected for /var/log/exim4/mainlog
|
|
@ -71,6 +71,9 @@ tests if the server is alive
|
|||
.TP
|
||||
\fBhelp\fR
|
||||
return this output
|
||||
.TP
|
||||
\fBversion\fR
|
||||
return the server version
|
||||
.IP
|
||||
LOGGING
|
||||
.TP
|
||||
|
|
Loading…
Reference in New Issue