fail2ban/server/asyncserver.py

179 lines
5.2 KiB
Python
Raw Normal View History

# 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: Cyril Jaquier
#
__author__ = "Cyril Jaquier"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier"
__license__ = "GPL"
from pickle import dumps, loads, HIGHEST_PROTOCOL
from common import helpers
FD_CLOEXEC support * 001-fail2ban-server-socket-close-on-exec-no-leak.diff Add code that marks server and client sockets with FD_CLOEXEC flags. Avoid leaking file descriptors to processes spawned when handling fail2ban actions (ex: iptables). Unix sockets managed by fail2ban-server don't need to be passed to any child process. Fail2ban already uses the FD_CLOEXEC flags in the filter code. This patch also avoids giving iptables access to fail2ban UNIX socket in a SELinux environment (A sane SELinux policy should trigger an audit event because "iptables" will be given read/write access to the fail2ban control socket). Some random references related to this bug: http://sourceforge.net/tracker/?func=detail&atid=689044&aid=2086568&group_id=121032 http://www.redhat.com/archives/fedora-selinux-list/2009-June/msg00124.html http://forums.fedoraforum.org/showthread.php?t=234230 * 002-fail2ban-filters-close-on-exec-typo-fix.diff There is a typo in the fail2ban server/filter.py source code. The FD_CLOEXEC is correctly set but additional *random* flags are also set. It has no side-effect as long as the fd doesn't match a valid flag :) "fcntl.fcntl(fd, fcntl.F_SETFD, fd | fcntl.FD_CLOEXEC)" <== the 3rd parameter should be flags, not a file descriptor. * 003-fail2ban-gamin-socket-close-on-exec-no-leak.diff Add code that marks the Gamin monitor file descriptor with FD_CLOEXEC flags. Avoid leaking file descriptors to processes spawned when handling fail2ban actions (ex: iptables). --- File descriptors in action process before patches: dr-x------ 2 root root 0 . dr-xr-xr-x 8 root root 0 .. lr-x------ 1 root root 64 0 -> /dev/null <== OK l-wx------ 1 root root 64 1 -> /tmp/test.log <== used by test action lrwx------ 1 root root 64 2 -> /dev/null <== OK lrwx------ 1 root root 64 3 -> socket:[116361] <== NOK (fail2ban.sock leak) lr-x------ 1 root root 64 4 -> /proc/20090/fd <== used by test action l-wx------ 1 root root 64 5 -> /var/log/fail2ban.log <== OK lrwx------ 1 root root 64 6 -> socket:[115608] <== NOK (gamin sock leak) File descriptors in action process after patches: dr-x------ 2 root root 0 . dr-xr-xr-x 8 root root 0 .. lr-x------ 1 root root 64 0 -> /dev/null <== OK l-wx------ 1 root root 64 1 -> /tmp/test.log <== used by test action lrwx------ 1 root root 64 2 -> /dev/null <== OK lr-x------ 1 root root 64 3 -> /proc/18284/fd <== used by test action l-wx------ 1 root root 64 5 -> /var/log/fail2ban.log <== OK
2013-04-02 17:11:59 +00:00
import asyncore, asynchat, socket, os, logging, sys, traceback, fcntl
# Gets the instance of the logger.
logSys = logging.getLogger("fail2ban.server")
##
# Request handler class.
#
# This class extends asynchat in order to provide a request handler for
# incoming query.
class RequestHandler(asynchat.async_chat):
END_STRING = "<F2B_END_COMMAND>"
def __init__(self, conn, transmitter):
asynchat.async_chat.__init__(self, conn)
self.__transmitter = transmitter
self.__buffer = []
# Sets the terminator.
self.set_terminator(RequestHandler.END_STRING)
def collect_incoming_data(self, data):
#logSys.debug("Received raw data: " + str(data))
self.__buffer.append(data)
##
# Handles a new request.
#
# This method is called once we have a complete request.
def found_terminator(self):
# Joins the buffer items.
message = loads("".join(self.__buffer))
# Gives the message to the transmitter.
message = self.__transmitter.proceed(message)
# Serializes the response.
message = dumps(message, HIGHEST_PROTOCOL)
# Sends the response to the client.
self.send(message + RequestHandler.END_STRING)
# Closes the channel.
self.close_when_done()
def handle_error(self):
e1, e2 = helpers.formatExceptionInfo()
logSys.error("Unexpected communication error: %s" % str(e2))
logSys.error(traceback.format_exc().splitlines())
self.close()
##
# Asynchronous server class.
#
# This class extends asyncore and dispatches connection requests to
# RequestHandler.
class AsyncServer(asyncore.dispatcher):
def __init__(self, transmitter):
asyncore.dispatcher.__init__(self)
self.__transmitter = transmitter
self.__sock = "/var/run/fail2ban/fail2ban.sock"
self.__init = False
##
# Returns False as we only read the socket first.
def writable(self):
return False
def handle_accept(self):
try:
conn, addr = self.accept()
except socket.error:
logSys.warning("Socket error")
return
except TypeError:
logSys.warning("Type error")
return
FD_CLOEXEC support * 001-fail2ban-server-socket-close-on-exec-no-leak.diff Add code that marks server and client sockets with FD_CLOEXEC flags. Avoid leaking file descriptors to processes spawned when handling fail2ban actions (ex: iptables). Unix sockets managed by fail2ban-server don't need to be passed to any child process. Fail2ban already uses the FD_CLOEXEC flags in the filter code. This patch also avoids giving iptables access to fail2ban UNIX socket in a SELinux environment (A sane SELinux policy should trigger an audit event because "iptables" will be given read/write access to the fail2ban control socket). Some random references related to this bug: http://sourceforge.net/tracker/?func=detail&atid=689044&aid=2086568&group_id=121032 http://www.redhat.com/archives/fedora-selinux-list/2009-June/msg00124.html http://forums.fedoraforum.org/showthread.php?t=234230 * 002-fail2ban-filters-close-on-exec-typo-fix.diff There is a typo in the fail2ban server/filter.py source code. The FD_CLOEXEC is correctly set but additional *random* flags are also set. It has no side-effect as long as the fd doesn't match a valid flag :) "fcntl.fcntl(fd, fcntl.F_SETFD, fd | fcntl.FD_CLOEXEC)" <== the 3rd parameter should be flags, not a file descriptor. * 003-fail2ban-gamin-socket-close-on-exec-no-leak.diff Add code that marks the Gamin monitor file descriptor with FD_CLOEXEC flags. Avoid leaking file descriptors to processes spawned when handling fail2ban actions (ex: iptables). --- File descriptors in action process before patches: dr-x------ 2 root root 0 . dr-xr-xr-x 8 root root 0 .. lr-x------ 1 root root 64 0 -> /dev/null <== OK l-wx------ 1 root root 64 1 -> /tmp/test.log <== used by test action lrwx------ 1 root root 64 2 -> /dev/null <== OK lrwx------ 1 root root 64 3 -> socket:[116361] <== NOK (fail2ban.sock leak) lr-x------ 1 root root 64 4 -> /proc/20090/fd <== used by test action l-wx------ 1 root root 64 5 -> /var/log/fail2ban.log <== OK lrwx------ 1 root root 64 6 -> socket:[115608] <== NOK (gamin sock leak) File descriptors in action process after patches: dr-x------ 2 root root 0 . dr-xr-xr-x 8 root root 0 .. lr-x------ 1 root root 64 0 -> /dev/null <== OK l-wx------ 1 root root 64 1 -> /tmp/test.log <== used by test action lrwx------ 1 root root 64 2 -> /dev/null <== OK lr-x------ 1 root root 64 3 -> /proc/18284/fd <== used by test action l-wx------ 1 root root 64 5 -> /var/log/fail2ban.log <== OK
2013-04-02 17:11:59 +00:00
AsyncServer.__markCloseOnExec(conn)
# Creates an instance of the handler class to handle the
# request/response on the incoming connection.
RequestHandler(conn, self.__transmitter)
##
# Starts the communication server.
#
# @param sock: socket file.
# @param force: remove the socket file if exists.
def start(self, sock, force):
self.__sock = sock
# Remove socket
if os.path.exists(sock):
logSys.error("Fail2ban seems to be already running")
if force:
logSys.warn("Forcing execution of the server")
os.remove(sock)
else:
raise AsyncServerException("Server already running")
# Creates the socket.
self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.set_reuse_addr()
try:
self.bind(sock)
except Exception:
raise AsyncServerException("Unable to bind socket %s" % self.__sock)
FD_CLOEXEC support * 001-fail2ban-server-socket-close-on-exec-no-leak.diff Add code that marks server and client sockets with FD_CLOEXEC flags. Avoid leaking file descriptors to processes spawned when handling fail2ban actions (ex: iptables). Unix sockets managed by fail2ban-server don't need to be passed to any child process. Fail2ban already uses the FD_CLOEXEC flags in the filter code. This patch also avoids giving iptables access to fail2ban UNIX socket in a SELinux environment (A sane SELinux policy should trigger an audit event because "iptables" will be given read/write access to the fail2ban control socket). Some random references related to this bug: http://sourceforge.net/tracker/?func=detail&atid=689044&aid=2086568&group_id=121032 http://www.redhat.com/archives/fedora-selinux-list/2009-June/msg00124.html http://forums.fedoraforum.org/showthread.php?t=234230 * 002-fail2ban-filters-close-on-exec-typo-fix.diff There is a typo in the fail2ban server/filter.py source code. The FD_CLOEXEC is correctly set but additional *random* flags are also set. It has no side-effect as long as the fd doesn't match a valid flag :) "fcntl.fcntl(fd, fcntl.F_SETFD, fd | fcntl.FD_CLOEXEC)" <== the 3rd parameter should be flags, not a file descriptor. * 003-fail2ban-gamin-socket-close-on-exec-no-leak.diff Add code that marks the Gamin monitor file descriptor with FD_CLOEXEC flags. Avoid leaking file descriptors to processes spawned when handling fail2ban actions (ex: iptables). --- File descriptors in action process before patches: dr-x------ 2 root root 0 . dr-xr-xr-x 8 root root 0 .. lr-x------ 1 root root 64 0 -> /dev/null <== OK l-wx------ 1 root root 64 1 -> /tmp/test.log <== used by test action lrwx------ 1 root root 64 2 -> /dev/null <== OK lrwx------ 1 root root 64 3 -> socket:[116361] <== NOK (fail2ban.sock leak) lr-x------ 1 root root 64 4 -> /proc/20090/fd <== used by test action l-wx------ 1 root root 64 5 -> /var/log/fail2ban.log <== OK lrwx------ 1 root root 64 6 -> socket:[115608] <== NOK (gamin sock leak) File descriptors in action process after patches: dr-x------ 2 root root 0 . dr-xr-xr-x 8 root root 0 .. lr-x------ 1 root root 64 0 -> /dev/null <== OK l-wx------ 1 root root 64 1 -> /tmp/test.log <== used by test action lrwx------ 1 root root 64 2 -> /dev/null <== OK lr-x------ 1 root root 64 3 -> /proc/18284/fd <== used by test action l-wx------ 1 root root 64 5 -> /var/log/fail2ban.log <== OK
2013-04-02 17:11:59 +00:00
AsyncServer.__markCloseOnExec(self.socket)
self.listen(1)
# Sets the init flag.
self.__init = True
# TODO Add try..catch
# There's a bug report for Python 2.6/3.0 that use_poll=True yields some 2.5 incompatibilities:
if sys.version_info >= (2, 6): # if python 2.6 or greater...
logSys.debug("Detected Python 2.6 or greater. asyncore.loop() not using poll")
asyncore.loop(use_poll = False) # fixes the "Unexpected communication problem" issue on Python 2.6 and 3.0
else: # pragma: no cover
logSys.debug("NOT Python 2.6/3.* - asyncore.loop() using poll")
asyncore.loop(use_poll = True)
##
# Stops the communication server.
def stop(self):
if self.__init:
# Only closes the socket if it was initialized first.
self.close()
# Remove socket
if os.path.exists(self.__sock):
logSys.debug("Removed socket file " + self.__sock)
os.remove(self.__sock)
logSys.debug("Socket shutdown")
FD_CLOEXEC support * 001-fail2ban-server-socket-close-on-exec-no-leak.diff Add code that marks server and client sockets with FD_CLOEXEC flags. Avoid leaking file descriptors to processes spawned when handling fail2ban actions (ex: iptables). Unix sockets managed by fail2ban-server don't need to be passed to any child process. Fail2ban already uses the FD_CLOEXEC flags in the filter code. This patch also avoids giving iptables access to fail2ban UNIX socket in a SELinux environment (A sane SELinux policy should trigger an audit event because "iptables" will be given read/write access to the fail2ban control socket). Some random references related to this bug: http://sourceforge.net/tracker/?func=detail&atid=689044&aid=2086568&group_id=121032 http://www.redhat.com/archives/fedora-selinux-list/2009-June/msg00124.html http://forums.fedoraforum.org/showthread.php?t=234230 * 002-fail2ban-filters-close-on-exec-typo-fix.diff There is a typo in the fail2ban server/filter.py source code. The FD_CLOEXEC is correctly set but additional *random* flags are also set. It has no side-effect as long as the fd doesn't match a valid flag :) "fcntl.fcntl(fd, fcntl.F_SETFD, fd | fcntl.FD_CLOEXEC)" <== the 3rd parameter should be flags, not a file descriptor. * 003-fail2ban-gamin-socket-close-on-exec-no-leak.diff Add code that marks the Gamin monitor file descriptor with FD_CLOEXEC flags. Avoid leaking file descriptors to processes spawned when handling fail2ban actions (ex: iptables). --- File descriptors in action process before patches: dr-x------ 2 root root 0 . dr-xr-xr-x 8 root root 0 .. lr-x------ 1 root root 64 0 -> /dev/null <== OK l-wx------ 1 root root 64 1 -> /tmp/test.log <== used by test action lrwx------ 1 root root 64 2 -> /dev/null <== OK lrwx------ 1 root root 64 3 -> socket:[116361] <== NOK (fail2ban.sock leak) lr-x------ 1 root root 64 4 -> /proc/20090/fd <== used by test action l-wx------ 1 root root 64 5 -> /var/log/fail2ban.log <== OK lrwx------ 1 root root 64 6 -> socket:[115608] <== NOK (gamin sock leak) File descriptors in action process after patches: dr-x------ 2 root root 0 . dr-xr-xr-x 8 root root 0 .. lr-x------ 1 root root 64 0 -> /dev/null <== OK l-wx------ 1 root root 64 1 -> /tmp/test.log <== used by test action lrwx------ 1 root root 64 2 -> /dev/null <== OK lr-x------ 1 root root 64 3 -> /proc/18284/fd <== used by test action l-wx------ 1 root root 64 5 -> /var/log/fail2ban.log <== OK
2013-04-02 17:11:59 +00:00
##
# Marks socket as close-on-exec to avoid leaking file descriptors when
# running actions involving command execution.
# @param sock: socket file.
#@staticmethod
def __markCloseOnExec(sock):
fd = sock.fileno()
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC)
__markCloseOnExec = staticmethod(__markCloseOnExec)
##
# AsyncServerException is used to wrap communication exceptions.
class AsyncServerException(Exception):
pass