Teaspeak-Server/server/src/snapshots/deploy.cpp

503 lines
22 KiB
C++

//
// Created by WolverinDEV on 11/04/2020.
//
#include "./snapshot.h"
#include "./server.h"
#include "./channel.h"
#include "./permission.h"
#include "./client.h"
#include "./groups.h"
#include "../VirtualServerManager.h"
#include "../InstanceHandler.h"
#include <sql/insert.h>
#include <log/LogUtils.h>
using namespace ts;
using namespace ts::server;
using SnapshotType = ts::server::snapshots::type;
using SnapshotVersion = ts::server::snapshots::version_t;
bool VirtualServerManager::deploy_snapshot(std::string &error, ServerId server_id, const command_parser &data) {
if(data.bulk(0).has_key("version")) {
return this->deploy_ts3_snapshot(error, server_id, data);
} else if(data.bulk(1).has_key("snapshot_version")) {
/* teaspeak snapshot */
return this->deploy_teaspeak_snapshot(error, server_id, data);
} else {
/* old TS3 snapshot format */
return this->deploy_ts3_snapshot(error, server_id, data);
}
}
bool VirtualServerManager::deploy_teaspeak_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
if(!data.bulk(1).has_key("snapshot_version")) {
error = "Missing snapshot version";
return false;
}
auto version = data.bulk(1).value_as<snapshots::version_t>("snapshot_version");
auto hash = data.bulk(0).value("hash");
if(version < 1) {
error = "snapshot version too old";
return false;
} else if(version > 2) {
error = "snapshot version is too new";
return false;
}
/* the actual snapshot begins at index 2 */
return this->deploy_raw_snapshot(error, server_id, data, hash, 2, SnapshotType::TEASPEAK, version);
}
bool VirtualServerManager::deploy_ts3_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
snapshots::version_t version{0};
if(data.bulk(0).has_key("version"))
version = data.bulk(0).value_as<snapshots::version_t>("version");
auto hash = data.bulk(0).value("hash");
if(data.bulk(0).has_key("salt")) {
error = "TeaSpeak dosn't support encrypted snapshots yet";
return false;
}
if(version == 0) {
return this->deploy_raw_snapshot(error, server_id, data, hash, 1, SnapshotType::TEAMSPEAK, version);
} else if(version == 1) {
error = "version 1 is an invalid version";
return false;
} else if(version == 2) {
/* compressed data */
error = "version 2 isn't currently supported";
return false;
} else if(version == 3) {
error = "version 3 isn't currently supported";
return false;
} else {
error = "snapshots with version 1-3 are currently supported";
return false;
}
}
struct parse_client_entry {
snapshots::client_entry parsed_data{};
};
struct parsed_group_entry {
snapshots::group_entry parsed_data{};
};
bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &command, const std::string& /* hash */, size_t command_offset,
snapshots::type type, snapshots::version_t version) {
snapshots::server_entry parsed_server{};
//TODO: Verify hash
/* all snapshots start with the virtual server properties */
{
snapshots::server_parser parser{type, version, command};
if(!parser.parse(error, parsed_server, command_offset))
return false;
}
std::vector<snapshots::channel_entry> parsed_channels{};
/* afterwards all channels */
{
snapshots::channel_parser parser{type, version, command};
auto data = command.bulk(command_offset);
if(!data.has_key("begin_channels")) {
error = "missing begin channels token at " + std::to_string(data.command_character_index());
return false;
}
auto end_bulk = command.next_bulk_containing("end_channels", command_offset);
if(!end_bulk.has_value()) {
error = "missing end channels token";
return false;
} else if(*end_bulk == command_offset) {
error = "snapshot contains no channels";
return false;
}
parsed_channels.reserve(*end_bulk - command_offset);
debugMessage(server_id, "Snapshot contains {} channels", *end_bulk - command_offset);
while(!command.bulk(command_offset).has_key("end_channels")) {
auto& entry = parsed_channels.emplace_back();
if(!parser.parse(error, entry, command_offset))
return false;
}
command_offset++; /* the "end_channels" token */
}
std::vector<parse_client_entry> parsed_clients{};
/* after channels all clients */
{
snapshots::client_parser parser{type, version, command};
auto data = command.bulk(command_offset);
if(!data.has_key("begin_clients")) {
error = "missing begin clients token at " + std::to_string(data.command_character_index());
return false;
}
auto end_bulk = command.next_bulk_containing("end_clients", command_offset);
if(!end_bulk.has_value()) {
error = "missing end clients token";
return false;
}
parsed_channels.reserve(*end_bulk - command_offset);
debugMessage(server_id, "Snapshot contains {} clients", *end_bulk - command_offset);
while(!command.bulk(command_offset).has_key("end_clients")) {
auto& entry = parsed_clients.emplace_back();
if(!parser.parse(error, entry.parsed_data, command_offset))
return false;
}
command_offset++; /* the "end_clients" token */
}
bool server_groups_parsed{false},
channel_groups_parsed{false},
client_permissions_parsed{false},
channel_permissions_parsed{false},
client_channel_permissions_parsed{false};
std::vector<parsed_group_entry> parsed_server_groups{};
snapshots::group_relations parsed_server_group_relations{};
std::vector<parsed_group_entry> parsed_channel_groups{};
snapshots::group_relations parsed_channel_group_relations{};
std::deque<snapshots::permissions_flat_entry> client_permissions{};
std::deque<snapshots::permissions_flat_entry> channel_permissions{};
std::deque<snapshots::permissions_flat_entry> client_channel_permissions{};
/* permissions */
{
if(!command.bulk(command_offset++).has_key("begin_permissions")) {
error = "missing begin permissions key";
return false;
}
snapshots::relation_parser relation_parser{type, version, command};
while(!command.bulk(command_offset).has_key("end_permissions")) {
if(command.bulk(command_offset).has_key("server_groups")) {
if(server_groups_parsed) {
error = "duplicated server group list";
return false;
} else server_groups_parsed = true;
snapshots::group_parser group_parser{type, version, command, "id", permission::teamspeak::GroupType::SERVER};
/* parse all groups */
while(!command.bulk(command_offset).has_key("end_groups")){
auto& group = parsed_server_groups.emplace_back();
if(!group_parser.parse(error, group.parsed_data, command_offset)) /* will consume the end group token */
return false;
command_offset++; /* for the "end_group" token */
}
command_offset++; /* for the "end_groups" token */
/* parse relations */
if(!relation_parser.parse(error, parsed_server_group_relations, command_offset))
return false;
command_offset++; /* for the "end_relations" token */
if(parsed_server_group_relations.size() > 1) {
error = "all group relations should be for channel id 0 but received more than one different channel.";
return false;
} else if(!parsed_server_group_relations.empty() && parsed_server_group_relations.begin()->first != 0) {
error = "all group relations should be for channel id 0 but received it for " + std::to_string(parsed_server_group_relations.begin()->first);
return false;
}
} else if(command.bulk(command_offset).has_key("channel_groups")) {
if(channel_groups_parsed) {
error = "duplicated channel group list";
return false;
} else channel_groups_parsed = true;
snapshots::group_parser group_parser{type, version, command, "id", permission::teamspeak::GroupType::CHANNEL};
/* parse all groups */
while(!command.bulk(command_offset).has_key("end_groups")){
auto& group = parsed_channel_groups.emplace_back();
if(!group_parser.parse(error, group.parsed_data, command_offset))
return false;
command_offset++; /* for the "end_group" token */
}
command_offset++; /* for the "end_groups" token */
/* parse relations */
if(!relation_parser.parse(error, parsed_channel_group_relations, command_offset))
return false;
command_offset++; /* for the "end_relations" token */
} else if(command.bulk(command_offset).has_key("client_flat")) {
/* client permissions */
if(client_permissions_parsed) {
error = "duplicated client permissions list";
return false;
} else client_permissions_parsed = true;
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CLIENT};
if(!flat_parser.parse(error, client_permissions, command_offset))
return false;
command_offset++; /* for the "end_flat" token */
} else if(command.bulk(command_offset).has_key("channel_flat")) {
/* channel permissions */
if(channel_permissions_parsed) {
error = "duplicated channel permissions list";
return false;
} else channel_permissions_parsed = true;
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CHANNEL};
if(!flat_parser.parse(error, channel_permissions, command_offset))
return false;
command_offset++; /* for the "end_flat" token */
} else if(command.bulk(command_offset).has_key("channel_client_flat")) {
/* channel client permissions */
if(client_channel_permissions_parsed) {
error = "duplicated client channel permissions list";
return false;
} else client_channel_permissions_parsed = true;
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CLIENT};
if(!flat_parser.parse(error, client_channel_permissions, command_offset))
return false;
command_offset++; /* for the "end_flat" token */
} else {
command_offset++;
}
}
}
/* check if everything has been parsed */
{
/* basic stuff */
if(!server_groups_parsed) {
error = "missing server groups";
return false;
}
if(!channel_groups_parsed) {
error = "missing channel groups";
return false;
}
if(!client_permissions_parsed) {
error = "missing client permissions";
return false;
}
if(!channel_permissions_parsed) {
error = "missing channel permissions";
return false;
}
if(!client_channel_permissions_parsed) {
error = "missing client channel permissions";
return false;
}
}
std::map<ChannelId, ChannelId> channel_id_mapping{};
std::map<ChannelId, ChannelId> channel_group_id_mapping{};
std::map<ChannelId, ChannelId> server_group_id_mapping{};
/* lets start inserting data to the database */
{
/* cleanup all old data */
this->delete_server_in_db(server_id);
/* register clients */
{
//`original_client_id` INTEGER DEFAULT 0
//CREATE TABLE `clients_v2` (`serverId` INT NOT NULL, `cldbid` INTEGER, `clientUid` VARCHAR(64) NOT NULL, `firstConnect` BIGINT DEFAULT 0, `lastConnect` BIGINT DEFAULT 0, `connections` INT DEFAULT 0, `lastName` VARCHAR(128) DEFAULT '', UNIQUE(`serverId`, `clientUid`));
sql::InsertQuery insert_general_query{"clients",
sql::Column<ServerId>("serverId"),
sql::Column<ClientDbId>("original_client_id"),
sql::Column<std::string>("clientUid"),
sql::Column<int64_t>("firstConnect"),
sql::Column<int64_t>("lastConnect"),
sql::Column<uint64_t>("connections"),
sql::Column<std::string>("lastName")
};
sql::InsertQuery insert_server_query{"clients",
sql::Column<ServerId>("serverId"),
sql::Column<ClientDbId>("original_client_id"),
sql::Column<std::string>("clientUid"),
sql::Column<int64_t>("firstConnect"),
sql::Column<int64_t>("lastConnect"),
sql::Column<uint64_t>("connections"),
sql::Column<std::string>("lastName")
};
for(const auto& client : parsed_clients) {
/*
insert_general_query.add_entry(
server_id,
client.parsed_data.database_id,
client.parsed_data.unique_id,
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_created.time_since_epoch()).count(),
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_last_connected.time_since_epoch()).count(),
client.parsed_data.client_total_connections,
client.parsed_data.nickname
);
*/
insert_general_query.add_entry(
server_id,
client.parsed_data.database_id,
client.parsed_data.unique_id,
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_created.time_since_epoch()).count()
);
}
auto result = insert_general_query.execute(this->handle->getSql(), true);
for(const auto& fail : result.failed_entries)
logWarning(server_id, "Failed to insert client {} into the database: {}", parsed_clients[std::get<0>(fail)], std::get<1>(fail).fmtStr());
sql::command{this->handle->getSql(), "SELECT `original_client_id`,`cldbid` FROM `clients` WHERE `serverId` = :sid;"}
.value(":serverId", server_id)
.query([&](int length, std::string* values, std::string* names) {
ClientDbId original_id{0}, new_id{0};
try {
original_id = std::stoull(values[0]);
new_id = std::stoull(values[1]);
} catch (std::exception& ex) {
logWarning(server_id, "Failed to parse client database entry mapping for group id {} (New ID: {})", values[1], values[0]);
return;
}
server_group_id_mapping[original_id] = new_id;
});
}
/* channels */
{
/* Assign each channel a new id */
ChannelId current_id{0};
for(auto& channel : parsed_channels) {
const auto new_id = current_id++;
channel_id_mapping[channel.properties[property::CHANNEL_ID]] = new_id;
channel.properties[property::CHANNEL_ID] = new_id;
}
/* Update channel parents */
for(auto& channel : parsed_channels) {
auto pid = channel.properties[property::CHANNEL_PID].as<ChannelId>();
if(pid > 0) {
auto new_id = channel_id_mapping.find(pid);
if(new_id == channel_id_mapping.end()) {
error = "failed to remap channel parent id for channel \"" + channel.properties[property::CHANNEL_NAME].value() + "\" (snapshot/channel tree broken?)";
return false;
}
channel.properties[property::CHANNEL_PID] = new_id->second;
}
}
//TODO: Insert them into the database
}
/* channel permissions */
{
for(auto& entry : channel_permissions) {
auto new_id = channel_id_mapping.find(entry.id1);
if(new_id == channel_id_mapping.end()) {
error = "missing channel id mapping for channel permission entry";
return false;
}
entry.id1 = new_id->second;
}
}
/* server groups */
{
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_id`) VALUES (:serverId, :target, :type, :name, :id)"};
insert_model.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL);
for(auto& group : parsed_server_groups) {
auto result = insert_model.command().value(":name", group.parsed_data.name).value(":id", group.parsed_data.group_id).execute();
if(!result)
logWarning(server_id, "Failed to insert server group \"{}\" into the database", group.parsed_data.name);
}
sql::command{this->handle->getSql(), "SELECT `original_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL)
.query([&](int length, std::string* values, std::string* names) {
GroupId original_id{0}, new_id{0};
try {
original_id = std::stoull(values[0]);
new_id = std::stoull(values[1]);
} catch (std::exception& ex) {
logWarning(server_id, "Failed to parse server group mapping for group id {} (New ID: {})", values[1], values[0]);
return;
}
server_group_id_mapping[original_id] = new_id;
});
}
/* channel groups */
{
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_id`) VALUES (:serverId, :target, :type, :name, :id)"};
insert_model.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL);
for(auto& group : parsed_server_groups) {
auto result = insert_model.command().value(":name", group.parsed_data.name).value(":id", group.parsed_data.group_id).execute();
if(!result)
logWarning(server_id, "Failed to insert channel group \"{}\" into the database", group.parsed_data.name);
}
sql::command{this->handle->getSql(), "SELECT `original_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL)
.query([&](int length, std::string* values, std::string* names) {
GroupId original_id{0}, new_id{0};
try {
original_id = std::stoull(values[0]);
new_id = std::stoull(values[1]);
} catch (std::exception& ex) {
logWarning(server_id, "Failed to parse channel group mapping for group id {} (New ID: {})", values[1], values[0]);
return;
}
channel_group_id_mapping[original_id] = new_id;
});
}
#define INSERT_PERMISSION_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"
/* client permissions */
{
sql::InsertQuery insert_query{"permissions",
sql::Column<ServerId>("serverId"),
sql::Column<permission::PermissionSqlType>("type"),
sql::Column<uint64_t>("id"),
sql::Column<ChannelId>("channelId"),
sql::Column<std::string>("permId"),
sql::Column<permission::PermissionValue>("value"),
sql::Column<permission::PermissionValue>("grant"),
sql::Column<bool>("flag_skip"),
sql::Column<bool>("flag_negate"),
};
for(auto& permission : client_permissions) {
}
auto result = insert_query.execute(this->handle->getSql(), true);
}
/* register clients in the database */
{
}
/* client channel permissions */
{
for(auto& entry : client_channel_permissions) {
auto new_id = channel_id_mapping.find(entry.id1);
if(new_id == channel_id_mapping.end()) {
error = "missing channel id mapping for client channel permission entry";
return false;
}
entry.id1 = new_id->second;
}
}
}
error = "not implemented";
return false;
}