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

992 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
{
unique_lock server_lock(this->server_lock);
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