mirror of https://github.com/fail2ban/fail2ban
some compatibility fixes (prevent forking of testcase-process, code review), wait 4 server ready, test cases fixed (py2/py3)
parent
2fcb6358ff
commit
0b4143730d
|
@ -34,14 +34,18 @@ from threading import Thread
|
|||
from ..version import version
|
||||
from .csocket import CSocket
|
||||
from .beautifier import Beautifier
|
||||
from .fail2bancmdline import Fail2banCmdLine, ExitException, PRODUCTION, logSys, exit, output
|
||||
from .fail2bancmdline import Fail2banCmdLine, ServerExecutionException, ExitException, \
|
||||
logSys, PRODUCTION, exit, output
|
||||
|
||||
MAX_WAITTIME = 30
|
||||
PROMPT = "fail2ban> "
|
||||
|
||||
|
||||
def _thread_name():
|
||||
return threading.current_thread().__class__.__name__
|
||||
|
||||
def input_command():
|
||||
return raw_input(PROMPT)
|
||||
|
||||
##
|
||||
#
|
||||
|
@ -49,8 +53,6 @@ def _thread_name():
|
|||
|
||||
class Fail2banClient(Fail2banCmdLine, Thread):
|
||||
|
||||
PROMPT = "fail2ban> "
|
||||
|
||||
def __init__(self):
|
||||
Fail2banCmdLine.__init__(self)
|
||||
Thread.__init__(self)
|
||||
|
@ -91,11 +93,11 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
client = CSocket(self._conf["socket"])
|
||||
ret = client.send(c)
|
||||
if ret[0] == 0:
|
||||
logSys.debug("OK : " + `ret[1]`)
|
||||
logSys.debug("OK : %r", ret[1])
|
||||
if showRet or c[0] == 'echo':
|
||||
output(beautifier.beautify(ret[1]))
|
||||
else:
|
||||
logSys.error("NOK: " + `ret[1].args`)
|
||||
logSys.error("NOK: %r", ret[1].args)
|
||||
if showRet:
|
||||
output(beautifier.beautifyError(ret[1]))
|
||||
streamRet = False
|
||||
|
@ -202,7 +204,10 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
except Exception as e:
|
||||
output("")
|
||||
logSys.error("Exception while starting server " + ("background" if background else "foreground"))
|
||||
logSys.error(e)
|
||||
if self._conf["verbose"] > 1:
|
||||
logSys.exception(e)
|
||||
else:
|
||||
logSys.error(e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@ -249,7 +254,9 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
if self._conf.get("interactive", False):
|
||||
output(' ## stop ... ')
|
||||
self.__processCommand(['stop'])
|
||||
self.__waitOnServer(False)
|
||||
if not self.__waitOnServer(False):
|
||||
logSys.error("Could not stop server")
|
||||
return False
|
||||
# in interactive mode reset config, to make full-reload if there something changed:
|
||||
if self._conf.get("interactive", False):
|
||||
output(' ## load configuration ... ')
|
||||
|
@ -286,10 +293,14 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
def __processStartStreamAfterWait(self, *args):
|
||||
try:
|
||||
# Wait for the server to start
|
||||
self.__waitOnServer()
|
||||
if not self.__waitOnServer():
|
||||
logSys.error("Could not find server, waiting failed")
|
||||
return False
|
||||
# Configure the server
|
||||
self.__processCmd(*args)
|
||||
except ServerExecutionException:
|
||||
except ServerExecutionException as e:
|
||||
if self._conf["verbose"] > 1:
|
||||
logSys.exception(e)
|
||||
logSys.error("Could not start server. Maybe an old "
|
||||
"socket file is still present. Try to "
|
||||
"remove " + self._conf["socket"] + ". If "
|
||||
|
@ -306,11 +317,13 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
# Wait for the server to start (the server has 30 seconds to answer ping)
|
||||
starttime = time.time()
|
||||
logSys.debug("__waitOnServer: %r", (alive, maxtime))
|
||||
test = lambda: os.path.exists(self._conf["socket"]) and self.__ping()
|
||||
with VisualWait(self._conf["verbose"]) as vis:
|
||||
while self._alive and (
|
||||
not self.__ping() == alive or (
|
||||
not alive and os.path.exists(self._conf["socket"])
|
||||
)):
|
||||
sltime = 0.0125 / 2
|
||||
while self._alive:
|
||||
runf = test()
|
||||
if runf == alive:
|
||||
return True
|
||||
now = time.time()
|
||||
# Wonderful visual :)
|
||||
if now > starttime + 1:
|
||||
|
@ -318,7 +331,9 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
# f end time reached:
|
||||
if now - starttime >= maxtime:
|
||||
raise ServerExecutionException("Failed to start server")
|
||||
time.sleep(0.1)
|
||||
sltime = min(sltime * 2, 0.5)
|
||||
time.sleep(sltime)
|
||||
return False
|
||||
|
||||
def start(self, argv):
|
||||
# Install signal handlers
|
||||
|
@ -332,27 +347,31 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
if self._argv is None:
|
||||
ret = self.initCmdLine(argv)
|
||||
if ret is not None:
|
||||
return ret
|
||||
if ret:
|
||||
return True
|
||||
raise ServerExecutionException("Init of command line failed")
|
||||
|
||||
# Commands
|
||||
args = self._args
|
||||
|
||||
# Interactive mode
|
||||
if self._conf.get("interactive", False):
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
logSys.error("Readline not available")
|
||||
return False
|
||||
# no readline in test:
|
||||
if PRODUCTION: # pragma: no cover
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
raise ServerExecutionException("Readline not available")
|
||||
try:
|
||||
ret = True
|
||||
if len(args) > 0:
|
||||
ret = self.__processCommand(args)
|
||||
if ret:
|
||||
readline.parse_and_bind("tab: complete")
|
||||
if PRODUCTION: # pragma: no cover
|
||||
readline.parse_and_bind("tab: complete")
|
||||
self.dispInteractive()
|
||||
while True:
|
||||
cmd = raw_input(self.PROMPT)
|
||||
cmd = input_command()
|
||||
if cmd == "exit" or cmd == "quit":
|
||||
# Exit
|
||||
return True
|
||||
|
@ -362,26 +381,31 @@ class Fail2banClient(Fail2banCmdLine, Thread):
|
|||
try:
|
||||
self.__processCommand(shlex.split(cmd))
|
||||
except Exception, e:
|
||||
logSys.error(e)
|
||||
if self._conf["verbose"] > 1:
|
||||
logSys.exception(e)
|
||||
else:
|
||||
logSys.error(e)
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
output("")
|
||||
return True
|
||||
raise
|
||||
# Single command mode
|
||||
else:
|
||||
if len(args) < 1:
|
||||
self.dispUsage()
|
||||
return False
|
||||
return self.__processCommand(args)
|
||||
except Exception as e:
|
||||
if self._conf["verbose"] > 1:
|
||||
logSys.exception(e)
|
||||
else:
|
||||
logSys.error(e)
|
||||
return False
|
||||
finally:
|
||||
self._alive = False
|
||||
for s, sh in _prev_signals.iteritems():
|
||||
signal.signal(s, sh)
|
||||
|
||||
|
||||
class ServerExecutionException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
##
|
||||
# Wonderful visual :)
|
||||
#
|
||||
|
|
|
@ -261,5 +261,9 @@ class Fail2banCmdLine():
|
|||
exit = Fail2banCmdLine.exit
|
||||
|
||||
|
||||
class ExitException:
|
||||
pass
|
||||
class ExitException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ServerExecutionException(Exception):
|
||||
pass
|
||||
|
|
|
@ -24,10 +24,8 @@ __license__ = "GPL"
|
|||
import os
|
||||
import sys
|
||||
|
||||
from ..version import version
|
||||
from ..server.server import Server
|
||||
from ..server.utils import Utils
|
||||
from .fail2bancmdline import Fail2banCmdLine, logSys, PRODUCTION, exit
|
||||
from .fail2bancmdline import Fail2banCmdLine, ServerExecutionException, \
|
||||
logSys, PRODUCTION, exit
|
||||
|
||||
MAX_WAITTIME = 30
|
||||
|
||||
|
@ -44,13 +42,14 @@ class Fail2banServer(Fail2banCmdLine):
|
|||
# Fail2banCmdLine.__init__(self)
|
||||
|
||||
##
|
||||
# Start Fail2Ban server in main thread without fork (foreground).
|
||||
# Start Fail2Ban server in main thread without fork (direct, it can fork itself in Server if daemon=True).
|
||||
#
|
||||
# Start the Fail2ban server in foreground (daemon mode or not).
|
||||
# Start the Fail2ban server in background/foreground (daemon mode or not).
|
||||
|
||||
@staticmethod
|
||||
def startServerDirect(conf, daemon=True):
|
||||
logSys.debug("-- direct starting of server in %s, deamon: %s", os.getpid(), daemon)
|
||||
from ..server.server import Server
|
||||
server = None
|
||||
try:
|
||||
# Start it in foreground (current thread, not new process),
|
||||
|
@ -59,11 +58,14 @@ class Fail2banServer(Fail2banCmdLine):
|
|||
server.start(conf["socket"],
|
||||
conf["pidfile"], conf["force"],
|
||||
conf=conf)
|
||||
except Exception, e:
|
||||
logSys.exception(e)
|
||||
if server:
|
||||
server.quit()
|
||||
exit(-1)
|
||||
except Exception as e:
|
||||
try:
|
||||
if server:
|
||||
server.quit()
|
||||
except Exception as e2:
|
||||
if conf["verbose"] > 1:
|
||||
logSys.exception(e2)
|
||||
raise
|
||||
|
||||
return server
|
||||
|
||||
|
@ -74,10 +76,6 @@ class Fail2banServer(Fail2banCmdLine):
|
|||
|
||||
@staticmethod
|
||||
def startServerAsync(conf):
|
||||
# Directory of client (to try the first start from the same directory as client):
|
||||
startdir = sys.path[0]
|
||||
if startdir in ("", "."): # may be uresolved in test-cases, so get bin-directory:
|
||||
startdir = os.path.dirname(sys.argv[0])
|
||||
# Forks the current process, don't fork if async specified (ex: test cases)
|
||||
pid = 0
|
||||
frk = not conf["async"] and PRODUCTION
|
||||
|
@ -103,25 +101,43 @@ class Fail2banServer(Fail2banCmdLine):
|
|||
for o in ('loglevel', 'logtarget', 'syslogsocket'):
|
||||
args.append("--"+o)
|
||||
args.append(conf[o])
|
||||
|
||||
try:
|
||||
# Use the current directory.
|
||||
exe = os.path.abspath(os.path.join(startdir, SERVER))
|
||||
# Directory of client (to try the first start from current or the same directory as client, and from relative bin):
|
||||
exe = Fail2banServer.getServerPath()
|
||||
if not frk:
|
||||
# Wrapr args to use the same python version in client/server (important for multi-python systems):
|
||||
args[0] = exe
|
||||
exe = sys.executable
|
||||
args[0:0] = [exe]
|
||||
logSys.debug("Starting %r with args %r", exe, args)
|
||||
if frk:
|
||||
os.execv(exe, args)
|
||||
return os.execv(exe, args)
|
||||
else:
|
||||
os.spawnv(os.P_NOWAITO, exe, args)
|
||||
# use P_WAIT instead of P_NOWAIT (to prevent defunct-zomby process), it startet as daemon, so parent exit fast after fork):
|
||||
return os.spawnv(os.P_WAIT, exe, args)
|
||||
except OSError as e:
|
||||
try:
|
||||
# Use the PATH env.
|
||||
logSys.warning("Initial start attempt failed (%s). Starting %r with the same args", e, SERVER)
|
||||
if frk:
|
||||
os.execvp(SERVER, args)
|
||||
else:
|
||||
os.spawnvp(os.P_NOWAITO, SERVER, args)
|
||||
except OSError:
|
||||
exit(-1)
|
||||
# Use the PATH env.
|
||||
logSys.warning("Initial start attempt failed (%s). Starting %r with the same args", e, SERVER)
|
||||
if frk:
|
||||
return os.execvp(SERVER, args)
|
||||
else:
|
||||
del args[0]
|
||||
args[0] = SERVER
|
||||
return os.spawnvp(os.P_WAIT, SERVER, args)
|
||||
return pid
|
||||
|
||||
@staticmethod
|
||||
def getServerPath():
|
||||
startdir = sys.path[0]
|
||||
exe = os.path.abspath(os.path.join(startdir, SERVER))
|
||||
if not os.path.isfile(exe): # may be uresolved in test-cases, so get relative starter (client):
|
||||
startdir = os.path.dirname(sys.argv[0])
|
||||
exe = os.path.abspath(os.path.join(startdir, SERVER))
|
||||
if not os.path.isfile(exe): # may be uresolved in test-cases, so try to get relative bin-directory:
|
||||
startdir = os.path.dirname(os.path.abspath(__file__))
|
||||
startdir = os.path.join(os.path.dirname(os.path.dirname(startdir)), "bin")
|
||||
exe = os.path.abspath(os.path.join(startdir, SERVER))
|
||||
return exe
|
||||
|
||||
def _Fail2banClient(self):
|
||||
from .fail2banclient import Fail2banClient
|
||||
|
@ -139,18 +155,24 @@ class Fail2banServer(Fail2banCmdLine):
|
|||
args = self._args
|
||||
|
||||
cli = None
|
||||
# If client mode - whole processing over client:
|
||||
if len(args) or self._conf.get("interactive", False):
|
||||
cli = self._Fail2banClient()
|
||||
return cli.start(argv)
|
||||
# Just start:
|
||||
if len(args) == 1 and args[0] == 'start' and not self._conf.get("interactive", False):
|
||||
pass
|
||||
else:
|
||||
# If client mode - whole processing over client:
|
||||
if len(args) or self._conf.get("interactive", False):
|
||||
cli = self._Fail2banClient()
|
||||
return cli.start(argv)
|
||||
|
||||
# Start the server:
|
||||
server = None
|
||||
try:
|
||||
# async = True, if started from client, should fork, daemonize, etc...
|
||||
# background = True, if should start in new process, otherwise start in foreground
|
||||
async = self._conf.get("async", False)
|
||||
from ..server.utils import Utils
|
||||
# background = True, if should be new process running in background, otherwise start in foreground
|
||||
# process will be forked in daemonize, inside of Server module.
|
||||
# async = True, if started from client, should...
|
||||
background = self._conf["background"]
|
||||
async = self._conf.get("async", False)
|
||||
# If was started not from the client:
|
||||
if not async:
|
||||
# Start new thread with client to read configuration and
|
||||
|
@ -162,13 +184,14 @@ class Fail2banServer(Fail2banCmdLine):
|
|||
# wait up to MAX_WAITTIME, do not continue if configuration is not 100% valid:
|
||||
Utils.wait_for(lambda: phase.get('ready', None) is not None, MAX_WAITTIME)
|
||||
if not phase.get('start', False):
|
||||
return False
|
||||
raise ServerExecutionException('Async configuration of server failed')
|
||||
|
||||
# Start server, daemonize it, etc.
|
||||
if async or not background:
|
||||
server = Fail2banServer.startServerDirect(self._conf, background)
|
||||
else:
|
||||
Fail2banServer.startServerAsync(self._conf)
|
||||
pid = os.getpid()
|
||||
server = Fail2banServer.startServerDirect(self._conf, background)
|
||||
# If forked - just exit other processes
|
||||
if pid != os.getpid():
|
||||
os._exit(0)
|
||||
if cli:
|
||||
cli._server = server
|
||||
|
||||
|
@ -182,7 +205,8 @@ class Fail2banServer(Fail2banCmdLine):
|
|||
logSys.debug('Starting server done')
|
||||
|
||||
except Exception, e:
|
||||
logSys.exception(e)
|
||||
if self._conf["verbose"] > 1:
|
||||
logSys.exception(e)
|
||||
if server:
|
||||
server.quit()
|
||||
exit(-1)
|
||||
|
|
|
@ -93,9 +93,15 @@ class Server:
|
|||
if self.__daemon: # pragma: no cover
|
||||
logSys.info("Starting in daemon mode")
|
||||
ret = self.__createDaemon()
|
||||
if not ret:
|
||||
logSys.error("Could not create daemon")
|
||||
raise ServerInitializationError("Could not create daemon")
|
||||
# If forked parent - return here (parent process will configure server later):
|
||||
if ret is None:
|
||||
return False
|
||||
# If error:
|
||||
if not ret[0]:
|
||||
err = "Could not create daemon %s", ret[1:]
|
||||
logSys.error(err)
|
||||
raise ServerInitializationError(err)
|
||||
# We are daemon.
|
||||
|
||||
# Set all logging parameters (or use default if not specified):
|
||||
self.setSyslogSocket(conf.get("syslogsocket",
|
||||
|
@ -159,8 +165,12 @@ class Server:
|
|||
logging.shutdown()
|
||||
|
||||
# Restore default signal handlers:
|
||||
for s, sh in self.__prev_signals.iteritems():
|
||||
signal.signal(s, sh)
|
||||
if _thread_name() == '_MainThread':
|
||||
for s, sh in self.__prev_signals.iteritems():
|
||||
signal.signal(s, sh)
|
||||
|
||||
# Prevent to call quit twice:
|
||||
self.quit = lambda: False
|
||||
|
||||
def addJail(self, name, backend):
|
||||
self.__jails.add(name, backend, self.__db)
|
||||
|
@ -569,10 +579,9 @@ 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.
|
||||
if _thread_name() == '_MainThread':
|
||||
for s in (signal.SIGHUP,):
|
||||
self.__prev_signals[s] = signal.getsignal(s)
|
||||
signal.signal(s, signal.SIG_IGN)
|
||||
for s in (signal.SIGHUP,):
|
||||
self.__prev_signals[s] = signal.getsignal(s)
|
||||
signal.signal(s, signal.SIG_IGN)
|
||||
|
||||
try:
|
||||
# Fork a child process so the parent can exit. This will return control
|
||||
|
@ -583,7 +592,7 @@ class Server:
|
|||
# PGID.
|
||||
pid = os.fork()
|
||||
except OSError, e:
|
||||
return((e.errno, e.strerror)) # ERROR (return a tuple)
|
||||
return (False, (e.errno, e.strerror)) # ERROR (return a tuple)
|
||||
|
||||
if pid == 0: # The first child.
|
||||
|
||||
|
@ -604,7 +613,7 @@ class Server:
|
|||
# preventing the daemon from ever acquiring a controlling terminal.
|
||||
pid = os.fork() # Fork a second child.
|
||||
except OSError, e:
|
||||
return((e.errno, e.strerror)) # ERROR (return a tuple)
|
||||
return (False, (e.errno, e.strerror)) # ERROR (return a tuple)
|
||||
|
||||
if (pid == 0): # The second child.
|
||||
# Ensure that the daemon doesn't keep any directory in use. Failure
|
||||
|
@ -613,7 +622,8 @@ class Server:
|
|||
else:
|
||||
os._exit(0) # Exit parent (the first child) of the second child.
|
||||
else:
|
||||
os._exit(0) # Exit parent of the first child.
|
||||
# Signal to exit, parent of the first child.
|
||||
return None
|
||||
|
||||
# Close all open files. Try the system configuration variable, SC_OPEN_MAX,
|
||||
# for the maximum number of open files to close. If it doesn't exist, use
|
||||
|
@ -641,7 +651,7 @@ class Server:
|
|||
os.open("/dev/null", os.O_RDONLY) # standard input (0)
|
||||
os.open("/dev/null", os.O_RDWR) # standard output (1)
|
||||
os.open("/dev/null", os.O_RDWR) # standard error (2)
|
||||
return True
|
||||
return (True,)
|
||||
|
||||
|
||||
class ServerInitializationError(Exception):
|
||||
|
|
|
@ -26,6 +26,7 @@ __license__ = "GPL"
|
|||
import fileinput
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import unittest
|
||||
|
||||
|
@ -50,10 +51,9 @@ else:
|
|||
|
||||
CLIENT = "fail2ban-client"
|
||||
SERVER = "fail2ban-server"
|
||||
BIN = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "bin")
|
||||
BIN = os.path.dirname(Fail2banServer.getServerPath())
|
||||
|
||||
MAX_WAITTIME = 10
|
||||
MAX_WAITTIME = unittest.F2B.maxWaitTime(MAX_WAITTIME)
|
||||
MAX_WAITTIME = 30 if not unittest.F2B.fast else 5
|
||||
|
||||
##
|
||||
# Several wrappers and settings for proper testing:
|
||||
|
@ -67,8 +67,6 @@ fail2bancmdline.logSys = \
|
|||
fail2banclient.logSys = \
|
||||
fail2banserver.logSys = logSys
|
||||
|
||||
LOG_LEVEL = logSys.level
|
||||
|
||||
server.DEF_LOGTARGET = "/dev/null"
|
||||
|
||||
def _test_output(*args):
|
||||
|
@ -89,13 +87,13 @@ fail2banclient.exit = \
|
|||
fail2banserver.exit = _test_exit
|
||||
|
||||
INTERACT = []
|
||||
def _test_raw_input(*args):
|
||||
def _test_input_command(*args):
|
||||
if len(INTERACT):
|
||||
#print('--- interact command: ', INTERACT[0])
|
||||
#logSys.debug('--- interact command: %r', INTERACT[0])
|
||||
return INTERACT.pop(0)
|
||||
else:
|
||||
return "exit"
|
||||
fail2banclient.raw_input = _test_raw_input
|
||||
fail2banclient.input_command = _test_input_command
|
||||
|
||||
# prevents change logging params, log capturing, etc:
|
||||
fail2bancmdline.PRODUCTION = \
|
||||
|
@ -142,7 +140,7 @@ 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", "wb")
|
||||
f = open(cfg+"/fail2ban.conf", "w")
|
||||
f.write('\n'.join((
|
||||
"[Definition]",
|
||||
"loglevel = INFO",
|
||||
|
@ -156,20 +154,20 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"):
|
|||
"",
|
||||
)))
|
||||
f.close()
|
||||
f = open(cfg+"/jail.conf", "wb")
|
||||
f = open(cfg+"/jail.conf", "w")
|
||||
f.write('\n'.join((
|
||||
"[INCLUDES]", "",
|
||||
"[DEFAULT]", "",
|
||||
"",
|
||||
)))
|
||||
f.close()
|
||||
if LOG_LEVEL < logging.DEBUG: # if HEAVYDEBUG
|
||||
if logSys.level < logging.DEBUG: # if HEAVYDEBUG
|
||||
_out_file(cfg+"/fail2ban.conf")
|
||||
_out_file(cfg+"/jail.conf")
|
||||
# parameters:
|
||||
return ("-c", cfg,
|
||||
"--logtarget", logtarget, "--loglevel", "DEBUG", "--syslogsocket", "auto",
|
||||
"-s", tmp+"/f2b.sock", "-p", tmp+"/f2b.pid")
|
||||
# parameters (sock/pid and config, increase verbosity, set log, etc.):
|
||||
return ("-c", cfg, "-s", tmp+"/f2b.sock", "-p", tmp+"/f2b.pid",
|
||||
"-vv", "--logtarget", logtarget, "--loglevel", "DEBUG", "--syslogsocket", "auto",
|
||||
)
|
||||
|
||||
def _kill_srv(pidfile): # pragma: no cover
|
||||
def _pid_exists(pid):
|
||||
|
@ -206,14 +204,14 @@ def _kill_srv(pidfile): # pragma: no cover
|
|||
os.kill(pid, signal.SIGKILL)
|
||||
return not _pid_exists(pid)
|
||||
except Exception as e:
|
||||
sysLog.debug(e)
|
||||
logSys.debug(e)
|
||||
finally:
|
||||
if f is not None:
|
||||
f.close()
|
||||
return True
|
||||
|
||||
|
||||
class Fail2banClientTest(LogCaptureTestCase):
|
||||
class Fail2banClientServerBase(LogCaptureTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
|
@ -223,6 +221,21 @@ class Fail2banClientTest(LogCaptureTestCase):
|
|||
"""Call after every test case."""
|
||||
LogCaptureTestCase.tearDown(self)
|
||||
|
||||
def _wait_for_srv(self, tmp, ready=True, startparams=None):
|
||||
sock = tmp+"/f2b.sock"
|
||||
# wait for server (socket):
|
||||
ret = Utils.wait_for(lambda: os.path.exists(sock), MAX_WAITTIME)
|
||||
if not ret:
|
||||
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,))
|
||||
|
||||
|
||||
class Fail2banClientTest(Fail2banClientServerBase):
|
||||
|
||||
def testClientUsage(self):
|
||||
self.assertRaises(ExitException, _exec_client,
|
||||
(CLIENT, "-h",))
|
||||
|
@ -232,12 +245,13 @@ class Fail2banClientTest(LogCaptureTestCase):
|
|||
@withtmpdir
|
||||
def testClientStartBackgroundInside(self, tmp):
|
||||
try:
|
||||
# always add "--async" by start inside, should don't fork by async (not replace client with server, just start in new process)
|
||||
# (we can't fork the test cases process):
|
||||
# use once the stock configuration (to test starting also)
|
||||
startparams = _start_params(tmp, True)
|
||||
# start:
|
||||
self.assertRaises(ExitException, _exec_client,
|
||||
(CLIENT, "--async", "-b") + startparams + ("start",))
|
||||
(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:
|
||||
|
@ -245,6 +259,11 @@ class Fail2banClientTest(LogCaptureTestCase):
|
|||
(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:
|
||||
|
@ -260,11 +279,14 @@ class Fail2banClientTest(LogCaptureTestCase):
|
|||
try:
|
||||
global INTERACT
|
||||
startparams = _start_params(tmp)
|
||||
# start (without async in new process):
|
||||
cmd = os.path.join(os.path.join(BIN), CLIENT)
|
||||
# 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)
|
||||
Utils.executeCmd((cmd,) + startparams + ("start",),
|
||||
timeout=MAX_WAITTIME, shell=False, output=False)
|
||||
cmd = cmd + startparams + ("start",)
|
||||
Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True)
|
||||
# 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):
|
||||
|
@ -312,7 +334,7 @@ class Fail2banClientTest(LogCaptureTestCase):
|
|||
# start and wait to end (foreground):
|
||||
logSys.debug("-- start of test worker")
|
||||
phase['start'] = True
|
||||
self.assertRaises(ExitException, _exec_client,
|
||||
self.assertRaises(fail2bancmdline.ExitException, _exec_client,
|
||||
(CLIENT, "-f") + startparams + ("start",))
|
||||
# end :
|
||||
phase['end'] = True
|
||||
|
@ -334,9 +356,10 @@ class Fail2banClientTest(LogCaptureTestCase):
|
|||
# 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):
|
||||
Utils.wait_for(lambda: os.path.exists(tmp+"/f2b.sock"), MAX_WAITTIME)
|
||||
self.assertLogged("Starting communication")
|
||||
# wait for server (socket and ready):
|
||||
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,
|
||||
|
@ -382,15 +405,7 @@ class Fail2banClientTest(LogCaptureTestCase):
|
|||
cntr -= 1
|
||||
|
||||
|
||||
class Fail2banServerTest(LogCaptureTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Call before every test case."""
|
||||
LogCaptureTestCase.setUp(self)
|
||||
|
||||
def tearDown(self):
|
||||
"""Call after every test case."""
|
||||
LogCaptureTestCase.tearDown(self)
|
||||
class Fail2banServerTest(Fail2banClientServerBase):
|
||||
|
||||
def testServerUsage(self):
|
||||
self.assertRaises(ExitException, _exec_server,
|
||||
|
@ -401,15 +416,17 @@ class Fail2banServerTest(LogCaptureTestCase):
|
|||
@withtmpdir
|
||||
def testServerStartBackground(self, tmp):
|
||||
try:
|
||||
# don't add "--async" by start, because if will fork current process by daemonize
|
||||
# (we can't fork the test cases process),
|
||||
# because server started internal communication in new thread use INHERITED as logtarget here:
|
||||
startparams = _start_params(tmp, logtarget="INHERITED")
|
||||
# start:
|
||||
self.assertRaises(ExitException, _exec_server,
|
||||
(SERVER, "-b") + startparams)
|
||||
# to prevent fork of test-cases process, start server in background via command:
|
||||
startparams = _start_params(tmp)
|
||||
# start (in new process, using the same python version):
|
||||
cmd = (sys.executable, os.path.join(os.path.join(BIN), SERVER))
|
||||
logSys.debug('Start %s ...', cmd)
|
||||
cmd = cmd + startparams + ("-b",)
|
||||
Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True)
|
||||
# wait for server (socket and ready):
|
||||
self._wait_for_srv(tmp, True, startparams=cmd)
|
||||
self.assertLogged("Server ready")
|
||||
self.assertLogged("Exit with code 0")
|
||||
self.pruneLog()
|
||||
try:
|
||||
self.assertRaises(ExitException, _exec_server,
|
||||
(SERVER,) + startparams + ("echo", "TEST-ECHO",))
|
||||
|
@ -429,7 +446,7 @@ class Fail2banServerTest(LogCaptureTestCase):
|
|||
# start and wait to end (foreground):
|
||||
logSys.debug("-- start of test worker")
|
||||
phase['start'] = True
|
||||
self.assertRaises(ExitException, _exec_server,
|
||||
self.assertRaises(fail2bancmdline.ExitException, _exec_server,
|
||||
(SERVER, "-f") + startparams + ("start",))
|
||||
# end :
|
||||
phase['end'] = True
|
||||
|
@ -451,9 +468,10 @@ class Fail2banServerTest(LogCaptureTestCase):
|
|||
# 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):
|
||||
Utils.wait_for(lambda: os.path.exists(tmp+"/f2b.sock"), MAX_WAITTIME)
|
||||
self.assertLogged("Starting communication")
|
||||
# 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,
|
||||
|
|
|
@ -54,6 +54,10 @@ 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:
|
||||
os.putenv('PYTHONPATH', os.path.dirname(os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__)))))
|
||||
|
||||
class F2B(optparse.Values):
|
||||
def __init__(self, opts={}):
|
||||
|
@ -343,7 +347,7 @@ class LogCaptureTestCase(unittest.TestCase):
|
|||
if self._old_level <= logging.DEBUG: # so if DEBUG etc -- show them (and log it in travis)!
|
||||
print("")
|
||||
logSys.handlers += self._old_handlers
|
||||
logSys.debug('--'*40)
|
||||
logSys.debug('='*10 + ' %s ' + '='*20, self.id())
|
||||
logSys.setLevel(getattr(logging, 'DEBUG'))
|
||||
|
||||
def tearDown(self):
|
||||
|
|
Loading…
Reference in New Issue