1345 lines
59 KiB
C++
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, <c_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);
|
|
}
|
|
});
|
|
}
|
|
} |