Teaspeak-Server/server/src/VirtualServer.cpp

1182 lines
48 KiB
C++

#include <cstring>
#include <functional>
#include <protocol/buffers.h>
#include <netinet/in.h>
#include <bitset>
#include <tomcrypt.h>
#include <log/LogUtils.h>
#include <misc/digest.h>
#include <misc/base64.h>
#include <files/FileServer.h>
#include "./client/web/WebClient.h"
#include "./client/voice/VoiceClient.h"
#include "./client/InternalClient.h"
#include "./client/music/MusicClient.h"
#include "./client/query/QueryClient.h"
#include "music/MusicBotManager.h"
#include "server/VoiceServer.h"
#include "server/QueryServer.h"
#include "InstanceHandler.h"
#include "Configuration.h"
#include "VirtualServer.h"
#include "./rtc/lib.h"
#include "src/manager/ConversationManager.h"
#include <misc/sassert.h>
#include <src/manager/ActionLogger.h>
#include "./groups/GroupManager.h"
#include "./PermissionCalculator.h"
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::protocol;
using namespace ts::buffer;
#define ECC_TYPE_INDEX 5
#ifndef BUILD_VERSION
#define BUILD_VERSION "Unknown build"
#endif
VirtualServer::VirtualServer(uint16_t serverId, sql::SqlManager* database) : serverId(serverId), sql(database) {
memtrack::allocated<VirtualServer>(this);
}
bool VirtualServer::initialize(bool test_properties) {
assert(self.lock());
std::string error{};
this->rtc_server_ = std::make_unique<rtc::Server>();
this->_properties = serverInstance->databaseHelper()->loadServerProperties(self.lock());
this->_properties->registerNotifyHandler([&](Property& prop){
if(prop.type() == property::VIRTUALSERVER_DISABLE_IP_SAVING) {
this->_disable_ip_saving = prop.as_or<bool>(false);
return;
} else if(prop.type() == property::VIRTUALSERVER_CODEC_ENCRYPTION_MODE) {
this->_voice_encryption_mode = prop.as_or<int>(0);
return;
} else if(prop.type() == property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH) {
auto file_vs = file::server()->find_virtual_server(this->getServerId());
if(!file_vs) return;
file_vs->max_networking_upload_bandwidth(prop.as_or<int64_t>(-1));
} else if(prop.type() == property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH) {
auto file_vs = file::server()->find_virtual_server(this->getServerId());
if(!file_vs) return;
file_vs->max_networking_download_bandwidth(prop.as_or<int64_t>(-1));
}
std::string sql{};
if(prop.type() == property::VIRTUALSERVER_HOST)
sql = "UPDATE `servers` SET `host` = :value WHERE `serverId` = :sid";
else if(prop.type() == property::VIRTUALSERVER_PORT)
sql = "UPDATE `servers` SET `port` = :value WHERE `serverId` = :sid";
if(sql.empty()) return;
sql::command(this->sql, sql, variable{":sid", this->getServerId()}, variable{":value", prop.value()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, sql::result{1, "future failed"});
});
this->properties()[property::VIRTUALSERVER_PLATFORM] = config::server::DefaultServerPlatform;
this->properties()[property::VIRTUALSERVER_VERSION] = config::server::DefaultServerVersion;
this->properties()[property::VIRTUALSERVER_ID] = serverId;
this->_disable_ip_saving = this->properties()[property::VIRTUALSERVER_DISABLE_IP_SAVING];
/* initialize logging */
{
auto server_id = this->serverId;
auto sync_property = [server_id](Property& prop) {
log::LoggerGroup action_type;
switch (prop.type().property_index) {
case property::VIRTUALSERVER_LOG_SERVER:
action_type = log::LoggerGroup::SERVER;
break;
case property::VIRTUALSERVER_LOG_CHANNEL:
action_type = log::LoggerGroup::CHANNEL;
break;
case property::VIRTUALSERVER_LOG_CLIENT:
action_type = log::LoggerGroup::CLIENT;
break;
case property::VIRTUALSERVER_LOG_FILETRANSFER:
action_type = log::LoggerGroup::FILES;
break;
case property::VIRTUALSERVER_LOG_PERMISSIONS:
action_type = log::LoggerGroup::PERMISSION;
break;
case property::VIRTUALSERVER_LOG_QUERY:
action_type = log::LoggerGroup::QUERY;
break;
default:
return;
}
serverInstance->action_logger()->toggle_logging_group(server_id, action_type,
prop.as_or<bool>(true));
};
for(const property::VirtualServerProperties& property : {
property::VIRTUALSERVER_LOG_SERVER,
property::VIRTUALSERVER_LOG_CHANNEL,
property::VIRTUALSERVER_LOG_CLIENT,
property::VIRTUALSERVER_LOG_FILETRANSFER,
property::VIRTUALSERVER_LOG_QUERY,
property::VIRTUALSERVER_LOG_PERMISSIONS
}) {
auto prop = this->_properties->get(property::PROP_TYPE_SERVER, property);
sync_property(prop);
}
this->_properties->registerNotifyHandler([sync_property](Property& prop){
sync_property(prop);
});
}
if(!properties()[property::VIRTUALSERVER_KEYPAIR].value().empty()){
debugMessage(this->serverId, "Importing server keypair");
this->_serverKey = new ecc_key;
auto bytes = base64::decode(properties()[property::VIRTUALSERVER_KEYPAIR].value());
int err;
if((err = ecc_import(reinterpret_cast<const unsigned char *>(bytes.data()), bytes.length(), this->_serverKey)) != CRYPT_OK){
logError(this->getServerId(), "Cant import key. ({} => {})", err, error_to_string(err));
logError(this->serverId, "Could not import server keypair! {} ({}). Generating new one!", err, error_to_string(err));
delete this->_serverKey;
this->_serverKey = nullptr;
properties()[property::VIRTUALSERVER_KEYPAIR] = "";
}
}
int err;
if(!_serverKey){
debugMessage(this->serverId, "Generating new server keypair");
this->_serverKey = new ecc_key;
prng_state state{};
if((err = ecc_make_key_ex(&state, find_prng("sprng"), this->_serverKey, &ltc_ecc_sets[ECC_TYPE_INDEX])) != CRYPT_OK){
logError(this->serverId, "Could not generate a server keypair! {} ({})", err, error_to_string(err));
delete this->_serverKey;
this->_serverKey = nullptr;
return false;
}
size_t bytesBufferLength = 1024;
char bytesBuffer[bytesBufferLength];
if((err = ecc_export(reinterpret_cast<unsigned char *>(bytesBuffer), &bytesBufferLength, PK_PRIVATE, this->_serverKey)) != CRYPT_OK){
logError(this->serverId, "Could not export the server keypair (private)! {} ({})", err, error_to_string(err));
delete this->_serverKey;
this->_serverKey = nullptr;
return false;
}
properties()[property::VIRTUALSERVER_KEYPAIR] = base64_encode(bytesBuffer, bytesBufferLength);
this->properties()[property::VIRTUALSERVER_CREATED] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}
if(_serverKey){
size_t bufferLength = 265;
char buffer[bufferLength];
if((err = ecc_export(reinterpret_cast<unsigned char *>(buffer), &bufferLength, PK_PUBLIC, this->_serverKey)) != CRYPT_OK)
logError(this->serverId, "Could not generate server uid! (Could not export the server keypair (public)! {} ({}))", err, error_to_string(err));
properties()[property::VIRTUALSERVER_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(base64::encode(buffer, bufferLength)));
}
this->conversation_manager_ = make_shared<conversation::ConversationManager>(this->ref());
this->conversation_manager_->initialize(this->conversation_manager_);
channelTree = new ServerChannelTree(self.lock(), this->sql);
channelTree->loadChannelsFromDatabase();
this->groups_manager_ = std::make_shared<groups::GroupManager>(this->getSql(), this->getServerId(), serverInstance->group_manager());
if(!this->groups_manager_->initialize(this->groups_manager_, error)) {
logCritical(this->getServerId(), "Failed to initialize group manager: {}", error);
return false;
}
channelTree->deleteSemiPermanentChannels();
if(channelTree->channel_count() == 0) {
logMessage(this->serverId, "Creating new channel tree (Copy from server 0)");
LOG_SQL_CMD(sql::command(this->getSql(), "INSERT INTO `channels` (`serverId`, `channelId`, `type`, `parentId`) SELECT :serverId AS `serverId`, `channelId`, `type`, `parentId` FROM `channels` WHERE `serverId` = 0", variable{":serverId", this->serverId}).execute());
LOG_SQL_CMD(sql::command(this->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :serverId AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `serverId` = 0 AND `type` = :type",
variable{":serverId", this->serverId}, variable{":type", property::PROP_TYPE_CHANNEL}).execute());
LOG_SQL_CMD(sql::command(this->getSql(), "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) "
"SELECT :serverId AS `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = 0 AND `type` = :type",
variable{":serverId", this->serverId}, variable{":type", permission::SQL_PERM_CHANNEL}).execute());
channelTree->loadChannelsFromDatabase();
if(channelTree->channel_count() == 0){
logCritical(this->serverId, "Failed to setup channel tree!");
return false;
}
if(!channelTree->getDefaultChannel()) {
logError(this->serverId, "Missing default channel! Using first one!");
channelTree->setDefaultChannel(channelTree->channels().front());
}
}
if(!channelTree->getDefaultChannel()) channelTree->setDefaultChannel(*channelTree->channels().begin());
auto default_channel = channelTree->getDefaultChannel();
assert(default_channel);
if(default_channel->properties()[property::CHANNEL_FLAG_PASSWORD].as_or<bool>(false))
default_channel->properties()[property::CHANNEL_FLAG_PASSWORD] = false;
this->tokenManager = new token::TokenManager(this->sql, this->getServerId());
this->tokenManager->initialize_cache();
this->complains = new ComplainManager(this);
if(!this->complains->loadComplains()) logError(this->serverId, "Could not load complains");
{
using groups::GroupLoadResult;
bool initialize_groups{false};
switch(this->groups_manager_->server_groups()->load_data(true)) {
case GroupLoadResult::SUCCESS:
break;
case GroupLoadResult::NO_GROUPS:
initialize_groups = true;
break;
case GroupLoadResult::DATABASE_ERROR:
logError(this->getServerId(), "Failed to load server groups (Database error)");
return false;
}
switch(this->groups_manager_->channel_groups()->load_data(true)) {
case GroupLoadResult::SUCCESS:
break;
case GroupLoadResult::NO_GROUPS:
initialize_groups = true;
break;
case GroupLoadResult::DATABASE_ERROR:
logError(this->getServerId(), "Failed to load channel groups (Database error)");
return false;
}
if(!this->groups_manager_->assignments().load_data(error)) {
logError(this->getServerId(), "Failed to load group assignments: {}", error);
return false;
}
if (initialize_groups) {
if(!this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY].value().empty()) {
logCritical(this->getServerId(), "Missing default groups. Applying permission reset!");
}
string token;
if(!this->resetPermissions(token)) {
logCritical(this->serverId, "Failed to reset server permissions! This could be fatal!");
}
logMessageFmt(true, this->serverId, "---------------------- Token ----------------------");
logMessageFmt(true, this->serverId, "{:^51}", "The server's serveradmin token:");
logMessageFmt(true, this->serverId, "{:^51}", token);
logMessageFmt(true, this->serverId, "");
logMessageFmt(true, this->serverId, "{:^51}", "Note: This token could be used just once!");
logMessageFmt(true, this->serverId, "---------------------- Token ----------------------");
}
}
if(test_properties)
this->ensureValidDefaultGroups();
letters = new letter::LetterManager(this);
server_statistics_ = make_shared<stats::ConnectionStatistics>(serverInstance->getStatistics());
this->serverRoot = std::make_shared<InternalClient>(this->sql, self.lock(),
this->properties()[property::VIRTUALSERVER_NAME].value(), false);
this->serverRoot->initialize_weak_reference(this->serverRoot);
this->properties()->registerNotifyHandler([&](Property& property) {
if(property.type() == property::VIRTUALSERVER_NAME) {
static_pointer_cast<InternalClient>(this->serverRoot)->properties()[property::CLIENT_NICKNAME] = property.value();
}
});
this->serverRoot->server = nullptr;
this->serverAdmin = std::make_shared<InternalClient>(this->sql, self.lock(), "serveradmin", true);
this->serverAdmin->initialize_weak_reference(this->serverAdmin);
DatabaseHelper::assignDatabaseId(this->sql, this->serverId, this->serverAdmin);
this->serverAdmin->server = nullptr;
this->registerInternalClient(this->serverAdmin); /* lets assign server id 0 */
{
using ErrorType = file::filesystem::ServerCommandErrorType;
auto file_vs = file::server()->register_server(this->getServerId());
auto initialize_result = file::server()->file_system().initialize_server(file_vs);
if(!initialize_result->wait_for(std::chrono::seconds{5})) {
logError(this->getServerId(), "Failed to wait for file directory initialisation.");
} else if(!initialize_result->succeeded()) {
switch (initialize_result->error().error_type) {
case ErrorType::FAILED_TO_CREATE_DIRECTORIES:
logError(this->getServerId(), "Failed to create server file directories ({}).", initialize_result->error().error_message);
break;
case ErrorType::UNKNOWN:
case ErrorType::FAILED_TO_DELETE_DIRECTORIES:
logError(this->getServerId(), "Failed to initialize server file directory due to an unknown error: {}/{}",
(int) initialize_result->error().error_type, initialize_result->error().error_message);
break;
}
}
this->properties()[property::VIRTUALSERVER_FILEBASE] = file::server()->file_base_path();
file_vs->max_networking_download_bandwidth(
this->properties()[property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH].as_or<int64_t>(-1));
file_vs->max_networking_upload_bandwidth(
this->properties()[property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH].as_or<int64_t>(-1));
}
this->channelTree->printChannelTree([&](std::string msg){ debugMessage(this->serverId, msg); });
this->music_manager_ = make_shared<music::MusicBotManager>(self.lock());
this->music_manager_->_self = this->music_manager_;
this->music_manager_->load_playlists();
this->music_manager_->load_bots();
#if 0
if(this->properties()[property::VIRTUALSERVER_ICON_ID] != (IconId) 0)
if(!serverInstance->getFileServer()->iconExists(self.lock(), this->properties()[property::VIRTUALSERVER_ICON_ID])) {
debugMessage(this->getServerId(), "Removing invalid icon id of server");
this->properties()[property::VIRTUALSERVER_ICON_ID] = 0;
}
#endif
for(const auto& type : vector<property::VirtualServerProperties>{
property::VIRTUALSERVER_DOWNLOAD_QUOTA,
property::VIRTUALSERVER_UPLOAD_QUOTA,
property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH,
property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH,
}) {
const auto& info = property::describe(type);
auto prop = this->properties()[type];
if(prop.default_value() == prop.value()) continue;
if(!info.validate_input(this->properties()[type].value())) {
this->properties()[type] = info.default_value;
logMessage(this->getServerId(), "Server property " + std::string{info.name} + " contains an invalid value! Resetting it.");
}
}
/* lets cleanup the conversations for not existent channels */
this->conversation_manager_->synchronize_channels();
{
auto ref_self = this->self;
this->task_notify_channel_group_list = multi_shot_task{serverInstance->general_task_executor(), "server notify channel group list", [ref_self]{
auto this_ = ref_self.lock();
if(this_) {
this_->forEachClient([](const std::shared_ptr<ConnectedClient>& client) {
std::optional<ts::command_builder> generated_notify{};
client->notifyChannelGroupList(generated_notify, true);
});
}
}};
this->task_notify_server_group_list = multi_shot_task{serverInstance->general_task_executor(), "server notify server group list", [ref_self]{
auto this_ = ref_self.lock();
if(this_) {
this_->forEachClient([](const std::shared_ptr<ConnectedClient>& client) {
std::optional<ts::command_builder> generated_notify{};
client->notifyServerGroupList(generated_notify, true);
});
}
}};
}
return true;
}
VirtualServer::~VirtualServer() {
memtrack::freed<VirtualServer>(this);
delete this->tokenManager;
delete this->channelTree;
delete this->letters;
delete this->complains;
this->conversation_manager_.reset();
if(this->_serverKey) ecc_free(this->_serverKey);
delete this->_serverKey;
}
inline bool evaluateAddress4(const string &input, in_addr &address) {
if(input == "0.0.0.0") {
address.s_addr = INADDR_ANY;
return true;
};
auto record = gethostbyname(input.c_str());
if(!record) return false;
address.s_addr = ((in_addr*) record->h_addr)->s_addr;
return true;
}
inline bool evaluateAddress6(const string &input, in6_addr &address) {
if(input == "::") {
address = IN6ADDR_ANY_INIT;
return true;
};
auto record = gethostbyname2(input.c_str(), AF_INET6);
if(!record) return false;
address = *(in6_addr*) record->h_addr;
return true;
}
inline string strip(std::string message) {
while(!message.empty()) {
if(message[0] == ' ')
message = message.substr(1);
else if(message[message.length() - 1] == ' ')
message = message.substr(0, message.length() - 1);
else break;
}
return message;
}
inline vector<string> split_hosts(const std::string& message, char delimiter) {
vector<string> result;
size_t found, index = 0;
do {
found = message.find(delimiter, index);
result.push_back(strip(message.substr(index, found - index)));
index = found + 1;
} while(index != 0);
return result;
}
bool VirtualServer::start(std::string& error) {
{
threads::Mutex lock(this->stateLock);
if(this->state != ServerState::OFFLINE){
error = "Server isn't offline";
return false;
}
this->state = ServerState::BOOTING;
}
this->serverRoot->server = self.lock();
this->serverAdmin->server = self.lock();
{ //Client delete after server stop/start
lock_guard lock(this->clients.lock);
for(auto& client : this->clients.clients) {
if(!client) continue;
if(client->getType() == ClientType::CLIENT_WEB || client->getType() == ClientType::CLIENT_TEAMSPEAK) {
client.reset();
}
}
}
auto host = this->properties()[property::VIRTUALSERVER_HOST].value();
if(config::binding::enforce_default_voice_host)
host = config::binding::DefaultVoiceHost;
if(host.empty()){
error = "invalid host (\"" + host + "\")";
this->stop("failed to start", true);
return false;
}
if(this->properties()[property::VIRTUALSERVER_PORT].as_or<uint16_t>(0) <= 0){
error = "invalid port";
this->stop("failed to start", true);
return false;
}
deque<shared_ptr<VoiceServerBinding>> bindings;
for(const auto& address : split_hosts(host, ',')) {
auto entry = make_shared<VoiceServerBinding>();
if(net::is_ipv4(address)) {
sockaddr_in addr{};
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as_or<uint16_t>(0));
if(!evaluateAddress4(address, addr.sin_addr)) {
logError(this->serverId, "Fail to resolve v4 address info for \"{}\"", address);
continue;
}
memcpy(&entry->address, &addr, sizeof(addr));
} else if(net::is_ipv6(address)) {
sockaddr_in6 addr{};
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as_or<uint16_t>(0));
if(!evaluateAddress6(address, addr.sin6_addr)) {
logError(this->serverId, "Fail to resolve v6 address info for \"{}\"", address);
continue;
}
memcpy(&entry->address, &addr, sizeof(addr));
} else {
logError(this->serverId, "Failed to determinate address type for \"{}\"", address);
continue;
}
bindings.push_back(entry);
}
if(bindings.empty()) {
error = "failed to resole any host!";
this->stop("failed to start", false);
return false;
}
//Setup voice server
udpVoiceServer = make_shared<VoiceServer>(self.lock());
if(!udpVoiceServer->start(bindings, error)) {
error = "could not start voice server. Message: " + error;
this->stop("failed to start", false);
return false;
}
if(ts::config::web::activated && serverInstance->sslManager()->web_ssl_options()) {
string web_host_string = this->properties()[property::VIRTUALSERVER_WEB_HOST];
if(web_host_string.empty())
web_host_string = this->properties()[property::VIRTUALSERVER_HOST].as_or<string>(0);
auto web_port = this->properties()[property::VIRTUALSERVER_WEB_PORT].as_or<uint16_t>(0);
if(web_port == 0)
web_port = this->properties()[property::VIRTUALSERVER_PORT].as_or<uint16_t>(0);
startTimestamp = std::chrono::system_clock::now();
#ifdef COMPILE_WEB_CLIENT
webControlServer = new WebControlServer(self.lock());
auto web_bindings = net::resolve_bindings(web_host_string, web_port);
deque<shared_ptr<WebControlServer::Binding>> bindings;
for(auto& binding : web_bindings) {
if(!get<2>(binding).empty()) {
logError(this->serverId, "[Web] Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding));
continue;
}
auto entry = make_shared<WebControlServer::Binding>();
memcpy(&entry->address, &get<1>(binding), sizeof(sockaddr_storage));
entry->file_descriptor = -1;
entry->event_accept = nullptr;
bindings.push_back(entry);
}
logMessage(this->serverId, "[Web] Starting server on {}:{}", web_host_string, web_port);
if(!webControlServer->start(bindings, error)) {
error = "could not start web server. Message: " + error;
this->stop("failed to start", false);
return false;
}
#endif
}
auto weak_this = this->self;
serverInstance->general_task_executor()->schedule_repeating(
this->tick_task_id,
"server tick " + std::to_string(this->serverId),
std::chrono::milliseconds {500},
[weak_this](const auto& scheduled){
auto ref_self = weak_this.lock();
if(ref_self) {
ref_self->executeServerTick();
}
}
);
properties()[property::VIRTUALSERVER_CLIENTS_ONLINE] = 0;
properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE] = 0;
properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = 0;
properties()[property::VIRTUALSERVER_UPTIME] = 0;
this->startTimestamp = system_clock::now();
this->music_manager_->cleanup_semi_bots();
this->music_manager_->connectBots();
{
threads::MutexLock lock(this->stateLock);
this->state = ServerState::ONLINE;
}
return true;
}
std::string VirtualServer::publicServerKey() {
size_t keyBufferLength = 265;
char keyBuffer[keyBufferLength];
if(ecc_export((unsigned char *) keyBuffer, &keyBufferLength, PK_PUBLIC, this->_serverKey) != CRYPT_OK) return "";
return base64::encode(string(keyBuffer, keyBufferLength));
}
bool VirtualServer::running() {
return this->state == ServerState::BOOTING || this->state == ServerState::ONLINE;
}
void VirtualServer::preStop(const std::string& reason) {
{
threads::MutexLock lock(this->stateLock);
if(!this->running() && this->state != ServerState::SUSPENDING) return;
this->state = ServerState::SUSPENDING;
}
for(const auto& cl : this->getClients()) {
unique_lock channel_lock(cl->channel_lock);
if (cl->currentChannel) {
auto vc = dynamic_pointer_cast<VoiceClient>(cl);
if(vc) {
vc->disconnect(VREASON_SERVER_SHUTDOWN, reason, nullptr, false);
} else {
cl->notifyClientLeftView(cl, nullptr, ViewReasonId::VREASON_SERVER_SHUTDOWN, reason, nullptr, false);
}
}
cl->visibleClients.clear();
cl->mutedClients.clear();
}
}
void VirtualServer::stop(const std::string& reason, bool disconnect_query) {
auto self_lock = this->self.lock();
assert(self_lock);
{
threads::MutexLock lock(this->stateLock);
if(!this->running() && this->state != ServerState::SUSPENDING) return;
this->state = ServerState::SUSPENDING;
}
this->preStop(reason);
for(const auto& cl : this->getClients()) { //start disconnecting
if(cl->getType() == CLIENT_TEAMSPEAK || cl->getType() == CLIENT_TEASPEAK || cl->getType() == CLIENT_WEB) {
cl->close_connection(chrono::system_clock::now() + chrono::seconds(1));
} else if(cl->getType() == CLIENT_QUERY){
threads::MutexLock lock(cl->command_lock);
cl->currentChannel = nullptr;
if(disconnect_query) {
auto qc = dynamic_pointer_cast<QueryClient>(cl);
qc->disconnect_from_virtual_server("server disconnect");
}
} else if(cl->getType() == CLIENT_MUSIC) {
cl->disconnect("");
cl->currentChannel = nullptr;
} else if(cl->getType() == CLIENT_INTERNAL) {
} else {
logError(this->serverId, "Got client with unknown type: " + to_string(cl->getType()));
}
}
this->music_manager_->disconnectBots();
serverInstance->general_task_executor()->cancel_task(this->tick_task_id);
this->tick_task_id = 0;
if(this->udpVoiceServer) this->udpVoiceServer->stop();
this->udpVoiceServer = nullptr;
#ifdef COMPILE_WEB_CLIENT
if(this->webControlServer) this->webControlServer->stop();
delete this->webControlServer;
this->webControlServer = nullptr;
#endif
properties()[property::VIRTUALSERVER_CLIENTS_ONLINE] = 0;
properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE] = 0;
properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = 0;
properties()[property::VIRTUALSERVER_UPTIME] = 0;
{
threads::MutexLock lock(this->stateLock);
this->state = ServerState::OFFLINE;
}
this->serverRoot->server = nullptr;
this->serverAdmin->server = nullptr;
}
size_t VirtualServer::onlineClients() {
size_t result = 0;
lock_guard lock(this->clients.lock);
for(const auto &cl : this->clients.clients) {
if(!cl)
continue;
if(cl->getType() == CLIENT_TEAMSPEAK || cl->getType() == CLIENT_QUERY)
result++;
}
return result;
}
OnlineClientReport VirtualServer::onlineStats() {
OnlineClientReport response{};
{
lock_guard lock(this->clients.lock);
for(const auto &cl : this->clients.clients) {
if(!cl) continue;
switch (cl->getType()) {
case CLIENT_TEAMSPEAK:
case CLIENT_TEASPEAK:
response.clients_ts++;
break;
case CLIENT_WEB:
response.clients_web++;
break;
case CLIENT_QUERY:
response.queries++;
break;
case CLIENT_MUSIC:
response.bots++;
break;
case CLIENT_INTERNAL:
case MAX:
default:
break;
}
}
}
return response;
}
std::shared_ptr<ConnectedClient> VirtualServer::find_client_by_id(uint16_t client_id) {
lock_guard lock(this->clients.lock);
if(this->clients.clients.size() > client_id)
return this->clients.clients[client_id];
else
return nullptr;
}
deque<shared_ptr<ConnectedClient>> VirtualServer::findClientsByCldbId(uint64_t cldbId) {
deque<shared_ptr<ConnectedClient>> result;
lock_guard lock(this->clients.lock);
for(const auto &client : this->clients.clients) {
if(!client) continue;
if(client->getClientDatabaseId() == cldbId)
result.push_back(client);
}
return result;
}
deque<shared_ptr<ConnectedClient>> VirtualServer::findClientsByUid(std::string uid) {
lock_guard lock(this->clients.lock);
deque<shared_ptr<ConnectedClient>> result;
for(const auto &client : this->clients.clients) {
if(!client) continue;
if(client->getUid() == uid) {
result.push_back(client);
}
}
return result;
}
std::shared_ptr<ConnectedClient> VirtualServer::findClient(std::string name, bool ignoreCase) {
if(ignoreCase) {
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
}
{
lock_guard lock(this->clients.lock);
for(const auto& client : this->clients.clients) {
if(!client) continue;
string clName = client->getDisplayName();
if(ignoreCase) {
std::transform(clName.begin(), clName.end(), clName.begin(), ::tolower);
}
if(clName == name)
return client;
}
}
return nullptr;
}
bool VirtualServer::forEachClient(std::function<void(std::shared_ptr<ConnectedClient>)> function) {
for(const auto& elm : this->getClients()) {
shared_lock close_lock(elm->finalDisconnectLock, try_to_lock_t{});
if(close_lock.owns_lock()) {
//If not locked than client is on the way to disconnect
if(elm->state == ConnectionState::CONNECTED && elm->getType() != ClientType::CLIENT_INTERNAL) {
function(elm);
}
}
}
return true;
}
std::vector<std::shared_ptr<ConnectedClient>> VirtualServer::getClients() {
vector<shared_ptr<ConnectedClient>> clients;
{
lock_guard lock(this->clients.lock);
clients.reserve(this->clients.count);
for(auto& client : this->clients.clients) {
if(!client) continue;
clients.push_back(client);
}
}
return clients;
}
deque<shared_ptr<ConnectedClient>> VirtualServer::getClientsByChannel(std::shared_ptr<BasicChannel> channel) {
assert(this);
auto s_channel = dynamic_pointer_cast<ServerChannel>(channel);
if(!s_channel) return {}; /* what had we done wrong here... :D */
shared_lock client_lock(s_channel->client_lock);
auto weak_clients = s_channel->clients;
client_lock.unlock();
std::deque<std::shared_ptr<ConnectedClient>> result;
for(const auto& weak_client : weak_clients) {
auto client = weak_client.lock();
if(!client) continue;
if(client->connectionState() != ConnectionState::CONNECTED) continue;
if(client->getChannel() != channel) continue; /* to be sure */
result.push_back(move(client));
}
return result;
}
deque<shared_ptr<ConnectedClient>> VirtualServer::getClientsByChannelRoot(const std::shared_ptr<BasicChannel> &root, bool lock) {
assert(this);
shared_lock channel_lock(this->channel_tree_lock, defer_lock);
if(lock)
channel_lock.lock();
std::deque<std::shared_ptr<ConnectedClient>> result;
for(const auto& channel : this->channelTree->channels(root)) {
auto clients = this->getClientsByChannel(channel);
result.insert(result.end(), clients.begin(), clients.end());
}
return result;
}
bool VirtualServer::notifyServerEdited(std::shared_ptr<ConnectedClient> invoker, deque<string> keys) {
if(!invoker) return false;
Command cmd("notifyserveredited");
cmd["invokerid"] = invoker->getClientId();
cmd["invokername"] = invoker->getDisplayName();
cmd["invokeruid"] = invoker->getUid();
cmd["reasonid"] = ViewReasonId::VREASON_EDITED;
for(const auto& key : keys) {
const auto& info = property::find<property::VirtualServerProperties>(key);
if(info == property::VIRTUALSERVER_UNDEFINED) {
logError(this->getServerId(), "Tried to broadcast a server update with an unknown info: " + key);
continue;
}
cmd[key] = properties()[info].value();
}
this->forEachClient([&cmd](shared_ptr<ConnectedClient> client){
client->sendCommand(cmd);
});
return true;
}
bool VirtualServer::notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient> client, const deque<const property::PropertyDescription*>& keys, bool selfNotify) {
if(keys.empty() || !client) return false;
this->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
shared_lock client_channel_lock(cl->channel_lock);
if(cl->isClientVisible(client, false) || (cl == client && selfNotify))
cl->notifyClientUpdated(client, keys, false);
});
return true;
}
void VirtualServer::broadcastMessage(std::shared_ptr<ConnectedClient> invoker, std::string message) {
if(!invoker) {
logCritical(this->serverId, "Tried to broadcast with an invalid invoker!");
return;
}
this->forEachClient([&](shared_ptr<ConnectedClient> cl){
cl->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, invoker, 0, 0, system_clock::now(), message);
});
}
ts_always_inline bool channel_ignore_permission(ts::permission::PermissionType type) {
return permission::i_icon_id == type;
}
vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlaggedValue>> VirtualServer::calculate_permissions(
const std::deque<permission::PermissionType>& permissions,
ClientDbId client_dbid,
ClientType client_type,
ChannelId channel_id,
bool calculate_granted,
std::shared_ptr<CalculateCache> cache) {
ClientPermissionCalculator calculator{this->ref(), client_dbid, client_type, channel_id};
return calculator.calculate_permissions(permissions, calculate_granted);
}
permission::v2::PermissionFlaggedValue VirtualServer::calculate_permission(
permission::PermissionType permission,
ClientDbId cldbid,
ClientType type,
ChannelId channel,
bool granted,
std::shared_ptr<CalculateCache> cache) {
auto result = this->calculate_permissions({permission}, cldbid, type, channel, granted, cache);
if(result.empty()) return {0, false};
return result.front().second;
}
bool VirtualServer::verifyServerPassword(std::string password, bool hashed) {
if(!this->properties()[property::VIRTUALSERVER_FLAG_PASSWORD].as_or<bool>(false)) {
return true;
}
if(password.empty()) {
return false;
}
if(!hashed) {
password = base64::encode(digest::sha1(password));
}
return password == this->properties()[property::VIRTUALSERVER_PASSWORD].value();
}
VirtualServer::NetworkReport VirtualServer::generate_network_report() {
double total_ping{0}, total_loss{0};
size_t pings_counted{0}, loss_counted{0};
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& client) {
if(auto vc = dynamic_pointer_cast<VoiceClient>(client); vc) {
total_ping += vc->current_ping().count();
total_loss += vc->current_packet_loss();
pings_counted++;
loss_counted++;
}
#ifdef COMPILE_WEB_CLIENT
else if(client->getType() == ClientType::CLIENT_WEB) {
pings_counted++;
total_ping += duration_cast<milliseconds>(dynamic_pointer_cast<WebClient>(client)->client_ping()).count();
}
#endif
});
VirtualServer::NetworkReport result{};
if(loss_counted) result.average_loss = total_loss / loss_counted;
if(pings_counted) result.average_ping = total_ping / pings_counted;
return result;
}
bool VirtualServer::resetPermissions(std::string& new_permission_token) {
std::map<GroupId, GroupId> server_group_mapping{};
std::map<GroupId, GroupId> channel_group_mapping{};
{
this->group_manager()->server_groups()->reset_groups(server_group_mapping);
this->group_manager()->channel_groups()->reset_groups(channel_group_mapping);
this->group_manager()->assignments().reset_all();
}
/* assign the properties */
{
this->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] =
server_group_mapping[serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP].as_or<GroupId>(0)];
this->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP] =
server_group_mapping[serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_or<GroupId>(0)];
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] =
channel_group_mapping[serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP].as_or<GroupId>(0)];
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] =
channel_group_mapping[serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP].as_or<GroupId>(0)];
}
{
this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
auto admin_group_id = server_group_mapping[serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP].as_or<GroupId>(0)];
auto admin_group = this->group_manager()->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, admin_group_id);
if(!admin_group) {
logCritical(this->getServerId(), "Missing default server admin group. Don't generate a new token.");
new_permission_token = "missing server admin group";
} else {
auto token = this->tokenManager->create_token(0, "default server admin token", 1, std::chrono::system_clock::time_point{});
if(!token) {
logCritical(this->serverId, "Failed to register the default server admin token.");
} else {
std::vector<token::TokenAction> actions{};
actions.push_back(token::TokenAction{
.id = 0,
.type = token::ActionType::AddServerGroup,
.id1 = admin_group->group_id(),
.id2 = 0
});
this->tokenManager->add_token_actions(token->id, actions);
new_permission_token = token->token;
this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = token->token;
this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = true;
}
}
}
this->ensureValidDefaultGroups();
for(const auto& client : this->getClients()) {
client->task_update_displayed_groups.enqueue();
client->task_update_needed_permissions.enqueue();
client->task_update_channel_client_properties.enqueue();
}
this->task_notify_channel_group_list.enqueue();
this->task_notify_server_group_list.enqueue();
return true;
}
void VirtualServer::ensureValidDefaultGroups() {
/* TODO: FIXME: Impl! */
#if 0
auto default_server_group = this->group_manager()->defaultGroup(GROUPTARGET_SERVER, true);
if(!default_server_group) {
logError(this->serverId, "Missing server's default server group! (Id: {})", this->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP].value());
default_server_group = this->group_manager()->availableServerGroups(false).front();
logError(this->serverId, "Using {} ({}) instead!", default_server_group->groupId(), default_server_group->name());
this->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] = default_server_group->groupId();
}
auto default_music_group = this->group_manager()->defaultGroup(GROUPTARGET_SERVER, true);
if(!default_music_group) {
logError(this->serverId, "Missing server's default music group! (Id: {})", this->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP].value());
default_music_group = default_server_group;
logError(this->serverId, "Using {} ({}) instead!", default_music_group->groupId(), default_music_group->name());
this->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP] = default_music_group->groupId();
}
auto default_channel_group = this->group_manager()->defaultGroup(GROUPTARGET_CHANNEL, true);
if(!default_channel_group) {
logError(this->serverId, "Missing server's default channel group! (Id: {})", this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].value());
default_channel_group = this->group_manager()->availableChannelGroups(false).front();
logError(this->serverId, "Using {} ({}) instead!", default_channel_group->groupId(), default_channel_group->name());
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = default_channel_group->groupId();
}
auto admin_channel_group = this->group_manager()->findGroupLocal(
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].as_or<GroupId>(0));
if(!admin_channel_group) {
logError(this->serverId, "Missing server's default channel admin group! (Id: {})", this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].value());
admin_channel_group = this->group_manager()->availableChannelGroups(false).front();
logError(this->serverId, "Using {} ({}) instead!", admin_channel_group->groupId(), admin_channel_group->name());
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = admin_channel_group->groupId();
}
#endif
}
void VirtualServer::send_text_message(const std::shared_ptr<BasicChannel> &channel, const std::shared_ptr<ConnectedClient> &sender, const std::string &message) {
assert(channel);
assert(sender);
auto client_id = sender->getClientId();
auto channel_id = channel->channelId();
auto now = chrono::system_clock::now();
bool conversation_private;
auto conversation_mode = channel->properties()[property::CHANNEL_CONVERSATION_MODE].as_or<ChannelConversationMode>(ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE);
if(conversation_mode == ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE) {
/* nothing to do */
return;
} else {
conversation_private = conversation_mode == ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE;
}
auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as_or<bool>(false);
for(const auto& client : this->getClients()) {
if(client->connectionState() != ConnectionState::CONNECTED)
continue;
auto type = client->getType();
if(type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC)
continue;
auto own_channel = client->currentChannel == channel;
if(conversation_private && !own_channel)
continue;
if(type != ClientType::CLIENT_TEAMSPEAK || own_channel) {
if(!own_channel && &*client != client) {
if(flag_password)
continue; /* TODO: Send notification about new message. The client then could request messages via message history */
if(auto err_perm{client->calculate_and_get_join_state(channel)}; err_perm)
continue;
}
client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, sender, client_id, channel_id, now, message);
}
}
if(!conversation_private) {
auto conversations = this->conversation_manager();
auto conversation = conversations->get_or_create(channel->channelId());
conversation->register_message(sender->getClientDatabaseId(), sender->getUid(), sender->getDisplayName(), now, message);
}
}
void VirtualServer::update_channel_from_permissions(const std::shared_ptr<BasicChannel> &channel, const std::shared_ptr<ConnectedClient>& issuer) {
bool require_view_update;
auto property_updates = channel->update_properties_from_permissions(require_view_update);
if(!property_updates.empty()) {
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& cl) {
shared_lock client_channel_lock(cl->channel_lock);
cl->notifyChannelEdited(channel, property_updates, issuer, false);
});
}
if(require_view_update) {
auto l_source = this->channelTree->findLinkedChannel(channel->channelId());
this->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
/* server tree read lock still active */
auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId());
sassert(l_source);
if(cl->currentChannel) sassert(l_target);
{
unique_lock client_channel_lock(cl->channel_lock);
deque<ChannelId> deleted;
for(const auto& [flag_visible, channel] : cl->channels->update_channel(l_source, l_target)) {
if(flag_visible) {
cl->notifyChannelShow(channel->channel(), channel->previous_channel);
} else {
deleted.push_back(channel->channelId());
}
}
if(!deleted.empty()) {
cl->notifyChannelHide(deleted, false);
}
}
});
}
}
std::shared_ptr<groups::ChannelGroup> VirtualServer::default_channel_group() {
auto group_id = this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_or<GroupId>(0);
auto group = this->group_manager()->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(!group) {
auto groups = this->group_manager()->channel_groups()->available_groups(groups::GroupCalculateMode::GLOBAL);
/* TODO: Log warning? */
group = groups.back();
}
return group;
}