1#
2# BitBake XMLRPC Server Interface
3#
4# Copyright (C) 2006 - 2007  Michael 'Mickey' Lauer
5# Copyright (C) 2006 - 2008  Richard Purdie
6#
7# SPDX-License-Identifier: GPL-2.0-only
8#
9
10import hashlib
11import time
12import inspect
13from xmlrpc.server import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
14import bb.server.xmlrpcclient
15
16import bb
17
18# This request handler checks if the request has a "Bitbake-token" header
19# field (this comes from the client side) and compares it with its internal
20# "Bitbake-token" field (this comes from the server). If the two are not
21# equal, it is assumed that a client is trying to connect to the server
22# while another client is connected to the server. In this case, a 503 error
23# ("service unavailable") is returned to the client.
24class BitBakeXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
25    def __init__(self, request, client_address, server):
26        self.server = server
27        SimpleXMLRPCRequestHandler.__init__(self, request, client_address, server)
28
29    def do_POST(self):
30        try:
31            remote_token = self.headers["Bitbake-token"]
32        except:
33            remote_token = None
34        if 0 and remote_token != self.server.connection_token and remote_token != "observer":
35            self.report_503()
36        else:
37            if remote_token == "observer":
38                self.server.readonly = True
39            else:
40                self.server.readonly = False
41            SimpleXMLRPCRequestHandler.do_POST(self)
42
43    def report_503(self):
44        self.send_response(503)
45        response = 'No more client allowed'
46        self.send_header("Content-type", "text/plain")
47        self.send_header("Content-length", str(len(response)))
48        self.end_headers()
49        self.wfile.write(bytes(response, 'utf-8'))
50
51class BitBakeXMLRPCServer(SimpleXMLRPCServer):
52    # remove this when you're done with debugging
53    # allow_reuse_address = True
54
55    def __init__(self, interface, cooker, parent):
56        # Use auto port configuration
57        if (interface[1] == -1):
58            interface = (interface[0], 0)
59        SimpleXMLRPCServer.__init__(self, interface,
60                                    requestHandler=BitBakeXMLRPCRequestHandler,
61                                    logRequests=False, allow_none=True)
62        self.host, self.port = self.socket.getsockname()
63        self.interface = interface
64
65        self.connection_token = None
66        self.commands = BitBakeXMLRPCServerCommands(self)
67        self.register_functions(self.commands, "")
68
69        self.cooker = cooker
70        self.parent = parent
71
72
73    def register_functions(self, context, prefix):
74        """
75        Convenience method for registering all functions in the scope
76        of this class that start with a common prefix
77        """
78        methodlist = inspect.getmembers(context, inspect.ismethod)
79        for name, method in methodlist:
80            if name.startswith(prefix):
81                self.register_function(method, name[len(prefix):])
82
83    def get_timeout(self, delay):
84        socktimeout = self.socket.gettimeout() or delay
85        return min(socktimeout, delay)
86
87    def handle_requests(self):
88        self._handle_request_noblock()
89
90class BitBakeXMLRPCServerCommands():
91
92    def __init__(self, server):
93        self.server = server
94        self.has_client = False
95
96    def registerEventHandler(self, host, port):
97        """
98        Register a remote UI Event Handler
99        """
100        s, t = bb.server.xmlrpcclient._create_server(host, port)
101
102        # we don't allow connections if the cooker is running
103        if (self.server.cooker.state in [bb.cooker.state.parsing, bb.cooker.state.running]):
104            return None, "Cooker is busy: %s" % bb.cooker.state.get_name(self.server.cooker.state)
105
106        self.event_handle = bb.event.register_UIHhandler(s, True)
107        return self.event_handle, 'OK'
108
109    def unregisterEventHandler(self, handlerNum):
110        """
111        Unregister a remote UI Event Handler
112        """
113        ret = bb.event.unregister_UIHhandler(handlerNum, True)
114        self.event_handle = None
115        return ret
116
117    def runCommand(self, command):
118        """
119        Run a cooker command on the server
120        """
121        return self.server.cooker.command.runCommand(command, self.server.parent, self.server.readonly)
122
123    def getEventHandle(self):
124        return self.event_handle
125
126    def terminateServer(self):
127        """
128        Trigger the server to quit
129        """
130        self.server.parent.quit = True
131        print("XMLRPC Server triggering exit")
132        return
133
134    def addClient(self):
135        if self.server.parent.haveui:
136            return None
137        token = hashlib.md5(str(time.time()).encode("utf-8")).hexdigest()
138        self.server.connection_token = token
139        self.server.parent.haveui = True
140        return token
141
142    def removeClient(self):
143        if self.server.parent.haveui:
144            self.server.connection_token = None
145            self.server.parent.haveui = False
146
147