Fixed the query client view power

This commit is contained in:
WolverinDEV 2021-04-14 16:00:02 +02:00
parent 6b774d3a80
commit 25b454ad0e
9 changed files with 283 additions and 278 deletions

View File

@ -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<BasicChannel>& /* target channel */);
explicit ClientPermissionCalculator(DataClient* /* client */, ChannelId /* target channel id */);
explicit ClientPermissionCalculator(

View File

@ -1,5 +1,6 @@
#include <cstring>
#include <protocol/buffers.h>
#include "./PermissionCalculator.h"
#include "client/voice/VoiceClient.h"
#include "client/InternalClient.h"
#include "VirtualServer.h"
@ -111,17 +112,35 @@ bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> cl, std::string
}
}
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK || cl->getType() == ClientType::CLIENT_WEB)
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
else if(cl->getType() == ClientType::CLIENT_QUERY)
this->properties()[property::VIRTUALSERVER_LAST_QUERY_DISCONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
auto current_time_seconds = std::chrono::duration_cast<seconds>(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<ConnectedClient> cl
}
bool VirtualServer::assignDefaultChannel(const shared_ptr<ConnectedClient>& client, bool join) {
std::shared_lock server_channel_lock{this->channel_tree_mutex};
std::shared_ptr<BasicChannel> 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<ConnectedClient>& 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<ts::ServerChannel> 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<ts::server::ConnectedClient> &target,
const shared_ptr<ts::server::ConnectedClient> &target_client,
shared_ptr<ts::BasicChannel> target_channel,
const std::shared_ptr<ts::server::ConnectedClient> &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<ServerChannel>(target_channel);
auto s_source_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
assert(!target->currentChannel || s_source_channel != nullptr);
auto s_source_channel = dynamic_pointer_cast<ServerChannel>(target_client->currentChannel);
assert(!target_client->currentChannel || s_source_channel != nullptr);
std::deque<property::ClientProperties> 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<ConnectedClient>& 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<ConnectedClient>& 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<SpeakingClient>(target)}; client) {
s_target_channel->register_client(target_client);
if(auto client{dynamic_pointer_cast<SpeakingClient>(target_client)}; client) {
this->rtc_server().assign_channel(client->rtc_client_id, s_target_channel->rtc_channel_id);
}
if(auto client{dynamic_pointer_cast<VoiceClient>(target)}; client) {
if(auto client{dynamic_pointer_cast<VoiceClient>(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<SpeakingClient>(target)}; client) {
if(auto client{dynamic_pointer_cast<SpeakingClient>(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::milliseconds>(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<chrono::milliseconds>(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<bool>(false) ||
target->properties()[property::CLIENT_TALK_REQUEST].as_or<int64_t>(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<ChannelId> 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));
}

View File

@ -191,9 +191,15 @@ namespace ts {
bool notifyServerEdited(std::shared_ptr<ConnectedClient>, std::deque<std::string> keys);
bool notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient>, const std::deque<const property::PropertyDescription*>& keys, bool selfNotify = true); /* execute only with at least channel tree read lock! */
inline bool notifyClientPropertyUpdates(const std::shared_ptr<ConnectedClient>& client, const std::deque<property::ClientProperties>& keys, bool selfNotify = true) {
if(keys.empty()) return false;
if(keys.empty()) {
return false;
}
std::deque<const property::PropertyDescription*> _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);
};

View File

@ -335,7 +335,21 @@ void ConnectedClient::subscribeChannel(const std::deque<std::shared_ptr<BasicCha
for(const auto& target_channel : subscribed_channels) {
/* getClientsByChannel() does not acquire the server channel tree mutex */
auto channel_clients = ref_server->getClientsByChannel(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()) {

View File

@ -586,27 +586,32 @@ bool ConnectedClient::notifyChannelHide(const std::deque<ChannelId> &channel_ids
}
bool ConnectedClient::notifyChannelShow(const std::shared_ptr<ts::BasicChannel> &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;
}

View File

@ -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;
}
}

View File

@ -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&);

View File

@ -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::ClientProperties>{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<VirtualServer> target;
if(cmd[0].has("port")){
std::optional<ServerId> target_server_id{};
std::shared_ptr<VirtualServer> target{};
if(cmd[0].has("port")) {
target = serverInstance->getVoiceServerManager()->findServerByPort(cmd["port"].as<uint16_t>());
} else if(cmd[0].has("sid")) {
target = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"].as<ServerId>());
}
for(auto& parm : cmd[0].keys())
if(parm.find_first_not_of("0123456789") == string::npos)
target = serverInstance->getVoiceServerManager()->findServerById(static_cast<ServerId>(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<ServerId>());
} 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<ServerId>(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<milliseconds>(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<milliseconds>(_end - _start);
auto end_ = system_clock::now();
time_start = duration_cast<milliseconds>(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<string>();
port = this->server->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>();
} 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<milliseconds>(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());

View File

@ -22,11 +22,13 @@ constexpr static ServerId kSnapshotServerId{0xFFFF};
VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot(std::string &error, std::shared_ptr<VirtualServer>& 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<uint16_t>() : 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)