Teaspeak-Server/server/src/client/query/QueryClientCommands.cpp

1196 lines
59 KiB
C++

#include "Properties.h"
#include "query/Command.h"
#include <algorithm>
#include <zstd.h>
#include <src/server/QueryServer.h>
#include <src/VirtualServerManager.h>
#include <src/InstanceHandler.h>
#include <log/LogUtils.h>
#include <misc/digest.h>
#include "QueryClient.h"
#include <misc/base64.h>
#include <src/ShutdownHelper.h>
#include <ThreadPool/Timer.h>
#include <numeric>
#include "src/manager/ActionLogger.h"
#include "../../groups/GroupManager.h"
#include "src/client/command_handler/helpers.h"
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
QueryClientCommandHandler::QueryClientCommandHandler(const std::shared_ptr<QueryClient> &client) : client_ref{client} {}
bool QueryClientCommandHandler::handle_command(const std::string_view &command) {
auto client = this->client_ref.lock();
if(!client) {
return false;
}
if(command.empty() || command.find_first_not_of(' ') == std::string::npos) {
logTrace(LOG_QUERY, "[{}:{}] Got query idle command.", client->getLoggingPeerIp(), client->getPeerPort());
client->resetIdleTime();
return true;
}
unique_ptr<Command> cmd;
command_result error{};
try {
cmd = make_unique<Command>(Command::parse(command, true, !ts::config::server::strict_ut8_mode));
} catch(std::invalid_argument& ex) {
logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (invalid argument): {}", client->getLoggingPeerIp(), client->getPeerPort(), command);
error.reset(command_result{error::parameter_convert});
goto handle_error;
} catch(std::exception& ex) {
logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (exception: {}): {}", client->getLoggingPeerIp(), client->getPeerPort(), ex.what(), command);
error.reset(command_result{error::vs_critical, std::string{ex.what()}});
goto handle_error;
}
try {
std::lock_guard execute_lock{client->command_lock};
if(client->state >= ConnectionState::DISCONNECTING) {
return false;
}
client->handleCommandFull(*cmd);
} catch(std::exception& ex) {
error.reset(command_result{error::vs_critical, std::string{ex.what()}});
goto handle_error;
}
return true;
handle_error:
client->notifyError(error);
error.release_data();
return true;
}
constexpr unsigned int string_hash(const char* str, unsigned int h = 0) {
return !str[h] ? 5381 : (string_hash(str, h + 1U) * 33U) ^ (unsigned int) str[h];
}
command_result QueryClient::handleCommand(Command& cmd) {
/*
if (cmd.command() == "exit" || cmd.command() == "quit") return this->handleCommandExit(cmd);
else if (cmd.command() == "use" || cmd.command() == "serverselect") return this->handleCommandServerSelect(cmd);
else if (cmd.command() == "serverinfo") return this->handleCommandServerInfo(cmd);
else if (cmd.command() == "channellist") return this->handleCommandChannelList(cmd);
else if (cmd.command() == "login") return this->handleCommandLogin(cmd);
else if (cmd.command() == "logout") return this->handleCommandLogout(cmd);
else if (cmd.command() == "join") return this->handleCommandJoin(cmd);
else if (cmd.command() == "left") return this->handleCommandLeft(cmd);
else if (cmd.command() == "globalmessage" || cmd.command() == "gm") return this->handleCommandGlobalMessage(cmd);
else if (cmd.command() == "serverlist") return this->handleCommandServerList(cmd);
else if (cmd.command() == "servercreate") return this->handleCommandServerCreate(cmd);
else if (cmd.command() == "serverstart") return this->handleCommandServerStart(cmd);
else if (cmd.command() == "serverstop") return this->handleCommandServerStop(cmd);
else if (cmd.command() == "serverdelete") return this->handleCommandServerDelete(cmd);
else if (cmd.command() == "serveridgetbyport") return this->handleCommandServerIdGetByPort(cmd);
else if (cmd.command() == "instanceinfo") return this->handleCommandInstanceInfo(cmd);
else if (cmd.command() == "instanceedit") return this->handleCommandInstanceEdit(cmd);
else if (cmd.command() == "hostinfo") return this->handleCommandHostInfo(cmd);
else if (cmd.command() == "bindinglist") return this->handleCommandBindingList(cmd);
else if (cmd.command() == "serversnapshotdeploy") return this->handleCommandServerSnapshotDeploy(cmd);
else if (cmd.command() == "serversnapshotcreate") return this->handleCommandServerSnapshotCreate(cmd);
else if (cmd.command() == "serverprocessstop") return this->handleCommandServerProcessStop(cmd);
else if (cmd.command() == "servernotifyregister") return this->handleCommandServerNotifyRegister(cmd);
else if (cmd.command() == "servernotifylist") return this->handleCommandServerNotifyList(cmd);
else if (cmd.command() == "servernotifyunregister") return this->handleCommandServerNotifyUnregister(cmd);
*/
auto command = cmd.command();
auto command_hash = string_hash(command.c_str());
switch (command_hash) {
case string_hash("exit"):
case string_hash("quit"):
return this->handleCommandExit(cmd);
case string_hash("use"):
case string_hash("serverselect"):
return this->handleCommandServerSelect(cmd);
case string_hash("serverinfo"):
return this->handleCommandServerInfo(cmd);
case string_hash("channellist"):
return this->handleCommandChannelList(cmd);
case string_hash("login"):
return this->handleCommandLogin(cmd);
case string_hash("logout"):
return this->handleCommandLogout(cmd);
case string_hash("join"):
return this->handleCommandJoin(cmd);
case string_hash("left"):
return this->handleCommandLeft(cmd);
case string_hash("globalmessage"):
case string_hash("gm"):
return this->handleCommandGlobalMessage(cmd);
case string_hash("serverlist"):
return this->handleCommandServerList(cmd);
case string_hash("servercreate"):
return this->handleCommandServerCreate(cmd);
case string_hash("serverstart"):
return this->handleCommandServerStart(cmd);
case string_hash("serverstop"):
return this->handleCommandServerStop(cmd);
case string_hash("serverdelete"):
return this->handleCommandServerDelete(cmd);
case string_hash("serveridgetbyport"):
return this->handleCommandServerIdGetByPort(cmd);
case string_hash("instanceinfo"):
return this->handleCommandInstanceInfo(cmd);
case string_hash("instanceedit"):
return this->handleCommandInstanceEdit(cmd);
case string_hash("hostinfo"):
return this->handleCommandHostInfo(cmd);
case string_hash("bindinglist"):
return this->handleCommandBindingList(cmd);
case string_hash("serversnapshotdeploy"): {
#if 0
return this->handleCommandServerSnapshotDeploy(cmd);
#else
auto cmd_str = cmd.build();
ts::command_parser parser{cmd_str};
if(!parser.parse(true)) {
return command_result{error::vs_critical};
}
return this->handleCommandServerSnapshotDeployNew(parser);
#endif
}
case string_hash("serversnapshotcreate"):
return this->handleCommandServerSnapshotCreate(cmd);
case string_hash("serverprocessstop"):
return this->handleCommandServerProcessStop(cmd);
case string_hash("servernotifyregister"):
return this->handleCommandServerNotifyRegister(cmd);
case string_hash("servernotifylist"):
return this->handleCommandServerNotifyList(cmd);
case string_hash("servernotifyunregister"):
return this->handleCommandServerNotifyUnregister(cmd);
default:
break;
}
return ConnectedClient::handleCommand(cmd);
}
command_result QueryClient::handleCommandExit(Command &) {
logMessage(LOG_QUERY, "[Query] {}:{} disconnected. (Requested by client)", this->getLoggingPeerIp(), this->getPeerPort());
this->close_connection(system_clock::now() + seconds(1));
return command_result{error::ok};
}
//login client_login_name=andreas client_login_password=meinPW
command_result QueryClient::handleCommandLogin(Command& cmd) {
CMD_RESET_IDLE;
std::string username, password;
if(cmd[0].has("client_login_name") && cmd[0].has("client_login_password")){
username = cmd["client_login_name"].string();
password = cmd["client_login_password"].string();
} else {
username = cmd[0][0].key();
password = cmd[0][1].key();
}
debugMessage(LOG_QUERY, "Having query login attempt for username {}", username);
auto _account = serverInstance->getQueryServer()->find_query_account_by_name(username);
auto account = _account ? serverInstance->getQueryServer()->load_password(_account) : nullptr;
{
std::lock_guard connect_lock{this->handle->client_connect_mutex};
if(!account) {
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::UNKNOWN_USER);
return command_result{error::client_invalid_password, "username or password dose not match"};
}
if (account->password != password) {
if(!this->whitelisted) {
this->handle->client_connect_count[this->getPeerIp()]++;
if(this->handle->client_connect_count[this->getPeerIp()] > 3) {
this->handle->client_connect_bans[this->getPeerIp()] = system_clock::now() + seconds(
serverInstance->properties()[property::SERVERINSTANCE_SERVERQUERY_BAN_TIME].as_unchecked<uint64_t>()); //TODO configurable | Disconnect all others?
this->postCommandHandler.emplace_back([&](){
this->close_connection(system_clock::now() + seconds(1));
});
return command_result{error::ban_flooding};
}
}
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::INVALID_PASSWORD);
return command_result{error::client_invalid_password, "username or password dose not match"};
}
}
if(!this->properties()[property::CLIENT_LOGIN_NAME].as_unchecked<string>().empty()) {
Command log("logout");
auto result = this->handleCommandLogout(log);
if(result.has_error()) {
result.release_data();
logError(this->getServerId(), "Query client failed to login from old login.");
return command_result{error::vs_critical};
}
}
this->query_account = account;
auto joined_channel = this->currentChannel;
if(this->server) {
{
unique_lock tree_lock(this->server->channel_tree_lock);
if(joined_channel) {
this->server->client_move(this->ref(), nullptr, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock);
}
this->server->unregisterClient(this->ref(), "login", tree_lock);
}
}
logMessage(LOG_QUERY, "Got new authenticated client. Username: {}, Unique-ID: {}, Bounded Server: {}", account->username, account->unique_id, account->bound_server);
this->properties()[property::CLIENT_LOGIN_NAME] = username;
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = account->unique_id; //TODO load from table
this->properties()[property::CLIENT_NICKNAME] = username;
auto target_server = this->server; /* keep the server alive 'ill we've joined the server */
if(account->bound_server) {
target_server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server);
if(target_server != this->server)
joined_channel = nullptr;
if(!target_server)
return command_result{error::server_invalid_id, "server does not exists anymore"};
}
this->server = target_server;
DatabaseHelper::assignDatabaseId(this->sql, static_cast<ServerId>(target_server ? target_server->getServerId() : 0), this->ref());
if(target_server) {
target_server->registerClient(this->ref());
{
shared_lock server_tree_lock(target_server->channel_tree_lock);
if(joined_channel) /* needs only notify if we were already on that server within a channel */
target_server->notifyClientPropertyUpdates(this->ref(), deque<property::ClientProperties>{property::CLIENT_NICKNAME, property::CLIENT_UNIQUE_IDENTIFIER});
unique_lock client_tree_lock(this->channel_lock);
this->channels->reset();
this->channels->insert_channels(target_server->channelTree->tree_head(), true, false);
this->subscribeChannel(this->server->channelTree->channels(), false, false);
}
if(joined_channel) {
unique_lock tree_lock(this->server->channel_tree_lock);
if(joined_channel)
this->server->client_move(this->ref(), joined_channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock);
} else if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_select_godmode, 1))) {
this->server->assignDefaultChannel(this->ref(), true);
} else {
this->task_update_needed_permissions.enqueue();
}
} else {
this->task_update_needed_permissions.enqueue();
}
this->properties()[property::CLIENT_TOTALCONNECTIONS].increment_by<uint64_t>(1);
this->task_update_channel_client_properties.enqueue();
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), username, log::QueryAuthenticateResult::SUCCESS);
return command_result{error::ok};
}
command_result QueryClient::handleCommandLogout(Command &) {
CMD_RESET_IDLE;
if(this->properties()[property::CLIENT_LOGIN_NAME].as_unchecked<string>().empty()) return command_result{error::client_not_logged_in};
this->properties()[property::CLIENT_LOGIN_NAME] = "";
this->query_account = nullptr;
auto joined = this->currentChannel;
if(this->server){
{
unique_lock tree_lock(this->server->channel_tree_lock);
if(joined)
this->server->client_move(this->ref(), nullptr, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock);
this->server->unregisterClient(this->ref(), "logout", tree_lock);
}
}
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = "UnknownQuery"; //TODO load from table
this->properties()[property::CLIENT_NICKNAME] = string() + "ServerQuery#" + this->getLoggingPeerIp() + "/" + to_string(ntohs(this->getPeerPort()));
DatabaseHelper::assignDatabaseId(this->sql, static_cast<ServerId>(this->server ? this->server->getServerId() : 0), this->ref());
if(this->server){
this->server->registerClient(this->ref());
{
shared_lock server_channel_r_lock(this->server->channel_tree_lock);
unique_lock client_channel_lock(this->channel_lock);
this->channels->reset();
this->channels->insert_channels(this->server->channelTree->tree_head(), true, false);
this->subscribeChannel(this->server->channelTree->channels(), false, false);
}
if(joined) {
unique_lock server_channel_w_lock(this->server->channel_tree_lock, defer_lock);
this->server->client_move(this->ref(), joined, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_w_lock);
} else if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_select_godmode, 1))) {
this->server->assignDefaultChannel(this->ref(), true);
} else {
this->task_update_needed_permissions.enqueue();
}
} else {
this->task_update_needed_permissions.enqueue();
}
this->task_update_channel_client_properties.enqueue();
serverInstance->action_logger()->query_authenticate_logger.log_query_authenticate(this->getServerId(), std::dynamic_pointer_cast<QueryClient>(this->ref()), "", log::QueryAuthenticateResult::SUCCESS);
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerSelect(Command &cmd) {
CMD_RESET_IDLE;
shared_ptr<VirtualServer> target;
if(cmd[0].has("port")){
target = serverInstance->getVoiceServerManager()->findServerByPort(cmd["port"].as<uint16_t>());
} else if(cmd[0].has("sid")) {
target = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"].as<ServerId>());
}
for(auto& parm : cmd[0].keys())
if(parm.find_first_not_of("0123456789") == string::npos)
target = serverInstance->getVoiceServerManager()->findServerById(static_cast<ServerId>(stol(parm)));
if(!target && (!cmd[0].has("0") && (!cmd[0].has("sid") || !cmd["sid"] == 0))) return command_result{error::server_invalid_id, "Invalid server id"};
if(target && target->getState() != ServerState::ONLINE && target->getState() != ServerState::OFFLINE) return command_result{error::server_is_not_running};
if(target == this->server) return command_result{error::ok};
auto old_server_id = this->getServerId();
if(target) {
if(this->query_account && this->query_account->bound_server > 0) {
if(target->getServerId() != this->query_account->bound_server)
return command_result{error::server_invalid_id, "You're a server bound query, and the target server isn't your origin."};
} else {
auto allowed = target->calculate_permission(permission::b_virtualserver_select, this->getClientDatabaseId(), this->getType(), 0);
if(!permission::v2::permission_granted(1, allowed)) return command_result{permission::b_virtualserver_select};
}
}
this->disconnect_from_virtual_server("server switch");
this->resetEventMask();
//register at current server
{
//unique_lock server_lock(this->server_lock);
/* We dont need to lock the server because only one command can be executed at the time. Everything else should copy the server once and test the copy and use that ref then */
this->server = target;
}
if(cmd[0].has("client_nickname"))
this->properties()[property::CLIENT_NICKNAME] = cmd["client_nickname"].string();
DatabaseHelper::assignDatabaseId(this->sql, static_cast<ServerId>(this->server ? this->server->getServerId() : 0), this->ref());
if(this->server) {
this->server->registerClient(this->ref());
{
shared_lock server_channel_lock(target->channel_tree_lock);
unique_lock client_channel_lock(this->channel_lock);
this->subscribeToAll = true;
this->channels->insert_channels(this->server->channelTree->tree_head(), true, false);
this->subscribeChannel(this->server->channelTree->channels(), false, false);
}
auto negated_enforce_join = permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_select_godmode, 1));
if(!negated_enforce_join) {
this->server->assignDefaultChannel(this->ref(), true);
} else {
this->task_update_needed_permissions.enqueue();
}
} else {
this->task_update_needed_permissions.enqueue();
}
this->task_update_channel_client_properties.enqueue();
serverInstance->action_logger()->query_logger.log_query_switch(std::dynamic_pointer_cast<QueryClient>(this->ref()), this->properties()[property::CLIENT_LOGIN_NAME].value(), old_server_id, this->getServerId());
return command_result{error::ok};
}
command_result QueryClient::handleCommandJoin(Command &) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
if(this->server->state != ServerState::ONLINE)
return command_result{error::server_is_not_running};
if(this->currentChannel)
return command_result{error::server_already_joined, "already joined!"};
this->server->assignDefaultChannel(this->ref(), true);
return command_result{error::ok};
}
command_result QueryClient::handleCommandLeft(Command&) {
CMD_REQ_SERVER;
CMD_REQ_CHANNEL;
CMD_RESET_IDLE;
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_select_godmode, 1);
unique_lock server_channel_lock(this->server->channel_tree_lock);
this->server->client_move(this->ref(), nullptr, nullptr, "leaving", ViewReasonId::VREASON_SERVER_LEFT, true, server_channel_lock);
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerInfo(Command &) {
CMD_RESET_IDLE;
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_info_view, 1);
Command cmd("");
auto properties = this->server ? this->server->properties() : serverInstance->getDefaultServerProperties();
for(const auto &prop : properties->list_properties(property::FLAG_SERVER_VIEW | property::FLAG_SERVER_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
cmd[prop.type().name] = prop.as_unchecked<string>();
if(prop.type() == property::VIRTUALSERVER_HOST)
cmd["virtualserver_ip"] = prop.as_unchecked<string>();
}
cmd["virtualserver_status"] = this->server ? ServerState::string(this->server->state) : "template";
if(this->server && permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_connectioninfo_view, 0))) {
auto total_stats = this->server->getServerStatistics()->total_stats();
auto report_second = this->server->server_statistics_->second_stats();
auto report_minute = this->server->server_statistics_->minute_stats();
cmd["connection_bandwidth_sent_last_second_total"] = std::accumulate(report_second.connection_bytes_sent.begin(), report_second.connection_bytes_sent.end(), (size_t) 0U);
cmd["connection_bandwidth_sent_last_minute_total"] = std::accumulate(report_minute.connection_bytes_sent.begin(), report_minute.connection_bytes_sent.end(), (size_t) 0U);
cmd["connection_bandwidth_received_last_second_total"] = std::accumulate(report_second.connection_bytes_received.begin(), report_second.connection_bytes_received.end(), (size_t) 0U);
cmd["connection_bandwidth_received_last_minute_total"] = std::accumulate(report_minute.connection_bytes_received.begin(), report_minute.connection_bytes_received.end(), (size_t) 0U);
cmd["connection_filetransfer_bandwidth_sent"] = report_minute.file_bytes_sent;
cmd["connection_filetransfer_bandwidth_received"] = report_minute.file_bytes_received;
cmd["connection_filetransfer_bytes_sent_total"] = total_stats.file_bytes_sent;
cmd["connection_filetransfer_bytes_received_total"] = total_stats.file_bytes_received;
cmd["connection_packets_sent_speech"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
cmd["connection_bytes_sent_speech"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
cmd["connection_packets_received_speech"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::VOICE];
cmd["connection_bytes_received_speech"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::VOICE];
cmd["connection_packets_sent_keepalive"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_packets_received_keepalive"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_bytes_received_keepalive"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_bytes_sent_keepalive"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_packets_sent_control"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_bytes_sent_control"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_packets_received_control"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_bytes_received_control"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_packets_sent_total"] = std::accumulate(total_stats.connection_packets_sent.begin(), total_stats.connection_packets_sent.end(), (size_t) 0U);
cmd["connection_bytes_sent_total"] = std::accumulate(total_stats.connection_bytes_sent.begin(), total_stats.connection_bytes_sent.end(), (size_t) 0U);
cmd["connection_packets_received_total"] = std::accumulate(total_stats.connection_packets_received.begin(), total_stats.connection_packets_received.end(), (size_t) 0U);
cmd["connection_bytes_received_total"] = std::accumulate(total_stats.connection_bytes_received.begin(), total_stats.connection_bytes_received.end(), (size_t) 0U);
} else {
cmd["connection_bandwidth_sent_last_second_total"] = "0";
cmd["connection_bandwidth_sent_last_minute_total"] = "0";
cmd["connection_bandwidth_received_last_second_total"] = "0";
cmd["connection_bandwidth_received_last_minute_total"] = "0";
cmd["connection_filetransfer_bandwidth_sent"] = "0";
cmd["connection_filetransfer_bandwidth_received"] = "0";
cmd["connection_filetransfer_bytes_sent_total"] = "0";
cmd["connection_filetransfer_bytes_received_total"] = "0";
cmd["connection_packets_sent_speech"] = "0";
cmd["connection_bytes_sent_speech"] = "0";
cmd["connection_packets_received_speech"] = "0";
cmd["connection_bytes_received_speech"] = "0";
cmd["connection_packets_sent_keepalive"] = "0";
cmd["connection_packets_received_keepalive"] = "0";
cmd["connection_bytes_received_keepalive"] = "0";
cmd["connection_bytes_sent_keepalive"] = "0";
cmd["connection_packets_sent_control"] = "0";
cmd["connection_bytes_sent_control"] = "0";
cmd["connection_packets_received_control"] = "0";
cmd["connection_bytes_received_control"] = "0";
cmd["connection_packets_sent_total"] = "0";
cmd["connection_bytes_sent_total"] = "0";
cmd["connection_packets_received_total"] = "0";
cmd["connection_bytes_received_total"] = "0";
}
this->sendCommand(cmd);
return command_result{error::ok};
}
command_result QueryClient::handleCommandChannelList(Command& cmd) {
CMD_RESET_IDLE;
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channel_list, 1);
int index = 0;
shared_lock channel_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock());
auto entries = this->server ? this->channels->channels() : serverInstance->getChannelTree()->channels();
channel_lock.unlock();
command_builder result{"", 1024, entries.size()};
for(const auto& channel : entries){
if(!channel) continue;
const auto channel_clients = this->server ? this->server->getClientsByChannel(channel).size() : 0;
result.put_unchecked(index, "cid", channel->channelId());
result.put_unchecked(index, "pid", channel->properties()[property::CHANNEL_PID].as_unchecked<string>());
result.put_unchecked(index, "channel_name", channel->name());
result.put_unchecked(index, "channel_order", channel->channelOrder());
result.put_unchecked(index, "total_clients", channel_clients);
/* result.put_unchecked(index, "channel_needed_subscribe_power", channel->permissions()->getPermissionValue(permission::i_channel_needed_subscribe_power, channel, 0)); */
if(cmd.hasParm("flags")){
result.put_unchecked(index, "channel_flag_default",
channel->properties()[property::CHANNEL_FLAG_DEFAULT].as_unchecked<string>());
result.put_unchecked(index, "channel_flag_password",
channel->properties()[property::CHANNEL_FLAG_PASSWORD].as_unchecked<string>());
result.put_unchecked(index, "channel_flag_permanent",
channel->properties()[property::CHANNEL_FLAG_PERMANENT].as_unchecked<string>());
result.put_unchecked(index, "channel_flag_semi_permanent",
channel->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as_unchecked<string>());
}
if(cmd.hasParm("voice")){
result.put_unchecked(index, "channel_codec",
channel->properties()[property::CHANNEL_CODEC].as_unchecked<string>());
result.put_unchecked(index, "channel_codec_quality",
channel->properties()[property::CHANNEL_CODEC_QUALITY].as_unchecked<string>());
result.put_unchecked(index, "channel_needed_talk_power",
channel->properties()[property::CHANNEL_NEEDED_TALK_POWER].as_unchecked<string>());
}
if(cmd.hasParm("icon")){
result.put_unchecked(index, "channel_icon_id",
channel->properties()[property::CHANNEL_ICON_ID].as_unchecked<string>());
}
if(cmd.hasParm("limits")){
result.put_unchecked(index, "total_clients_family", this->server ? this->server->getClientsByChannelRoot(channel, false).size() : 0);
result.put_unchecked(index, "total_clients", this->server ? this->server->getClientsByChannel(channel).size() : 0);
result.put_unchecked(index, "channel_maxclients",
channel->properties()[property::CHANNEL_MAXCLIENTS].as_unchecked<string>());
result.put_unchecked(index, "channel_maxfamilyclients",
channel->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as_unchecked<string>());
{
auto needed_power = channel->permissions()->permission_value_flagged(permission::i_channel_subscribe_power);
result.put_unchecked(index, "channel_needed_subscribe_power", needed_power.has_value ? needed_power.value : 0);
}
}
if(cmd.hasParm("topic")) {
result.put_unchecked(index, "channel_topic",
channel->properties()[property::CHANNEL_TOPIC].as_unchecked<string>());
}
if(cmd.hasParm("times") || cmd.hasParm("secondsempty")){
result.put_unchecked(index, "seconds_empty", channel_clients == 0 ? channel->emptySince() : 0);
}
index++;
}
this->sendCommand(result, false);
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerList(Command& cmd) {
CMD_RESET_IDLE;
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_virtualserver_list, 1);
auto servers = serverInstance->getVoiceServerManager()->serverInstances();
command_builder result{"", 256, servers.size()};
size_t index = 0;
for(const auto& server : serverInstance->getVoiceServerManager()->serverInstances()) {
result.put_unchecked(index, "virtualserver_id", server->getServerId());
result.put_unchecked(index, "virtualserver_host",
server->properties()[property::VIRTUALSERVER_HOST].as_unchecked<string>());
result.put_unchecked(index, "virtualserver_port",
server->properties()[property::VIRTUALSERVER_PORT].as_unchecked<string>());
result.put_unchecked(index, "virtualserver_web_host",
server->properties()[property::VIRTUALSERVER_WEB_HOST].as_unchecked<string>());
result.put_unchecked(index, "virtualserver_web_port",
server->properties()[property::VIRTUALSERVER_WEB_PORT].as_unchecked<string>());
result.put_unchecked(index, "virtualserver_status", ServerState::string(server->state));
result.put_unchecked(index, "virtualserver_clientsonline",
server->properties()[property::VIRTUALSERVER_CLIENTS_ONLINE].as_unchecked<string>());
result.put_unchecked(index, "virtualserver_queryclientsonline",
server->properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE].as_unchecked<string>());
result.put_unchecked(index, "virtualserver_maxclients",
server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_unchecked<string>());
if(server->startTimestamp.time_since_epoch().count() > 0 && server->state == ServerState::ONLINE)
result.put_unchecked(index, "virtualserver_uptime", duration_cast<seconds>(system_clock::now() - server->startTimestamp).count());
else
result.put_unchecked(index, "virtualserver_uptime", 0);
result.put_unchecked(index, "virtualserver_name",
server->properties()[property::VIRTUALSERVER_NAME].as_unchecked<string>());
result.put_unchecked(index, "virtualserver_autostart",
server->properties()[property::VIRTUALSERVER_AUTOSTART].as_unchecked<string>());
result.put_unchecked(index, "virtualserver_machine_id",
server->properties()[property::VIRTUALSERVER_MACHINE_ID].as_unchecked<string>());
if(cmd.hasParm("uid"))
result.put_unchecked(index, "virtualserver_unique_identifier",
server->properties()[property::VIRTUALSERVER_UNIQUE_IDENTIFIER].as_unchecked<string>());
else result.put_unchecked(index, "virtualserver_unique_identifier", "");
index++;
}
this->sendCommand(result, false);
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerCreate(Command& cmd) {
CMD_RESET_IDLE;
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_create, 1);
if(serverInstance->getVoiceServerManager()->getState() != VirtualServerManager::STARTED) {
return command_result{error::vs_critical, "Server manager isn't started yet or not finished starting"};
}
string startError;
shared_ptr<VirtualServer> server;
milliseconds time_create, time_wait, time_start, time_global;
{
auto start = system_clock::now();
threads::MutexLock lock(serverInstance->getVoiceServerManager()->server_create_lock);
auto instances = serverInstance->getVoiceServerManager()->serverInstances();
if(config::server::max_virtual_server != -1 && instances.size() > config::server::max_virtual_server)
return command_result{error::server_max_vs_reached, "You reached the via config.yml enabled virtual server limit."};
if(instances.size() >= 65535)
return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Software limit reached)"};
{
auto end = system_clock::now();
time_wait = duration_cast<milliseconds>(end - start);
}
std::string host = cmd[0].has("virtualserver_host") ? cmd["virtualserver_host"].as<string>() : config::binding::DefaultVoiceHost;
uint16_t freePort = serverInstance->getVoiceServerManager()->next_available_port(host);
uint16_t port = cmd[0].has("virtualserver_port") ? cmd["virtualserver_port"].as<uint16_t>() : freePort;
{
auto _start = system_clock::now();
server = serverInstance->getVoiceServerManager()->create_server(host, port);
auto _end = system_clock::now();
time_create = duration_cast<milliseconds>(_end - _start);
}
if(!server) return command_result{error::vs_critical, "could not create new server"};
for(const auto& key : cmd[0].keys()){
if(key == "virtualserver_port") continue;
if(key == "virtualserver_host") continue;
const auto& info = property::find<property::VirtualServerProperties>(key);
if(info == property::VIRTUALSERVER_UNDEFINED) {
logError(server->getServerId(), "Tried to change unknown server property " + key);
continue;
}
if(!info.validate_input(cmd[key].as<string>())) {
logError(server->getServerId(), "Tried to change " + key + " to an invalid value: " + cmd[key].as<string>());
continue;
}
server->properties()[info] = cmd[key].string();
}
if(!cmd.hasParm("offline")) {
auto _start = system_clock::now();
if(!server->start(startError));
auto _end = system_clock::now();
time_start = duration_cast<milliseconds>(_end - _start);
}
auto end = system_clock::now();
time_global = duration_cast<milliseconds>(end - start);
}
serverInstance->action_logger()->server_logger.log_server_create(server->getServerId(), this->ref(), log::ServerCreateReason::USER_ACTION);
size_t total_tokens{};
auto tokens = server->tokenManager->list_tokens(total_tokens, std::nullopt, { 1 }, std::nullopt);
Command res("");
res["sid"] = server->getServerId();
res["error"] = startError;
res["virtualserver_port"] = server->properties()[property::VIRTUALSERVER_PORT].as_unchecked<string>();
res["token"] = tokens.empty() ? "unknown" : tokens[0]->token;
res["time_create"] = time_create.count();
res["time_start"] = time_start.count();
res["time_global"] = time_global.count();
res["time_wait"] = time_wait.count();
this->sendCommand(res);
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerDelete(Command& cmd) {
CMD_RESET_IDLE;
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_delete, 1);
if(serverInstance->getVoiceServerManager()->getState() != VirtualServerManager::STARTED)
return command_result{error::vs_critical, "Server manager isn't started yet or not finished starting"};
auto server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]);
if(!server) return command_result{error::server_invalid_id, "invalid bounded server"};
if(!serverInstance->getVoiceServerManager()->deleteServer(server)) return command_result{error::vs_critical};
serverInstance->action_logger()->server_logger.log_server_delete(server->getServerId(), this->ref());
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerStart(Command& cmd) {
CMD_RESET_IDLE;
if(serverInstance->getVoiceServerManager()->getState() != VirtualServerManager::STARTED)
return command_result{error::vs_critical, "Server manager isn't started yet or not finished starting"};
auto server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]);
if(!server) return command_result{error::server_invalid_id, "invalid bounded server"};
switch (server->state) {
case ServerState::BOOTING:
return command_result{error::server_is_booting};
case ServerState::ONLINE:
return command_result{error::server_running};
case ServerState::SUSPENDING:
return command_result{error::server_is_shutting_down};
case ServerState::DELETING:
return command_result{error::server_invalid_id};
case ServerState::OFFLINE: break;
}
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_virtualserver_start, this->getClientDatabaseId(), ClientType::CLIENT_QUERY, 0)))
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_start_any, 1);
string err;
if(!server->start(err))
return command_result{error::vs_critical, err};
serverInstance->action_logger()->server_logger.log_server_start(server->getServerId(), this->ref());
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerStop(Command& cmd) {
CMD_RESET_IDLE;
if(serverInstance->getVoiceServerManager()->getState() != VirtualServerManager::STARTED)
return command_result{error::vs_critical, "Server manager isn't started yet or not finished starting"};
auto server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]);
if(!server) return command_result{error::server_invalid_id, "invalid bounded server"};
switch (server->state) {
case ServerState::BOOTING:
return command_result{error::server_is_booting};
case ServerState::OFFLINE:
return command_result{error::server_is_not_running};
case ServerState::SUSPENDING:
return command_result{error::server_is_shutting_down};
case ServerState::DELETING:
return command_result{error::server_invalid_id};
case ServerState::ONLINE: break;
}
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_virtualserver_stop, this->getClientDatabaseId(), ClientType::CLIENT_QUERY, 0)))
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_stop_any, 1);
server->stop("server stopped", false);
serverInstance->action_logger()->server_logger.log_server_stop(server->getServerId(), this->ref());
return command_result{error::ok};
}
command_result QueryClient::handleCommandInstanceInfo(Command& cmd) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_info_view, 1);
Command res("");
for(const auto& e : serverInstance->properties()->list_properties(property::FLAG_INSTANCE_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
res[e.type().name] = e.value();
if(!this->properties()[property::CLIENT_LOGIN_NAME].value().empty())
res["serverinstance_teaspeak"] = true;
res["serverinstance_serverquery_max_connections_per_ip"] = res["serverinstance_query_max_connections_per_ip"].as<std::string>();
this->sendCommand(res);
return command_result{error::ok};
}
command_result QueryClient::handleCommandInstanceEdit(Command& cmd) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_modify_settings, 1);
for(const auto &key : cmd[0].keys()){
const auto* info = &property::find<property::InstanceProperties>(key);
if(key == "serverinstance_serverquery_max_connections_per_ip")
info = &property::describe(property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS_PER_IP);
if(*info == property::SERVERINSTANCE_UNDEFINED) {
logError(LOG_QUERY, "Query {} tried to change a non existing instance property: {}", this->getLoggingPeerIp(), key);
continue;
}
if(!info->validate_input(cmd[key])) {
logError(LOG_QUERY, "Query {} tried to change {} to an invalid value {}", this->getLoggingPeerIp(), key, cmd[key].as<string>());
continue;
}
if(*info == property::SERVERINSTANCE_VIRTUAL_SERVER_ID_INDEX) {
/* ensure we've a valid id */
serverInstance->properties()[info] = cmd[key].as<ServerId>();
continue;
}
serverInstance->properties()[info] = cmd[key].string();
}
return command_result{error::ok};
}
command_result QueryClient::handleCommandHostInfo(Command &) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_info_view, 1);
Command res("");
res["instance_uptime"] = duration_cast<seconds>(system_clock::now() - serverInstance->getStartTimestamp()).count();
res["host_timestamp_utc"] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
auto vsReport = serverInstance->getVoiceServerManager()->report();
res["virtualservers_running_total"] = vsReport.online;
res["virtualservers_total_maxclients"] = vsReport.slots;
res["virtualservers_total_clients_online"] = vsReport.onlineClients;
res["virtualservers_total_channels_online"] = vsReport.onlineChannels;
auto total_stats = serverInstance->getStatistics()->total_stats();
res["connection_packets_sent_total"] = std::accumulate(total_stats.connection_packets_sent.begin(), total_stats.connection_packets_sent.end(), (size_t) 0U);
res["connection_bytes_sent_total"] = std::accumulate(total_stats.connection_bytes_sent.begin(), total_stats.connection_bytes_sent.end(), (size_t) 0U);
res["connection_packets_received_total"] = std::accumulate(total_stats.connection_packets_received.begin(), total_stats.connection_packets_received.end(), (size_t) 0U);
res["connection_bytes_received_total"] = std::accumulate(total_stats.connection_bytes_received.begin(), total_stats.connection_bytes_received.end(), (size_t) 0U);
auto report_second = serverInstance->getStatistics()->second_stats();
auto report_minute = serverInstance->getStatistics()->minute_stats();
res["connection_bandwidth_sent_last_second_total"] = std::accumulate(report_second.connection_bytes_sent.begin(), report_second.connection_bytes_sent.end(), (size_t) 0U);
res["connection_bandwidth_sent_last_minute_total"] = std::accumulate(report_minute.connection_bytes_sent.begin(), report_minute.connection_bytes_sent.end(), (size_t) 0U);
res["connection_bandwidth_received_last_second_total"] = std::accumulate(report_second.connection_bytes_received.begin(), report_second.connection_bytes_received.end(), (size_t) 0U);
res["connection_bandwidth_received_last_minute_total"] = std::accumulate(report_minute.connection_bytes_received.begin(), report_minute.connection_bytes_received.end(), (size_t) 0U);
res["connection_filetransfer_bandwidth_sent"] = report_second.file_bytes_sent;
res["connection_filetransfer_bandwidth_received"] = report_second.file_bytes_received;
res["connection_filetransfer_bytes_sent_total"] = total_stats.file_bytes_sent;
res["connection_filetransfer_bytes_received_total"] = total_stats.file_bytes_received;
this->sendCommand(res);
return command_result{error::ok};
}
command_result QueryClient::handleCommandGlobalMessage(Command& cmd) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_textmessage_send, 1);
for(const auto &server : serverInstance->getVoiceServerManager()->serverInstances())
if(server->running()) server->broadcastMessage(server->getServerRoot(), cmd["msg"]);
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerIdGetByPort(Command& cmd) {
uint16_t port = cmd["virtualserver_port"];
auto server = serverInstance->getVoiceServerManager()->findServerByPort(port);
if(!server) return command_result{error::server_invalid_id};
Command res("");
res["server_id"] = server->getServerId();
this->sendCommand(res);
return command_result{error::ok};
}
command_result QueryClient::handleCommandBindingList(Command& cmd) {
Command res("");
res["ip"] = "0.0.0.0 ";
this->sendCommand(res); //TODO maybe list here all bindings from voice & file and query server?
return command_result{error::ok};
}
//TODO with mapping!
command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
#if 0
CMD_RESET_IDLE;
auto start = system_clock::now();
string error;
string host = "0.0.0.0";
uint16_t port = 0;
if(this->server) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
host = this->server->properties()[property::VIRTUALSERVER_HOST].as<string>();
port = this->server->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>();
} else {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
}
auto hash = cmd["hash"].string();
if(hash.empty()) return command_result{error::parameter_invalid, "Invalid hash (not present)"};
debugMessage(this->getServerId(), "Serversnapshot calculated hash: {}", hash);
bool mapping = cmd.hasParm("mapping");
bool ignore_hash = cmd.hasParm("ignorehash");
cmd.clear_parameters();
cmd.pop_bulk();
auto str = cmd.build().substr(strlen("serversnapshotdeploy "));
if(!ignore_hash) {
auto buildHash = base64::encode(digest::sha1(str));
if(buildHash != hash)
return command_result{error::parameter_invalid, "Invalid hash (Expected: \"" + hash + "\", Got: \"" + buildHash + "\")"};
}
unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock);
{
auto instances = serverInstance->getVoiceServerManager()->serverInstances();
if(config::server::max_virtual_server != -1 && instances.size() > config::server::max_virtual_server)
return command_result{error::server_max_vs_reached, "You reached the via config.yml enabled virtual server limit."};
if(instances.size() >= 65535)
return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Software limit reached)"};
}
if(port == 0)
port = serverInstance->getVoiceServerManager()->next_available_port(host);
auto result = serverInstance->getVoiceServerManager()->createServerFromSnapshot(this->server, host, port, cmd, error);
server_create_lock.unlock();
auto end = system_clock::now();
Command res("");
if(!result){
logError(this->getServerId(), "Could not apply server snapshot: {}", error);
res["success"] = false;
res["sid"] = 0;
res["message"] = error;
} else {
res["success"] = true;
res["sid"] = result->getServerId();
res["message"] = "";
if(!result->start(error)){
res["error_start"] = error;
res["started"] = false;
} else {
res["error_start"] = "";
res["started"] = true;
}
serverInstance->action_logger()->server_logger.log_server_create(result->getServerId(), this->ref(), log::ServerCreateReason::SNAPSHOT_DEPLOY);
}
res["time"] = duration_cast<milliseconds>(end - start).count();
this->sendCommand(res);
return command_result{error::ok};
#else
return command_result{error::command_not_found};
#endif
}
command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) {
CMD_RESET_IDLE;
if(this->server) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
} else {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
}
std::string error{};
auto server = this->server;
auto result = serverInstance->getVoiceServerManager()->deploy_snapshot(error, server, command);
using SnapshotDeployResult = VirtualServerManager::SnapshotDeployResult;
switch (result) {
case SnapshotDeployResult::SUCCESS:
break;
case SnapshotDeployResult::REACHED_SERVER_ID_LIMIT:
return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Server ID limit reached)"};
case SnapshotDeployResult::REACHED_SOFTWARE_SERVER_LIMIT:
return command_result{error::server_max_vs_reached, "You cant create anymore virtual servers. (Software limit reached)"};
case SnapshotDeployResult::REACHED_CONFIG_SERVER_LIMIT:
return command_result{error::server_max_vs_reached, "You reached the via config.yml enabled virtual server limit."};
case SnapshotDeployResult::CUSTOM_ERROR:
return command_result{error::vs_critical, error};
}
ts::command_builder notify{""};
notify.put_unchecked(0, "virtualserver_port", server->properties()[property::VIRTUALSERVER_PORT].value());
notify.put_unchecked(0, "sid", server->getServerId());
this->sendCommand(notify, false);
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerSnapshotCreate(Command& cmd) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_create, 1);
CMD_RESET_IDLE;
CMD_REQ_SERVER;
Command snapshot_command("");
string error;
int version = cmd[0].has("version") ? cmd[0]["version"] : -1;
if(version == -1 && (cmd.hasParm("lagacy") || cmd.hasParm("legacy"))) {
version = 0;
}
if(!serverInstance->getVoiceServerManager()->createServerSnapshot(snapshot_command, this->server, version, error)) {
return command_result{error::vs_critical, error};
}
if(version == -1 || version >= 3) {
auto build_version = snapshot_command[0]["snapshot_version"].as<int>();
snapshot_command.pop_bulk();
auto snapshot_data = snapshot_command.build();
auto max_compressed_size = ZSTD_compressBound(snapshot_data.size());
if(ZSTD_isError(max_compressed_size)) {
return command_result{error::vs_critical, "failed to calculate compressed size: " + std::string{ZSTD_getErrorName(max_compressed_size)}};
}
std::string buffer{};
buffer.resize(max_compressed_size);
auto compressed_size = ZSTD_compress(buffer.data(), buffer.size(), snapshot_data.data(), snapshot_data.length(), 1);
if(ZSTD_isError(compressed_size)) {
return command_result{error::vs_critical, "failed to compressed snapshot: " + std::string{ZSTD_getErrorName(compressed_size)}};
}
ts::command_builder result{""};
result.bulk(0).reserve(100 + compressed_size * 4/3);
result.put_unchecked(0, "snapshot_version", build_version);
result.put_unchecked(0, "data", base64::encode(std::string_view{buffer.data(), compressed_size}));
this->sendCommand(result, false);
} else {
auto data = snapshot_command.build();
auto buildHash = base64::encode(digest::sha1(data));
snapshot_command.push_bulk_front();
snapshot_command[0]["hash"] = buildHash;
this->sendCommand(snapshot_command);
}
return command_result{error::ok};
}
extern bool mainThreadActive;
command_result QueryClient::handleCommandServerProcessStop(Command& cmd) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_stop, 1);
if(cmd[0].has("type")) {
if(cmd["type"] == "cancel") {
auto task = ts::server::scheduledShutdown();
if(!task) return command_result{error::server_is_not_shutting_down, "There isn't a shutdown scheduled"};
ts::server::cancelShutdown(true);
return command_result{error::ok};
} else if(cmd["type"] == "schedule") {
if(!cmd[0].has("time")) return command_result{error::parameter_missing, "Missing time"};
ts::server::scheduleShutdown(system_clock::now() + seconds(cmd["time"].as<uint64_t>()), cmd[0].has("msg") ? cmd["msg"].string() : ts::config::messages::applicationStopped);
return command_result{error::ok};
}
}
string reason = ts::config::messages::applicationStopped;
if(cmd[0].has("msg"))
reason = cmd["msg"].string();
if(cmd[0].has("reasonmsg"))
reason = cmd["reasonmsg"].string();
serverInstance->getVoiceServerManager()->shutdownAll(reason);
mainThreadActive = false;
return command_result{error::ok};
}
#define XMACRO_EV(evName0, evSpec0, evName1, evSpec1) \
if(cmd["event"].value() == "all" || (cmd["event"].value() == (evName0) && (cmd["specifier"].value() == "all" || cmd["specifier"].value() == (evSpec0)))) \
events.push_back({QueryEventGroup::evName1, QueryEventSpecifier::evSpec1});
inline bool parseEvent(ParameterBulk& cmd, vector<pair<QueryEventGroup, QueryEventSpecifier>>& events){
auto start = events.size();
#include "XMacroEventTypes.h"
if(start == events.size()) return false;
return true;
}
#undef XMACRO_EV
#define XMACRO_LAGACY_EV(lagacyName, gr, spec) \
if(event == lagacyName) this->toggleEvent(QueryEventGroup::gr, QueryEventSpecifier::spec, true);
command_result QueryClient::handleCommandServerNotifyRegister(Command &cmd) {
CMD_REQ_SERVER;
CMD_REQ_PARM("event");
CMD_RESET_IDLE;
if(!cmd[0].has("specifier") && cmd["event"].as<string>() != "all") { //Lagacy support
logMessage(this->getServerId(), "{} Client {}:{} uses the lagacy notify system, which is deprecated!", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp(), this->getPeerPort());
string event = cmd["event"];
std::transform(event.begin(), event.end(), event.begin(), ::tolower);
#include "XMacroEventTypes.h"
return command_result{error::ok};
}
//TODO implement bulk
vector<pair<QueryEventGroup, QueryEventSpecifier>> events;
//parameter_invalid
auto result = parseEvent(cmd[0], events);
if(!result) return command_result{error::parameter_invalid};
for(const auto& ev : events)
this->toggleEvent(ev.first, ev.second, true);
return command_result{error::ok};
}
#undef XMACRO_LAGACY_EV
#define XMACRO_EV(evName0, evSpec0, evName1, evSpec1) \
else if((evName1) == group && (evSpec1) == spec){ \
res[index]["event"] = evName0; \
res[index]["specifier"] = evSpec0; \
index++; \
}
command_result QueryClient::handleCommandServerNotifyList(Command& cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
Command res("");
int index = 0;
for(int group = QueryEventGroup::QEVENTGROUP_MIN; group < QueryEventGroup::QEVENTGROUP_MAX; group = group + 1)
for(int spec = QueryEventSpecifier::QEVENTSPECIFIER_MIN; spec < QueryEventSpecifier::QEVENTSPECIFIER_MAX; spec = spec + 1) {
bool state = this->eventActive((QueryEventGroup) group, (QueryEventSpecifier) spec);
if(state || cmd.hasParm("all")){
res[index]["active"] = state;
if(false);
#include "XMacroEventTypes.h"
}
}
if(index == 0) return command_result{error::database_empty_result};
this->sendCommand(res);
return command_result{error::ok};
}
#undef XMACRO_EV
#define XMACRO_LAGACY_EV(lagacyName, gr, spec) \
if(event == lagacyName) this->toggleEvent(QueryEventGroup::gr, QueryEventSpecifier::spec, false);
command_result QueryClient::handleCommandServerNotifyUnregister(Command &cmd) {
CMD_REQ_SERVER;
CMD_REQ_PARM("event");
CMD_RESET_IDLE;
if(!cmd[0].has("specifier")){
logMessage(this->getServerId(), "{} Client {}:{} uses the lagacy notify system, which is deprecated!", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp(), this->getPeerPort());
string event = cmd["event"];
std::transform(event.begin(), event.end(), event.begin(), ::tolower);
#include "XMacroEventTypes.h"
return command_result{error::ok};
}
//TODO implemt bulk
vector<pair<QueryEventGroup, QueryEventSpecifier>> events;
auto result = parseEvent(cmd[0], events);
if(!result) return command_result{error::parameter_invalid};
for(const auto& ev : events)
this->toggleEvent(ev.first, ev.second, false);
return command_result{error::ok};
}
#undef XMACRO_LAGACY_EV