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))
Exemple #2
0
    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
Exemple #3
0
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
Exemple #4
0
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()