503 lines
22 KiB
C++
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;
|
|
} |