starting of the server (and client/server communication behavior during start and daemonize) completely rewritten:

- client/server functionality moved away from bin and using now the common interface (introduced in fail2bancmdline);
  - start in foreground fixed;
  - server can act as client corresponding command line;
  - command "restart" added: in opposite to "reload" in reality restarts the server (new process);
  - several client/server bugs during starting process fixed.
pull/1483/head
sebres 2016-02-11 08:56:12 +01:00
parent 556ddaabd7
commit 5a053f4b74
10 changed files with 745 additions and 498 deletions

View File

@ -156,8 +156,11 @@ fail2ban/client/configparserinc.py
fail2ban/client/configreader.py fail2ban/client/configreader.py
fail2ban/client/configurator.py fail2ban/client/configurator.py
fail2ban/client/csocket.py fail2ban/client/csocket.py
fail2ban/client/fail2banclient.py
fail2ban/client/fail2bancmdline.py
fail2ban/client/fail2banreader.py fail2ban/client/fail2banreader.py
fail2ban/client/fail2banregex.py fail2ban/client/fail2banregex.py
fail2ban/client/fail2banserver.py
fail2ban/client/filterreader.py fail2ban/client/filterreader.py
fail2ban/client/__init__.py fail2ban/client/__init__.py
fail2ban/client/jailreader.py fail2ban/client/jailreader.py

37
bin/fail2ban-client Executable file
View File

@ -0,0 +1,37 @@
#!/usr/bin/python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
Fail2Ban reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
This tools starts/stops fail2ban server or does client/server communication,
to change/read parameters of the server or jails.
"""
__author__ = "Fail2Ban Developers"
__copyright__ = "Copyright (c) 2004-2008 Cyril Jaquier, 2012-2014 Yaroslav Halchenko, 2014-2016 Serg G. Brester"
__license__ = "GPL"
from fail2ban.client.fail2banclient import exec_command_line
if __name__ == "__main__":
exec_command_line()

37
bin/fail2ban-server Executable file
View File

@ -0,0 +1,37 @@
#!/usr/bin/python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
Fail2Ban reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules.
This tools starts/stops fail2ban server or does client/server communication,
to change/read parameters of the server or jails.
"""
__author__ = "Fail2Ban Developers"
__copyright__ = "Copyright (c) 2004-2008 Cyril Jaquier, 2012-2014 Yaroslav Halchenko, 2014-2016 Serg G. Brester"
__license__ = "GPL"
from fail2ban.client.fail2banserver import exec_command_line
if __name__ == "__main__":
exec_command_line()

View File

@ -68,6 +68,8 @@ class Beautifier:
msg = "Added jail " + response msg = "Added jail " + response
elif inC[0] == "flushlogs": elif inC[0] == "flushlogs":
msg = "logs: " + response msg = "logs: " + response
elif inC[0] == "echo":
msg = ' '.join(msg)
elif inC[0:1] == ['status']: elif inC[0:1] == ['status']:
if len(inC) > 1: if len(inC) > 1:
# Display information # Display information

View File

@ -1,7 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet : # vi: set ft=python sts=4 ts=4 sw=4 noet :
#
# This file is part of Fail2Ban. # This file is part of Fail2Ban.
# #
# Fail2Ban is free software; you can redistribute it and/or modify # Fail2Ban is free software; you can redistribute it and/or modify
@ -17,99 +17,38 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software # along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
__author__ = "Fail2Ban Developers"
__author__ = "Cyril Jaquier" __copyright__ = "Copyright (c) 2004-2008 Cyril Jaquier, 2012-2014 Yaroslav Halchenko, 2014-2016 Serg G. Brester"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import getopt
import logging
import os import os
import pickle
import re
import shlex import shlex
import signal import signal
import socket import socket
import string
import sys import sys
import time import time
from fail2ban.version import version from threading import Thread
from fail2ban.protocol import printFormatted
from fail2ban.client.csocket import CSocket
from fail2ban.client.configurator import Configurator
from fail2ban.client.beautifier import Beautifier
from fail2ban.helpers import getLogger
# Gets the instance of the logger. from ..version import version
logSys = getLogger("fail2ban") from .csocket import CSocket
from .beautifier import Beautifier
from .fail2bancmdline import Fail2banCmdLine, logSys, exit
## ##
# #
# @todo This class needs cleanup. # @todo This class needs cleanup.
class Fail2banClient: class Fail2banClient(Fail2banCmdLine, Thread):
SERVER = "fail2ban-server"
PROMPT = "fail2ban> " PROMPT = "fail2ban> "
def __init__(self): def __init__(self):
self.__server = None Fail2banCmdLine.__init__(self)
self.__argv = None Thread.__init__(self)
self.__stream = None self._alive = True
self.__configurator = Configurator() self._server = None
self.__conf = dict() self._beautifier = None
self.__conf["conf"] = "/etc/fail2ban"
self.__conf["dump"] = False
self.__conf["force"] = False
self.__conf["background"] = True
self.__conf["verbose"] = 1
self.__conf["interactive"] = False
self.__conf["socket"] = None
self.__conf["pidfile"] = None
def dispVersion(self):
print "Fail2Ban v" + version
print
print "Copyright (c) 2004-2008 Cyril Jaquier, 2008- Fail2Ban Contributors"
print "Copyright of modifications held by their respective authors."
print "Licensed under the GNU General Public License v2 (GPL)."
print
print "Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>."
print "Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>."
def dispUsage(self):
""" Prints Fail2Ban command line options and exits
"""
print "Usage: "+self.__argv[0]+" [OPTIONS] <COMMAND>"
print
print "Fail2Ban v" + version + " reads log file that contains password failure report"
print "and bans the corresponding IP addresses using firewall rules."
print
print "Options:"
print " -c <DIR> configuration directory"
print " -s <FILE> socket path"
print " -p <FILE> pidfile path"
print " --loglevel <LEVEL> logging level"
print " --logtarget <FILE>|STDOUT|STDERR|SYSLOG"
print " --syslogsocket auto|file"
print " -d dump configuration. For debugging"
print " -i interactive mode"
print " -v increase verbosity"
print " -q decrease verbosity"
print " -x force execution of the server (remove socket file)"
print " -b start server in background (default)"
print " -f start server in foreground"
print " -h, --help display this help message"
print " -V, --version print the version"
print
print "Command:"
# Prints the protocol
printFormatted()
print
print "Report bugs to https://github.com/fail2ban/fail2ban/issues"
def dispInteractive(self): def dispInteractive(self):
print "Fail2Ban v" + version + " reads log file that contains password failure report" print "Fail2Ban v" + version + " reads log file that contains password failure report"
@ -120,58 +59,32 @@ class Fail2banClient:
# Print a new line because we probably come from wait # Print a new line because we probably come from wait
print print
logSys.warning("Caught signal %d. Exiting" % signum) logSys.warning("Caught signal %d. Exiting" % signum)
sys.exit(-1) exit(-1)
def __getCmdLineOptions(self, optList):
""" Gets the command line options
"""
for opt in optList:
if opt[0] == "-c":
self.__conf["conf"] = opt[1]
elif opt[0] == "-s":
self.__conf["socket"] = opt[1]
elif opt[0] == "-p":
self.__conf["pidfile"] = opt[1]
elif opt[0].startswith("--log") or opt[0].startswith("--sys"):
self.__conf[ opt[0][2:] ] = opt[1]
elif opt[0] == "-d":
self.__conf["dump"] = True
elif opt[0] == "-v":
self.__conf["verbose"] = self.__conf["verbose"] + 1
elif opt[0] == "-q":
self.__conf["verbose"] = self.__conf["verbose"] - 1
elif opt[0] == "-x":
self.__conf["force"] = True
elif opt[0] == "-i":
self.__conf["interactive"] = True
elif opt[0] == "-b":
self.__conf["background"] = True
elif opt[0] == "-f":
self.__conf["background"] = False
elif opt[0] in ["-h", "--help"]:
self.dispUsage()
sys.exit(0)
elif opt[0] in ["-V", "--version"]:
self.dispVersion()
sys.exit(0)
def __ping(self): def __ping(self):
return self.__processCmd([["ping"]], False) return self.__processCmd([["ping"]], False)
def __processCmd(self, cmd, showRet = True): @property
def beautifier(self):
if self._beautifier:
return self._beautifier
self._beautifier = Beautifier()
return self._beautifier
def __processCmd(self, cmd, showRet=True):
client = None client = None
try: try:
beautifier = Beautifier() beautifier = self.beautifier
streamRet = True streamRet = True
for c in cmd: for c in cmd:
beautifier.setInputCmd(c) beautifier.setInputCmd(c)
try: try:
if not client: if not client:
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 : " + `ret[1]`)
if showRet: if showRet or c[0] == 'echo':
print beautifier.beautify(ret[1]) print beautifier.beautify(ret[1])
else: else:
logSys.error("NOK: " + `ret[1].args`) logSys.error("NOK: " + `ret[1].args`)
@ -179,38 +92,126 @@ class Fail2banClient:
print beautifier.beautifyError(ret[1]) print beautifier.beautifyError(ret[1])
streamRet = False streamRet = False
except socket.error: except socket.error:
if showRet: if showRet or self._conf["verbose"] > 1:
self.__logSocketError() self.__logSocketError()
return False return False
except Exception, e: except Exception, e:
if showRet: if showRet or self._conf["verbose"] > 1:
logSys.error(e) logSys.error(e)
return False return False
finally: finally:
if client: if client:
client.close() client.close()
if showRet or c[0] == 'echo':
sys.stdout.flush()
return streamRet return streamRet
def __logSocketError(self): def __logSocketError(self):
try: try:
if os.access(self.__conf["socket"], os.F_OK): if os.access(self._conf["socket"], os.F_OK):
# This doesn't check if path is a socket, # This doesn't check if path is a socket,
# but socket.error should be raised # but socket.error should be raised
if os.access(self.__conf["socket"], os.W_OK): if os.access(self._conf["socket"], os.W_OK):
# Permissions look good, but socket.error was raised # Permissions look good, but socket.error was raised
logSys.error("Unable to contact server. Is it running?") logSys.error("Unable to contact server. Is it running?")
else: else:
logSys.error("Permission denied to socket: %s," logSys.error("Permission denied to socket: %s,"
" (you must be root)", self.__conf["socket"]) " (you must be root)", self._conf["socket"])
else: else:
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:
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)
##
def __prepareStartServer(self):
if self.__ping():
logSys.error("Server already running")
return None
# Read the config
ret, stream = self.readConfig()
# Do not continue if configuration is not 100% valid
if not ret:
return None
# verify that directory for the socket file exists
socket_dir = os.path.dirname(self._conf["socket"])
if not os.path.exists(socket_dir):
logSys.error(
"There is no directory %s to contain the socket file %s."
% (socket_dir, self._conf["socket"]))
return None
if not os.access(socket_dir, os.W_OK | os.X_OK):
logSys.error(
"Directory %s exists but not accessible for writing"
% (socket_dir,))
return None
# Check already running
if not self._conf["force"] and os.path.exists(self._conf["socket"]):
logSys.error("Fail2ban seems to be in unexpected state (not running but the socket exists)")
return None
stream.append(['echo', 'Server ready'])
return stream
##
def __startServer(self, background=True):
from .fail2banserver import Fail2banServer
stream = self.__prepareStartServer()
self._alive = True
if not stream:
return False
# Start the server or just initialize started one:
try:
if background:
# Start server daemon as fork of client process:
Fail2banServer.startServerAsync(self._conf)
# Send config stream to server:
if not self.__processStartStreamAfterWait(stream, False):
return False
else:
# In foreground mode we should make server/client communication in different threads:
Thread(target=Fail2banClient.__processStartStreamAfterWait, args=(self, stream, False)).start()
# Mark current (main) thread as daemon:
self.setDaemon(True)
# Start server direct here in main thread (not fork):
self._server = Fail2banServer.startServerDirect(self._conf, False)
except Exception as e:
print
logSys.error("Exception while starting server foreground")
logSys.error(e)
finally:
self._alive = False
return True
##
def configureServer(self, async=True, phase=None):
# if asynchron start this operation in the new thread:
if async:
return Thread(target=Fail2banClient.configureServer, args=(self, False, phase)).start()
# prepare: read config, check configuration is valid, etc.:
if phase is not None:
phase['start'] = True
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)
if not stream:
return False
# configure server with config stream:
ret = self.__processStartStreamAfterWait(stream, False)
if phase is not None:
phase['done'] = ret
return ret
## ##
# Process a command line. # Process a command line.
# #
@ -219,253 +220,101 @@ class Fail2banClient:
def __processCommand(self, cmd): def __processCommand(self, cmd):
if len(cmd) == 1 and cmd[0] == "start": if len(cmd) == 1 and cmd[0] == "start":
if self.__ping():
logSys.error("Server already running") ret = self.__startServer(self._conf["background"])
if not ret:
return False return False
else: return ret
# Read the config
ret = self.__readConfig()
# Do not continue if configuration is not 100% valid
if not ret:
return False
# verify that directory for the socket file exists
socket_dir = os.path.dirname(self.__conf["socket"])
if not os.path.exists(socket_dir):
logSys.error(
"There is no directory %s to contain the socket file %s."
% (socket_dir, self.__conf["socket"]))
return False
if not os.access(socket_dir, os.W_OK | os.X_OK):
logSys.error(
"Directory %s exists but not accessible for writing"
% (socket_dir,))
return False
# Check already running elif len(cmd) == 1 and cmd[0] == "restart":
if not self.__conf["force"] and os.path.exists(self.__conf["socket"]):
logSys.error("Fail2ban seems to be in unexpected state (not running but socket exists)")
return False
# Start the server if self._conf.get("interactive", False):
t = None print(' ## stop ... ')
if self.__conf["background"]: self.__processCommand(['stop'])
# Start server daemon as fork of client process: self.__waitOnServer(False)
self.__startServerAsync() # in interactive mode reset config, to make full-reload if there something changed:
# Send config stream to server: if self._conf.get("interactive", False):
return self.__processStartStreamAfterWait() print(' ## load configuration ... ')
self.resetConf()
ret = self.initCmdLine(self._argv)
if ret is not None:
return ret
if self._conf.get("interactive", False):
print(' ## start ... ')
return self.__processCommand(['start'])
elif len(cmd) >= 1 and cmd[0] == "reload":
if self.__ping():
if len(cmd) == 1:
jail = 'all'
ret, stream = self.readConfig()
else: else:
# In foreground mode we should start server/client communication in other thread: jail = cmd[1]
from threading import Thread ret, stream = self.readConfig(jail)
t = Thread(target=Fail2banClient.__processStartStreamAfterWait, args=(self,))
t.start()
# Start server direct here in main thread:
try:
self.__startServerDirect()
except KeyboardInterrupt:
None
return True
elif len(cmd) == 1 and cmd[0] == "reload":
if self.__ping():
ret = self.__readConfig()
# Do not continue if configuration is not 100% valid
if not ret:
return False
self.__processCmd([['stop', 'all']], False)
# Configure the server
return self.__processCmd(self.__stream, False)
else:
logSys.error("Could not find server")
return False
elif len(cmd) == 2 and cmd[0] == "reload":
if self.__ping():
jail = cmd[1]
ret = self.__readConfig(jail)
# Do not continue if configuration is not 100% valid # Do not continue if configuration is not 100% valid
if not ret: if not ret:
return False return False
self.__processCmd([['stop', jail]], False) self.__processCmd([['stop', jail]], False)
# Configure the server # Configure the server
return self.__processCmd(self.__stream, False) return self.__processCmd(stream, True)
else: else:
logSys.error("Could not find server") logSys.error("Could not find server")
return False return False
else: else:
return self.__processCmd([cmd]) return self.__processCmd([cmd])
def __processStartStreamAfterWait(self): def __processStartStreamAfterWait(self, *args):
try: try:
# Wait for the server to start # Wait for the server to start
self.__waitOnServer() self.__waitOnServer()
# Configure the server # Configure the server
self.__processCmd(self.__stream, False) self.__processCmd(*args)
except ServerExecutionException: except ServerExecutionException:
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 "
"you used fail2ban-client to start the " "you used fail2ban-client to start the "
"server, adding the -x option will do it") "server, adding the -x option will do it")
if not self.__conf["background"]: if self._server:
self.__server.quit() self._server.quit()
sys.exit(-1) exit(-1)
return False return False
return True return True
def __waitOnServer(self, alive=True, maxtime=30):
## # Wait for the server to start (the server has 30 seconds to answer ping)
# Start Fail2Ban server in main thread without fork (foreground). starttime = time.time()
# with VisualWait(self._conf["verbose"]) as vis:
# Start the Fail2ban server in foreground (daemon mode or not). while self._alive and not self.__ping() == alive or (
not alive and os.path.exists(self._conf["socket"])
def __startServerDirect(self): ):
from fail2ban.server.server import Server now = time.time()
try: # Wonderful visual :)
self.__server = Server(False) if now > starttime + 1:
self.__server.start(self.__conf["socket"], vis.heartbeat()
self.__conf["pidfile"], self.__conf["force"], # f end time reached:
conf=self.__conf) if now - starttime >= maxtime:
except Exception, e: raise ServerExecutionException("Failed to start server")
logSys.exception(e) time.sleep(0.1)
if self.__server:
self.__server.quit()
sys.exit(-1)
##
# Start Fail2Ban server.
#
# Start the Fail2ban server in daemon mode.
def __startServerAsync(self):
# Forks the current process.
pid = os.fork()
if pid == 0:
args = list()
args.append(self.SERVER)
# Set the socket path.
args.append("-s")
args.append(self.__conf["socket"])
# Set the pidfile
args.append("-p")
args.append(self.__conf["pidfile"])
# Force the execution if needed.
if self.__conf["force"]:
args.append("-x")
# Start in background as requested.
args.append("-b")
try:
# Use the current directory.
exe = os.path.abspath(os.path.join(sys.path[0], self.SERVER))
logSys.debug("Starting %r with args %r" % (exe, args))
os.execv(exe, args)
except OSError:
try:
# Use the PATH env.
logSys.warning("Initial start attempt failed. Starting %r with the same args" % (self.SERVER,))
os.execvp(self.SERVER, args)
except OSError:
logSys.error("Could not start %s" % self.SERVER)
os.exit(-1)
def __waitOnServer(self):
# Wait for the server to start
cnt = 0
if self.__conf["verbose"] > 1:
pos = 0
delta = 1
mask = "[ ]"
while not self.__ping():
# Wonderful visual :)
if self.__conf["verbose"] > 1:
pos += delta
sys.stdout.write("\rINFO " + mask[:pos] + '#' + mask[pos+1:] +
" Waiting on the server...")
sys.stdout.flush()
if pos > len(mask)-3:
delta = -1
elif pos < 2:
delta = 1
# The server has 30 seconds to start.
if cnt >= 300:
if self.__conf["verbose"] > 1:
sys.stdout.write('\n')
raise ServerExecutionException("Failed to start server")
time.sleep(0.1)
cnt += 1
if self.__conf["verbose"] > 1:
sys.stdout.write('\n')
def start(self, argv): def start(self, argv):
# Command line options
self.__argv = argv
# Install signal handlers # Install signal handlers
signal.signal(signal.SIGTERM, self.__sigTERMhandler) signal.signal(signal.SIGTERM, self.__sigTERMhandler)
signal.signal(signal.SIGINT, self.__sigTERMhandler) signal.signal(signal.SIGINT, self.__sigTERMhandler)
# Reads the command line options. # Command line options
try: if self._argv is None:
cmdOpts = 'hc:s:p:xfbdviqV' ret = self.initCmdLine(argv)
cmdLongOpts = ['loglevel', 'logtarget', 'syslogsocket', 'help', 'version'] if ret is not None:
optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts) return ret
except getopt.GetoptError:
self.dispUsage()
return False
self.__getCmdLineOptions(optList) # Commands
args = self._args
verbose = self.__conf["verbose"]
if verbose <= 0:
logSys.setLevel(logging.ERROR)
elif verbose == 1:
logSys.setLevel(logging.WARNING)
elif verbose == 2:
logSys.setLevel(logging.INFO)
elif verbose == 3:
logSys.setLevel(logging.DEBUG)
else:
logSys.setLevel(logging.HEAVYDEBUG)
# Add the default logging handler to dump to stderr
logout = logging.StreamHandler(sys.stderr)
# set a format which is simpler for console use
formatter = logging.Formatter('%(levelname)-6s %(message)s')
# tell the handler to use this format
logout.setFormatter(formatter)
logSys.addHandler(logout)
# Set the configuration path
self.__configurator.setBaseDir(self.__conf["conf"])
# Set socket path
self.__configurator.readEarly()
conf = self.__configurator.getEarlyOptions()
if self.__conf["socket"] is None:
self.__conf["socket"] = conf["socket"]
if self.__conf["pidfile"] is None:
self.__conf["pidfile"] = conf["pidfile"]
if self.__conf.get("logtarget", None) is None:
self.__conf["logtarget"] = conf["logtarget"]
if self.__conf.get("loglevel", None) is None:
self.__conf["loglevel"] = conf["loglevel"]
if self.__conf.get("syslogsocket", None) is None:
self.__conf["syslogsocket"] = conf["syslogsocket"]
logSys.info("Using socket file %s", self.__conf["socket"])
logSys.info("Using pid file %s, [%s] logging to %s",
self.__conf["pidfile"], self.__conf["loglevel"], self.__conf["logtarget"])
if self.__conf["dump"]:
ret = self.__readConfig()
self.dumpConfig(self.__stream)
return ret
# Interactive mode # Interactive mode
if self.__conf["interactive"]: if self._conf.get("interactive", False):
try: try:
import readline import readline
except ImportError: except ImportError:
@ -500,35 +349,56 @@ class Fail2banClient:
return False return False
return self.__processCommand(args) return self.__processCommand(args)
def __readConfig(self, jail=None):
# Read the configuration
# TODO: get away from stew of return codes and exception
# handling -- handle via exceptions
try:
self.__configurator.Reload()
self.__configurator.readAll()
ret = self.__configurator.getOptions(jail)
self.__configurator.convertToProtocol()
self.__stream = self.__configurator.getConfigStream()
except Exception, e:
logSys.error("Failed during configuration: %s" % e)
ret = False
return ret
@staticmethod
def dumpConfig(cmd):
for c in cmd:
print c
return True
class ServerExecutionException(Exception): class ServerExecutionException(Exception):
pass pass
if __name__ == "__main__": # pragma: no cover - can't test main
##
# Wonderful visual :)
#
class _VisualWait:
pos = 0
delta = 1
maxpos = 10
def __enter__(self):
return self
def __exit__(self, *args):
if self.pos:
sys.stdout.write('\r'+(' '*(35+self.maxpos))+'\r')
sys.stdout.flush()
def heartbeat(self):
if not self.pos:
sys.stdout.write("\nINFO [#" + (' '*self.maxpos) + "] Waiting on the server...\r\x1b[8C")
self.pos += self.delta
if self.delta > 0:
s = " #\x1b[1D" if self.pos > 1 else "# \x1b[2D"
else:
s = "\x1b[1D# \x1b[2D"
sys.stdout.write(s)
sys.stdout.flush()
if self.pos > self.maxpos:
self.delta = -1
elif self.pos < 2:
self.delta = 1
class _NotVisualWait:
def __enter__(self):
return self
def __exit__(self, *args):
pass
def heartbeat(self):
pass
def VisualWait(verbose):
return _VisualWait() if verbose > 1 else _NotVisualWait()
def exec_command_line(): # pragma: no cover - can't test main
client = Fail2banClient() client = Fail2banClient()
# Exit with correct return value # Exit with correct return value
if client.start(sys.argv): if client.start(sys.argv):
sys.exit(0) exit(0)
else: else:
sys.exit(-1) exit(-1)

View File

@ -0,0 +1,247 @@
#!/usr/bin/python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet :
#
# This file is part of Fail2Ban.
#
# Fail2Ban is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Fail2Ban is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
__author__ = "Fail2Ban Developers"
__copyright__ = "Copyright (c) 2004-2008 Cyril Jaquier, 2012-2014 Yaroslav Halchenko, 2014-2016 Serg G. Brester"
__license__ = "GPL"
import getopt
import logging
import os
import sys
from ..version import version
from ..protocol import printFormatted
from ..helpers import getLogger
# Gets the instance of the logger.
logSys = getLogger("fail2ban")
CONFIG_PARAMS = ("socket", "pidfile", "logtarget", "loglevel", "syslogsocket",)
class Fail2banCmdLine():
def __init__(self):
self._argv = self._args = None
self._configurator = None
self.resetConf()
def resetConf(self):
self._conf = {
"async": False,
"conf": "/etc/fail2ban",
"force": False,
"background": True,
"verbose": 1,
"socket": None,
"pidfile": None
}
@property
def configurator(self):
if self._configurator:
return self._configurator
# New configurator
from .configurator import Configurator
self._configurator = Configurator()
# Set the configuration path
self._configurator.setBaseDir(self._conf["conf"])
return self._configurator
def applyMembers(self, obj):
for o in obj.__dict__:
self.__dict__[o] = obj.__dict__[o]
def dispVersion(self):
print "Fail2Ban v" + version
print
print "Copyright (c) 2004-2008 Cyril Jaquier, 2008- Fail2Ban Contributors"
print "Copyright of modifications held by their respective authors."
print "Licensed under the GNU General Public License v2 (GPL)."
print
print "Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>."
print "Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>."
def dispUsage(self):
""" Prints Fail2Ban command line options and exits
"""
caller = os.path.basename(self._argv[0])
print "Usage: "+caller+" [OPTIONS]" + (" <COMMAND>" if not caller.endswith('server') else "")
print
print "Fail2Ban v" + version + " reads log file that contains password failure report"
print "and bans the corresponding IP addresses using firewall rules."
print
print "Options:"
print " -c <DIR> configuration directory"
print " -s <FILE> socket path"
print " -p <FILE> pidfile path"
print " --loglevel <LEVEL> logging level"
print " --logtarget <FILE>|STDOUT|STDERR|SYSLOG"
print " --syslogsocket auto|<FILE>"
print " -d dump configuration. For debugging"
print " -i interactive mode"
print " -v increase verbosity"
print " -q decrease verbosity"
print " -x force execution of the server (remove socket file)"
print " -b start server in background (default)"
print " -f start server in foreground"
print " --async start server in async mode (for internal usage only, don't read configuration)"
print " -h, --help display this help message"
print " -V, --version print the version"
if not caller.endswith('server'):
print
print "Command:"
# Prints the protocol
printFormatted()
print
print "Report bugs to https://github.com/fail2ban/fail2ban/issues"
def __getCmdLineOptions(self, optList):
""" Gets the command line options
"""
for opt in optList:
o = opt[0]
if o == "--async":
self._conf["async"] = True
if o == "-c":
self._conf["conf"] = opt[1]
elif o == "-s":
self._conf["socket"] = opt[1]
elif o == "-p":
self._conf["pidfile"] = opt[1]
elif o.startswith("--log") or o.startswith("--sys"):
self._conf[ o[2:] ] = opt[1]
elif o == "-d":
self._conf["dump"] = True
elif o == "-v":
self._conf["verbose"] += 1
elif o == "-q":
self._conf["verbose"] -= 1
elif o == "-x":
self._conf["force"] = True
elif o == "-i":
self._conf["interactive"] = True
elif o == "-b":
self._conf["background"] = True
elif o == "-f":
self._conf["background"] = False
elif o in ["-h", "--help"]:
self.dispUsage()
exit(0)
elif o in ["-V", "--version"]:
self.dispVersion()
exit(0)
def initCmdLine(self, argv):
# First time?
initial = (self._argv is None)
# Command line options
self._argv = argv
# Reads the command line options.
try:
cmdOpts = 'hc:s:p:xfbdviqV'
cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'async', 'help', 'version']
optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts)
except getopt.GetoptError:
self.dispUsage()
exit(-1)
self.__getCmdLineOptions(optList)
if initial:
verbose = self._conf["verbose"]
if verbose <= 0:
logSys.setLevel(logging.ERROR)
elif verbose == 1:
logSys.setLevel(logging.WARNING)
elif verbose == 2:
logSys.setLevel(logging.INFO)
elif verbose == 3:
logSys.setLevel(logging.DEBUG)
else:
logSys.setLevel(logging.HEAVYDEBUG)
# Add the default logging handler to dump to stderr
logout = logging.StreamHandler(sys.stderr)
# set a format which is simpler for console use
formatter = logging.Formatter('%(levelname)-6s %(message)s')
# tell the handler to use this format
logout.setFormatter(formatter)
logSys.addHandler(logout)
# Set expected parameters (like socket, pidfile, etc) from configuration,
# if those not yet specified, in which read configuration only if needed here:
conf = None
for o in CONFIG_PARAMS:
if self._conf.get(o, None) is None:
if not conf:
self.configurator.readEarly()
conf = self.configurator.getEarlyOptions()
self._conf[o] = conf[o]
logSys.info("Using socket file %s", self._conf["socket"])
logSys.info("Using pid file %s, [%s] logging to %s",
self._conf["pidfile"], self._conf["loglevel"], self._conf["logtarget"])
if self._conf.get("dump", False):
ret, stream = self.readConfig()
self.dumpConfig(stream)
return ret
# Nothing to do here, process in client/server
return None
def readConfig(self, jail=None):
# Read the configuration
# TODO: get away from stew of return codes and exception
# handling -- handle via exceptions
stream = None
try:
self.configurator.Reload()
self.configurator.readAll()
ret = self.configurator.getOptions(jail)
self.configurator.convertToProtocol()
stream = self.configurator.getConfigStream()
except Exception, e:
logSys.error("Failed during configuration: %s" % e)
ret = False
return ret, stream
@staticmethod
def dumpConfig(cmd):
for c in cmd:
print c
return True
@staticmethod
def exit(code=0):
logSys.debug("Exit with code %s", code)
if os._exit:
os._exit(code)
else:
sys.exit(code)
# global exit handler:
exit = Fail2banCmdLine.exit

234
fail2ban/client/fail2banserver.py Executable file → Normal file
View File

@ -1,7 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*- # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
# vi: set ft=python sts=4 ts=4 sw=4 noet : # vi: set ft=python sts=4 ts=4 sw=4 noet :
#
# This file is part of Fail2Ban. # This file is part of Fail2Ban.
# #
# Fail2Ban is free software; you can redistribute it and/or modify # Fail2Ban is free software; you can redistribute it and/or modify
@ -17,125 +17,171 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Fail2Ban; if not, write to the Free Software # along with Fail2Ban; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
__author__ = "Fail2Ban Developers"
__author__ = "Cyril Jaquier" __copyright__ = "Copyright (c) 2004-2008 Cyril Jaquier, 2012-2014 Yaroslav Halchenko, 2014-2016 Serg G. Brester"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL" __license__ = "GPL"
import getopt
import os import os
import sys import sys
from fail2ban.version import version from ..version import version
from fail2ban.server.server import Server from ..server.server import Server, ServerDaemonize
from fail2ban.helpers import getLogger from ..server.utils import Utils
from .fail2bancmdline import Fail2banCmdLine, logSys, exit
# Gets the instance of the logger.
logSys = getLogger("fail2ban")
SERVER = "fail2ban-server"
## ##
# \mainpage Fail2Ban # \mainpage Fail2Ban
# #
# \section Introduction # \section Introduction
# #
# Fail2ban is designed to protect your server against brute force attacks. class Fail2banServer(Fail2banCmdLine):
# Its first goal was to protect a SSH server.
class Fail2banServer: # def __init__(self):
# Fail2banCmdLine.__init__(self)
def __init__(self): ##
self.__server = None # Start Fail2Ban server in main thread without fork (foreground).
self.__argv = None #
self.__conf = dict() # Start the Fail2ban server in foreground (daemon mode or not).
self.__conf["background"] = True
self.__conf["force"] = False
self.__conf["socket"] = "/var/run/fail2ban/fail2ban.sock"
self.__conf["pidfile"] = "/var/run/fail2ban/fail2ban.pid"
def dispVersion(self): @staticmethod
print "Fail2Ban v" + version def startServerDirect(conf, daemon=True):
print server = None
print "Copyright (c) 2004-2008 Cyril Jaquier, 2008- Fail2Ban Contributors" try:
print "Copyright of modifications held by their respective authors." # Start it in foreground (current thread, not new process),
print "Licensed under the GNU General Public License v2 (GPL)." # server object will internally fork self if daemon is True
print server = Server(daemon)
print "Written by Cyril Jaquier <cyril.jaquier@fail2ban.org>." server.start(conf["socket"],
print "Many contributions by Yaroslav O. Halchenko <debian@onerussian.com>." conf["pidfile"], conf["force"],
conf=conf)
except ServerDaemonize:
pass
except Exception, e:
logSys.exception(e)
if server:
server.quit()
exit(-1)
def dispUsage(self): return server
""" Prints Fail2Ban command line options and exits
"""
print "Usage: "+self.__argv[0]+" [OPTIONS]"
print
print "Fail2Ban v" + version + " reads log file that contains password failure report"
print "and bans the corresponding IP addresses using firewall rules."
print
print "Only use this command for debugging purpose. Start the server with"
print "fail2ban-client instead. The default behaviour is to start the server"
print "in background."
print
print "Options:"
print " -b start in background"
print " -f start in foreground"
print " -s <FILE> socket path"
print " -p <FILE> pidfile path"
print " -x force execution of the server (remove socket file)"
print " -h, --help display this help message"
print " -V, --version print the version"
print
print "Report bugs to https://github.com/fail2ban/fail2ban/issues"
def __getCmdLineOptions(self, optList): ##
""" Gets the command line options # Start Fail2Ban server.
""" #
for opt in optList: # Start the Fail2ban server in daemon mode (background, start from client).
if opt[0] == "-b":
self.__conf["background"] = True @staticmethod
if opt[0] == "-f": def startServerAsync(conf):
self.__conf["background"] = False # Forks the current process.
if opt[0] == "-s": pid = os.fork()
self.__conf["socket"] = opt[1] if pid == 0:
if opt[0] == "-p": args = list()
self.__conf["pidfile"] = opt[1] args.append(SERVER)
if opt[0] == "-x": # Start async (don't read config) and in background as requested.
self.__conf["force"] = True args.append("--async")
if opt[0] in ["-h", "--help"]: args.append("-b")
self.dispUsage() # Set the socket path.
sys.exit(0) args.append("-s")
if opt[0] in ["-V", "--version"]: args.append(conf["socket"])
self.dispVersion() # Set the pidfile
sys.exit(0) args.append("-p")
args.append(conf["pidfile"])
# Force the execution if needed.
if conf["force"]:
args.append("-x")
# Logging parameters:
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(sys.path[0], SERVER))
logSys.debug("Starting %r with args %r", exe, args)
os.execv(exe, args)
except OSError:
try:
# Use the PATH env.
logSys.warning("Initial start attempt failed. Starting %r with the same args", SERVER)
os.execvp(SERVER, args)
except OSError:
exit(-1)
def _Fail2banClient(self):
from .fail2banclient import Fail2banClient
cli = Fail2banClient()
cli.applyMembers(self)
return cli
def start(self, argv): def start(self, argv):
# Command line options # Command line options
self.__argv = argv ret = self.initCmdLine(argv)
if ret is not None:
return ret
# Reads the command line options. # Commands
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)
# Start the server:
server = None
try: try:
cmdOpts = 'bfs:p:xhV' # async = True, if started from client, should fork, daemonize, etc...
cmdLongOpts = ['help', 'version'] # background = True, if should start in new process, otherwise start in foreground
optList, args = getopt.getopt(self.__argv[1:], cmdOpts, cmdLongOpts) async = self._conf.get("async", False)
except getopt.GetoptError: background = self._conf["background"]
self.dispUsage() # If was started not from the client:
sys.exit(-1) if not async:
# Start new thread with client to read configuration and
# transfer it to the server:
cli = self._Fail2banClient()
phase = dict()
logSys.debug('Configure via async client thread')
cli.configureServer(async=True, phase=phase)
# wait up to 30 seconds, do not continue if configuration is not 100% valid:
Utils.wait_for(lambda: phase.get('ready', None) is not None, 30)
if not phase.get('start', False):
return False
self.__getCmdLineOptions(optList) # Start server, daemonize it, etc.
if async or not background:
server = Fail2banServer.startServerDirect(self._conf, background)
else:
Fail2banServer.startServerAsync(self._conf)
if cli:
cli._server = server
# wait for client answer "done":
if not async and cli:
Utils.wait_for(lambda: phase.get('done', None) is not None, 30)
if not phase.get('done', False):
if server:
server.quit()
exit(-1)
logSys.debug('Starting server done')
try:
self.__server = Server(self.__conf["background"])
self.__server.start(self.__conf["socket"],
self.__conf["pidfile"],
self.__conf["force"])
return True
except Exception, e: except Exception, e:
logSys.exception(e) logSys.exception(e)
if self.__server: if server:
self.__server.quit() server.quit()
return False exit(-1)
if __name__ == "__main__": return True
@staticmethod
def exit(code=0): # pragma: no cover
if code != 0:
logSys.error("Could not start %s", SERVER)
exit(code)
def exec_command_line(): # pragma: no cover - can't test main
server = Fail2banServer() server = Fail2banServer()
if server.start(sys.argv): if server.start(sys.argv):
sys.exit(0) exit(0)
else: else:
sys.exit(-1) exit(-1)

View File

@ -42,11 +42,13 @@ CSPROTO = dotdict({
protocol = [ protocol = [
['', "BASIC", ""], ['', "BASIC", ""],
["start", "starts the server and the jails"], ["start", "starts the server and the jails"],
["reload", "reloads the configuration"], ["restart", "restarts the server"],
["reload", "reloads the configuration without restart"],
["reload <JAIL>", "reloads the jail <JAIL>"], ["reload <JAIL>", "reloads the jail <JAIL>"],
["stop", "stops all jails and terminate the server"], ["stop", "stops all jails and terminate the server"],
["status", "gets the current status of the server"], ["status", "gets the current status of the server"],
["ping", "tests if the server is alive"], ["ping", "tests if the server is alive"],
["echo", "for internal usage, returns back and outputs a given string"],
["help", "return this output"], ["help", "return this output"],
["version", "return the server version"], ["version", "return the server version"],
['', "LOGGING", ""], ['', "LOGGING", ""],

View File

@ -67,6 +67,11 @@ class Server:
'FreeBSD': '/var/run/log', 'FreeBSD': '/var/run/log',
'Linux': '/dev/log', 'Linux': '/dev/log',
} }
# todo: remove that, if test cases are fixed
self.setSyslogSocket("auto")
# Set logging level
self.setLogLevel("INFO")
self.setLogTarget("STDOUT")
def __sigTERMhandler(self, signum, frame): def __sigTERMhandler(self, signum, frame):
logSys.debug("Caught signal %d. Exiting" % signum) logSys.debug("Caught signal %d. Exiting" % signum)
@ -77,13 +82,27 @@ class Server:
self.flushLogs() self.flushLogs()
def start(self, sock, pidfile, force=False, conf={}): def start(self, sock, pidfile, force=False, conf={}):
# First set all logging parameters: # First set the mask to only allow access to owner
self.setSyslogSocket(conf.get("syslogsocket", "auto")) os.umask(0077)
self.setLogLevel(conf.get("loglevel", "INFO")) # Second daemonize before logging etc, because it will close all handles:
self.setLogTarget(conf.get("logtarget", "STDOUT")) 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")
# Set all logging parameters (or use default if not specified):
self.setSyslogSocket(conf.get("syslogsocket", self.__syslogSocket))
self.setLogLevel(conf.get("loglevel", self.__logLevel))
self.setLogTarget(conf.get("logtarget", self.__logTarget))
logSys.info("-"*50)
logSys.info("Starting Fail2ban v%s", version.version) logSys.info("Starting Fail2ban v%s", version.version)
if self.__daemon: # pragma: no cover
logSys.info("Daemon started")
# Install signal handlers # Install signal handlers
signal.signal(signal.SIGTERM, self.__sigTERMhandler) signal.signal(signal.SIGTERM, self.__sigTERMhandler)
signal.signal(signal.SIGINT, self.__sigTERMhandler) signal.signal(signal.SIGINT, self.__sigTERMhandler)
@ -92,17 +111,6 @@ class Server:
# Ensure unhandled exceptions are logged # Ensure unhandled exceptions are logged
sys.excepthook = excepthook sys.excepthook = excepthook
# First set the mask to only allow access to owner
os.umask(0077)
if self.__daemon: # pragma: no cover
logSys.info("Starting in daemon mode")
ret = self.__createDaemon()
if ret:
logSys.info("Daemon started")
else:
logSys.error("Could not create daemon")
raise ServerInitializationError("Could not create daemon")
# Creates a PID file. # Creates a PID file.
try: try:
logSys.debug("Creating PID file %s" % pidfile) logSys.debug("Creating PID file %s" % pidfile)
@ -139,11 +147,8 @@ class Server:
self.stopAllJail() self.stopAllJail()
# Only now shutdown the logging. # Only now shutdown the logging.
try: with self.__loggingLock:
self.__loggingLock.acquire()
logging.shutdown() logging.shutdown()
finally:
self.__loggingLock.release()
def addJail(self, name, backend): def addJail(self, name, backend):
self.__jails.add(name, backend, self.__db) self.__jails.add(name, backend, self.__db)
@ -372,16 +377,15 @@ class Server:
# @param value the level # @param value the level
def setLogLevel(self, value): def setLogLevel(self, value):
try: value = value.upper()
self.__loggingLock.acquire() with self.__loggingLock:
getLogger("fail2ban").setLevel( if self.__logLevel == value:
getattr(logging, value.upper())) return
except AttributeError: try:
raise ValueError("Invalid log level") getLogger("fail2ban").setLevel(getattr(logging, value))
else: self.__logLevel = value
self.__logLevel = value.upper() except AttributeError:
finally: raise ValueError("Invalid log level")
self.__loggingLock.release()
## ##
# Get the logging level. # Get the logging level.
@ -390,11 +394,8 @@ class Server:
# @return the log level # @return the log level
def getLogLevel(self): def getLogLevel(self):
try: with self.__loggingLock:
self.__loggingLock.acquire()
return self.__logLevel return self.__logLevel
finally:
self.__loggingLock.release()
## ##
# Sets the logging target. # Sets the logging target.
@ -480,24 +481,21 @@ class Server:
# syslogsocket is the full path to the syslog socket # syslogsocket is the full path to the syslog socket
# @param syslogsocket the syslog socket path # @param syslogsocket the syslog socket path
def setSyslogSocket(self, syslogsocket): def setSyslogSocket(self, syslogsocket):
self.__syslogSocket = syslogsocket with self.__loggingLock:
# Conditionally reload, logtarget depends on socket path when SYSLOG if self.__syslogSocket == syslogsocket:
return self.__logTarget != "SYSLOG"\ return True
or self.setLogTarget(self.__logTarget) self.__syslogSocket = syslogsocket
# Conditionally reload, logtarget depends on socket path when SYSLOG
return self.__logTarget != "SYSLOG"\
or self.setLogTarget(self.__logTarget)
def getLogTarget(self): def getLogTarget(self):
try: with self.__loggingLock:
self.__loggingLock.acquire()
return self.__logTarget return self.__logTarget
finally:
self.__loggingLock.release()
def getSyslogSocket(self): def getSyslogSocket(self):
try: with self.__loggingLock:
self.__loggingLock.acquire()
return self.__syslogSocket return self.__syslogSocket
finally:
self.__loggingLock.release()
def flushLogs(self): def flushLogs(self):
if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']: if self.__logTarget not in ['STDERR', 'STDOUT', 'SYSLOG']:
@ -625,3 +623,6 @@ class Server:
class ServerInitializationError(Exception): class ServerInitializationError(Exception):
pass pass
class ServerDaemonize(Exception):
pass

View File

@ -93,6 +93,8 @@ class Transmitter:
name = command[1] name = command[1]
self.__server.stopJail(name) self.__server.stopJail(name)
return None return None
elif command[0] == "echo":
return command[1:]
elif command[0] == "sleep": elif command[0] == "sleep":
value = command[1] value = command[1]
time.sleep(float(value)) time.sleep(float(value))