reader bug fix: prevent to silent "load" of not existing jail;

coverage of test cases increased;
pull/1483/head
sebres 2016-02-15 20:41:20 +01:00
parent 22576d7150
commit 060ea085f4
5 changed files with 47 additions and 24 deletions

View File

@ -208,7 +208,7 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
# Or it is a dict: # Or it is a dict:
# {name: [type, default], ...} # {name: [type, default], ...}
def getOptions(self, sec, options, pOptions=None): def getOptions(self, sec, options, pOptions=None, shouldExist=False):
values = dict() values = dict()
for optname in options: for optname in options:
if isinstance(options, (list,tuple)): if isinstance(options, (list,tuple)):
@ -229,6 +229,8 @@ class ConfigReaderUnshared(SafeConfigParserWithIncludes):
continue continue
values[optname] = v values[optname] = v
except NoSectionError, e: except NoSectionError, e:
if shouldExist:
raise
# No "Definition" section or wrong basedir # No "Definition" section or wrong basedir
logSys.error(e) logSys.error(e)
values[optname] = optvalue values[optname] = optvalue

View File

@ -64,7 +64,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
output("and bans the corresponding IP addresses using firewall rules.") output("and bans the corresponding IP addresses using firewall rules.")
output("") output("")
def __sigTERMhandler(self, signum, frame): def __sigTERMhandler(self, signum, frame): # pragma: no cover
# Print a new line because we probably come from wait # Print a new line because we probably come from wait
output("") output("")
logSys.warning("Caught signal %d. Exiting" % signum) logSys.warning("Caught signal %d. Exiting" % signum)
@ -141,7 +141,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
logSys.error("Failed to access socket path: %s." logSys.error("Failed to access socket path: %s."
" Is fail2ban running?", " Is fail2ban running?",
self._conf["socket"]) self._conf["socket"])
except Exception as e: except Exception as e: # pragma: no cover
logSys.error("Exception while checking socket access: %s", logSys.error("Exception while checking socket access: %s",
self._conf["socket"]) self._conf["socket"])
logSys.error(e) logSys.error(e)
@ -165,7 +165,7 @@ class Fail2banClient(Fail2banCmdLine, Thread):
"There is no directory %s to contain the socket file %s." "There is no directory %s to contain the socket file %s."
% (socket_dir, self._conf["socket"])) % (socket_dir, self._conf["socket"]))
return None return None
if not os.access(socket_dir, os.W_OK | os.X_OK): if not os.access(socket_dir, os.W_OK | os.X_OK): # pragma: no cover
logSys.error( logSys.error(
"Directory %s exists but not accessible for writing" "Directory %s exists but not accessible for writing"
% (socket_dir,)) % (socket_dir,))
@ -204,9 +204,9 @@ class Fail2banClient(Fail2banCmdLine, Thread):
# Start server direct here in main thread (not fork): # Start server direct here in main thread (not fork):
self._server = Fail2banServer.startServerDirect(self._conf, False) self._server = Fail2banServer.startServerDirect(self._conf, False)
except ExitException: except ExitException: # pragma: no cover
pass pass
except Exception as e: except Exception as e: # pragma: no cover
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: if self._conf["verbose"] > 1:
@ -259,7 +259,7 @@ 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'])
if not self.__waitOnServer(False): if not self.__waitOnServer(False): # pragma: no cover
logSys.error("Could not stop server") logSys.error("Could not stop server")
return False 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:
@ -298,12 +298,12 @@ 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
if not self.__waitOnServer(): if not self.__waitOnServer(): # pragma: no cover
logSys.error("Could not find server, waiting failed") logSys.error("Could not find server, waiting failed")
return False return False
# Configure the server # Configure the server
self.__processCmd(*args) self.__processCmd(*args)
except ServerExecutionException as e: except ServerExecutionException as e: # pragma: no cover
if self._conf["verbose"] > 1: if self._conf["verbose"] > 1:
logSys.exception(e) logSys.exception(e)
logSys.error("Could not start server. Maybe an old " logSys.error("Could not start server. Maybe an old "
@ -385,12 +385,12 @@ class Fail2banClient(Fail2banCmdLine, Thread):
elif not cmd == "": elif not cmd == "":
try: try:
self.__processCommand(shlex.split(cmd)) self.__processCommand(shlex.split(cmd))
except Exception, e: except Exception, e: # pragma: no cover
if self._conf["verbose"] > 1: if self._conf["verbose"] > 1:
logSys.exception(e) logSys.exception(e)
else: else:
logSys.error(e) logSys.error(e)
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt): # pragma: no cover
output("") output("")
raise raise
# Single command mode # Single command mode

View File

@ -56,7 +56,7 @@ 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 as e: except Exception as e: # pragma: no cover
try: try:
if server: if server:
server.quit() server.quit()
@ -77,7 +77,7 @@ class Fail2banServer(Fail2banCmdLine):
# 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
if frk: if frk: # pragma: no cover
pid = os.fork() 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: if pid == 0:
@ -108,22 +108,20 @@ class Fail2banServer(Fail2banCmdLine):
exe = sys.executable exe = sys.executable
args[0:0] = [exe] 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: # pragma: no cover
return os.execv(exe, args) os.execv(exe, args)
else: else:
# use P_WAIT instead of P_NOWAIT (to prevent defunct-zomby process), it startet as daemon, so parent exit fast after fork): # use P_WAIT instead of P_NOWAIT (to prevent defunct-zomby process), it startet as daemon, so parent exit fast after fork):
ret = os.spawnv(os.P_WAIT, exe, args) ret = os.spawnv(os.P_WAIT, exe, args)
if ret != 0: if ret != 0: # pragma: no cover
raise OSError(ret, "Unknown error by executing server %r with %r" % (args[1], exe)) raise OSError(ret, "Unknown error by executing server %r with %r" % (args[1], exe))
return 0
except OSError as e: # pragma: no cover except OSError as e: # pragma: no cover
if not frk: #not PRODUCTION: if not frk: #not PRODUCTION:
raise raise
# 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: # pragma: no cover
return os.execvp(SERVER, args) os.execvp(SERVER, args)
return pid
@staticmethod @staticmethod
def getServerPath(): def getServerPath():
@ -189,7 +187,7 @@ class Fail2banServer(Fail2banCmdLine):
pid = os.getpid() pid = os.getpid()
server = Fail2banServer.startServerDirect(self._conf, background) server = Fail2banServer.startServerDirect(self._conf, background)
# If forked - just exit other processes # If forked - just exit other processes
if pid != os.getpid(): if pid != os.getpid(): # pragma: no cover
os._exit(0) os._exit(0)
if cli: if cli:
cli._server = server cli._server = server
@ -198,7 +196,7 @@ class Fail2banServer(Fail2banCmdLine):
if not async and cli: if not async and cli:
Utils.wait_for(lambda: phase.get('done', None) is not None, self._conf["timeout"]) Utils.wait_for(lambda: phase.get('done', None) is not None, self._conf["timeout"])
if not phase.get('done', False): if not phase.get('done', False):
if server: if server: # pragma: no cover
server.quit() server.quit()
exit(-1) exit(-1)
logSys.debug('Starting server done') logSys.debug('Starting server done')
@ -206,7 +204,9 @@ class Fail2banServer(Fail2banCmdLine):
except Exception, e: except Exception, e:
if self._conf["verbose"] > 1: if self._conf["verbose"] > 1:
logSys.exception(e) logSys.exception(e)
if server: else:
logSys.error(e)
if server: # pragma: no cover
server.quit() server.quit()
exit(-1) exit(-1)

View File

@ -114,7 +114,7 @@ class JailReader(ConfigReader):
defsec["fail2ban_version"] = version defsec["fail2ban_version"] = version
# Read first options only needed for merge defaults ('known/...' from filter): # Read first options only needed for merge defaults ('known/...' from filter):
self.__opts = ConfigReader.getOptions(self, self.__name, opts1st) self.__opts = ConfigReader.getOptions(self, self.__name, opts1st, shouldExist=True)
if not self.__opts: if not self.__opts:
return False return False

View File

@ -347,6 +347,21 @@ class Fail2banClientTest(Fail2banClientServerBase):
self.assertLogged("Server ready") self.assertLogged("Server ready")
self.assertLogged("Exit with code 0") self.assertLogged("Exit with code 0")
self.pruneLog() 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: finally:
self.pruneLog() self.pruneLog()
# stop: # stop:
@ -425,6 +440,12 @@ class Fail2banClientTest(Fail2banClientServerBase):
self.assertLogged("There is no directory " + tmp+"/miss" + " to contain the socket file") self.assertLogged("There is no directory " + tmp+"/miss" + " to contain the socket file")
self.pruneLog() self.pruneLog()
## not running
self.assertRaises(FailExitException, _exec_client,
(CLIENT, "-c", tmp+"/config", "-s", tmp+"/f2b.sock", "reload",))
self.assertLogged("Could not find server")
self.pruneLog()
## already exists: ## already exists:
open(tmp+"/f2b.sock", 'a').close() open(tmp+"/f2b.sock", 'a').close()
self.assertRaises(FailExitException, _exec_client, self.assertRaises(FailExitException, _exec_client,