From 23616e6fe46f16762a7d5b494c862b21f2cb55f4 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Mon, 29 Aug 2022 15:23:16 -0400 Subject: [PATCH 1/4] Reallow split packets to be processed --- server/network/ao_protocol.py | 25 ++++++++++++++++++------- server/tsuserver.py | 4 ++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/server/network/ao_protocol.py b/server/network/ao_protocol.py index 1e2b674a9..10a3103ac 100644 --- a/server/network/ao_protocol.py +++ b/server/network/ao_protocol.py @@ -105,7 +105,7 @@ def connection_lost(self, exc): self.server.remove_client(self.client) self.ping_timeout.cancel() - def _get_messages(self): + def _get_messages(self) -> str: """ Parses out full messages from the buffer. :return: yields messages @@ -115,13 +115,20 @@ def _get_messages(self): self.buffer = spl[1] yield spl[0] + def _shortened_buffer(self) -> str: + short_buffer = self.buffer + if len(short_buffer) >= 512: + short_buffer = short_buffer[:500] + '...' + short_buffer[-12:] + + return f'{short_buffer} ({len(self.buffer)} bytes)' + def _process_message(self, msg): if len(msg) < 2: # This immediatelly kills any client that does not even try to follow the proper # client protocol msg = self.buffer if len(self.buffer) < 512 else self.buffer[:512] + '...' logger.log_server(f'Terminated {self.client.get_ipreal()} (packet too short): ' - f'sent {msg} ({len(self.buffer)} bytes)') + f'sent {self._shortened_buffer()}.') self.client.disconnect() return False @@ -175,9 +182,8 @@ def data_received(self, data): self.buffer = self.buffer.translate({ord(c): None for c in '\0'}) if len(self.buffer) > 8192: - msg = self.buffer if len(self.buffer) < 512 else self.buffer[:512] + '...' logger.log_server(f'Terminated {self.client.get_ipreal()} (packet too long): ' - f'sent {msg} ({len(self.buffer)} bytes)') + f'sent {self._shortened_buffer()}.') self.client.disconnect() return @@ -187,14 +193,19 @@ def data_received(self, data): if not self._process_message(msg): return - if not found_message: + if found_message: + return + + # Check if valid packet split by evil router on client side + buffer_command = self.buffer.split('#')[0] + if buffer_command not in self._net_cmd_dispatcher: # This immediatelly kills any client that does not even try to follow the proper # client protocol - msg = self.buffer if len(self.buffer) < 512 else self.buffer[:512] + '...' logger.log_server(f'Terminated {self.client.get_ipreal()} (packet syntax ' - f'unrecognized): sent {msg} ({len(self.buffer)} bytes)') + f'unrecognized): sent {self._shortened_buffer()}.') self.client.disconnect() + def _validate_net_cmd(self, args, *types, needs_auth=True): """ Makes sure the net command's arguments match expectations. diff --git a/server/tsuserver.py b/server/tsuserver.py index 5e8b776f7..fb327bba7 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -69,8 +69,8 @@ def __init__(self, protocol: AOProtocol = None, self.release = 4 self.major_version = 3 self.minor_version = 4 - self.segment_version = 'post1' - self.internal_version = '220821b' + self.segment_version = 'post2' + self.internal_version = 'M220829a' version_string = self.get_version_string() self.software = 'TsuserverDR {}'.format(version_string) self.version = 'TsuserverDR {} ({})'.format(version_string, self.internal_version) From b984abcf4923d13c2f3408768a280d3eda6dda4a Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 31 Aug 2022 10:31:17 -0400 Subject: [PATCH 2/4] Fix character list reloads crashing when someone is in server select screen --- server/area_manager.py | 8 +++++--- server/client_changearea.py | 2 +- server/client_manager.py | 24 ++++++++++++++---------- server/commands.py | 8 ++++---- server/tasker.py | 4 ++-- server/tsuserver.py | 4 ++-- 6 files changed, 28 insertions(+), 22 deletions(-) diff --git a/server/area_manager.py b/server/area_manager.py index 27a49b525..e4625aa92 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -340,8 +340,7 @@ def get_chars_unusable(self, allow_restricted: bool = False, if more_unavail_chars is None: more_unavail_chars = set() - unavailable = {x.char_id for x in self.clients if x.char_id is not None - and x.char_id >= 0} + unavailable = {x.char_id for x in self.clients if x.has_character()} unavailable |= more_unavail_chars restricted = {self.server.char_list.index(name) for name in self.restricted_chars} @@ -406,9 +405,12 @@ def is_char_available(self, char_id: int, allow_restricted: bool = False, is not found to be among the area's unusable characters. """ + if char_id < 0: + return True + unused = char_id in self.get_chars_unusable(allow_restricted=allow_restricted, more_unavail_chars=more_unavail_chars) - return char_id == -1 or not unused + return not unused def add_to_dicelog(self, client: ClientManager.Client, msg: str): """ diff --git a/server/client_changearea.py b/server/client_changearea.py index 811dabfb7..aeeba13c8 100644 --- a/server/client_changearea.py +++ b/server/client_changearea.py @@ -461,7 +461,7 @@ def notify_others(self, area: AreaManager.Area, old_dname: str, # Assuming this is not a spectator... # If autopassing, send OOC messages - if not ignore_autopass and not client.char_id < 0: + if not ignore_autopass and client.has_character(): self.notify_others_moving(client, old_area, '{} has left to the {}.'.format(old_dname, area.name), 'You hear footsteps going out of the room.') diff --git a/server/client_manager.py b/server/client_manager.py index b11359045..b5521a5fa 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -776,6 +776,12 @@ def showname_else_char_showname(self) -> str: return self.showname return self.char_showname + def has_character(self, char_id: int = None) -> bool: + if char_id is None: + char_id = self.char_id + + return char_id is not None and char_id >= 0 + def change_character(self, char_id: int, force: bool = False, target_area: AreaManager.Area = None, announce_zwatch: bool = True): @@ -812,7 +818,8 @@ def change_character(self, char_id: int, force: bool = False, # Code after this comment assumes the character change will be successful self.ever_chose_character = True - if old_char_id < 0 and char_id >= 0: # No longer spectator? + if not self.has_character() and self.has_character(char_id=char_id): + # No longer spectator? # Now bound by AFK rules self.server.tasker.create_task(self, ['as_afk_kick', self.area.afk_delay, self.area.afk_sendto]) @@ -826,7 +833,8 @@ def change_character(self, char_id: int, force: bool = False, f'and you are not logged in.') self.unfollow_user() - elif old_char_id >= 0 and char_id < 0: # Now a spectator? + elif self.has_character() and not self.has_character(char_id=char_id): + # Now a spectator? # No longer bound to AFK rules try: self.server.tasker.remove_task(self, ['as_afk_kick']) @@ -958,7 +966,7 @@ def notify_change_area(self, area: AreaManager.Area, old_char: str, just_me=just_me) def check_lurk(self): - if self.area.lurk_length > 0 and not self.is_staff() and self.char_id >= 0: + if self.area.lurk_length > 0 and not self.is_staff() and self.has_character(): self.server.tasker.create_task(self, ['as_lurk', self.area.lurk_length]) else: # Otherwise, end any existing lurk, if there is one try: @@ -1600,8 +1608,7 @@ def refresh_char_list(self): for x in unusable_ids: char_list[x] = -1 - # If not spectator - if self.char_id is not None and self.char_id >= 0: + if self.has_character(): char_list[self.char_id] = 0 # Self is always available self.send_command_dict('CharsCheck', { 'chars_status_ao2_list': char_list, @@ -1610,7 +1617,7 @@ def refresh_char_list(self): def refresh_visible_char_list(self): char_list = [0] * len(self.server.char_list) unusable_ids = {c.char_id for c in self.get_visible_clients(self.area) - if c.char_id >= 0} + if c.has_character()} if not self.is_staff(): unusable_ids |= {self.server.char_list.index(name) for name in self.area.restricted_chars} @@ -1619,7 +1626,7 @@ def refresh_visible_char_list(self): char_list[x] = -1 # Self is always available - if self.char_id is not None and self.char_id >= 0: + if self.has_character(): char_list[self.char_id] = 0 self.send_command_dict('CharsCheck', { 'chars_status_ao2_list': char_list, @@ -1889,9 +1896,6 @@ def get_char_name(self, char_id: int = None) -> str: return self.server.server_select_name return self.server.char_list[char_id] - def has_character(self) -> bool: - return self.char_id not in [-1, None] - def get_showname_history(self) -> str: info = '== Showname history of client {} =='.format(self.id) diff --git a/server/commands.py b/server/commands.py index 1b6a0fc7c..694c0bd54 100644 --- a/server/commands.py +++ b/server/commands.py @@ -2570,8 +2570,8 @@ def ooc_cmd_follow(client: ClientManager.Client, arg: str): Constants.assert_command(client, arg, is_staff=True, parameters='=1') except ClientError.UnauthorizedError: Constants.assert_command(client, arg, parameters='=1') - if not client.char_id < 0: - raise ClientError('You must be authorized to follow without being in spectator mode.') + if client.has_character(): + raise ClientError('You must be authorized to follow while having a character.') if client.party: raise PartyError('You cannot follow someone while in a party.') @@ -9297,8 +9297,8 @@ def ooc_cmd_unfollow(client: ClientManager.Client, arg: str): Constants.assert_command(client, arg, is_staff=True, parameters='=0') except ClientError.UnauthorizedError: Constants.assert_command(client, arg, parameters='=0') - if not client.char_id < 0: - raise ClientError('You must be authorized to unfollow without being in spectator mode.') + if client.has_character(): + raise ClientError('You must be authorized to unfollow while having a character.') client.unfollow_user() diff --git a/server/tasker.py b/server/tasker.py index 5931809e2..7fe514dee 100644 --- a/server/tasker.py +++ b/server/tasker.py @@ -223,7 +223,7 @@ async def as_afk_kick(self, client: ClientManager.Client, args: List): raise ServerError(info) if client.area.id == afk_sendto: # Don't try and kick back to same area return - if client.char_id < 0: # Assumes spectators are exempted from AFK kicks + if not client.has_character(): # Assumes spectators are exempted from AFK kicks return if client.is_staff(): # Assumes staff are exempted from AFK kicks return @@ -728,6 +728,6 @@ async def as_phantom_peek(self, client: ClientManager.Client, args: List): return if client.is_staff(): return - if client.char_id is None or client.char_id < 0: + if not client.has_character(): return client.send_ooc('You feel as though you are being peeked on.') diff --git a/server/tsuserver.py b/server/tsuserver.py index fb327bba7..3ccebec5b 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -70,7 +70,7 @@ def __init__(self, protocol: AOProtocol = None, self.major_version = 3 self.minor_version = 4 self.segment_version = 'post2' - self.internal_version = 'M220829a' + self.internal_version = 'M220830a' version_string = self.get_version_string() self.software = 'TsuserverDR {}'.format(version_string) self.version = 'TsuserverDR {} ({})'.format(version_string, self.internal_version) @@ -408,7 +408,7 @@ def load_characters(self) -> List[str]: target_char_id = -1 old_char_name = client.get_char_name() - if client.char_id < 0: + if not client.has_character(): # Do nothing for spectators pass elif old_char_name not in new_chars: From c09ebc8ec385b6f9f57bbc2d9a5d255dabc77625 Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 31 Aug 2022 10:34:00 -0400 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a3714c1f..269fa7eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -796,3 +796,7 @@ ### 220821b (4.3.4-post1) * Fixed area list reloads crashing + +### 220830a (4.3.4-post2) +* Fixed clients sending legitimate split packets being abnormally disconnected +* Fixed character list reloads crashing when someone is in server select From 03e69bdaaf0fc4c1c0b52c480c6b9ad5272f29fe Mon Sep 17 00:00:00 2001 From: Chrezm Date: Wed, 31 Aug 2022 10:39:30 -0400 Subject: [PATCH 4/4] Bump version --- server/tsuserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/tsuserver.py b/server/tsuserver.py index 3ccebec5b..6f898dcb2 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -70,7 +70,7 @@ def __init__(self, protocol: AOProtocol = None, self.major_version = 3 self.minor_version = 4 self.segment_version = 'post2' - self.internal_version = 'M220830a' + self.internal_version = '220830a' version_string = self.get_version_string() self.software = 'TsuserverDR {}'.format(version_string) self.version = 'TsuserverDR {} ({})'.format(version_string, self.internal_version)