From 2bd43391a6912db708d2c95416ada295b7fbe177 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 13 Dec 2023 21:07:13 -0800 Subject: [PATCH] mitigate potential $persist abuse --- src/ChatCommands.cc | 2 ++ src/Lobby.cc | 65 ++++++++++++++++++++++++++++++++++++++++++++- src/Lobby.hh | 2 ++ src/SendCommands.cc | 41 ++++++++++++++-------------- 4 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 4a3a1a6a..c133a6d0 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -484,6 +484,8 @@ static void server_command_persist(shared_ptr c, const std::string&) { send_text_message(c, "$C6Private lobbies\ncannot be marked\npersistent"); } else if (l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) || l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS)) { send_text_message(c, "$C6Games cannot be\npersistent if a\nquest has already\nbegun"); + } else if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) { + send_text_message(c, "$C6Spectator teams\ncannot be marked\npersistent"); } else { l->toggle_flag(Lobby::Flag::PERSISTENT); send_text_message_printf(l, "Lobby persistence\n%s", l->check_flag(Lobby::Flag::PERSISTENT) ? "enabled" : "disabled"); diff --git a/src/Lobby.cc b/src/Lobby.cc index 85254090..5d66ada9 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -427,7 +427,10 @@ void Lobby::remove_client(shared_ptr c) { // If the lobby is persistent but has an idle timeout, make it expire after // the specified time - if ((this->count_clients() == 0) && this->check_flag(Flag::PERSISTENT) && (this->idle_timeout_usecs > 0)) { + if ((this->count_clients() == 0) && + this->check_flag(Flag::PERSISTENT) && + !this->check_flag(Flag::DEFAULT) && + (this->idle_timeout_usecs > 0)) { auto tv = usecs_to_timeval(this->idle_timeout_usecs); event_add(this->idle_timeout_event.get(), &tv); this->log.info("Idle timeout scheduled"); @@ -586,3 +589,63 @@ void Lobby::dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx) { event_del(l->idle_timeout_event.get()); } } + +bool Lobby::compare_shared(const shared_ptr& a, const shared_ptr& b) { + // Sort keys: + // 1. Priority class: has free space < empty (persistent) < full < non-joinable (in quest/battle) + // 2. Password: public < locked + // 3. Game mode: Normal < Battle < Challenge < Solo + // 4. Episode: 1 < 2 < 4 + // 5. Difficulty: Normal < Hard < Very Hard < Ultimate + // 6. Game name + static auto get_priority = +[](const shared_ptr& l) -> size_t { + if (l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) || l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) { + return 4; + } + size_t num_clients = l->count_clients(); + if (num_clients == l->max_clients) { + return 3; + } + if (num_clients == 0) { + return 2; + } + return 1; + }; + size_t a_priority = get_priority(a); + size_t b_priority = get_priority(b); + if (a_priority < b_priority) { + return true; + } else if (a_priority > b_priority) { + return false; + } + + if (a->password.empty() && !b->password.empty()) { + return true; + } else if (!a->password.empty() && b->password.empty()) { + return false; + } + + size_t a_mode = static_cast(a->mode); + size_t b_mode = static_cast(b->mode); + if (a_mode < b_mode) { + return true; + } else if (a_mode > b_mode) { + return false; + } + + size_t a_episode = static_cast(a->episode); + size_t b_episode = static_cast(b->episode); + if (a_episode < b_episode) { + return true; + } else if (a_episode > b_episode) { + return false; + } + + if (a->difficulty < b->difficulty) { + return true; + } else if (a->difficulty > b->difficulty) { + return false; + } + + return a->name < b->name; +} diff --git a/src/Lobby.hh b/src/Lobby.hh index 3aacaad8..a69664e1 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -207,4 +207,6 @@ struct Lobby : public std::enable_shared_from_this { std::unordered_map> clients_by_serial_number() const; static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx); + + static bool compare_shared(const std::shared_ptr& a, const std::shared_ptr& b); }; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 20c8ed53..3c2421b3 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -4,12 +4,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include "CommandFormats.hh" #include "Compression.hh" @@ -61,8 +63,7 @@ const unordered_set bb_crypt_initial_client_commands({ string("\xDC\x00\xDB\x00\x00\x00\x00\x00", 8), }); -void send_command(std::shared_ptr c, uint16_t command, - uint32_t flag, const std::vector>& blocks) { +void send_command(shared_ptr c, uint16_t command, uint32_t flag, const vector>& blocks) { c->channel.send(command, flag, blocks); } @@ -329,7 +330,7 @@ void send_quest_buffer_overflow(shared_ptr c) { void empty_function_call_response_handler(uint32_t, uint32_t) {} -void prepare_client_for_patches(shared_ptr c, std::function on_complete) { +void prepare_client_for_patches(shared_ptr c, function on_complete) { auto s = c->require_server_state(); auto send_version_detect = [s, wc = weak_ptr(c), on_complete]() -> void { @@ -1326,20 +1327,20 @@ void send_game_menu_t( e.flags = 0x04; } + set, bool (*)(const shared_ptr&, const shared_ptr&)> games(Lobby::compare_shared); for (shared_ptr l : s->all_lobbies()) { - if (!l->is_game()) { - continue; - } - if (!l->version_is_allowed(c->version())) { - continue; - } - if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) != is_spectator_team_list) { - continue; - } - if (show_tournaments_only && !l->tournament_match) { - continue; + if (l->is_game() && + l->version_is_allowed(c->version()) && + (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) == is_spectator_team_list) && + (!show_tournaments_only || l->tournament_match)) { + games.emplace(l); } + } + for (const auto& l : games) { + if (entries.size() >= 0x41) { + break; + } uint8_t episode_num; switch (l->episode) { case Episode::EP1: @@ -2303,7 +2304,7 @@ void send_set_player_visibility(shared_ptr l, shared_ptr c, bool send_command_t(l, 0x60, 0x00, cmd); } -void send_artificial_item_state(std::shared_ptr c) { +void send_artificial_item_state(shared_ptr c) { auto l = c->require_lobby(); if (c->lobby_client_id != l->leader_id) { throw runtime_error("artificial item state can only be sent to the leader"); @@ -2516,7 +2517,7 @@ void send_give_experience(shared_ptr c, uint32_t amount) { send_command_t(l, 0x60, 0x00, cmd); } -void send_set_exp_multiplier(std::shared_ptr l) { +void send_set_exp_multiplier(shared_ptr l) { if (l->base_version != Version::BB_V4) { throw logic_error("6xDD can only be sent to BB clients"); } @@ -3442,7 +3443,7 @@ void send_all_nearby_team_metadatas_to_client(shared_ptr c, bool is_13EA send_command_vt(c, is_13EA ? 0x13EA : 0x15EA, entries.size(), entries); } -void send_update_team_reward_flags(std::shared_ptr c) { +void send_update_team_reward_flags(shared_ptr c) { auto team = c->team(); send_command(c, 0x1DEA, team ? team->reward_flags : 0x00000000); } @@ -3479,7 +3480,7 @@ void send_team_member_list(shared_ptr c) { send_command_t_vt(c, 0x09EA, 0x00000000, header, entries); } -void send_intra_team_ranking(std::shared_ptr c) { +void send_intra_team_ranking(shared_ptr c) { auto team = c->team(); if (!team) { throw runtime_error("client is not in a team"); @@ -3515,7 +3516,7 @@ void send_intra_team_ranking(std::shared_ptr c) { send_command_t_vt(c, 0x18EA, 0x00000000, cmd, entries); } -void send_cross_team_ranking(std::shared_ptr c) { +void send_cross_team_ranking(shared_ptr c) { auto s = c->require_server_state(); // TODO: At some point we should maintain a sorted index instead of sorting @@ -3543,7 +3544,7 @@ void send_cross_team_ranking(std::shared_ptr c) { send_command_t_vt(c, 0x1CEA, 0x00000000, cmd, entries); } -void send_team_reward_list(std::shared_ptr c, bool show_purchased) { +void send_team_reward_list(shared_ptr c, bool show_purchased) { auto team = c->team(); if (!team) { throw runtime_error("user is not in a team");