Exemple #1
0
class MinecraftProxy(asyncore.dispatcher_with_send):
    """Proxies a packet stream from a Minecraft client or server.
    """

    def __init__(self, src_sock, other_side=None):
        """Proxies one side of a client-server connection.

        MinecraftProxy instances are created in pairs that have references to
        one another. Since a client initiates a connection, the client side of
        the pair is always created first, with other_side = None. The creator
        of the client proxy is then responsible for connecting to the server
        and creating a server proxy with other_side=client. Finally, the
        proxy creator should do client_proxy.other_side = server_proxy.
        """
        asyncore.dispatcher_with_send.__init__(self, src_sock)
        self.plugin_mgr = None
        self.other_side = other_side
        if other_side == None:
            self.side = 'client'
            self.msg_spec = messages.protocol[0][0]
        else:
            self.side = 'server'
            self.msg_spec = messages.protocol[0][1]
            self.other_side.other_side = self
        self.stream = Stream()
        self.last_report = 0
        self.msg_queue = []
        self.out_of_sync = False

    def handle_read(self):
        """Read all available bytes, and process as many packets as possible.
        """
        t = time()
        if self.last_report + 5 < t and self.stream.tot_bytes > 0:
            self.last_report = t
            logger.debug("%s: total/wasted bytes is %d/%d (%f wasted)" % (
                 self.side, self.stream.tot_bytes, self.stream.wasted_bytes,
                 100 * float(self.stream.wasted_bytes) / self.stream.tot_bytes))
        self.stream.append(self.recv(4092))

        if self.out_of_sync:
            data = self.stream.read(len(self.stream))
            self.stream.packet_finished()
            if self.other_side:
                self.other_side.send(data)
            return

        try:
            packet = parse_packet(self.stream, self.msg_spec, self.side)
            while packet != None:
                if packet['msgtype'] == 0x01 and self.side == 'client':
                    # Determine which protocol message definitions to use.
                    proto_version = packet['proto_version']
                    logger.info('Client requests protocol version %d' % proto_version)
                    if not proto_version in messages.protocol:
                        logger.error("Unsupported protocol version %d" % proto_version)
                        self.handle_close()
                        return
                    self.msg_spec, self.other_side.msg_spec = messages.protocol[proto_version]
                forward = True
                if self.plugin_mgr:
                    forwarding = self.plugin_mgr.filter(packet, self.side)
                    if forwarding and packet.modified:
                        packet['raw_bytes'] = self.msg_spec[packet['msgtype']].parse(packet)
                if forwarding and self.other_side:
                    self.other_side.send(packet['raw_bytes'])
                # Since we know we're at a message boundary, we can inject
                # any messages in the queue.
                msgbytes = self.plugin_mgr.next_injected_msg_from(self.side)
                while self.other_side and msgbytes is not None:
                    self.other_side.send(msgbytes)
                    msgbytes = self.plugin_mgr.next_injected_msg_from(self.side)

                # Attempt to parse the next packet.
                packet = parse_packet(self.stream,self.msg_spec, self.side)
        except PartialPacketException:
            pass # Not all data for the current packet is available.
        except Exception:
            logger.error("MinecraftProxy for %s caught exception, out of sync" % self.side)
            logger.error(traceback.format_exc())
            logger.debug("Current stream buffer: %s" % repr(self.stream.buf))
            self.out_of_sync = True
            self.stream.reset()

    def handle_close(self):
        """Call shutdown handler."""
        logger.info("%s socket closed.", self.side)
        self.close()
        if self.other_side is not None:
            logger.info("shutting down other side")
            self.other_side.other_side = None
            self.other_side.close()
            self.other_side = None
            logger.info("shutting down plugin manager")
            self.plugin_mgr.destroy()
Exemple #2
0
class MinecraftProxy(asyncore.dispatcher_with_send):
    """Proxies a packet stream from a Minecraft client or server.
    """
    def __init__(self, src_sock, other_side=None):
        """Proxies one side of a client-server connection.

        MinecraftProxy instances are created in pairs that have references to
        one another. Since a client initiates a connection, the client side of
        the pair is always created first, with other_side = None. The creator
        of the client proxy is then responsible for connecting to the server
        and creating a server proxy with other_side=client. Finally, the
        proxy creator should do client_proxy.other_side = server_proxy.
        """
        asyncore.dispatcher_with_send.__init__(self, src_sock)
        self.plugin_mgr = None
        self.other_side = other_side
        if other_side == None:
            self.side = 'client'
            self.msg_spec = messages.protocol[0][0]
        else:
            self.side = 'server'
            self.msg_spec = messages.protocol[0][1]
            self.other_side.other_side = self
        self.stream = Stream()
        self.last_report = 0
        self.msg_queue = []
        self.out_of_sync = False

    def handle_read(self):
        """Read all available bytes, and process as many packets as possible.
        """
        t = time()
        if self.last_report + 5 < t and self.stream.tot_bytes > 0:
            self.last_report = t
            logger.debug(
                "%s: total/wasted bytes is %d/%d (%f wasted)" %
                (self.side, self.stream.tot_bytes, self.stream.wasted_bytes,
                 100 * float(self.stream.wasted_bytes) /
                 self.stream.tot_bytes))
        self.stream.append(self.recv(4092))

        if self.out_of_sync:
            data = self.stream.read(len(self.stream))
            self.stream.packet_finished()
            if self.other_side:
                self.other_side.send(data)
            return

        try:
            packet = parse_packet(self.stream, self.msg_spec, self.side)
            while packet != None:
                if packet['msgtype'] == 0x01 and self.side == 'client':
                    # Determine which protocol message definitions to use.
                    proto_version = packet['proto_version']
                    logger.info('Client requests protocol version %d' %
                                proto_version)
                    if not proto_version in messages.protocol:
                        logger.error("Unsupported protocol version %d" %
                                     proto_version)
                        self.handle_close()
                        return
                    self.msg_spec, self.other_side.msg_spec = messages.protocol[
                        proto_version]
                forward = True
                if self.plugin_mgr:
                    forwarding = self.plugin_mgr.filter(packet, self.side)
                    if forwarding and packet.modified:
                        packet['raw_bytes'] = self.msg_spec[
                            packet['msgtype']].parse(packet)
                if forwarding and self.other_side:
                    self.other_side.send(packet['raw_bytes'])
                # Since we know we're at a message boundary, we can inject
                # any messages in the queue.
                msgbytes = self.plugin_mgr.next_injected_msg_from(self.side)
                while self.other_side and msgbytes is not None:
                    self.other_side.send(msgbytes)
                    msgbytes = self.plugin_mgr.next_injected_msg_from(
                        self.side)

                # Attempt to parse the next packet.
                packet = parse_packet(self.stream, self.msg_spec, self.side)
        except PartialPacketException:
            pass  # Not all data for the current packet is available.
        except Exception:
            logger.error(
                "MinecraftProxy for %s caught exception, out of sync" %
                self.side)
            logger.error(traceback.format_exc())
            logger.debug("Current stream buffer: %s" % repr(self.stream.buf))
            self.out_of_sync = True
            self.stream.reset()

    def handle_close(self):
        """Call shutdown handler."""
        logger.info("%s socket closed.", self.side)
        self.close()
        if self.other_side is not None:
            logger.info("shutting down other side")
            self.other_side.other_side = None
            self.other_side.close()
            self.other_side = None
            logger.info("shutting down plugin manager")
            self.plugin_mgr.destroy()
Exemple #3
0
class MinecraftProxy(gevent.Greenlet):
    """Proxies a packet stream from a Minecraft client or server.
    """
    def __init__(self, src_sock, other_side=None):
        """Proxies one side of a client-server connection.

        MinecraftProxy instances are created in pairs that have references to
        one another. Since a client initiates a connection, the client side of
        the pair is always created first, with other_side = None. The creator
        of the client proxy is then responsible for connecting to the server
        and creating a server proxy with other_side=client. Finally, the
        proxy creator should do client_proxy.other_side = server_proxy.
        """
        super(MinecraftProxy, self).__init__()

        self.sock = src_sock
        self.closed = False
        self.plugin_mgr = None
        self.other_side = other_side
        self.rsa_key = None
        self.shared_secret = None
        self.challenge_token = None
        self.send_cipher = None
        self.recv_cipher = None
        if other_side == None:
            self.side = 'client'
            self.msg_spec = messages.protocol[0][0]
            self.rsa_key = rsa_key
            self.username = None
        else:
            self.side = 'server'
            self.msg_spec = messages.protocol[0][1]
            self.other_side.other_side = self
            self.shared_secret = encryption.generate_shared_secret()
        self.stream = Stream()
        self.last_report = 0
        self.msg_queue = []
        self.out_of_sync = False

    def handle_read(self):
        """Read all available bytes, and process as many packets as possible.
        """
        t = time()
        if self.last_report + 5 < t and self.stream.tot_bytes > 0:
            self.last_report = t
            logger.debug(
                "%s: total/wasted bytes is %d/%d (%f wasted)" %
                (self.side, self.stream.tot_bytes, self.stream.wasted_bytes,
                 100 * float(self.stream.wasted_bytes) /
                 self.stream.tot_bytes))
        self.stream.append(self.recv(4092))

        if self.out_of_sync:
            data = self.stream.read(len(self.stream))
            self.stream.packet_finished()
            if self.other_side:
                self.other_side.send(data)
            return

        try:
            packet = parse_packet(self.stream, self.msg_spec, self.side)
            while packet != None:
                rebuild = False
                if packet['msgtype'] == 0x02 and self.side == 'client':
                    # Determine which protocol message definitions to use.
                    proto_version = packet['proto_version']
                    logger.info('Client requests protocol version %d' %
                                proto_version)
                    if not proto_version in messages.protocol:
                        logger.error("Unsupported protocol version %d" %
                                     proto_version)
                        self.handle_close()
                        return
                    self.username = packet['username']
                    self.msg_spec, self.other_side.msg_spec = messages.protocol[
                        proto_version]
                    self.cipher = encryption.encryption_for_version(
                        proto_version)
                    self.other_side.cipher = self.cipher
                elif packet['msgtype'] == 0xfd:
                    self.rsa_key = encryption.decode_public_key(
                        packet['public_key'])
                    self.encoded_rsa_key = packet['public_key']
                    packet['public_key'] = encryption.encode_public_key(
                        self.other_side.rsa_key)
                    if 'challenge_token' in packet:
                        self.challenge_token = packet['challenge_token']
                        self.other_side.challenge_token = self.challenge_token
                    self.other_side.server_id = packet['server_id']
                    if check_auth:
                        packet['server_id'] = encryption.generate_server_id()
                    else:
                        packet['server_id'] = "-"
                    self.server_id = packet['server_id']
                    rebuild = True
                elif packet['msgtype'] == 0xfc and self.side == 'client':
                    self.shared_secret = encryption.decrypt_shared_secret(
                        packet['shared_secret'], self.rsa_key)
                    if (len(self.shared_secret) > 16
                            and self.cipher == encryption.RC4):
                        logger.error("Unsupported protocol version")
                        self.handle_close()
                        return
                    packet['shared_secret'] = encryption.encrypt_shared_secret(
                        self.other_side.shared_secret, self.other_side.rsa_key)
                    if 'challenge_token' in packet:
                        challenge_token = encryption.decrypt_shared_secret(
                            packet['challenge_token'], self.rsa_key)
                        if challenge_token != self.challenge_token:
                            self.kick("Invalid client reply")
                            return
                        packet[
                            'challenge_token'] = encryption.encrypt_shared_secret(
                                self.other_side.challenge_token,
                                self.other_side.rsa_key)
                    if auth:
                        logger.info("Authenticating on server")
                        auth.join_server(self.server_id,
                                         self.other_side.shared_secret,
                                         self.other_side.rsa_key)
                    if check_auth:
                        logger.info("Checking authenticity")
                        if not Authenticator.check_player(
                                self.username, self.other_side.server_id,
                                self.shared_secret, self.rsa_key):
                            self.kick("Unable to verify username")
                            return
                    rebuild = True
                elif packet['msgtype'] == 0xfc and self.side == 'server':
                    logger.debug("Starting encryption")
                    self.start_cipher()
                forward = True
                if self.plugin_mgr:
                    forwarding = self.plugin_mgr.filter(packet, self.side)
                    if forwarding and packet.modified:
                        rebuild = True
                if rebuild:
                    packet['raw_bytes'] = self.msg_spec[
                        packet['msgtype']].emit(packet)
                if forwarding and self.other_side is not None:
                    self.other_side.send(packet['raw_bytes'])
                if packet['msgtype'] == 0xfc and self.side == 'server':
                    self.other_side.start_cipher()
                # Since we know we're at a message boundary, we can inject
                # any messages in the queue.
                msgbytes = self.plugin_mgr.next_injected_msg_from(self.side)
                while self.other_side and msgbytes is not None:
                    self.other_side.send(msgbytes)
                    msgbytes = self.plugin_mgr.next_injected_msg_from(
                        self.side)

                # Attempt to parse the next packet.
                packet = parse_packet(self.stream, self.msg_spec, self.side)
        except PartialPacketException:
            pass  # Not all data for the current packet is available.
        except Exception:
            logger.error(
                "MinecraftProxy for %s caught exception, out of sync" %
                self.side)
            logger.error(traceback.format_exc())
            logger.debug("Current stream buffer: %s" % repr(self.stream.buf))
            self.out_of_sync = True
            self.stream.reset()

    def kick(self, reason):
        self.send(self.msg_spec[0xff].emit({'reason': reason}))
        self.handle_close()

    def handle_close(self):
        """Call shutdown handler."""
        logger.info("%s socket closed.", self.side)
        self.close()
        if self.other_side is not None:
            logger.info("shutting down other side")
            self.other_side.other_side = None
            self.other_side.close()
            self.other_side = None
            logger.info("shutting down plugin manager")
            self.plugin_mgr.destroy()

    def start_cipher(self):
        self.recv_cipher = self.cipher(self.shared_secret)
        self.send_cipher = self.cipher(self.shared_secret)

    def recv(self, buffer_size):
        data = self.sock.recv(buffer_size)
        if not data:
            raise EOFException()
        if self.recv_cipher is None:
            return data
        return self.recv_cipher.decrypt(data)

    def send(self, data):
        if self.send_cipher is not None:
            data = self.send_cipher.encrypt(data)
        return self.sock.sendall(data)

    def close(self):
        self.closed = True
        self.sock.close()

    def _run(self):
        while not self.closed:
            try:
                self.handle_read()
            except EOFException:
                break
            gevent.sleep()
        self.close()
        self.handle_close()
Exemple #4
0
class MinecraftProxy(gevent.Greenlet):
    """Proxies a packet stream from a Minecraft client or server.
    """

    def __init__(self, src_sock, other_side=None):
        """Proxies one side of a client-server connection.

        MinecraftProxy instances are created in pairs that have references to
        one another. Since a client initiates a connection, the client side of
        the pair is always created first, with other_side = None. The creator
        of the client proxy is then responsible for connecting to the server
        and creating a server proxy with other_side=client. Finally, the
        proxy creator should do client_proxy.other_side = server_proxy.
        """
        super(MinecraftProxy, self).__init__()

        self.sock = src_sock
        self.closed = False
        self.plugin_mgr = None
        self.other_side = other_side
        self.rsa_key = None
        self.shared_secret = None
        self.challenge_token = None
        self.send_cipher = None
        self.recv_cipher = None
        self.protocol = messages.protocol[0]
        if other_side is None:
            self.side = 'client'
            self.rsa_key = rsa_key
            self.username = None
        else:
            self.side = 'server'
            self.other_side.other_side = self
            self.shared_secret = encryption.generate_shared_secret()
        self.stream = Stream()
        self.last_report = 0
        self.msg_queue = []
        self.out_of_sync = False

    def handle_read(self):
        """Read all available bytes, and process as many packets as possible.
        """
        t = time()
        if self.last_report + 5 < t and self.stream.tot_bytes > 0:
            self.last_report = t
            logger.debug(
                "%s: total/wasted bytes is %d/%d (%f wasted)" % (
                self.side, self.stream.tot_bytes, self.stream.wasted_bytes,
                100 * float(self.stream.wasted_bytes) / self.stream.tot_bytes)
            )
        self.stream.append(self.recv(4092))

        if self.out_of_sync:
            data = self.stream.read(len(self.stream))
            self.stream.packet_finished()
            if self.other_side:
                self.other_side.send(data)
            return

        try:
            while True:
                packet = self.protocol.parse_message(self.stream, self.side)
                if packet.id == 0x02 and self.side == 'client':
                    # Determine which protocol message definitions to use.
                    proto_version = packet.version
                    logger.info('Client requests protocol version %d' %
                                proto_version)
                    if not proto_version in messages.protocol:
                        logger.error("Unsupported protocol version %d" %
                                     proto_version)
                        self.handle_close()
                        return
                    self.username = packet.username
                    self.protocol = messages.protocol[proto_version]
                    self.other_side.protocol = self.protocol
                    self.cipher = encryption.encryption_for_version(
                        proto_version
                    )
                    self.other_side.cipher = self.cipher
                elif packet.id == 0xfd:
                    self.rsa_key = encryption.decode_public_key(
                        packet.public_key
                    )
                    self.encoded_rsa_key = packet.public_key
                    packet.public_key = encryption.encode_public_key(
                        self.other_side.rsa_key
                    )
                    self.challenge_token = packet.challenge_token
                    self.other_side.challenge_token = self.challenge_token
                    self.other_side.server_id = packet.server_id
                    if check_auth:
                        packet.server_id = encryption.generate_server_id()
                    else:
                        packet.server_id = "-"
                    self.server_id = packet.server_id
                elif packet.id == 0xfc and self.side == 'client':
                    self.shared_secret = encryption.decrypt_shared_secret(
                        packet.shared_secret,
                        self.rsa_key
                    )
                    if (len(self.shared_secret) > 16 and
                            self.cipher == encryption.RC4):
                        logger.error("Unsupported protocol version")
                        self.handle_close()
                        return
                    packet.shared_secret = encryption.encrypt_shared_secret(
                        self.other_side.shared_secret,
                        self.other_side.rsa_key
                    )
                    challenge_token = encryption.decrypt_shared_secret(
                        packet.challenge_token, self.rsa_key
                    )
                    if challenge_token != self.challenge_token:
                        self.kick("Invalid client reply")
                        return
                    packet.challenge_token = encryption.encrypt_shared_secret(
                        self.other_side.challenge_token,
                        self.other_side.rsa_key
                    )
                    if auth:
                        logger.info("Authenticating on server")
                        auth.join_server(self.server_id,
                                         self.other_side.shared_secret,
                                         self.other_side.rsa_key)
                    if check_auth:
                        logger.info("Checking authenticity")
                        if not authentication.Authenticator.check_player(
                            self.username, self.other_side.server_id,
                            self.shared_secret, self.rsa_key
                        ):
                            self.kick("Unable to verify username")
                            return
                elif packet.id == 0xfc and self.side == 'server':
                    logger.debug("Starting encryption")
                    self.start_cipher()
                forwarding = True
                if self.plugin_mgr:
                    forwarding = self.plugin_mgr.filter(packet, self.side)
                if forwarding and self.other_side is not None:
                    self.other_side.send(packet.emit())
                if packet.id == 0xfc and self.side == 'server':
                    self.other_side.start_cipher()
                # Since we know we're at a message boundary, we can inject
                # any messages in the queue.
                while self.other_side:
                    msg = self.plugin_mgr.next_injected_msg_from(self.side)
                    if msg is None:
                        break
                    self.other_side.send(msg.emit())

        except PartialPacketException:
            pass  # Not all data for the current packet is available.
        except Exception:
            logger.error("MinecraftProxy for %s caught exception, out of sync"
                         % self.side)
            logger.error(traceback.format_exc())
            logger.debug("Current stream buffer: %s" % repr(self.stream.buf))
            self.out_of_sync = True
            self.stream.reset()

    def kick(self, reason):
        self.send(self.protocol[0xff](reason=reason).emit())
        self.handle_close()

    def handle_close(self):
        """Call shutdown handler."""
        logger.info("%s socket closed.", self.side)
        self.close()
        if self.other_side is not None:
            logger.info("shutting down other side")
            self.other_side.other_side = None
            self.other_side.close()
            self.other_side = None
            logger.info("shutting down plugin manager")
            self.plugin_mgr.destroy()

    def start_cipher(self):
        self.recv_cipher = self.cipher(self.shared_secret)
        self.send_cipher = self.cipher(self.shared_secret)

    def recv(self, buffer_size):
        data = self.sock.recv(buffer_size)
        if not data:
            raise EOFException()
        if self.recv_cipher is None:
            return data
        return self.recv_cipher.decrypt(data)

    def send(self, data):
        if self.send_cipher is not None:
            data = self.send_cipher.encrypt(data)
        return self.sock.sendall(data)

    def close(self):
        self.closed = True
        self.sock.close()

    def _run(self):
        while not self.closed:
            try:
                self.handle_read()
            except EOFException:
                break
            gevent.sleep()
        self.close()
        self.handle_close()