Source code for Bcfg2.Server.CherrypyCore

""" The core of the `CherryPy <http://www.cherrypy.org/>`_-powered
server. """

import sys
import time
import Bcfg2.Server.Statistics
from Bcfg2.Compat import urlparse, xmlrpclib, b64decode
from Bcfg2.Server.Core import NetworkCore
import cherrypy
from cherrypy.lib import xmlrpcutil
from cherrypy._cptools import ErrorTool
from cherrypy.process.plugins import Daemonizer, DropPrivileges, PIDFile


[docs]def on_error(*args, **kwargs): # pylint: disable=W0613 """ CherryPy error handler that handles :class:`xmlrpclib.Fault` objects and so allows for the possibility of returning proper error codes. This obviates the need to use :func:`cherrypy.lib.xmlrpc.on_error`, the builtin CherryPy xmlrpc tool, which does not handle xmlrpclib.Fault objects and returns the same error code for every error.""" err = sys.exc_info()[1] if not isinstance(err, xmlrpclib.Fault): err = xmlrpclib.Fault(xmlrpclib.INTERNAL_ERROR, str(err)) xmlrpcutil._set_response(xmlrpclib.dumps(err)) # pylint: disable=W0212
cherrypy.tools.xmlrpc_error = ErrorTool(on_error)
[docs]class CherrypyCore(NetworkCore): """ The CherryPy-based server core. """ #: Base CherryPy config for this class. We enable the #: ``xmlrpc_error`` tool created from :func:`on_error` and the #: ``bcfg2_authn`` tool created from :func:`do_authn`. _cp_config = {'tools.xmlrpc_error.on': True, 'tools.bcfg2_authn.on': True} def __init__(self): NetworkCore.__init__(self) cherrypy.tools.bcfg2_authn = cherrypy.Tool('on_start_resource', self.do_authn) #: List of exposed plugin RMI self.rmi = self._get_rmi() cherrypy.engine.subscribe('stop', self.shutdown) __init__.__doc__ = NetworkCore.__init__.__doc__.split('.. -----')[0]
[docs] def do_authn(self): """ Perform authentication by calling :func:`Bcfg2.Server.Core.NetworkCore.authenticate`. This is implemented as a CherryPy tool.""" try: header = cherrypy.request.headers['Authorization'] except KeyError: self.critical_error("No authentication data presented") auth_content = header.split()[1] auth_content = b64decode(auth_content) try: username, password = auth_content.split(":") except ValueError: username = auth_content password = "" # FIXME: Get client cert cert = None address = (cherrypy.request.remote.ip, cherrypy.request.remote.port) rpcmethod = xmlrpcutil.process_body()[1] if rpcmethod == 'ERRORMETHOD': raise Exception("Unknown error processing XML-RPC request body") if (not self.check_acls(address[0], rpcmethod) or not self.authenticate(cert, username, password, address)): raise cherrypy.HTTPError(401)
[docs] @cherrypy.expose def default(self, *args, **params): # pylint: disable=W0613 """ Handle all XML-RPC calls. It was necessary to make enough changes to the stock CherryPy :class:`cherrypy._cptools.XMLRPCController` to support plugin RMI and prepending the client address that we just rewrote it. It clearly wasn't written with inheritance in mind.""" rpcparams, rpcmethod = xmlrpcutil.process_body() if rpcmethod == 'ERRORMETHOD': raise Exception("Unknown error processing XML-RPC request body") elif "." not in rpcmethod: address = (cherrypy.request.remote.ip, cherrypy.request.remote.name) rpcparams = (address, ) + rpcparams handler = getattr(self, rpcmethod, None) if not handler or not getattr(handler, "exposed", False): raise Exception('Method "%s" is not supported' % rpcmethod) else: try: handler = self.rmi[rpcmethod] except KeyError: raise Exception('Method "%s" is not supported' % rpcmethod) method_start = time.time() try: body = handler(*rpcparams, **params) finally: Bcfg2.Server.Statistics.stats.add_value(rpcmethod, time.time() - method_start) xmlrpcutil.respond(body, 'utf-8', True) return cherrypy.serving.response.body
[docs] def _daemonize(self): """ Drop privileges, daemonize with :class:`cherrypy.process.plugins.Daemonizer` and write a PID file with :class:`cherrypy.process.plugins.PIDFile`. """ self._drop_privileges() Daemonizer(cherrypy.engine).subscribe() PIDFile(cherrypy.engine, Bcfg2.Options.setup.daemon).subscribe() return True
def _drop_privileges(self): """ Drop privileges with :class:`cherrypy.process.plugins.DropPrivileges` """ DropPrivileges(cherrypy.engine, uid=Bcfg2.Options.setup.daemon_uid, gid=Bcfg2.Options.setup.daemon_gid, umask=int(Bcfg2.Options.setup.umask, 8)).subscribe()
[docs] def _run(self): """ Start the server listening. """ hostname, port = urlparse(Bcfg2.Options.setup.server)[1].split(':') if Bcfg2.Options.setup.listen_all: hostname = '0.0.0.0' config = {'engine.autoreload.on': False, 'server.socket_port': int(port), 'server.socket_host': hostname} if Bcfg2.Options.setup.cert and Bcfg2.Options.setup.key: config.update({'server.ssl_module': 'pyopenssl', 'server.ssl_certificate': Bcfg2.Options.setup.cert, 'server.ssl_private_key': Bcfg2.Options.setup.key}) if Bcfg2.Options.setup.debug: config['log.screen'] = True cherrypy.config.update(config) cherrypy.tree.mount(self, '/', {'/': Bcfg2.Options.setup}) cherrypy.engine.start() return True
[docs] def _block(self): """ Enter the blocking infinite server loop. :func:`Bcfg2.Server.Core.NetworkCore.shutdown` is called on exit by a :meth:`subscription <cherrypy.process.wspbus.Bus.subscribe>` on the top-level CherryPy engine.""" cherrypy.engine.block()