From 25b454ad0e8932aea05a5c85736f62b0b1452129 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Wed, 14 Apr 2021 16:00:02 +0200 Subject: [PATCH] Fixed the query client view power --- server/src/PermissionCalculator.h | 1 + server/src/TS3ServerClientManager.cpp | 210 +++++++------ server/src/VirtualServer.h | 10 +- server/src/client/ConnectedClient.cpp | 16 +- .../client/ConnectedClientNotifyHandler.cpp | 17 +- server/src/client/query/QueryClient.cpp | 14 +- server/src/client/query/QueryClient.h | 3 - .../src/client/query/QueryClientCommands.cpp | 283 +++++++----------- server/src/snapshots/deploy.cpp | 7 +- 9 files changed, 283 insertions(+), 278 deletions(-) diff --git a/server/src/PermissionCalculator.h b/server/src/PermissionCalculator.h index 42eb12e..08baf89 100644 --- a/server/src/PermissionCalculator.h +++ b/server/src/PermissionCalculator.h @@ -25,6 +25,7 @@ namespace ts::server { */ class ClientPermissionCalculator { public: + /* When providing the pointer to the channel the channel tree **should** not be locked in any way! */ explicit ClientPermissionCalculator(DataClient* /* client */, const std::shared_ptr& /* target channel */); explicit ClientPermissionCalculator(DataClient* /* client */, ChannelId /* target channel id */); explicit ClientPermissionCalculator( diff --git a/server/src/TS3ServerClientManager.cpp b/server/src/TS3ServerClientManager.cpp index 0778442..8094e1d 100644 --- a/server/src/TS3ServerClientManager.cpp +++ b/server/src/TS3ServerClientManager.cpp @@ -1,5 +1,6 @@ #include #include +#include "./PermissionCalculator.h" #include "client/voice/VoiceClient.h" #include "client/InternalClient.h" #include "VirtualServer.h" @@ -111,17 +112,35 @@ bool VirtualServer::unregisterClient(shared_ptr cl, std::string } } - if(cl->getType() == ClientType::CLIENT_TEAMSPEAK || cl->getType() == ClientType::CLIENT_WEB) - this->properties()[property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT] = duration_cast(system_clock::now().time_since_epoch()).count(); - else if(cl->getType() == ClientType::CLIENT_QUERY) - this->properties()[property::VIRTUALSERVER_LAST_QUERY_DISCONNECT] = duration_cast(system_clock::now().time_since_epoch()).count(); + auto current_time_seconds = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + switch(cl->getType()) { + case ClientType::CLIENT_TEAMSPEAK: + case ClientType::CLIENT_TEASPEAK: + case ClientType::CLIENT_WEB: + this->properties()[property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT] = current_time_seconds; + break; + + case ClientType::CLIENT_QUERY: + this->properties()[property::VIRTUALSERVER_LAST_QUERY_DISCONNECT] = current_time_seconds; + break; + + + case ClientType::CLIENT_MUSIC: + case ClientType::CLIENT_INTERNAL: + case ClientType::MAX: + default: + break; + } { - if(!chan_tree_lock.owns_lock()) + if(!chan_tree_lock.owns_lock()) { chan_tree_lock.lock(); + } - if(cl->currentChannel) //We dont have to make him invisible if he hasnt even a channel + if(cl->currentChannel) { + //We dont have to make him invisible if he hasnt even a channel this->client_move(cl, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock); + } } serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions); @@ -175,9 +194,9 @@ void VirtualServer::unregisterInternalClient(std::shared_ptr cl } bool VirtualServer::assignDefaultChannel(const shared_ptr& client, bool join) { - std::shared_lock server_channel_lock{this->channel_tree_mutex}; std::shared_ptr channel{}; + std::unique_lock server_channel_lock{this->channel_tree_mutex}; auto requested_channel_path = client->properties()[property::CLIENT_DEFAULT_CHANNEL].value(); if(!requested_channel_path.empty()) { if (requested_channel_path[0] == '/' && requested_channel_path.find_first_not_of("0123456789", 1) == std::string::npos) { @@ -244,12 +263,11 @@ bool VirtualServer::assignDefaultChannel(const shared_ptr& clie debugMessage(this->getServerId(), "{} Using channel {} as default client channel.", client->getLoggingPrefix(), channel->channelId()); if(join) { - server_channel_lock.unlock(); - unique_lock server_channel_w_lock(this->channel_tree_mutex); - this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_w_lock); + this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_lock); } else { client->currentChannel = channel; } + return true; } @@ -412,10 +430,10 @@ void VirtualServer::delete_channel(shared_ptr channel, const /* * This method had previously owned the clients command lock but that's not really needed. * Everything which is related to the server channel tree or the client channel tree should be locked with - * the appropiate mutexes. + * the appropriate mutexes. */ void VirtualServer::client_move( - const shared_ptr &target, + const shared_ptr &target_client, shared_ptr target_channel, const std::shared_ptr &invoker, const std::string &reason_message, @@ -427,15 +445,16 @@ void VirtualServer::client_move( if(!server_channel_write_lock.owns_lock()) { server_channel_write_lock.unlock(); } + TIMING_STEP(timings, "chan tree l"); - if(target->currentChannel == target_channel) { + if(target_client->currentChannel == target_channel) { return; } /* first step: verify thew source and target channel */ auto s_target_channel = dynamic_pointer_cast(target_channel); - auto s_source_channel = dynamic_pointer_cast(target->currentChannel); - assert(!target->currentChannel || s_source_channel != nullptr); + auto s_source_channel = dynamic_pointer_cast(target_client->currentChannel); + assert(!target_client->currentChannel || s_source_channel != nullptr); std::deque updated_client_properties{}; if(target_channel) { @@ -449,147 +468,157 @@ void VirtualServer::client_move( auto l_source_channel = s_source_channel ? this->channelTree->findLinkedChannel(s_source_channel->channelId()) : nullptr; TIMING_STEP(timings, "channel res"); - /* second step: show the target channel to the client if its not shown and let him subscibe to the channel */ + /* second step: show the target channel to the client if its not shown and let him subscribe to the channel */ if(target_channel && notify_client) { - unique_lock client_channel_lock(target->channel_tree_mutex); + std::unique_lock client_channel_lock{target_client->channel_tree_mutex}; - bool success = false; + bool success{false}; /* TODO: Use a bunk here and not a notify for every single */ - for(const auto& channel : target->channel_tree->show_channel(l_target_channel, success)) - target->notifyChannelShow(channel->channel(), channel->previous_channel); - sassert(success); - if(!success) - return; + for(const auto& channel : target_client->channel_tree->show_channel(l_target_channel, success)) { + target_client->notifyChannelShow(channel->channel(), channel->previous_channel); + } - target->subscribeChannel({target_channel}, false, true); + sassert(success); + if(!success) { + return; + } + + target_client->subscribeChannel({ target_channel }, false, true); } TIMING_STEP(timings, "target show"); + if(s_source_channel) { + s_source_channel->unregister_client(target_client); + } + if(target_channel) { - this->forEachClient([&](const shared_ptr& client) { - if (!notify_client && client == target) return; + ClientPermissionCalculator target_client_permissions{&*target_client, target_channel}; + auto needed_view_power = target_client_permissions.calculate_permission(permission::i_client_needed_serverquery_view_power); - unique_lock client_channel_lock(client->channel_tree_mutex); - auto chan_target = client->channel_tree->find_channel(target_channel); + /* ct_... is for client channel tree */ + this->forEachClient([&](const std::shared_ptr& client) { + if (!notify_client && client == target_client) { + return; + } - if(chan_target) { - auto chan_source = client->channel_tree->find_channel(s_source_channel); - if(chan_source) { - if (chan_target->subscribed || client == target) { - if (client == target || client->isClientVisible(target, false)) { - client->notifyClientMoved(target, s_target_channel, reason_id, reason_message, invoker, false); + bool move_target_client_visible{true}; + if(target_client->getType() == ClientType::CLIENT_QUERY) { + auto query_view_power = client->calculate_permission(permission::i_client_serverquery_view_power, target_channel->channelId()); + move_target_client_visible = permission::v2::permission_granted(needed_view_power, query_view_power); + } + + std::unique_lock client_channel_lock{client->channel_tree_mutex}; + auto ct_target_channel = move_target_client_visible ? client->channel_tree->find_channel(target_channel) : nullptr; + + if(ct_target_channel) { + auto ct_source_channel = client->channel_tree->find_channel(s_source_channel); + if(ct_source_channel) { + /* Source and target channel are visible for the client. Just a "normal" move. */ + if (ct_target_channel->subscribed || client == target_client) { + if (client == target_client || client->isClientVisible(target_client, false)) { + client->notifyClientMoved(target_client, s_target_channel, reason_id, reason_message, invoker, false); } else { - client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, reason_id, s_source_channel, false); + client->notifyClientEnterView(target_client, invoker, reason_message, s_target_channel, reason_id, s_source_channel, false); } - } else if(client->isClientVisible(target, false)){ - //Client got out of view - client->notifyClientLeftView(target, s_target_channel, reason_id, reason_message.empty() ? string("view left") : reason_message, invoker, false); + } else if(client->isClientVisible(target_client, false)){ + /* Client has been moved into an unsubscribed channel */ + client->notifyClientLeftView(target_client, s_target_channel, reason_id, reason_message.empty() ? string("view left") : reason_message, invoker, false); } - } else { - if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC) - logCritical(this->getServerId(), "{} Client enters visibility twice!", CLIENT_STR_LOG_PREFIX_(client)); - - //Client entered view - if(chan_target->subscribed) - client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, ViewReasonId::VREASON_USER_ACTION, nullptr, false); + } else if(ct_target_channel->subscribed) { + /* Target client entered the view from an invisible channel */ + client->notifyClientEnterView(target_client, invoker, reason_message, s_target_channel, ViewReasonId::VREASON_USER_ACTION, nullptr, false); } } else { - /* target channel isn't visible => so client gone out of view */ - if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC) - logCritical(this->getServerId(), "{} Moving own client into a not visible channel! This shall not happen!", CLIENT_STR_LOG_PREFIX_(client)); - //Test for in view? (Notify already does but nvm) - - if(client->isClientVisible(target, false)){ - //Client got out of view - if(reason_id == ViewReasonId::VREASON_USER_ACTION) - client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "joined a hidden channel" : reason_message, invoker, false); - else - client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "moved to a hidden channel" : reason_message, invoker, false); + if(client->isClientVisible(target_client, false)) { + /* Client has been moved out of view into an invisible channel */ + if(reason_id == ViewReasonId::VREASON_USER_ACTION) { + client->notifyClientLeftView(target_client, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "joined a hidden channel" : reason_message, invoker, false); + } else { + client->notifyClientLeftView(target_client, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "moved to a hidden channel" : reason_message, invoker, false); + } } } }); - if(s_source_channel) { - s_source_channel->unregister_client(target); - } - s_target_channel->register_client(target); - if(auto client{dynamic_pointer_cast(target)}; client) { + s_target_channel->register_client(target_client); + if(auto client{dynamic_pointer_cast(target_client)}; client) { this->rtc_server().assign_channel(client->rtc_client_id, s_target_channel->rtc_channel_id); } - if(auto client{dynamic_pointer_cast(target)}; client) { + + if(auto client{dynamic_pointer_cast(target_client)}; client) { /* Start normal broadcasting, what the client expects */ this->rtc_server().start_broadcast_audio(client->rtc_client_id, 1); } } else { /* client left the server */ - if(target->currentChannel) { + if(target_client->currentChannel) { for(const auto& client : this->getClients()) { - if(!client || client == target) + if(!client || client == target_client) continue; unique_lock client_channel_lock(client->channel_tree_mutex); - if(client->isClientVisible(target, false)) - client->notifyClientLeftView(target, nullptr, reason_id, reason_message, invoker, false); + if(client->isClientVisible(target_client, false)) { + client->notifyClientLeftView(target_client, nullptr, reason_id, reason_message, invoker, false); + } } - s_source_channel->unregister_client(target); - if(auto client{dynamic_pointer_cast(target)}; client) { + if(auto client{dynamic_pointer_cast(target_client)}; client) { this->rtc_server().assign_channel(client->rtc_client_id, 0); } } } TIMING_STEP(timings, "notify view"); - target->currentChannel = target_channel; + target_client->currentChannel = target_channel; /* third step: update stuff for the client (remember: the client cant execute anything at the moment!) */ - unique_lock client_channel_lock{target->channel_tree_mutex}; + unique_lock client_channel_lock{target_client->channel_tree_mutex}; TIMING_STEP(timings, "lock own tr"); if (s_source_channel) { - s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); - this->group_manager()->assignments().cleanup_temporary_channel_assignment(target->getClientDatabaseId(), - s_source_channel->channelId()); + s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + this->group_manager()->assignments().cleanup_temporary_channel_assignment(target_client->getClientDatabaseId(), s_source_channel->channelId()); - auto update = target->properties()[property::CLIENT_IS_TALKER].as_or(false) || - target->properties()[property::CLIENT_TALK_REQUEST].as_or(0) > 0; - if(update) { - target->properties()[property::CLIENT_IS_TALKER] = 0; - target->properties()[property::CLIENT_TALK_REQUEST] = 0; - target->properties()[property::CLIENT_TALK_REQUEST_MSG] = ""; + if(target_client->properties()[property::CLIENT_IS_TALKER].update_value("0")) { updated_client_properties.push_back(property::CLIENT_IS_TALKER); + } + + if(target_client->properties()[property::CLIENT_TALK_REQUEST].update_value("0")) { updated_client_properties.push_back(property::CLIENT_TALK_REQUEST); + } + + if(target_client->properties()[property::CLIENT_TALK_REQUEST_MSG].update_value("")) { updated_client_properties.push_back(property::CLIENT_TALK_REQUEST_MSG); } TIMING_STEP(timings, "src chan up"); } if (s_target_channel) { - target->task_update_needed_permissions.enqueue(); - target->task_update_displayed_groups.enqueue(); + target_client->task_update_needed_permissions.enqueue(); + target_client->task_update_displayed_groups.enqueue(); TIMING_STEP(timings, "perm gr upd"); if(s_source_channel) { deque deleted; - for(const auto& channel : target->channel_tree->test_channel(l_source_channel, l_target_channel)) { + for(const auto& channel : target_client->channel_tree->test_channel(l_source_channel, l_target_channel)) { deleted.push_back(channel->channelId()); } if(!deleted.empty()) { - target->notifyChannelHide(deleted, false); + target_client->notifyChannelHide(deleted, false); } auto i_source_channel = s_source_channel->channelId(); if(std::find(deleted.begin(), deleted.end(), i_source_channel) == deleted.end()) { - auto source_channel_sub_power = target->calculate_permission(permission::i_channel_subscribe_power, i_source_channel); + auto source_channel_sub_power = target_client->calculate_permission(permission::i_channel_subscribe_power, i_source_channel); if(!s_source_channel->permission_granted(permission::i_channel_needed_subscribe_power, source_channel_sub_power, false)) { - auto source_channel_sub_power_ignore = target->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel); + auto source_channel_sub_power_ignore = target_client->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel); if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore)) { logTrace(this->serverId, "Force unsubscribing of client {} for channel {}/{}. (Channel switch and no permissions)", - CLIENT_STR_LOG_PREFIX_(target), s_source_channel->name(), + CLIENT_STR_LOG_PREFIX_(target_client), s_source_channel->name(), i_source_channel ); - target->unsubscribeChannel({s_source_channel}, false); //Unsubscribe last channel (hasn't permissions) + target_client->unsubscribeChannel({ s_source_channel }, false); //Unsubscribe last channel (hasn't permissions) } } } @@ -597,12 +626,13 @@ void VirtualServer::client_move( } } client_channel_lock.unlock(); + /* both methods lock if they require stuff */ - this->notifyClientPropertyUpdates(target, updated_client_properties, s_source_channel ? true : false); + this->notifyClientPropertyUpdates(target_client, updated_client_properties, s_source_channel ? true : false); TIMING_STEP(timings, "notify cpro"); if(s_target_channel) { - target->updateChannelClientProperties(false, s_source_channel ? true : false); + target_client->updateChannelClientProperties(false, s_source_channel ? true : false); TIMING_STEP(timings, "notify_t_pr"); } - debugMessage(this->getServerId(), "{} Client move timings: {}", CLIENT_STR_LOG_PREFIX_(target), TIMING_FINISH(timings)); + debugMessage(this->getServerId(), "{} Client move timings: {}", CLIENT_STR_LOG_PREFIX_(target_client), TIMING_FINISH(timings)); } \ No newline at end of file diff --git a/server/src/VirtualServer.h b/server/src/VirtualServer.h index 83eb1ac..8e5537b 100644 --- a/server/src/VirtualServer.h +++ b/server/src/VirtualServer.h @@ -191,9 +191,15 @@ namespace ts { bool notifyServerEdited(std::shared_ptr, std::deque keys); bool notifyClientPropertyUpdates(std::shared_ptr, const std::deque& keys, bool selfNotify = true); /* execute only with at least channel tree read lock! */ inline bool notifyClientPropertyUpdates(const std::shared_ptr& client, const std::deque& keys, bool selfNotify = true) { - if(keys.empty()) return false; + if(keys.empty()) { + return false; + } + std::deque _keys{}; - for(const auto& key : keys) _keys.push_back(&property::describe(key)); + for(const auto& key : keys) { + _keys.push_back(&property::describe(key)); + } + return this->notifyClientPropertyUpdates(client, _keys, selfNotify); }; diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 1780db2..058f093 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -335,7 +335,21 @@ void ConnectedClient::subscribeChannel(const std::dequegetClientsByChannel(target_channel); - visible_clients.insert(visible_clients.end(), channel_clients.begin(), channel_clients.end()); + + auto target_view_power = this->calculate_permission(permission::i_client_serverquery_view_power, target_channel->channelId()); + for(const auto& client : channel_clients) { + if(client->getType() == ClientType::CLIENT_QUERY) { + if(!target_view_power.has_power()) { + continue; + } + + if(!permission::v2::permission_granted(client->calculate_permission(permission::i_client_needed_serverquery_view_power, target_channel->channelId()), target_view_power)) { + continue; + } + } + + visible_clients.push_back(client); + } } if(!visible_clients.empty()) { diff --git a/server/src/client/ConnectedClientNotifyHandler.cpp b/server/src/client/ConnectedClientNotifyHandler.cpp index 15d1ad9..70d6575 100644 --- a/server/src/client/ConnectedClientNotifyHandler.cpp +++ b/server/src/client/ConnectedClientNotifyHandler.cpp @@ -586,27 +586,32 @@ bool ConnectedClient::notifyChannelHide(const std::deque &channel_ids } bool ConnectedClient::notifyChannelShow(const std::shared_ptr &channel, ts::ChannelId orderId) { - if(!channel) + if(!channel) { return false; + } auto result = false; - if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { //Voice hasn't that event + if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { + /* The TeamSpeak 3 client dosn't know about hidden channels */ result = this->notifyChannelCreate(channel, orderId, this->server->serverRoot); } else { Command notify("notifychannelshow"); - for (auto &prop : channel->properties()->list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { + for (auto &prop : channel->properties()->list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, (uint16_t) 0)) { if(prop.type() == property::CHANNEL_ORDER) { notify[prop.type().name] = orderId; } else if(prop.type() == property::CHANNEL_DESCRIPTION) { continue; - } - else + } else { notify[prop.type().name] = prop.value(); + } } this->sendCommand(notify); } - if(result && this->subscribeToAll) + + if(result && this->subscribeToAll) { this->subscribeChannel({channel}, false, true); + } + return true; } diff --git a/server/src/client/query/QueryClient.cpp b/server/src/client/query/QueryClient.cpp index a4a4bd1..416c7fa 100644 --- a/server/src/client/query/QueryClient.cpp +++ b/server/src/client/query/QueryClient.cpp @@ -618,7 +618,7 @@ void QueryClient::disconnect_from_virtual_server(const std::string& reason) { auto old_server = std::exchange(this->server, nullptr); if(old_server) { { - std::unique_lock tree_lock(old_server->channel_tree_mutex); + std::unique_lock tree_lock{old_server->channel_tree_mutex}; if(this->currentChannel) { old_server->client_move(this->ref(), nullptr, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock); } @@ -626,12 +626,12 @@ void QueryClient::disconnect_from_virtual_server(const std::string& reason) { old_server->unregisterClient(this->ref(), reason, tree_lock); } - { - std::lock_guard channel_lock{this->channel_tree_mutex}; - this->channel_tree->reset(); - this->currentChannel = nullptr; - } - this->loadDataForCurrentServer(); } + + { + std::lock_guard channel_lock{this->channel_tree_mutex}; + this->channel_tree->reset(); + this->currentChannel = nullptr; + } } \ No newline at end of file diff --git a/server/src/client/query/QueryClient.h b/server/src/client/query/QueryClient.h index f3faf86..aa58cab 100644 --- a/server/src/client/query/QueryClient.h +++ b/server/src/client/query/QueryClient.h @@ -189,8 +189,6 @@ namespace ts::server { command_result handleCommandServerSelect(Command &); command_result handleCommandServerInfo(Command&); command_result handleCommandChannelList(Command&); - command_result handleCommandJoin(Command&); - command_result handleCommandLeft(Command&); command_result handleCommandServerList(Command&); command_result handleCommandServerCreate(Command&); @@ -209,7 +207,6 @@ namespace ts::server { command_result handleCommandServerIdGetByPort(Command&); - command_result handleCommandServerSnapshotDeploy(Command&); command_result handleCommandServerSnapshotDeployNew(const command_parser&); command_result handleCommandServerSnapshotCreate(Command&); command_result handleCommandServerProcessStop(Command&); diff --git a/server/src/client/query/QueryClientCommands.cpp b/server/src/client/query/QueryClientCommands.cpp index a1c6fa8..404ea31 100644 --- a/server/src/client/query/QueryClientCommands.cpp +++ b/server/src/client/query/QueryClientCommands.cpp @@ -123,10 +123,6 @@ command_result QueryClient::handleCommand(Command& cmd) { return this->handleCommandLogin(cmd); case string_hash("logout"): return this->handleCommandLogout(cmd); - case string_hash("join"): - return this->handleCommandJoin(cmd); - case string_hash("left"): - return this->handleCommandLeft(cmd); case string_hash("globalmessage"): case string_hash("gm"): return this->handleCommandGlobalMessage(cmd); @@ -151,9 +147,6 @@ command_result QueryClient::handleCommand(Command& cmd) { case string_hash("bindinglist"): return this->handleCommandBindingList(cmd); case string_hash("serversnapshotdeploy"): { -#if 0 - return this->handleCommandServerSnapshotDeploy(cmd); -#else auto cmd_str = cmd.build(); ts::command_parser parser{cmd_str}; if(!parser.parse(true)) { @@ -161,7 +154,6 @@ command_result QueryClient::handleCommand(Command& cmd) { } return this->handleCommandServerSnapshotDeployNew(parser); -#endif } case string_hash("serversnapshotcreate"): return this->handleCommandServerSnapshotCreate(cmd); @@ -260,10 +252,13 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { auto target_server = this->server; /* keep the server alive 'ill we've joined the server */ if(account->bound_server) { target_server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server); - if(target_server != this->server) + if(target_server != this->server) { joined_channel = nullptr; - if(!target_server) - return command_result{error::server_invalid_id, "server does not exists anymore"}; + } + + if(!target_server) { + return command_result{error::server_invalid_id, "bound server does not exists"}; + } } this->server = target_server; @@ -273,8 +268,10 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { { shared_lock server_tree_lock(target_server->channel_tree_mutex); - if(joined_channel) /* needs only notify if we were already on that server within a channel */ + if(joined_channel) { + /* needs only notify if we were already on that server within a channel */ target_server->notifyClientPropertyUpdates(this->ref(), deque{property::CLIENT_NICKNAME, property::CLIENT_UNIQUE_IDENTIFIER}); + } unique_lock client_tree_lock(this->channel_tree_mutex); this->channel_tree->reset(); @@ -283,13 +280,10 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { } if(joined_channel) { - unique_lock tree_lock(this->server->channel_tree_mutex); - if(joined_channel) - this->server->client_move(this->ref(), joined_channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock); - } else if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_select_godmode, 1))) { - this->server->assignDefaultChannel(this->ref(), true); + std::unique_lock tree_lock{this->server->channel_tree_mutex}; + this->server->client_move(this->ref(), joined_channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock); } else { - this->task_update_needed_permissions.enqueue(); + this->server->assignDefaultChannel(this->ref(), true); } } else { this->task_update_needed_permissions.enqueue(); @@ -312,8 +306,9 @@ command_result QueryClient::handleCommandLogout(Command &) { if(this->server){ { unique_lock tree_lock(this->server->channel_tree_mutex); - if(joined) + if(joined) { this->server->client_move(this->ref(), nullptr, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock); + } this->server->unregisterClient(this->ref(), "logout", tree_lock); } } @@ -337,10 +332,8 @@ command_result QueryClient::handleCommandLogout(Command &) { if(joined) { unique_lock server_channel_w_lock(this->server->channel_tree_mutex, defer_lock); this->server->client_move(this->ref(), joined, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_w_lock); - } else if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_select_godmode, 1))) { - this->server->assignDefaultChannel(this->ref(), true); } else { - this->task_update_needed_permissions.enqueue(); + this->server->assignDefaultChannel(this->ref(), true); } } else { this->task_update_needed_permissions.enqueue(); @@ -354,65 +347,113 @@ command_result QueryClient::handleCommandLogout(Command &) { command_result QueryClient::handleCommandServerSelect(Command &cmd) { CMD_RESET_IDLE; - shared_ptr target; - if(cmd[0].has("port")){ + std::optional target_server_id{}; + std::shared_ptr target{}; + + if(cmd[0].has("port")) { target = serverInstance->getVoiceServerManager()->findServerByPort(cmd["port"].as()); } else if(cmd[0].has("sid")) { - target = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"].as()); - } - - for(auto& parm : cmd[0].keys()) - if(parm.find_first_not_of("0123456789") == string::npos) - target = serverInstance->getVoiceServerManager()->findServerById(static_cast(stol(parm))); - - if(!target && (!cmd[0].has("0") && (!cmd[0].has("sid") || !cmd["sid"] == 0))) return command_result{error::server_invalid_id, "Invalid server id"}; - if(target && target->getState() != ServerState::ONLINE && target->getState() != ServerState::OFFLINE) return command_result{error::server_is_not_running}; - if(target == this->server) return command_result{error::ok}; - - auto old_server_id = this->getServerId(); - if(target) { - if(this->query_account && this->query_account->bound_server > 0) { - if(target->getServerId() != this->query_account->bound_server) - return command_result{error::server_invalid_id, "You're a server bound query, and the target server isn't your origin."}; - } else { - auto allowed = target->calculate_permission(permission::b_virtualserver_select, this->getClientDatabaseId(), this->getType(), 0); - if(!permission::v2::permission_granted(1, allowed)) return command_result{permission::b_virtualserver_select}; + target_server_id = std::make_optional(cmd["sid"].as()); + } else { + for(const auto& parm : cmd[0].keys()) { + if(parm.length() < 6 && parm.find_first_not_of("0123456789") == string::npos) { + target_server_id = std::make_optional(std::stoul(parm)); + break; + } } } - this->disconnect_from_virtual_server("server switch"); - this->resetEventMask(); - - //register at current server - { - //unique_lock server_lock(this->server_lock); - /* We dont need to lock the server because only one command can be executed at the time. Everything else should copy the server once and test the copy and use that ref then */ - this->server = target; + if(!target && target_server_id.has_value()) { + target = serverInstance->getVoiceServerManager()->findServerById(*target_server_id); } - if(cmd[0].has("client_nickname")) - this->properties()[property::CLIENT_NICKNAME] = cmd["client_nickname"].string(); + if(target_server_id.has_value()) { + if(*target_server_id > 0) { + target = serverInstance->getVoiceServerManager()->findServerById(*target_server_id); + + if(!target) { + return ts::command_result{error::server_invalid_id}; + } + } + } else if(target) { + target_server_id = std::make_optional(target->getServerId()); + } else { + return ts::command_result{error::server_invalid_id}; + } + + if(target == this->server) { + return ts::command_result{error::ok}; + } + + auto old_server_id = this->getServerId(); + if(target) { + if(target->getState() != ServerState::ONLINE && target->getState() != ServerState::OFFLINE) { + return command_result{error::server_is_not_running}; + } + + if(this->query_account && this->query_account->bound_server > 0) { + if(target->getServerId() != this->query_account->bound_server) { + return command_result{error::server_invalid_id, "You're a server bound query, and the target server isn't your origin."}; + } + } else { + auto allowed = target->calculate_permission(permission::b_virtualserver_select, this->getClientDatabaseId(), this->getType(), 0); + if(!permission::v2::permission_granted(1, allowed)) { + return command_result{permission::b_virtualserver_select}; + } + } + } + + this->resetEventMask(); + + this->disconnect_from_virtual_server("server switch"); + this->server = target; + + auto target_client_nickname = cmd["client_nickname"].optional_string(); + if(target_client_nickname.has_value()) { + this->properties()[property::CLIENT_NICKNAME] = *target_client_nickname; + } + +#if 0 + command_result QueryClient::handleCommandJoin(Command &) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + if(this->server->state != ServerState::ONLINE) + return command_result{error::server_is_not_running}; + + if(this->currentChannel) + return command_result{error::server_already_joined, "already joined!"}; + + this->server->assignDefaultChannel(this->ref(), true); + return command_result{error::ok}; + } + + command_result QueryClient::handleCommandLeft(Command&) { + CMD_REQ_SERVER; + CMD_REQ_CHANNEL; + CMD_RESET_IDLE; + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_select_godmode, 1); + + unique_lock server_channel_lock(this->server->channel_tree_mutex); + this->server->client_move(this->ref(), nullptr, nullptr, "leaving", ViewReasonId::VREASON_SERVER_LEFT, true, server_channel_lock); + return command_result{error::ok}; + } +#endif DatabaseHelper::assignDatabaseId(this->sql, static_cast(this->server ? this->server->getServerId() : 0), this->ref()); if(this->server) { this->server->registerClient(this->ref()); { - shared_lock server_channel_lock(target->channel_tree_mutex); - unique_lock client_channel_lock(this->channel_tree_mutex); + std::shared_lock server_channel_lock{target->channel_tree_mutex}; + std::unique_lock client_channel_lock{this->channel_tree_mutex}; this->subscribeToAll = true; this->channel_tree->insert_channels(this->server->channelTree->tree_head(), true, false); this->subscribeChannel(this->server->channelTree->channels(), false, false); } - auto negated_enforce_join = permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_select_godmode, 1)); - if(!negated_enforce_join) { - this->server->assignDefaultChannel(this->ref(), true); - } else { - this->task_update_needed_permissions.enqueue(); - } + this->server->assignDefaultChannel(this->ref(), true); } else { this->task_update_needed_permissions.enqueue(); } @@ -422,30 +463,6 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) { return command_result{error::ok}; } -command_result QueryClient::handleCommandJoin(Command &) { - CMD_REQ_SERVER; - CMD_RESET_IDLE; - if(this->server->state != ServerState::ONLINE) - return command_result{error::server_is_not_running}; - - if(this->currentChannel) - return command_result{error::server_already_joined, "already joined!"}; - - this->server->assignDefaultChannel(this->ref(), true); - return command_result{error::ok}; -} - -command_result QueryClient::handleCommandLeft(Command&) { - CMD_REQ_SERVER; - CMD_REQ_CHANNEL; - CMD_RESET_IDLE; - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_select_godmode, 1); - - unique_lock server_channel_lock(this->server->channel_tree_mutex); - this->server->client_move(this->ref(), nullptr, nullptr, "leaving", ViewReasonId::VREASON_SERVER_LEFT, true, server_channel_lock); - return command_result{error::ok}; -} - command_result QueryClient::handleCommandServerInfo(Command &) { CMD_RESET_IDLE; ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_info_view, 1); @@ -664,10 +681,18 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) { auto start = system_clock::now(); threads::MutexLock lock(serverInstance->getVoiceServerManager()->server_create_lock); auto instances = serverInstance->getVoiceServerManager()->serverInstances(); - if(config::server::max_virtual_server != -1 && instances.size() > config::server::max_virtual_server) + if(config::server::max_virtual_server != -1 && instances.size() > config::server::max_virtual_server) { return command_result{error::server_max_vs_reached, "You reached the via config.yml enabled virtual server limit."}; - if(instances.size() >= 65535) + } + + /* + * 2 ^ 16 = 65536 + * We're using one less since we're using the server with id 65535 as snapshot deploy server. + */ + if(instances.size() >= 65535) { return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Software limit reached)"}; + } + { auto end = system_clock::now(); time_wait = duration_cast(end - start); @@ -702,10 +727,10 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) { } if(!cmd.hasParm("offline")) { - auto _start = system_clock::now(); + auto start_ = system_clock::now(); if(!server->start(startError)); - auto _end = system_clock::now(); - time_start = duration_cast(_end - _start); + auto end_ = system_clock::now(); + time_start = duration_cast(end_ - start_); } auto end = system_clock::now(); @@ -906,83 +931,6 @@ command_result QueryClient::handleCommandBindingList(Command& cmd) { return command_result{error::ok}; } -//TODO with mapping! -command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) { -#if 0 - CMD_RESET_IDLE; - - auto start = system_clock::now(); - string error; - string host = "0.0.0.0"; - uint16_t port = 0; - if(this->server) { - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1); - host = this->server->properties()[property::VIRTUALSERVER_HOST].as(); - port = this->server->properties()[property::VIRTUALSERVER_PORT].as(); - } else { - ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1); - } - - auto hash = cmd["hash"].string(); - if(hash.empty()) return command_result{error::parameter_invalid, "Invalid hash (not present)"}; - debugMessage(this->getServerId(), "Serversnapshot calculated hash: {}", hash); - bool mapping = cmd.hasParm("mapping"); - bool ignore_hash = cmd.hasParm("ignorehash"); - cmd.clear_parameters(); - - cmd.pop_bulk(); - auto str = cmd.build().substr(strlen("serversnapshotdeploy ")); - if(!ignore_hash) { - auto buildHash = base64::encode(digest::sha1(str)); - if(buildHash != hash) - return command_result{error::parameter_invalid, "Invalid hash (Expected: \"" + hash + "\", Got: \"" + buildHash + "\")"}; - } - - unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock); - { - auto instances = serverInstance->getVoiceServerManager()->serverInstances(); - if(config::server::max_virtual_server != -1 && instances.size() > config::server::max_virtual_server) - return command_result{error::server_max_vs_reached, "You reached the via config.yml enabled virtual server limit."}; - - if(instances.size() >= 65535) - return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Software limit reached)"}; - } - - if(port == 0) - port = serverInstance->getVoiceServerManager()->next_available_port(host); - auto result = serverInstance->getVoiceServerManager()->createServerFromSnapshot(this->server, host, port, cmd, error); - server_create_lock.unlock(); - auto end = system_clock::now(); - - Command res(""); - if(!result){ - logError(this->getServerId(), "Could not apply server snapshot: {}", error); - res["success"] = false; - res["sid"] = 0; - res["message"] = error; - } else { - res["success"] = true; - res["sid"] = result->getServerId(); - res["message"] = ""; - - if(!result->start(error)){ - res["error_start"] = error; - res["started"] = false; - } else { - res["error_start"] = ""; - res["started"] = true; - } - serverInstance->action_logger()->server_logger.log_server_create(result->getServerId(), this->ref(), log::ServerCreateReason::SNAPSHOT_DEPLOY); - } - - res["time"] = duration_cast(end - start).count(); - this->sendCommand(res); - return command_result{error::ok}; -#else - return command_result{error::command_not_found}; -#endif -} - command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) { CMD_RESET_IDLE; @@ -1014,6 +962,7 @@ command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::comma return command_result{error::vs_critical, error}; } + /* TODO: Send mapping */ ts::command_builder notify{""}; notify.put_unchecked(0, "virtualserver_port", server->properties()[property::VIRTUALSERVER_PORT].value()); notify.put_unchecked(0, "sid", server->getServerId()); diff --git a/server/src/snapshots/deploy.cpp b/server/src/snapshots/deploy.cpp index 4a06bd7..808346c 100644 --- a/server/src/snapshots/deploy.cpp +++ b/server/src/snapshots/deploy.cpp @@ -22,11 +22,13 @@ constexpr static ServerId kSnapshotServerId{0xFFFF}; VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot(std::string &error, std::shared_ptr& server, const command_parser &command) { if(!server) { auto instance_count = this->serverInstances().size(); - if(config::server::max_virtual_server != -1 && instance_count > config::server::max_virtual_server) + if(config::server::max_virtual_server != -1 && instance_count > config::server::max_virtual_server) { return SnapshotDeployResult::REACHED_CONFIG_SERVER_LIMIT; + } - if(instance_count >= 65534) + if(instance_count >= 65534) { return SnapshotDeployResult::REACHED_SOFTWARE_SERVER_LIMIT; + } } bool success{true}; @@ -37,6 +39,7 @@ VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot std::string server_host{server ? server->properties()[property::VIRTUALSERVER_HOST].value() : config::binding::DefaultVoiceHost}; uint16_t server_port{server ? server->properties()[property::VIRTUALSERVER_PORT].as_unchecked() : this->next_available_port(server_host)}; + this->delete_server_in_db(kSnapshotServerId, false); auto result = sql::command{this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)"} .value(":sid", kSnapshotServerId)