diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index b3c5a83..038dde8 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -160,7 +160,7 @@ if (COMPILE_WEB_CLIENT) src/client/web/WSWebClient.cpp src/client/web/SampleHandler.cpp src/client/web/VoiceBridge.cpp - src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h src/lincense/LicenseService.cpp src/lincense/LicenseService.h src/groups/GroupManager.cpp src/groups/GroupManager.h src/groups/GroupAssignmentManager.cpp src/groups/GroupAssignmentManager.h src/groups/Group.cpp src/groups/Group.h) + src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h src/lincense/LicenseService.cpp src/lincense/LicenseService.h src/groups/GroupManager.cpp src/groups/GroupManager.h src/groups/GroupAssignmentManager.cpp src/groups/GroupAssignmentManager.h src/groups/Group.cpp src/groups/Group.h src/services/VirtualServerInformation.cpp src/services/VirtualServerInformation.h src/vserver/VirtualServerManager.cpp src/vserver/VirtualServerManager.h) endif () add_executable(PermHelper helpers/permgen.cpp) diff --git a/server/lock_concept b/server/lock_concept index f258142..2492a96 100644 --- a/server/lock_concept +++ b/server/lock_concept @@ -1,58 +1,85 @@ -CommandResult handleCommandClientUpdate(Command&); -CommandResult handleCommandClientEdit(Command&); -CommandResult handleCommandClientEdit(Command&, const std::shared_ptr& /* target */); -CommandResult handleCommandClientMove(Command&); -CommandResult handleCommandClientGetVariables(Command&); -CommandResult handleCommandClientKick(Command&); -CommandResult handleCommandClientPoke(Command&); +General lock order: +- Client execute lock +- Server state lock +- Server channel tree +- Client channel tree -CommandResult handleCommandChannelSubscribe(Command&); read lock: server channel tree => client lock write -CommandResult handleCommandChannelSubscribeAll(Command&); read lock: server channel tree => client lock write -CommandResult handleCommandChannelUnsubscribe(Command&); read lock: server channel tree => client lock write -CommandResult handleCommandChannelUnsubscribeAll(Command&); read lock: server channel tree => client lock write -CommandResult handleCommandChannelCreate(Command&); write lock server channel tree => iterate clients lock (write) -CommandResult handleCommandChannelDelete(Command&); write lock server channel tree => iterate clients lock (write) -CommandResult handleCommandChannelEdit(Command&); write lock server channel tree -CommandResult handleCommandChannelGetDescription(Command&); read lock: server channel tree -CommandResult handleCommandChannelMove(Command&); write lock server channel tree +When executing a command: + Lock order: + - Client execute lock + - Server state lock (Server should not try to change state while a client is executing something) + Notes: + The server might be null or the default server. + This must be checked via the given macro! -CommandResult handleCommandChannelAddPerm(Command&); read lock: server channel tree => all clients write lock -CommandResult handleCommandChannelDelPerm(Command&); read lock: server channel tree => all clients write lock +Disconnect/unregister a client: +- Lock client execute lock +- Lock server state +- Unregister client from server and set server variable to nullptr +- Underlying server (UDP, Web, etc. disconnect) -CommandResult handleCommandChannelGroupDel(Command&); read lock: server channel tree => all clients in the group should not be allowed to switch a channel -CommandResult handleCommandSetClientChannelGroup(Command&); read lock: server channel tree => client should not be allowed to switch channel +Client channel tree connect/mode/disconnect: +- Lock client execute lock +- Lock server channel tree in write mode +- Lock client channel tree in write mode +- Move client & notify (notify required all clients to have their channel tree locked in shared mode) -CommandResult handleCommandPluginCmd(Command&); +Command lock overview: + CommandResult handleCommandClientUpdate(Command&); + CommandResult handleCommandClientEdit(Command&); + CommandResult handleCommandClientEdit(Command&, const std::shared_ptr& /* target */); + CommandResult handleCommandClientMove(Command&); + CommandResult handleCommandClientGetVariables(Command&); + CommandResult handleCommandClientKick(Command&); + CommandResult handleCommandClientPoke(Command&); -CommandResult handleCommandClientMute(Command&); -CommandResult handleCommandClientUnmute(Command&); + CommandResult handleCommandChannelSubscribe(Command&); read lock: server channel tree => client lock write + CommandResult handleCommandChannelSubscribeAll(Command&); read lock: server channel tree => client lock write + CommandResult handleCommandChannelUnsubscribe(Command&); read lock: server channel tree => client lock write + CommandResult handleCommandChannelUnsubscribeAll(Command&); read lock: server channel tree => client lock write + CommandResult handleCommandChannelCreate(Command&); write lock server channel tree => iterate clients lock (write) + CommandResult handleCommandChannelDelete(Command&); write lock server channel tree => iterate clients lock (write) + CommandResult handleCommandChannelEdit(Command&); write lock server channel tree + CommandResult handleCommandChannelGetDescription(Command&); read lock: server channel tree + CommandResult handleCommandChannelMove(Command&); write lock server channel tree -//Original from query but still reachable for all -CommandResult handleCommandClientList(Command&); + CommandResult handleCommandChannelAddPerm(Command&); read lock: server channel tree => all clients write lock + CommandResult handleCommandChannelDelPerm(Command&); read lock: server channel tree => all clients write lock -CommandResult handleCommandClientFind(Command&); -CommandResult handleCommandClientInfo(Command&); + CommandResult handleCommandChannelGroupDel(Command&); read lock: server channel tree => all clients in the group should not be allowed to switch a channel + CommandResult handleCommandSetClientChannelGroup(Command&); read lock: server channel tree => client should not be allowed to switch channel -CommandResult handleCommandVerifyChannelPassword(Command&); read lock: server channel tree + CommandResult handleCommandPluginCmd(Command&); -handleCommandChannelFind read lock: server channel tree -handleCommandChannelInfo read lock: server channel tree + CommandResult handleCommandClientMute(Command&); + CommandResult handleCommandClientUnmute(Command&); -General command handling: client_command_lock - Ensure that only one command at time will be handeled + //Original from query but still reachable for all + CommandResult handleCommandClientList(Command&); + + CommandResult handleCommandClientFind(Command&); + CommandResult handleCommandClientInfo(Command&); + + CommandResult handleCommandVerifyChannelPassword(Command&); read lock: server channel tree + + handleCommandChannelFind read lock: server channel tree + handleCommandChannelInfo read lock: server channel tree + + General command handling: client_command_lock + Ensure that only one command at time will be handeled -Read access server channel tree: read lock channel_tree_lock -Write access server channel tree: lock channel_tree_lock -Write access client channel tree: read lock channel_tree_lock => lock client channel tree - if we write to the server channel tree no client should have their channel tree updated -Read access client channel tree: no lock required - Note: the server channel tree should not be accessed! + Read access server channel tree: read lock channel_tree_lock + Write access server channel tree: lock channel_tree_lock + Write access client channel tree: read lock channel_tree_lock => lock client channel tree + if we write to the server channel tree no client should have their channel tree updated + Read access client channel tree: no lock required + Note: the server channel tree should not be accessed! -Move client acts like access server channel tree: write lock channel_tree_lock => for each client write lock their tree + Move client acts like access server channel tree: write lock channel_tree_lock => for each client write lock their tree TODO: Some kind of perm channel lock TODO: Fix handleCommandChannelEdit -Test: Channel hide & show with clients! Multible clients as well! \ No newline at end of file +Test: Channel hide & show with clients! Multiple clients as well! \ No newline at end of file diff --git a/server/src/DatabaseHelper.cpp b/server/src/DatabaseHelper.cpp index 794ef24..61b3e01 100644 --- a/server/src/DatabaseHelper.cpp +++ b/server/src/DatabaseHelper.cpp @@ -159,10 +159,9 @@ void DatabaseHelper::deleteClient(const std::shared_ptr& server, //TODO delete complains } -inline sql::result load_permissions_v2(const std::shared_ptr& server, v2::PermissionRegister* manager, sql::command& command, bool test_channel /* only used for client permissions (client channel permissions) */) { +inline sql::result load_permissions_v2(VirtualServerId server_id, v2::PermissionRegister* manager, sql::command& command, bool test_channel /* only used for client permissions (client channel permissions) */) { auto start = system_clock::now(); - auto server_id = server ? server->getServerId() : 0; return command.query([&](int length, char** values, char** names){ permission::PermissionType key = permission::PermissionType::undefined; permission::PermissionValue value = permNotGranted, granted = permNotGranted; @@ -198,6 +197,8 @@ inline sql::result load_permissions_v2(const std::shared_ptr& ser return 0; } if(channel_id > 0 && test_channel) { + //TODO: Test for invalid channels + /* if(!server) logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]); else { @@ -205,6 +206,7 @@ inline sql::result load_permissions_v2(const std::shared_ptr& ser if(!channel) logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]); } + */ } @@ -282,7 +284,7 @@ std::shared_ptr DatabaseHelper::loadClientPermissionMana variable{":serverId", server ? server->getServerId() : 0}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}); - LOG_SQL_CMD(load_permissions_v2(server, permission_manager.get(), command, true)); + LOG_SQL_CMD(load_permissions_v2(server ? server->getServerId() : 0, permission_manager.get(), command, true)); } @@ -336,14 +338,14 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr DatabaseHelper::loadGroupPermissions(const std::shared_ptr& server, ts::GroupId group_id) { +std::shared_ptr DatabaseHelper::loadGroupPermissions(VirtualServerId server, ts::GroupId group_id, uint8_t type) { auto result = std::make_shared(); if(this->use_startup_cache && server) { shared_ptr entry; { threads::MutexLock lock(this->startup_lock); for(const auto& entries : this->startup_entries) { - if(entries->sid == server->getServerId()) { + if(entries->sid == server) { entry = entries; break; } @@ -361,19 +363,18 @@ std::shared_ptr DatabaseHelper::loadGroupPer //7931 auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", - variable{":serverId", server ? server->getServerId() : 0}, + variable{":serverId", server}, variable{":type", permission::SQL_PERM_GROUP}, variable{":id", group_id}); LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false)); return result; } -void DatabaseHelper::saveGroupPermissions(const std::shared_ptr &server, ts::GroupId group_id, const std::shared_ptr &permissions) { +void DatabaseHelper::saveGroupPermissions(VirtualServerId server_id, ts::GroupId group_id, const std::shared_ptr &permissions) { const auto updates = permissions->flush_db_updates(); if(updates.empty()) return; - auto server_id = server ? server->getServerId() : 0; for(auto& update : updates) { std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND); @@ -388,7 +389,7 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptrsql, query, - variable{":serverId", server ? server->getServerId() : 0}, + variable{":serverId", server_id}, variable{":id", group_id}, variable{":chId", 0}, variable{":type", permission::SQL_PERM_GROUP}, @@ -436,7 +437,7 @@ std::shared_ptr DatabaseHelper::loadPlaylist variable{":serverId", server ? server->getServerId() : 0}, variable{":type", permission::SQL_PERM_PLAYLIST}, variable{":id", playlist_id}); - LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false)); + LOG_SQL_CMD(load_permissions_v2(server ? server->getServerId() : 0, result.get(), command, false)); return result; } @@ -502,7 +503,7 @@ std::shared_ptr DatabaseHelper::loadChannelP variable{":chid", channel}, variable{":id", 0}, variable{":type", permission::SQL_PERM_CHANNEL}); - LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false)); + LOG_SQL_CMD(load_permissions_v2(server ? server->getServerId() : 0, result.get(), command, false)); return result; } diff --git a/server/src/DatabaseHelper.h b/server/src/DatabaseHelper.h index 60af788..4078086 100644 --- a/server/src/DatabaseHelper.h +++ b/server/src/DatabaseHelper.h @@ -97,8 +97,8 @@ namespace ts { std::shared_ptr loadChannelPermissions(const std::shared_ptr&, ChannelId); void saveChannelPermissions(const std::shared_ptr&, ChannelId, const std::shared_ptr& /* permission manager */); - std::shared_ptr loadGroupPermissions(const std::shared_ptr&, GroupId); - void saveGroupPermissions(const std::shared_ptr&, GroupId, const std::shared_ptr& /* permission manager */); + std::shared_ptr loadGroupPermissions(VirtualServerId, GroupId, uint8_t /* group type */); + void saveGroupPermissions(VirtualServerId, GroupId, const std::shared_ptr& /* permission manager */); std::shared_ptr loadPlaylistPermissions(const std::shared_ptr&, PlaylistId /* playlist id */); void savePlaylistPermissions(const std::shared_ptr&, PlaylistId, const std::shared_ptr& /* permission manager */); diff --git a/server/src/Group.h b/server/src/Group.h index ef1cc47..28804de 100644 --- a/server/src/Group.h +++ b/server/src/Group.h @@ -29,7 +29,9 @@ namespace ts { enum GroupTarget { GROUPTARGET_SERVER, - GROUPTARGET_CHANNEL + GROUPTARGET_CHANNEL, + + GROUPTARGET_UNKNOWN }; enum GroupNameMode { diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 54856ae..a2dc31f 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -560,6 +560,10 @@ bool ConnectedClient::notifyClientNeededPermissions() { cmd[index]["permid"] = value.first; cmd[index++]["permvalue"] = value.second.has_value ? value.second.value : 0; } + if(index == 0) { + cmd[index]["permid"] = permission::i_client_talk_power; + cmd[index++]["permvalue"] = 0; + } this->sendCommand(cmd); return true; diff --git a/server/src/client/DataClient.h b/server/src/client/DataClient.h index 9af04e8..1761a7c 100644 --- a/server/src/client/DataClient.h +++ b/server/src/client/DataClient.h @@ -105,7 +105,8 @@ namespace ts { std::shared_ptr clientPermissions = nullptr; std::shared_ptr _properties; - std::shared_ptr currentChannel = nullptr; + /* the current channel is save to access when the server channel tree has been locked (shared or exclusively) */ + std::shared_ptr currentChannel{nullptr}; }; } } \ No newline at end of file diff --git a/server/src/groups/Group.cpp b/server/src/groups/Group.cpp index 09ee3e8..0c3b792 100644 --- a/server/src/groups/Group.cpp +++ b/server/src/groups/Group.cpp @@ -7,15 +7,8 @@ using namespace ts::server::groups; -Group::Group(const std::shared_ptr &manager, ts::GroupId id, ts::server::groups::GroupType type, std::string name, - std::shared_ptr permissions) : manager_{manager}, group_id_{id}, type_{type}, name_{std::move(name)}, permissions_{std::move(permissions)} { } - -void Group::set_display_name(const std::string &name) { - this->name_ = name; - - auto handle = this->manager_.lock(); - if(handle) handle->update_group_name(this); -} +Group::Group(VirtualServerId sid, ts::GroupId id, ts::server::groups::GroupType type, std::string name, + std::shared_ptr permissions) : virtual_server_id_{sid}, group_id_{id}, type_{type}, name_{std::move(name)}, permissions_{std::move(permissions)} { } void Group::set_permissions(const std::shared_ptr &permissions) { assert(permissions); diff --git a/server/src/groups/Group.h b/server/src/groups/Group.h index c113078..1239360 100644 --- a/server/src/groups/Group.h +++ b/server/src/groups/Group.h @@ -7,7 +7,9 @@ namespace ts::server::groups { enum GroupType { GROUP_TYPE_QUERY, GROUP_TYPE_TEMPLATE, - GROUP_TYPE_NORMAL + GROUP_TYPE_NORMAL, + + GROUP_TYPE_UNKNOWN = 0xFF }; enum GroupNameMode { @@ -23,13 +25,15 @@ namespace ts::server::groups { class Group { friend class GroupManager; public: - Group(const std::shared_ptr& /* manager */, GroupId /* id */, GroupType /* type */, std::string /* name */, std::shared_ptr /* permissions */); + Group(VirtualServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, std::shared_ptr /* permissions */); ~Group() = default; /* information getters */ + [[nodiscard]] inline VirtualServerId virtual_server_id() const { return this->virtual_server_id_; } [[nodiscard]] inline GroupId group_id() const { return this->group_id_; } [[nodiscard]] inline GroupType group_type() const { return this->type_; } [[nodiscard]] inline const std::string& display_name() const { return this->name_; } + /* we're not returning a cr here because the permissions might get reloaded */ [[nodiscard]] inline std::shared_ptr permissions() { return this->permissions_; } [[nodiscard]] inline bool save_assignments() const { @@ -56,11 +60,8 @@ namespace ts::server::groups { return value.has_value ? value.value : 0; } - /* information setters */ - void set_display_name(const std::string& name); private: - std::weak_ptr manager_; - + const VirtualServerId virtual_server_id_; const GroupId group_id_; const GroupType type_; std::string name_; @@ -70,6 +71,13 @@ namespace ts::server::groups { void set_permissions(const std::shared_ptr& /* permissions */); }; - class ServerGroup : public Group { }; - class ChannelGroup : public Group { }; + class ServerGroup : public Group { + public: + ServerGroup(VirtualServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, std::shared_ptr /* permissions */); + }; + + class ChannelGroup : public Group { + public: + ChannelGroup(VirtualServerId /* server id */, GroupId /* id */, GroupType /* type */, std::string /* name */, std::shared_ptr /* permissions */); + }; } \ No newline at end of file diff --git a/server/src/groups/GroupManager.cpp b/server/src/groups/GroupManager.cpp index a851a25..405dd3c 100644 --- a/server/src/groups/GroupManager.cpp +++ b/server/src/groups/GroupManager.cpp @@ -1,5 +1,612 @@ // // Created by WolverinDEV on 03/03/2020. // - +#include #include "GroupManager.h" +#include "../vserver/VirtualServerBase.h" +#include "../InstanceHandler.h" + +using namespace ts::server::groups; + +GroupManager::GroupManager(ts::server::vserver::VirtualServerBase *server, std::shared_ptr parent) + : virtual_server_{server}, parent_manager_{std::move(parent)}, assignment_manager_{this} { + assert(server); +} + +GroupManager::~GroupManager() = default; + +ts::ServerId GroupManager::server_id() { + return this->virtual_server_->server_id(); +} + +sql::SqlManager* GroupManager::sql_manager() { + return this->virtual_server_->sql_manager(); +} + +bool GroupManager::initialize(const std::shared_ptr& self, std::string &error) { + assert(&*self == this); + this->weak_ref_ = self; + return this->assignment_manager_.initialize(error); +} + +GroupLoadResult GroupManager::load_data(bool initialize) { + std::lock_guard manage_lock{this->group_manage_lock_}; + { + std::lock_guard list_lock{this->group_lock_}; + this->server_groups_.clear(); + this->channel_groups_.clear(); + } + + auto res = sql::command(this->sql_manager(), "SELECT * FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->server_id()}).query(&GroupManager::insert_group_from_sql, this); + if(!res) { + LOG_SQL_CMD(res); + return GroupLoadResult::DATABASE_ERROR; + } + + if(this->server_groups_.empty() || this->channel_groups_.empty()) + return GroupLoadResult::NO_GROUPS; + + this->ensure_valid_default_groups(initialize); + return GroupLoadResult::SUCCESS; +} + +void GroupManager::unload_data() { + std::lock_guard manage_lock{this->group_manage_lock_}; + { + std::lock_guard list_lock{this->group_lock_}; + this->server_groups_.clear(); + this->channel_groups_.clear(); + } +} + +void GroupManager::reset_groups(bool db_cleanup) { + std::lock_guard manage_lock{this->group_manage_lock_}; + this->unload_data(); + + if(db_cleanup) { + LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type", + variable{":serverId", this->server_id()}, + variable{":type", ts::permission::SQL_PERM_GROUP}).execute()); + LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->server_id()}).execute()); + LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->server_id()}).execute()); + } + + if(auto error = this->load_data(true); error != GroupLoadResult::SUCCESS) + logCritical(this->server_id(), "Failed to load groups after group unload ({}). There might be no groups loaded now!", (int) error); +} + +int GroupManager::insert_group_from_sql(int length, std::string *values, std::string *names) { + GroupId group_id{0}; + GroupType group_type{GroupType::GROUP_TYPE_UNKNOWN}; + std::string group_name{}; + GroupTarget group_target{GroupTarget::GROUPTARGET_UNKNOWN}; + + for(size_t index = 0; index < length; index++) { + try { + if(names[index] == "groupId") + group_id = std::stoull(values[index]); + else if(names[index] == "target") + group_target = (GroupTarget) std::stoull(values[index]); + else if(names[index] == "type") + group_type = (GroupType) std::stoull(values[index]); + else if(names[index] == "displayName") + group_name = names[index]; + } catch(std::exception& ex) { + logWarning(this->server_id(), "Failed to parse group from database. Failed to parse column {} (value: {})", names[index], values[index]); + return 0; + } + } + + if(!group_id || group_type == GroupType::GROUP_TYPE_UNKNOWN || group_target == GroupTarget::GROUPTARGET_UNKNOWN) { + logWarning(this->server_id(), "Failed to query group from database. Invalid values found."); + return 0; + } + + auto database_target = (uint8_t) (group_target == GroupTarget::GROUPTARGET_SERVER ? DatabaseGroupTarget::SERVER : DatabaseGroupTarget::CHANNEL); + auto permissions = serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), group_id, database_target); + + if(group_target == GroupTarget::GROUPTARGET_SERVER) { + auto group = std::make_shared(this->server_id(), group_id, group_type, group_name, permissions); + + std::lock_guard lock{this->group_lock_}; + this->server_groups_.push_back(group); + } else { + auto group = std::make_shared(this->server_id(), group_id, group_type, group_name, permissions); + + std::lock_guard lock{this->group_lock_}; + this->channel_groups_.push_back(group); + } + + return 0; +} + +void GroupManager::ensure_valid_default_groups(bool initialize) { + assert(!this->channel_groups_.empty() && !this->server_groups_.empty()); + + auto fallback_server_group = this->server_groups_.front(); + auto fallback_channel_group = this->channel_groups_.front(); + + auto properties = this->virtual_server_->server_properties(); + /* default server music group */ + { + auto group_id = properties->find(property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP).as_save(); + auto group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) { + if(!initialize) + logWarning(this->server_id(), "Missing default music bot group ({}). Try to reinitialize from serverinstance_template_musicdefault_group.", group_id); + + group_id = serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as(); + group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) { + group = fallback_server_group; + logCritical(LOG_INSTANCE, "Failed to find default music bot group ({}). Using first available group ({}).", group_id, fallback_server_group->group_id()); + } + + auto group_name = group->display_name(); + group = this->find_server_group_by_name(GroupCalculateMode::LOCAL, group_name); + if(!group) { + group = fallback_server_group; + logError(this->server_id(), "Missing default music bot group and could not find group from template name ({}). Using {} as fallback.", group_name, group); + } + + properties->find(property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP) = group->group_id(); + } + } + + /* default server guest group */ + { + auto group_id = properties->find(property::VIRTUALSERVER_DEFAULT_SERVER_GROUP).as_save(); + auto group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) { + if(!initialize) + logWarning(this->server_id(), "Missing default server guest group ({}). Try to reinitialize from serverinstance_template_serverdefault_group.", group_id); + + group_id = serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP].as(); + group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) { + group = fallback_server_group; + logCritical(LOG_INSTANCE, "Failed to find default server guest group ({}). Using first available group ({}).", group_id, fallback_server_group->group_id()); + } + + auto group_name = group->display_name(); + group = this->find_server_group_by_name(GroupCalculateMode::LOCAL, group_name); + if(!group) { + group = fallback_server_group; + logError(this->server_id(), "Missing default server guest group and could not find group from template name ({}). Using {} as fallback.", group_name, group); + } + + properties->find(property::VIRTUALSERVER_DEFAULT_SERVER_GROUP) = group->group_id(); + } + } + + /* default channel admin group */ + { + auto group_id = properties->find(property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP).as_save(); + auto group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) { + if(!initialize) + logWarning(this->server_id(), "Missing default channel admin group ({}). Try to reinitialize from serverinstance_template_channeladmin_group.", group_id); + + group_id = serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP].as(); + group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) { + group = fallback_channel_group; + logCritical(LOG_INSTANCE, "Failed to find default channel admin group ({}). Using first available group ({}).", group_id, fallback_channel_group->group_id()); + } + + auto group_name = group->display_name(); + group = this->find_channel_group_by_name(GroupCalculateMode::LOCAL, group_name); + if(!group) { + group = fallback_channel_group; + logError(this->server_id(), "Missing default channel admin group and could not find group from template name ({}). Using {} as fallback.", group_name, group); + } + + properties->find(property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP) = group->group_id(); + } + } + + /* default channel group */ + { + auto group_id = properties->find(property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP).as_save(); + auto group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) { + if(!initialize) + logWarning(this->server_id(), "Missing default channel guest group ({}). Try to reinitialize from serverinstance_template_channeldefault_group.", group_id); + + group_id = serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP].as(); + group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) { + group = fallback_channel_group; + logCritical(LOG_INSTANCE, "Failed to find default channel guest group ({}). Using first available group ({}).", group_id, fallback_channel_group->group_id()); + } + + auto group_name = group->display_name(); + group = this->find_channel_group_by_name(GroupCalculateMode::LOCAL, group_name); + if(!group) { + group = fallback_channel_group; + logError(this->server_id(), "Missing default channel guest group and could not find group from template name ({}). Using {} as fallback.", group_name, group); + } + + properties->find(property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP) = group->group_id(); + } + } +} + +std::shared_ptr GroupManager::default_server_group(ClientType client_type, bool enforce_id) { + auto id = + client_type != ClientType::CLIENT_QUERY ? + this->virtual_server_->server_properties()->find(property::VIRTUALSERVER_DEFAULT_SERVER_GROUP).as_save() : + serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save(); + auto group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, id); + if(group || enforce_id) return group; + + std::lock_guard glock{this->group_lock_}; + return this->server_groups_.empty() ? nullptr : this->server_groups_.front(); +} + +std::shared_ptr GroupManager::default_channel_group(ClientType client_type, bool enforce_id) { + auto id = this->virtual_server_->server_properties()->find(property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP).as_save(); + auto group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, id); + if(group || enforce_id) return group; + + std::lock_guard glock{this->group_lock_}; + return this->channel_groups_.empty() ? nullptr : this->channel_groups_.front(); +} + +std::shared_ptr GroupManager::find_server_group(GroupCalculateMode mode, GroupId group_id) { + { + std::lock_guard glock{this->group_lock_}; + auto it = std::find_if(this->server_groups_.begin(), this->server_groups_.end(), [&](const std::shared_ptr& group) { + return group->group_id() == group_id; + }); + + if(it != this->server_groups_.end()) + return *it; + } + if(mode == GroupCalculateMode::GLOBAL && this->parent_manager_) + return this->parent_manager_->find_server_group(mode, group_id); + return nullptr; +} + +std::shared_ptr GroupManager::find_server_group_by_name(GroupCalculateMode mode, const std::string &name) { + { + std::string lname{name}; + std::transform(lname.begin(), lname.end(), lname.begin(), ::tolower); + + std::lock_guard glock{this->group_lock_}; + auto it = std::find_if(this->server_groups_.begin(), this->server_groups_.end(), + [&](const std::shared_ptr &group) { + std::string lgroup_name{group->name_}; + std::transform(lgroup_name.begin(), lgroup_name.end(), lgroup_name.begin(), + ::tolower); + return lname == lgroup_name; + }); + + if(it != this->server_groups_.end()) + return *it; + } + + if(mode == GroupCalculateMode::GLOBAL && this->parent_manager_) + return this->parent_manager_->find_server_group_by_name(mode, name); + return nullptr; +} + +std::shared_ptr GroupManager::find_channel_group(GroupCalculateMode mode, GroupId group_id) { + { + std::lock_guard glock{this->group_lock_}; + auto it = std::find_if(this->channel_groups_.begin(), this->channel_groups_.end(), [&](const std::shared_ptr& group) { + return group->group_id() == group_id; + }); + + if(it != this->channel_groups_.end()) + return *it; + } + + if(mode == GroupCalculateMode::GLOBAL && this->parent_manager_) + return this->parent_manager_->find_channel_group(mode, group_id); + return nullptr; +} + +std::shared_ptr GroupManager::find_channel_group_by_name(GroupCalculateMode mode, const std::string &name) { + { + std::string lname{name}; + std::transform(lname.begin(), lname.end(), lname.begin(), ::tolower); + + std::lock_guard glock{this->group_lock_}; + auto it = std::find_if(this->channel_groups_.begin(), this->channel_groups_.end(), + [&](const std::shared_ptr &group) { + std::string lgroup_name{group->name_}; + std::transform(lgroup_name.begin(), lgroup_name.end(), lgroup_name.begin(), + ::tolower); + return lname == lgroup_name; + }); + + if(it != this->channel_groups_.end()) + return *it; + } + + if(mode == GroupCalculateMode::GLOBAL && this->parent_manager_) + return this->parent_manager_->find_channel_group_by_name(mode, name); + return nullptr; +} + +GroupCreateResult GroupManager::create_server_group(GroupType type, const std::string &name, std::shared_ptr& result) { + std::lock_guard manage_lock{this->group_manage_lock_}; + auto self = this->weak_ref_.lock(); + if(!self) return GroupCreateResult::DATABASE_ERROR; + + if(this->find_server_group_by_name(GroupCalculateMode::LOCAL, name)) + return GroupCreateResult::NAME_ALREADY_IN_USED; + + auto group_id = this->generate_server_group_id(); + if(!group_id) + return GroupCreateResult::FAILED_TO_GENERATE_ID; + + auto res = sql::command(this->sql_manager(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)", + variable{":sid", this->server_id()}, variable{":gid", group_id}, variable{":target", DatabaseGroupTarget::SERVER}, variable{":type", type}, variable{":name", name}).execute(); + + if(!res) { + LOG_SQL_CMD(res); + return GroupCreateResult::DATABASE_ERROR; + } + + auto permissions = serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), group_id, (uint8_t) DatabaseGroupTarget::SERVER); + auto group = std::make_shared(self->server_id(), group_id, type, name, permissions); + { + std::lock_guard glock{this->group_lock_}; + this->server_groups_.push_back(group); + } + result = group; + return GroupCreateResult::SUCCESS; +} + +GroupCreateResult GroupManager::create_channel_group(GroupType type, const std::string &name, std::shared_ptr &result) { + std::lock_guard manage_lock{this->group_manage_lock_}; + auto self = this->weak_ref_.lock(); + if(!self) return GroupCreateResult::DATABASE_ERROR; + + if(this->find_channel_group_by_name(GroupCalculateMode::LOCAL, name)) + return GroupCreateResult::NAME_ALREADY_IN_USED; + + auto group_id = this->generate_channel_group_id(); + if(!group_id) + return GroupCreateResult::FAILED_TO_GENERATE_ID; + + auto res = sql::command(this->sql_manager(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)", + variable{":sid", this->server_id()}, variable{":gid", group_id}, variable{":target", DatabaseGroupTarget::CHANNEL}, variable{":type", type}, variable{":name", name}).execute(); + + if(!res) { + LOG_SQL_CMD(res); + return GroupCreateResult::DATABASE_ERROR; + } + + auto permissions = serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), group_id, (uint8_t) DatabaseGroupTarget::CHANNEL); + auto group = std::make_shared(this->server_id(), group_id, type, name, permissions); + { + std::lock_guard glock{this->group_lock_}; + this->channel_groups_.push_back(group); + } + result = group; + return GroupCreateResult::SUCCESS; +} + +GroupCopyResult GroupManager::copy_server_group(GroupId source, GroupType target_type, const std::string &display_name, std::shared_ptr& result) { + std::lock_guard manage_lock{this->group_manage_lock_}; + auto self = this->weak_ref_.lock(); + if(!self) return GroupCopyResult::DATABASE_ERROR; + + std::shared_ptr group{}; + auto create_result = this->create_server_group(target_type, display_name, group); + switch(create_result) { + case GroupCreateResult::SUCCESS: + break; + + case GroupCreateResult::DATABASE_ERROR: + return GroupCopyResult::DATABASE_ERROR; + + case GroupCreateResult::NAME_ALREADY_IN_USED: + return GroupCopyResult::NAME_ALREADY_IN_USE; + + case GroupCreateResult::FAILED_TO_GENERATE_ID: + return GroupCopyResult::FAILED_TO_GENERATE_ID; + } + + assert(group); + return this->copy_server_group_permissions(source, group->group_id()); +} + +GroupCopyResult GroupManager::copy_channel_group(GroupId source, GroupType target_type, const std::string &display_name, std::shared_ptr &result) { + std::lock_guard manage_lock{this->group_manage_lock_}; + auto self = this->weak_ref_.lock(); + if(!self) return GroupCopyResult::DATABASE_ERROR; + + std::shared_ptr group{}; + auto create_result = this->create_channel_group(target_type, display_name, group); + switch(create_result) { + case GroupCreateResult::SUCCESS: + break; + + case GroupCreateResult::DATABASE_ERROR: + return GroupCopyResult::DATABASE_ERROR; + + case GroupCreateResult::NAME_ALREADY_IN_USED: + return GroupCopyResult::NAME_ALREADY_IN_USE; + + case GroupCreateResult::FAILED_TO_GENERATE_ID: + return GroupCopyResult::FAILED_TO_GENERATE_ID; + } + + assert(group); + return this->copy_channel_group_permissions(source, group->group_id()); +} + +GroupCopyResult GroupManager::copy_server_group_permissions(GroupId source, GroupId target) { + std::lock_guard manage_lock{this->group_manage_lock_}; + auto self = this->weak_ref_.lock(); + if(!self) return GroupCopyResult::DATABASE_ERROR; + + auto source_group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, source); + if(!source_group) + return GroupCopyResult::UNKNOWN_SOURCE_GROUP; + + auto target_group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, target); + if(!target_group) + return GroupCopyResult::UNKNOWN_TARGET_GROUP; + + auto res = sql::command(this->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", + variable{":sid", this->server_id()}, variable{":type", ts::permission::SQL_PERM_GROUP}, variable{":id", target}).execute(); + if(!res) { + LOG_SQL_CMD(res); + return GroupCopyResult::DATABASE_ERROR; + } + + res = sql::command(this->sql_manager(), "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source", + variable{":ssid", source_group->virtual_server_id()}, + variable{":tsid", target_group->virtual_server_id()}, + variable{":type", ts::permission::SQL_PERM_GROUP}, + variable{":source", source}, + variable{":target", target}).execute(); + target_group->set_permissions(serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), target, (uint8_t) DatabaseGroupTarget::SERVER)); + LOG_SQL_CMD(res); + + return GroupCopyResult::SUCCESS; +} + +GroupCopyResult GroupManager::copy_channel_group_permissions(GroupId source, GroupId target) { + std::lock_guard manage_lock{this->group_manage_lock_}; + auto self = this->weak_ref_.lock(); + if(!self) return GroupCopyResult::DATABASE_ERROR; + + auto source_group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, source); + if(!source_group) + return GroupCopyResult::UNKNOWN_SOURCE_GROUP; + + auto target_group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, target); + if(!target_group) + return GroupCopyResult::UNKNOWN_TARGET_GROUP; + + auto res = sql::command(this->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", + variable{":sid", this->server_id()}, variable{":type", ts::permission::SQL_PERM_GROUP}, variable{":id", target}).execute(); + if(!res) { + LOG_SQL_CMD(res); + return GroupCopyResult::DATABASE_ERROR; + } + + res = sql::command(this->sql_manager(), "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source", + variable{":ssid", source_group->virtual_server_id()}, + variable{":tsid", target_group->virtual_server_id()}, + variable{":type", ts::permission::SQL_PERM_GROUP}, + variable{":source", source}, + variable{":target", target}).execute(); + target_group->set_permissions(serverInstance->databaseHelper()->loadGroupPermissions(this->server_id(), target, (uint8_t) DatabaseGroupTarget::CHANNEL)); + LOG_SQL_CMD(res); + + return GroupCopyResult::SUCCESS; +} + + +GroupRenameResult GroupManager::rename_server_group(GroupId group_id, const std::string &name) { + std::lock_guard manage_lock{this->group_manage_lock_}; + auto self = this->weak_ref_.lock(); + if(!self) return GroupRenameResult::DATABASE_ERROR; + + if(name.empty() || name.length() > 40) + return GroupRenameResult::NAME_INVALID; + + auto group = this->find_server_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) return GroupRenameResult::INVALID_GROUP_ID; + + if(this->find_server_group_by_name(GroupCalculateMode::LOCAL, name)) + return GroupRenameResult::NAME_ALREADY_USED; + + sql::command(this->sql_manager(), "UPDATE `groups` SET `displayName` = :name WHERE `serverId` = :server AND `groupId` = :group_id AND `target` = :target", + variable{":server", this->server_id()}, + variable{":target", DatabaseGroupTarget::SERVER}, + variable{":group_id", group_id}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future failed"}); + group->name_ = name; + return GroupRenameResult::SUCCESS; +} + +GroupRenameResult GroupManager::rename_channel_group(GroupId group_id, const std::string &name) { + std::lock_guard manage_lock{this->group_manage_lock_}; + auto self = this->weak_ref_.lock(); + if(!self) return GroupRenameResult::DATABASE_ERROR; + + if(name.empty() || name.length() > 40) + return GroupRenameResult::NAME_INVALID; + + auto group = this->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id); + if(!group) return GroupRenameResult::INVALID_GROUP_ID; + + if(this->find_channel_group_by_name(GroupCalculateMode::LOCAL, name)) + return GroupRenameResult::NAME_ALREADY_USED; + + sql::command(this->sql_manager(), "UPDATE `groups` SET `displayName` = :name WHERE `serverId` = :server AND `groupId` = :group_id AND `target` = :target", + variable{":server", this->server_id()}, + variable{":target", DatabaseGroupTarget::CHANNEL}, + variable{":group_id", group_id}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future failed"}); + group->name_ = name; + return GroupRenameResult::SUCCESS; +} + +GroupDeleteResult GroupManager::delete_server_group(GroupId group_id) { + std::lock_guard manage_lock{this->group_manage_lock_}; + auto self = this->weak_ref_.lock(); + if(!self) return GroupDeleteResult::DATABASE_ERROR; + + { + std::lock_guard glock{this->group_lock_}; + auto it = std::find_if(this->server_groups_.begin(), this->server_groups_.begin(), [&](const std::shared_ptr& group) { + return group->group_id() == group_id; + }); + if(it == this->server_groups_.end()) + return GroupDeleteResult::INVALID_GROUP_ID; + this->server_groups_.erase(it); + } + + sql::command(this->sql_manager(), "DELETE FROM WHERE `serverId` = :server AND `groupId` = :group_id AND `target` = :target", + variable{":server", this->server_id()}, + variable{":target", DatabaseGroupTarget::SERVER}, + variable{":group_id", group_id}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future failed"}); + return GroupDeleteResult::SUCCESS; +} + +GroupDeleteResult GroupManager::delete_channel_group(GroupId group_id) { + std::lock_guard manage_lock{this->group_manage_lock_}; + auto self = this->weak_ref_.lock(); + if(!self) return GroupDeleteResult::DATABASE_ERROR; + + { + std::lock_guard glock{this->group_lock_}; + auto it = std::find_if(this->channel_groups_.begin(), this->channel_groups_.begin(), [&](const std::shared_ptr& group) { + return group->group_id() == group_id; + }); + if(it == this->channel_groups_.end()) + return GroupDeleteResult::INVALID_GROUP_ID; + this->channel_groups_.erase(it); + } + + sql::command(this->sql_manager(), "DELETE FROM WHERE `serverId` = :server AND `groupId` = :group_id AND `target` = :target", + variable{":server", this->server_id()}, + variable{":target", DatabaseGroupTarget::CHANNEL}, + variable{":group_id", group_id}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future failed"}); + return GroupDeleteResult::SUCCESS; +} + +ts::GroupId GroupManager::generate_server_group_id() { + std::lock_guard glock{this->group_lock_}; + ts::GroupId highest_group_id{0}; + for(auto& group : this->channel_groups_) + highest_group_id = std::max(group->group_id(), highest_group_id); + + for(auto& group : this->server_groups_) + highest_group_id = std::max(group->group_id(), highest_group_id); + + if(this->parent_manager_) + highest_group_id = std::max(this->parent_manager_->generate_server_group_id(), highest_group_id); + return highest_group_id + 1; +} + +ts::GroupId GroupManager::generate_channel_group_id() { + return this->generate_server_group_id(); +} \ No newline at end of file diff --git a/server/src/groups/GroupManager.h b/server/src/groups/GroupManager.h index 5471f43..cfaf8ba 100644 --- a/server/src/groups/GroupManager.h +++ b/server/src/groups/GroupManager.h @@ -20,63 +20,114 @@ namespace ts::server { GLOBAL /* use the parent group manager as well, if existing */ }; - enum struct ActionReturnCode { - RENAME_NAME_ALREADY_USED, - RENAME_GROUP_INVALID_ID, + enum struct GroupLoadResult { + SUCCESS, + NO_GROUPS, + DATABASE_ERROR + }; - DELETE_GROUP_INVALID_ID, + enum struct GroupCreateResult { + SUCCESS, + NAME_ALREADY_IN_USED, + FAILED_TO_GENERATE_ID, + DATABASE_ERROR + }; - CUSTOM_ERROR + enum struct GroupCopyResult { + SUCCESS, + UNKNOWN_SOURCE_GROUP, + UNKNOWN_TARGET_GROUP, + NAME_ALREADY_IN_USE, + FAILED_TO_GENERATE_ID, + DATABASE_ERROR + }; + + enum struct GroupRenameResult { + SUCCESS, + INVALID_GROUP_ID, + NAME_ALREADY_USED, + NAME_INVALID, + DATABASE_ERROR + }; + + enum struct GroupDeleteResult { + SUCCESS, + INVALID_GROUP_ID, + DATABASE_ERROR }; class GroupManager { friend class Group; public: + enum struct DatabaseGroupTarget { + SERVER, + CHANNEL + }; + GroupManager(vserver::VirtualServerBase* /* virtual server */, std::shared_ptr /* parent */); ~GroupManager(); - bool initialize(std::string& /* error */); - bool load_data(std::string& /* error */); + bool initialize(const std::shared_ptr& /* self ref, */, std::string& /* error */); + GroupLoadResult load_data(bool /* initialize */ = false); void unload_data(); + void reset_groups(bool /* cleanup database */); [[nodiscard]] inline vserver::VirtualServerBase* virtual_server() { return this->virtual_server_; } [[nodiscard]] inline const std::shared_ptr& parent_manager() { return this->parent_manager_; } [[nodiscard]] inline GroupAssignmentManager& assignments() { return this->assignment_manager_; } - [[nodiscard]] std::deque> server_groups(GroupCalculateMode /* mode */); - [[nodiscard]] std::deque> channel_groups(GroupCalculateMode /* mode */); + [[nodiscard]] std::vector> server_groups(GroupCalculateMode /* mode */); + [[nodiscard]] std::vector> channel_groups(GroupCalculateMode /* mode */); - [[nodiscard]] std::shared_ptr default_server_group(ClientType /* client type */); - [[nodiscard]] std::shared_ptr default_channel_group(ClientType /* client type */); + [[nodiscard]] std::shared_ptr default_server_group(ClientType /* client type */, bool /* enforce id */ = false); + [[nodiscard]] std::shared_ptr default_channel_group(ClientType /* client type */, bool /* enforce id */ = false); - [[nodiscard]] std::shared_ptr find_server_group(GroupId /* group id */); - [[nodiscard]] std::shared_ptr find_channel_group(GroupId /* group id */); + [[nodiscard]] std::shared_ptr find_server_group(GroupCalculateMode /* mode */, GroupId /* group id */); + [[nodiscard]] std::shared_ptr find_channel_group(GroupCalculateMode /* mode */, GroupId /* group id */); - [[nodiscard]] std::shared_ptr create_server_group(GroupType type, const std::string& /* group name */); - [[nodiscard]] std::shared_ptr create_channel_group(GroupType type, const std::string& /* group name */); + [[nodiscard]] std::shared_ptr find_server_group_by_name(GroupCalculateMode /* mode */, const std::string& /* group name */); + [[nodiscard]] std::shared_ptr find_channel_group_by_name(GroupCalculateMode /* mode */, const std::string& /* group name */); - [[nodiscard]] ActionReturnCode copy_server_group(GroupId /* group id */, GroupType /* target group type */, const std::string& /* target group name */); - [[nodiscard]] ActionReturnCode copy_channel_group(GroupId /* group id */, GroupType /* target group type */, const std::string& /* target group name */); + [[nodiscard]] GroupCreateResult create_server_group(GroupType type, const std::string& /* group name */, std::shared_ptr& /* result */); + [[nodiscard]] GroupCreateResult create_channel_group(GroupType type, const std::string& /* group name */, std::shared_ptr& /* result */); - [[nodiscard]] ActionReturnCode copy_server_group_permissions(GroupId /* group id */, GroupId /* target group */); - [[nodiscard]] ActionReturnCode copy_channel_group_permissions(GroupId /* group id */, GroupId /* target group */); + [[nodiscard]] GroupCopyResult copy_server_group(GroupId /* group id */, GroupType /* target group type */, const std::string& /* target group name */, std::shared_ptr& /* result */); + [[nodiscard]] GroupCopyResult copy_channel_group(GroupId /* group id */, GroupType /* target group type */, const std::string& /* target group name */, std::shared_ptr& /* result */); - [[nodiscard]] ActionReturnCode rename_server_group(GroupId /* group id */, const std::string& /* target group name */); - [[nodiscard]] ActionReturnCode rename_channel_group(GroupId /* group id */, const std::string& /* target group name */); + [[nodiscard]] GroupCopyResult copy_server_group_permissions(GroupId /* group id */, GroupId /* target group */); + [[nodiscard]] GroupCopyResult copy_channel_group_permissions(GroupId /* group id */, GroupId /* target group */); - [[nodiscard]] ActionReturnCode delete_server_group(GroupId /* group id */); - [[nodiscard]] ActionReturnCode delete_channel_group(GroupId /* group id */); + [[nodiscard]] GroupRenameResult rename_server_group(GroupId /* group id */, const std::string& /* target group name */); + [[nodiscard]] GroupRenameResult rename_channel_group(GroupId /* group id */, const std::string& /* target group name */); + + [[nodiscard]] GroupDeleteResult delete_server_group(GroupId /* group id */); + [[nodiscard]] GroupDeleteResult delete_channel_group(GroupId /* group id */); private: vserver::VirtualServerBase* virtual_server_; + std::weak_ptr weak_ref_; std::shared_ptr parent_manager_; GroupAssignmentManager assignment_manager_; + /* recursive_mutex due to the copy server/channel group methods */ + std::recursive_mutex group_manage_lock_{}; std::mutex group_lock_{}; + /* I think std::vector is better here because we will iterate more often than add groups */ + std::vector> server_groups_{}; + std::vector> channel_groups_{}; + [[nodiscard]] sql::SqlManager* sql_manager(); - void update_group_name(Group* /* group */); + [[nodiscard]] ServerId server_id(); + + void ensure_valid_default_groups(bool /* initialize */); + + int insert_group_from_sql(int /* length */, std::string* /* values */, std::string* /* columns */); + [[nodiscard]] GroupId generate_server_group_id(); + [[nodiscard]] GroupId generate_channel_group_id(); }; } -} \ No newline at end of file +} + +DEFINE_TRANSFORMS(ts::server::groups::GroupManager::DatabaseGroupTarget, uint8_t); \ No newline at end of file diff --git a/server/src/services/ClientChannelService.cpp b/server/src/services/ClientChannelService.cpp index 31eb8a7..aff84ae 100644 --- a/server/src/services/ClientChannelService.cpp +++ b/server/src/services/ClientChannelService.cpp @@ -19,6 +19,7 @@ ts::ServerId ClientChannelService::get_server_id() const { } ChannelGroupInheritance ClientChannelService::calculate_channel_group(ChannelId channel_id, ClientDbId client_dbid, ClientType client_type) { + auto server_state_lock = this->virtual_server_->lock_server_status(); auto tree_lock = this->virtual_server_->lock_channel_clients(); tree_lock.auto_lock_shared(); @@ -67,6 +68,7 @@ ClientMoveResult ClientChannelService::client_ban(const std::shared_ptrvirtual_server_->lock_server_status(); std::unique_lock client_chan_tree_lock{target->get_channel_lock()}; auto old_channel = target->getChannel(); target->setChannel(nullptr); @@ -105,6 +107,7 @@ ClientMoveResult ClientChannelService::client_kick(const std::shared_ptrvirtual_server_->lock_server_status(); std::unique_lock client_chan_tree_lock{target->get_channel_lock()}; auto old_channel = target->getChannel(); target->setChannel(nullptr); @@ -147,6 +150,8 @@ ClientMoveResult ClientChannelService::client_move(const std::shared_ptrvirtual_server_->lock_server_status(); + TIMING_STEP(timings, "tree lock setup"); if(target->getChannel() == s_target_channel) return ClientMoveResult::SUCCESS; diff --git a/server/src/services/PermissionsService.cpp b/server/src/services/PermissionsService.cpp index 1095efe..575e232 100644 --- a/server/src/services/PermissionsService.cpp +++ b/server/src/services/PermissionsService.cpp @@ -6,9 +6,11 @@ #include "./PermissionsService.h" #include "../InstanceHandler.h" #include "../vserver/VirtualServerBase.h" +#include "../vserver/VirtualServer.h" #include "../groups/Group.h" #include "../groups/GroupManager.h" #include "../groups/GroupAssignmentManager.h" +#include "../client/ConnectedClient.h" using namespace ts::permission; using namespace ts::permission::v2; @@ -274,7 +276,7 @@ void PermissionService::load_group_assignments(const std::shared_ptrassignment_server_groups.reserve(assignments.size()); for(auto& entry : assignments) { - auto group = group_manager->find_server_group(entry); + auto group = group_manager->find_server_group(groups::GroupCalculateMode::GLOBAL, entry); if(!group) continue; cache->assignment_server_groups.push_back(group); @@ -304,7 +306,7 @@ void PermissionService::load_channel_assignment(const std::shared_ptrassignment_channel_group = group_manager->find_channel_group(group_id); + cache->assignment_channel_group = group_manager->find_channel_group(groups::GroupCalculateMode::GLOBAL, group_id); if(!cache->assignment_channel_group) cache->assignment_channel_group = group_manager->default_channel_group(cache->client_type); } @@ -316,4 +318,55 @@ void PermissionService::load_server_channel(const std::shared_ptrvirtual_server_->server_channel_tree(); auto tree_lock = this->virtual_server_->lock_channel_clients(); cache->server_channel = channel_tree->findChannel(channel_id); +} + +PermissionResetResult PermissionService::reset_server_permissions() { + //TODO: Delete all tokens? + + LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `permissions` WHERE `serverId` = :serverId", variable{":serverId", this->get_server_id()}).execute()); + LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->get_server_id()}).execute()); + LOG_SQL_CMD(sql::command(this->virtual_server_->sql_manager(), "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->get_server_id()}).execute()); + + auto group_manager = this->virtual_server_->group_manager(); + group_manager->reset_groups(false); + + auto properties = this->virtual_server_->server_properties(); + properties->find(property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY) = ""; + properties->find(property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY) = true; + if(auto vs = dynamic_cast(this->virtual_server_); vs){ + auto default_admin_group_id = serverInstance->properties().find(property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP).as_save(); + auto group = this->virtual_server_->group_manager()->find_server_group(groups::GroupCalculateMode::GLOBAL, default_admin_group_id); + if(!group) { + logCritical(LOG_INSTANCE, "Missing default template server admin group ({}). We're not generating an admin token.", default_admin_group_id); + } else { + auto group_name = group->display_name(); + group = this->virtual_server_->group_manager()->find_server_group_by_name(groups::GroupCalculateMode::LOCAL, group_name); + if(!group) { + logError(this->get_server_id(), "Could not find server admin group from template name ({}). We're not generating an admin token.", group_name, group); + } else { + auto token = vs->token_manager().createToken(server::tokens::TOKEN_SERVER, group->group_id(), "Default server token for the server admin."); + if(!token) { + logError(this->get_server_id(), "Failed to generate default server admin token."); + } else { + properties->find(property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY) = token->token; + properties->find(property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY) = true; + } + } + } + } + + for(const auto& client : this->virtual_server_->connected_clients(false)) { + if(client->getType() != ClientType::CLIENT_QUERY) { + client->notifyServerGroupList(); + client->notifyChannelGroupList(); + } + + if(this->notifyClientPropertyUpdates(client, group_manager->assignments().update_client_group_properties(client, client->getChannelId()))) { + if(client->update_cached_permissions()) /* update cached calculated permissions */ + client->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + } + client->updateChannelClientProperties(true, true); + } + + return PermissionResetResult::SUCCESS; } \ No newline at end of file diff --git a/server/src/services/PermissionsService.h b/server/src/services/PermissionsService.h index 3270849..0b17b5a 100644 --- a/server/src/services/PermissionsService.h +++ b/server/src/services/PermissionsService.h @@ -29,6 +29,10 @@ namespace ts::server::permissions { ChannelId last_server_channel{0}; }; + enum struct PermissionResetResult { + SUCCESS + }; + class PermissionService { public: explicit PermissionService(vserver::VirtualServerBase*); @@ -39,6 +43,8 @@ namespace ts::server::permissions { bool load_data(std::string& /* error */); void unload_data(); + PermissionResetResult reset_server_permissions(); + permission::v2::PermissionFlaggedValue calculate_client_permission( permission::PermissionType, ClientDbId, diff --git a/server/src/services/VirtualServerInformation.cpp b/server/src/services/VirtualServerInformation.cpp new file mode 100644 index 0000000..77ebf67 --- /dev/null +++ b/server/src/services/VirtualServerInformation.cpp @@ -0,0 +1,15 @@ +// +// Created by WolverinDEV on 04/03/2020. +// + +#include "./VirtualServerInformation.h" +#include "../vserver/VirtualServerBase.h" + +using namespace ts::server::vserver; + +InformationService::InformationService(ts::server::vserver::VirtualServerBase *handle) : virtual_server_{handle} {} +InformationService::~InformationService() {} + +float InformationService::averagePing() { + +} \ No newline at end of file diff --git a/server/src/services/VirtualServerInformation.h b/server/src/services/VirtualServerInformation.h new file mode 100644 index 0000000..5699c18 --- /dev/null +++ b/server/src/services/VirtualServerInformation.h @@ -0,0 +1,18 @@ +#pragma once + +namespace ts::server::vserver { + class VirtualServerBase; + + class InformationService { + public: + explicit InformationService(VirtualServerBase*); + ~InformationService(); + + [[nodiscard]] float averagePing(); + [[nodiscard]] float averagePacketLoss(); + + [[nodiscard]] bool could_default_create_channel(); + private: + VirtualServerBase* virtual_server_; + }; +} \ No newline at end of file diff --git a/server/src/vserver/VirtualServerBase.cpp b/server/src/vserver/VirtualServerBase.cpp index 33bffe5..53bdd89 100644 --- a/server/src/vserver/VirtualServerBase.cpp +++ b/server/src/vserver/VirtualServerBase.cpp @@ -3,6 +3,9 @@ // #include "./VirtualServerBase.h" +#include "./VirtualServerBase.h" +#include "../client/ConnectedClient.h" +#include "VirtualServerBase.h" #include using namespace ts::server::vserver; @@ -75,4 +78,137 @@ VirtualServerStopResult VirtualServerBase::stop_server() { VirtualServerStartResult VirtualServerBase::start_server_(ts::rwshared_lock &slock, std::string &error) { //TODO: Load server permissions, etc +} + +VirtualServerClientRegisterResult VirtualServerBase::register_client(const std::shared_ptr &client) { + assert(client); + + ts::rwshared_lock status_lock{this->status_mutex_}; + if(this->server_status() != VirtualServerStatus::VIRTUAL && this->server_status() != VirtualServerStatus::RUNNING) + return VirtualServerClientRegisterResult::SERVER_NOT_ONLINE; + + { + std::lock_guard client_lock{this->registered_clients.mutex}; + if(client->getClientId() > 0) { + logCritical(this->server_id(), "Client {} ({}|{}) has been already registered somewhere!", client->getDisplayName(), client->getClientId(), client->getUid()); + return VirtualServerClientRegisterResult::CLIENT_ALREADY_KNOWN; + } + + ClientId client_id = 0; + ClientId max_client_id = this->registered_clients.clients.size(); + + while(client_id < max_client_id && this->registered_clients.clients[client_id]) + client_id++; + + if(client_id > 1024 * 8) + return VirtualServerClientRegisterResult::PROTOCOL_LIMIT_REACHED; + + if(client_id == max_client_id) + this->registered_clients.clients.push_back(client); + else + this->registered_clients.clients[client_id] = client; + this->registered_clients.count++; + client->setClientId(client_id); + } + + { + std::lock_guard nlock{this->client_nickname_lock_}; + + auto login_name = client->getDisplayName(); + while(login_name.length() < 3) + login_name += "."; + + if(client->getExternalType() == ClientType::CLIENT_TEAMSPEAK) + client->properties()[property::CLIENT_LOGIN_NAME] = login_name; + + std::shared_ptr found_client = nullptr; + + auto client_name = login_name; + size_t counter = 0; + + { + std::lock_guard clients_lock(this->registered_clients.mutex); + while(true) { + for(auto& _client : this->registered_clients.clients) { + if(!_client) continue; + + if(_client->getDisplayName() == client_name && _client != client) + goto increase_name; + } + goto nickname_valid; + + increase_name: + client_name = login_name + std::to_string(++counter); + } + } + nickname_valid: + client->setDisplayName(client_name); + } + + { + auto properties = this->server_properties(); + if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_WEB) { + properties->find(property::VIRTUALSERVER_CLIENT_CONNECTIONS) ++; //increase client connections + properties->find(property::VIRTUALSERVER_LAST_CLIENT_CONNECT) = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } else if(client->getType() == ClientType::CLIENT_QUERY) { + properties->find(property::VIRTUALSERVER_LAST_QUERY_CONNECT) ++; //increase query connections + properties->find(property::VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS) = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + } + + return VirtualServerClientRegisterResult::SUCCESS; +} + +void VirtualServerBase::unregister_client(const std::shared_ptr &client) { + ts::rwshared_lock status_lock{this->status_mutex_, ts::rw_lock_defered}; + { + auto execute_lock = client->lock_command_handling(); + if(client->getChannel()) { + auto tree_lock = this->lock_channel_clients(); + auto result = this->channel_service().client_move(client, nullptr, nullptr, "", ViewReasonId::VREASON_SERVER_LEFT, false, tree_lock); + if(result != channels::ClientMoveResult::SUCCESS) + logCritical(this->server_id(), "Failed to unregister client from channel tree on client unregister ({}).", (int) result); + } + + status_lock.auto_lock_shared(); + + auto client_id = client->getClientId(); + if(client_id == 0) return; /* client hasn't been registered */ + + { + std::lock_guard lock(this->registered_clients.mutex); + if(client_id >= this->registered_clients.clients.size()) { + logCritical(this->server_id(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister client.", client->getDisplayName(), client_id, client->getUid()); + } else { + auto& client_container = this->registered_clients.clients[client_id]; + if(client_container != client) { + logCritical(this->server_id(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister client.", client->getDisplayName(), client_id, client->getUid()); + } else { + client_container.reset(); + this->registered_clients.count--; + } + } + } + } + + { + auto properties = this->server_properties(); + if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_WEB) { + properties->find(property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT) = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } else if(client->getType() == ClientType::CLIENT_QUERY) { + properties->find(property::VIRTUALSERVER_LAST_QUERY_DISCONNECT) = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + } + + { + if(!chan_tree_lock.owns_lock()) + chan_tree_lock.lock(); + + if(cl->currentChannel) //We dont have to make him invisible if he hasnt even a channel + this->client_move(cl, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock); + } + + serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions); + cl->setClientId(0); + return true; } \ No newline at end of file diff --git a/server/src/vserver/VirtualServerBase.h b/server/src/vserver/VirtualServerBase.h index 5efa564..19b3847 100644 --- a/server/src/vserver/VirtualServerBase.h +++ b/server/src/vserver/VirtualServerBase.h @@ -57,6 +57,19 @@ namespace ts::server::vserver { STATE_LOCK_FAILED }; + enum struct VirtualServerClientRegisterResult { + SUCCESS, + PROTOCOL_LIMIT_REACHED, + CLIENT_ALREADY_KNOWN, + SERVER_NOT_ONLINE + }; + +#define var_access_requires_server_loaded(variable) \ +do { \ + assert(this->status_ != VirtualServerStatus::STOPPED); \ + return variable; \ +} while(0); + class VirtualServerBase { public: //TODO: Constructor @@ -85,15 +98,15 @@ namespace ts::server::vserver { [[nodiscard]] inline ts::rwshared_lock lock_channel_clients() { return ts::rwshared_lock{this->channel_clients_lock_, ts::rw_lock_shared}; } [[nodiscard]] inline std::mutex& client_nickname_lock() { return this->client_nickname_lock_; } - [[nodiscard]] inline const std::shared_ptr& server_properties() { return this->server_properties_; } - [[nodiscard]] inline const std::shared_ptr& server_channel_tree() { return this->server_channel_tree_; } + [[nodiscard]] inline const std::shared_ptr& server_properties() { var_access_requires_server_loaded(this->server_properties_); } + [[nodiscard]] inline const std::shared_ptr& server_channel_tree() { var_access_requires_server_loaded(this->server_channel_tree_); } - [[nodiscard]] inline const std::shared_ptr& group_manager() { return this->group_manager_; } + [[nodiscard]] inline const std::shared_ptr& group_manager() { var_access_requires_server_loaded(this->group_manager_); } /* client management */ /* this may block until the current command of the client has been finished executed */ void unregister_client(const std::shared_ptr& /* client */); - void register_client(const std::shared_ptr& /* client */); + VirtualServerClientRegisterResult register_client(const std::shared_ptr& /* client */); /* ATTENTION: lock_channel_clients should be acquired for the following methods. Else the client might be disconnected while working with him. */ [[nodiscard]] std::vector> connected_clients(bool /* must be registered within the channel tree */); diff --git a/server/src/vserver/VirtualServerManager.cpp b/server/src/vserver/VirtualServerManager.cpp new file mode 100644 index 0000000..21a4f1c --- /dev/null +++ b/server/src/vserver/VirtualServerManager.cpp @@ -0,0 +1,5 @@ +// +// Created by WolverinDEV on 04/03/2020. +// + +#include "VirtualServerManager.h" diff --git a/server/src/vserver/VirtualServerManager.h b/server/src/vserver/VirtualServerManager.h new file mode 100644 index 0000000..16f483a --- /dev/null +++ b/server/src/vserver/VirtualServerManager.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace ts::server::vserver { + enum struct VirtualServerStartResult { + + }; + + class VirtualServerManager { + public: + explicit VirtualServerManager(); + ~VirtualServerManager(); + + bool initialize(std::string& /* error */); + + + + private: + }; +} \ No newline at end of file diff --git a/shared b/shared index ae4751e..0d0d7dd 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit ae4751ee6fcb857771ff5f4e57459a75638e2b1f +Subproject commit 0d0d7dd1924ee93bcbe4875c3cf007117d05e4d7