summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/CherryPyCore.py
blob: 91e7f89bd6549715f416ee1cbb7b6663d5cc80c7 (plain)
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
""" the core of the CherryPy-powered server """

import sys
import base64
import atexit
import cherrypy
import Bcfg2.Options
from Bcfg2.Bcfg2Py3k import urlparse, xmlrpclib
from Bcfg2.Server.Core import BaseCore
from cherrypy.lib import xmlrpcutil
from cherrypy._cptools import ErrorTool

if cherrypy.engine.state == 0:
    cherrypy.engine.start(blocking=False)
    atexit.register(cherrypy.engine.stop)

# define our own error handler that handles xmlrpclib.Fault objects
# and so allows for the possibility of returning proper error
# codes. this obviates the need to use the builtin CherryPy xmlrpc
# tool
def on_error(*args, **kwargs):
    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))
cherrypy.tools.xmlrpc_error = ErrorTool(on_error)


class Core(BaseCore):
    _cp_config = {'tools.xmlrpc_error.on': True,
                  'tools.bcfg2_authn.on': True}

    def __init__(self, *args, **kwargs):
        BaseCore.__init__(self, *args, **kwargs)

        cherrypy.tools.bcfg2_authn = cherrypy.Tool('on_start_resource',
                                                   self.do_authn)

        self.rmi = self._get_rmi()

    def do_authn(self):
        try:
            header = cherrypy.request.headers['Authorization']
        except KeyError:
            self.critical_error("No authentication data presented")
        auth_type, auth_content = header.split()
        try:
            # py3k compatibility
            auth_content = base64.standard_b64decode(auth_content)
        except TypeError:
            auth_content = \
                base64.standard_b64decode(bytes(auth_content.encode('ascii')))
        try:
            # py3k compatibility
            try:
                username, password = auth_content.split(":")
            except TypeError:
                username, pw = auth_content.split(bytes(":", encoding='utf-8'))
                password = pw.decode('utf-8')
        except ValueError:
            username = auth_content
            password = ""
        
        # FIXME: Get client cert
        cert = None
        address = (cherrypy.request.remote.ip, cherrypy.request.remote.name)
        return self.authenticate(cert, username, password, address)

    @cherrypy.expose
    def default(self, *vpath, **params):
        # needed to make enough changes to the stock XMLRPCController
        # to support plugin.__rmi__ and prepending client address that
        # we just rewrote.  it clearly wasn't written with inheritance
        # in mind :(
        rpcparams, rpcmethod = xmlrpcutil.process_body()
        if "." not in rpcmethod:
            address = (cherrypy.request.remote.ip, cherrypy.request.remote.name)
            rpcparams = (address, ) + rpcparams

            handler = getattr(self, rpcmethod)
            if not handler or not getattr(handler, "exposed", False):
                raise Exception('method "%s" is not supported' % attr)
        else:
            try:
                handler = self.rmi[rpcmethod]
            except:
                raise Exception('method "%s" is not supported' % rpcmethod)

        body = handler(*rpcparams, **params)
        
        xmlrpcutil.respond(body, 'utf-8', True)
        return cherrypy.serving.response.body

    def run(self):
        hostname, port = urlparse(self.setup['location'])[1].split(':')
        if self.setup['listen_all']:
            hostname = '0.0.0.0'

        config = {'engine.autoreload.on': False,
                  'server.socket_port': int(port)}
        if self.setup['cert'] and self.setup['key']:
            config.update({'server.ssl_module': 'pyopenssl',
                           'server.ssl_certificate': self.setup['cert'],
                           'server.ssl_private_key': self.setup['key']})
        if self.setup['debug']:
            config['log.screen'] = True
        cherrypy.config.update(config)
        cherrypy.quickstart(self, config={'/': self.setup})
                                                        

def parse_opts(argv=None):
    if argv is None:
        argv = sys.argv[1:]
    optinfo = dict()
    optinfo.update(Bcfg2.Options.CLI_COMMON_OPTIONS)
    optinfo.update(Bcfg2.Options.SERVER_COMMON_OPTIONS)
    optinfo.update(Bcfg2.Options.DAEMON_COMMON_OPTIONS)
    setup = Bcfg2.Options.OptionParser(optinfo, argv=argv)
    setup.parse(argv)
    return setup

def application(environ, start_response):
    """ running behind Apache as a WSGI app is not currently
    supported, but I'm keeping this code here because I hope for it to
    be supported some day.  we'll need to set up an AMQP task queue
    and related magic for that to happen, though. """
    cherrypy.config.update({'environment': 'embedded'})
    setup = parse_opts(argv=['-C', environ['config']])
    root = Core(setup, start_fam_thread=True)
    cherrypy.tree.mount(root)
    return cherrypy.tree(environ, start_response)