1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
""" 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
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)
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]
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)
@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
def _daemonize(self):
""" Drop privileges with
:class:`cherrypy.process.plugins.DropPrivileges`, daemonize
with :class:`cherrypy.process.plugins.Daemonizer`, and write a
PID file with :class:`cherrypy.process.plugins.PIDFile`. """
DropPrivileges(cherrypy.engine,
uid=Bcfg2.Options.setup.daemon_uid,
gid=Bcfg2.Options.setup.daemon_gid,
umask=int(Bcfg2.Options.setup.umask, 8)).subscribe()
Daemonizer(cherrypy.engine).subscribe()
PIDFile(cherrypy.engine, Bcfg2.Options.setup.daemon).subscribe()
return True
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
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()
|