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