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()
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()
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()
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()