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

993 lines
44 KiB
C++
Raw Normal View History

#include "Properties.h"
#include "query/Command.h"
#include <algorithm>
#include <src/server/QueryServer.h>
#include <src/ServerManager.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>
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
extern ts::server::InstanceHandler* serverInstance;
constexpr unsigned int string_hash(const char* str, int h = 0) {
return !str[h] ? 5381 : (string_hash(str, h + 1) * 33) ^ str[h];
}
CommandResult 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"):
return this->handleCommandServerSnapshotDeploy(cmd);
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);
}
CommandResult QueryClient::handleCommandExit(Command &) {
logMessage(LOG_QUERY, "[Query] {}:{} disconnected. (Requested by client)", this->getLoggingPeerIp(), this->getPeerPort());
this->closeConnection(system_clock::now() + seconds(1));
return CommandResult::Success;
}
struct QuerySqlData {
std::string username;
std::string password;
std::string uniqueId;
};
//login client_login_name=andreas client_login_password=meinPW
CommandResult 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("Attempted query login with " + username + " - " + password);
auto _account = serverInstance->getQueryServer()->find_query_account_by_name(username);
auto account = _account ? serverInstance->getQueryServer()->load_password(_account) : nullptr;
{
threads::MutexLock lock(this->handle->loginLock);
if(!account)
return {findError("client_invalid_password"), "username or password dose not match"};
if (account->password != password) {
if(!this->whitelisted) {
this->handle->loginAttempts[this->getPeerIp()]++;
if(this->handle->loginAttempts[this->getPeerIp()] > 3) {
this->handle->queryBann[this->getPeerIp()] = system_clock::now() + seconds(serverInstance->properties()[property::SERVERINSTANCE_SERVERQUERY_BAN_TIME].as<uint64_t>()); //TODO configurable | Disconnect all others?
this->postCommandHandler.emplace_back([&](){
this->closeConnection(system_clock::now() + seconds(1));
});
return {findError("ban_flooding"), ""};
}
}
return {findError("client_invalid_password"), "username or password dose not match"};
}
}
if(!this->properties()[property::CLIENT_LOGIN_NAME].as<string>().empty()) {
Command log("logout");
if(!this->handleCommandLogout(log)) return {ErrorType::VSError, "Failed to logout!"};
}
this->query_account = account;
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.lock(), "login", tree_lock);
}
this->server->groups->disableCache(_this.lock());
} else serverInstance->getGroupManager()->disableCache(_this.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)
return {findError("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.lock());
if(target_server) {
target_server->groups->enableCache(_this.lock());
target_server->registerClient(_this.lock());
shared_lock server_tree_lock(target_server->channel_tree_lock);
target_server->notifyClientPropertyUpdates(_this.lock(), deque<property::ClientProperties>{property::CLIENT_NICKNAME, property::CLIENT_UNIQUE_IDENTIFIER});
shared_lock client_tree_lock(this->channel_lock);
this->channels->reset();
this->channels->insert_channels(target_server->channelTree->tree_head(), true, false);
} else {
serverInstance->getGroupManager()->enableCache(_this.lock());
this->update_cached_permissions();
}
if(target_server) {
unique_lock tree_lock(target_server->channel_tree_lock);
if(joined)
this->server->client_move(this->ref(), joined, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock);
}
this->properties()[property::CLIENT_TOTALCONNECTIONS]++;
this->updateChannelClientProperties(true, true);
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandLogout(Command &) {
CMD_RESET_IDLE;
if(this->properties()[property::CLIENT_LOGIN_NAME].as<string>().empty()) return {findError("client_not_logged_in"), "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.lock(), "logout", tree_lock);
}
this->server->groups->disableCache(_this.lock());
} else serverInstance->getGroupManager()->disableCache(_this.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.lock());
if(this->server){
this->server->groups->enableCache(_this.lock());
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);
client_channel_lock.unlock();
server_channel_r_lock.unlock();
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 {
serverInstance->getGroupManager()->enableCache(_this.lock());
}
this->updateChannelClientProperties(true, true);
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandServerSelect(Command &cmd) {
CMD_RESET_IDLE;
shared_ptr<TSServer> 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 {findError("server_invalid_id"), "Invalid server id"};
if(target == this->server) return CommandResult::Success;
if(target) {
if(this->query_account && this->query_account->bound_server > 0) {
if(target->getServerId() != this->query_account->bound_server)
return {findError("server_invalid_id"), "You're a server bound query, and the target server isn't your origin."};
} else {
auto allowed = target->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_virtualserver_select, this->getType(), nullptr);
if(allowed != -1 && allowed <= 0) return CommandResultPermissionError{permission::b_virtualserver_select};
}
}
{
auto server_locked = this->server;
if(server_locked) {
//unregister manager from old server
{
unique_lock tree_lock(this->server->channel_tree_lock);
if(this->currentChannel)
this->server->client_move(this->ref(), nullptr, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock);
this->server->unregisterClient(_this.lock(), "server switch", tree_lock);
}
server_locked->groups->disableCache(_this.lock());
this->channels->reset();
} else serverInstance->getGroupManager()->disableCache(_this.lock());
}
this->resetEventMask();
//register at current server
{
2019-07-19 13:42:57 -04:00
//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.lock());
if(this->server) {
this->server->groups->enableCache(_this.lock());
this->server->registerClient(_this.lock());
{
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, true);
}
auto negated_enforce_join = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_virtualserver_select_godmode, 1);
if(!negated_enforce_join)
this->server->assignDefaultChannel(this->ref(), true);
else
this->update_cached_permissions();
} else {
serverInstance->getGroupManager()->enableCache(_this.lock());
this->update_cached_permissions();
}
this->updateChannelClientProperties(true, true);
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandJoin(Command &) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
if(!this->server->running())
return {findError("server_is_not_running"), "server isnt started yet"};
if(this->currentChannel)
return {findError("server_already_joined"), "already joined!"};
debugMessage("Uid -> " + this->properties()[property::CLIENT_UNIQUE_IDENTIFIER].as<string>());
this->server->assignDefaultChannel(this->ref(), true);
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandLeft(Command&) {
CMD_REQ_SERVER;
CMD_REQ_CHANNEL;
CMD_RESET_IDLE;
PERM_CHECK_CHANNELR(permission::b_virtualserver_select_godmode, 1, nullptr, 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 CommandResult::Success;
}
CommandResult QueryClient::handleCommandServerInfo(Command &) {
CMD_RESET_IDLE;
PERM_CHECKR(permission::b_virtualserver_info_view, 1, true);
Command cmd("");
for(const auto &prop : (this->server ? this->server->properties() : *serverInstance->getDefaultServerProperties()).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<string>();
if(prop.type() == property::VIRTUALSERVER_HOST)
cmd["virtualserver_ip"] = prop.as<string>();
}
cmd["virtualserver_status"] = this->server ? ServerState::string(this->server->state) : "template";
if(this->server && this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_virtualserver_connectioninfo_view, 1, nullptr, true)) {
auto stats = this->server->getServerStatistics()->statistics();
auto report = this->server->serverStatistics->dataReport();
cmd["connection_bandwidth_sent_last_second_total"] = report.send_second;
cmd["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
cmd["connection_bandwidth_received_last_second_total"] = report.recv_second;
cmd["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
cmd["connection_filetransfer_bandwidth_sent"] = report.file_send;
cmd["connection_filetransfer_bandwidth_received"] = report.file_recv;
cmd["connection_filetransfer_bytes_sent_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
cmd["connection_filetransfer_bytes_received_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
cmd["connection_packets_sent_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT].value();
cmd["connection_bytes_sent_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED].value();
cmd["connection_packets_received_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].value();
cmd["connection_bytes_received_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].value();
cmd["connection_packets_sent_keepalive"] = (*stats)[property::CONNECTION_PACKETS_SENT_KEEPALIVE].value();
cmd["connection_packets_received_keepalive"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE].value();
cmd["connection_bytes_received_keepalive"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_KEEPALIVE].value();
cmd["connection_bytes_sent_keepalive"] = (*stats)[property::CONNECTION_BYTES_SENT_KEEPALIVE].value();
cmd["connection_packets_sent_control"] = (*stats)[property::CONNECTION_PACKETS_SENT_CONTROL].value();
cmd["connection_bytes_sent_control"] = (*stats)[property::CONNECTION_BYTES_SENT_CONTROL].value();
cmd["connection_packets_received_control"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_CONTROL].value();
cmd["connection_bytes_received_control"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_CONTROL].value();
cmd["connection_packets_sent_total"] = (*stats)[property::CONNECTION_PACKETS_SENT_TOTAL].value();
cmd["connection_bytes_sent_total"] = (*stats)[property::CONNECTION_BYTES_SENT_TOTAL].value();
cmd["connection_packets_received_total"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].value();
cmd["connection_bytes_received_total"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_TOTAL].value();
} 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 CommandResult::Success;
}
CommandResult QueryClient::handleCommandChannelList(Command& cmd) {
PERM_CHECKR(permission::b_virtualserver_channel_list, 1, true);
CMD_RESET_IDLE;
Command result("");
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();
for(const auto& channel : entries){
if(!channel) continue;
auto& bulk = result[index];
bulk["cid"] = channel->channelId();
bulk["pid"] = channel->properties()[property::CHANNEL_PID].as<string>();
bulk["channel_name"] = channel->name();
bulk["channel_order"] = channel->channelOrder();
bulk["total_clients"] = this->server ? this->server->getClientsByChannel(channel).size() : 0;
/* bulk["channel_needed_subscribe_power"] = channel->permissions()->getPermissionValue(permission::PERMTEST_ORDERED, permission::i_channel_needed_subscribe_power, channel, 0); */
if(cmd.hasParm("flags")){
bulk["channel_flag_default"] = channel->properties()[property::CHANNEL_FLAG_DEFAULT].as<string>();
bulk["channel_flag_password"] = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<string>();
bulk["channel_flag_permanent"] = channel->properties()[property::CHANNEL_FLAG_PERMANENT].as<string>();
bulk["channel_flag_semi_permanent"] = channel->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as<string>();
}
if(cmd.hasParm("voice")){
bulk["channel_codec"] = channel->properties()[property::CHANNEL_CODEC].as<string>();
bulk["channel_codec_quality"] = channel->properties()[property::CHANNEL_CODEC_QUALITY].as<string>();
bulk["channel_needed_talk_power"] = channel->properties()[property::CHANNEL_NEEDED_TALK_POWER].as<string>();
}
if(cmd.hasParm("icon")){
bulk["channel_icon_id"] = channel->properties()[property::CHANNEL_ICON_ID].as<string>();
}
if(cmd.hasParm("limits")){
bulk["total_clients_family"] = this->server ? this->server->getClientsByChannelRoot(channel, false).size() : 0;
bulk["total_clients"] = this->server ? this->server->getClientsByChannel(channel).size() : 0;
bulk["channel_maxclients"] = channel->properties()[property::CHANNEL_MAXCLIENTS].as<string>();
bulk["channel_maxfamilyclients"] = channel->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as<string>();
{
auto needed_power = channel->permissions()->permission_value_flagged(permission::i_channel_subscribe_power);
bulk["channel_needed_subscribe_power"] = needed_power.has_value ? needed_power.value : 0;
}
}
if(cmd.hasParm("topic")) {
bulk["channel_topic"] = channel->properties()[property::CHANNEL_TOPIC].as<string>();
}
if(cmd.hasParm("times")){
bulk["seconds_empty"] = channel->emptySince();
}
index++;
}
this->sendCommand(result);
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandServerList(Command& cmd) {
CMD_RESET_IDLE;
PERM_CHECKR(permission::b_serverinstance_virtualserver_list, 1, true);
Command res("");
int index = 0;
for(const auto& server : serverInstance->getVoiceServerManager()->serverInstances()){
res[index]["virtualserver_id"] = server->getServerId();
res[index]["virtualserver_host"] = server->properties()[property::VIRTUALSERVER_HOST].as<string>();
res[index]["virtualserver_port"] = server->properties()[property::VIRTUALSERVER_PORT].as<string>();
res[index]["virtualserver_web_host"] = server->properties()[property::VIRTUALSERVER_WEB_HOST].as<string>();
res[index]["virtualserver_web_port"] = server->properties()[property::VIRTUALSERVER_WEB_PORT].as<string>();
res[index]["virtualserver_status"] = ServerState::string(server->state);
res[index]["virtualserver_clientsonline"] = server->properties()[property::VIRTUALSERVER_CLIENTS_ONLINE].as<string>();
res[index]["virtualserver_queryclientsonline"] = server->properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE].as<string>();
res[index]["virtualserver_maxclients"] = server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as<string>();
if(server->startTimestamp.time_since_epoch().count() > 0 && server->state == ServerState::ONLINE)
res[index]["virtualserver_uptime"] = duration_cast<seconds>(system_clock::now() - server->startTimestamp).count();
else
res[index]["virtualserver_uptime"] = 0;
res[index]["virtualserver_name"] = server->properties()[property::VIRTUALSERVER_NAME].as<string>();
res[index]["virtualserver_autostart"] = server->properties()[property::VIRTUALSERVER_AUTOSTART].as<string>();
res[index]["virtualserver_machine_id"] = server->properties()[property::VIRTUALSERVER_MACHINE_ID].as<string>();
if(cmd.hasParm("uid"))
res[index]["virtualserver_unique_identifier"] = server->properties()[property::VIRTUALSERVER_UNIQUE_IDENTIFIER].as<string>();
else res[index]["virtualserver_unique_identifier"] = "";
index++;
}
this->sendCommand(res);
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandServerCreate(Command& cmd) {
CMD_RESET_IDLE;
PERM_CHECKR(permission::b_virtualserver_create, 1, true);
if(serverInstance->getVoiceServerManager()->getState() != ServerManager::STARTED) {
return {findError("vs_critical"), "Server manager isn't started yet or not finished starting"};
}
string startError;
shared_ptr<TSServer> 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 {findError("server_max_vs_reached"), "You reached the via config.yml enabled virtual server limit."};
if(instances.size() >= 65535)
return {findError("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);
}
uint16_t freePort = serverInstance->getVoiceServerManager()->next_available_port();
std::string host = cmd[0].has("virtualserver_host") ? cmd["virtualserver_host"].as<string>() : config::binding::DefaultVoiceHost;
uint16_t port = cmd[0].has("virtualserver_port") ? cmd["virtualserver_port"].as<uint16_t>() : freePort;
{
auto _start = system_clock::now();
server = serverInstance->getVoiceServerManager()->createServer(host, port);
auto _end = system_clock::now();
time_create = duration_cast<milliseconds>(_end - _start);
}
if(!server) return {findError("vs_critical"), "could not create new server"};
for(const auto& key : cmd[0].keys()){
if(key == "virtualserver_port") continue;
if(key == "virtualserver_host") continue;
auto info = property::impl::info<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);
}
Command res("");
res["sid"] = server->getServerId();
res["error"] = startError;
res["virtualserver_port"] = server->properties()[property::VIRTUALSERVER_PORT].as<string>();
res["token"] = server->tokenManager->avariableTokes().empty() ? "unknown" : server->tokenManager->avariableTokes()[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 CommandResult::Success;
}
CommandResult QueryClient::handleCommandServerDelete(Command& cmd) {
CMD_RESET_IDLE;
PERM_CHECKR(permission::b_virtualserver_delete, 1, true);
if(serverInstance->getVoiceServerManager()->getState() != ServerManager::STARTED)
return {findError("vs_critical"), "Server manager isn't started yet or not finished starting"};
auto server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]);
if(!server) return {findError("server_invalid_id"), "invalid bounded server"};
if(!serverInstance->getVoiceServerManager()->deleteServer(server)) return {ErrorType::VSError};
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandServerStart(Command& cmd) {
CMD_RESET_IDLE;
PERM_CHECKR(permission::b_virtualserver_start_any, 1, true);
if(serverInstance->getVoiceServerManager()->getState() != ServerManager::STARTED)
return {findError("vs_critical"), "Server manager isn't started yet or not finished starting"};
auto server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]);
if(!server) return {findError("server_invalid_id"), "invalid bounded server"};
if(server->running()) return {findError("server_running"), "server already running"};
if(server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_virtualserver_start, ClientType::CLIENT_QUERY, nullptr) != 1) {
if(!this->permissionGranted(permission::PERMTEST_HIGHEST, permission::b_virtualserver_start_any, 1))
return CommandResultPermissionError{permission::b_virtualserver_start};
}
string err;
if(!server->start(err)) return {findError("vs_critical"), err};
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandServerStop(Command& cmd) {
CMD_RESET_IDLE;
PERM_CHECKR(permission::b_virtualserver_stop, 1, true);
if(serverInstance->getVoiceServerManager()->getState() != ServerManager::STARTED)
return {findError("vs_critical"), "Server manager isn't started yet or not finished starting"};
auto server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]);
if(!server) return {findError("server_invalid_id"), "invalid bounded server"};
if(!server->running()) return {findError("server_is_not_running"), "server is not running"};
if(server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_virtualserver_stop, ClientType::CLIENT_QUERY, nullptr) != 1) {
if(!this->permissionGranted(permission::PERMTEST_HIGHEST, permission::b_virtualserver_stop_any, 1))
return CommandResultPermissionError{permission::b_virtualserver_stop};
}
server->stop("server stopped");
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandInstanceInfo(Command& cmd) {
PERM_CHECKR(permission::b_serverinstance_info_view, 1, true);
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.as<string>();
if(!this->properties()[property::CLIENT_LOGIN_NAME].value().empty())
res["serverinstance_teaspeak"] = true;
this->sendCommand(res);
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandInstanceEdit(Command& cmd) {
PERM_CHECKR(permission::b_serverinstance_modify_settings, 1, true);
for(const auto &key : cmd[0].keys()){
auto info = property::impl::info<property::InstanceProperties>(key);
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 CommandResult::Success;
}
CommandResult QueryClient::handleCommandHostInfo(Command &) {
PERM_CHECKR(permission::b_serverinstance_info_view, 1, true);
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 stats = serverInstance->getStatistics()->statistics();
res["connection_packets_sent_total"] = (*stats)[property::CONNECTION_PACKETS_SENT_TOTAL].as<string>();
res["connection_bytes_sent_total"] = (*stats)[property::CONNECTION_BYTES_SENT_TOTAL].as<string>();
res["connection_packets_received_total"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].as<string>();
res["connection_bytes_received_total"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_TOTAL].as<string>();
auto report = serverInstance->getStatistics()->dataReport();
res["connection_bandwidth_sent_last_second_total"] = report.send_second;
res["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
res["connection_bandwidth_received_last_second_total"] = report.recv_second;
res["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
res["connection_filetransfer_bandwidth_sent"] = report.file_send;
res["connection_filetransfer_bandwidth_received"] = report.file_recv;
res["connection_filetransfer_bytes_sent_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
res["connection_filetransfer_bytes_received_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
this->sendCommand(res);
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandGlobalMessage(Command& cmd) {
PERM_CHECKR(permission::b_serverinstance_textmessage_send, 1, true);
for(const auto &server : serverInstance->getVoiceServerManager()->serverInstances())
if(server->running()) server->broadcastMessage(server->getServerRoot(), cmd["msg"]);
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandServerIdGetByPort(Command& cmd) {
uint16_t port = cmd["virtualserver_port"];
auto server = serverInstance->getVoiceServerManager()->findServerByPort(port);
if(!server) return {findError("databse_empty_result"), "Invalid port"};
Command res("");
res["server_id"] = server->getServerId();
this->sendCommand(res);
return CommandResult::Success;
}
CommandResult 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 CommandResult::Success;
}
//TODO with mapping!
CommandResult QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
PERM_CHECKR(permission::b_virtualserver_snapshot_deploy, 1, true);
CMD_RESET_IDLE;
auto start = system_clock::now();
string error;
string host = "0.0.0.0";
uint16_t port = 0;
if(this->server) {
host = this->server->properties()[property::VIRTUALSERVER_HOST].as<string>();
port = this->server->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>();
}
auto hash = cmd["hash"].string();
if(hash.empty()) return {findError("parameter_invalid"), "Invalid hash (not present)"};
debugMessage("Hash: '" + hash + "'");
bool mapping = cmd.hasParm("mapping");
cmd.disableParm("mapping");
bool ignore_hash = cmd.hasParm("ignorehash");
cmd.disableParm("ignorehash");
cmd.pop_bulk();
auto str = cmd.build().substr(strlen("serversnapshotdeploy "));
if(!ignore_hash) {
auto buildHash = base64::encode(digest::sha1(str));
if(buildHash != hash) return {findError("parameter_invalid"), "Invalid hash (Expected: \"" + hash + "\", Got: \"" + buildHash + "\")"};
}
unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock);
if(port == 0)
port = serverInstance->getVoiceServerManager()->next_available_port();
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("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;
}
}
res["time"] = duration_cast<milliseconds>(end - start).count();
this->sendCommand(res);
return CommandResult::Success;
}
CommandResult QueryClient::handleCommandServerSnapshotCreate(Command& cmd) {
PERM_CHECKR(permission::b_virtualserver_snapshot_create, 1, true);
CMD_RESET_IDLE;
CMD_REQ_SERVER;
Command result("");
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(result, this->server, version, error))
return {ErrorType::VSError, error};
string data = result.build();
auto buildHash = base64::encode(digest::sha1(data));
result.push_bulk_front();
result[0]["hash"] = buildHash;
this->sendCommand(result);
return CommandResult::Success;
}
extern bool mainThreadActive;
CommandResult QueryClient::handleCommandServerProcessStop(Command& cmd) {
PERM_CHECKR(permission::b_serverinstance_stop, 1, true);
if(cmd[0].has("type")) {
if(cmd["type"] == "cancel") {
auto task = ts::server::scheduledShutdown();
if(!task) return {findError("server_is_not_shutting_down"), "There isn't a shutdown scheduled"};
ts::server::cancelShutdown(true);
return CommandResult::Success;
} else if(cmd["type"] == "schedule") {
if(!cmd[0].has("time")) return {findError("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 CommandResult::Success;
}
}
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 CommandResult::Success;
}
#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 CommandResult parseEvent(ParameterBulk& cmd, vector<pair<QueryEventGroup, QueryEventSpecifier>>& events){
auto start = events.size();
#include "XMacroEventTypes.h"
if(start == events.size()) return {findError("parameter_invalid"), "invalid group/specifier"};
return CommandResult::Success;
}
#undef XMACRO_EV
#define XMACRO_LAGACY_EV(lagacyName, gr, spec) \
if(event == lagacyName) this->toggleEvent(QueryEventGroup::gr, QueryEventSpecifier::spec, true);
CommandResult 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("[Query] Client " + this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()) + " uses the lagacy notify system, which is deprecated!");
string event = cmd["event"];
std::transform(event.begin(), event.end(), event.begin(), ::tolower);
#include "XMacroEventTypes.h"
return CommandResult::Success;
}
//TODO implement bulk
vector<pair<QueryEventGroup, QueryEventSpecifier>> events;
//parameter_invalid
auto result = parseEvent(cmd[0], events);
if(!result) return result;
for(const auto& ev : events)
this->toggleEvent(ev.first, ev.second, true);
return CommandResult::Success;
}
#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++; \
}
CommandResult 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 {findError("database_empty_result"), ""};
this->sendCommand(res);
return CommandResult::Success;
}
#undef XMACRO_EV
#define XMACRO_LAGACY_EV(lagacyName, gr, spec) \
if(event == lagacyName) this->toggleEvent(QueryEventGroup::gr, QueryEventSpecifier::spec, false);
CommandResult QueryClient::handleCommandServerNotifyUnregister(Command &cmd) {
CMD_REQ_SERVER;
CMD_REQ_PARM("event");
CMD_RESET_IDLE;
if(!cmd[0].has("specifier")){
logMessage("[Query] Client " + this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()) + " uses the lagacy notify system, which is deprecated!");
string event = cmd["event"];
std::transform(event.begin(), event.end(), event.begin(), ::tolower);
#include "XMacroEventTypes.h"
return CommandResult::Success;
}
//TODO implemt bulk
vector<pair<QueryEventGroup, QueryEventSpecifier>> events;
auto result = parseEvent(cmd[0], events);
if(!result) return result;
for(const auto& ev : events)
this->toggleEvent(ev.first, ev.second, false);
return CommandResult::Success;
}
#undef XMACRO_LAGACY_EV