Teaspeak-Server/server/src/VirtualServer.cpp

1345 lines
59 KiB
C++

#include <cstring>
#include <functional>
#include <protocol/buffers.h>
#include <openssl/sha.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 "weblist/WebListManager.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>
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());
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<bool>();
return;
} else if(prop.type() == property::VIRTUALSERVER_CODEC_ENCRYPTION_MODE) {
this->_voice_encryption_mode = prop.as<int>();
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_save<int64_t>([]{ return -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_save<int64_t>([]{ return -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_save<bool>([]{ return 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->find(property::PROP_TYPE_SERVER, property);
sync_property(prop);
}
this->_properties->registerNotifyHandler([sync_property](Property& prop){
sync_property(prop);
});
}
if(!properties()[property::VIRTUALSERVER_KEYPAIR].as<std::string>().empty()){
debugMessage(this->serverId, "Importing server keypair");
this->_serverKey = new ecc_key;
auto bytes = base64::decode(properties()[property::VIRTUALSERVER_KEYPAIR].as<std::string>());
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 = new GroupManager(self.lock(), this->sql, serverInstance->getGroupManager());
if(!this->groups->loadGroupFormDatabase()){ //TODO exception etc
logCritical(this->serverId, "Cant setup group manager!");
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<bool>())
default_channel->properties()[property::CHANNEL_FLAG_PASSWORD] = false;
this->tokenManager = new token::TokenManager(this);
this->tokenManager->loadTokens();
this->complains = new ComplainManager(this);
if(!this->complains->loadComplains()) logError(this->serverId, "Could not load complains");
//Setup new server if needed
if(this->groups->availableServerGroups(false).empty() || this->groups->availableChannelGroups(false).empty()){
if(!this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY].as<string>().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].as<string>(), false);
static_pointer_cast<InternalClient>(this->serverRoot)->setSharedLock(this->serverRoot);
this->properties().registerNotifyHandler([&](Property& property) {
if(property.type() == property::VIRTUALSERVER_NAME) static_pointer_cast<InternalClient>(this->serverRoot)->properties()[property::CLIENT_NICKNAME] = property.as<string>();
});
this->serverRoot->server = nullptr;
this->serverAdmin = std::make_shared<InternalClient>(this->sql, self.lock(), "serveradmin", true);
static_pointer_cast<InternalClient>(this->serverAdmin)->setSharedLock(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_save<int64_t>([]{ return -1; }));
file_vs->max_networking_upload_bandwidth(this->properties()[property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH].as_save<int64_t>([]{ return -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();
return true;
}
VirtualServer::~VirtualServer() {
memtrack::freed<VirtualServer>(this);
delete this->tokenManager;
delete this->groups;
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].as<string>();
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<uint16_t>() <= 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<uint16_t>());
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<uint16_t>());
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<string>();
auto web_port = this->properties()[property::VIRTUALSERVER_WEB_PORT].as<uint16_t>();
if(web_port == 0)
web_port = this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>();
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
}
//Startup ticking
serverInstance->executeTick(this);
if(this->properties()[property::VIRTUALSERVER_WEBLIST_ENABLED].as<bool>())
serverInstance->getWebList()->enable_report(this->self.lock());
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();
}
} 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->cancelExecute(this);
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
{
auto list = serverInstance->getWebList();
if(list)
list->disable_report(self_lock);
}
if(this->groups) {
this->groups->clearCache();
}
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:
response.clients_ts++;
break;
case CLIENT_WEB:
response.clients_web++;
break;
case CLIENT_QUERY:
response.queries++;
break;
case CLIENT_MUSIC:
response.bots++;
break;
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].as<std::string>();
}
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);
});
}
std::vector<std::shared_ptr<GroupAssignment>> CalculateCache::getGroupAssignments(VirtualServer* server, ClientDbId cldbid, ClientType type) {
if(assignment_server_groups_set) return assignment_server_groups;
assignment_server_groups = server->getGroupManager()->getServerGroups(cldbid, type);
assignment_server_groups_set = true;
return assignment_server_groups;
}
std::shared_ptr<GroupAssignment> CalculateCache::getChannelAssignment(VirtualServer* server, ClientDbId client_dbid, ChannelId channel_id) {
if(this->assignment_channel_group_set && this->assignment_channel_group_channel == channel_id) return this->assignment_channel_group;
auto channel = this->getServerChannel(server, channel_id);
assignment_channel_group = channel ? server->getGroupManager()->getChannelGroup(client_dbid, channel, true) : nullptr;
assignment_channel_group_set = true;
assignment_channel_group_channel = channel_id;
return assignment_channel_group;
}
std::shared_ptr<BasicChannel> CalculateCache::getServerChannel(ts::server::VirtualServer *server, ts::ChannelId channel_id) {
if(this->last_server_channel == channel_id)
return this->server_channel;
this->last_server_channel = channel_id;
this->server_channel = server && channel_id > 0 ? server->getChannelTree()->findChannel(channel_id) : nullptr;
return this->server_channel;
}
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) {
if(permissions.empty()) return {};
vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlaggedValue>> result;
result.reserve(permissions.size());
if(!cache) {
cache = make_shared<CalculateCache>();
}
if(!cache->client_permissions) {
cache->client_permissions = serverInstance->databaseHelper()->loadClientPermissionManager(self.lock(), client_dbid);
}
bool have_skip_permission = false;
int skip_permission_type = -1; /* -1 := unset | 0 := skip, not explicit | 1 := skip, explicit */
/*
* server_group_data[0] := Server group id
* server_group_data[1] := Skip flag
* server_group_data[2] := Negate flag
* server_group_data[3] := Permission value
*/
typedef std::tuple<GroupId, bool, bool, permission::PermissionValue> GroupData;
bool server_group_data_initialized = false;
vector<GroupData> server_group_data;
GroupData* active_server_group;
/* function to calculate skip permission */
auto calculate_skip = [&]{
skip_permission_type = 0;
/* test for skip permission within the client permission manager */
{
auto skip_value = cache->client_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions);
if(skip_value.has_value) {
have_skip_permission = skip_value.value == 1;
skip_permission_type = 1;
logTrace(this->serverId, "[Permission] Found skip permission in client permissions. Value: {}", have_skip_permission);
}
}
/* test for skip permission within all server groups */
if(skip_permission_type != 1) {
for(const auto& assignment : cache->getGroupAssignments(this, client_dbid, client_type)) {
auto group_permissions = assignment->group->permissions();
auto flagged_value = group_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions);
if(flagged_value.has_value) {
have_skip_permission |= flagged_value.value == 1;
if(have_skip_permission) {
logTrace(this->serverId, "[Permission] Found skip permission in client server group. Group: {} ({}), Value: {}", assignment->group->groupId(), assignment->group->name(), have_skip_permission);
break;
}
}
}
}
};
auto initialize_group_data = [&](const permission::PermissionType& permission_type) {
server_group_data_initialized = true;
active_server_group = nullptr;
auto assigned_groups = cache->getGroupAssignments(this, client_dbid, client_type);
server_group_data.resize(assigned_groups.size());
auto it = server_group_data.begin();
for(auto& group : assigned_groups) {
auto group_permissions = group->group->permissions();
auto permission_flags = group_permissions->permission_flags(permission_type);
auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set;
if(!flag_set)
continue;
//TODO: Test if there is may a group channel permissions
auto value = group_permissions->permission_values(permission_type);
*it = std::make_tuple(group->group->groupId(), (bool) permission_flags.skip, (bool) permission_flags.negate, calculate_granted ? value.grant : value.value);
it++;
}
if(it == server_group_data.begin())
return; /* no server group has that permission */
server_group_data.erase(it, server_group_data.end()); /* remove unneeded */
auto found_negate = false;
for(auto& group : server_group_data) {
if(std::get<2>(group)) {
found_negate = true;
break;
}
}
if(found_negate) {
server_group_data.erase(remove_if(server_group_data.begin(), server_group_data.end(), [](auto data) { return !std::get<2>(data); }), server_group_data.end());
logTrace(this->serverId, "[Permission] Found negate flag within server groups. Groups left: {}", server_group_data.size());
if(server_group_data.empty())
logTrace(this->serverId, "[Permission] After non negated groups have been kicked out the negated groups are empty! This should not happen! Permission: {}, Client ID: {}", permission_type, client_dbid);
permission::PermissionValue current_lowest = 0;
for(auto& group : server_group_data) {
if(!active_server_group || (std::get<3>(group) < current_lowest && std::get<3>(group) != -1)) {
current_lowest = std::get<3>(group);
active_server_group = &group;
}
}
} else {
permission::PermissionValue current_highest = 0;
for(auto& group : server_group_data) {
if(!active_server_group || (std::get<3>(group) > current_highest || std::get<3>(group) == -1)) {
current_highest = std::get<3>(group);
active_server_group = &group;
}
}
}
};
for(const auto& permission : permissions) {
server_group_data_initialized = false; /* reset all group data */
auto client_permission_flags = cache->client_permissions->permission_flags(permission);
/* lets try to resolve the channel specific permission */
if(channel_id > 0 && client_permission_flags.channel_specific) {
auto data = cache->client_permissions->channel_permission(permission, channel_id);
if(calculate_granted ? data.flags.grant_set : data.flags.value_set) {
result.push_back({permission, {calculate_granted ? data.values.grant : data.values.value, true}});
logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned {} (Client channel permission)", client_dbid, permission::resolvePermissionData(permission)->name, data.values.value);
continue;
}
}
bool skip_channel_permissions = channel_id == 0;
if(!skip_channel_permissions) {
/* look if somewhere is the skip permission flag set */
if(skip_permission_type == -1) /* initialize skip flag */
calculate_skip();
skip_channel_permissions = have_skip_permission;
}
if(!skip_channel_permissions) {
/* okey we've no global skip. Then now lookup the groups and the client permissions */
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
/* okey the client has the permission, this counts */
skip_channel_permissions = client_permission_flags.skip;
} else {
if(!server_group_data_initialized)
initialize_group_data(permission);
if(active_server_group)
skip_channel_permissions = std::get<1>(*active_server_group);
}
}
if(!skip_channel_permissions) {
/* lookup the channel group */
{
auto channel_assignment = cache->getChannelAssignment(this, client_dbid, channel_id);
if(channel_assignment) {
auto group_permissions = channel_assignment->group->permissions();
auto permission_flags = group_permissions->permission_flags(permission);
auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set;
if(flag_set) {
auto value = group_permissions->permission_values(permission);
result.push_back({permission, {calculate_granted ? value.grant : value.value, true}});
logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned {} (Channel group permission)", client_dbid, permission::resolvePermissionData(permission)->name, calculate_granted ? value.grant : value.value);
continue;
}
}
}
/* lookup the channel permissions. Whyever? */
{
auto channel = cache->getServerChannel(this, channel_id);
if(channel) {
auto channel_permissions = channel->permissions();
auto data = calculate_granted ? channel_permissions->permission_granted_flagged(permission) : channel_permissions->permission_value_flagged(permission);
if(data.has_value) {
result.push_back({permission, {data.value, true}});
logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned {} (Channel permission)", client_dbid, permission::resolvePermissionData(permission)->name, data.value);
continue;
}
}
}
}
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
auto client_value = cache->client_permissions->permission_values(permission);
result.push_back({permission, {calculate_granted ? client_value.grant : client_value.value, true}});
logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned {} (Client permission)", client_dbid, permission::resolvePermissionData(permission)->name, client_value.value);
continue;
}
if(!server_group_data_initialized)
initialize_group_data(permission);
if(active_server_group) {
result.push_back({permission, {get<3>(*active_server_group), true}});
logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned {} (Server group permission of group {})", client_dbid, permission::resolvePermissionData(permission)->name, get<3>(*active_server_group), get<0>(*active_server_group));
continue;
}
logTrace(this->serverId, "[Permission] Calculation for client {} of permission {} returned in no permission.", client_dbid, permission::resolvePermissionData(permission)->name);
result.push_back({permission, {permNotGranted, false}});
}
return result;
}
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<bool>()) return true;
if(password.empty()) return false;
if(!hashed){
char buffer[SHA_DIGEST_LENGTH];
SHA1(reinterpret_cast<const unsigned char *>(password.data()), password.length(), reinterpret_cast<unsigned char *>(buffer));
password = base64_encode(string(buffer, SHA_DIGEST_LENGTH));
}
return password == this->properties()[property::VIRTUALSERVER_PASSWORD].as<std::string>();
}
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& token) {
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` != :channel_type", variable{":serverId", this->serverId}, variable{":channel_type", permission::SQL_PERM_CHANNEL}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
{
threads::MutexLock lock(this->getGroupManager()->cacheLock);
this->getGroupManager()->deleteAllGroups();
deque<shared_ptr<Group>> saved_groups;
for(const auto& group : serverInstance->getGroupManager()->availableGroups(false)){
if(group->type() != GroupType::GROUP_TYPE_TEMPLATE) continue;
debugMessage(this->serverId, "Copy default group {{Id: {}, Type: {}, Target: {}, Name: {}}} to server", group->groupId(), group->type(), group->target(), group->name());
this->getGroupManager()->copyGroup(group, GroupType::GROUP_TYPE_NORMAL, group->name(), this->serverId);
}
}
//Server admin
auto default_server_admin = serverInstance->getGroupManager()->findGroup(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP].as<GroupId>());
auto default_server_music = serverInstance->getGroupManager()->findGroup(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as<GroupId>());
auto default_server_guest = serverInstance->getGroupManager()->findGroup(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP].as<GroupId>());
auto default_channel_admin = serverInstance->getGroupManager()->findGroup(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP].as<GroupId>());
auto default_channel_guest = serverInstance->getGroupManager()->findGroup(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP].as<GroupId>());
if(!default_server_guest) {
logCritical(0, "Missing default server guest template group!");
assert(!serverInstance->getGroupManager()->availableChannelGroups().empty());
default_server_guest = serverInstance->getGroupManager()->availableServerGroups().front();
logCritical(0, "Using group {} as default server guest group for server {}.", default_server_guest->name(), this->serverId);
}
if(!default_channel_admin) {
logCritical(0, "Missing default channel guest template group!");
assert(!serverInstance->getGroupManager()->availableChannelGroups().empty());
default_channel_admin = serverInstance->getGroupManager()->availableChannelGroups().front();
logCritical(0, "Using group {} as channel server guest group for server {}.", default_channel_admin->name(), this->serverId);
}
if(!default_server_music) {
logCritical(0, "Missing default channel guest template group!");
assert(!serverInstance->getGroupManager()->availableChannelGroups().empty());
default_server_music = serverInstance->getGroupManager()->availableChannelGroups().front();
logCritical(0, "Using group {} as channel server guest group for server {}.", default_server_music->name(), this->serverId);
}
if(!default_server_admin) {
logCritical(0, "Missing default server admin template group! Using default guest group ({})", default_server_guest->name());
default_server_admin = default_server_guest;
}
if(!default_channel_admin) {
logCritical(0, "Missing default channel admin template group! Using default guest group ({})", default_channel_guest->name());
default_channel_admin = default_channel_guest;
}
this->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_guest->name()).front()->groupId();
this->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_music->name()).front()->groupId();
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_admin->name()).front()->groupId();
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_guest->name()).front()->groupId();
auto token_admin = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_admin->name()).front()->groupId();
auto created = this->tokenManager->createToken(token::TOKEN_SERVER, token_admin, "Default server token for the server admin.");
if(!created) {
logCritical(this->serverId, "Failed to generate default serveradmin token!");
} else {
token = created->token;
this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = token;
this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = true;
}
if(this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY].as<bool>()) {
auto requested_token = this->tokenManager->findToken(this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY]);
if(!requested_token) {
logError(this->serverId, "Failed to resolve default token! Don't ask for privilege key anymore.");
this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = "";
}
}
this->ensureValidDefaultGroups();
for(const auto& client : this->getClients()) {
if(client->getType() != ClientType::CLIENT_QUERY) {
client->notifyServerGroupList();
client->notifyChannelGroupList();
}
if(this->notifyClientPropertyUpdates(client, this->getGroupManager()->update_server_group_property(client, true, client->getChannel()))) {
if(client->update_cached_permissions()) /* update cached calculated permissions */
client->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
}
client->updateChannelClientProperties(true, true);
}
return true;
}
void VirtualServer::ensureValidDefaultGroups() {
auto default_server_group = this->getGroupManager()->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->getGroupManager()->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->getGroupManager()->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->getGroupManager()->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->getGroupManager()->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->getGroupManager()->findGroupLocal(this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].as_save<GroupId>());
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->getGroupManager()->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();
}
}
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<ChannelConversationMode>();
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<bool>();
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);
}
});
}
}