Updating the deploy algorithm

This commit is contained in:
WolverinDEV 2020-07-31 17:35:14 +02:00
parent 72abd7e20e
commit 3dea4906e1
28 changed files with 1384 additions and 912 deletions

View File

@ -61,6 +61,8 @@ find_package(Opus REQUIRED)
find_package(spdlog REQUIRED) find_package(spdlog REQUIRED)
find_package(Jemalloc REQUIRED) find_package(Jemalloc REQUIRED)
find_package(Protobuf REQUIRED) find_package(Protobuf REQUIRED)
message("${zstd_DIR}")
find_package(zstd REQUIRED)
include_directories(${StringVariable_INCLUDE_DIR}) include_directories(${StringVariable_INCLUDE_DIR})
add_subdirectory(music/) add_subdirectory(music/)

@ -1 +1 @@
Subproject commit 0dc8e28125f9727f2dffef4b6f93b397099db1f0 Subproject commit ef2d6842d547b8f84ebbe4fe9fa6f132bcd84d35

View File

@ -296,6 +296,7 @@ target_link_libraries(TeaSpeakServer
jsoncpp_lib jsoncpp_lib
${ed25519_LIBRARIES_STATIC} ${ed25519_LIBRARIES_STATIC}
zstd::libzstd_static
) )
if (COMPILE_WEB_CLIENT) if (COMPILE_WEB_CLIENT)

View File

@ -13,38 +13,42 @@ using namespace ts::permission;
//#define DISABLE_CACHING //#define DISABLE_CACHING
struct ts::server::CachedPermissionManager {
ServerId server_id{0};
ClientDbId client_database_id{0};
std::weak_ptr<permission::v2::PermissionManager> instance{};
std::shared_ptr<permission::v2::PermissionManager> instance_ref{}; /* reference to the current instance, will be refreshed every time the instance gets accessed */
std::chrono::time_point<std::chrono::system_clock> last_access{};
};
struct ts::server::StartupCacheEntry {
ServerId sid{0};
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions{};
std::deque<std::unique_ptr<StartupPropertyEntry>> properties{};
};
DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {} DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {}
DatabaseHelper::~DatabaseHelper() { DatabaseHelper::~DatabaseHelper() {
for(const auto& elm : cachedPermissionManagers) this->cached_permission_managers.clear();
delete elm;
cachedPermissionManagers.clear();
} }
void DatabaseHelper::tick() { void DatabaseHelper::tick() {
auto cache_timeout = std::chrono::system_clock::now() - std::chrono::minutes{10};
{ {
threads::MutexLock l(this->permManagerLock); std::lock_guard cp_lock{this->cached_permission_manager_lock};
auto cpy = this->cachedPermissionManagers;
for(const auto& mgr : cpy){
//if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) //TODO remove instand delete!
mgr->ownLock.reset();
if(mgr->manager.expired()){
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), mgr));
delete mgr;
}
}
}
{ this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const std::unique_ptr<CachedPermissionManager>& manager) {
threads::MutexLock l(this->propsLock); if(manager->last_access < cache_timeout)
auto pcpy = this->cachedProperties; manager->instance_ref = nullptr;
for(const auto& mgr : pcpy) {
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) if(manager->instance.expired())
mgr->ownLock.reset(); return true;
if(mgr->properties.expired()) {
this->cachedProperties.erase(std::find(this->cachedProperties.begin(), this->cachedProperties.end(), mgr)); return false;
delete mgr; }), this->cached_permission_managers.end());
}
}
} }
} }
@ -136,15 +140,12 @@ bool DatabaseHelper::validClientDatabaseId(const std::shared_ptr<VirtualServer>&
void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) { void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
auto serverId = (ServerId) (server ? server->getServerId() : 0); auto serverId = (ServerId) (server ? server->getServerId() : 0);
{ {
lock_guard<threads::Mutex> lock(permManagerLock); lock_guard lock{cached_permission_manager_lock};
for(auto permMgr : this->cachedPermissionManagers)
if(serverId == 0 || (permMgr->cldbid == cldbid && permMgr->sid == serverId)) { this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr)); return entry->server_id == serverId && entry->client_database_id == cldbid;
delete permMgr; }), this->cached_permission_managers.end());
break;
} }
}
//TODO remove from props cache?
sql::result state{}; sql::result state{};
@ -236,26 +237,38 @@ inline sql::result load_permissions_v2(
logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count()); logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
} }
#define UPDATE_COMMAND "UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId" constexpr static std::string_view kPermissionUpdateCommand{"UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
#define INSERT_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)" constexpr static std::string_view kPermissionInsertCommand{"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)"};
#define DELETE_COMMAND "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId" constexpr static std::string_view kPermissionDeleteCommand{"DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::find_cached_permission_manager(ServerId server_id,
ClientDbId client_database_id) {
for(auto it = this->cached_permission_managers.begin(); it != this->cached_permission_managers.end(); it++) {
auto& cached_manager = *it;
if(cached_manager->client_database_id == client_database_id && cached_manager->server_id == server_id) {
auto manager = cached_manager->instance.lock();
if(!manager){
this->cached_permission_managers.erase(it);
break;
}
cached_manager->last_access = system_clock::now();
cached_manager->instance_ref = manager;
return manager;
}
}
return nullptr;
}
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) { std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
auto server_id = server ? server->getServerId() : 0; auto server_id = server ? server->getServerId() : 0;
#ifndef DISABLE_CACHING #ifndef DISABLE_CACHING
{ {
lock_guard<threads::Mutex> lock(permManagerLock); std::lock_guard lock{cached_permission_manager_lock};
for(auto permMgr : this->cachedPermissionManagers) auto manager = this->find_cached_permission_manager(server_id, cldbid);
if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)) { if(manager) return manager;
auto ptr = permMgr->manager.lock();
if(!ptr){
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
delete permMgr;
break;
}
permMgr->lastAccess = system_clock::now();
return ptr;
}
} }
#endif #endif
@ -298,15 +311,23 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
#ifndef DISABLE_CACHING #ifndef DISABLE_CACHING
this->permManagerLock.lock(); auto cache_entry = std::make_unique<CachedPermissionManager>();
auto entry = new CachedPermissionManager(); cache_entry->server_id = server_id;
entry->sid = server_id; cache_entry->instance = permission_manager;
entry->manager = permission_manager; cache_entry->instance_ref = permission_manager;
entry->ownLock = permission_manager; cache_entry->client_database_id = cldbid;
entry->cldbid = cldbid; cache_entry->last_access = system_clock::now();
entry->lastAccess = system_clock::now();
this->cachedPermissionManagers.push_back(entry); {
this->permManagerLock.unlock(); std::lock_guard cache_lock{this->cached_permission_manager_lock};
/* test if we might not got a second instance */
auto manager = this->find_cached_permission_manager(server_id, cldbid);
if(manager) return manager;
this->cached_permission_managers.push_back(std::move(cache_entry));
}
#endif #endif
return permission_manager; return permission_manager;
} }
@ -319,7 +340,7 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
auto server_id = server ? server->getServerId() : 0; auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) { for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND); std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
auto permission_data = permission::resolvePermissionData(update.permission); auto permission_data = permission::resolvePermissionData(update.permission);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value; auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
@ -386,7 +407,7 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::Virt
auto server_id = server ? server->getServerId() : 0; auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) { for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND); std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
auto permission_data = permission::resolvePermissionData(update.permission); auto permission_data = permission::resolvePermissionData(update.permission);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value; auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
@ -458,7 +479,7 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
auto server_id = server ? server->getServerId() : 0; auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) { for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND); std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
auto permission_data = permission::resolvePermissionData(update.permission); auto permission_data = permission::resolvePermissionData(update.permission);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value; auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
@ -524,7 +545,7 @@ void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::Vi
auto server_id = server ? server->getServerId() : 0; auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) { for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND); std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value; auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant; auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
@ -1058,6 +1079,15 @@ void DatabaseHelper::clearStartupCache(ts::ServerId sid) {
} }
} }
void DatabaseHelper::handleServerDelete(ServerId server_id) {
{
std::lock_guard pm_lock{this->cached_permission_manager_lock};
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
return entry->server_id == server_id;
}), this->cached_permission_managers.end());
}
}
//SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId` //SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`
//SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId` //SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`
struct StartupPermissionArgument { struct StartupPermissionArgument {
@ -1247,3 +1277,81 @@ bool DatabaseHelper::deletePlaylist(const std::shared_ptr<ts::server::VirtualSer
return true; return true;
} }
constexpr static auto kDBListQuery{R"(
SELECT `clients`.*, `properties`.`value` as `client_description` FROM (
SELECT
`clients_server`.`client_database_id`,
`clients_server`.`client_unique_id`,
`clients_server`.`client_nickname`,
`clients_server`.`client_ip`,
`clients_server`.`client_created`,
`clients_server`.`client_last_connected`,
`clients_server`.`client_total_connections`,
`clients`.`client_login_name` FROM `clients_server`
INNER JOIN `clients` ON `clients`.`client_database_id` = `clients_server`.`client_database_id`
WHERE `server_id` = :serverId LIMIT :offset, :limit
) AS `clients`
LEFT JOIN `properties` ON `properties`.serverId = :serverId AND `properties`.key = 'client_description' AND `properties`.`id` = `clients`.`client_database_id`
)"};
void DatabaseHelper::listDatabaseClients(
ServerId server_id,
const std::optional<int64_t>& offset,
const std::optional<int64_t>& limit,
void (* callback)(void *, const DatabaseClient &),
void *user_argument) {
DatabaseClient client;
size_t set_index{0};
auto sqlResult = sql::command{this->sql, kDBListQuery,
variable{":serverId", server_id},
variable{":offset", offset.has_value() ? *offset : 0},
variable{":limit", limit.has_value() ? *limit : -1}
}.query([&](int length, std::string* values, std::string* names) {
set_index++;
auto index{0};
try {
assert(names[index] == "client_database_id");
client.client_database_id = std::stoull(values[index++]);
assert(names[index] == "client_unique_id");
client.client_unique_id = values[index++];
assert(names[index] == "client_nickname");
client.client_nickname = values[index++];
assert(names[index] == "client_ip");
client.client_ip = values[index++];
assert(names[index] == "client_created");
client.client_last_connected = values[index++];
assert(names[index] == "client_last_connected");
client.client_created = values[index++];
assert(names[index] == "client_total_connections");
client.client_total_connections = values[index++];
assert(names[index] == "client_login_name");
client.client_login_name = values[index++];
assert(names[index] == "client_description");
client.client_description = values[index++];
assert(index == length);
} catch (std::exception& ex) {
logError(server_id, "Failed to parse client base properties at index {}: {}. Offset: {} Limit: {} Set: {}",
index - 1,
ex.what(),
offset.has_value() ? std::to_string(*offset) : "not given",
limit.has_value() ? std::to_string(*limit) : "not given",
set_index - 1
);
return;
}
callback(user_argument, client);
});
}

View File

@ -25,26 +25,6 @@ namespace ts::server {
uint32_t client_total_connections; uint32_t client_total_connections;
}; };
struct CachedPermissionManager {
ServerId sid;
ClientDbId cldbid;
std::weak_ptr<permission::v2::PermissionManager> manager;
std::shared_ptr<permission::v2::PermissionManager> ownLock;
std::chrono::time_point<std::chrono::system_clock> lastAccess;
};
struct CachedProperties {
ServerId sid;
ClientDbId cldbid;
std::weak_ptr<Properties> properties;
std::shared_ptr<Properties> ownLock;
std::chrono::time_point<std::chrono::system_clock> lastAccess;
};
/*
CREATE_TABLE("properties", "`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT", command_append_utf8);
CREATE_TABLE("permissions", "`serverId` INT NOT NULL, `type` INT, `id` INT, `channelId` INT, `permId` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` INT, `grant` INT", command_append_utf8);
*/
struct StartupPermissionEntry { struct StartupPermissionEntry {
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL; permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
uint64_t id = 0; uint64_t id = 0;
@ -64,11 +44,19 @@ namespace ts::server {
std::string value; std::string value;
}; };
struct StartupCacheEntry { struct DatabaseClient {
ServerId sid; ClientDbId client_database_id;
std::string client_unique_id;
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions; std::string client_nickname;
std::deque<std::unique_ptr<StartupPropertyEntry>> properties; std::string client_ip;
std::string client_created; /* seconds since epoch */
std::string client_last_connected; /* seconds since epoch */
std::string client_total_connections;
std::string client_login_name;
std::string client_description; /* optional and only given sometimes */
}; };
struct FastPropertyEntry { struct FastPropertyEntry {
@ -76,6 +64,8 @@ namespace ts::server {
std::string value; std::string value;
}; };
struct CachedPermissionManager;
struct StartupCacheEntry;
class DatabaseHelper { class DatabaseHelper {
public: public:
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */); static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
@ -88,6 +78,15 @@ namespace ts::server {
size_t cacheBinarySize(); size_t cacheBinarySize();
void clearStartupCache(ServerId sid = 0); void clearStartupCache(ServerId sid = 0);
void handleServerDelete(ServerId /* server id */);
void listDatabaseClients(
ServerId /* server id */,
const std::optional<int64_t>& offset,
const std::optional<int64_t>& limit,
void(* /* callback */)(void* /* user argument */, const DatabaseClient& /* client */),
void* /* user argument */);
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId); void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId); bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&); std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
@ -125,9 +124,11 @@ namespace ts::server {
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries; std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
sql::SqlManager* sql = nullptr; sql::SqlManager* sql = nullptr;
threads::Mutex permManagerLock;
std::deque<CachedPermissionManager*> cachedPermissionManagers; threads::Mutex cached_permission_manager_lock;
threads::Mutex propsLock; std::deque<std::unique_ptr<CachedPermissionManager>> cached_permission_managers;
std::deque<CachedProperties*> cachedProperties;
/* Attention: cached_permission_manager_lock should be locked! */
[[nodiscard]] inline std::shared_ptr<permission::v2::PermissionManager> find_cached_permission_manager(ServerId /* server id */, ClientDbId /* client id */);
}; };
} }

View File

@ -163,6 +163,7 @@ struct SnapshotPermissionEntry {
} }
}; };
#if 0
std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(shared_ptr<VirtualServer> old, std::string host, std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(shared_ptr<VirtualServer> old, std::string host,
uint16_t port, const ts::Command &arguments, uint16_t port, const ts::Command &arguments,
std::string &error) { std::string &error) {
@ -692,6 +693,7 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
server->ensureValidDefaultGroups(); server->ensureValidDefaultGroups();
return server; return server;
} }
#endif
struct CommandTuple { struct CommandTuple {
Command& cmd; Command& cmd;
@ -771,8 +773,8 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
int index = 0; int index = 0;
if(version == -1) version = 2; //Auto versioned if(version == -1) version = 3; //Auto versioned
if(version < 0 || version > 2) { if(version < 0 || version > 3) {
error = "Invalid snapshot version!"; error = "Invalid snapshot version!";
return false; return false;
} }
@ -817,44 +819,26 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
//Clients //Clients
{ {
cmd[index]["begin_clients"] = ""; cmd[index]["begin_clients"] = "";
CommandTuple parm{cmd, index, version};
auto res = sql::command(server->getSql(), "SELECT `cldbid`, `clientUid`, `firstConnect`, `lastName` FROM `clients` WHERE `serverId` = :sid",
variable{":sid", server->getServerId()}
).query([&](CommandTuple* commandIndex, int length, char** value, char** name) {
ClientDbId cldbid = 0;
int64_t clientCreated = 0;
string clientUid;
string lastName;
string description; //TODO description
for(int idx = 0; idx < length; idx++) { struct CallbackArgument {
try { Command& command;
if(strcmp(name[idx], "cldbid") == 0) int& index;
cldbid = value[idx] && strlen(value[idx]) > 0 ? stoul(value[idx]) : 0; int version;
else if(strcmp(name[idx], "clientUid") == 0) };
clientUid = value[idx];
else if(strcmp(name[idx], "firstConnect") == 0)
clientCreated = value[idx] && strlen(value[idx]) > 0 ? stoll(value[idx]) : 0L;
else if(strcmp(name[idx], "lastName") == 0)
lastName = value[idx];
} catch (std::exception& ex) {
logError(0, "Failed to write snapshot client (Skipping it)! Message: {} @ {} => {}", ex.what(), name[idx], value[idx]);
return 0;
}
}
CallbackArgument callback_argument{cmd, index, version};
this->handle->databaseHelper()->listDatabaseClients(server->getServerId(), std::nullopt, std::nullopt, [](void* ptr_argument, const DatabaseClient& client) {
auto argument = (CallbackArgument*) ptr_argument;
commandIndex->cmd[commandIndex->index]["client_id"] = cldbid; argument->command[argument->index]["client_id"] = client.client_database_id;
commandIndex->cmd[commandIndex->index]["client_unique_id"] = clientUid; argument->command[argument->index]["client_unique_id"] = client.client_unique_id;
commandIndex->cmd[commandIndex->index]["client_nickname"] = lastName; argument->command[argument->index]["client_nickname"] = client.client_nickname;
commandIndex->cmd[commandIndex->index]["client_created"] = clientCreated; argument->command[argument->index]["client_created"] = client.client_created;
commandIndex->cmd[commandIndex->index]["client_description"] = description; argument->command[argument->index]["client_description"] = client.client_description;
if(commandIndex->version == 0) if(argument->version == 0)
commandIndex->cmd[commandIndex->index]["client_unread_messages"] = 0; argument->command[argument->index]["client_unread_messages"] = 0;
commandIndex->index++; argument->index++;
return 0; }, &callback_argument);
}, &parm);
LOG_SQL_CMD(res);
cmd[index++]["end_clients"] = ""; cmd[index++]["end_clients"] = "";
} }

View File

@ -110,6 +110,9 @@ bool VirtualServerManager::initialize(bool autostart) {
if(id == 0) { if(id == 0) {
logError(LOG_INSTANCE, "Failed to load virtual server from database. Server id is zero!"); logError(LOG_INSTANCE, "Failed to load virtual server from database. Server id is zero!");
return 0; return 0;
} else if(id == 0xFFFF) {
/* snapshot server */
return 0;
} }
if(host.empty()) { if(host.empty()) {
@ -341,7 +344,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
if(!sid_success) if(!sid_success)
return nullptr; return nullptr;
this->delete_server_in_db(serverId, true); /* just to ensure */ this->delete_server_in_db(serverId, false); /* just to ensure */
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", hosts}, variable{":port", port}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"}); sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", hosts}, variable{":port", port}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
auto prop_copy = sql::command(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :target_sid AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `type` = :type AND `id` = 0 AND `serverId` = 0;", auto prop_copy = sql::command(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :target_sid AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `type` = :type AND `id` = 0 AND `serverId` = 0;",
@ -419,7 +422,8 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
} }
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>(); this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
this->delete_server_in_db(server->serverId, true); this->delete_server_in_db(server->serverId, false);
this->handle->databaseHelper()->handleServerDelete(server->serverId);
if(auto file_vs = file::server()->find_virtual_server(server->getServerId()); file_vs) { if(auto file_vs = file::server()->find_virtual_server(server->getServerId()); file_vs) {
using ErrorType = file::filesystem::ServerCommandErrorType; using ErrorType = file::filesystem::ServerCommandErrorType;
@ -517,7 +521,7 @@ if(!result) { \
} }
#define execute_change(table, column) \ #define execute_change(table, column) \
result = sql::command(this->handle->getSql(), "UPDATE TABLE `" table "` SET `" column "` = :nsid WHERE `" column "` = :osid;", \ result = sql::command(this->handle->getSql(), "UPDATE `" table "` SET `" column "` = :nsid WHERE `" column "` = :osid;", \
variable{":osid", old_id}, variable{":nsid", new_id}).execute(); \ variable{":osid", old_id}, variable{":nsid", new_id}).execute(); \
if(!result) { \ if(!result) { \
logWarning(LOG_INSTANCE, "Failed to execute server id change on table {} (column {}): {}", table, column, result.fmtStr()); \ logWarning(LOG_INSTANCE, "Failed to execute server id change on table {} (column {}): {}", table, column, result.fmtStr()); \

View File

@ -64,12 +64,9 @@ namespace ts::server {
void executeAutostart(); void executeAutostart();
void shutdownAll(const std::string&); void shutdownAll(const std::string&);
//Dotn use shared_ptr references to keep sure that they be hold in memory //Don't use shared_ptr references to keep sure that they be hold in memory
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error); bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &); SnapshotDeployResult deploy_snapshot(std::string& /* error */, std::shared_ptr<VirtualServer>& /* target server */, const command_parser& /* source */);
//Dotn use shared_ptr references to keep sure that they be hold in memory
SnapshotDeployResult deploy_snapshot(std::string& /* error */, std::shared_ptr<VirtualServer> /* target server */, const command_parser& /* source */);
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; } udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }

View File

@ -529,6 +529,8 @@ int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column)
//assert(type != 0xFF); //assert(type != 0xFF);
assert(channelId != 0); assert(channelId != 0);
if(channelId == 0)
return 0;
auto channel = std::make_shared<ServerChannel>(parentId, channelId); auto channel = std::make_shared<ServerChannel>(parentId, channelId);
auto server = this->server.lock(); auto server = this->server.lock();

View File

@ -798,6 +798,30 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
command_result result; command_result result;
try { try {
result.reset(this->handleCommand(cmd)); result.reset(this->handleCommand(cmd));
} catch(command_value_cast_failed& ex){
auto message = ex.key() + " at " + std::to_string(ex.index()) + " could not be casted to " + ex.target_type().name();
if(disconnectOnFail) {
this->disconnect(message);
return false;
} else {
result.reset(command_result{error::parameter_convert, message});
}
} catch(command_bulk_index_out_of_bounds_exception& ex){
auto message = "missing bulk for index " + std::to_string(ex.index());
if(disconnectOnFail) {
this->disconnect(message);
return false;
} else {
result.reset(command_result{error::parameter_invalid_count, message});
}
} catch(command_value_missing_exception& ex){
auto message = "missing value for " + ex.key() + (ex.bulk_index() == std::string::npos ? "" : " at " + std::to_string(ex.bulk_index()));
if(disconnectOnFail) {
this->disconnect(message);
return false;
} else {
result.reset(command_result{error::parameter_missing, message});
}
} catch(invalid_argument& ex){ } catch(invalid_argument& ex){
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what()); logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what());
if(disconnectOnFail) { if(disconnectOnFail) {

View File

@ -647,7 +647,7 @@ bool ConnectedClient::handle_text_command(
//send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id())); //send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id()));
} }
return true; return true;
} else if(TARG(0, "ping")) { } else if(TARG(0, "rtt")) {
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock()); auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false; if(!vc) return false;
@ -681,6 +681,16 @@ bool ConnectedClient::handle_text_command(
return true; return true;
} }
return true; return true;
} else if(TARG(0, "ping")) {
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false;
auto ping_handler = vc->getConnection()->ping_handler();
send_message(this->ref(), "Ping: " + std::to_string(ping_handler.current_ping().count()));
auto last_response_ms = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::system_clock::now() - ping_handler.last_ping_response()).count();
send_message(this->ref(), "Last ping response: " + std::to_string(last_response_ms));
return true;
} else if(TARG(0, "disconnect")) { } else if(TARG(0, "disconnect")) {
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock()); auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false; if(!vc) return false;

View File

@ -35,7 +35,7 @@ command_result SpeakingClient::handleCommandHandshakeBegin(Command& cmd) { //If
if(authenticationMethod == IdentityType::TEAMSPEAK) { if(authenticationMethod == IdentityType::TEAMSPEAK) {
this->handshake.identityType = IdentityType::TEAMSPEAK; this->handshake.identityType = IdentityType::TEAMSPEAK;
auto identity = base64::decode(cmd["publicKey"]); auto identity = base64::decode(cmd["publicKey"].string());
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(cmd["publicKey"].string())); this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(cmd["publicKey"].string()));
this->handshake.identityKey = shared_ptr<ecc_key>(new ecc_key{}, free_ecc); this->handshake.identityKey = shared_ptr<ecc_key>(new ecc_key{}, free_ecc);
@ -144,7 +144,7 @@ command_result SpeakingClient::handleCommandHandshakeIdentityProof(Command& cmd)
this->properties()[property::CLIENT_TEAFORO_NAME] = (*this->handshake.identityData)["user_name"].asString(); this->properties()[property::CLIENT_TEAFORO_NAME] = (*this->handshake.identityData)["user_name"].asString();
this->handshake.state = HandshakeState::SUCCEEDED; this->handshake.state = HandshakeState::SUCCEEDED;
} else if(this->handshake.identityType == IdentityType::TEAMSPEAK) { } else if(this->handshake.identityType == IdentityType::TEAMSPEAK) {
auto proof = base64::decode(cmd["proof"]); auto proof = base64::decode(cmd["proof"].string());
int result; int result;
if(ecc_verify_hash((u_char*) proof.data(), proof.length(), (u_char*) this->handshake.proof_message.data(), this->handshake.proof_message.length(), &result, this->handshake.identityKey.get()) != CRYPT_OK) if(ecc_verify_hash((u_char*) proof.data(), proof.length(), (u_char*) this->handshake.proof_message.data(), this->handshake.proof_message.length(), &result, this->handshake.identityKey.get()) != CRYPT_OK)

View File

@ -367,23 +367,6 @@ command_result ConnectedClient::handleCommandClientChatClosed(Command &cmd) {
return command_result{error::ok}; return command_result{error::ok};
} }
constexpr static auto kDBListQuery{R"(
SELECT `clients`.*, `properties`.`value` as `client_description` FROM (
SELECT
`clients_server`.`client_database_id`,
`clients_server`.`client_unique_id`,
`clients_server`.`client_nickname`,
`clients_server`.`client_ip`,
`clients_server`.`client_created`,
`clients_server`.`client_last_connected`,
`clients_server`.`client_total_connections`,
`clients`.`client_login_name` FROM `clients_server`
INNER JOIN `clients` ON `clients`.`client_database_id` = `clients_server`.`client_database_id`
WHERE `server_id` = :serverId LIMIT :offset, :limit
) AS `clients`
LEFT JOIN `properties` ON `properties`.serverId = :serverId AND `properties`.key = 'client_description' AND `properties`.`id` = `clients`.`client_database_id`
)"};
command_result ConnectedClient::handleCommandClientDbList(Command &cmd) { command_result ConnectedClient::handleCommandClientDbList(Command &cmd) {
CMD_REQ_SERVER; CMD_REQ_SERVER;
CMD_RESET_IDLE; CMD_RESET_IDLE;
@ -398,59 +381,32 @@ command_result ConnectedClient::handleCommandClientDbList(Command &cmd) {
ts::command_builder result{this->notify_response_command("notifyclientdblist")}; ts::command_builder result{this->notify_response_command("notifyclientdblist")};
result.reserve_bulks(limit); result.reserve_bulks(limit);
size_t command_index{0}, set_index{0}; struct CallbackArgument {
ts::command_builder& result;
bool show_ip{false};
size_t command_index{0};
};
auto show_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); CallbackArgument callback_argument{result};
callback_argument.show_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
serverInstance->databaseHelper()->listDatabaseClients(this->getServerId(), { offset }, { limit }, [](void* ptr_data, const DatabaseClient& client) {
auto argument = (CallbackArgument*) ptr_data;
auto sqlResult = sql::command{this->server->getSql(), kDBListQuery, variable{":serverId", this->getServerId()}, variable{":offset", offset}, variable{":limit", limit}} auto bulk = argument->result.bulk(argument->command_index++);
.query([&](int length, std::string* values, std::string* names) {
set_index++;
auto bulk = result.bulk(command_index++);
bulk.reserve(300); bulk.reserve(300);
auto index{0}; bulk.put_unchecked("cldbid", client.client_database_id);
try { bulk.put_unchecked("client_unique_identifier", client.client_unique_id);
assert(names[index] == "client_database_id"); bulk.put_unchecked("client_nickname", client.client_nickname);
bulk.put_unchecked("cldbid", values[index++]); bulk.put_unchecked("client_lastip", argument->show_ip ? client.client_ip : "hidden");
bulk.put_unchecked("client_lastconnected", client.client_last_connected);
bulk.put_unchecked("client_created", client.client_created);
bulk.put_unchecked("client_totalconnections", client.client_total_connections);
bulk.put_unchecked("client_login_name", client.client_login_name);
bulk.put_unchecked("client_description", client.client_description);
}, &callback_argument);
assert(names[index] == "client_unique_id"); if (callback_argument.command_index == 0)
bulk.put_unchecked("client_unique_identifier", values[index++]);
assert(names[index] == "client_nickname");
bulk.put_unchecked("client_nickname", values[index++]);
assert(names[index] == "client_ip");
bulk.put_unchecked("client_lastip", show_ip ? values[index++] : "hidden");
assert(names[index] == "client_created");
bulk.put_unchecked("client_lastconnected", values[index++]);
assert(names[index] == "client_last_connected");
bulk.put_unchecked("client_created", values[index++]);
assert(names[index] == "client_total_connections");
bulk.put_unchecked("client_totalconnections", values[index++]);
assert(names[index] == "client_login_name");
bulk.put_unchecked("client_login_name", values[index++]);
assert(names[index] == "client_description");
bulk.put_unchecked("client_description", values[index++]);
assert(index == length);
} catch (std::exception& ex) {
command_index--;
logError(this->getServerId(), "Failed to parse client base properties at index {}: {}. Offset: {} Limit: {} Set: {}",
index - 1,
ex.what(),
offset,
limit,
set_index - 1
);
}
});
if (command_index == 0)
return command_result{error::database_empty_result}; return command_result{error::database_empty_result};
if (cmd.hasParm("count")) { if (cmd.hasParm("count")) {

View File

@ -1,6 +1,7 @@
#include "Properties.h" #include "Properties.h"
#include "query/Command.h" #include "query/Command.h"
#include <algorithm> #include <algorithm>
#include <zstd.h>
#include <src/server/QueryServer.h> #include <src/server/QueryServer.h>
#include <src/VirtualServerManager.h> #include <src/VirtualServerManager.h>
#include <src/InstanceHandler.h> #include <src/InstanceHandler.h>
@ -834,6 +835,7 @@ command_result QueryClient::handleCommandBindingList(Command& cmd) {
//TODO with mapping! //TODO with mapping!
command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) { command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
#if 0
CMD_RESET_IDLE; CMD_RESET_IDLE;
auto start = system_clock::now(); auto start = system_clock::now();
@ -903,20 +905,23 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
res["time"] = duration_cast<milliseconds>(end - start).count(); res["time"] = duration_cast<milliseconds>(end - start).count();
this->sendCommand(res); this->sendCommand(res);
return command_result{error::ok}; return command_result{error::ok};
#else
return command_result{error::command_not_found};
#endif
} }
command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) { command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) {
CMD_RESET_IDLE; CMD_RESET_IDLE;
if(this->server) { if(this->server) {
return command_result{error::not_implemented};
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
} else { } else {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1); ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
} }
std::string error{}; std::string error{};
auto result = serverInstance->getVoiceServerManager()->deploy_snapshot(error, this->server, command); auto server = this->server;
auto result = serverInstance->getVoiceServerManager()->deploy_snapshot(error, server, command);
using SnapshotDeployResult = VirtualServerManager::SnapshotDeployResult; using SnapshotDeployResult = VirtualServerManager::SnapshotDeployResult;
switch (result) { switch (result) {
@ -935,6 +940,12 @@ command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::comma
case SnapshotDeployResult::CUSTOM_ERROR: case SnapshotDeployResult::CUSTOM_ERROR:
return command_result{error::vs_critical, error}; return command_result{error::vs_critical, error};
} }
ts::command_builder notify{""};
notify.put_unchecked(0, "virtualserver_port", server->properties()[property::VIRTUALSERVER_PORT].value());
notify.put_unchecked(0, "sid", server->getServerId());
this->sendCommand(notify, false);
return command_result{error::ok}; return command_result{error::ok};
} }
@ -943,22 +954,50 @@ command_result QueryClient::handleCommandServerSnapshotCreate(Command& cmd) {
CMD_RESET_IDLE; CMD_RESET_IDLE;
CMD_REQ_SERVER; CMD_REQ_SERVER;
Command result(""); Command snapshot_command("");
string error; string error;
int version = cmd[0].has("version") ? cmd[0]["version"] : -1; int version = cmd[0].has("version") ? cmd[0]["version"] : -1;
if(version == -1 && (cmd.hasParm("lagacy") || cmd.hasParm("legacy"))) if(version == -1 && (cmd.hasParm("lagacy") || cmd.hasParm("legacy"))) {
version = 0; version = 0;
if(!serverInstance->getVoiceServerManager()->createServerSnapshot(result, this->server, version, error)) }
return command_result{error::vs_critical, error};
string data = result.build(); if(!serverInstance->getVoiceServerManager()->createServerSnapshot(snapshot_command, this->server, version, error)) {
return command_result{error::vs_critical, error};
}
if(version == -1 || version >= 3) {
auto build_version = snapshot_command[0]["snapshot_version"].as<int>();
snapshot_command.pop_bulk();
auto snapshot_data = snapshot_command.build();
auto max_compressed_size = ZSTD_compressBound(snapshot_data.size());
if(ZSTD_isError(max_compressed_size)) {
return command_result{error::vs_critical, "failed to calculate compressed size: " + std::string{ZSTD_getErrorName(max_compressed_size)}};
}
std::string buffer{};
buffer.resize(max_compressed_size);
auto compressed_size = ZSTD_compress(buffer.data(), buffer.size(), snapshot_data.data(), snapshot_data.length(), 1);
if(ZSTD_isError(compressed_size)) {
return command_result{error::vs_critical, "failed to compressed snapshot: " + std::string{ZSTD_getErrorName(compressed_size)}};
}
ts::command_builder result{""};
result.bulk(0).reserve(100 + compressed_size * 4/3);
result.put_unchecked(0, "snapshot_version", build_version);
result.put_unchecked(0, "data", base64::encode(std::string_view{buffer.data(), compressed_size}));
this->sendCommand(result, false);
} else {
auto data = snapshot_command.build();
auto buildHash = base64::encode(digest::sha1(data)); auto buildHash = base64::encode(digest::sha1(data));
result.push_bulk_front(); snapshot_command.push_bulk_front();
result[0]["hash"] = buildHash; snapshot_command[0]["hash"] = buildHash;
this->sendCommand(result); this->sendCommand(snapshot_command);
}
return command_result{error::ok}; return command_result{error::ok};
} }

View File

@ -71,7 +71,7 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
std::unique_lock state_lock{client->state_lock}; std::unique_lock state_lock{client->state_lock};
if(client->connectionState() == ConnectionState::CONNECTED) { /* we've a reconnect */ if(client->connectionState() == ConnectionState::CONNECTED) { /* we've a reconnect */
auto lastPingResponse = this->connection->ping_handler().last_ping_response(); auto lastPingResponse = std::max(this->connection->ping_handler().last_ping_response(), this->connection->ping_handler().last_command_acknowledged());
if(std::chrono::system_clock::now() - lastPingResponse < std::chrono::seconds(5)) { if(std::chrono::system_clock::now() - lastPingResponse < std::chrono::seconds(5)) {
logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt", logMessage(this->connection->virtual_server_id(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt",
this->connection->log_prefix(), this->connection->log_prefix(),
@ -162,6 +162,11 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
this->seed_server = std::string{beta, server_seed_length}; this->seed_server = std::string{beta, server_seed_length};
} }
auto server_public_key = client->getServer()->publicServerKey();
if(server_public_key.empty()) {
return ts::command_result{error::vs_critical, "failed to export server public key"};
}
if(this->new_protocol) { if(this->new_protocol) {
//Pre setup //Pre setup
//Generate chain //Generate chain
@ -193,7 +198,7 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
answer.put_unchecked(0, "time", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count()); answer.put_unchecked(0, "time", std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count());
answer.put_unchecked(0, "l", base64::encode(crypto_chain)); answer.put_unchecked(0, "l", base64::encode(crypto_chain));
answer.put_unchecked(0, "beta", base64::encode(this->seed_server)); answer.put_unchecked(0, "beta", base64::encode(this->seed_server));
answer.put_unchecked(0, "omega", client->getServer()->publicServerKey()); answer.put_unchecked(0, "omega", server_public_key);
answer.put_unchecked(0, "proof", base64::encode((const char*) sign_buffer, sign_buffer_size)); answer.put_unchecked(0, "proof", base64::encode((const char*) sign_buffer, sign_buffer_size));
answer.put_unchecked(0, "tvd", ""); answer.put_unchecked(0, "tvd", "");
answer.put_unchecked(0, "root", base64::encode((char*) this->chain_data->public_key, 32)); answer.put_unchecked(0, "root", base64::encode((char*) this->chain_data->public_key, 32));
@ -206,11 +211,6 @@ CryptSetupHandler::CommandResult CryptSetupHandler::handleCommandClientInitIv(co
} else { } else {
debugMessage(this->connection->virtual_server_id(), "{} Got non client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_, this->client_protocol_time_); debugMessage(this->connection->virtual_server_id(), "{} Got non client 3.1 protocol with build timestamp {}", this->connection->log_prefix(), this->client_protocol_time_, this->client_protocol_time_);
auto server_public_key = client->getServer()->publicServerKey();
if(server_public_key.empty()) {
return ts::command_result{error::vs_critical, "failed to export server public key"};
}
{ {
ts::command_builder answer{"initivexpand"}; ts::command_builder answer{"initivexpand"};
answer.put_unchecked(0, "alpha", base64::encode(this->seed_client)); answer.put_unchecked(0, "alpha", base64::encode(this->seed_client));

View File

@ -4,12 +4,9 @@
#include "PacketEncoder.h" #include "PacketEncoder.h"
#include <protocol/buffers.h> #include <protocol/buffers.h>
#include <protocol/AcknowledgeManager.h>
#include <protocol/CompressionHandler.h> #include <protocol/CompressionHandler.h>
#include <protocol/CryptHandler.cpp> #include <protocol/CryptHandler.cpp>
#include "../../ConnectionStatistics.h"
using namespace ts; using namespace ts;
using namespace ts::server::server::udp; using namespace ts::server::server::udp;
@ -164,9 +161,9 @@ void PacketEncoder::send_command(const std::string_view &command, bool low, std:
packets_tail = &packet->next; packets_tail = &packet->next;
} }
{ {
std::lock_guard id_lock{this->packet_id_mutex}; std::lock_guard id_lock{this->packet_id_mutex};
{
uint32_t full_id; uint32_t full_id;
auto head = packets_head; auto head = packets_head;
while(head) { while(head) {
@ -174,24 +171,30 @@ void PacketEncoder::send_command(const std::string_view &command, bool low, std:
head->set_packet_id(full_id & 0xFFFFU); head->set_packet_id(full_id & 0xFFFFU);
head->generation = full_id >> 16U; head->generation = full_id >> 16U;
/* loss stats (In order required so we're using the this->packet_id_mutex) */
this->packet_statistics_->send_command(head->packet_type(), full_id);
head = head->next; head = head->next;
} }
} }
}
packets_head->type_and_flags |= head_pflags; packets_head->type_and_flags |= head_pflags;
/* general stats */ /* general stats */
{
auto head = packets_head; auto head = packets_head;
while(head) { while(head) {
this->callback_connection_stats(this->callback_data, StatisticsCategory::COMMAND, head->packet_length() + 96); /* 96 for the UDP overhead */ this->callback_connection_stats(this->callback_data, StatisticsCategory::COMMAND, head->packet_length() + 96); /* 96 for the UDP overhead */
head = head->next; head = head->next;
} }
}
/* loss stats */ /* ack handler */
{ {
auto head = packets_head; auto head = packets_head;
while(head) { while(head) {
auto full_packet_id = (uint32_t) (head->generation << 16U) | head->packet_id(); auto full_packet_id = (uint32_t) (head->generation << 16U) | head->packet_id();
this->packet_statistics_->send_command(head->packet_type(), full_packet_id);
/* increase a reference for the ack handler */ /* increase a reference for the ack handler */
head->ref(); head->ref();
@ -230,9 +233,18 @@ void PacketEncoder::encrypt_pending_packets() {
return; return;
auto packet = packets_head; auto packet = packets_head;
auto packet_tail = packet;
while(packet) { while(packet) {
this->prepare_outgoing_packet(packet); this->prepare_outgoing_packet(packet);
packet = packet->next; packet = packet->next;
if(packet)
packet_tail = packet;
}
{
std::lock_guard wlock{this->write_queue_mutex};
*this->resend_queue_tail = packets_head;
this->resend_queue_tail = &packet_tail->next;
} }
} }
@ -267,7 +279,7 @@ bool PacketEncoder::prepare_outgoing_packet(ts::protocol::OutgoingServerPacket *
} }
PacketEncoder::BufferPopResult PacketEncoder::pop_write_buffer(protocol::OutgoingServerPacket *&result) { PacketEncoder::BufferPopResult PacketEncoder::pop_write_buffer(protocol::OutgoingServerPacket *&result) {
bool need_prepare_packet{false}, more_packets{false}; bool need_prepare_packet{false}, more_packets;
{ {
std::lock_guard wlock{this->write_queue_mutex}; std::lock_guard wlock{this->write_queue_mutex};
if(this->resend_queue_head) { if(this->resend_queue_head) {

View File

@ -18,13 +18,14 @@ namespace ts::server::server::udp {
[[nodiscard]] inline std::chrono::milliseconds current_ping() const { return this->current_ping_; } [[nodiscard]] inline std::chrono::milliseconds current_ping() const { return this->current_ping_; }
[[nodiscard]] inline std::chrono::system_clock::time_point last_ping_response() const { return this->last_response_; } [[nodiscard]] inline std::chrono::system_clock::time_point last_ping_response() const { return this->last_response_; }
[[nodiscard]] inline std::chrono::system_clock::time_point last_command_acknowledged() const { return this->last_command_acknowledge_; }
void* callback_argument{nullptr}; void* callback_argument{nullptr};
callback_send_ping_t callback_send_ping{nullptr}; callback_send_ping_t callback_send_ping{nullptr};
callback_send_recovery_command_t callback_send_recovery_command{nullptr}; callback_send_recovery_command_t callback_send_recovery_command{nullptr};
callback_time_outed_t callback_time_outed{nullptr}; callback_time_outed_t callback_time_outed{nullptr};
private: private:
constexpr static std::chrono::milliseconds kPingRequestInterval{2500}; constexpr static std::chrono::milliseconds kPingRequestInterval{1000};
constexpr static std::chrono::milliseconds kPingTimeout{15 * 1000}; constexpr static std::chrono::milliseconds kPingTimeout{15 * 1000};
constexpr static std::chrono::milliseconds kRecoveryRequestInterval{1000}; constexpr static std::chrono::milliseconds kRecoveryRequestInterval{1000};

View File

@ -170,11 +170,18 @@ bool SqlDataManager::initialize(std::string& error) {
} else { } else {
res = sql::command(this->sql(), "UPDATE `general` SET `value`= :value WHERE `key`= :key;", variable{":key", "lock_test"}, variable{":value", "TeaSpeak created by WolverinDEV <3"}).execute(); res = sql::command(this->sql(), "UPDATE `general` SET `value`= :value WHERE `key`= :key;", variable{":key", "lock_test"}, variable{":value", "TeaSpeak created by WolverinDEV <3"}).execute();
} }
if(!res) { if(!res) {
if(res.msg().find("database is locked") != string::npos) error = "database is locked"; if(res.msg().find("database is locked") != string::npos) error = "database is locked";
else error = "Failed to execute lock test! Command result: " + res.fmtStr(); else error = "Failed to execute lock test! Command result: " + res.fmtStr();
return false; return false;
} }
res = sql::command{this->sql(), "DELETE FROM `general` WHERE `key` = :key ORDER BY `key` LIMIT -1 OFFSET 1;", variable{":key", "lock_test"}}.execute();
if(!res) {
error = res.fmtStr();
return false;
}
} }
if(!this->update_permissions(error)) { if(!this->update_permissions(error)) {
@ -500,16 +507,17 @@ bool SqlDataManager::update_database(std::string &error) {
} }
case 13: { case 13: {
constexpr static std::array<std::string_view, 7> kUpdateCommands{ constexpr static std::array<std::string_view, 8> kUpdateCommands{
"CREATE INDEX `idx_properties_serverid_id_value` ON `properties` (`serverId`, `id`, `key`);", "CREATE INDEX `idx_properties_serverid_id_value` ON `properties` (`serverId`, `id`, `key`);",
"CREATE INDEX `idx_properties_serverid_id_type` ON `properties` (`serverId`, `id`, `type`);",
"CREATE TABLE `groups_v2` (`serverId` INT NOT NULL, `groupId` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, `target` INT, `type` INT, `displayName` VARCHAR(128), `original_group_id` INTEGER);", "CREATE TABLE `groups_v2` (`serverId` INT NOT NULL, `groupId` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `target` INT, `type` INT, `displayName` VARCHAR(128), `original_group_id` INTEGER);",
"INSERT INTO `groups_v2` (`serverId`, `groupId`, `target`, `type`, `displayName`) SELECT `serverId`, `groupId`, `target`, `type`, `displayName` FROM `groups`;", "INSERT INTO `groups_v2` (`serverId`, `groupId`, `target`, `type`, `displayName`) SELECT `serverId`, `groupId`, `target`, `type`, `displayName` FROM `groups`;",
"DROP TABLE `groups`;", "DROP TABLE `groups`;",
"ALTER TABLE `groups_v2` RENAME TO groups;", "ALTER TABLE `groups_v2` RENAME TO groups;",
"CREATE INDEX `idx_groups_serverId_groupId` ON `groups` (`server_id`);", "CREATE INDEX `idx_groups_serverId_groupId` ON `groups` (`serverId`);",
"CREATE INDEX `idx_groups_serverid` ON `groups` (`server_id`, `groupId`);" "CREATE INDEX `idx_groups_serverid` ON `groups` (`serverId`, `groupId`);"
}; };
if(!execute_commands(this->sql(), error, kUpdateCommands)) if(!execute_commands(this->sql(), error, kUpdateCommands))

View File

@ -30,7 +30,7 @@ bool TokenManager::loadTokens() {
} }
int TokenManager::loadTokenFromDb(int length, char **values, char **columns) { int TokenManager::loadTokenFromDb(int length, char **values, char **columns) {
std::string token, description = ""; std::string token, description;
auto type = static_cast<TokenType>(0xFF); auto type = static_cast<TokenType>(0xFF);
GroupId group = 0; GroupId group = 0;
ChannelId chId = 0; ChannelId chId = 0;

View File

@ -16,17 +16,15 @@
using namespace ts; using namespace ts;
using namespace ts::server; using namespace ts::server;
using SnapshotType = ts::server::snapshots::type;
using SnapshotVersion = ts::server::snapshots::version_t;
constexpr static ServerId kSnapshotServerId{0xFFFF}; constexpr static ServerId kSnapshotServerId{0xFFFF};
VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot(std::string &error, std::shared_ptr<VirtualServer> server, const command_parser &command) { VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot(std::string &error, std::shared_ptr<VirtualServer>& server, const command_parser &command) {
if(!server) { if(!server) {
auto instances = this->serverInstances(); auto instance_count = this->serverInstances().size();
if(config::server::max_virtual_server != -1 && instances.size() > config::server::max_virtual_server) if(config::server::max_virtual_server != -1 && instance_count > config::server::max_virtual_server)
return SnapshotDeployResult::REACHED_CONFIG_SERVER_LIMIT; return SnapshotDeployResult::REACHED_CONFIG_SERVER_LIMIT;
if(instances.size() >= 65534) if(instance_count >= 65534)
return SnapshotDeployResult::REACHED_SOFTWARE_SERVER_LIMIT; return SnapshotDeployResult::REACHED_SOFTWARE_SERVER_LIMIT;
} }
@ -36,7 +34,7 @@ VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot
return SnapshotDeployResult::REACHED_SERVER_ID_LIMIT; return SnapshotDeployResult::REACHED_SERVER_ID_LIMIT;
} }
this->delete_server_in_db(kSnapshotServerId, true); this->delete_server_in_db(kSnapshotServerId, false);
auto result = sql::command{this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)"} auto result = sql::command{this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)"}
.value(":sid", kSnapshotServerId) .value(":sid", kSnapshotServerId)
.value(":host", server ? server->properties()[property::VIRTUALSERVER_HOST].value() : config::binding::DefaultVoiceHost) .value(":host", server ? server->properties()[property::VIRTUALSERVER_HOST].value() : config::binding::DefaultVoiceHost)
@ -53,7 +51,7 @@ VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot
} }
if(!server) { if(!server) {
this->delete_server_in_db(target_server_id, true); this->delete_server_in_db(target_server_id, false);
this->change_server_id_in_db(kSnapshotServerId, target_server_id); this->change_server_id_in_db(kSnapshotServerId, target_server_id);
} else { } else {
this->deleteServer(server); this->deleteServer(server);
@ -66,6 +64,7 @@ VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot
//FIXME error handling //FIXME error handling
} }
server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
{ {
threads::MutexLock l(this->instanceLock); threads::MutexLock l(this->instanceLock);
this->instances.push_back(server); this->instances.push_back(server);
@ -74,59 +73,15 @@ VirtualServerManager::SnapshotDeployResult VirtualServerManager::deploy_snapshot
return SnapshotDeployResult::SUCCESS; return SnapshotDeployResult::SUCCESS;
} }
#if 0
bool VirtualServerManager::deploy_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &command) {
auto result = this->try_deploy_snapshot(error, server_id, command);
if(!result) this->delete_server_in_db(server_id, true);
return result;
}
#endif
bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId target_server_id, ts::ServerId logging_server_id, const ts::command_parser &command) { bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId target_server_id, ts::ServerId logging_server_id, const ts::command_parser &command) {
snapshots::snapshot_data snapshot_data{}; snapshots::snapshot_data snapshot_data{};
if(!snapshots::parse_snapshot(snapshot_data, error, logging_server_id, command)) if(!snapshots::parse_snapshot(snapshot_data, error, logging_server_id, command))
return false; return false;
std::map<ClientDbId, ClientDbId> client_id_mapping{}; snapshots::snapshot_mappings mappings{};
std::map<ChannelId, ChannelId> channel_id_mapping{};
std::map<ChannelId, ChannelId> channel_group_id_mapping{};
std::map<ChannelId, ChannelId> server_group_id_mapping{};
std::map<PlaylistId, PlaylistId> playlist_id_mapping{};
/* lets start inserting data to the database */ /* register clients */
{
/* register server & properties */
{
sql::InsertQuery insert_property_query{"properties",
sql::Column<ServerId>("serverId"),
sql::Column<property::PropertyType>("type"),
sql::Column<uint64_t>("id"),
sql::Column<std::string>("key"),
sql::Column<std::string>("value")
};
for(const auto& property : snapshot_data.parsed_server.properties.list_properties(property::FLAG_SAVE)) {
if(!property.isModified()) continue;
insert_property_query.add_entry(
target_server_id,
property::PROP_TYPE_SERVER,
0,
std::string{property.type().name},
property.value()
);
}
{
auto result = insert_property_query.execute(this->handle->getSql(), true);
if(!result.failed_entries.empty())
logWarning(logging_server_id, "Failed to insert all server properties into the database. Failed property count: {}", result.failed_entries.size());
}
}
/* register clients and bots */
{ {
sql::InsertQuery insert_general_query{"clients", sql::InsertQuery insert_general_query{"clients",
sql::Column<std::string>("client_unique_id"), sql::Column<std::string>("client_unique_id"),
@ -140,12 +95,6 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
std::chrono::floor<std::chrono::seconds>(client.timestamp_created.time_since_epoch()).count() std::chrono::floor<std::chrono::seconds>(client.timestamp_created.time_since_epoch()).count()
); );
} }
for(const auto& client : snapshot_data.music_bots) {
insert_general_query.add_entry(
client.unique_id,
current_time_seconds
);
}
auto result = insert_general_query.execute(this->handle->getSql(), true); auto result = insert_general_query.execute(this->handle->getSql(), true);
for(const auto& fail : result.failed_entries) for(const auto& fail : result.failed_entries)
@ -179,10 +128,10 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
logWarning(logging_server_id, "Failed to insert music bot {} into the database: {}", client.unique_id, sqlResult.fmtStr()); logWarning(logging_server_id, "Failed to insert music bot {} into the database: {}", client.unique_id, sqlResult.fmtStr());
} }
auto map_query_result = sql::command{this->handle->getSql(), "SELECT `original_client_id`,`client_database_id` FROM `clients_server` WHERE `server_id` = :sid;"} auto map_query_result = sql::command{this->handle->getSql(), "SELECT `original_client_id`,`client_database_id` FROM `clients_server` WHERE `server_id` = :serverId;"}
.value(":serverId", target_server_id) .value(":serverId", target_server_id)
.query([&](int length, std::string* values, std::string* names) { .query([&](int length, std::string* values, std::string* names) {
ClientDbId original_id{0}, new_id{0}; ClientDbId original_id, new_id;
try { try {
original_id = std::stoull(values[0]); original_id = std::stoull(values[0]);
new_id = std::stoull(values[1]); new_id = std::stoull(values[1]);
@ -190,7 +139,7 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
logWarning(logging_server_id, "Failed to parse client database entry mapping for group id {} (New ID: {})", values[1], values[0]); logWarning(logging_server_id, "Failed to parse client database entry mapping for group id {} (New ID: {})", values[1], values[0]);
return; return;
} }
client_id_mapping[original_id] = new_id; mappings.client_id[original_id] = new_id;
}); });
if(!map_query_result) { if(!map_query_result) {
error = "failed to query client database id mappings (" + map_query_result.fmtStr() + ")"; error = "failed to query client database id mappings (" + map_query_result.fmtStr() + ")";
@ -201,10 +150,10 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
/* channels */ /* channels */
{ {
/* Assign each channel a new id */ /* Assign each channel a new id */
ChannelId current_id{0}; ChannelId current_id{1};
for(auto& channel : snapshot_data.parsed_channels) { for(auto& channel : snapshot_data.parsed_channels) {
const auto new_id = current_id++; const auto new_id = current_id++;
channel_id_mapping[channel.properties[property::CHANNEL_ID]] = new_id; mappings.channel_id[channel.properties[property::CHANNEL_ID]] = new_id;
channel.properties[property::CHANNEL_ID] = new_id; channel.properties[property::CHANNEL_ID] = new_id;
} }
@ -212,8 +161,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
for(auto& channel : snapshot_data.parsed_channels) { for(auto& channel : snapshot_data.parsed_channels) {
auto pid = channel.properties[property::CHANNEL_PID].as<ChannelId>(); auto pid = channel.properties[property::CHANNEL_PID].as<ChannelId>();
if(pid > 0) { if(pid > 0) {
auto new_id = channel_id_mapping.find(pid); auto new_id = mappings.channel_id.find(pid);
if(new_id == channel_id_mapping.end()) { if(new_id == mappings.channel_id.end()) {
error = "failed to remap channel parent id for channel \"" + channel.properties[property::CHANNEL_NAME].value() + "\" (snapshot/channel tree broken?)"; error = "failed to remap channel parent id for channel \"" + channel.properties[property::CHANNEL_NAME].value() + "\" (snapshot/channel tree broken?)";
return false; return false;
} }
@ -287,8 +236,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
for(auto& entry : snapshot_data.channel_permissions) { for(auto& entry : snapshot_data.channel_permissions) {
{ {
auto new_id = channel_id_mapping.find(entry.id1); auto new_id = mappings.channel_id.find(entry.id1);
if(new_id == channel_id_mapping.end()) { if(new_id == mappings.channel_id.end()) {
logWarning(logging_server_id, "Missing channel id mapping for channel permission entry (channel id: {}). Skipping permission insert.", entry.id1); logWarning(logging_server_id, "Missing channel id mapping for channel permission entry (channel id: {}). Skipping permission insert.", entry.id1);
continue; continue;
} }
@ -299,8 +248,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
insert_query.add_entry( insert_query.add_entry(
target_server_id, target_server_id,
permission::SQL_PERM_CHANNEL, permission::SQL_PERM_CHANNEL,
entry.id1,
0, 0,
entry.id1,
permission.type->name, permission.type->name,
permission.value.has_value ? permission.value.value : permNotGranted, permission.value.has_value ? permission.value.value : permNotGranted,
permission.granted.has_value ? permission.granted.value : permNotGranted, permission.granted.has_value ? permission.granted.value : permNotGranted,
@ -323,13 +272,13 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
for(auto& group : snapshot_data.parsed_server_groups) { for(auto& group : snapshot_data.parsed_server_groups) {
auto result = insert_model.command().value(":name", group.name).value(":id", group.group_id).execute(); auto result = insert_model.command().value(":name", group.name).value(":id", group.group_id).execute();
if(!result) if(!result)
logWarning(logging_server_id, "Failed to insert server group \"{}\" into the database", group.name); logWarning(logging_server_id, "Failed to insert server group \"{}\" into the database: {}", group.name, result.fmtStr());
} }
sql::command{this->handle->getSql(), "SELECT `original_group_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"} sql::command{this->handle->getSql(), "SELECT `original_group_id`,`groupId` FROM `groups` WHERE `serverId` = :serverId AND `target` = :target AND `type` = :type"}
.value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL) .value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL)
.query([&](int length, std::string* values, std::string* names) { .query([&](int length, std::string* values, std::string* names) {
GroupId original_id{0}, new_id{0}; GroupId original_id, new_id;
try { try {
original_id = std::stoull(values[0]); original_id = std::stoull(values[0]);
new_id = std::stoull(values[1]); new_id = std::stoull(values[1]);
@ -337,7 +286,7 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
logWarning(logging_server_id, "Failed to parse server group mapping for group id {} (New ID: {})", values[1], values[0]); logWarning(logging_server_id, "Failed to parse server group mapping for group id {} (New ID: {})", values[1], values[0]);
return; return;
} }
server_group_id_mapping[original_id] = new_id; mappings.server_group_id[original_id] = new_id;
}); });
} }
@ -354,18 +303,28 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
for(auto& entry : relation.second) { for(auto& entry : relation.second) {
ClientId client_id{}; ClientId client_id{};
{ {
auto new_id = client_id_mapping.find(entry.client_id); auto new_id = mappings.client_id.find(entry.client_id);
if(new_id == client_id_mapping.end()) { if(new_id == mappings.client_id.end()) {
logWarning(logging_server_id, "Missing client id mapping for channel group relation permission entry (client id: {}). Skipping relation insert.", entry.client_id); logWarning(logging_server_id, "Missing client id mapping for channel group relation permission entry (client id: {}). Skipping relation insert.", entry.client_id);
continue; continue;
} }
client_id = new_id->second; client_id = new_id->second;
} }
GroupId group_id;
{
auto new_id = mappings.server_group_id.find(entry.group_id);
if(new_id == mappings.server_group_id.end()) {
logWarning(logging_server_id, "Missing server group mapping of group {} for client {}. Skipping relation insert.", entry.group_id, entry.client_id);
continue;
}
group_id = new_id->second;
}
insert_query.add_entry( insert_query.add_entry(
target_server_id, target_server_id,
client_id, client_id,
entry.group_id, group_id,
0 0
); );
} }
@ -376,21 +335,67 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
logWarning(logging_server_id, "Failed to insert all server group relations into the database. Failed insert count: {}", result.failed_entries.size()); logWarning(logging_server_id, "Failed to insert all server group relations into the database. Failed insert count: {}", result.failed_entries.size());
} }
/* server group 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& group : snapshot_data.parsed_server_groups) {
GroupId group_id;
{
auto new_id = mappings.server_group_id.find(group.group_id);
if(new_id == mappings.server_group_id.end()) {
logWarning(logging_server_id, "Missing server group mapping of group {}. Skipping permission insert.", group.group_id);
continue;
}
group_id = new_id->second;
}
for(const auto& permission : group.permissions) {
insert_query.add_entry(
target_server_id,
permission::SQL_PERM_GROUP,
group_id,
0,
permission.type->name,
permission.value.has_value ? permission.value.value : permNotGranted,
permission.granted.has_value ? permission.granted.value : permNotGranted,
permission.flag_skip,
permission.flag_negate
);
}
}
auto result = insert_query.execute(this->handle->getSql(), true);
if(!result.failed_entries.empty())
logWarning(logging_server_id, "Failed to insert some channel group permissions into the database. Failed permission count: {}", result.failed_entries.size());
}
/* channel groups */ /* channel groups */
{ {
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_group_id`) VALUES (:serverId, :target, :type, :name, :id)"}; sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_group_id`) VALUES (:serverId, :target, :type, :name, :id)"};
insert_model.value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL); insert_model.value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL);
for(auto& group : snapshot_data.parsed_server_groups) { for(auto& group : snapshot_data.parsed_channel_groups) {
auto result = insert_model.command().value(":name", group.name).value(":id", group.group_id).execute(); auto result = insert_model.command().value(":name", group.name).value(":id", group.group_id).execute();
if(!result) if(!result)
logWarning(logging_server_id, "Failed to insert channel group \"{}\" into the database", group.name); logWarning(logging_server_id, "Failed to insert channel group \"{}\" into the database: {}", group.name, result.fmtStr());
} }
sql::command{this->handle->getSql(), "SELECT `original_group_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"} sql::command{this->handle->getSql(), "SELECT `original_group_id`,`groupId` FROM `groups` WHERE `serverId` = :serverId AND `target` = :target AND `type` = :type"}
.value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL) .value(":serverId", target_server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL)
.query([&](int length, std::string* values, std::string* names) { .query([&](int length, std::string* values, std::string* names) {
GroupId original_id{0}, new_id{0}; GroupId original_id, new_id;
try { try {
original_id = std::stoull(values[0]); original_id = std::stoull(values[0]);
new_id = std::stoull(values[1]); new_id = std::stoull(values[1]);
@ -398,7 +403,7 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
logWarning(logging_server_id, "Failed to parse channel group mapping for group id {} (New ID: {})", values[1], values[0]); logWarning(logging_server_id, "Failed to parse channel group mapping for group id {} (New ID: {})", values[1], values[0]);
return; return;
} }
channel_group_id_mapping[original_id] = new_id; mappings.channel_group_id[original_id] = new_id;
}); });
} }
@ -412,10 +417,10 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
}; };
for(auto& relation : snapshot_data.parsed_channel_group_relations) { for(auto& relation : snapshot_data.parsed_channel_group_relations) {
ChannelId channel_id{}; ChannelId channel_id;
{ {
auto new_id = channel_id_mapping.find(relation.first); auto new_id = mappings.channel_id.find(relation.first);
if(new_id == channel_id_mapping.end()) { if(new_id == mappings.channel_id.end()) {
logWarning(logging_server_id, "Missing channel id mapping for channel group relation entry (channel id: {}). Skipping relation insert.", relation.first); logWarning(logging_server_id, "Missing channel id mapping for channel group relation entry (channel id: {}). Skipping relation insert.", relation.first);
continue; continue;
} }
@ -423,20 +428,30 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
} }
for(auto& entry : relation.second) { for(auto& entry : relation.second) {
ClientId client_id{}; ClientId client_id;
{ {
auto new_id = client_id_mapping.find(entry.client_id); auto new_id = mappings.client_id.find(entry.client_id);
if(new_id == client_id_mapping.end()) { if(new_id == mappings.client_id.end()) {
logWarning(logging_server_id, "Missing client id mapping for channel group relation permission entry (client id: {}, channel id: {}). Skipping relation insert.", entry.client_id, relation.first); logWarning(logging_server_id, "Missing client id mapping for channel group relation permission entry (client id: {}, channel id: {}). Skipping relation insert.", entry.client_id, relation.first);
continue; continue;
} }
client_id = new_id->second; client_id = new_id->second;
} }
GroupId group_id;
{
auto new_id = mappings.channel_group_id.find(entry.group_id);
if(new_id == mappings.channel_group_id.end()) {
logWarning(logging_server_id, "Missing channel group mapping of group {} for client {} for channel {}. Skipping relation insert.", entry.group_id, entry.client_id, channel_id);
continue;
}
group_id = new_id->second;
}
insert_query.add_entry( insert_query.add_entry(
target_server_id, target_server_id,
client_id, client_id,
entry.group_id, group_id,
channel_id channel_id
); );
} }
@ -447,6 +462,111 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
logWarning(logging_server_id, "Failed to insert all channel group relations into the database. Failed insert count: {}", result.failed_entries.size()); logWarning(logging_server_id, "Failed to insert all channel group relations into the database. Failed insert count: {}", result.failed_entries.size());
} }
/* channel group 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& group : snapshot_data.parsed_channel_groups) {
GroupId group_id;
{
auto new_id = mappings.channel_group_id.find(group.group_id);
if(new_id == mappings.channel_group_id.end()) {
logWarning(logging_server_id, "Missing channel group mapping of group {}. Skipping permission insert.", group.group_id);
continue;
}
group_id = new_id->second;
}
for(const auto& permission : group.permissions) {
insert_query.add_entry(
target_server_id,
permission::SQL_PERM_GROUP,
group_id,
0,
permission.type->name,
permission.value.has_value ? permission.value.value : permNotGranted,
permission.granted.has_value ? permission.granted.value : permNotGranted,
permission.flag_skip,
permission.flag_negate
);
}
}
auto result = insert_query.execute(this->handle->getSql(), true);
if(!result.failed_entries.empty())
logWarning(logging_server_id, "Failed to insert some channel group permissions into the database. Failed permission count: {}", result.failed_entries.size());
}
/* register server & properties */
{
sql::InsertQuery insert_property_query{"properties",
sql::Column<ServerId>("serverId"),
sql::Column<property::PropertyType>("type"),
sql::Column<uint64_t>("id"),
sql::Column<std::string>("key"),
sql::Column<std::string>("value")
};
for(const auto& property : snapshot_data.parsed_server.properties.list_properties(property::FLAG_SAVE)) {
if(!property.isModified()) continue;
auto value = property.value();
switch(property.type().property_index) {
case property::VIRTUALSERVER_DEFAULT_SERVER_GROUP: {
auto new_id = mappings.server_group_id.find(property.as<GroupId>());
if(new_id == mappings.server_group_id.end()) {
logWarning(logging_server_id, "Missing server group mapping for group {}. {} will reference an invalid group.", property.value(), property.type().name);
break;
}
value = std::to_string(new_id->second);
break;
}
case property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP:
case property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP: {
auto new_id = mappings.channel_group_id.find(property.as<GroupId>());
if(new_id == mappings.channel_group_id.end()) {
logWarning(logging_server_id, "Missing channel group mapping for group {}. {} will reference an invalid group.", property.value(), property.type().name);
break;
}
value = std::to_string(new_id->second);
break;
}
default:
break;
}
insert_property_query.add_entry(
target_server_id,
property::PROP_TYPE_SERVER,
0,
std::string{property.type().name},
value
);
}
{
auto result = insert_property_query.execute(this->handle->getSql(), true);
if(!result.failed_entries.empty())
logWarning(logging_server_id, "Failed to insert all server properties into the database. Failed property count: {}", result.failed_entries.size());
}
}
/* client permissions */ /* client permissions */
{ {
sql::InsertQuery insert_query{"permissions", sql::InsertQuery insert_query{"permissions",
@ -464,8 +584,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
for(auto& entry : snapshot_data.client_permissions) { for(auto& entry : snapshot_data.client_permissions) {
{ {
auto new_id = client_id_mapping.find(entry.id1); auto new_id = mappings.client_id.find(entry.id1);
if(new_id == client_id_mapping.end()) { if(new_id == mappings.client_id.end()) {
logWarning(logging_server_id, "Missing client id mapping for client permission entry (client id: {}). Skipping permission insert.", entry.id1); logWarning(logging_server_id, "Missing client id mapping for client permission entry (client id: {}). Skipping permission insert.", entry.id1);
continue; continue;
} }
@ -509,8 +629,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
for(auto& entry : snapshot_data.client_channel_permissions) { for(auto& entry : snapshot_data.client_channel_permissions) {
{ {
auto new_id = channel_id_mapping.find(entry.id1); auto new_id = mappings.channel_id.find(entry.id1);
if(new_id == channel_id_mapping.end()) { if(new_id == mappings.channel_id.end()) {
logWarning(logging_server_id, "Missing channel id mapping for client channel permission entry (client id: {}, channel id: {}). Skipping permission insert.", entry.id2, entry.id1); logWarning(logging_server_id, "Missing channel id mapping for client channel permission entry (client id: {}, channel id: {}). Skipping permission insert.", entry.id2, entry.id1);
continue; continue;
} }
@ -518,8 +638,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
} }
{ {
auto new_id = client_id_mapping.find(entry.id2); auto new_id = mappings.client_id.find(entry.id2);
if(new_id == client_id_mapping.end()) { if(new_id == mappings.client_id.end()) {
logWarning(logging_server_id, "Missing client id mapping for client channel permission entry (client id: {}, channel id: {}). Skipping permission insert.", entry.id2, entry.id1); logWarning(logging_server_id, "Missing client id mapping for client channel permission entry (client id: {}, channel id: {}). Skipping permission insert.", entry.id2, entry.id1);
continue; continue;
} }
@ -546,6 +666,53 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
logWarning(logging_server_id, "Failed to insert all client channel permissions into the database. Failed permission count: {}", result.failed_entries.size()); logWarning(logging_server_id, "Failed to insert all client channel permissions into the database. Failed permission count: {}", result.failed_entries.size());
} }
/* music bots */
{
sql::InsertQuery insert_query{"musicbots",
sql::Column<ServerId>("serverId"),
sql::Column<ClientDbId>("botId"),
sql::Column<std::string>("uniqueId"),
sql::Column<ClientDbId>("owner"),
};
for(const auto& client : snapshot_data.music_bots) {
ClientDbId bot_id;
{
auto new_id = mappings.client_id.find(client.database_id);
if(new_id == mappings.client_id.end()) {
logWarning(logging_server_id, "Missing music bot database id mapping for bot {}. Skipping bot register.", client.unique_id);
continue;
}
bot_id = new_id->second;
}
ClientDbId bot_owner_id;
{
auto new_id = mappings.client_id.find(client.bot_owner_id);
if(new_id == mappings.client_id.end()) {
logWarning(logging_server_id, "Missing music bots owner database id mapping for bot {}. Resetting it to zero.", client.unique_id);
bot_owner_id = 0;
} else {
bot_owner_id = new_id->second;
}
}
insert_query.add_entry(
target_server_id,
bot_id,
client.unique_id,
bot_owner_id
);
}
{
auto result = insert_query.execute(this->handle->getSql(), true);
if(!result.failed_entries.empty())
logWarning(logging_server_id, "Failed to insert some music bots into the database. Failed bot count: {}", result.failed_entries.size());
}
}
/* music bot attributes */ /* music bot attributes */
{ {
@ -558,8 +725,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
}; };
for(auto& bot : snapshot_data.music_bots) { for(auto& bot : snapshot_data.music_bots) {
auto new_id = client_id_mapping.find(bot.database_id); auto new_id = mappings.client_id.find(bot.database_id);
if(new_id == client_id_mapping.end()) { if(new_id == mappings.client_id.end()) {
logWarning(logging_server_id, "Missing client id mapping for music bot (Unique ID: {}). Skipping permission insert.", bot.unique_id); logWarning(logging_server_id, "Missing client id mapping for music bot (Unique ID: {}). Skipping permission insert.", bot.unique_id);
continue; continue;
} }
@ -571,8 +738,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
if(property.type() == property::CLIENT_LAST_CHANNEL) { if(property.type() == property::CLIENT_LAST_CHANNEL) {
auto channel_id = property.as<ChannelId>(); auto channel_id = property.as<ChannelId>();
auto new_channel_id = channel_id_mapping.find(channel_id); auto new_channel_id = mappings.channel_id.find(channel_id);
if(new_channel_id == channel_id_mapping.end()) { if(new_channel_id == mappings.channel_id.end()) {
logWarning(logging_server_id, "Missing channel id mapping for channel {} for music bot {}.", channel_id, bot.unique_id); logWarning(logging_server_id, "Missing channel id mapping for channel {} for music bot {}.", channel_id, bot.unique_id);
continue; continue;
} }
@ -654,8 +821,8 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
for(auto& song : snapshot_data.playlist_songs) { for(auto& song : snapshot_data.playlist_songs) {
{ {
auto new_id = client_id_mapping.find(song.invoker_id); auto new_id = mappings.client_id.find(song.invoker_id);
if(new_id == client_id_mapping.end()) { if(new_id == mappings.client_id.end()) {
logWarning(logging_server_id, "Missing client id mapping for playlist song invoker (Invoker ID: {}). Changing it to zero.", song.invoker_id); logWarning(logging_server_id, "Missing client id mapping for playlist song invoker (Invoker ID: {}). Changing it to zero.", song.invoker_id);
song.invoker_id = 0; song.invoker_id = 0;
} else { } else {
@ -682,7 +849,6 @@ bool VirtualServerManager::try_deploy_snapshot(std::string &error, ts::ServerId
logWarning(logging_server_id, "Failed to insert some playlist songs into the database. Failed song count: {}", result.failed_entries.size()); logWarning(logging_server_id, "Failed to insert some playlist songs into the database. Failed song count: {}", result.failed_entries.size());
} }
} }
}
return true; return true;
} }

View File

@ -43,21 +43,28 @@ bool relation_parser::parse(std::string &error, group_relations &result, size_t
bool key_found; bool key_found;
while(offset < *relation_end) { while(offset < *relation_end) {
ChannelId channel_id;
auto begin_bulk = this->command.bulk(offset); auto begin_bulk = this->command.bulk(offset);
if(!begin_bulk.has_key("iid")) { if(begin_bulk.has_key("iid")) {
error = "missing iid at character " + std::to_string(begin_bulk.command_character_index()); channel_id = begin_bulk.value_as<ChannelId>("iid");
return false; } else {
/* may be the case for the global assignments (iid may be not send in the first few generations) */
channel_id = 0;
} }
auto& relations = result[begin_bulk.value_as<ChannelId>("iid")]; auto& relations = result[channel_id];
auto next_iid = this->command.next_bulk_containing("iid", offset + 1); auto next_iid = this->command.next_bulk_containing("iid", offset + 1);
if(next_iid.has_value() && *next_iid < relation_end) size_t end_index;
if(next_iid.has_value() && *next_iid < relation_end) {
end_index = *next_iid;
relations.reserve(*next_iid - offset); relations.reserve(*next_iid - offset);
else } else {
end_index = *relation_end;
relations.reserve(*relation_end - offset); relations.reserve(*relation_end - offset);
}
while(offset < *next_iid) { while(offset < end_index) {
auto relation_data = this->command.bulk(offset++); auto relation_data = this->command.bulk(offset++);
auto& relation = relations.emplace_back(); auto& relation = relations.emplace_back();

View File

@ -9,6 +9,8 @@ using namespace ts::server::snapshots;
bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t &index) { bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t &index) {
auto data = this->command[index]; auto data = this->command[index];
entry.properties.register_property_type<property::ClientProperties>();
size_t entry_index{0}; size_t entry_index{0};
std::string_view key{}; std::string_view key{};
std::string value{}; std::string value{};
@ -23,7 +25,12 @@ bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t
} else if(key == "bot_unique_id") { } else if(key == "bot_unique_id") {
entry.unique_id = value; entry.unique_id = value;
} else if(key == "bot_owner_id") { } else if(key == "bot_owner_id") {
entry.bot_owner_id = value; char* end_ptr{nullptr};
entry.bot_owner_id = strtoull(value.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "failed to parse bot owner database id at character " + std::to_string(data.key_command_character_index(key) + key.length());
return false;
}
} else if(key == "end_bots") { } else if(key == "end_bots") {
continue; continue;
} else { } else {
@ -52,6 +59,8 @@ bool music_bot_parser::parse(std::string &error, music_bot_entry &entry, size_t
bool playlist_parser::parse(std::string &error, playlist_entry &entry, size_t &index) { bool playlist_parser::parse(std::string &error, playlist_entry &entry, size_t &index) {
auto data = this->command[index]; auto data = this->command[index];
entry.properties.register_property_type<property::PlaylistProperties>();
size_t entry_index{0}; size_t entry_index{0};
std::string_view key{}; std::string_view key{};
std::string value{}; std::string value{};
@ -82,7 +91,7 @@ bool playlist_parser::parse(std::string &error, playlist_entry &entry, size_t &i
return false; return false;
} }
return false; return true;
} }
bool playlist_song_parser::parse(std::string &error, playlist_song_entry &entry, size_t &index) { bool playlist_song_parser::parse(std::string &error, playlist_song_entry &entry, size_t &index) {

View File

@ -11,7 +11,7 @@ namespace ts::server::snapshots {
ClientDbId database_id{0}; ClientDbId database_id{0};
std::string unique_id{}; std::string unique_id{};
std::string bot_owner_id{}; ClientDbId bot_owner_id{};
Properties properties{}; Properties properties{};
}; };

View File

@ -4,6 +4,10 @@
#include <Definitions.h> #include <Definitions.h>
#include <log/LogUtils.h> #include <log/LogUtils.h>
#include <misc/base64.h>
#include <misc/digest.h>
#include <zstd.h>
#include "./snapshot.h" #include "./snapshot.h"
#include "./server.h" #include "./server.h"
@ -13,12 +17,33 @@
#include "./groups.h" #include "./groups.h"
#include "./snapshot_data.h" #include "./snapshot_data.h"
using namespace ts;
using namespace ts::server; using namespace ts::server;
inline bool verify_hash(std::string &error, ServerId server_id, const command_parser &data, size_t hash_offset, const std::string_view& expected_hash) {
if(!expected_hash.empty() && !data.has_switch("ignorehash")) {
auto payload = data.payload_view(hash_offset);
if(auto switches{payload.find(" -")}; switches != std::string::npos) {
debugMessage(server_id, "Truncating the snapshot payload after index {} (First switch).", switches);
payload = payload.substr(0, switches);
}
auto calculated_hash = base64::encode(digest::sha1(payload));
/* debugMessage(server_id, "SHA1 for playload: {}", payload); */
debugMessage(server_id, "Expected hash: {}, Calculated hash: {}", expected_hash, calculated_hash);
if(expected_hash != calculated_hash) {
error = "invalid snapshot hash.\nExpected: " + std::string{expected_hash} + "\nCalculated: " + calculated_hash;
return false;
}
}
return true;
}
bool snapshots::parse_snapshot(snapshot_data &result, std::string &error, ServerId server_id, const command_parser &data) { bool snapshots::parse_snapshot(snapshot_data &result, std::string &error, ServerId server_id, const command_parser &data) {
if(data.bulk(0).has_key("version")) { if(data.bulk(0).has_key("version")) {
return snapshots::parse_snapshot_ts3(result, error, server_id, data); return snapshots::parse_snapshot_ts3(result, error, server_id, data);
} else if(data.bulk(1).has_key("snapshot_version")) { } else if(data.bulk(0).has_key("snapshot_version") || (data.bulk_count() > 1 && data.bulk(1).has_key("snapshot_version"))) {
/* teaspeak snapshot */ /* teaspeak snapshot */
return snapshots::parse_snapshot_teaspeak(result, error, server_id, data); return snapshots::parse_snapshot_teaspeak(result, error, server_id, data);
} else { } else {
@ -27,24 +52,83 @@ bool snapshots::parse_snapshot(snapshot_data &result, std::string &error, Server
} }
} }
bool snapshots::parse_snapshot_teaspeak(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) { inline std::unique_ptr<char, void(*)(void*)> decompress_snapshot(std::string& error, size_t& decompressed_length, const std::string& data) {
if(!data.bulk(1).has_key("snapshot_version")) { std::unique_ptr<char, void(*)(void*)> result{nullptr, ::free};
error = "Missing snapshot version";
return false; auto uncompressed_length = ZSTD_getFrameContentSize(data.data(), data.length());
if(uncompressed_length == ZSTD_CONTENTSIZE_UNKNOWN) {
error = "unknown uncompressed size";
return result;
} else if(uncompressed_length == ZSTD_CONTENTSIZE_ERROR) {
error = "failed to calculate uncompressed size";
return result;
} else if(ZSTD_isError(uncompressed_length)) {
error = "failed to calculate uncompressed size: " + std::string{ZSTD_getErrorName(uncompressed_length)};
return result;
} else if(uncompressed_length > 512 * 1024 * 1028) {
error = "uncompressed size exceeds half a gigabyte!";
return result;
}
result.reset((char*) ::malloc(uncompressed_length));
if(!result) {
error = "failed to allocate decompress buffer";
return result;
}
decompressed_length = ZSTD_decompress(&*result, uncompressed_length, data.data(), data.length());
if(ZSTD_isError(decompressed_length)) {
error = "decompress error occurred: " + std::string{ZSTD_getErrorName(decompressed_length)};
result = nullptr;
return result;
}
return result;
}
bool snapshots::parse_snapshot_teaspeak(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
snapshots::version_t version;
if(data.bulk(0).has_key("snapshot_version")) {
version = data.bulk(0).value_as<snapshots::version_t>("snapshot_version");
} else {
version = data.bulk(1).value_as<snapshots::version_t>("snapshot_version");
} }
auto version = data.bulk(1).value_as<snapshots::version_t>("snapshot_version");
auto hash = data.bulk(0).value("hash");
if(version < 1) { if(version < 1) {
error = "snapshot version too old"; error = "snapshot version too old";
return false; return false;
} else if(version == 2) {
auto hash = data.bulk(0).value("hash");
if(!verify_hash(error, server_id, data, 1, hash))
return false;
/* the actual snapshot begins at index 2 */
return snapshots::parse_snapshot_raw(result, error, server_id, data, 2, type::TEASPEAK, version);
} else if(version > 2) { } else if(version > 2) {
error = "snapshot version is too new"; auto compressed_data{base64::decode(data.value("data"))};
size_t decompressed_length;
auto decompressed = decompress_snapshot(error, decompressed_length, compressed_data);
if(!decompressed) {
return false; return false;
} }
/* the actual snapshot begins at index 2 */ std::string_view decompressed_data{&*decompressed, decompressed_length};
return snapshots::parse_snapshot_raw(result, error, server_id, data, hash, 2, type::TEASPEAK, version); ts::command_parser decompressed_parser{decompressed_data};
if(!decompressed_parser.parse(false)) {
error = "failed to parse snapshot payload";
return false;
}
return snapshots::parse_snapshot_raw(result, error, server_id, decompressed_parser, 0, type::TEASPEAK, version);
} else if(version > 3) {
error = "snapshot version is too new";
return false;
} else {
error = "invalid snapshot version";
return false;
}
} }
bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) { bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
@ -52,24 +136,42 @@ bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts
if(data.bulk(0).has_key("version")) if(data.bulk(0).has_key("version"))
version = data.bulk(0).value_as<snapshots::version_t>("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")) { if(data.bulk(0).has_key("salt")) {
error = "TeaSpeak dosn't support encrypted snapshots yet"; error = "TeaSpeak dosn't support encrypted snapshots yet";
return false; return false;
} }
if(version == 0) { if(version == 0) {
return snapshots::parse_snapshot_raw(result, error, server_id, data, hash, 1, type::TEAMSPEAK, version); auto hash = data.bulk(0).value("hash");
if(!verify_hash(error, server_id, data, 1, hash))
return false;
return snapshots::parse_snapshot_raw(result, error, server_id, data, 1, type::TEAMSPEAK, version);
} else if(version == 1) { } else if(version == 1) {
error = "version 1 is an invalid version"; error = "version 1 is an invalid version";
return false; return false;
} else if(version == 2) { } else if(version >= 2 && version <= 3) {
/* compressed data */ std::string compressed_data;
error = "version 2 isn't currently supported"; if(version == 2) {
compressed_data = base64::decode(data.payload_view(1));
} else {
compressed_data = base64::decode(data.value("data"));
}
size_t decompressed_length;
auto decompressed = decompress_snapshot(error, decompressed_length, compressed_data);
if(!decompressed) {
return false; return false;
} else if(version == 3) { }
error = "version 3 isn't currently supported";
std::string_view decompressed_data{&*decompressed, decompressed_length};
ts::command_parser decompressed_parser{decompressed_data};
if(!decompressed_parser.parse(false)) {
error = "failed to parse snapshot payload";
return false; return false;
}
return snapshots::parse_snapshot_raw(result, error, server_id, decompressed_parser, 0, type::TEAMSPEAK, version);
} else { } else {
error = "snapshots with version 1-3 are currently supported"; error = "snapshots with version 1-3 are currently supported";
return false; return false;
@ -77,9 +179,7 @@ bool snapshots::parse_snapshot_ts3(snapshot_data &result, std::string &error, ts
} }
bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &error, ServerId server_id, const command_parser &command, bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &error, ServerId server_id, const command_parser &command,
const std::string &hash, size_t command_offset, snapshots::type type, snapshots::version_t version) { size_t command_offset, snapshots::type type, snapshots::version_t version) {
//TODO: Verify hash
/* all snapshots start with the virtual server properties */ /* all snapshots start with the virtual server properties */
{ {
snapshots::server_parser parser{type, version, command}; snapshots::server_parser parser{type, version, command};
@ -105,7 +205,6 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
return false; return false;
} }
snapshot_data.parsed_channels.reserve(*end_bulk - command_offset); snapshot_data.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")) { while(!command.bulk(command_offset).has_key("end_channels")) {
auto& entry = snapshot_data.parsed_channels.emplace_back(); auto& entry = snapshot_data.parsed_channels.emplace_back();
@ -130,7 +229,6 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
return false; return false;
} }
snapshot_data.parsed_channels.reserve(*end_bulk - command_offset); snapshot_data.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")) { while(!command.bulk(command_offset).has_key("end_clients")) {
auto& entry = snapshot_data.parsed_clients.emplace_back(); auto& entry = snapshot_data.parsed_clients.emplace_back();
@ -140,6 +238,93 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
command_offset++; /* the "end_clients" token */ command_offset++; /* the "end_clients" token */
} }
if(type == snapshots::type::TEASPEAK && version >= 2) {
bool music_bots_parsed{false},
playlist_parsed{false},
playlist_songs_parsed{false};
if(!command.bulk(command_offset).has_key("begin_music")) {
error = "missing begin music key";
return false;
}
while(!command.bulk(command_offset).has_key("end_music")) {
if(command.bulk(command_offset).has_key("begin_bots")) {
if(std::exchange(music_bots_parsed, true)) {
error = "duplicated music bot list";
return false;
}
snapshots::music_bot_parser parser{type, version, command};
while(!command.bulk(command_offset).has_key("end_bots")) {
auto& bot = snapshot_data.music_bots.emplace_back();
if(!parser.parse(error, bot, command_offset))
return false;
command_offset++;
}
} else if(command.bulk(command_offset).has_key("begin_playlist")) {
if(std::exchange(playlist_parsed, true)) {
error = "duplicated playlist list";
return false;
}
snapshots::playlist_parser parser{type, version, command};
while(!command.bulk(command_offset).has_key("end_playlist")) {
auto& playlist = snapshot_data.playlists.emplace_back();
if(!parser.parse(error, playlist, command_offset))
return false;
command_offset++;
}
} else if(command.bulk(command_offset).has_key("begin_playlist_songs")) {
if(std::exchange(playlist_songs_parsed, true)) {
error = "duplicated playlist songs list";
return false;
}
snapshots::playlist_song_parser parser{type, version, command};
PlaylistId current_playlist_id{0};
while(!command.bulk(command_offset).has_key("end_playlist_songs")) {
if(snapshot_data.playlist_songs.empty() || command.bulk(command_offset).has_key("song_playlist_id"))
current_playlist_id = command.bulk(command_offset).value_as<PlaylistId>("song_playlist_id");
auto& playlist_song = snapshot_data.playlist_songs.emplace_back();
playlist_song.playlist_id = current_playlist_id;
if(!parser.parse(error, playlist_song, command_offset))
return false;
command_offset++;
}
} else {
__asm__("nop");
}
command_offset++;
}
command_offset++;
/* check if everything has been parsed */
if(!music_bots_parsed) {
error = "missing music bots";
return false;
}
if(!playlist_parsed) {
error = "missing playlists";
return false;
}
if(!playlist_songs_parsed) {
error = "missing playlist songs";
return false;
}
}
/* permissions */ /* permissions */
{ {
bool server_groups_parsed{false}, bool server_groups_parsed{false},
@ -274,82 +459,14 @@ bool snapshots::parse_snapshot_raw(snapshot_data &snapshot_data, std::string &er
} }
} }
if(type == snapshots::type::TEASPEAK && version >= 2) { debugMessage(server_id, "Parsed snapshot containing {} channels, {} server groups, {} channel groups, {} clients, {} music bots and {} playlists.",
snapshot_data.parsed_channels.size(),
bool music_bots_parsed{false}, snapshot_data.parsed_server_groups.size(),
playlist_parsed{false}, snapshot_data.parsed_channel_groups.size(),
playlist_songs_parsed{false}; snapshot_data.parsed_clients.size(),
snapshot_data.music_bots.size(),
if(!command.bulk(command_offset++).has_key("begin_music")) { snapshot_data.playlists.size()
error = "missing begin music key"; );
return false;
}
while(!command.bulk(command_offset).has_key("end_music")) {
if(command.bulk(command_offset).has_key("begin_bots")) {
if(std::exchange(music_bots_parsed, true)) {
error = "duplicated music bot list";
return false;
}
snapshots::music_bot_parser parser{type, version, command};
while(!command.bulk(command_offset).has_key("end_bots")) {
auto& bot = snapshot_data.music_bots.emplace_back();
if(!parser.parse(error, bot, command_offset))
return false;
}
} else if(command.bulk(command_offset).has_key("begin_playlist")) {
if(std::exchange(playlist_parsed, true)) {
error = "duplicated playlist list";
return false;
}
snapshots::playlist_parser parser{type, version, command};
while(!command.bulk(command_offset).has_key("end_playlist")) {
auto& playlist = snapshot_data.playlists.emplace_back();
if(!parser.parse(error, playlist, command_offset))
return false;
}
} else if(command.bulk(command_offset).has_key("begin_playlist_songs")) {
if(std::exchange(playlist_songs_parsed, true)) {
error = "duplicated playlist songs list";
return false;
}
snapshots::playlist_song_parser parser{type, version, command};
PlaylistId current_playlist_id{0};
while(!command.bulk(command_offset).has_key("end_playlist_songs")) {
if(snapshot_data.playlist_songs.empty() || command.bulk(command_offset).has_key("song_playlist_id"))
current_playlist_id = command.bulk(command_offset).value_as<PlaylistId>("song_playlist_id");
auto& playlist_song = snapshot_data.playlist_songs.emplace_back();
playlist_song.playlist_id = current_playlist_id;
if(!parser.parse(error, playlist_song, command_offset))
return false;
}
}
}
/* check if everything has been parsed */
if(!music_bots_parsed) {
error = "missing music bots";
return false;
}
if(!playlist_parsed) {
error = "missing playlists";
return false;
}
if(!playlist_songs_parsed) {
error = "missing playlist songs";
return false;
}
}
return true; return true;
} }

View File

@ -143,7 +143,7 @@ bool permission_parser::parse_entry_teaspeak_v1(std::string &error, std::vector<
return false; return false;
} }
permission::PermissionValue value{}; permission::PermissionValue value;
{ {
auto value_string = data.value("value", key_found); auto value_string = data.value("value", key_found);
if(!key_found) { if(!key_found) {
@ -159,7 +159,7 @@ bool permission_parser::parse_entry_teaspeak_v1(std::string &error, std::vector<
} }
} }
permission::PermissionValue granted{}; permission::PermissionValue granted;
{ {
auto value_string = data.value("grant", key_found); auto value_string = data.value("grant", key_found);
if(!key_found) { if(!key_found) {

View File

@ -54,6 +54,6 @@ namespace ts::server::snapshots {
extern bool parse_snapshot_ts3(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */); extern bool parse_snapshot_ts3(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
extern bool parse_snapshot_teaspeak(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */); extern bool parse_snapshot_teaspeak(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
extern bool parse_snapshot_raw(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */, const std::string& /* hash */, size_t /* offset */, snapshots::type /* type */, snapshots::version_t /* version */); extern bool parse_snapshot_raw(snapshot_data& /* result */, std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */, size_t /* offset */, snapshots::type /* type */, snapshots::version_t /* version */);
} }

View File

@ -29,4 +29,28 @@ namespace ts::server::snapshots {
std::deque<snapshots::playlist_entry> playlists{}; std::deque<snapshots::playlist_entry> playlists{};
std::deque<snapshots::playlist_song_entry> playlist_songs{}; std::deque<snapshots::playlist_song_entry> playlist_songs{};
}; };
struct snapshot_mappings {
ServerId new_server_id{};
std::map<ClientDbId, ClientDbId> client_id{};
std::map<ChannelId, ChannelId> channel_id{};
std::map<ChannelId, ChannelId> channel_group_id{};
std::map<ChannelId, ChannelId> server_group_id{};
std::map<PlaylistId, PlaylistId> playlist_id{};
};
struct snapshot_deploy_timings {
std::chrono::system_clock::time_point initial_timestamp{};
std::chrono::system_clock::time_point decode_timestamp{}; /* end decode */
std::chrono::system_clock::time_point parse_timestamp{}; /* end parse */
std::chrono::system_clock::time_point deploy_timestamp{}; /* else deploy */
std::chrono::system_clock::time_point startup_timestamp{}; /* end startup */
};
struct snapshot_deploy {
snapshot_deploy_timings timings{};
snapshot_mappings mappings{};
snapshot_data data{};
};
} }

2
shared

@ -1 +1 @@
Subproject commit a6343968450d4e626bcf99ec622b4a325bf8c4ba Subproject commit c3188cd9e5f499f4b5813953d2bc62f56800222f