#include #include #include "./PermissionCalculator.h" #include "client/voice/VoiceClient.h" #include "client/InternalClient.h" #include "VirtualServer.h" #include #include #include #include #include "InstanceHandler.h" #include "./groups/GroupManager.h" using namespace std; using namespace ts::server; using namespace ts::protocol; using namespace ts::buffer; using namespace ts::permission; using namespace std::chrono; bool VirtualServer::registerClient(shared_ptr client) { sassert(client); { std::lock_guard clients_lock{this->clients_mutex}; if(client->getClientId() > 0) { logCritical(this->getServerId(), "Client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid()); return false; } ClientId client_id{0}; while(this->clients.count(client_id)) { client_id++; } this->clients.emplace(client_id, client); client->setClientId(client_id); } { std::lock_guard lock{this->client_nickname_lock}; auto login_name = client->getDisplayName(); if(client->getExternalType() == ClientType::CLIENT_TEAMSPEAK) { client->properties()[property::CLIENT_LOGIN_NAME] = login_name; } while(login_name.length() < 3) { login_name += "."; } std::shared_ptr found_client{nullptr}; auto registered_clients = this->getClients(); auto client_name{login_name}; size_t counter{0}; { while(true) { for(auto& _client : registered_clients) { if(_client->getDisplayName() == client_name && _client != client) { goto increase_name; } } goto nickname_valid; increase_name: client_name = login_name + std::to_string(++counter); } } nickname_valid: client->setDisplayName(client_name); } switch(client->getType()) { case ClientType::CLIENT_TEAMSPEAK: case ClientType::CLIENT_TEASPEAK: case ClientType::CLIENT_WEB: this->properties()[property::VIRTUALSERVER_CLIENT_CONNECTIONS].increment_by(1); //increase manager connections this->properties()[property::VIRTUALSERVER_LAST_CLIENT_CONNECT] = duration_cast(system_clock::now().time_since_epoch()).count(); break; case ClientType::CLIENT_QUERY: this->properties()[property::VIRTUALSERVER_LAST_QUERY_CONNECT] = duration_cast(system_clock::now().time_since_epoch()).count(); this->properties()[property::VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS].increment_by(1); //increase manager connections break; case ClientType::CLIENT_MUSIC: case ClientType::CLIENT_INTERNAL: break; case ClientType::MAX: default: assert(false); break; } return true; } bool VirtualServer::unregisterClient(shared_ptr client, std::string reason, std::unique_lock& chan_tree_lock) { if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_TEASPEAK || client->getType() == ClientType::CLIENT_WEB) { sassert(client->state == ConnectionState::DISCONNECTED); } { if(!chan_tree_lock.owns_lock()) { chan_tree_lock.lock(); } if(client->currentChannel) { //We dont have to make him invisible if he hasnt even a channel this->client_move(client, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock); } chan_tree_lock.unlock(); } { std::lock_guard clients_lock{this->clients_mutex}; auto client_id = client->getClientId(); if(client_id == 0) { return false; /* not registered */ } if(!this->clients.erase(client_id)) { client->setClientId(0); logError(this->getServerId(), "Tried to unregister a not registered client {}/{} ({})", client->getDisplayName(), client->getUid(), client_id); return false; } client->setClientId(0); } auto current_time_seconds = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); switch(client->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; } serverInstance->databaseHelper()->saveClientPermissions(this->ref(), client->getClientDatabaseId(), client->clientPermissions); return true; } void VirtualServer::registerInternalClient(std::shared_ptr client) { client->state = ConnectionState::CONNECTED; this->registerClient(client); } void VirtualServer::unregisterInternalClient(std::shared_ptr client) { client->state = ConnectionState::DISCONNECTED; std::unique_lock tree_lock{this->channel_tree_mutex}; this->unregisterClient(client, "internal disconnect", tree_lock); } bool VirtualServer::assignDefaultChannel(const shared_ptr& client, bool join) { 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) { ChannelId channel_id{0}; try { channel_id = std::stoull(requested_channel_path.substr(1)); } catch (std::exception&) { logTrace(this->getServerId(), "{} Failed to parse provided channel path as channel id."); } if(channel_id > 0) { channel = this->channelTree->findChannel(channel_id); } } else { channel = this->channelTree->findChannelByPath(requested_channel_path); } } if(channel) { /* Client proposes a target channel */ auto& channel_whitelist = client->join_whitelisted_channel; auto whitelist_entry = std::find_if(channel_whitelist.begin(), channel_whitelist.end(), [&](const auto& entry) { return entry.first == channel->channelId(); }); auto client_channel_password = client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD].value(); if(whitelist_entry != channel_whitelist.end()) { debugMessage(this->getServerId(), "{} Allowing client to join channel {} because the token he used explicitly allowed it.", client->getLoggingPrefix(), channel->channelId()); if(whitelist_entry->second != "ignore") { if (!channel->verify_password(std::make_optional(client_channel_password), true)) { if (!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) { channel = nullptr; goto skip_permissions; } } } goto skip_permissions; } if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) { debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't enough join power.", client->getLoggingPrefix(), channel->channelId()); channel = nullptr; goto skip_permissions; } if (!channel->verify_password(std::make_optional(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD].value()), true)) { if(!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) { debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't given the right channel password.", client->getLoggingPrefix(), channel->channelId()); channel = nullptr; goto skip_permissions; } } skip_permissions:; } /* Clear these parameters. We don't need them any more after we initially payed attention. */ client->properties()[property::CLIENT_DEFAULT_CHANNEL] = ""; client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD] = ""; if(!channel) { /* Client did not propose a channel or the proposed channel got rejected */ channel = this->channelTree->getDefaultChannel(); if(!channel) { logCritical(this->getServerId(), "Channel tree is missing the default channel."); return false; } } debugMessage(this->getServerId(), "{} Using channel {} as default client channel.", client->getLoggingPrefix(), channel->channelId()); if(join) { this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_lock); } else { client->currentChannel = channel; } return true; } void VirtualServer::testBanStateChange(const std::shared_ptr& invoker) { this->forEachClient([&](shared_ptr client) { auto ban = client->resolveActiveBan(client->getPeerIp()); if(ban) { logMessage(this->getServerId(), "Client {} was online, but had an ban whcih effect him has been registered. Disconnecting client.", CLIENT_STR_LOG_PREFIX_(client)); auto entryTime = ban->until.time_since_epoch().count() > 0 ? (uint64_t) chrono::ceil(ban->until - system_clock::now()).count() : 0UL; this->notify_client_ban(client, invoker, ban->reason, entryTime); client->close_connection(system_clock::now() + seconds(1)); } }); } void VirtualServer::notify_client_ban(const shared_ptr &target, const std::shared_ptr &invoker, const std::string &reason, size_t time) { /* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */ lock_guard command_lock(target->command_lock); unique_lock server_channel_lock(this->channel_tree_mutex); /* we're "moving" a client! */ if(target->currentChannel) { for(const auto& client : this->getClients()) { if(!client || client == target) continue; unique_lock client_channel_lock(client->channel_tree_mutex); if(client->isClientVisible(target, false)) client->notifyClientLeftViewBanned(target, reason, invoker, time, false); } auto s_channel = dynamic_pointer_cast(target->currentChannel); s_channel->unregister_client(target); } /* now disconnect the target itself */ unique_lock client_channel_lock(target->channel_tree_mutex); target->notifyClientLeftViewBanned(target, reason, invoker, time, false); target->currentChannel = nullptr; } void VirtualServer::notify_client_kick( const std::shared_ptr &target, const std::shared_ptr &invoker, const std::string &reason, const std::shared_ptr &target_channel) { if(target_channel) { /* use the move! */ unique_lock server_channel_lock(this->channel_tree_mutex, defer_lock); this->client_move(target, target_channel, invoker, reason, ViewReasonId::VREASON_CHANNEL_KICK, true, server_channel_lock); } else { /* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */ lock_guard command_lock(target->command_lock); unique_lock server_channel_lock(this->channel_tree_mutex); /* we're "moving" a client! */ if(target->currentChannel) { for(const auto& client : this->getClients()) { if(!client || client == target) continue; unique_lock client_channel_lock(client->channel_tree_mutex); if(client->isClientVisible(target, false)) client->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false); } auto s_channel = dynamic_pointer_cast(target->currentChannel); s_channel->unregister_client(target); if(auto client{dynamic_pointer_cast(target)}; client) { this->rtc_server().assign_channel(client->rtc_client_id, 0); } } /* now disconnect the target itself */ unique_lock client_channel_lock(target->channel_tree_mutex); target->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false); target->currentChannel = nullptr; } } /* * 1. flag channel as deleted (lock channel tree so no moves) * 2. Gather all clients within the channel (lock their execute lock) * 3. Unlock channel tree and lock client locks * 4. lock channel tree again and move the clients (No new clients should be joined because channel is flagged as deleted!) * * Note: channel cant be a ref because the channel itself gets deleted! */ void VirtualServer::delete_channel(shared_ptr channel, const shared_ptr &invoker, const std::string& kick_message, unique_lock &tree_lock, bool temp_delete) { if(!tree_lock.owns_lock()) { tree_lock.lock(); } if(channel->deleted) { return; } deque> clients; { for(const auto& sub_channel : this->channelTree->channels(channel)) { auto s_channel = dynamic_pointer_cast(sub_channel); assert(s_channel); auto chan_clients = this->getClientsByChannel(sub_channel); clients.insert(clients.end(), chan_clients.begin(), chan_clients.end()); s_channel->deleted = true; } auto chan_clients = this->getClientsByChannel(channel); clients.insert(clients.end(), chan_clients.begin(), chan_clients.end()); channel->deleted = true; } auto default_channel = this->channelTree->getDefaultChannel(); deque> command_locks; for(const auto& client : clients) { command_locks.push_back(move(unique_lock(client->command_lock))); } for(const auto& client : clients) { this->client_move(client, default_channel, invoker, kick_message, ViewReasonId::VREASON_CHANNEL_KICK, true, tree_lock); } if(!tree_lock.owns_lock()) { /* This case should never happen. client_move should never unlock the tree lock! */ tree_lock.lock(); } command_locks.clear(); auto deleted_channels = this->channelTree->delete_channel_root(channel); log::ChannelDeleteReason delete_reason{temp_delete ? log::ChannelDeleteReason::EMPTY : log::ChannelDeleteReason::USER_ACTION}; for(const auto& deleted_channel : deleted_channels) { serverInstance->action_logger()->channel_logger.log_channel_delete(this->serverId, invoker, deleted_channel->channelId(), channel == deleted_channel ? delete_reason : log::ChannelDeleteReason::PARENT_DELETED); } this->forEachClient([&](const shared_ptr& client) { unique_lock client_channel_lock(client->channel_tree_mutex); client->notifyChannelDeleted(client->channel_tree->delete_channel_root(channel), invoker); }); { std::vector deleted_channel_ids{}; deleted_channel_ids.reserve(deleted_channels.size()); for(const auto& deleted_channel : deleted_channels) { deleted_channel_ids.push_back(deleted_channel->channelId()); } auto ref_self = this->ref(); task_id task_id{}; serverInstance->general_task_executor()->schedule(task_id, "database cleanup after channel delete", [ref_self, deleted_channel_ids]{ for(const auto& deleted_channel_id : deleted_channel_ids) { ref_self->tokenManager->handle_channel_deleted(deleted_channel_id); } for(const auto& deleted_channel_id : deleted_channel_ids) { ref_self->group_manager()->assignments().handle_channel_deleted(deleted_channel_id); } }); } } /* * 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 appropriate mutexes. */ void VirtualServer::client_move( const shared_ptr &target_client, shared_ptr target_channel, const std::shared_ptr &invoker, const std::string &reason_message, ts::ViewReasonId reason_id, bool notify_client, std::unique_lock &server_channel_write_lock) { TIMING_START(timings); if(!server_channel_write_lock.owns_lock()) { server_channel_write_lock.lock(); } TIMING_STEP(timings, "chan tree l"); 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_client->currentChannel); assert(!target_client->currentChannel || s_source_channel != nullptr); std::deque updated_client_properties{}; if(target_channel) { assert(s_target_channel); if(s_target_channel->deleted) { return; } } auto l_target_channel = s_target_channel ? this->channelTree->findLinkedChannel(s_target_channel->channelId()) : nullptr; 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 subscribe to the channel */ if(target_channel && notify_client) { std::unique_lock client_channel_lock{target_client->channel_tree_mutex}; bool success{false}; /* TODO: Use a bunk here and not a notify for every single */ for(const auto& channel : target_client->channel_tree->show_channel(l_target_channel, success)) { target_client->notifyChannelShow(channel->channel(), channel->previous_channel); } 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) { ClientPermissionCalculator target_client_permissions{&*target_client, target_channel}; auto needed_view_power = target_client_permissions.calculate_permission(permission::i_client_needed_serverquery_view_power); /* ct_... is for client channel tree */ this->forEachClient([&](const std::shared_ptr& client) { if (!notify_client && client == target_client) { return; } 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_client, invoker, reason_message, s_target_channel, reason_id, s_source_channel, 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(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 { 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); } } } }); 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)}; client) { /* Start normal broadcasting, what the client expects */ this->rtc_server().start_broadcast_audio(client->rtc_client_id, 1); client->clear_video_unsupported_message_flag(); } } else { /* client left the server */ if(target_client->currentChannel) { for(const auto& client : this->getClients()) { if(!client || client == target_client) continue; unique_lock client_channel_lock(client->channel_tree_mutex); if(client->isClientVisible(target_client, false)) { client->notifyClientLeftView(target_client, nullptr, reason_id, reason_message, invoker, false); } } 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_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_client->channel_tree_mutex}; TIMING_STEP(timings, "lock own tr"); if (s_source_channel) { 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()); 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_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_client->channel_tree->test_channel(l_source_channel, l_target_channel)) { deleted.push_back(channel->channelId()); } if(!deleted.empty()) { 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_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_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_client), s_source_channel->name(), i_source_channel ); target_client->unsubscribeChannel({ s_source_channel }, false); //Unsubscribe last channel (hasn't permissions) } } } TIMING_STEP(timings, "src hide ts"); } } client_channel_lock.unlock(); /* both methods lock if they require stuff */ this->notifyClientPropertyUpdates(target_client, updated_client_properties, s_source_channel ? true : false); TIMING_STEP(timings, "notify cpro"); if(s_target_channel) { 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_client), TIMING_FINISH(timings)); }