#include #include #include "client/voice/VoiceClient.h" #include "client/InternalClient.h" #include "VirtualServer.h" #include #include #include #include #include "InstanceHandler.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); { lock_guard lock(this->clients.lock); if(client->getClientId() > 0) { logCritical(this->getServerId(), "Client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid()); return false; } ClientId client_id = 0; ClientId max_client_id = this->clients.clients.size(); while(client_id < max_client_id && this->clients.clients[client_id]) client_id++; if(client_id == max_client_id) this->clients.clients.push_back(client); else this->clients.clients[client_id] = client; this->clients.count++; client->setClientId(client_id); } { lock_guard lock(this->client_nickname_lock); auto login_name = client->getDisplayName(); while(login_name.length() < 3) login_name += "."; if(client->getExternalType() == ClientType::CLIENT_TEAMSPEAK) client->properties()[property::CLIENT_LOGIN_NAME] = login_name; std::shared_ptr found_client = nullptr; auto client_name = login_name; size_t counter = 0; { lock_guard clients_lock(this->clients.lock); while(true) { for(auto& _client : this->clients.clients) { if(!_client) continue; if(_client->getDisplayName() == client_name && _client != client) goto increase_name; } goto nickname_valid; increase_name: client_name = login_name + to_string(++counter); } } nickname_valid: client->setDisplayName(client_name); } if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_WEB) { this->properties()[property::VIRTUALSERVER_CLIENT_CONNECTIONS] ++; //increase manager connections this->properties()[property::VIRTUALSERVER_LAST_CLIENT_CONNECT] = duration_cast(system_clock::now().time_since_epoch()).count(); } else if(client->getType() == 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] ++; //increase manager connections } return true; } bool VirtualServer::unregisterClient(shared_ptr cl, std::string reason, std::unique_lock& chan_tree_lock) { if(cl->getType() == ClientType::CLIENT_TEAMSPEAK && cl->getType() == ClientType::CLIENT_WEB) { sassert(cl->state == ConnectionState::DISCONNECTED); } auto client_id = cl->getClientId(); if(client_id == 0) return false; /* not registered */ { lock_guard lock(this->clients.lock); if(client_id >= this->clients.clients.size()) { logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister client.", cl->getDisplayName(), client_id, cl->getUid()); } else { auto& client_container = this->clients.clients[client_id]; if(client_container != cl) { logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister client.", cl->getDisplayName(), client_id, cl->getUid()); } else { client_container.reset(); this->clients.count--; } } } 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(); { 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 this->client_move(cl, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock); } serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions); cl->setClientId(0); return true; } void VirtualServer::registerInternalClient(std::shared_ptr client) { client->state = ConnectionState::CONNECTED; { lock_guard lock(this->clients.lock); if(client->getClientId() > 0) { logCritical(this->getServerId(), "Internal client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid()); return; } ClientId client_id = 0; ClientId max_client_id = this->clients.clients.size(); while(client_id < max_client_id && this->clients.clients[client_id]) client_id++; if(client_id == max_client_id) this->clients.clients.push_back(client); else this->clients.clients[client_id] = client; this->clients.clients[client_id] = client; this->clients.count++; client->setClientId(client_id); } } void VirtualServer::unregisterInternalClient(std::shared_ptr client) { client->state = ConnectionState::DISCONNECTED; { auto client_id = client->getClientId(); lock_guard lock(this->clients.lock); if(client_id >= this->clients.clients.size()) { logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister internal client.", client->getDisplayName(), client_id, client->getUid()); } else { auto& client_container = this->clients.clients[client_id]; if(client_container != client) { logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister internal client.", client->getDisplayName(), client_id, client->getUid()); } else { this->clients.count--; client_container.reset(); } } } } bool VirtualServer::assignDefaultChannel(const shared_ptr& client, bool join) { shared_lock server_channel_lock(this->channel_tree_lock); std::shared_ptr channel = nullptr; if(client->properties()->hasProperty(property::CLIENT_DEFAULT_CHANNEL) && !client->properties()[property::CLIENT_DEFAULT_CHANNEL].as().empty()) { auto str = client->properties()[property::CLIENT_DEFAULT_CHANNEL].as(); if(str[0] == '/' && str.find_first_not_of("0123456789", 1) == string::npos) channel = this->channelTree->findChannel(static_cast(stoll(str.substr(1)))); else channel = this->channelTree->findChannelByPath(str); if (channel) { if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) { logMessage(this->serverId, "{} Client tried to connect to a channel which he hasn't permission for. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name()); channel = nullptr; } else if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true) && !permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) { logMessage(this->serverId, "{} Client tried to connect to a channel which is password protected and he hasn't the right password. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name()); channel = nullptr; } } else logMessage(this->serverId, "{} Client {}/{} tried to join on a not existing channel. Name: {}", CLIENT_STR_LOG_PREFIX_(client), client->getDisplayName(), client->getUid(), client->properties()[property::CLIENT_DEFAULT_CHANNEL].as()); } if(!channel) channel = this->channelTree->getDefaultChannel(); if(!channel) return false; if(join) { server_channel_lock.unlock(); unique_lock server_channel_w_lock(this->channel_tree_lock); this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_w_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)); } }); } bool VirtualServer::could_default_create_channel() { { auto default_group = this->getGroupManager()->defaultGroup(GroupTarget::GROUPTARGET_SERVER); if(default_group) { auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1; flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1; flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1; if(flag) return true; } } { auto default_group = this->getGroupManager()->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL); if(default_group) { auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1; flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1; flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1; if(flag) return true; } } return false; } /* * for (auto &cl : this->server->getClients()) if (cl->isClientVisible(client) || client == cl) cl->notifyClientLeftViewKicked(client, client->currentChannel, nullptr, cmd["reasonmsg"].as(), this); */ 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_lock); /* 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_lock); 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_lock); 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_lock, 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_lock); /* 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_lock); 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); } /* now disconnect the target itself */ unique_lock client_channel_lock(target->channel_lock); 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(); tree_lock.unlock(); 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()) tree_lock.lock(); /* no clients left within that tree */ 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_lock); client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker); }); } void VirtualServer::client_move( const shared_ptr &target, 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.unlock(); lock_guard client_command_lock(target->command_lock); server_channel_write_lock.lock(); TIMING_STEP(timings, "chan tree l"); if(target->currentChannel == target_channel) return; /* first step: resolve the target channel / or fix missing */ 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); deque client_updates; std::deque changed_groups{}; if(target_channel) { assert(s_target_channel); if(s_target_channel->deleted) { target_channel = this->channelTree->getDefaultChannel(); s_target_channel = dynamic_pointer_cast(target_channel); assert(s_target_channel); } /* update the group properties here already, so for all enter views we could just send the new props directly */ changed_groups = this->groups->update_server_group_property(target, true, s_target_channel); client_updates.insert(client_updates.end(), changed_groups.begin(), changed_groups.end()); //TODO: Only update for clients which have no enter? } 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 subscibe to the channel */ if(target_channel && notify_client) { unique_lock client_channel_lock(target->channel_lock); bool success = false; /* TODO: Use a bunk here and not a notify for every single */ for(const auto& channel : target->channels->show_channel(l_target_channel, success)) target->notifyChannelShow(channel->channel(), channel->previous_channel); sassert(success); if(!success) return; target->subscribeChannel({target_channel}, false, true); } TIMING_STEP(timings, "target show"); if(target_channel) { this->forEachClient([&](const shared_ptr& client) { if (!notify_client && client == target) return; unique_lock client_channel_lock(client->channel_lock); auto chan_target = client->channels->find_channel(target_channel); if(chan_target) { auto chan_source = client->channels->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); } else { client->notifyClientEnterView(target, 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 == 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 { /* 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(s_source_channel) s_source_channel->unregister_client(target); s_target_channel->register_client(target); } else { /* client left the server */ if(target->currentChannel) { for(const auto& client : this->getClients()) { if(!client || client == target) continue; unique_lock client_channel_lock(client->channel_lock); if(client->isClientVisible(target, false)) client->notifyClientLeftView(target, nullptr, reason_id, reason_message, invoker, false); } s_source_channel->unregister_client(target); } } TIMING_STEP(timings, "notify view"); target->currentChannel = target_channel; server_channel_write_lock.unlock(); /* third step: update stuff for the client (remember: the client cant execute anything at the moment!) */ shared_lock server_channel_read_lock(this->channel_tree_lock); unique_lock client_channel_lock(target->channel_lock); 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(); auto source_channel_group = this->groups->getChannelGroupExact(target->getClientDatabaseId(), s_source_channel, false); if(source_channel_group) { auto default_data = source_channel_group->group->permissions()->permission_value_flagged(permission::b_group_is_permanent); if(!default_data.has_value || default_data.value != 1) this->groups->setChannelGroup(target->getClientDatabaseId(), nullptr, s_source_channel); } auto update = target->properties()[property::CLIENT_IS_TALKER].as() || target->properties()[property::CLIENT_TALK_REQUEST].as() > 0; if(update) { target->properties()[property::CLIENT_IS_TALKER] = 0; target->properties()[property::CLIENT_TALK_REQUEST] = 0; target->properties()[property::CLIENT_TALK_REQUEST_MSG] = ""; client_updates.push_back(property::CLIENT_IS_TALKER); client_updates.push_back(property::CLIENT_TALK_REQUEST); client_updates.push_back(property::CLIENT_TALK_REQUEST_MSG); } TIMING_STEP(timings, "src chan up"); } if (s_target_channel) { if(target->update_cached_permissions()) /* update cached calculated permissions */ target->sendNeededPermissions(false); TIMING_STEP(timings, "perm gr upd"); if(s_source_channel) { deque deleted; for(const auto& channel : target->channels->test_channel(l_source_channel, l_target_channel)) deleted.push_back(channel->channelId()); if(!deleted.empty()) target->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); 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); if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore, true)) { logTrace(this->serverId, "Force unsubscribing of client {} for channel {}/{}. (Channel switch and no permissions)", CLIENT_STR_LOG_PREFIX_(target), s_source_channel->name(), i_source_channel ); target->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_updates, s_source_channel ? true : false); TIMING_STEP(timings, "notify cpro"); if(s_target_channel) { target->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)); }