Merge tag '0.10.0a1' into 0.10-full

pull/1460/head
sebres 2016-07-15 10:32:42 +02:00
commit 922213f3d9
21 changed files with 569 additions and 432 deletions

View File

@ -88,10 +88,14 @@ ver. 0.9.5 (2016/XX/XXX) - wanna-be-released
added new parameter `__date_ambit`
* gentoo-initd fixed --pidfile bug: `--pidfile` is option of start-stop-daemon,
not argument of fail2ban (see gh-1434)
* filter.d/asterisk.conf
- fix security log support for PJSIP and Asterisk 13+ (gh-1456)
- improved log support for PJSIP and Asterisk 13+ with different callID (gh-1458)
- New Features:
* New Actions:
- action.d/firewallcmd-rich-rules and action.d/firewallcmd-rich-logging (gh-1367)
- Enhancements:
* Extreme speedup of all sqlite database operations (gh-1436),
by using of following sqlite options:
@ -100,7 +104,9 @@ ver. 0.9.5 (2016/XX/XXX) - wanna-be-released
- (temp_store = MEMORY) temporary tables and indices are kept in memory
* journald journalmatch for pure-ftpd (gh-1362)
* Add additional regex filter for dovecot ldap authentication failures (gh-1370)
* added additional regex filters for exim (gh-1371)
* filter.d/exim*conf
- added additional regexes (gh-1371)
- made port entry optional
ver. 0.9.4 (2016/03/08) - for-you-ladies

View File

@ -13,9 +13,12 @@ config/action.d/complain.conf
config/action.d/dshield.conf
config/action.d/dummy.conf
config/action.d/firewallcmd-allports.conf
config/action.d/firewallcmd-common.conf
config/action.d/firewallcmd-ipset.conf
config/action.d/firewallcmd-multiport.conf
config/action.d/firewallcmd-new.conf
config/action.d/firewallcmd-rich-logging.conf
config/action.d/firewallcmd-rich-rules.conf
config/action.d/hostsdeny.conf
config/action.d/ipfilter.conf
config/action.d/ipfw.conf
@ -31,6 +34,7 @@ config/action.d/iptables-new.conf
config/action.d/iptables-xt_recent-echo.conf
config/action.d/mail-buffered.conf
config/action.d/mail.conf
config/action.d/mail-whois-common.conf
config/action.d/mail-whois.conf
config/action.d/mail-whois-lines.conf
config/action.d/mynetwatchman.conf
@ -52,6 +56,7 @@ config/action.d/sendmail-whois-ipmatches.conf
config/action.d/sendmail-whois-lines.conf
config/action.d/sendmail-whois-matches.conf
config/action.d/shorewall.conf
config/action.d/shorewall-ipset-proto6.conf
config/action.d/smtp.py
config/action.d/symbiosis-blacklist-allports.conf
config/action.d/ufw.conf
@ -67,6 +72,7 @@ config/filter.d/apache-modsecurity.conf
config/filter.d/apache-nohome.conf
config/filter.d/apache-noscript.conf
config/filter.d/apache-overflows.conf
config/filter.d/apache-pass.conf
config/filter.d/apache-shellshock.conf
config/filter.d/assp.conf
config/filter.d/asterisk.conf
@ -79,17 +85,18 @@ config/filter.d/cyrus-imap.conf
config/filter.d/directadmin.conf
config/filter.d/dovecot.conf
config/filter.d/dropbear.conf
config/filter.d/drupal-auth.conf
config/filter.d/ejabberd-auth.conf
config/filter.d/exim-common.conf
config/filter.d/exim.conf
config/filter.d/exim-spam.conf
config/filter.d/freeswitch.conf
config/filter.d/froxlor-auth.conf
config/filter.d/groupoffice.conf
config/filter.d/gssftpd.conf
config/filter.d/guacamole.conf
config/filter.d/haproxy-http-auth.conf
config/filter.d/horde.conf
config/filter.d/ignorecommands
config/filter.d/ignorecommands/apache-fakegooglebot
config/filter.d/kerio.conf
config/filter.d/lighttpd-auth.conf
@ -122,7 +129,6 @@ config/filter.d/selinux-common.conf
config/filter.d/selinux-ssh.conf
config/filter.d/sendmail-auth.conf
config/filter.d/sendmail-reject.conf
config/filter.d/sendmail-spam.conf
config/filter.d/sieve.conf
config/filter.d/sogo-auth.conf
config/filter.d/solid-pop3d.conf
@ -148,7 +154,6 @@ config/paths-osx.conf
CONTRIBUTING.md
COPYING
DEVELOP
doc/run-rootless.txt
fail2ban-2to3
fail2ban/client/actionreader.py
fail2ban/client/beautifier.py
@ -185,7 +190,6 @@ fail2ban/server/filterpyinotify.py
fail2ban/server/filtersystemd.py
fail2ban/server/__init__.py
fail2ban/server/ipdns.py
fail2ban/server/iso8601.py
fail2ban/server/jail.py
fail2ban/server/jails.py
fail2ban/server/jailthread.py
@ -203,21 +207,19 @@ fail2ban/tests/action_d/test_smtp.py
fail2ban/tests/actionstestcase.py
fail2ban/tests/actiontestcase.py
fail2ban/tests/banmanagertestcase.py
fail2ban/tests/clientreadertestcase.py
fail2ban/tests/clientbeautifiertestcase.py
fail2ban/tests/clientreadertestcase.py
fail2ban/tests/config/action.d/brokenaction.conf
fail2ban/tests/config/fail2ban.conf
fail2ban/tests/config/filter.d/simple.conf
fail2ban/tests/config/filter.d/test.conf
fail2ban/tests/config/filter.d/test.local
fail2ban/tests/config/filter.d/zzz-generic-example.conf
fail2ban/tests/config/jail.conf
fail2ban/tests/config/paths-common.conf
fail2ban/tests/config/paths-debian.conf
fail2ban/tests/config/paths-freebsd.conf
fail2ban/tests/config/paths-osx.conf
fail2ban/tests/databasetestcase.py
fail2ban/tests/datedetectortestcase.py
fail2ban/tests/dummyjail.py
fail2ban/tests/fail2banclienttestcase.py
fail2ban/tests/fail2banregextestcase.py
fail2ban/tests/failmanagertestcase.py
fail2ban/tests/files/action.d/action_checkainfo.py
@ -250,13 +252,13 @@ fail2ban/tests/files/ignorecommand.py
fail2ban/tests/files/logs/3proxy
fail2ban/tests/files/logs/apache-auth
fail2ban/tests/files/logs/apache-badbots
fail2ban/tests/files/logs/apache-botscripts
fail2ban/tests/files/logs/apache-botsearch
fail2ban/tests/files/logs/apache-fakegooglebot
fail2ban/tests/files/logs/apache-modsecurity
fail2ban/tests/files/logs/apache-nohome
fail2ban/tests/files/logs/apache-noscript
fail2ban/tests/files/logs/apache-overflows
fail2ban/tests/files/logs/apache-pass
fail2ban/tests/files/logs/apache-shellshock
fail2ban/tests/files/logs/assp
fail2ban/tests/files/logs/asterisk
@ -270,10 +272,12 @@ fail2ban/tests/files/logs/cyrus-imap
fail2ban/tests/files/logs/directadmin
fail2ban/tests/files/logs/dovecot
fail2ban/tests/files/logs/dropbear
fail2ban/tests/files/logs/drupal-auth
fail2ban/tests/files/logs/ejabberd-auth
fail2ban/tests/files/logs/exim
fail2ban/tests/files/logs/exim-spam
fail2ban/tests/files/logs/freeswitch
fail2ban/tests/files/logs/froxlor-auth
fail2ban/tests/files/logs/groupoffice
fail2ban/tests/files/logs/gssftpd
fail2ban/tests/files/logs/guacamole
@ -309,7 +313,6 @@ fail2ban/tests/files/logs/screensharingd
fail2ban/tests/files/logs/selinux-ssh
fail2ban/tests/files/logs/sendmail-auth
fail2ban/tests/files/logs/sendmail-reject
fail2ban/tests/files/logs/sendmail-spam
fail2ban/tests/files/logs/sieve
fail2ban/tests/files/logs/sogo-auth
fail2ban/tests/files/logs/solid-pop3d
@ -325,6 +328,7 @@ fail2ban/tests/files/logs/vsftpd
fail2ban/tests/files/logs/webmin-auth
fail2ban/tests/files/logs/wuftpd
fail2ban/tests/files/logs/xinetd-fail
fail2ban/tests/files/logs/zzz-generic-example
fail2ban/tests/files/testcase01.log
fail2ban/tests/files/testcase02.log
fail2ban/tests/files/testcase03.log
@ -356,6 +360,8 @@ files/gentoo-confd
files/gentoo-initd
files/ipmasq-ZZZzzz_fail2ban.rul
files/logwatch/fail2ban
files/logwatch/fail2ban-0.8.log
files/logwatch/fail2ban-0.9.log
files/macosx-initd
files/monit/fail2ban
files/nagios/check_fail2ban
@ -373,6 +379,8 @@ man/fail2ban-regex.1
man/fail2ban-regex.h2m
man/fail2ban-server.1
man/fail2ban-server.h2m
man/fail2ban-testcases.1
man/fail2ban-testcases.h2m
man/generate-man
man/jail.conf.5
README.md

View File

@ -190,7 +190,7 @@ Post Release
Add the following to the top of the ChangeLog::
ver. 0.9.6 (2016/XX/XXX) - wanna-be-released
ver. 0.10.0 (2016/XX/XXX) - wanna-be-released
-----------
- Fixes:

View File

@ -122,7 +122,7 @@ if verbosity > 1: # pragma: no cover
if verbosity > 3:
fmt = ' | %(module)15.15s-%(levelno)-2d: %(funcName)-20.20s |' + fmt
if verbosity > 2:
fmt = ' +%(relativeCreated)5d %(thread)X %(levelname)-5.5s' + fmt
fmt = ' +%(relativeCreated)5d %(thread)X %(name)-25.25s %(levelname)-5.5s' + fmt
else:
fmt = ' %(asctime)-15s %(thread)X %(levelname)-5.5s' + fmt
#

View File

@ -27,6 +27,7 @@ failregex = ^%(__prefix_line)s%(log_prefix)s Registration from '[^']*' failed fo
^%(__prefix_line)s%(log_prefix)s hacking attempt detected '<HOST>'$
^%(__prefix_line)s%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="([\d-]+|%(iso8601)s)",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="(\d*|<unknown>)",SessionID=".+",LocalAddress="IPV[46]/(UDP|TCP|WS)/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UDP|TCP|WS)/<HOST>/\d+"(,Challenge="[\w/]+")?(,ReceivedChallenge="\w+")?(,Response="\w+",ExpectedResponse="\w*")?(,ReceivedHash="[\da-f]+")?(,ACLName="\w+")?$
^%(__prefix_line)s%(log_prefix)s "Rejecting unknown SIP connection from <HOST>"$
^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '<HOST>(?::\d+)?'\s\(callid: [^\)]*\) - (?:No matching endpoint found|Not match Endpoint(?: Contact)? ACL|(?:Failed|Error) to authenticate)\s*$
ignoreregex =

View File

@ -9,8 +9,8 @@ after = exim-common.local
[Definition]
host_info = H=([\w.-]+ )?(\(\S+\) )?\[<HOST>\](:\d+)? (I=\[\S+\]:\d+ )?(U=\S+ )?(P=e?smtp )?
pid = ( \[\d+\])?
host_info = (?:H=([\w.-]+ )?(?:\(\S+\) )?)?\[<HOST>\](?::\d+)? (?:I=\[\S+\](:\d+)? )?(?:U=\S+ )?(?:P=e?smtp )?
pid = (?: \[\d+\])?
# DEV Notes:
# From exim source code: ./src/receive.c:add_host_info_for_log

View File

@ -14,13 +14,13 @@ before = exim-common.conf
[Definition]
failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user|Unrouteable address|all relevant MX records point to non-existent hosts)\s*$
^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[<HOST>\](:\d+)?( I=\[\S+\](:\d+)?)?: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$
^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (relay not permitted|Sender verify failed|Unknown user)\s*$
^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (connection from|"\S+") %(host_info)s(next )?input=".*"\s*$
^%(pid)s SMTP call from \S+ \[<HOST>\](:\d+)? (I=\[\S+\](:\d+)? )?dropped: too many nonmail commands \(last was "\S+"\)\s*$
^%(pid)s SMTP protocol error in "AUTH \S*(| \S*)" H=(|\S* )(|\(\S*\) )\[<HOST>\]\:\d+ I=\[\S*\]\:\d+ AUTH command used when not advertised\s*$
^%(pid)s no MAIL in SMTP connection from (|\S* )(|\(\S*\) )\[<HOST>\]\:\d+ I=\[\S*\]\:\d+ D=\d+s(| C=\S*)\s*$
^%(pid)s \S+ SMTP connection from (|\S* )(|\(\S*\) )\[<HOST>\]\:\d+ I=\[\S*\]\:\d+ closed by DROP in ACL\s*$
^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[<HOST>\](?::\d+)?(?: I=\[\S+\](:\d+)?)?: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$
^%(pid)s %(host_info)sF=(?:<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (?:relay not permitted|Sender verify failed|Unknown user)\s*$
^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (?:connection from|"\S+") %(host_info)s(?:next )?input=".*"\s*$
^%(pid)s SMTP call from \S+ %(host_info)sdropped: too many nonmail commands \(last was "\S+"\)\s*$
^%(pid)s SMTP protocol error in "AUTH \S*(?: \S*)?" %(host_info)sAUTH command used when not advertised\s*$
^%(pid)s no MAIL in SMTP connection from (?:\S* )?(?:\(\S*\) )?%(host_info)sD=\d+s(?: C=\S*)?\s*$
^%(pid)s \S+ SMTP connection from (?:\S* )?(?:\(\S*\) )?%(host_info)sclosed by DROP in ACL\s*$
ignoreregex =

View File

@ -35,7 +35,7 @@ from ..version import version
from .csocket import CSocket
from .beautifier import Beautifier
from .fail2bancmdline import Fail2banCmdLine, ServerExecutionException, ExitException, \
logSys, PRODUCTION, exit, output
logSys, exit, output
PROMPT = "fail2ban> "
@ -227,11 +227,11 @@ class Fail2banClient(Fail2banCmdLine, Thread):
# prepare: read config, check configuration is valid, etc.:
if phase is not None:
phase['start'] = True
logSys.debug('-- client phase %s', phase)
logSys.debug(' client phase %s', phase)
stream = self.__prepareStartServer()
if phase is not None:
phase['ready'] = phase['start'] = (True if stream else False)
logSys.debug('-- client phase %s', phase)
logSys.debug(' client phase %s', phase)
if not stream:
return False
# configure server with config stream:
@ -361,8 +361,6 @@ class Fail2banClient(Fail2banCmdLine, Thread):
# Interactive mode
if self._conf.get("interactive", False):
# no readline in test:
if PRODUCTION: # pragma: no cover
try:
import readline
except ImportError:
@ -372,7 +370,6 @@ class Fail2banClient(Fail2banCmdLine, Thread):
if len(args) > 0:
ret = self.__processCommand(args)
if ret:
if PRODUCTION: # pragma: no cover
readline.parse_and_bind("tab: complete")
self.dispInteractive()
while True:
@ -411,11 +408,9 @@ class Fail2banClient(Fail2banCmdLine, Thread):
signal.signal(s, sh)
##
# Wonderful visual :)
#
class _VisualWait:
"""Small progress indication (as "wonderful visual") during waiting process
"""
pos = 0
delta = 1
def __init__(self, maxpos=10):
@ -427,6 +422,8 @@ class _VisualWait:
sys.stdout.write('\r'+(' '*(35+self.maxpos))+'\r')
sys.stdout.flush()
def heartbeat(self):
"""Show or step for progress indicator
"""
if not self.pos:
sys.stdout.write("\nINFO [#" + (' '*self.maxpos) + "] Waiting on the server...\r\x1b[8C")
self.pos += self.delta
@ -441,6 +438,8 @@ class _VisualWait:
elif self.pos < 2:
self.delta = 1
class _NotVisualWait:
"""Mockup for invisible progress indication (not verbose)
"""
def __enter__(self):
return self
def __exit__(self, *args):
@ -449,6 +448,8 @@ class _NotVisualWait:
pass
def VisualWait(verbose, *args, **kwargs):
"""Wonderful visual progress indication (if verbose)
"""
return _VisualWait(*args, **kwargs) if verbose > 1 else _NotVisualWait()

View File

@ -42,6 +42,7 @@ PRODUCTION = True
MAX_WAITTIME = 30
class Fail2banCmdLine():
def __init__(self):
@ -83,9 +84,6 @@ class Fail2banCmdLine():
output("Copyright (c) 2004-2008 Cyril Jaquier, 2008- Fail2Ban Contributors")
output("Copyright of modifications held by their respective authors.")
output("Licensed under the GNU General Public License v2 (GPL).")
output("")
output("Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>.")
output("Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>.")
def dispUsage(self):
""" Prints Fail2Ban command line options and exits
@ -187,7 +185,7 @@ class Fail2banCmdLine():
if ret is not None:
return ret
logSys.debug("-- conf: %r, args: %r", self._conf, self._args)
logSys.debug(" conf: %r, args: %r", self._conf, self._args)
if initial and PRODUCTION: # pragma: no cover - can't test
verbose = self._conf["verbose"]
@ -259,14 +257,26 @@ class Fail2banCmdLine():
output(c)
return True
#
# _exit is made to ease mocking out of the behaviour in tests,
# since method is also exposed in API via globally bound variable
@staticmethod
def exit(code=0): # pragma: no cover - can't test
logSys.debug("Exit with code %s", code)
if os._exit:
def _exit(code=0):
if hasattr(os, '_exit') and os._exit:
os._exit(code)
else:
sys.exit(code)
@staticmethod
def exit(code=0):
logSys.debug("Exit with code %s", code)
# because of possible buffered output in python, we should flush it before exit:
sys.stdout.flush()
sys.stderr.flush()
# exit
Fail2banCmdLine._exit(code)
# global exit handler:
exit = Fail2banCmdLine.exit

View File

@ -46,7 +46,7 @@ class Fail2banServer(Fail2banCmdLine):
@staticmethod
def startServerDirect(conf, daemon=True):
logSys.debug("-- direct starting of server in %s, deamon: %s", os.getpid(), daemon)
logSys.debug(" direct starting of server in %s, deamon: %s", os.getpid(), daemon)
from ..server.server import Server
server = None
try:
@ -79,7 +79,7 @@ class Fail2banServer(Fail2banCmdLine):
frk = not conf["async"] and PRODUCTION
if frk: # pragma: no cover
pid = os.fork()
logSys.debug("-- async starting of server in %s, fork: %s - %s", os.getpid(), frk, pid)
logSys.debug(" async starting of server in %s, fork: %s - %s", os.getpid(), frk, pid)
if pid == 0:
args = list()
args.append(SERVER)

View File

@ -27,6 +27,9 @@ __license__ = "GPL"
import textwrap
def output(s):
"""Default output handler for printing protocol.
Used to ease mocking in the test cases.
"""
print(s)
##

View File

@ -88,6 +88,11 @@ class Server:
logSys.debug("Caught signal %d. Flushing logs" % signum)
self.flushLogs()
def _rebindSignal(self, s, new):
"""Bind new signal handler while storing old one in _prev_signals"""
self.__prev_signals[s] = signal.getsignal(s)
signal.signal(s, new)
def start(self, sock, pidfile, force=False, observer=True, conf={}):
# First set the mask to only allow access to owner
os.umask(0077)
@ -121,9 +126,10 @@ class Server:
# Install signal handlers
if _thread_name() == '_MainThread':
for s in (signal.SIGTERM, signal.SIGINT, signal.SIGUSR1):
self.__prev_signals[s] = signal.getsignal(s)
signal.signal(s, self.__sigTERMhandler if s != signal.SIGUSR1 else self.__sigUSR1handler)
for s in (signal.SIGTERM, signal.SIGINT):
self._rebindSignal(s, self.__sigTERMhandler)
self._rebindSignal(signal.SIGUSR1, self.__sigUSR1handler)
# Ensure unhandled exceptions are logged
sys.excepthook = excepthook
@ -389,7 +395,7 @@ class Server:
return self.__jails[name].getBanTimeExtra(opt)
def isStarted(self):
self.__asyncServer.isActive()
return self.__asyncServer is not None and self.__asyncServer.isActive()
def isAlive(self, jailnum=None):
if jailnum is not None and len(self.__jails) != jailnum:
@ -437,7 +443,7 @@ class Server:
getLogger("fail2ban").setLevel(getattr(logging, value))
self.__logLevel = value
except AttributeError:
raise ValueError("Invalid log level")
raise ValueError("Invalid log level %r" % value)
##
# Get the logging level.
@ -519,7 +525,7 @@ class Server:
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
# Does not display this message at startup.
if not self.__logTarget is None:
if self.__logTarget is not None:
logSys.info("Start Fail2ban v%s", version.version)
logSys.info(
"Changed logging target to %s for Fail2ban v%s"
@ -608,9 +614,7 @@ class Server:
# We need to set this in the parent process, so it gets inherited by the
# child process, and this makes sure that it is effect even if the parent
# terminates quickly.
for s in (signal.SIGHUP,):
self.__prev_signals[s] = signal.getsignal(s)
signal.signal(s, signal.SIG_IGN)
self._rebindSignal(signal.SIGHUP, signal.SIG_IGN)
try:
# Fork a child process so the parent can exit. This will return control

View File

@ -31,25 +31,30 @@ import time
import signal
import unittest
from os.path import join as pjoin, isdir, isfile, exists, dirname
from functools import wraps
from threading import Thread
from ..client import fail2banclient, fail2banserver, fail2bancmdline
from ..client.fail2banclient import Fail2banClient, exec_command_line as _exec_client, VisualWait
from ..client.fail2bancmdline import Fail2banCmdLine
from ..client.fail2banclient import exec_command_line as _exec_client, VisualWait
from ..client.fail2banserver import Fail2banServer, exec_command_line as _exec_server
from .. import protocol
from ..server import server
from ..server.utils import Utils
from .utils import LogCaptureTestCase, logSys, with_tmpdir, shutil, logging
from .utils import LogCaptureTestCase, with_tmpdir, shutil, logging
from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger(__name__)
STOCK_CONF_DIR = "config"
STOCK = os.path.exists(os.path.join(STOCK_CONF_DIR,'fail2ban.conf'))
STOCK = exists(pjoin(STOCK_CONF_DIR, 'fail2ban.conf'))
CLIENT = "fail2ban-client"
SERVER = "fail2ban-server"
BIN = os.path.dirname(Fail2banServer.getServerPath())
BIN = dirname(Fail2banServer.getServerPath())
MAX_WAITTIME = 30 if not unittest.F2B.fast else 5
@ -72,63 +77,68 @@ fail2banclient.output = \
fail2banserver.output = \
protocol.output = _test_output
def _test_exit(code=0):
logSys.debug("Exit with code %s", code)
if code == 0:
raise ExitException()
else:
raise FailExitException()
fail2bancmdline.exit = \
fail2banclient.exit = \
fail2banserver.exit = _test_exit
#
# Mocking .exit so we could test its correct operation.
# Two custom exceptions will be assessed to be raised in the tests
#
class ExitException(fail2bancmdline.ExitException):
"""Exception upon a normal exit"""
pass
class FailExitException(fail2bancmdline.ExitException):
"""Exception upon abnormal exit"""
pass
INTERACT = []
def _test_input_command(*args):
if len(INTERACT):
#logSys.debug('--- interact command: %r', INTERACT[0])
return INTERACT.pop(0)
else:
return "exit"
fail2banclient.input_command = _test_input_command
# prevents change logging params, log capturing, etc:
fail2bancmdline.PRODUCTION = \
fail2banclient.PRODUCTION = \
fail2banserver.PRODUCTION = False
class ExitException(fail2bancmdline.ExitException):
pass
class FailExitException(fail2bancmdline.ExitException):
pass
def _out_file(fn): # pragma: no cover
def _out_file(fn):
"""Helper which outputs content of the file at HEAVYDEBUG loglevels"""
logSys.debug('---- ' + fn + ' ----')
for line in fileinput.input(fn):
line = line.rstrip('\n')
logSys.debug(line)
logSys.debug('-'*30)
def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
cfg = tmp+"/config"
cfg = pjoin(tmp, "config")
if use_stock and STOCK:
# copy config (sub-directories as alias):
def ig_dirs(dir, files):
return [f for f in files if os.path.isdir(os.path.join(dir, f))]
"""Filters list of 'files' to contain only directories (under dir)"""
return [f for f in files if isdir(pjoin(dir, f))]
shutil.copytree(STOCK_CONF_DIR, cfg, ignore=ig_dirs)
os.symlink(STOCK_CONF_DIR+"/action.d", cfg+"/action.d")
os.symlink(STOCK_CONF_DIR+"/filter.d", cfg+"/filter.d")
os.symlink(pjoin(STOCK_CONF_DIR, "action.d"), pjoin(cfg, "action.d"))
os.symlink(pjoin(STOCK_CONF_DIR, "filter.d"), pjoin(cfg, "filter.d"))
# replace fail2ban params (database with memory):
r = re.compile(r'^dbfile\s*=')
for line in fileinput.input(cfg+"/fail2ban.conf", inplace=True):
for line in fileinput.input(pjoin(cfg, "fail2ban.conf"), inplace=True):
line = line.rstrip('\n')
if r.match(line):
line = "dbfile = :memory:"
print(line)
# replace jail params (polling as backend to be fast in initialize):
r = re.compile(r'^backend\s*=')
for line in fileinput.input(cfg+"/jail.conf", inplace=True):
for line in fileinput.input(pjoin(cfg, "jail.conf"), inplace=True):
line = line.rstrip('\n')
if r.match(line):
line = "backend = polling"
@ -136,21 +146,21 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
else:
# just empty config directory without anything (only fail2ban.conf/jail.conf):
os.mkdir(cfg)
f = open(cfg+"/fail2ban.conf", "w")
f = open(pjoin(cfg, "fail2ban.conf"), "w")
f.write('\n'.join((
"[Definition]",
"loglevel = INFO",
"logtarget = " + logtarget,
"syslogsocket = auto",
"socket = "+tmp+"/f2b.sock",
"pidfile = "+tmp+"/f2b.pid",
"socket = " + pjoin(tmp, "f2b.sock"),
"pidfile = " + pjoin(tmp, "f2b.pid"),
"backend = polling",
"dbfile = :memory:",
"dbpurgeage = 1d",
"",
)))
f.close()
f = open(cfg+"/jail.conf", "w")
f = open(pjoin(cfg, "jail.conf"), "w")
f.write('\n'.join((
"[INCLUDES]", "",
"[DEFAULT]", "",
@ -158,54 +168,60 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
)))
f.close()
if logSys.level < logging.DEBUG: # if HEAVYDEBUG
_out_file(cfg+"/fail2ban.conf")
_out_file(cfg+"/jail.conf")
_out_file(pjoin(cfg, "fail2ban.conf"))
_out_file(pjoin(cfg, "jail.conf"))
# parameters (sock/pid and config, increase verbosity, set log, etc.):
return ("-c", cfg, "-s", tmp+"/f2b.sock", "-p", tmp+"/f2b.pid",
return (
"-c", cfg, "-s", pjoin(tmp, "f2b.sock"), "-p", pjoin(tmp, "f2b.pid"),
"-vv", "--logtarget", logtarget, "--loglevel", "DEBUG", "--syslogsocket", "auto",
"--timeout", str(fail2bancmdline.MAX_WAITTIME),
)
def _kill_srv(pidfile): # pragma: no cover
def _pid_exists(pid):
try:
os.kill(pid, 0)
return True
except OSError:
return False
logSys.debug("-- cleanup: %r", (pidfile, os.path.isdir(pidfile)))
if os.path.isdir(pidfile):
def _kill_srv(pidfile):
logSys.debug("cleanup: %r", (pidfile, isdir(pidfile)))
if isdir(pidfile):
piddir = pidfile
pidfile = piddir + "/f2b.pid"
if not os.path.isfile(pidfile):
pidfile = piddir + "/fail2ban.pid"
if not os.path.isfile(pidfile):
logSys.debug("--- cleanup: no pidfile for %r", piddir)
pidfile = pjoin(piddir, "f2b.pid")
if not isfile(pidfile): # pragma: no cover
pidfile = pjoin(piddir, "fail2ban.pid")
if not isfile(pidfile):
logSys.debug("cleanup: no pidfile for %r", piddir)
return True
f = pid = None
try:
logSys.debug("--- cleanup pidfile: %r", pidfile)
logSys.debug("cleanup pidfile: %r", pidfile)
f = open(pidfile)
pid = f.read().split()[1]
pid = f.read()
pid = re.match(r'\S+', pid).group()
pid = int(pid)
logSys.debug("--- cleanup pid: %r", pid)
if pid <= 0:
raise ValueError('pid %s of %s is invalid' % (pid, pidfile))
if not _pid_exists(pid):
return True
## try to preper stop (have signal handler):
os.kill(pid, signal.SIGTERM)
## check still exists after small timeout:
if not Utils.wait_for(lambda: not _pid_exists(pid), 1):
## try to kill hereafter:
os.kill(pid, signal.SIGKILL)
return not _pid_exists(pid)
except Exception as e:
except Exception as e: # pragma: no cover
logSys.debug(e)
return False
finally:
if f is not None:
f.close()
try:
logSys.debug("cleanup pid: %r", pid)
if pid <= 0 or pid == os.getpid(): # pragma: no cover
raise ValueError('pid %s of %s is invalid' % (pid, pidfile))
if not Utils.pid_exists(pid):
return True
## try to properly stop (have signal handler):
os.kill(pid, signal.SIGTERM)
## check still exists after small timeout:
if not Utils.wait_for(lambda: not Utils.pid_exists(pid), 1):
## try to kill hereafter:
os.kill(pid, signal.SIGKILL)
logSys.debug("cleanup: kill ready")
return not Utils.pid_exists(pid)
except Exception as e: # pragma: no cover
logSys.exception(e)
return True
def with_kill_srv(f):
"""Helper to decorate tests which receive in the last argument tmpdir to pass to kill_srv
@ -224,187 +240,83 @@ def with_kill_srv(f):
class Fail2banClientServerBase(LogCaptureTestCase):
_orig_exit = Fail2banCmdLine._exit
def setUp(self):
"""Call before every test case."""
LogCaptureTestCase.setUp(self)
Fail2banCmdLine._exit = staticmethod(self._test_exit)
def tearDown(self):
"""Call after every test case."""
Fail2banCmdLine._exit = self._orig_exit
LogCaptureTestCase.tearDown(self)
@staticmethod
def _test_exit(code=0):
if code == 0:
raise ExitException()
else:
raise FailExitException()
def _wait_for_srv(self, tmp, ready=True, startparams=None):
try:
sock = tmp+"/f2b.sock"
sock = pjoin(tmp, "f2b.sock")
# wait for server (socket):
ret = Utils.wait_for(lambda: os.path.exists(sock), MAX_WAITTIME)
ret = Utils.wait_for(lambda: exists(sock), MAX_WAITTIME)
if not ret:
raise Exception('Unexpected: Socket file does not exists.\nStart failed: %r' % (startparams,))
raise Exception(
'Unexpected: Socket file does not exists.\nStart failed: %r'
% (startparams,)
)
if ready:
# wait for communication with worker ready:
ret = Utils.wait_for(lambda: "Server ready" in self.getLog(), MAX_WAITTIME)
if not ret:
raise Exception('Unexpected: Server ready was not found.\nStart failed: %r' % (startparams,))
raise Exception(
'Unexpected: Server ready was not found.\nStart failed: %r'
% (startparams,)
)
except: # pragma: no cover
log = tmp+"/f2b.log"
if os.path.isfile(log):
log = pjoin(tmp, "f2b.log")
if isfile(log):
_out_file(log)
else:
logSys.debug("No log file %s to examine details of error", log)
raise
def execSuccess(self, startparams, *args):
raise NotImplementedError("To be defined in subclass")
class Fail2banClientTest(Fail2banClientServerBase):
def execFailed(self, startparams, *args):
raise NotImplementedError("To be defined in subclass")
def testConsistency(self):
self.assertTrue(os.path.isfile(os.path.join(os.path.join(BIN), CLIENT)))
self.assertTrue(os.path.isfile(os.path.join(os.path.join(BIN), SERVER)))
def testClientUsage(self):
self.assertRaises(ExitException, _exec_client,
(CLIENT, "-h",))
self.assertLogged("Usage: " + CLIENT)
self.assertLogged("Report bugs to ")
self.pruneLog()
self.assertRaises(ExitException, _exec_client,
(CLIENT, "-vq", "-V",))
self.assertLogged("Fail2Ban v" + fail2bancmdline.version)
@with_tmpdir
def testClientDump(self, tmp):
# use here the stock configuration (if possible)
startparams = _start_params(tmp, True)
self.assertRaises(ExitException, _exec_client,
((CLIENT,) + startparams + ("-vvd",)))
self.assertLogged("Loading files")
self.assertLogged("logtarget")
@with_tmpdir
@with_kill_srv
def testClientStartBackgroundInside(self, tmp):
# use once the stock configuration (to test starting also)
startparams = _start_params(tmp, True)
# start:
self.assertRaises(ExitException, _exec_client,
(CLIENT, "-b") + startparams + ("start",))
# wait for server (socket and ready):
self._wait_for_srv(tmp, True, startparams=startparams)
self.assertLogged("Server ready")
self.assertLogged("Exit with code 0")
try:
self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("echo", "TEST-ECHO",))
self.assertRaises(FailExitException, _exec_client,
(CLIENT,) + startparams + ("~~unknown~cmd~failed~~",))
self.pruneLog()
# start again (should fail):
self.assertRaises(FailExitException, _exec_client,
(CLIENT, "-b") + startparams + ("start",))
self.assertLogged("Server already running")
finally:
self.pruneLog()
# stop:
self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("stop",))
self.assertLogged("Shutdown successful")
self.assertLogged("Exit with code 0")
self.pruneLog()
# stop again (should fail):
self.assertRaises(FailExitException, _exec_client,
(CLIENT,) + startparams + ("stop",))
self.assertLogged("Failed to access socket path")
self.assertLogged("Is fail2ban running?")
@with_tmpdir
@with_kill_srv
def testClientStartBackgroundCall(self, tmp):
global INTERACT
startparams = _start_params(tmp, logtarget=tmp+"/f2b.log")
# start (in new process, using the same python version):
cmd = (sys.executable, os.path.join(os.path.join(BIN), CLIENT))
logSys.debug('Start %s ...', cmd)
cmd = cmd + startparams + ("--async", "start",)
ret = Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True)
self.assertTrue(len(ret) and ret[0])
# wait for server (socket and ready):
self._wait_for_srv(tmp, True, startparams=cmd)
self.assertLogged("Server ready")
self.pruneLog()
try:
# echo from client (inside):
self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("echo", "TEST-ECHO",))
self.assertLogged("TEST-ECHO")
self.assertLogged("Exit with code 0")
self.pruneLog()
# interactive client chat with started server:
INTERACT += [
"echo INTERACT-ECHO",
"status",
"exit"
]
self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("-i",))
self.assertLogged("INTERACT-ECHO")
self.assertLogged("Status", "Number of jail:")
self.assertLogged("Exit with code 0")
self.pruneLog()
# test reload and restart over interactive client:
INTERACT += [
"reload",
"restart",
"exit"
]
self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("-i",))
self.assertLogged("Reading config files:")
self.assertLogged("Shutdown successful")
self.assertLogged("Server ready")
self.assertLogged("Exit with code 0")
self.pruneLog()
# test reload missing jail (interactive):
INTERACT += [
"reload ~~unknown~jail~fail~~",
"exit"
]
self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("-i",))
self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'")
self.pruneLog()
# test reload missing jail (direct):
self.assertRaises(FailExitException, _exec_client,
(CLIENT,) + startparams + ("reload", "~~unknown~jail~fail~~"))
self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'")
self.assertLogged("Exit with code -1")
self.pruneLog()
finally:
self.pruneLog()
# stop:
self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("stop",))
self.assertLogged("Shutdown successful")
self.assertLogged("Exit with code 0")
def _testClientStartForeground(self, tmp, startparams, phase):
#
# Common tests
#
def _testStartForeground(self, tmp, startparams, phase):
# start and wait to end (foreground):
logSys.debug("-- start of test worker")
logSys.debug("start of test worker")
phase['start'] = True
self.assertRaises(fail2bancmdline.ExitException, _exec_client,
(CLIENT, "-f") + startparams + ("start",))
self.execSuccess(("-f",) + startparams, "start")
# end :
phase['end'] = True
logSys.debug("-- end of test worker")
logSys.debug("end of test worker")
@with_tmpdir
def testClientStartForeground(self, tmp):
def testStartForeground(self, tmp):
# intended to be ran only in subclasses
th = None
phase = dict()
try:
# started directly here, so prevent overwrite test cases logger with "INHERITED"
startparams = _start_params(tmp, logtarget="INHERITED")
# because foreground block execution - start it in thread:
phase = dict()
th = Thread(name="_TestCaseWorker",
target=Fail2banClientTest._testClientStartForeground, args=(self, tmp, startparams, phase))
th = Thread(
name="_TestCaseWorker",
target=self._testStartForeground,
args=(tmp, startparams, phase)
)
th.daemon = True
th.start()
try:
@ -415,26 +327,150 @@ class Fail2banClientTest(Fail2banClientServerBase):
self._wait_for_srv(tmp, True, startparams=startparams)
self.pruneLog()
# several commands to server:
self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("ping",))
self.assertRaises(FailExitException, _exec_client,
(CLIENT,) + startparams + ("~~unknown~cmd~failed~~",))
self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("echo", "TEST-ECHO",))
self.execSuccess(startparams, "ping")
self.execFailed(startparams, "~~unknown~cmd~failed~~")
self.execSuccess(startparams, "echo", "TEST-ECHO")
finally:
self.pruneLog()
# stop:
self.assertRaises(ExitException, _exec_client,
(CLIENT,) + startparams + ("stop",))
self.execSuccess(startparams, "stop")
# wait for end:
Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME)
self.assertTrue(phase.get('end', None))
self.assertLogged("Shutdown successful", "Exiting Fail2ban")
finally:
_kill_srv(tmp)
if th:
# we start client/server directly in current process (new thread),
# so don't kill (same process) - if success, just wait for end of worker:
if phase.get('end', None):
th.join()
class Fail2banClientTest(Fail2banClientServerBase):
def execSuccess(self, startparams, *args):
self.assertRaises(ExitException, _exec_client,
((CLIENT,) + startparams + args))
def execFailed(self, startparams, *args):
self.assertRaises(FailExitException, _exec_client,
((CLIENT,) + startparams + args))
def testConsistency(self):
self.assertTrue(isfile(pjoin(BIN, CLIENT)))
self.assertTrue(isfile(pjoin(BIN, SERVER)))
def testClientUsage(self):
self.execSuccess((), "-h")
self.assertLogged("Usage: " + CLIENT)
self.assertLogged("Report bugs to ")
self.pruneLog()
self.execSuccess((), "-vq", "-V")
self.assertLogged("Fail2Ban v" + fail2bancmdline.version)
@with_tmpdir
def testClientDump(self, tmp):
# use here the stock configuration (if possible)
startparams = _start_params(tmp, True)
self.execSuccess(startparams, "-vvd")
self.assertLogged("Loading files")
self.assertLogged("logtarget")
@with_tmpdir
@with_kill_srv
def testClientStartBackgroundInside(self, tmp):
# use once the stock configuration (to test starting also)
startparams = _start_params(tmp, True)
# start:
self.execSuccess(("-b",) + startparams, "start")
# wait for server (socket and ready):
self._wait_for_srv(tmp, True, startparams=startparams)
self.assertLogged("Server ready")
self.assertLogged("Exit with code 0")
try:
self.execSuccess(startparams, "echo", "TEST-ECHO")
self.execFailed(startparams, "~~unknown~cmd~failed~~")
self.pruneLog()
# start again (should fail):
self.execFailed(("-b",) + startparams, "start")
self.assertLogged("Server already running")
finally:
self.pruneLog()
# stop:
self.execSuccess(startparams, "stop")
self.assertLogged("Shutdown successful")
self.assertLogged("Exit with code 0")
self.pruneLog()
# stop again (should fail):
self.execFailed(startparams, "stop")
self.assertLogged("Failed to access socket path")
self.assertLogged("Is fail2ban running?")
@with_tmpdir
@with_kill_srv
def testClientStartBackgroundCall(self, tmp):
global INTERACT
startparams = _start_params(tmp, logtarget=pjoin(tmp, "f2b.log"))
# start (in new process, using the same python version):
cmd = (sys.executable, pjoin(BIN, CLIENT))
logSys.debug('Start %s ...', cmd)
cmd = cmd + startparams + ("--async", "start",)
ret = Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True)
self.assertTrue(len(ret) and ret[0])
# wait for server (socket and ready):
self._wait_for_srv(tmp, True, startparams=cmd)
self.assertLogged("Server ready")
self.pruneLog()
try:
# echo from client (inside):
self.execSuccess(startparams, "echo", "TEST-ECHO")
self.assertLogged("TEST-ECHO")
self.assertLogged("Exit with code 0")
self.pruneLog()
# interactive client chat with started server:
INTERACT += [
"echo INTERACT-ECHO",
"status",
"exit"
]
self.execSuccess(startparams, "-i")
self.assertLogged("INTERACT-ECHO")
self.assertLogged("Status", "Number of jail:")
self.assertLogged("Exit with code 0")
self.pruneLog()
# test reload and restart over interactive client:
INTERACT += [
"reload",
"restart",
"exit"
]
self.execSuccess(startparams, "-i")
self.assertLogged("Reading config files:")
self.assertLogged("Shutdown successful")
self.assertLogged("Server ready")
self.assertLogged("Exit with code 0")
self.pruneLog()
# test reload missing jail (interactive):
INTERACT += [
"reload ~~unknown~jail~fail~~",
"exit"
]
self.execSuccess(startparams, "-i")
self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'")
self.pruneLog()
# test reload missing jail (direct):
self.execFailed(startparams, "reload", "~~unknown~jail~fail~~")
self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'")
self.assertLogged("Exit with code -1")
self.pruneLog()
finally:
self.pruneLog()
# stop:
self.execSuccess(startparams, "stop")
self.assertLogged("Shutdown successful")
self.assertLogged("Exit with code 0")
@with_tmpdir
@with_kill_srv
def testClientFailStart(self, tmp):
@ -442,34 +478,33 @@ class Fail2banClientTest(Fail2banClientServerBase):
startparams = _start_params(tmp, logtarget="INHERITED")
## wrong config directory
self.assertRaises(FailExitException, _exec_client,
(CLIENT, "--async", "-c", tmp+"/miss", "start",))
self.assertLogged("Base configuration directory " + tmp+"/miss" + " does not exist")
self.execFailed((),
"--async", "-c", pjoin(tmp, "miss"), "start")
self.assertLogged("Base configuration directory " + pjoin(tmp, "miss") + " does not exist")
self.pruneLog()
## wrong socket
self.assertRaises(FailExitException, _exec_client,
(CLIENT, "--async", "-c", tmp+"/config", "-s", tmp+"/miss/f2b.sock", "start",))
self.assertLogged("There is no directory " + tmp+"/miss" + " to contain the socket file")
self.execFailed((),
"--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "miss/f2b.sock"), "start")
self.assertLogged("There is no directory " + pjoin(tmp, "miss") + " to contain the socket file")
self.pruneLog()
## not running
self.assertRaises(FailExitException, _exec_client,
(CLIENT, "-c", tmp+"/config", "-s", tmp+"/f2b.sock", "reload",))
self.execFailed((),
"-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "reload")
self.assertLogged("Could not find server")
self.pruneLog()
## already exists:
open(tmp+"/f2b.sock", 'a').close()
self.assertRaises(FailExitException, _exec_client,
(CLIENT, "--async", "-c", tmp+"/config", "-s", tmp+"/f2b.sock", "start",))
open(pjoin(tmp, "f2b.sock"), 'a').close()
self.execFailed((),
"--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "start")
self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)")
self.pruneLog()
os.remove(tmp+"/f2b.sock")
os.remove(pjoin(tmp, "f2b.sock"))
## wrong option:
self.assertRaises(FailExitException, _exec_client,
(CLIENT, "-s",))
self.execFailed((), "-s")
self.assertLogged("Usage: ")
self.pruneLog()
@ -487,9 +522,16 @@ class Fail2banClientTest(Fail2banClientServerBase):
class Fail2banServerTest(Fail2banClientServerBase):
def testServerUsage(self):
def execSuccess(self, startparams, *args):
self.assertRaises(ExitException, _exec_server,
(SERVER, "-h",))
((SERVER,) + startparams + args))
def execFailed(self, startparams, *args):
self.assertRaises(FailExitException, _exec_server,
((SERVER,) + startparams + args))
def testServerUsage(self):
self.execSuccess((), "-h")
self.assertLogged("Usage: " + SERVER)
self.assertLogged("Report bugs to ")
@ -497,9 +539,9 @@ class Fail2banServerTest(Fail2banClientServerBase):
@with_kill_srv
def testServerStartBackground(self, tmp):
# to prevent fork of test-cases process, start server in background via command:
startparams = _start_params(tmp, logtarget=tmp+"/f2b.log")
startparams = _start_params(tmp, logtarget=pjoin(tmp, "f2b.log"))
# start (in new process, using the same python version):
cmd = (sys.executable, os.path.join(os.path.join(BIN), SERVER))
cmd = (sys.executable, pjoin(BIN, SERVER))
logSys.debug('Start %s ...', cmd)
cmd = cmd + startparams + ("-b",)
ret = Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True)
@ -509,68 +551,15 @@ class Fail2banServerTest(Fail2banClientServerBase):
self.assertLogged("Server ready")
self.pruneLog()
try:
self.assertRaises(ExitException, _exec_server,
(SERVER,) + startparams + ("echo", "TEST-ECHO",))
self.assertRaises(FailExitException, _exec_server,
(SERVER,) + startparams + ("~~unknown~cmd~failed~~",))
self.execSuccess(startparams, "echo", "TEST-ECHO")
self.execFailed(startparams, "~~unknown~cmd~failed~~")
finally:
self.pruneLog()
# stop:
self.assertRaises(ExitException, _exec_server,
(SERVER,) + startparams + ("stop",))
self.execSuccess(startparams, "stop")
self.assertLogged("Shutdown successful")
self.assertLogged("Exit with code 0")
def _testServerStartForeground(self, tmp, startparams, phase):
# start and wait to end (foreground):
logSys.debug("-- start of test worker")
phase['start'] = True
self.assertRaises(fail2bancmdline.ExitException, _exec_server,
(SERVER, "-f") + startparams + ("start",))
# end :
phase['end'] = True
logSys.debug("-- end of test worker")
@with_tmpdir
def testServerStartForeground(self, tmp):
th = None
try:
# started directly here, so prevent overwrite test cases logger with "INHERITED"
startparams = _start_params(tmp, logtarget="INHERITED")
# because foreground block execution - start it in thread:
phase = dict()
th = Thread(name="_TestCaseWorker",
target=Fail2banServerTest._testServerStartForeground, args=(self, tmp, startparams, phase))
th.daemon = True
th.start()
try:
# wait for start thread:
Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME)
self.assertTrue(phase.get('start', None))
# wait for server (socket and ready):
self._wait_for_srv(tmp, True, startparams=startparams)
self.pruneLog()
# several commands to server:
self.assertRaises(ExitException, _exec_server,
(SERVER,) + startparams + ("ping",))
self.assertRaises(FailExitException, _exec_server,
(SERVER,) + startparams + ("~~unknown~cmd~failed~~",))
self.assertRaises(ExitException, _exec_server,
(SERVER,) + startparams + ("echo", "TEST-ECHO",))
finally:
self.pruneLog()
# stop:
self.assertRaises(ExitException, _exec_server,
(SERVER,) + startparams + ("stop",))
# wait for end:
Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME)
self.assertTrue(phase.get('end', None))
self.assertLogged("Shutdown successful", "Exiting Fail2ban")
finally:
_kill_srv(tmp)
if th:
th.join()
@with_tmpdir
@with_kill_srv
def testServerFailStart(self, tmp):
@ -578,21 +567,48 @@ class Fail2banServerTest(Fail2banClientServerBase):
startparams = _start_params(tmp, logtarget="INHERITED")
## wrong config directory
self.assertRaises(FailExitException, _exec_server,
(SERVER, "-c", tmp+"/miss",))
self.assertLogged("Base configuration directory " + tmp+"/miss" + " does not exist")
self.execFailed((),
"-c", pjoin(tmp, "miss"))
self.assertLogged("Base configuration directory " + pjoin(tmp, "miss") + " does not exist")
self.pruneLog()
## wrong socket
self.assertRaises(FailExitException, _exec_server,
(SERVER, "-c", tmp+"/config", "-x", "-s", tmp+"/miss/f2b.sock",))
self.assertLogged("There is no directory " + tmp+"/miss" + " to contain the socket file")
self.execFailed((),
"-c", pjoin(tmp, "config"), "-x", "-s", pjoin(tmp, "miss/f2b.sock"))
self.assertLogged("There is no directory " + pjoin(tmp, "miss") + " to contain the socket file")
self.pruneLog()
## already exists:
open(tmp+"/f2b.sock", 'a').close()
self.assertRaises(FailExitException, _exec_server,
(SERVER, "-c", tmp+"/config", "-s", tmp+"/f2b.sock",))
open(pjoin(tmp, "f2b.sock"), 'a').close()
self.execFailed((),
"-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"))
self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)")
self.pruneLog()
os.remove(tmp+"/f2b.sock")
os.remove(pjoin(tmp, "f2b.sock"))
@with_tmpdir
def testKillAfterStart(self, tmp):
try:
# to prevent fork of test-cases process, start server in background via command:
startparams = _start_params(tmp, logtarget=pjoin(tmp, "f2b.log"))
# start (in new process, using the same python version):
cmd = (sys.executable, pjoin(BIN, SERVER))
logSys.debug('Start %s ...', cmd)
cmd = cmd + startparams + ("-b",)
ret = Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True)
self.assertTrue(len(ret) and ret[0])
# wait for server (socket and ready):
self._wait_for_srv(tmp, True, startparams=cmd)
self.assertLogged("Server ready")
self.pruneLog()
logSys.debug('Kill server ... %s', tmp)
finally:
self.assertTrue(_kill_srv(tmp))
# wait for end (kill was successful):
Utils.wait_for(lambda: not isfile(pjoin(tmp, "f2b.pid")), MAX_WAITTIME)
self.assertFalse(isfile(pjoin(tmp, "f2b.pid")))
self.assertLogged("cleanup: kill ready")
self.pruneLog()
# again:
self.assertTrue(_kill_srv(tmp))
self.assertLogged("cleanup: no pidfile for")

View File

@ -67,3 +67,18 @@ Nov 4 18:30:40 localhost asterisk[32229]: NOTICE[32257]: chan_sip.c:23417 in han
[2016-01-28 10:34:31] NOTICE[3477][C-000003c3] chan_sip.c: Call from '' (1.2.3.4:10836) to extension '0+441772285407' rejected because extension not found in context 'default'.
# failJSON: { "time": "2016-01-28T10:34:33", "match": true , "host": "1.2.3.4" }
[2016-01-28 10:34:33] NOTICE[3477][C-000003c3] chan_sip.c: Call from '' (1.2.3.4:10836) to extension '' rejected because extension not found in context 'my-context'.
# Failed authentication with pjsip on Asterisk 13+
# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" }
[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" <sip:1000@10.0.0.1>' failed for '1.2.3.4:48336' (callid: 276666022) - No matching endpoint found
# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" }
[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" <sip:1000@10.0.0.1>' failed for '1.2.3.4:48336' (callid: 276666022) - Not match Endpoint ACL
# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" }
[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" <sip:1000@10.0.0.1>' failed for '1.2.3.4:48336' (callid: 276666022) - Not match Endpoint Contact ACL
# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" }
[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" <sip:1000@10.0.0.1>' failed for '1.2.3.4:48336' (callid: 276666022) - Failed to authenticate
# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" }
[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" <sip:1000@10.0.0.1>' failed for '1.2.3.4:48336' (callid: 276666022) - Error to authenticate
# Failed authentication with pjsip on Asterisk 13+
# failJSON: { "time": "2016-06-08T23:40:26", "match": true , "host": "2.3.4.5" }
[2016-06-08 23:40:26] NOTICE[32497] res_pjsip/pjsip_distributor.c: Request from '"317" <sip:317@1.2.3.4>' failed for '2.3.4.5:5089' (callid: 206f178f-896564cb-57573f49@1.2.3.4) - No matching endpoint found

View File

@ -48,10 +48,14 @@
2016-03-18 00:34:06 [7513] SMTP protocol error in "AUTH LOGIN" H=(ylmf-pc) [45.32.34.167]:60723 I=[172.89.0.6]:587 AUTH command used when not advertised
# failJSON: { "time": "2016-03-19T18:40:44", "match": true , "host": "92.45.204.170" }
2016-03-19 18:40:44 [26221] SMTP protocol error in "AUTH LOGIN aW5mb0BtYW5iYXQub3Jn" H=([127.0.0.1]) [92.45.204.170]:14243 I=[172.89.0.6]:587 AUTH command used when not advertised
# failJSON: { "time": "2016-05-17T06:25:27", "match": true , "host": "69.10.61.61", "desc": "from gh-1430" }
2016-05-17 06:25:27 SMTP protocol error in "AUTH LOGIN" H=(ylmf-pc) [69.10.61.61] AUTH command used when not advertised
# failJSON: { "time": "2016-03-21T06:38:05", "match": true , "host": "49.212.207.15" }
2016-03-21 06:38:05 [5718] no MAIL in SMTP connection from www3005.sakura.ne.jp [49.212.207.15]:28890 I=[172.89.0.6]:25 D=21s C=EHLO,STARTTLS
# failJSON: { "time": "2016-03-21T06:57:36", "match": true , "host": "122.165.71.116" }
2016-03-21 06:57:36 [5908] no MAIL in SMTP connection from [122.165.71.116]:2056 I=[172.89.0.6]:25 D=10s
# failJSON: { "time": "2016-03-21T06:57:36", "match": true , "host": "122.165.71.116" }
2016-03-21 06:57:36 [5908] no MAIL in SMTP connection from [122.165.71.116] I=[172.89.0.6]:25 D=10s
# failJSON: { "time": "2016-03-21T04:07:49", "match": true , "host": "174.137.147.204" }
2016-03-21 04:07:49 [25874] 1ahr79-0006jK-G9 SMTP connection from (voyeur.webair.com) [174.137.147.204]:44884 I=[172.89.0.6]:25 closed by DROP in ACL
# failJSON: { "time": "2016-03-21T04:33:13", "match": true , "host": "206.214.71.53" }

View File

@ -159,6 +159,11 @@ class Transmitter(TransmitterBase):
self.server = TestServer()
super(Transmitter, self).setUp()
def testServerIsNotStarted(self):
# so far isStarted only tested but not used otherwise
# and here we don't really .start server
self.assertFalse(self.server.isStarted())
def testStopServer(self):
self.assertEqual(self.transm.proceed(["stop"]), (0, None))
@ -1014,6 +1019,7 @@ class LoggingTests(LogCaptureTestCase):
server = TestServer()
try:
server.start(sock_name, pidfile_name, force=False)
self.assertFalse(server.isStarted())
self.assertLogged("Server already running")
finally:
server.quit()

View File

@ -54,8 +54,7 @@ if not CONFIG_DIR:
else:
CONFIG_DIR = '/etc/fail2ban'
# In not installed env (setup, test-cases) use fail2ban modules from main directory:
if 1 or os.environ.get('PYTHONPATH', None) is None:
# During the test cases (or setup) use fail2ban modules from main directory:
os.putenv('PYTHONPATH', os.path.dirname(os.path.dirname(os.path.dirname(
os.path.abspath(__file__)))))

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.2.
.TH FAIL2BAN-CLIENT "1" "March 2016" "fail2ban-client v0.9.4" "User Commands"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1.
.TH FAIL2BAN-CLIENT "1" "July 2016" "fail2ban-client v0.10.0a1" "User Commands"
.SH NAME
fail2ban-client \- configure and control the server
.SH SYNOPSIS
.B fail2ban-client
[\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR
[\fIOPTIONS\fR] \fI<COMMAND>\fR
.SH DESCRIPTION
Fail2Ban v0.9.4 reads log file that contains password failure report
Fail2Ban v0.10.0a1 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS
.TP
@ -19,6 +19,13 @@ socket path
\fB\-p\fR <FILE>
pidfile path
.TP
\fB\-\-loglevel\fR <LEVEL>
logging level
.HP
\fB\-\-logtarget\fR <FILE>|STDOUT|STDERR|SYSLOG
.HP
\fB\-\-syslogsocket\fR auto|<FILE>
.TP
\fB\-d\fR
dump configuration. For debugging
.TP
@ -38,7 +45,13 @@ force execution of the server (remove socket file)
start server in background (default)
.TP
\fB\-f\fR
start server in foreground (note that the client forks once itself)
start server in foreground
.TP
\fB\-\-async\fR
start server in async mode (for internal usage only, don't read configuration)
.TP
\fB\-\-timeout\fR
timeout to wait for the server (for internal usage only, don't read configuration)
.TP
\fB\-h\fR, \fB\-\-help\fR
display this help message
@ -52,8 +65,12 @@ BASIC
\fBstart\fR
starts the server and the jails
.TP
\fBrestart\fR
restarts the server
.TP
\fBreload\fR
reloads the configuration
reloads the configuration without
restart
.TP
\fBreload <JAIL>\fR
reloads the jail <JAIL>
@ -69,6 +86,10 @@ server
\fBping\fR
tests if the server is alive
.TP
\fBecho\fR
for internal usage, returns back
and outputs a given string
.TP
\fBhelp\fR
return this output
.TP
@ -227,8 +248,9 @@ for <JAIL>
\fBset <JAIL> maxlines <LINES>\fR
sets the number of <LINES> to
buffer for regex search for <JAIL>
.TP
\fBset <JAIL> addaction <ACT>[ <PYTHONFILE> <JSONKWARGS>]\fR
.IP
set <JAIL> addaction <ACT>[ <PYTHONFILE> <JSONKWARGS>]
.IP
adds a new action named <ACT> for
<JAIL>. Optionally for a Python
based action, a <PYTHONFILE> and
@ -240,38 +262,45 @@ removes the action <ACT> from
<JAIL>
.IP
COMMAND ACTION CONFIGURATION
.TP
\fBset <JAIL> action <ACT> actionstart <CMD>\fR
.IP
set <JAIL> action <ACT> actionstart <CMD>
.IP
sets the start command <CMD> of
the action <ACT> for <JAIL>
.TP
\fBset <JAIL> action <ACT> actionstop <CMD> sets the stop command <CMD> of the\fR
.IP
set <JAIL> action <ACT> actionstop <CMD> sets the stop command <CMD> of the
.IP
action <ACT> for <JAIL>
.TP
\fBset <JAIL> action <ACT> actioncheck <CMD>\fR
.IP
set <JAIL> action <ACT> actioncheck <CMD>
.IP
sets the check command <CMD> of
the action <ACT> for <JAIL>
.TP
\fBset <JAIL> action <ACT> actionban <CMD>\fR
sets the ban command <CMD> of the
action <ACT> for <JAIL>
.TP
\fBset <JAIL> action <ACT> actionunban <CMD>\fR
.IP
set <JAIL> action <ACT> actionunban <CMD>
.IP
sets the unban command <CMD> of
the action <ACT> for <JAIL>
.TP
\fBset <JAIL> action <ACT> timeout <TIMEOUT>\fR
.IP
set <JAIL> action <ACT> timeout <TIMEOUT>
.IP
sets <TIMEOUT> as the command
timeout in seconds for the action
<ACT> for <JAIL>
.IP
GENERAL ACTION CONFIGURATION
.TP
\fBset <JAIL> action <ACT> <PROPERTY> <VALUE>\fR
.IP
set <JAIL> action <ACT> <PROPERTY> <VALUE>
.IP
sets the <VALUE> of <PROPERTY> for
the action <ACT> for <JAIL>
.TP
\fBset <JAIL> action <ACT> <METHOD>[ <JSONKWARGS>]\fR
.IP
set <JAIL> action <ACT> <METHOD>[ <JSONKWARGS>]
.IP
calls the <METHOD> with
<JSONKWARGS> for the action <ACT>
for <JAIL>
@ -376,9 +405,6 @@ gets the value of <PROPERTY> for
the action <ACT> for <JAIL>
.SH FILES
\fI/etc/fail2ban/*\fR
.SH AUTHOR
Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>.
Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>.
.SH "REPORTING BUGS"
Report bugs to https://github.com/fail2ban/fail2ban/issues
.SH COPYRIGHT

View File

@ -1,10 +1,10 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.2.
.TH FAIL2BAN-REGEX "1" "March 2016" "fail2ban-regex 0.9.4" "User Commands"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1.
.TH FAIL2BAN-REGEX "1" "July 2016" "fail2ban-regex 0.10.0a1" "User Commands"
.SH NAME
fail2ban-regex \- test Fail2ban "failregex" option
.SH SYNOPSIS
.B fail2ban-regex
[\fI\,OPTIONS\/\fR] \fI\,<LOG> <REGEX> \/\fR[\fI\,IGNOREREGEX\/\fR]
[\fIOPTIONS\fR] \fI<LOG> <REGEX> \fR[\fIIGNOREREGEX\fR]
.SH DESCRIPTION
Fail2Ban reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
@ -16,7 +16,7 @@ string
a string representing a log line
.TP
filename
path to a log file (\fI\,/var/log/auth.log\/\fP)
path to a log file (\fI/var/log/auth.log\fP)
.TP
"systemd\-journal"
search systemd journal (systemd\-python required)
@ -42,20 +42,23 @@ show program's version number and exit
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-d\fR DATEPATTERN, \fB\-\-datepattern\fR=\fI\,DATEPATTERN\/\fR
\fB\-d\fR DATEPATTERN, \fB\-\-datepattern\fR=\fIDATEPATTERN\fR
set custom pattern used to match date/times
.TP
\fB\-e\fR ENCODING, \fB\-\-encoding\fR=\fI\,ENCODING\/\fR
\fB\-e\fR ENCODING, \fB\-\-encoding\fR=\fIENCODING\fR
File encoding. Default: system locale
.TP
\fB\-L\fR MAXLINES, \fB\-\-maxlines\fR=\fI\,MAXLINES\/\fR
\fB\-r\fR, \fB\-\-raw\fR
Raw hosts, don't resolve dns
.TP
\fB\-L\fR MAXLINES, \fB\-\-maxlines\fR=\fIMAXLINES\fR
maxlines for multi\-line regex
.TP
\fB\-m\fR JOURNALMATCH, \fB\-\-journalmatch\fR=\fI\,JOURNALMATCH\/\fR
\fB\-m\fR JOURNALMATCH, \fB\-\-journalmatch\fR=\fIJOURNALMATCH\fR
journalctl style matches overriding filter file.
"systemd\-journal" only
.TP
\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fI\,LOG_LEVEL\/\fR
\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fILOG_LEVEL\fR
Log level for the Fail2Ban logger to use
.TP
\fB\-v\fR, \fB\-\-verbose\fR

View File

@ -1,24 +1,17 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.2.
.TH FAIL2BAN-SERVER "1" "March 2016" "fail2ban-server v0.9.4" "User Commands"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1.
.TH FAIL2BAN-SERVER "1" "July 2016" "fail2ban-server v0.10.0a1" "User Commands"
.SH NAME
fail2ban-server \- start the server
.SH SYNOPSIS
.B fail2ban-server
[\fI\,OPTIONS\/\fR]
[\fIOPTIONS\fR]
.SH DESCRIPTION
Fail2Ban v0.9.4 reads log file that contains password failure report
Fail2Ban v0.10.0a1 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
.PP
Only use this command for debugging purpose. Start the server with
fail2ban\-client instead. The default behaviour is to start the server
in background.
.SH OPTIONS
.TP
\fB\-b\fR
start in background
.TP
\fB\-f\fR
start in foreground
\fB\-c\fR <DIR>
configuration directory
.TP
\fB\-s\fR <FILE>
socket path
@ -26,17 +19,45 @@ socket path
\fB\-p\fR <FILE>
pidfile path
.TP
\fB\-\-loglevel\fR <LEVEL>
logging level
.HP
\fB\-\-logtarget\fR <FILE>|STDOUT|STDERR|SYSLOG
.HP
\fB\-\-syslogsocket\fR auto|<FILE>
.TP
\fB\-d\fR
dump configuration. For debugging
.TP
\fB\-i\fR
interactive mode
.TP
\fB\-v\fR
increase verbosity
.TP
\fB\-q\fR
decrease verbosity
.TP
\fB\-x\fR
force execution of the server (remove socket file)
.TP
\fB\-b\fR
start server in background (default)
.TP
\fB\-f\fR
start server in foreground
.TP
\fB\-\-async\fR
start server in async mode (for internal usage only, don't read configuration)
.TP
\fB\-\-timeout\fR
timeout to wait for the server (for internal usage only, don't read configuration)
.TP
\fB\-h\fR, \fB\-\-help\fR
display this help message
.TP
\fB\-V\fR, \fB\-\-version\fR
print the version
.SH AUTHOR
Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>.
Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>.
.SH "REPORTING BUGS"
Report bugs to https://github.com/fail2ban/fail2ban/issues
.SH COPYRIGHT

View File

@ -1,10 +1,10 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.2.
.TH FAIL2BAN-TESTCASES "1" "March 2016" "fail2ban-testcases 0.9.4" "User Commands"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1.
.TH FAIL2BAN-TESTCASES "1" "July 2016" "fail2ban-testcases 0.10.0a1" "User Commands"
.SH NAME
fail2ban-testcases \- run Fail2Ban unit-tests
.SH SYNOPSIS
.B fail2ban-testcases
[\fI\,OPTIONS\/\fR] [\fI\,regexps\/\fR]
[\fIOPTIONS\fR] [\fIregexps\fR]
.SH DESCRIPTION
Script to run Fail2Ban tests battery
.SH OPTIONS
@ -15,12 +15,26 @@ show program's version number and exit
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fI\,LOG_LEVEL\/\fR
\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fILOG_LEVEL\fR
Log level for the logger to use during running tests
.TP
\fB\-n\fR, \fB\-\-no\-network\fR
Do not run tests that require the network
.TP
\fB\-g\fR, \fB\-\-no\-gamin\fR
Do not run tests that require the gamin
.TP
\fB\-m\fR, \fB\-\-memory\-db\fR
Run database tests using memory instead of file
.TP
\fB\-f\fR, \fB\-\-fast\fR
Try to increase speed of the tests, decreasing of wait
intervals, memory database
.TP
\fB\-i\fR, \fB\-\-ignore\fR
negate [regexps] filter to ignore tests matched
specified regexps
.TP
\fB\-t\fR, \fB\-\-log\-traceback\fR
Enrich log\-messages with compressed tracebacks
.TP