def _handle_packet(self, packet): """ Find and call a handler for that packet. It is possible that we do not know the opcode, which is not a problem. If it is known, it should be legal (in a valid state) and we need to enforce a handler to it, even if it's just NopHandler. If I don't feel like adding the simplest handler for an opcode, it probably shouldn't be in the OpCode enum in the first place. If an opcode is not valid, we do not throw an error anymore because there are some cases where misc opcodes can be received just after changing states. """ opcode, packet_data = self._parse_packet(packet) if opcode is None: return if ( self.state not in self.UNMANAGED_STATES and opcode not in self.UNMANAGED_OPS and not self.opcode_is_legal(opcode) ): LOG.debug("{}: received illegal opcode {} in state {}".format( type(self).__name__, opcode.name, self.state.name )) return handler_class = self.OP_HANDLERS.get(opcode, self.DEFAULT_HANDLER) self._call_handler(handler_class, packet_data)
def create_char(char_values): """ Try to create a new character and add it to the database. Return 0 on success, 1 on unspecified failure, 2 on name already used, 3 if the race and class combination isn't supported. The arg char_values is a tuple containing the Character data in the order they're defined, from name to features. This last value has to be a tuple with CharacterFeatures fields values. This should check of other things like account char limit etc. """ consts = _CharacterCreator._get_constants(char_values) if consts is None: return 3 if CharacterManager.does_char_with_name_exist(char_values["name"]): return 2 char_data = _CharacterCreator._try_create_char(char_values, consts) if char_data is None: return 1 _CharacterCreator._add_default_skills(char_data, consts) _CharacterCreator._add_default_spells(char_data, consts) LOG.debug("Character " + char_data.name + " created.") return 0
def process(self): self._parse_packet(self.packet) # We start by loading the session key because in any case the client # expects an encrypted response. self._load_session_key() if not self.session_key: LOG.warning("A client not logged in tried to join world server.") return self.conn.MAIN_ERROR_STATE, None self._setup_encryption() if self.build != int(CONFIG["general"]["build"]): LOG.warning("Wrong build tried to auth to world server: {}".format( str(self.build) )) error_code = AuthSessionResponseCode.AUTH_VERSION_MISMATCH response = self._get_failure_packet(error_code) return self.conn.MAIN_ERROR_STATE, response self._generate_server_hash() if self.server_hash != self.client_hash: LOG.warning("Wrong client hash in world server auth.") error_code = AuthSessionResponseCode.AUTH_REJECT response = self._get_failure_packet(error_code) return self.conn.MAIN_ERROR_STATE, response # Once the session cipher is up and the client is fully checked, # accept the authentication and move on. LOG.debug("World server auth OK.") response = self._get_success_packet() return WorldConnectionState.AUTH_OK, response
def _get_response_packet(self, manager_code): response_code = { 0: CharDeleteResponseCode.SUCCESS, 1: CharDeleteResponseCode.FAILED }.get(manager_code, 1) LOG.debug("Character deletion status: " + str(response_code.name)) response_data = self.RESPONSE_BIN.pack(response_code.value) return WorldPacket(OpCode.SMSG_CHAR_DELETE, response_data)
def _actions_after_main_loop(self): LOG.debug("WorldConnection: session ended.") if self.account and self.session_cipher: AccountSessionManager.delete_session(self.account) if self.player: self.unset_player() with self.server.world_connections_lock: self.server.world_connections.remove(self)
def process(self): if "worldport_ack_pending" in self.conn.shared_data: LOG.debug( "Received expected " + str(OpCode.MSG_MOVE_WORLDPORT_ACK) ) del self.conn.shared_data["worldport_ack_pending"] return None, None else: LOG.error( "Received unexpected " + str(OpCode.MSG_MOVE_WORLDPORT_ACK) ) return self.conn.MAIN_ERROR_STATE, None
def _try_recv_packet(self): packet, has_timeout = None, False try: packet = self._recv_packet() if packet is None: LOG.debug("Client closed the connection.") except socket.timeout: has_timeout = True return packet, has_timeout
def _get_response_packet(self, manager_code): response_code = { 0: CharCreateResponseCode.SUCCESS, 1: CharCreateResponseCode.FAILED, 2: CharCreateResponseCode.NAME_IN_USE, 3: CharCreateResponseCode.ERROR }.get(manager_code, 1) LOG.debug("Character creation status: " + str(response_code.name)) response_data = self.RESPONSE_BIN.pack(response_code.value) return WorldPacket(OpCode.SMSG_CHAR_CREATE, response_data)
def process(self): self._parse_packet(self.packet) self._generate_local_proof() if self.client_proof == self.local_proof: LOG.debug("Reconnection: correct proof") response = self._get_success_response() return LoginConnectionState.RECON_PROOF, response else: LOG.warning("Reconnection: wrong proof!") response = self._get_failure_response() return LoginConnectionState.CLOSED, response
def _maintain_realm_list(self): """ Maintain realmlist by removing realms not updated for a while. """ with self.locks["realms"]: to_remove = [] for realm in self.realms: update_delay = time.time() - self.realms[realm]["last_update"] if update_delay > self.REALM_MAX_UPDATE_TIME: to_remove.append(realm) LOG.debug("Realm " + realm + " down, removed from list.") for realm_to_remove in to_remove: del self.realms[realm_to_remove]
def _process_reconnection(self): session = AccountSessionManager.get_session(self.account_name) if session is not None: LOG.debug("Reconnection: account was logged in.") self.conn.account = ReconChallenge._get_session_account(session) self.conn.recon_challenge = os.urandom(16) response = self._get_success_response() return LoginConnectionState.RECON_CHALL, response else: LOG.warning("Reconnection: account wasn't logged in!") response = self._get_failure_response() return LoginConnectionState.CLOSED, response
def close(self): """ Close the database connection, return True on success. """ with self.num_connections_lock: self.num_connections -= 1 if self.num_connections == 0: try: self.database.close() if DEBUG: LOG.debug("[db] Database closed") except OperationalError as exc: _DbConnector.log_error("close", exc) return False return True
def process(self): self._parse_packet(self.packet) LOG.debug("NameQuery: GUID {:X}".format(self.guid)) object_manager = self.conn.server.object_manager unit = object_manager.get_player(self.guid) if unit is None: LOG.warning("NameQueryHandler: couldn't find player {:X}".format( self.guid )) return None, None response = self._get_response_packet(unit) return None, response
def connect(self): """ Connect to the database, return True on success. """ with self.num_connections_lock: assert self.num_connections >= 0 self.num_connections += 1 if self.num_connections == 1: try: self.database.connect() if DEBUG: LOG.debug("[db] Database connected") except OperationalError as exc: _DbConnector.log_error("connect", exc) self.num_connections -= 1 return False return True
def create_account(account_name, password): """ Create a valid account and add it to the database, or None if the arguments are invalid. """ if not ACCOUNT_NAME_RE.match(account_name): LOG.debug("Invalid account name.") return None account = Account( name = account_name.upper() , status = AccountStatus.NOT_READY.value ) Srp.generate_account_srp_data(account, password) account.status = AccountStatus.VALID.value account.save() AccountDataManager.create_account_data(account) return account
def _delete_char(guid): character = CharacterData.get(CharacterData.guid == guid) _CharacterDestructor._delete_char_skills(character) _CharacterDestructor._delete_char_spells(character) features = character.features stats = character.stats position = character.position character.delete_instance() features.delete_instance() stats.delete_instance() position.delete_instance() LOG.debug("Character " + str(guid) + " deleted.") return 0
def process(self): self._parse_packet(self.packet) account = self.conn.account verifier = account.srp_verifier_as_int self.conn.srp.generate_session_key(self.client_ephemeral, verifier) self.conn.srp.generate_client_proof(self.client_ephemeral, account) local_client_proof = self.conn.srp.client_proof if local_client_proof == self.client_proof: LOG.debug("Login: authenticated!") self.conn.accept_login() self.conn.srp.generate_server_proof(self.client_ephemeral) response = self._get_success_response() return LoginConnectionState.SENT_PROOF, response else: LOG.warning("Login: wrong proof!") response = self._get_failure_response() return LoginConnectionState.CLOSED, response
def _actions_before_main_loop(self): LOG.debug("Sending auth challenge to setup session cipher.") self._send_auth_challenge()
def _actions_after_main_loop(self): """ Close connection with client. """ LOG.debug("LoginConnection: session ended.") self.socket.close()
def process(self): """ Process the challenge packet: parse its data and check whether that account name can log. """ self._parse_packet(self.packet) LOG.debug("Login: account " + self.account_name) return self._process_account()