mitigate potential $persist abuse
This commit is contained in:
@@ -484,6 +484,8 @@ static void server_command_persist(shared_ptr<Client> c, const std::string&) {
|
|||||||
send_text_message(c, "$C6Private lobbies\ncannot be marked\npersistent");
|
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)) {
|
} 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");
|
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 {
|
} else {
|
||||||
l->toggle_flag(Lobby::Flag::PERSISTENT);
|
l->toggle_flag(Lobby::Flag::PERSISTENT);
|
||||||
send_text_message_printf(l, "Lobby persistence\n%s", l->check_flag(Lobby::Flag::PERSISTENT) ? "enabled" : "disabled");
|
send_text_message_printf(l, "Lobby persistence\n%s", l->check_flag(Lobby::Flag::PERSISTENT) ? "enabled" : "disabled");
|
||||||
|
|||||||
+64
-1
@@ -427,7 +427,10 @@ void Lobby::remove_client(shared_ptr<Client> c) {
|
|||||||
|
|
||||||
// If the lobby is persistent but has an idle timeout, make it expire after
|
// If the lobby is persistent but has an idle timeout, make it expire after
|
||||||
// the specified time
|
// 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);
|
auto tv = usecs_to_timeval(this->idle_timeout_usecs);
|
||||||
event_add(this->idle_timeout_event.get(), &tv);
|
event_add(this->idle_timeout_event.get(), &tv);
|
||||||
this->log.info("Idle timeout scheduled");
|
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());
|
event_del(l->idle_timeout_event.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Lobby::compare_shared(const shared_ptr<const Lobby>& a, const shared_ptr<const Lobby>& 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<const Lobby>& 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<size_t>(a->mode);
|
||||||
|
size_t b_mode = static_cast<size_t>(b->mode);
|
||||||
|
if (a_mode < b_mode) {
|
||||||
|
return true;
|
||||||
|
} else if (a_mode > b_mode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t a_episode = static_cast<size_t>(a->episode);
|
||||||
|
size_t b_episode = static_cast<size_t>(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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -207,4 +207,6 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
|||||||
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_serial_number() const;
|
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_serial_number() const;
|
||||||
|
|
||||||
static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx);
|
static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||||
|
|
||||||
|
static bool compare_shared(const std::shared_ptr<const Lobby>& a, const std::shared_ptr<const Lobby>& b);
|
||||||
};
|
};
|
||||||
|
|||||||
+21
-20
@@ -4,12 +4,14 @@
|
|||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <phosg/Encoding.hh>
|
#include <phosg/Encoding.hh>
|
||||||
#include <phosg/Hash.hh>
|
#include <phosg/Hash.hh>
|
||||||
#include <phosg/Random.hh>
|
#include <phosg/Random.hh>
|
||||||
#include <phosg/Strings.hh>
|
#include <phosg/Strings.hh>
|
||||||
#include <phosg/Time.hh>
|
#include <phosg/Time.hh>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#include "CommandFormats.hh"
|
#include "CommandFormats.hh"
|
||||||
#include "Compression.hh"
|
#include "Compression.hh"
|
||||||
@@ -61,8 +63,7 @@ const unordered_set<string> bb_crypt_initial_client_commands({
|
|||||||
string("\xDC\x00\xDB\x00\x00\x00\x00\x00", 8),
|
string("\xDC\x00\xDB\x00\x00\x00\x00\x00", 8),
|
||||||
});
|
});
|
||||||
|
|
||||||
void send_command(std::shared_ptr<Client> c, uint16_t command,
|
void send_command(shared_ptr<Client> c, uint16_t command, uint32_t flag, const vector<pair<const void*, size_t>>& blocks) {
|
||||||
uint32_t flag, const std::vector<std::pair<const void*, size_t>>& blocks) {
|
|
||||||
c->channel.send(command, flag, blocks);
|
c->channel.send(command, flag, blocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +330,7 @@ void send_quest_buffer_overflow(shared_ptr<Client> c) {
|
|||||||
|
|
||||||
void empty_function_call_response_handler(uint32_t, uint32_t) {}
|
void empty_function_call_response_handler(uint32_t, uint32_t) {}
|
||||||
|
|
||||||
void prepare_client_for_patches(shared_ptr<Client> c, std::function<void()> on_complete) {
|
void prepare_client_for_patches(shared_ptr<Client> c, function<void()> on_complete) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
|
|
||||||
auto send_version_detect = [s, wc = weak_ptr<Client>(c), on_complete]() -> void {
|
auto send_version_detect = [s, wc = weak_ptr<Client>(c), on_complete]() -> void {
|
||||||
@@ -1326,20 +1327,20 @@ void send_game_menu_t(
|
|||||||
e.flags = 0x04;
|
e.flags = 0x04;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set<shared_ptr<const Lobby>, bool (*)(const shared_ptr<const Lobby>&, const shared_ptr<const Lobby>&)> games(Lobby::compare_shared);
|
||||||
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
||||||
if (!l->is_game()) {
|
if (l->is_game() &&
|
||||||
continue;
|
l->version_is_allowed(c->version()) &&
|
||||||
}
|
(l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) == is_spectator_team_list) &&
|
||||||
if (!l->version_is_allowed(c->version())) {
|
(!show_tournaments_only || l->tournament_match)) {
|
||||||
continue;
|
games.emplace(l);
|
||||||
}
|
|
||||||
if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) != is_spectator_team_list) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (show_tournaments_only && !l->tournament_match) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& l : games) {
|
||||||
|
if (entries.size() >= 0x41) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
uint8_t episode_num;
|
uint8_t episode_num;
|
||||||
switch (l->episode) {
|
switch (l->episode) {
|
||||||
case Episode::EP1:
|
case Episode::EP1:
|
||||||
@@ -2303,7 +2304,7 @@ void send_set_player_visibility(shared_ptr<Lobby> l, shared_ptr<Client> c, bool
|
|||||||
send_command_t(l, 0x60, 0x00, cmd);
|
send_command_t(l, 0x60, 0x00, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_artificial_item_state(std::shared_ptr<Client> c) {
|
void send_artificial_item_state(shared_ptr<Client> c) {
|
||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (c->lobby_client_id != l->leader_id) {
|
if (c->lobby_client_id != l->leader_id) {
|
||||||
throw runtime_error("artificial item state can only be sent to the leader");
|
throw runtime_error("artificial item state can only be sent to the leader");
|
||||||
@@ -2516,7 +2517,7 @@ void send_give_experience(shared_ptr<Client> c, uint32_t amount) {
|
|||||||
send_command_t(l, 0x60, 0x00, cmd);
|
send_command_t(l, 0x60, 0x00, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_set_exp_multiplier(std::shared_ptr<Lobby> l) {
|
void send_set_exp_multiplier(shared_ptr<Lobby> l) {
|
||||||
if (l->base_version != Version::BB_V4) {
|
if (l->base_version != Version::BB_V4) {
|
||||||
throw logic_error("6xDD can only be sent to BB clients");
|
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<Client> c, bool is_13EA
|
|||||||
send_command_vt(c, is_13EA ? 0x13EA : 0x15EA, entries.size(), entries);
|
send_command_vt(c, is_13EA ? 0x13EA : 0x15EA, entries.size(), entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_update_team_reward_flags(std::shared_ptr<Client> c) {
|
void send_update_team_reward_flags(shared_ptr<Client> c) {
|
||||||
auto team = c->team();
|
auto team = c->team();
|
||||||
send_command(c, 0x1DEA, team ? team->reward_flags : 0x00000000);
|
send_command(c, 0x1DEA, team ? team->reward_flags : 0x00000000);
|
||||||
}
|
}
|
||||||
@@ -3479,7 +3480,7 @@ void send_team_member_list(shared_ptr<Client> c) {
|
|||||||
send_command_t_vt(c, 0x09EA, 0x00000000, header, entries);
|
send_command_t_vt(c, 0x09EA, 0x00000000, header, entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_intra_team_ranking(std::shared_ptr<Client> c) {
|
void send_intra_team_ranking(shared_ptr<Client> c) {
|
||||||
auto team = c->team();
|
auto team = c->team();
|
||||||
if (!team) {
|
if (!team) {
|
||||||
throw runtime_error("client is not in a team");
|
throw runtime_error("client is not in a team");
|
||||||
@@ -3515,7 +3516,7 @@ void send_intra_team_ranking(std::shared_ptr<Client> c) {
|
|||||||
send_command_t_vt(c, 0x18EA, 0x00000000, cmd, entries);
|
send_command_t_vt(c, 0x18EA, 0x00000000, cmd, entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_cross_team_ranking(std::shared_ptr<Client> c) {
|
void send_cross_team_ranking(shared_ptr<Client> c) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
|
|
||||||
// TODO: At some point we should maintain a sorted index instead of sorting
|
// 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<Client> c) {
|
|||||||
send_command_t_vt(c, 0x1CEA, 0x00000000, cmd, entries);
|
send_command_t_vt(c, 0x1CEA, 0x00000000, cmd, entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_team_reward_list(std::shared_ptr<Client> c, bool show_purchased) {
|
void send_team_reward_list(shared_ptr<Client> c, bool show_purchased) {
|
||||||
auto team = c->team();
|
auto team = c->team();
|
||||||
if (!team) {
|
if (!team) {
|
||||||
throw runtime_error("user is not in a team");
|
throw runtime_error("user is not in a team");
|
||||||
|
|||||||
Reference in New Issue
Block a user