#include #include #include #include #include #include "ServerManager.h" #include "src/server/VoiceServer.h" #include "InstanceHandler.h" #include "InstanceHandler.h" using namespace std; using namespace ts; using namespace ts::server; #define PREFIX string("[SNAPSHOT] ") inline std::string avArguments(const ts::Command& cmd, int index){ stringstream res; for(const auto &key : cmd[index].keys()) res << key << "=" << cmd[index][key].as() << " "; return res.str(); } //TeamSpeak: permid=i_channel_needed_permission_modify_power permvalue=75 permskip=0 permnegated=0 //TeaSpeak: perm=i_channel_needed_permission_modify_power value=75 granted=75 flag_skip=0 flag_negated=0 struct SnapshotPermissionEntry { std::shared_ptr type = permission::PermissionTypeEntry::unknown; permission::PermissionValue value = permNotGranted; permission::PermissionValue grant = permNotGranted; bool negated = false; bool skipped = false; SnapshotPermissionEntry(const std::shared_ptr& type, permission::PermissionValue value, permission::PermissionValue grant) : type(std::move(type)), value(value), grant(grant) {} SnapshotPermissionEntry(const std::shared_ptr& type, permission::PermissionValue value, permission::PermissionValue grant, bool negated, bool skipped) : type(type), value(value), grant(grant), negated(negated), skipped(skipped) {} SnapshotPermissionEntry() = default; static deque> parse(const Command& cmd, int& index, permission::teamspeak::GroupType type, int version, const std::vector& ends, bool ignore_first = false) { deque> result{}; while(true) { bool end_reached = false; for(const auto& e : ends) if(cmd[index].has(e)) { end_reached = true; break; } if(end_reached || cmd.bulkCount() < index) { if(ignore_first) ignore_first = false; else break; } if(version == 0) { auto permission_name = cmd[index]["permid"].string(); for(const auto& mapped : permission::teamspeak::map_key(permission_name, type)) { auto permission = permission::resolvePermissionData(mapped); if(permission->type == permission::unknown) { logError(0, "Failed to parse permission {}. Original: {}", mapped, permission_name); index++; continue; } bool found = false; for(auto& e : result) if(e->type->type == permission->type) { found = true; if(permission->grantName() == mapped) e->grant = cmd[index]["permvalue"]; else { e->value = cmd[index]["permvalue"]; e->negated = cmd[index]["permnegated"]; e->skipped = cmd[index]["permskip"]; } } if(!found) { auto entry = make_unique(); entry->type = permission; if(permission->grantName() == mapped) entry->grant = cmd[index]["permvalue"]; else { entry->value = cmd[index]["permvalue"]; entry->negated = cmd[index]["permnegated"]; entry->skipped = cmd[index]["permskip"]; } result.push_back(std::move(entry)); } } } else if(version >= 1) { auto permission_name = cmd[index]["perm"].string(); auto permission = permission::resolvePermissionData(permission_name); if(permission->type == permission::unknown) { logError(0, "Failed to parse permission {}", permission_name); index++; continue; } auto entry = make_unique(); entry->type = permission; entry->value = cmd[index]["value"]; entry->grant = cmd[index]["grant"]; entry->skipped = cmd[index]["flag_skip"]; entry->negated = cmd[index]["flag_negated"]; result.push_back(std::move(entry)); } else { logError(0, "Failed to parse snapshot permission entry! Invalid version!"); index++; continue; } index++; } deque> addings{}; for(const auto& perm : result) { if(perm->type->type == permission::i_group_auto_update_type) { //Migrate this type for(const auto& e : permission::update::migrate) if(e.type == perm->value) { auto _type = permission::resolvePermissionData(e.permission.name); if(_type->type == permission::unknown) { logError(0, "Invalid update permission for update group {}. Permission name: {}", e.type, e.permission.name); } else { addings.push_back(make_unique(_type, e.permission.value, e.permission.granted, e.permission.negated, e.permission.skipped)); } } } } for(auto& a : addings) result.push_back(move(a)); return result; } void write(Command& cmd, int& index, permission::teamspeak::GroupType type, int version) { if(version == 0) { if(this->value != permNotGranted) { for(const auto& name : permission::teamspeak::unmap_key(this->type->name, type)) { cmd[index]["permid"] = name; cmd[index]["permvalue"] = this->value; cmd[index]["permskip"] = this->skipped; cmd[index]["permnegated"] = this->negated; index++; } } if(this->grant != permNotGranted) { for(const auto& name : permission::teamspeak::unmap_key(this->type->grantName(), type)) { cmd[index]["permid"] = name; cmd[index]["permvalue"] = this->grant; cmd[index]["permskip"] = false; cmd[index]["permnegated"] = false; index++; } } } else if(version >= 1) { cmd[index]["perm"] = this->type->name; cmd[index]["value"] = this->value; cmd[index]["grant"] = this->grant; cmd[index]["flag_skip"] = this->skipped; cmd[index]["flag_negated"] = this->negated; index++; } else { logError(0, "Could not write snapshot permission! Invalid version. ({})", version); } } }; std::shared_ptr ServerManager::createServerFromSnapshot(shared_ptr old, std::string host, uint16_t port, const ts::Command &arguments, std::string &error) { ServerId serverId = 0; map> channelGroupRelations; //cid is the new cgid map channelGroupMapping; map> serverGroupRelations; //gid is the new gid map serverGroupMapping; map db_mapping_client_id; deque>> futures; bool sid_success = false; serverId = this->next_available_server_id(sid_success); if(!sid_success) { error = "failed to generate new sid"; return nullptr; } ServerId log_server_id = old ? old->getServerId() : serverId; futures.push_back({ "server id register", sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", host}, variable{":port", port}).executeLater() }); sql::model sql_insert_channel(this->handle->getSql(), "INSERT INTO `channels` (`serverId`, `channelId`, `type`, `parentId`) VALUES (:sid, :id, :type, :pid)", variable{":sid", serverId}); sql::model sql_insert_group_assignment(this->handle->getSql(), "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)", variable{":sid", serverId}, variable{":until", 0}); sql::model sql_insert_permission(this->handle->getSql(), "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)", variable{":serverId", serverId}); sql::model sql_update_permission_grant(this->handle->getSql(), "UPDATE `permissions` SET `grant` = :grant WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `channelId` = :chid AND `permId` = :key", variable{":sid", serverId}); sql::model sql_insert_property(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)", variable{":sid", serverId}); sql::model sql_insert_bot(this->handle->getSql(), "INSERT INTO `musicbots` (`serverId`, `botId`, `uniqueId`, `owner`) VALUES (:server_id, :bot_id, :unique_id, :owner)", variable{":server_id", serverId}); sql::model sql_insert_playlist(this->handle->getSql(), "INSERT INTO `playlists` (`serverId`, `playlist_id`) VALUES (:server_id, :playlist_id)", variable{":server_id", serverId}); sql::model sql_insert_playlist_song(this->handle->getSql(), "INSERT INTO `playlist_songs` (`serverId`, `playlist_id`, `song_id`, `order_id`, `invoker_dbid`, `url`, `url_loader`, `loaded`, `metadata`) VALUES (:server_id, :playlist_id, :song_id, :order_id, :invoker_dbid, :url, :url_loader, :loaded, :metadata)", variable{":server_id", serverId}); int index = 0; auto snapshot_version = arguments[index].has("snapshot_version") ? arguments[index++]["snapshot_version"] : 0; debugMessage(0, "Got server snapshot with version {}", snapshot_version); while(true){ for(auto &key : arguments[index].keys()){ if(key == "end_virtualserver") continue; if(key == "begin_virtualserver") continue; if(snapshot_version == 0) { if(key == "virtualserver_download_quota" || key == "virtualserver_upload_quota" || key == "virtualserver_max_download_total_bandwidth" || key == "virtualserver_max_upload_total_bandwidth") { auto value = arguments[index][key].string(); try { arguments[index][key] = to_string((int64_t) std::stoull(value)); } catch(const std::exception& ex) { continue; } } } debugMessage(LOG_GENERAL, PREFIX + " Having server key {} = {}", key, arguments[index][key].as()); futures.push_back({ "server property import proprty=" + key, sql_insert_property.command().values( variable{":type", property::PropertyType::PROP_TYPE_SERVER}, variable{":id", 0}, variable{":key", key}, variable{":value", arguments[index][key].string()} ).executeLater() }); } if(arguments[index].has("end_virtualserver")) break; index++; } //`serverId` INT NOT NULL, `playlist_id` INT, `song_id` INT, `order_id` INT, `invoker_dbid` INT, `url` TEXT, `url_loader` TEXT /* * sql::command(this->handle->getSql(), "INSERT INTO `playlists` (`serverId`, `playlist_id`) VALUES (:server_id, :playlist_id)", variable{":server_id", this->ref_server()->getServerId()}, variable{":playlist_id", playlist_id} ) */ index++; for(;index < arguments.bulkCount(); index++) { if (arguments[index].has("begin_channels")) { while (true) { if (arguments[index].has("end_channels")) break; auto channel_id = arguments[index]["channel_id"].string(); futures.push_back({ "channel register cid=" + channel_id, sql_insert_channel.command().values(variable{":id", channel_id}, variable{":type", -1}, variable{":pid", arguments[index]["channel_pid"].string()}).executeLater() }); //TODO findout type! for (const auto &key : arguments[index].keys()) { if(key == "channel_id") continue; if(key == "channel_pid") continue; if(key == "channel_security_salt") continue; if(key == "channel_filepath") continue; //TODO implement channel_filepath? if(key == "begin_channels") continue; debugMessage(log_server_id, PREFIX + "Having channel key " + key + "=" + arguments[index][key].as() + " for channel " + arguments[index]["channel_id"].as()); futures.push_back({ "channel prop register cid=" + channel_id + " property=" + key, sql_insert_property.command().values(variable{":sid", serverId}, variable{":type", property::PropertyType::PROP_TYPE_CHANNEL}, variable{":id", channel_id}, variable{":key", key}, variable{":value", arguments[index][key].as()} ).executeLater() }); } index++; } } else if (arguments[index].has("begin_clients")) { while (true) { if (arguments[index].has("end_clients")) break; debugMessage(log_server_id, PREFIX + "Create client {}/{} -> end: {}",arguments[index]["client_id"].as(), arguments[index]["client_nickname"].as(), to_string(arguments[index].has("end_clients"))); ClientDbId oldId = 0; auto res = sql::command(this->handle->getSql(), "SELECT `cldbid` FROM `clients` WHERE `clientUid` = :uid AND `serverId` = 0", variable{":uid", arguments[index]["client_unique_id"].as()}).query([](ClientDbId* ptr, int, char** v, char**){ *ptr = stoll(v[0]); return 0;}, &oldId); LOG_SQL_CMD(res); if(oldId == 0){ res = sql::command(this->handle->getSql(), "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([](ClientDbId* ptr, int length, char** values, char** names){ *ptr = static_cast(stoll(values[0])); return 0; }, &oldId); LOG_SQL_CMD(res); oldId += 1; //Create a new client sql::command(this->handle->getSql(), "INSERT INTO `clients` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) VALUES (:sid, :cldbid, :cluid, :firstConnect, :lastConnect, :connections, :name)", variable{":sid", 0}, variable{":cldbid", oldId}, variable{":cluid", arguments[index]["client_unique_id"].as()}, variable{":firstConnect", arguments[index]["client_created"].as()}, variable{":lastConnect", "0"}, variable{":connections", "0"}, variable{":name", arguments[index]["client_nickname"].as()}).execute(); //TODO log error } db_mapping_client_id[arguments[index]["client_id"].as()] = oldId; sql::command(this->handle->getSql(), "INSERT INTO `clients` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) VALUES (:sid, :cldbid, :cluid, :firstConnect, :lastConnect, :connections, :name)", variable{":sid", serverId}, variable{":cldbid", oldId}, variable{":cluid", arguments[index]["client_unique_id"].as()}, variable{":firstConnect", arguments[index]["client_created"].as()}, variable{":lastConnect", "0"}, variable{":connections", "0"}, variable{":name", arguments[index]["client_nickname"].as()}).execute(); //TODO log error //TODO here index++; } } else if (arguments[index].has("begin_permissions")) { while (true) { index++; if (arguments[index].has("end_permissions")) break; else if (arguments[index].has("server_groups")) { GroupId currentGroupId = 1; while (true) { if (arguments[index].has("end_groups")) break; if(!arguments[index].has("id")){ //new group logError(0, "Invalid server group permission entry! Missing group id!"); index++; continue; } currentGroupId = static_cast(GroupManager::generateGroupId(this->handle->getSql())); debugMessage(log_server_id, PREFIX + "Insert group: " + to_string(currentGroupId) + " - " + arguments[index]["name"].as()); serverGroupMapping[arguments[index]["id"].as()] = currentGroupId; LOG_SQL_CMD(sql::command(this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)", variable{":sid", serverId}, variable{":gid", currentGroupId}, variable{":target", GROUPTARGET_SERVER}, variable{":type", GroupType::GROUP_TYPE_NORMAL}, variable{":name", arguments[index]["name"].as()}) .execute()); auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::SERVER, snapshot_version, {"end_group"}); for(const auto& entry : permissions) { futures.push_back({ "server group permission sgid=" + to_string(currentGroupId) + " permId=" + entry->type->name, sql_insert_permission.command().values( variable{":id", currentGroupId}, variable{":type", permission::SQL_PERM_GROUP}, variable{":chId", 0}, variable{":permId", entry->type->name}, variable{":value", entry->value}, variable{":grant", entry->grant}, variable{":flag_skip", entry->skipped}, variable{":flag_negate", entry->negated} ).executeLater() }); } index++; } //Relations for sgroups after list index++; while (true) { if (arguments[index].has("end_relations")) break; serverGroupRelations[db_mapping_client_id[arguments[index]["cldbid"].as()]].push_back(serverGroupMapping[arguments[index]["gid"].as()]); futures.push_back({ "server group relation", sql_insert_group_assignment.command().values(variable{":cldbid", db_mapping_client_id[arguments[index]["cldbid"].as()]}, variable{":gid", serverGroupMapping[arguments[index]["gid"].as()]}, variable{":chid", "0"}).executeLater() }); index++; } } else if(arguments[index].has("channel_groups")){ GroupId currentGroupId = 1; while (true) { if (arguments[index].has("end_groups")) break; if(!arguments[index].has("id")){ //new group logError(0, "Invalid server group permission entry! Missing group id!"); index++; continue; } currentGroupId = static_cast(GroupManager::generateGroupId(this->handle->getSql())); debugMessage(log_server_id, PREFIX + "Insert channel group: " + to_string(currentGroupId) + " - " + arguments[index]["name"].as()); channelGroupMapping[arguments[index]["id"].as()] = currentGroupId; LOG_SQL_CMD(sql::command(this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)", variable{":sid", serverId}, variable{":gid", currentGroupId}, variable{":target", GROUPTARGET_CHANNEL}, variable{":type", GroupType::GROUP_TYPE_NORMAL}, variable{":name", arguments[index]["name"].as()}) .execute()); auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::SERVER, snapshot_version, {"end_group"}); for(const auto& entry : permissions) { futures.push_back({ "channel group property insert cgid=" + to_string(currentGroupId) + " property=" + entry->type->name, sql_insert_permission.command().values( variable{":id", currentGroupId}, variable{":type", permission::SQL_PERM_GROUP}, variable{":chId", 0}, variable{":permId", entry->type->name}, variable{":value", entry->value}, variable{":grant", entry->grant}, variable{":flag_skip", entry->skipped}, variable{":flag_negate", entry->negated} ).executeLater() }); } index++; } //Relations for sgroups after list ChannelId chId = 0; index++; while (true) { if (arguments[index].has("end_relations")) break; if(arguments[index].has("iid")) chId = arguments[index]["iid"]; channelGroupRelations[db_mapping_client_id[arguments[index]["cldbid"].as()]][chId] = channelGroupMapping[arguments[index]["gid"].as()]; futures.push_back({ "channel group relation", sql_insert_group_assignment.command().values(variable{":cldbid", db_mapping_client_id[arguments[index]["cldbid"].as()]}, variable{":gid", channelGroupMapping[arguments[index]["gid"].as()]}, variable{":chid", chId}).executeLater() }); index++; } } else if (arguments[index].has("client_flat")) { ClientDbId currentId = 0; while (true) { if (arguments[index].has("end_flat")) break; if(arguments[index].has("id1")) currentId = arguments[index]["id1"]; auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CLIENT, snapshot_version, {"id1", "end_flat"}, true); for(const auto& entry : permissions) { futures.push_back({ "client permission insert clientId=" + to_string(currentId) + " permId=" + entry->type->name, sql_insert_permission.command().values( variable{":id", currentId}, variable{":type", permission::SQL_PERM_USER}, variable{":chId", 0}, variable{":permId", entry->type->name}, variable{":value", entry->value}, variable{":grant", entry->grant}, variable{":flag_skip", entry->skipped}, variable{":flag_negate", entry->negated} ).executeLater() }); } } } else if (arguments[index].has("channel_flat")) { ChannelId currentChannelId = 0; while (true) { if (arguments[index].has("end_flat")) break; if (arguments[index].has("id1")) currentChannelId = arguments[index]["id1"]; //We also have id2 unknown for what this is. Maybe parent? auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CHANNEL, snapshot_version, {"id1", "end_flat"}, true); for(const auto& entry : permissions) { futures.push_back({ "channel permission insert channelId=" + to_string(currentChannelId) + " permId=" + entry->type->name, sql_insert_permission.command().values( variable{":id", 0}, variable{":type", permission::SQL_PERM_CHANNEL}, variable{":chId", currentChannelId}, variable{":permId", entry->type->name}, variable{":value", entry->value}, variable{":grant", entry->grant}, variable{":flag_skip", entry->skipped}, variable{":flag_negate", entry->negated} ).executeLater() }); } } } else if (arguments[index].has("channel_client_flat")) { ClientDbId currentClientId = 0; ChannelId currentChannelId = 0; while (true) { if (arguments[index].has("end_flat")) break; //id1 = channel id //id2 = client id if(arguments[index].has("id1")) currentChannelId = arguments[index]["id1"]; if(arguments[index].has("id2")) currentClientId = db_mapping_client_id[arguments[index]["id2"]]; if(currentChannelId > 0 && currentClientId > 0){ auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CLIENT, snapshot_version, {"id1", "id2", "end_flat"}, true); for(const auto& entry : permissions) { futures.push_back({ "client channel permission insert", sql_insert_permission.command().values( variable{":id", currentClientId}, variable{":type", permission::SQL_PERM_CHANNEL}, variable{":chId", currentChannelId}, variable{":permId", entry->type->name}, variable{":value", entry->value}, variable{":grant", entry->grant}, variable{":flag_skip", entry->skipped}, variable{":flag_negate", entry->negated} ).executeLater() }); } } else index++; } } else { error = "Invalid tag in permissions. Available: " + avArguments(arguments, index); return nullptr; } } } else if(arguments[index].has("begin_music")) { PlaylistId playlist_index = 0; PlaylistId playlist_error = 0xFFFFFFFF; map db_mapping_playlist; /* //gather static info (not needed because its a new server) { auto sql_result = sql::command(this->handle->getSql(), "SELECT `playlist_id` FROM `playlists` WHERE `serverId` = :server_id ORDER BY `playlist_id` DESC", variable{":server_id", serverId}).query([&](int length, string* values, string* names){ if(length != 1) return; try { playlist_index = (PlaylistId) stoll(values[0]); } catch(const std::exception& ex) { error = "Failed to parse playlist id from database. ID: " + values[0]; return; } }); LOG_SQL_CMD(sql_result); if(playlist_index == 0) { error = "failed to gather info"; return nullptr; } } */ while(!arguments[index].has("end_music")) { if(arguments[index].has("begin_bots")) { while(!arguments[index].has("end_bots")) { ClientDbId bot_id = arguments[index]["bot_id"]; if(db_mapping_client_id.find(bot_id) == db_mapping_client_id.end()) { logError(log_server_id, PREFIX + "Failed to insert music bot within id {}. (Mapping not found)", bot_id); index++; continue; } ClientDbId new_bot_id = db_mapping_client_id[bot_id]; //begin_bots auto sql_result = sql_insert_bot.command().values( variable{":bot_id", new_bot_id}, variable{":unique_id", arguments[index]["bot_unique_id"].string()}, variable{":owner", arguments[index]["bot_owner_id"].string()} ).execute(); if(!sql_result) { logError(log_server_id, PREFIX + "Failed to insert music bot with id {} (old: {}). ({})", new_bot_id, bot_id, sql_result.fmtStr()); index++; continue; } for(const auto& key : arguments[index].keys()) { if(key == "begin_music") continue; if(key == "begin_bots") continue; if(key == "bot_unique_id") continue; if(key == "bot_owner_id") continue; if(key == "bot_id") continue; const auto& property = property::info(key); if(property->property_index == property::CLIENT_UNDEFINED) { debugMessage(log_server_id, PREFIX + "Failed to parse give music bot property {} for bot {} (old: {}). Value: {}", key, new_bot_id, bot_id, arguments[index][key].string()); continue; } futures.push_back({ "music bot insert", sql_insert_property.command().values( variable{":type", property::PropertyType::PROP_TYPE_CLIENT}, variable{":id", new_bot_id}, variable{":key", key}, variable{":value", arguments[index][key].string()} ).executeLater() }); } index++; } } else if(arguments[index].has("begin_playlist")) { while(!arguments[index].has("end_playlist")) { PlaylistId playlist_id = arguments[index]["playlist_id"]; db_mapping_playlist[++playlist_index] = playlist_id; auto sql_result = sql_insert_playlist.command().values(variable{":playlist_id", playlist_index}).execute(); if(!sql_result) { db_mapping_playlist[playlist_index] = playlist_error; logError(log_server_id, PREFIX + "Failed to insert playlist with id {} (old: {}). ({})", playlist_index, playlist_id, sql_result.fmtStr()); index++; continue; } for(const auto& key : arguments[index].keys()) { if(key == "begin_music") continue; if(key == "begin_playlist") continue; if(key == "playlist_id") continue; const auto& property = property::info(key); if(property->property_index == property::CLIENT_UNDEFINED) { debugMessage(log_server_id, PREFIX + "Failed to parse given playlist property {} for playlist {} (old: {}). Value: {}", key, playlist_index, playlist_id, arguments[index][key].string()); continue; } futures.push_back({ "playlist insert", sql_insert_property.command().values( variable{":type", property::PropertyType::PROP_TYPE_PLAYLIST}, variable{":id", playlist_index}, variable{":key", key}, variable{":value", arguments[index][key].string()} ).executeLater() }); } index++; } } else if(arguments[index].has("begin_playlist_songs")) { PlaylistId current_playlist = 0; PlaylistId current_playlist_mapped = 0; while(!arguments[index].has("end_playlist_songs")) { if(arguments[index].has("song_playlist_id")) { current_playlist = arguments[index]["song_playlist_id"]; current_playlist_mapped = (PlaylistId) db_mapping_client_id[current_playlist]; if(current_playlist_mapped == 0) { logError(log_server_id, PREFIX + "Failed to insert playlist song entry {}. Playlist id {} hasn't been mapped", arguments[index]["song_id"].value(), current_playlist); current_playlist_mapped = playlist_error; } } if(current_playlist_mapped == playlist_error) { /* current list is an error */ index++; continue; } auto sql_future = sql_insert_playlist_song.command().values( variable{":playlist_id", current_playlist_mapped}, variable{":song_id", arguments[index]["song_id"].value()}, variable{":order_id", arguments[index]["song_order"].value()}, variable{":invoker_dbid", arguments[index]["song_invoker"].value()}, variable{":url", arguments[index]["song_url"].value()}, variable{":url_loader", arguments[index]["song_url_loader"].value()}, variable{":loaded", arguments[index]["song_loaded"].value()}, variable{":metadata", arguments[index]["song_metadata"].value()} ).executeLater(); futures.push_back({ "song insert", sql_future }); index++; } } index++; } } else { error = "Invalid root tag. Available: " + avArguments(arguments, index); return nullptr; } } debugMessage("Wait for success!"); for(const auto& future : futures) { auto result = future.second.waitAndGet({-1, "timeout"}, chrono::system_clock::now() + chrono::seconds(5)); if(!result) { logError(serverId, "Failed to execute snapshotdeploy command {}: {}", future.first, result.fmtStr()); } } if(old) this->deleteServer(old); //Now we load the server auto server = make_shared(serverId, this->handle->getSql()); server->self = server; if(!server->initialize(false)) { //FIXME Error handling! } server->properties()[property::VIRTUALSERVER_HOST] = host; server->properties()[property::VIRTUALSERVER_PORT] = port; server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; { threads::MutexLock l(this->instanceLock); this->instances.push_back(server); } this->adjust_executor_threads(); server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] = serverGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP].as()]; server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = channelGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as()]; server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = channelGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].as()]; server->ensureValidDefaultGroups(); return server; } struct CommandTuple { Command& cmd; int& index; int version; }; struct PermissionCommandTuple { Command& cmd; int& index; int version; ClientDbId client; ChannelId channel; }; inline bool writePermissions(const shared_ptr& manager, Command& cmd, int& index, int version, permission::teamspeak::GroupType type, std::string& error) { for(const auto& permission_container : manager->permissions()) { auto permission = get<1>(permission_container); SnapshotPermissionEntry{ permission::resolvePermissionData(get<0>(permission_container)), permission.flags.value_set ? permission.values.value : permNotGranted, permission.flags.grant_set ? permission.values.grant : permNotGranted, permission.flags.negate, permission.flags.skip }.write(cmd, index, type, version); } return true; } inline void writeRelations(const shared_ptr& server, GroupTarget type, Command& cmd, int& index, int version) { PermissionCommandTuple parm{cmd, index, version, 0, 0}; auto res = sql::command(server->getSql(), "SELECT `cldbid`, `groups`.`groupId`, `channelId`, `until` FROM `assignedGroups` INNER JOIN `groups` ON `groups`.`serverId` = `assignedGroups`.`serverId` AND `groups`.`groupId` = `assignedGroups`.`groupId` WHERE `groups`.`serverId` = :sid AND `groups`.target = :type", variable{":sid", server->getServerId()}, variable{":type", type} ).query([](PermissionCommandTuple* commandIndex, int length, char** value, char** name) { ClientDbId cldbid = 0; ChannelId channelId = 0; GroupId gid = 0; int64_t until = 0; for(int idx = 0; idx < length; idx++) { try { if(strcmp(name[idx], "cldbid") == 0) cldbid = stoul(value[idx]); else if(strcmp(name[idx], "groupId") == 0) gid = stoul(value[idx]); else if(strcmp(name[idx], "channelId") == 0) channelId = stoul(value[idx]); else if(strcmp(name[idx], "until") == 0) until = stoll(value[idx]); } catch (std::exception& ex) { logError(0, "Failed to write snapshot group relation (Skipping it)! Message: {} @ {} => {}", ex.what(), name[idx], value[idx]); return 0; } } if(commandIndex->channel != channelId) { commandIndex->channel = channelId; commandIndex->cmd[commandIndex->index]["iid"] = channelId; } commandIndex->cmd[commandIndex->index]["gid"] = gid; commandIndex->cmd[commandIndex->index]["until"] = until; commandIndex->cmd[commandIndex->index++]["cldbid"] = cldbid; return 0; }, &parm); LOG_SQL_CMD(res); } struct DatabaseMusicbot { ClientDbId bot_id; ClientDbId bot_owner_id; std::string bot_unique_id; }; bool ServerManager::createServerSnapshot(Command &cmd, shared_ptr server, int version, std::string &error) { int index = 0; if(version == -1) version = 2; //Auto versioned if(version < 0 || version > 2) { error = "Invalid snapshot version!"; return false; } if(version > 0) cmd[index++]["snapshot_version"] = version; //Server { cmd[index]["begin_virtualserver"] = ""; for(const auto& serverProperty : server->properties().list_properties(property::FLAG_SNAPSHOT)) { if(version == 0) { switch (serverProperty.type().property_index) { case property::VIRTUALSERVER_DOWNLOAD_QUOTA: case property::VIRTUALSERVER_UPLOAD_QUOTA: case property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH: case property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH: cmd[index][serverProperty.type().name] = (uint64_t) serverProperty.as_save(); default: break; } } cmd[index][serverProperty.type().name] = serverProperty.value(); } cmd[index++]["end_virtualserver"] = ""; } //Channels { cmd[index]["begin_channels"] = ""; for(const auto& channel : server->getChannelTree()->channels()) { for(const auto& channelProperty : channel->properties().list_properties(property::FLAG_SNAPSHOT)) { if(channelProperty.type() == property::CHANNEL_ID) cmd[index]["channel_id"] = channelProperty.as(); else if(channelProperty.type() == property::CHANNEL_PID) cmd[index]["channel_pid"] = channelProperty.as(); else cmd[index][channelProperty.type().name] = channelProperty.as(); } index++; } cmd[index++]["end_channels"] = ""; } //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++) { try { if(strcmp(name[idx], "cldbid") == 0) cldbid = value[idx] && strlen(value[idx]) > 0 ? stoul(value[idx]) : 0; 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; } } commandIndex->cmd[commandIndex->index]["client_id"] = cldbid; commandIndex->cmd[commandIndex->index]["client_unique_id"] = clientUid; commandIndex->cmd[commandIndex->index]["client_nickname"] = lastName; commandIndex->cmd[commandIndex->index]["client_created"] = clientCreated; commandIndex->cmd[commandIndex->index]["client_description"] = description; if(commandIndex->version == 0) commandIndex->cmd[commandIndex->index]["client_unread_messages"] = 0; commandIndex->index++; return 0; }, &parm); LOG_SQL_CMD(res); cmd[index++]["end_clients"] = ""; } //music and music bots if(version >= 2) { cmd[index]["begin_music"] = ""; /* music bots */ { cmd[index]["begin_bots"] = ""; deque music_bots; auto sql_result = sql::command(server->getSql(), "SELECT `botId`, `uniqueId`, `owner` FROM `musicbots` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).query([&](int length, string* values, string* names) { DatabaseMusicbot data; for(int column = 0; column < length; column++) { try { if(names[column] == "botId") data.bot_id = stoll(values[column]); else if(names[column] == "uniqueId") data.bot_unique_id = values[column]; else if(names[column] == "owner") data.bot_owner_id = stoll(values[column]); } catch(std::exception& ex) { return; } } music_bots.emplace_back(data); }); if(!sql_result) logError(server->getServerId(), PREFIX + "Failed to write music bots to snapshot. {}", sql_result.fmtStr()); for(const auto& music_bot : music_bots) { auto properties = serverInstance->databaseHelper()->query_properties(server->getServerId(), property::PROP_TYPE_CLIENT, music_bot.bot_id); cmd[index]["bot_unique_id"] = music_bot.bot_unique_id; cmd[index]["bot_owner_id"] = music_bot.bot_owner_id; cmd[index]["bot_id"] = music_bot.bot_id; for(const auto& property : properties) { if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue; if(property->value == property->type->default_value) continue; cmd[index][property->type->name] = property->value; } index++; } cmd[index++]["end_bots"] = ""; } /* playlists */ { cmd[index]["begin_playlist"] = ""; deque playlist_ids; auto sql_result = sql::command(server->getSql(), "SELECT `playlist_id` FROM `playlists` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).query([&](int length, string* values, string* names) { try { playlist_ids.push_back(stoll(values[0])); } catch(std::exception& ex) { return; } }); if(!sql_result) logError(server->getServerId(), PREFIX + "Failed to write playlists to snapshot. {}", sql_result.fmtStr()); for(const auto& playlist : playlist_ids) { auto properties = serverInstance->databaseHelper()->query_properties(server->getServerId(), property::PROP_TYPE_PLAYLIST, playlist); cmd[index]["playlist_id"] = playlist; for(const auto& property : properties) { if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue; if(property->value == property->type->default_value) continue; cmd[index][property->type->name] = property->value; } index++; } cmd[index++]["end_playlist"] = ""; } /* playlist info */ { cmd[index]["begin_playlist_songs"] = ""; //playlist_songs => `serverId` INT NOT NULL, `playlist_id` INT, `song_id` INT, `order_id` INT, `invoker_dbid` INT, `url` TEXT, `url_loader` TEXT, `loaded` BOOL, `metadata` TEXT PlaylistId current_playlist = 0; auto sql_result = sql::command(server->getSql(), "SELECT `playlist_id`, `song_id`, `order_id`, `invoker_dbid`, `url`, `url_loader`, `loaded`, `metadata` FROM `playlist_songs` WHERE `serverId` = :sid ORDER BY `playlist_id`", variable{":sid", server->getServerId()} ).query([&](int length, string* values, string* names) { for(int column = 0; column < length; column++) { if(names[column] == "song_id") cmd[index]["song_id"] = values[column]; else if(names[column] == "order_id") cmd[index]["song_order"] = values[column]; else if(names[column] == "invoker_dbid") cmd[index]["song_invoker"] = values[column]; else if(names[column] == "url") cmd[index]["song_url"] = values[column]; else if(names[column] == "url_loader") cmd[index]["song_url_loader"] = values[column]; else if(names[column] == "loaded") cmd[index]["song_loaded"] = values[column]; else if(names[column] == "metadata") cmd[index]["song_metadata"] = values[column]; else if(names[column] == "playlist_id") { try { auto playlist_id = stoll(values[column]); if(current_playlist != playlist_id) { cmd[index]["song_playlist_id"] = values[column]; /* song_playlist_id will be only set if the playlist id had changed */ current_playlist = playlist_id; } } catch(std::exception& ex) { logError(server->getServerId(), PREFIX + "Failed to parse playlist id. value: {}, message: {}", values[column], ex.what()); } } } index++; }); if(!sql_result) logError(server->getServerId(), PREFIX + "Failed to write playlist songs to snapshot. {}", sql_result.fmtStr()); cmd[index++]["end_playlist_songs"] = ""; } cmd[index++]["end_music"] = ""; } //Permissions { cmd[index++]["begin_permissions"] = ""; //Server groups { //List groups { cmd[index]["server_groups"] = ""; for(const auto& group : server->getGroupManager()->availableServerGroups(false)) { cmd[index]["id"] = group->groupId(); cmd[index]["name"] = group->name(); if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) break; cmd[index++]["end_group"] = ""; } cmd[index++]["end_groups"] = ""; } //List relations { cmd[index]["begin_relations"] = ""; writeRelations(server, GROUPTARGET_SERVER, cmd, index, version); cmd[index++]["end_relations"] = ""; } } //Channel groups { //List groups { cmd[index]["channel_groups"] = ""; for(const auto& group : server->getGroupManager()->availableChannelGroups(false)) { cmd[index]["id"] = group->groupId(); cmd[index]["name"] = group->name(); if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) break; cmd[index++]["end_group"] = ""; } cmd[index++]["end_groups"] = ""; } //List relations { cmd[index]["begin_relations"] = ""; writeRelations(server, GROUPTARGET_CHANNEL, cmd, index, version); cmd[index++]["end_relations"] = ""; } } //Client rights { cmd[index]["client_flat"] = ""; PermissionCommandTuple parm{cmd, index, version, 0, 0}; auto res = sql::command(server->getSql(), "SELECT `id`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `channelId` = 0 ORDER BY `id`", variable{":sid", server->getServerId()}, variable{":type", permission::SQL_PERM_USER} ).query([&](PermissionCommandTuple* commandIndex, int length, char** values, char**names){ auto type = permission::resolvePermissionData(permission::unknown); permission::PermissionValue value = 0, grant = 0; bool skipped = false, negated = false; ClientDbId cldbid = 0; for(int idx = 0; idx < length; idx++) { try { if(strcmp(names[idx], "id") == 0) cldbid = stoul(values[idx] && strlen(values[idx]) > 0 ? values[idx] : "0"); else if(strcmp(names[idx], "value") == 0) value = values[idx] && strlen(values[idx]) > 0 ? stoi(values[idx]) : permNotGranted; else if(strcmp(names[idx], "grant") == 0) grant = values[idx] && strlen(values[idx]) > 0 ? stoi(values[idx]) : permNotGranted; else if(strcmp(names[idx], "permId") == 0) { type = permission::resolvePermissionData(values[idx]); if(type->type == permission::unknown) { logError(0, "Could not parse client permission for snapshot (Invalid type {})! Skipping it!", values[idx]); return 0; } } else if(strcmp(names[idx], "flag_skip") == 0) skipped = values[idx] ? strcmp(values[idx], "1") == 0 : false; else if(strcmp(names[idx], "flag_negate") == 0) negated = values[idx] ? strcmp(values[idx], "1") == 0 : false; } catch (std::exception& ex) { logError(0, "Failed to write snapshot client permission (Skipping it)! Message: {} @ {} => {}", ex.what(), names[idx], values[idx]); return 0; } } if(type->type == permission::unknown) { logError(0, "Could not parse client permission for snapshot (Missing type)! Skipping it!"); return 0; } if(cldbid == 0) { logError(0, "Could not parse client permission for snapshot (Missing cldbid)! Skipping it!"); return 0; } if(value == permNotGranted && grant == permNotGranted && !skipped && !negated) return 0; if(commandIndex->client != cldbid) { commandIndex->client = cldbid; commandIndex->cmd[commandIndex->index]["id1"] = cldbid; commandIndex->cmd[commandIndex->index]["id2"] = 0; } SnapshotPermissionEntry{type, value, grant, negated, skipped}.write(commandIndex->cmd, commandIndex->index, permission::teamspeak::CLIENT, commandIndex->version); return 0; }, &parm); LOG_SQL_CMD(res); cmd[index++]["end_flat"] = ""; } //Channel rights { cmd[index]["channel_flat"] = ""; PermissionCommandTuple parm{cmd, index, version, 0, 0}; auto res = sql::command(server->getSql(), "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :sid AND `type` = :type", variable{":sid", server->getServerId()}, variable{":type", permission::SQL_PERM_CHANNEL}).query( [&](PermissionCommandTuple* commandIndex, int length, char** values, char**names){ auto type = permission::resolvePermissionData(permission::unknown); permission::PermissionValue value = 0, grant = 0; bool skipped = false, negated = false; ChannelId chid = 0; for(int idx = 0; idx < length; idx++) { try { if(strcmp(names[idx], "channelId") == 0) chid = stoul(values[idx] && strlen(values[idx]) > 0 ? values[idx] : "0"); else if(strcmp(names[idx], "value") == 0) value = values[idx] && strlen(values[idx]) > 0 ? stoi(values[idx]) : permNotGranted; else if(strcmp(names[idx], "grant") == 0) grant = values[idx] && strlen(values[idx]) > 0 ? stoi(values[idx]) : permNotGranted; else if(strcmp(names[idx], "permId") == 0) { type = permission::resolvePermissionData(values[idx]); if(type->type == permission::unknown) { logError(0, "Could not parse channel permission for snapshot (Invalid type {})! Skipping it!", values[idx]); return 0; } } else if(strcmp(names[idx], "flag_skip") == 0) skipped = values[idx] ? strcmp(values[idx], "1") == 0 : false; else if(strcmp(names[idx], "flag_negate") == 0) negated = values[idx] ? strcmp(values[idx], "1") == 0 : false; } catch (std::exception& ex) { logError(0, "Failed to write snapshot channel permission (Skipping it)! Message: {} @ {} => {}", ex.what(), names[idx], values[idx]); return 0; } } if(type->type == permission::unknown) { logError(0, "Could not parse channel permission for snapshot (Missing type)! Skipping it!"); return 0; } if(chid == 0) { logError(0, "Could not parse channel permission for snapshot (Missing channel id)! Skipping it!"); return 0; } if(value == permNotGranted && grant == permNotGranted && !skipped && !negated) return 0; if(commandIndex->channel != chid) { commandIndex->channel = chid; commandIndex->cmd[commandIndex->index]["id1"] = chid; commandIndex->cmd[commandIndex->index]["id2"] = 0; } SnapshotPermissionEntry{type, value, grant, negated, skipped}.write(commandIndex->cmd, commandIndex->index, permission::teamspeak::CHANNEL, commandIndex->version); return 0; }, &parm); LOG_SQL_CMD(res); cmd[index++]["end_flat"] = ""; } //Client channel rights { cmd[index]["channel_client_flat"] = ""; PermissionCommandTuple parm{cmd, index, version, 0, 0}; auto res = sql::command(server->getSql(), "SELECT `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `channelId` != 0", variable{":sid", server->getServerId()}, variable{":type", permission::SQL_PERM_USER}).query( [&](PermissionCommandTuple* commandIndex, int length, char** values, char**names){ auto type = permission::resolvePermissionData(permission::unknown); permission::PermissionValue value = 0, grant = 0; bool skipped = false, negated = false; ChannelId chid = 0; ClientDbId cldbid = 0; for(int idx = 0; idx < length; idx++) { try { if(strcmp(names[idx], "channelId") == 0) chid = stoul(values[idx] && strlen(values[idx]) > 0 ? values[idx] : "0"); if(strcmp(names[idx], "id") == 0) cldbid = stoul(values[idx] && strlen(values[idx]) > 0 ? values[idx] : "0"); else if(strcmp(names[idx], "value") == 0) value = values[idx] && strlen(values[idx]) > 0 ? stoi(values[idx]) : permNotGranted; else if(strcmp(names[idx], "grant") == 0) grant = values[idx] && strlen(values[idx]) > 0 ? stoi(values[idx]) : permNotGranted; else if(strcmp(names[idx], "permId") == 0) { type = permission::resolvePermissionData(values[idx]); if(type->type == permission::unknown) { logError(0, "Could not parse channel client permission for snapshot (Invalid type {})! Skipping it!", values[idx]); return 0; } } else if(strcmp(names[idx], "flag_skip") == 0) skipped = values[idx] ? strcmp(values[idx], "1") == 0 : false; else if(strcmp(names[idx], "flag_negate") == 0) negated = values[idx] ? strcmp(values[idx], "1") == 0 : false; } catch (std::exception& ex) { logError(0, "Failed to write snapshot channel client permission (Skipping it)! Message: {} @ {} => {}", ex.what(), names[idx], values[idx]); return 0; } } if(type->type == permission::unknown) { logError(0, "Could not parse channel client permission for snapshot (Missing type)! Skipping it!"); return 0; } if(chid == 0) { logError(0, "Could not parse channel client permission for snapshot (Missing channel id)! Skipping it!"); return 0; } if(cldbid == 0) { logError(0, "Could not parse channel client permission for snapshot (Missing cldbid)! Skipping it!"); return 0; } if(value == permNotGranted && grant == permNotGranted && !skipped && !negated) return 0; if(commandIndex->channel != chid || commandIndex->client != cldbid) { commandIndex->channel = chid; commandIndex->client = cldbid; commandIndex->cmd[commandIndex->index]["id1"] = chid; commandIndex->cmd[commandIndex->index]["id2"] = cldbid; } SnapshotPermissionEntry{type, value, grant, negated, skipped}.write(commandIndex->cmd, commandIndex->index, permission::teamspeak::CLIENT, commandIndex->version); return 0; }, &parm); LOG_SQL_CMD(res); cmd[index++]["end_flat"] = ""; } cmd[index++]["end_permissions"] = ""; } return true; }