def assertLoopback(self, root: Node) -> Optional[int]: proto = EAmuseProtocol() for encoding in [EAmuseProtocol.BINARY, EAmuseProtocol.XML]: if encoding == EAmuseProtocol.BINARY: loop_name = "binary" elif encoding == EAmuseProtocol.XML: loop_name = "xml" else: raise Exception("Logic error!") binary = proto.encode(None, None, root, text_encoding=EAmuseProtocol.SHIFT_JIS, packet_encoding=encoding) newroot = proto.decode(None, None, binary) self.assertEqual( newroot, root, "Round trip with {} and no encryption/compression doesn't match!" .format(loop_name)) binary = proto.encode(None, '1-abcdef-0123', root, text_encoding=EAmuseProtocol.SHIFT_JIS, packet_encoding=encoding) newroot = proto.decode(None, '1-abcdef-0123', binary) self.assertEqual( newroot, root, "Round trip with {}, encryption and no compression doesn't match!" .format(loop_name)) binary = proto.encode('none', None, root, text_encoding=EAmuseProtocol.SHIFT_JIS, packet_encoding=encoding) newroot = proto.decode('none', None, binary) self.assertEqual( newroot, root, "Round trip with {}, encryption and no compression doesn't match!" .format(loop_name)) binary = proto.encode('lz77', None, root, text_encoding=EAmuseProtocol.SHIFT_JIS, packet_encoding=encoding) newroot = proto.decode('lz77', None, binary) self.assertEqual( newroot, root, "Round trip with {}, no encryption and lz77 compression doesn't match!" .format(loop_name))
def exchange(self, uri: str, tree: Node, text_encoding: str="shift-jis", packet_encoding: str="binary") -> Node: headers = {} if self.__verbose: print('Outgoing request:') print(tree) # Handle encoding if packet_encoding == "xml": _packet_encoding = EAmuseProtocol.XML elif packet_encoding == "binary": _packet_encoding = EAmuseProtocol.BINARY else: raise Exception(f"Unknown packet encoding {packet_encoding}") # Handle encryption if self.__encryption: encryption = f'1-{hex_string(8)}-{hex_string(4)}' headers['X-Eamuse-Info'] = encryption else: encryption = None # Handle compression if self.__compression: compression = 'lz77' else: compression = None headers['X-Compress'] = compression # Convert it proto = EAmuseProtocol() req = proto.encode( compression, encryption, tree, text_encoding=text_encoding, packet_encoding=_packet_encoding, ) # Send the request, get the response r = requests.post( f'http://{self.__address}:{self.__port}{"/" if uri[0] != "/" else ""}{uri}', headers=headers, data=req, ) # Get the compression and encryption encryption = headers.get('X-Eamuse-Info') compression = headers.get('X-Compress') # Decode it packet = proto.decode( compression, encryption, r.content, ) if self.__verbose: print('Incoming response:') print(packet) return packet
def receive_request(path: str) -> Response: # First, parse the packet itself client_proto = EAmuseProtocol() server_proto = EAmuseProtocol() remote_address = request.headers.get('X-Remote-Address', None) request_compression = request.headers.get('X-Compress', None) request_encryption = request.headers.get('X-Eamuse-Info', None) actual_path = f'/{path}' if request.query_string is not None and len(request.query_string) > 0: actual_path = actual_path + f'?{request.query_string.decode("ascii")}' if config['verbose']: print(f"HTTP request for URI {actual_path}") print(f"Compression is {request_compression}") print(f"Encryption key is {request_encryption}") req = client_proto.decode( request_compression, request_encryption, request.data, ) if req is None: # Nothing to do here return Response("Unrecognized packet!", 500) if config['verbose']: print("Original request to server:") print(req) # Grab PCBID for directing to mulitple servers pcbid = req.attribute('srcid') if pcbid in config['remote']: remote_host = config['remote'][pcbid]['host'] remote_port = config['remote'][pcbid]['port'] elif '*' in config['remote']: remote_host = config['remote']['*']['host'] remote_port = config['remote']['*']['port'] else: return Response(f"No route for PCBID {pcbid}", 500) modified_request = modify_request(config, req) if modified_request is None: # Return the original binary data instead of re-encoding it # to the exact same thing. req_binary = request.data else: if config['verbose']: print("Modified request to server:") print(modified_request) # Re-encode the modified packet req_binary = server_proto.encode( request_compression, request_encryption, modified_request, client_proto.last_text_encoding, client_proto.last_packet_encoding, ) # Set up custom headers for remote request headers = {} if request_compression is not None: headers['X-Compress'] = request_compression if request_encryption is not None: headers['X-Eamuse-Info'] = request_encryption # For lobby functionality, make sure the request receives # the original IP address headers['X-Remote-Address'] = remote_address or request.remote_addr # Make request to foreign service, using the same parameters r = requests.post( f'http://{remote_host}:{remote_port}{actual_path}', headers=headers, data=req_binary, timeout=config['timeout'], ) if r.status_code != 200: # Failed on remote side return Response("Failed to get response!", 500) # Decode response, for modification if necessary response_compression = r.headers.get('X-Compress', None) response_encryption = r.headers.get('X-Eamuse-Info', None) resp = server_proto.decode( response_compression, response_encryption, r.content, ) if resp is None: # Nothing to do here return Response("Unrecognized packet!", 500) if config['verbose']: print("Original response from server:") print(resp) modified_response = modify_response(config, resp) if modified_response is None: # Return the original response data instead of re-encoding it # to the exact same thing. resp_binary = r.content else: if config['verbose']: print("Modified response from server:") print(modified_response) # Re-encode the modified packet resp_binary = client_proto.encode( response_compression, response_encryption, modified_response, ) # Some old clients are case sensitive, so be careful to capitalize # these responses here. flask_resp = Response(resp_binary) if response_compression is not None: flask_resp.headers['X-Compress'] = response_compression if response_encryption is not None: flask_resp.headers['X-Eamuse-Info'] = response_encryption return flask_resp
def receive_request(path: str) -> Response: proto = EAmuseProtocol() remote_address = request.headers.get('x-remote-address', None) compression = request.headers.get('x-compress', None) encryption = request.headers.get('x-eamuse-info', None) req = proto.decode( compression, encryption, request.data, ) if req is None: # Nothing to do here return Response("Unrecognized packet!", 500) if req.name in {'soapenv:Envelope', 'soap:Envelope', 'methodCall'}: # We get lots of spam from random bots trying to SOAP # us up, so ignore this shit. return Response("Unrecognized packet!", 500) # Create and format config global config requestconfig = copy.copy(config) requestconfig['client'] = { 'address': remote_address or request.remote_addr, } dataprovider = Data(requestconfig) try: dispatch = Dispatch(requestconfig, dataprovider, True) resp = dispatch.handle(req) if resp is None: # Nothing to do here dataprovider.local.network.put_event( 'unhandled_packet', { 'request': str(req), }, ) return Response("No response generated", 404) compression = None data = proto.encode( compression, encryption, resp, ) response = make_response(data) # Some old clients are case-sensitive, even though http spec says these # shouldn't matter, so capitalize correctly. if compression: response.headers['X-Compress'] = compression else: response.headers['X-Compress'] = 'none' if encryption: response.headers['X-Eamuse-Info'] = encryption return response except UnrecognizedPCBIDException as e: dataprovider.local.network.put_event( 'unauthorized_pcbid', { 'pcbid': e.pcbid, 'model': e.model, 'ip': e.ip, }, ) return Response("Unauthorized client", 403) except Exception: stack = traceback.format_exc() print(stack) dataprovider.local.network.put_event( 'exception', { 'service': 'xrpc', 'request': str(req), 'traceback': stack, }, ) return Response("Crash when handling packet!", 500) finally: dataprovider.close()