diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index c158559..849a713 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -220,7 +220,7 @@ target_link_libraries(PermMapHelper SET(CPACK_PACKAGE_VERSION_MAJOR "1") SET(CPACK_PACKAGE_VERSION_MINOR "5") -SET(CPACK_PACKAGE_VERSION_PATCH "0") +SET(CPACK_PACKAGE_VERSION_PATCH "1") if (BUILD_TYPE_NAME EQUAL OFF) SET(CPACK_PACKAGE_VERSION_DATA "beta") elseif (BUILD_TYPE_NAME STREQUAL "") diff --git a/server/src/Group.cpp b/server/src/Group.cpp index fd48f8d..82f5966 100644 --- a/server/src/Group.cpp +++ b/server/src/Group.cpp @@ -452,8 +452,6 @@ bool GroupManager::deleteGroup(std::shared_ptr group) { flag_sql |= !res; serverInstance->databaseHelper()->deleteGroupArtifacts(this->getServerId(), group->groupId()); - if(auto server{this->server.lock()}; server) - server->getTokenManager()->handleGroupDelete(group->groupId()); if(flag_sql) logError(this->getServerId(), "Could not delete group {} ({}) from database. May leader to invalid data", group->name(), group->groupId()); diff --git a/server/src/InstanceHandler.cpp b/server/src/InstanceHandler.cpp index 48afe3f..be87eac 100644 --- a/server/src/InstanceHandler.cpp +++ b/server/src/InstanceHandler.cpp @@ -515,11 +515,10 @@ void InstanceHandler::tickInstance() { if(memcleanTimestamp + minutes(10) < now) { memcleanTimestamp = now; { - { - ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5)); - auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS); - if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0) - logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal); + ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5)); + auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS); + if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0) { + logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal); } } } @@ -532,7 +531,7 @@ void InstanceHandler::tickInstance() { auto command = this->sql->sql()->getType() == sql::TYPE_SQLITE ? "SELECT * FROM `sqlite_master`" : "SHOW TABLES"; auto result = sql::command(this->getSql(), command).query([command](int, string*, string*){ return 0; }); if(!result) { - logCritical(LOG_INSTANCE, "Dummy sql connection test faild! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr()); + logCritical(LOG_INSTANCE, "Dummy sql connection test failed! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr()); logCritical(LOG_INSTANCE, "Stopping instance!"); ts::server::shutdownInstance("invalid sql connection!"); } diff --git a/server/src/TS3ServerClientManager.cpp b/server/src/TS3ServerClientManager.cpp index 2a7654a..9491af0 100644 --- a/server/src/TS3ServerClientManager.cpp +++ b/server/src/TS3ServerClientManager.cpp @@ -174,31 +174,74 @@ void VirtualServer::unregisterInternalClient(std::shared_ptr cl } bool VirtualServer::assignDefaultChannel(const shared_ptr& client, bool join) { - shared_lock server_channel_lock(this->channel_tree_lock); - std::shared_ptr channel = nullptr; - if(client->properties()->hasProperty(property::CLIENT_DEFAULT_CHANNEL) && !client->properties()[property::CLIENT_DEFAULT_CHANNEL].as().empty()) { - auto str = client->properties()[property::CLIENT_DEFAULT_CHANNEL].as(); - if(str[0] == '/' && str.find_first_not_of("0123456789", 1) == string::npos) - channel = this->channelTree->findChannel(static_cast(stoll(str.substr(1)))); - else - channel = this->channelTree->findChannelByPath(str); - if (channel) { - if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) { - logMessage(this->serverId, "{} Client tried to connect to a channel which he hasn't permission for. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name()); - channel = nullptr; - } else if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true) && !permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) { - logMessage(this->serverId, "{} Client tried to connect to a channel which is password protected and he hasn't the right password. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name()); - channel = nullptr; - } - } else - logMessage(this->serverId, "{} Client {}/{} tried to join on a not existing channel. Name: {}", - CLIENT_STR_LOG_PREFIX_(client), - client->getDisplayName(), client->getUid(), - client->properties()[property::CLIENT_DEFAULT_CHANNEL].as()); - } - if(!channel) channel = this->channelTree->getDefaultChannel(); - if(!channel) return false; + std::shared_lock server_channel_lock{this->channel_tree_lock}; + std::shared_ptr channel{}; + auto requested_channel_path = client->properties()[property::CLIENT_DEFAULT_CHANNEL].value(); + if(!requested_channel_path.empty()) { + if (requested_channel_path[0] == '/' && requested_channel_path.find_first_not_of("0123456789", 1) == std::string::npos) { + ChannelId channel_id{0}; + try { + channel_id = std::stoull(requested_channel_path.substr(1)); + } catch (std::exception&) { + logTrace(this->getServerId(), "{} Failed to parse provided channel path as channel id."); + } + + if(channel_id > 0) { + channel = this->channelTree->findChannel(channel_id); + } + } else { + channel = this->channelTree->findChannelByPath(requested_channel_path); + } + } + + if(channel) { + /* Client proposes a target channel */ + auto& channel_whitelist = client->join_whitelisted_channel; + auto whitelist_entry = std::find_if(channel_whitelist.begin(), channel_whitelist.end(), [&](const auto& entry) { return entry.first == channel->channelId(); }); + + auto client_channel_password = client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD].value(); + if(whitelist_entry != channel_whitelist.end()) { + debugMessage(this->getServerId(), "{} Allowing client to join channel {} because the token he used explicitly allowed it.", client->getLoggingPrefix(), channel->channelId()); + + if(whitelist_entry->second != "ignore") { + if (!channel->passwordMatch(client_channel_password, true)) { + if (!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) { + channel = nullptr; + goto skip_permissions; + } + } + } + goto skip_permissions; + } + + if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) { + debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't enough join power.", client->getLoggingPrefix(), channel->channelId()); + channel = nullptr; + goto skip_permissions; + } + + if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true)) { + if(!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) { + debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't given the right channel password.", client->getLoggingPrefix(), channel->channelId()); + channel = nullptr; + goto skip_permissions; + } + } + + skip_permissions:; + } + + if(!channel) { + /* Client did not propose a channel or the proposed channel got rejected */ + channel = this->channelTree->getDefaultChannel(); + if(!channel) { + logCritical(this->getServerId(), "Channel tree is missing the default channel."); + return false; + } + } + + debugMessage(this->getServerId(), "{} Using channel {} as default client channel.", client->getLoggingPrefix(), channel->channelId()); if(join) { server_channel_lock.unlock(); unique_lock server_channel_w_lock(this->channel_tree_lock); @@ -321,10 +364,13 @@ void VirtualServer::notify_client_kick( * Note: channel cant be a ref because the channel itself gets deleted! */ void VirtualServer::delete_channel(shared_ptr channel, const shared_ptr &invoker, const std::string& kick_message, unique_lock &tree_lock, bool temp_delete) { - if(!tree_lock.owns_lock()) + if(!tree_lock.owns_lock()) { tree_lock.lock(); - if(channel->deleted) + } + + if(channel->deleted) { return; + } deque> clients; { @@ -365,6 +411,8 @@ void VirtualServer::delete_channel(shared_ptr channel, const unique_lock client_channel_lock(client->channel_lock); client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker); }); + + this->tokenManager->handle_channel_deleted(channel->channelId()); } void VirtualServer::client_move( diff --git a/server/src/TS3ServerHeartbeat.cpp b/server/src/TS3ServerHeartbeat.cpp index a83b4f8..baa3834 100644 --- a/server/src/TS3ServerHeartbeat.cpp +++ b/server/src/TS3ServerHeartbeat.cpp @@ -76,6 +76,9 @@ void VirtualServer::executeServerTick() { case ClientType::CLIENT_MUSIC: queryOnline++; break; + + case ClientType::CLIENT_INTERNAL: + case ClientType::MAX: default: break; } @@ -84,8 +87,10 @@ void VirtualServer::executeServerTick() { properties()[property::VIRTUALSERVER_UPTIME] = std::chrono::duration_cast(std::chrono::system_clock::now() - this->startTimestamp).count(); properties()[property::VIRTUALSERVER_CLIENTS_ONLINE] = clientOnline + queryOnline; properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE] = queryOnline; - if(clientOnline + queryOnline == 0) //We don't need to tick, when server is empty! + if(clientOnline + queryOnline == 0) { + //We don't need to tick, when server is empty! return; + } properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = this->channelTree->channel_count(); properties()[property::VIRTUALSERVER_TOTAL_PING] = this->generate_network_report().average_ping; END_TIMINGS(timing_update_states); @@ -149,9 +154,10 @@ void VirtualServer::executeServerTick() { } } - if(cl->clientPermissions->require_db_updates()) { + auto client_permissions = cl->clientPermissions; + if(client_permissions->require_db_updates()) { auto begin = system_clock::now(); - serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions); + serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), client_permissions); auto end = system_clock::now(); debugMessage(this->serverId, "Saved client permissions for client {} ({}) in {}ms", cl->getClientDatabaseId(), cl->getDisplayName(), duration_cast(end - begin).count()); } diff --git a/server/src/VirtualServer.cpp b/server/src/VirtualServer.cpp index 9c72191..0916f77 100644 --- a/server/src/VirtualServer.cpp +++ b/server/src/VirtualServer.cpp @@ -220,8 +220,8 @@ bool VirtualServer::initialize(bool test_properties) { if(default_channel->properties()[property::CHANNEL_FLAG_PASSWORD].as()) default_channel->properties()[property::CHANNEL_FLAG_PASSWORD] = false; - this->tokenManager = new token::TokenManager(this); - this->tokenManager->loadTokens(); + this->tokenManager = new token::TokenManager(this->sql, this->getServerId()); + this->tokenManager->initialize_cache(); this->complains = new ComplainManager(this); if(!this->complains->loadComplains()) logError(this->serverId, "Could not load complains"); @@ -1137,7 +1137,7 @@ VirtualServer::NetworkReport VirtualServer::generate_network_report() { return result; } -bool VirtualServer::resetPermissions(std::string& token) { +bool VirtualServer::resetPermissions(std::string& new_permission_token) { LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` != :channel_type", variable{":serverId", this->serverId}, variable{":channel_type", permission::SQL_PERM_CHANNEL}).execute()); LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute()); LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute()); @@ -1200,23 +1200,26 @@ bool VirtualServer::resetPermissions(std::string& token) { this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_admin->name()).front()->groupId(); this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_guest->name()).front()->groupId(); - auto token_admin = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_admin->name()).front()->groupId(); - auto created = this->tokenManager->createToken(token::TOKEN_SERVER, token_admin, "Default server token for the server admin."); - if(!created) { - logCritical(this->serverId, "Failed to generate default serveradmin token!"); + auto server_admin_group_id = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_admin->name()).front()->groupId(); + auto token = this->tokenManager->create_token(0, "Default server admin token", 1, std::chrono::system_clock::time_point{}); + if(!token) { + logCritical(this->serverId, "Failed to register the default server admin token."); + this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; } else { - token = created->token; - this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = token; + std::vector actions{}; + actions.push_back(token::TokenAction{ + .id = 0, + .type = token::ActionType::AddServerGroup, + .id1 = server_admin_group_id, + .id2 = 0 + }); + + this->tokenManager->add_token_actions(token->id, actions); + new_permission_token = token->token; + + this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = token->token; this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = true; } - if(this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY].as()) { - auto requested_token = this->tokenManager->findToken(this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY]); - if(!requested_token) { - logError(this->serverId, "Failed to resolve default token! Don't ask for privilege key anymore."); - this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; - this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = ""; - } - } this->ensureValidDefaultGroups(); for(const auto& client : this->getClients()) { diff --git a/server/src/VirtualServer.h b/server/src/VirtualServer.h index 690cc61..0ec9c9a 100644 --- a/server/src/VirtualServer.h +++ b/server/src/VirtualServer.h @@ -191,8 +191,8 @@ namespace ts { inline GroupManager* getGroupManager() { return this->groups; } inline rtc::Server& rtc_server() { return *this->rtc_server_; } - [[nodiscard]] inline auto getTokenManager() -> token::TokenManager* { - return this->tokenManager; + [[nodiscard]] inline auto& getTokenManager() { + return *this->tokenManager; } bool notifyServerEdited(std::shared_ptr, std::deque keys); diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 4e4ebb9..db366cc 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -17,7 +17,6 @@ using namespace std; using namespace std::chrono; using namespace ts; using namespace ts::server; -using namespace ts::token; extern ts::server::InstanceHandler* serverInstance; @@ -947,6 +946,11 @@ std::shared_ptr ConnectedClient::resolveActiveBan(const std::string& } bool ConnectedClient::update_client_needed_permissions() { + if(this->getType() == ClientType::CLIENT_QUERY) { + /* Query clients are not interested in their permissions */ + return true; + } + /* The server and/or the channel might change while we're executing this method */ auto currentChannel = this->currentChannel; auto values = this->calculate_permissions(permission::neededPermissions, currentChannel ? currentChannel->channelId() : 0); @@ -1001,39 +1005,164 @@ do { \ } while(0) permission::PermissionType ConnectedClient::calculate_and_get_join_state(const std::shared_ptr& channel) { - shared_ptr ventry; + std::shared_ptr ventry; { shared_lock view_lock(this->channel_lock); ventry = this->channel_view()->find_channel(channel); - if(!ventry) + if(!ventry) { return permission::i_channel_view_power; + } } - if(ventry->join_state_id == this->join_state_id) + if(ventry->join_state_id == this->join_state_id) { return ventry->join_permission_error; + } auto channel_id = channel->channelId(); auto permission_cache = make_shared(); switch(channel->channelType()) { case ChannelType::permanent: - if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_permanent, channel_id))) + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_permanent, channel_id))) { RESULT(permission::b_channel_join_permanent); + } break; case ChannelType::semipermanent: - if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_semi_permanent, channel_id))) + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_semi_permanent, channel_id))) { RESULT(permission::b_channel_join_semi_permanent); + } break; case ChannelType::temporary: - if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_temporary, channel_id))) + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_temporary, channel_id))) { RESULT(permission::b_channel_join_temporary); + } break; } if(!channel->permission_granted(permission::i_channel_needed_join_power, this->calculate_permission(permission::i_channel_join_power, channel_id), false)) { - if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_join_power, channel_id))) + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_join_power, channel_id))) { RESULT(permission::i_channel_join_power); + } } - if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_is_sticky, this->currentChannel ? this->currentChannel->channelId() : 0))) - if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_sticky, channel_id))) + if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_is_sticky, this->currentChannel ? this->currentChannel->channelId() : 0))) { + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_sticky, channel_id))) { RESULT(permission::b_client_is_sticky); + } + } RESULT(permission::ok); +} + +void ConnectedClient::useToken(token::TokenId token_id) { + auto server_ref = this->server; + if(!server_ref) { + return; + } + + std::deque actions{}; + if(!server_ref->getTokenManager().query_token_actions(token_id, actions)) { + return; + } + + if(actions.empty()) { + return; + } + + bool tree_registered = !!this->currentChannel; + + bool server_groups_changed{false}, channel_group_changed{false}; + std::deque> added_server_groups{}; + std::deque> removed_server_groups{}; + + for(const auto& action : actions) { + switch(action.type) { + case token::ActionType::AddServerGroup: + case token::ActionType::RemoveServerGroup: { + auto group = server->getGroupManager()->findGroup(action.id1); + if(!group || group->target() != GroupTarget::GROUPTARGET_SERVER) { + debugMessage(this->getServerId(), "{} Skipping token action add/remove server group for group {} because the group does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1); + break; + } + + if(action.type == token::ActionType::AddServerGroup) { + if(this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), group)) { + debugMessage(this->getServerId(), "{} Skipping token action add server group for group {} because client is already member of that group.", CLIENT_STR_LOG_PREFIX, action.id1); + } else { + debugMessage(this->getServerId(), "{} Executing token action add server group for group {}.", CLIENT_STR_LOG_PREFIX, action.id1); + this->server->groups->addServerGroup(this->getClientDatabaseId(), group); + + added_server_groups.push_back(group); + server_groups_changed = true; + } + } else { + if(!this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), group)) { + debugMessage(this->getServerId(), "{} Skipping token action remove server group for group {} because client is not a member of that group.", CLIENT_STR_LOG_PREFIX, action.id1); + } else { + debugMessage(this->getServerId(), "{} Executing token action remove server group for group {}.", CLIENT_STR_LOG_PREFIX, action.id1); + this->server->groups->removeServerGroup(this->getClientDatabaseId(), group); + + removed_server_groups.push_back(group); + server_groups_changed = true; + } + } + + break; + } + + case token::ActionType::SetChannelGroup: { + auto group = server->getGroupManager()->findGroup(action.id1); + if(!group || group->target() != GroupTarget::GROUPTARGET_CHANNEL) { + debugMessage(this->getServerId(), "{} Skipping token action set channel group for group {} at channel {} because the group does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1, action.id2); + break; + } + + auto channel = this->server->channelTree->findChannel(action.id2); + if (!channel) { + debugMessage(this->getServerId(), "{} Skipping token action set channel group for group {} at channel {} because the channel does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1, action.id2); + break; + } + + channel_group_changed = true; + this->server->groups->setChannelGroup(this->getClientDatabaseId(), group, channel); + break; + } + + case token::ActionType::AllowChannelJoin: { + auto speaking_client = dynamic_cast(this); + if(speaking_client) { + speaking_client->join_whitelisted_channel.emplace_back(action.id2, action.text); + } + break; + } + + case token::ActionType::ActionSqlFailed: + case token::ActionType::ActionDeleted: + case token::ActionType::ActionIgnore: + default: + break; + } + } + + if(this->state > ConnectionState::INIT_HIGH) { + this->task_update_channel_client_properties.enqueue(); + this->task_update_needed_permissions.enqueue(); + } + + if(tree_registered && (server_groups_changed || channel_group_changed)) { + auto updated_properties = this->getServer()->getGroupManager()->update_server_group_property(this->ref(), true, this->currentChannel); + if(!updated_properties.empty()) { + this->getServer()->notifyClientPropertyUpdates(this->ref(), updated_properties); + } + } + + if(tree_registered && server_groups_changed) { + for(const auto &viewer : this->server->getClients()) { + if(viewer->isClientVisible(this->ref(), true)) { + for(const auto& group : added_server_groups) { + viewer->notifyServerGroupClientAdd(this->server->serverRoot, this->ref(), group); + } + + for(const auto& group : removed_server_groups) { + viewer->notifyServerGroupClientRemove(this->server->serverRoot, this->ref(), group); + } + } + } + } } \ No newline at end of file diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index 30654fa..8e7ddf6 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -10,7 +10,7 @@ #include "DataClient.h" #include "query/command3.h" -#define CLIENT_STR_LOG_PREFIX_(this) (std::string("[") + this->getLoggingPeerIp() + ":" + std::to_string(this->getPeerPort()) + "/" + this->getDisplayName() + " | " + std::to_string(this->getClientId()) + "]") +#define CLIENT_STR_LOG_PREFIX_(this) (this->getLoggingPrefix()) #define CLIENT_STR_LOG_PREFIX CLIENT_STR_LOG_PREFIX_(this) #define CMD_REQ_SERVER \ @@ -83,10 +83,14 @@ namespace ts { ConnectionState connectionState(){ return this->state; } std::string getLoggingPeerIp() { return config::server::disable_ip_saving || (this->server && this->server->disable_ip_saving()) ? "X.X.X.X" : this->getPeerIp(); } - std::string getPeerIp(){ return this->isAddressV4() ? net::to_string(this->getAddressV4()->sin_addr) : this->isAddressV6() ? net::to_string(this->getAddressV6()->sin6_addr) : "localhost"; } - uint16_t getPeerPort(){ return ntohs(this->isAddressV4() ? this->getAddressV4()->sin_port : this->isAddressV6() ? this->getAddressV6()->sin6_port : (uint16_t) 0); } + std::string getPeerIp(){ return net::to_string(this->remote_address, false); } + uint16_t getPeerPort(){ return net::port(this->remote_address); } std::string getHardwareId(){ return properties()[property::CLIENT_HARDWARE_ID]; } + [[nodiscard]] inline std::string getLoggingPrefix() { + return std::string{"["} + this->getLoggingPeerIp() + ":" + std::to_string(this->getPeerPort()) + "/" + this->getDisplayName() + " | " + std::to_string(this->getClientId()) + "]"; + } + //General connection stuff bool isAddressV4() { return this->remote_address.ss_family == AF_INET; } const sockaddr_in* getAddressV4(){ return (sockaddr_in*) &this->remote_address; } @@ -320,6 +324,8 @@ namespace ts { inline bool playlist_subscribed(const std::shared_ptr& playlist) const { return this->subscribed_playlist_.lock() == playlist; } + + permission::PermissionType calculate_and_get_join_state(const std::shared_ptr&); protected: std::weak_ptr _this; sockaddr_storage remote_address; @@ -328,7 +334,7 @@ namespace ts { std::mutex state_lock; ConnectionState state{ConnectionState::UNKNWON}; - bool allowedToTalk = false; + bool allowedToTalk{false}; std::shared_mutex finalDisconnectLock; /* locked before state lock! */ @@ -384,9 +390,10 @@ namespace ts { virtual void initialize_weak_reference(const std::shared_ptr& /* self reference */); - bool subscribeToAll = false; - uint16_t join_state_id = 1; /* default channel value is 0 and by default we need to calculate at least once, so we use 1 */ - permission::PermissionType calculate_and_get_join_state(const std::shared_ptr&); + bool subscribeToAll{false}; + uint16_t join_state_id{1}; /* default channel value is 0 and by default we need to calculate at least once, so we use 1 */ + /* (ChannelId, ChannelPasswordHash!) (If empty no password/permissions, if "ignore" ignore permissions granted) */ + std::vector> join_whitelisted_channel{}; /* Access only when the command mutex is acquired */ std::weak_ptr selectedBot; std::weak_ptr subscribed_bot; @@ -496,7 +503,9 @@ namespace ts { command_result handleCommandBanTriggerList(Command&); command_result handleCommandTokenList(Command&); + command_result handleCommandTokenActionList(Command&); command_result handleCommandTokenAdd(Command&); + command_result handleCommandTokenEdit(Command&); command_result handleCommandTokenUse(Command&); command_result handleCommandTokenDelete(Command&); @@ -646,6 +655,16 @@ namespace ts { bool handleTextMessage(ChatMessageMode, std::string, const std::shared_ptr& /* sender target */); + /** + * Call this method only when command handling is locked (aka the client can't do anything). + * All other locks shall be free. + * + * Note: This will not increase the token use count. + * The callee will have to do so. + * + */ + void useToken(token::TokenId); + typedef std::function& /* sender */, const std::string& /* message */)> handle_text_command_fn_t; bool handle_text_command( ChatMessageMode, diff --git a/server/src/client/ConnectedClientNotifyHandler.cpp b/server/src/client/ConnectedClientNotifyHandler.cpp index 828658a..2ea70d1 100644 --- a/server/src/client/ConnectedClientNotifyHandler.cpp +++ b/server/src/client/ConnectedClientNotifyHandler.cpp @@ -14,7 +14,6 @@ using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; -using namespace ts::token; # define INVOKER(command, invoker) \ do { \ diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index 45c42b7..c1546a8 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -401,6 +401,7 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) { void SpeakingClient::processJoin() { TIMING_START(timings); auto ref_server = this->server; + assert(ref_server); this->resetIdleTime(); threads::MutexLock lock(this->command_lock); //Don't process any commands! @@ -445,10 +446,10 @@ void SpeakingClient::processJoin() { if(geoloc::provider) { auto loc = this->isAddressV4() ? geoloc::provider->resolveInfoV4(this->getPeerIp(), false) : geoloc::provider->resolveInfoV6(this->getPeerIp(), false); if(loc) { - debugMessage(this->getServerId(), "Client " + this->getDisplayName() + "|" + this->getLoggingPeerIp() + " comes from " + loc->name + "|" + loc->identifier); + debugMessage(this->getServerId(), "{} Resolved ip address to {}/{}", this->getLoggingPrefix(), loc->name, loc->identifier); this->properties()[property::CLIENT_COUNTRY] = loc->identifier; } else { - logError(ref_server ? ref_server->getServerId() : 0, "Could not resolve country for ip " + this->getLoggingPeerIp() + "|" + this->getDisplayName()); + debugMessage(this->getServerId(), "{} Could not resolve IP info for {}", this->getLoggingPrefix(), this->getLoggingPeerIp()); } } //this->updateChannelClientProperties(false); /* will be already updated via assignChannel */ @@ -468,6 +469,25 @@ void SpeakingClient::processJoin() { debugMessage(this->getServerId(), "Client id: " + to_string(this->getClientId())); TIMING_STEP(timings, "notify sini"); + if(auto token{this->properties()[property::CLIENT_DEFAULT_TOKEN].value()}; !token.empty()){ + auto token_info = ref_server->getTokenManager().load_token(token, true); + if(token_info) { + if(token_info->is_expired()) { + debugMessage(this->getServerId(), "{} Client tried to use an expired token {}", this->getLoginName(), token); + } else if(token_info->use_limit_reached()) { + debugMessage(this->getServerId(), "{} Client tried to use an token which reached the use limit {}", this->getLoginName(), token); + } else { + debugMessage(this->getServerId(), "{} Client used token {}", this->getLoginName(), token); + ref_server->getTokenManager().log_token_use(token_info->id); + this->useToken(token_info->id); + } + } else { + debugMessage(this->getServerId(), "{} Client tried to use an unknown token {}", token); + } + } + TIMING_STEP(timings, "token use "); + + if(!ref_server->assignDefaultChannel(this->ref(), false)) { auto result = command_result{error::vs_critical, "Could not assign default channel!"}; this->notifyError(result); @@ -533,6 +553,7 @@ void SpeakingClient::processJoin() { } debugMessage(this->getServerId(), "{} Client join timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings)); + this->join_whitelisted_channel.clear(); serverInstance->action_logger()->client_channel_logger.log_client_join(this->getServerId(), this->ref(), this->getChannelId(), this->currentChannel->name()); } diff --git a/server/src/client/command_handler/channel.cpp b/server/src/client/command_handler/channel.cpp index 8e32470..ca032c7 100644 --- a/server/src/client/command_handler/channel.cpp +++ b/server/src/client/command_handler/channel.cpp @@ -32,7 +32,6 @@ using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; -using namespace ts::token; //{findError("parameter_invalid"), "could not resolve permission " + (cmd[index].has("permid") ? cmd[index]["permid"].as() : cmd[index]["permsid"].as())}; \ //TODO: Log missing permissions? @@ -419,6 +418,10 @@ command_result ConnectedClient::handleCommandChannelGroupDel(Command &cmd) { } } + if(this->server) { + this->server->tokenManager->handle_channel_group_deleted(channel_group->groupId()); + } + return command_result{error::ok}; } diff --git a/server/src/client/command_handler/client.cpp b/server/src/client/command_handler/client.cpp index d8e8ff1..98a876b 100644 --- a/server/src/client/command_handler/client.cpp +++ b/server/src/client/command_handler/client.cpp @@ -33,7 +33,6 @@ using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; -using namespace ts::token; #define QUERY_PASSWORD_LENGTH 12 @@ -170,15 +169,35 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { } auto permission_cache = make_shared(); - if(!cmd[0].has("cpw")) - cmd["cpw"] = ""; - if (!channel->passwordMatch(cmd["cpw"], true)) - if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) - return command_result{error::channel_invalid_password}; + auto& channel_whitelist = this->join_whitelisted_channel; + auto whitelist_entry = std::find_if(channel_whitelist.begin(), channel_whitelist.end(), [&](const auto& entry) { return entry.first == channel->channelId(); }); + if(whitelist_entry != channel_whitelist.end()) { + debugMessage(this->getServerId(), "{} Allowing client to join channel {} because the token he used earlier explicitly allowed it.", this->getLoggingPrefix(), channel->channelId()); + if(whitelist_entry->second != "ignore") { + if (!channel->passwordMatch(cmd["cpw"], true)) { + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) { + return command_result{error::channel_invalid_password}; + } + } + } + } else { + if(!cmd[0].has("cpw")) { + cmd["cpw"] = ""; + } - auto permission_error = this->calculate_and_get_join_state(channel); - if(permission_error != permission::unknown) return command_result{permission_error}; + if (!channel->passwordMatch(cmd["cpw"], true)) { + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) { + return command_result{error::channel_invalid_password}; + } + } + + auto permission_error = this->calculate_and_get_join_state(channel); + if(permission_error != permission::unknown) { + return command_result{permission_error}; + } + } + channel_whitelist.clear(); command_result_bulk result{}; result.reserve(cmd.bulkCount()); @@ -218,29 +237,36 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { result.emplace_result(error::ok); } + /* FIXME: Some kind of invite key frags to prevent limit checking! */ if (!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as() || !channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as()) { if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_maxclients, channel->channelId()))) { if(!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as()) { auto maxClients = channel->properties()[property::CHANNEL_MAXCLIENTS].as(); - if (maxClients >= 0 && maxClients < this->server->getClientsByChannel(channel).size() + clients.size()) + if (maxClients >= 0 && maxClients < this->server->getClientsByChannel(channel).size() + clients.size()) { return command_result{error::channel_maxclients_reached}; + } } if(!channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as()) { shared_ptr family_root; if(channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as()) { family_root = channel; - while(family_root && family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as()) + while(family_root && family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as()) { family_root = family_root->parent(); + } } if(family_root && !family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED]) { //Could not be CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED auto maxClients = family_root->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as(); auto client_count = 0; - for(const auto& entry : this->server->getClientsByChannelRoot(channel, false)) - if(entry.get() != this) client_count++; //Dont count the client itself + for(const auto& entry : this->server->getClientsByChannelRoot(channel, false)) { + if(entry.get() != this) { + client_count++; //Dont count the client itself + } + } - if (maxClients >= 0 && maxClients < client_count + clients.size()) + if (maxClients >= 0 && maxClients < client_count + clients.size()) { return command_result{error::channel_maxfamily_reached}; + } } } } @@ -253,7 +279,9 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { for(auto& client : clients) { auto oldChannel = client->getChannel(); - if(!oldChannel) continue; + if(!oldChannel) { + continue; + } this->server->client_move( client.client, @@ -267,8 +295,9 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { serverInstance->action_logger()->client_channel_logger.log_client_move(this->getServerId(), this->ref(), client->ref(), channel->channelId(), channel->name(), oldChannel->channelId(), oldChannel->name()); - if(std::find_if(channels.begin(), channels.end(), [&](const std::shared_ptr& channel) { return &*channel == &*oldChannel; }) == channels.end()) + if(std::find_if(channels.begin(), channels.end(), [&](const std::shared_ptr& channel) { return &*channel == &*oldChannel; }) == channels.end()) { channels.push_back(oldChannel); + } } for(const auto& oldChannel : channels) { diff --git a/server/src/client/command_handler/file.cpp b/server/src/client/command_handler/file.cpp index 7d04493..f142229 100644 --- a/server/src/client/command_handler/file.cpp +++ b/server/src/client/command_handler/file.cpp @@ -34,7 +34,6 @@ using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; -using namespace ts::token; constexpr static auto kFileAPITimeout = std::chrono::milliseconds{500}; constexpr static auto kMaxClientTransfers = 10; diff --git a/server/src/client/command_handler/misc.cpp b/server/src/client/command_handler/misc.cpp index c474722..ff17869 100644 --- a/server/src/client/command_handler/misc.cpp +++ b/server/src/client/command_handler/misc.cpp @@ -24,13 +24,13 @@ #include #include #include +#include #include "helpers.h" #include #include #include -#include #include namespace fs = std::experimental::filesystem; @@ -38,7 +38,7 @@ using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; -using namespace ts::token; +using namespace ts::server::token; #define QUERY_PASSWORD_LENGTH 12 @@ -164,8 +164,10 @@ command_result ConnectedClient::handleCommand(Command &cmd) { else if (command == "bandelall") return this->handleCommandBanDelAll(cmd); else if (command == "bantriggerlist") return this->handleCommandBanTriggerList(cmd); //Tokens + else if (command == "tokenactionlist") return this->handleCommandTokenActionList(cmd); else if (command == "tokenlist" || command == "privilegekeylist") return this->handleCommandTokenList(cmd); else if (command == "tokenadd" || command == "privilegekeyadd") return this->handleCommandTokenAdd(cmd); + else if (command == "tokenedit") return this->handleCommandTokenEdit(cmd); else if (command == "tokenuse" || command == "privilegekeyuse") return this->handleCommandTokenUse(cmd); else if (command == "tokendelete" || command == "privilegekeydelete") return this->handleCommandTokenDelete(cmd); @@ -1017,102 +1019,580 @@ command_result ConnectedClient::handleCommandTokenList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_list, 1); - Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifytokenlist" : ""); - int index = 0; - for (auto &token : this->server->tokenManager->avariableTokes()) { - notify[index]["token"] = token->token; - notify[index]["token_type"] = token->type; - notify[index]["token_id1"] = token->groupId; - notify[index]["token_id2"] = token->channelId; - notify[index]["token_created"] = chrono::duration_cast(token->created.time_since_epoch()).count(); - notify[index]["token_description"] = token->description; - index++; + auto& token_manager = this->server->getTokenManager(); + + size_t offset = cmd[0].has("offset") ? cmd["offset"].as() : 0; + size_t limit = cmd[0].has("limit") ? cmd["limit"].as() : 0; + if(limit > 2000 || limit < 1) { + limit = 1000; } - if (index == 0) return command_result{error::database_empty_result}; + size_t total_tokens{0}; + std::deque> tokens{}; + + auto own_tokens_only = cmd.hasParm("own-only") || !permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_token_list_all, 0)); + if(own_tokens_only) { + tokens = token_manager.list_tokens(total_tokens, { offset }, { limit }, { this->getClientDatabaseId() }); + } else { + tokens = token_manager.list_tokens(total_tokens, { offset }, { limit }, std::nullopt); + } + + if(tokens.empty()) { + return ts::command_result{error::database_empty_result}; + } + + auto new_command = cmd.hasParm("new"); + auto own_database_id = this->getClientDatabaseId(); + + ts::command_builder notify{this->notify_response_command("notifytokenlist")}; + size_t notify_index{0}; + for(size_t index{0}; index < tokens.size(); index++) { + auto& token = tokens[index]; + if(own_tokens_only && token->issuer_database_id != own_database_id) { + continue; + } + + auto bulk = notify.bulk(notify_index++); + bulk.put_unchecked("token", token->token); + bulk.put_unchecked("token_id", token->id); + bulk.put_unchecked("token_created", std::chrono::duration_cast(token->timestamp_created.time_since_epoch()).count()); + bulk.put_unchecked("token_expired", std::chrono::duration_cast(token->timestamp_expired.time_since_epoch()).count()); + bulk.put_unchecked("token_use_count", token->use_count); + bulk.put_unchecked("token_max_uses", token->max_uses); + bulk.put_unchecked("token_issuer_database_id", token->issuer_database_id); + bulk.put_unchecked("token_description", token->description); + + if(!new_command) { + bulk.put_unchecked("token_type", 0); + bulk.put_unchecked("token_id1", 0); + bulk.put_unchecked("token_id2", 0); + } + } + + if(notify_index == 0) { + return ts::command_result{error::database_empty_result}; + } + + notify.put_unchecked(0, "token_count", total_tokens); this->sendCommand(notify); return command_result{error::ok}; } +command_result ConnectedClient::handleCommandTokenActionList(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto& token_manager = this->server->getTokenManager(); + std::shared_ptr token_info{}; + if(cmd[0].has("token_id")) { + token_info = this->server->getTokenManager().load_token_by_id(cmd["token_id"].as(), true); + } else if(cmd[0].has("token")) { + token_info = this->server->getTokenManager().load_token(cmd["token"], true); + } + if(!token_info) { + return ts::command_result{error::token_invalid_id}; + } + + if(token_info->issuer_database_id != this->getClientDatabaseId()) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_list_all, 1); + } + + std::deque actions{}; + if(!token_manager.query_token_actions(token_info->id, actions)) { + return ts::command_result{error::vs_critical}; + } + + ts::command_builder notify{this->notify_response_command("notifytokenactions")}; + for(size_t index{0}; index < actions.size(); index++) { + auto bulk = notify.bulk(index); + auto& action = actions[index]; + + bulk.put_unchecked("action_type", (uint8_t) action.type); + bulk.put_unchecked("action_id", action.id); + bulk.put_unchecked("action_id1", action.id1); + bulk.put_unchecked("action_id2", action.id2); + bulk.put_unchecked("action_text", action.text); + } + this->sendCommand(notify); + return command_result{error::ok}; +} + +/** + * + * @param result Should already contain cmd.bulkCount() results + * @param cmd + * @param token_description + * @param max_uses + * @param timestamp_expired + * @param actions + * @return + */ +ts::command_result parseTokenParameters( + ts::command_result_bulk& result, + Command &cmd, + std::optional& token_description, + std::optional& max_uses, + std::optional& timestamp_expired, + std::vector& actions +) { + actions.reserve(cmd.bulkCount()); + + /* legacy (should not be provided on token edit) */ + if(cmd[0].has("tokentype")) { + auto ttype = cmd["tokentype"].as(); + auto gId = cmd["tokenid1"].as(); + auto cId = cmd["tokenid2"].as(); + + if(ttype == 0) { + auto& action = actions.emplace_back(); + action.type = token::ActionType::AddServerGroup; + action.id1 = gId; + } else if(ttype == 1) { + auto& action = actions.emplace_back(); + action.type = token::ActionType::SetChannelGroup; + action.id1 = gId; + action.id2 = cId; + } else { + result.set_result(0, ts::command_result{error::parameter_invalid, "tokentype"}); + } + } else { + for(size_t index{0}; index < cmd.bulkCount(); index++) { + auto& action = actions.emplace_back(); + action.id = cmd[index].has("action_id") ? cmd[index]["action_id"] : 0; + + if(!cmd[index].has("action_type")) { + if(action.id > 0) { + /* we want to delete the action */ + action.type = token::ActionType::ActionDeleted; + continue; + } + + /* Missing the parameter action type */ + action.type = token::ActionType::ActionIgnore; + if(index == 0) { + /* no actions provided */ + break; + } else { + result.set_result(index, ts::command_result{error::parameter_missing, "action_type"}); + continue; + } + } + + action.type = cmd[index]["action_type"]; + switch(action.type) { + case token::ActionType::AddServerGroup: + case token::ActionType::RemoveServerGroup: + case token::ActionType::SetChannelGroup: + case token::ActionType::AllowChannelJoin: + break; + + case token::ActionType::ActionSqlFailed: + case token::ActionType::ActionDeleted: + case token::ActionType::ActionIgnore: + default: + result.set_result(index, ts::command_result{error::parameter_invalid, "action_type"}); + continue; + } + + if(cmd[index].has("action_id1")) { + action.id1 = cmd[index]["action_id1"]; + } + + if(cmd[index].has("action_id2")) { + action.id2 = cmd[index]["action_id2"]; + } + + if(cmd[index].has("action_text")) { + action.text = cmd[index]["action_text"].string(); + } + } + } + + if(cmd[0].has("token_description")) { + token_description = { cmd["token_description"].string() }; + } else if(cmd[0].has("tokendescription")) { + token_description = { cmd["tokendescription"].string() }; + } + + if(token_description.has_value() && token_description->length() > 255) { + return ts::command_result{error::parameter_invalid, "token_description"}; + } + + if(cmd[0].has("token_max_uses")) { + max_uses = { cmd["token_max_uses"].as() }; + } + + if(cmd[0].has("token_expired")) { + auto seconds = cmd["token_expired"].as(); + timestamp_expired = { std::chrono::system_clock::time_point{} + std::chrono::seconds{seconds} }; + } + + return ts::command_result{error::ok}; +} + +/** + * Check if the client has permissions to do such actions. + * If not the toke will be set to ignored and a proper error will be emplaced + */ +void check_token_action_permissions(const std::shared_ptr& client, ts::command_result_bulk& result, std::vector& actions) { + for(size_t index{0}; index < actions.size(); index++) { + auto& action = actions[index]; + + switch(action.type) { + case ActionType::AddServerGroup: { + auto target_group = client->getServer()->getGroupManager()->findGroup(action.id1); + if(!target_group || target_group->type() == GroupType::GROUP_TYPE_TEMPLATE || target_group->target() != GroupTarget::GROUPTARGET_SERVER) { + action.type = ActionType::ActionIgnore; + result.set_result(index, ts::command_result{error::group_invalid_id}); + break; + } + + if(!target_group->permission_granted(permission::i_server_group_needed_member_add_power, client->calculate_permission(permission::i_server_group_member_add_power, 0), true)) { + action.type = ActionType::ActionIgnore; + result.set_result(index, ts::command_result{permission::i_server_group_member_add_power}); + break; + } + + break; + } + + case ActionType::RemoveServerGroup: { + auto target_group = client->getServer()->getGroupManager()->findGroup(action.id1); + if(!target_group || target_group->type() == GroupType::GROUP_TYPE_TEMPLATE || target_group->target() != GroupTarget::GROUPTARGET_SERVER) { + action.type = ActionType::ActionIgnore; + result.set_result(index, ts::command_result{error::group_invalid_id}); + break; + } + + if(!target_group->permission_granted(permission::i_server_group_needed_member_remove_power, client->calculate_permission(permission::i_server_group_member_remove_power, 0), true)) { + action.type = ActionType::ActionIgnore; + result.set_result(index, ts::command_result{permission::i_server_group_member_remove_power}); + break; + } + + break; + } + + case ActionType::SetChannelGroup: { + auto target_group = client->getServer()->getGroupManager()->findGroup(action.id1); + if(!target_group || target_group->type() == GroupType::GROUP_TYPE_TEMPLATE || target_group->target() != GroupTarget::GROUPTARGET_CHANNEL) { + action.type = ActionType::ActionIgnore; + result.set_result(index, ts::command_result{error::group_invalid_id}); + break; + } + + auto granted_permission = client->calculate_permission(permission::i_channel_group_member_add_power, action.id2); + if(!target_group->permission_granted(permission::i_channel_group_needed_member_add_power, granted_permission, true)) { + action.type = ActionType::ActionIgnore; + result.set_result(index, ts::command_result{permission::i_channel_group_member_add_power}); + break; + } + + { + std::shared_lock tree_lock{client->getServer()->get_channel_tree_lock()}; + auto target_channel = client->getServer()->getChannelTree()->findChannel(action.id2); + tree_lock.unlock(); + + if(!target_channel) { + action.type = ActionType::ActionIgnore; + result.set_result(index, ts::command_result{error::channel_invalid_id}); + break; + } + } + + break; + } + + case ActionType::AllowChannelJoin: { + std::shared_lock tree_lock{client->getServer()->get_channel_tree_lock()}; + auto target_channel = client->getServer()->getChannelTree()->findChannel(action.id2); + + if(!target_channel) { + action.type = ActionType::ActionIgnore; + result.set_result(index, ts::command_result{error::channel_invalid_id}); + break; + } + + auto join_permission = client->calculate_and_get_join_state(target_channel); + if(join_permission != permission::ok) { + action.type = ActionType::ActionIgnore; + result.set_result(index, ts::command_result{join_permission}); + break; + } + + if(client->getType() == ClientType::CLIENT_QUERY && !action.text.empty()) { + action.text = digest::sha1(action.text); + } + + if(permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, target_channel->channelId()))) { + action.text = "ignore"; + } else if(!target_channel->passwordMatch(action.text, true)) { + action.type = ActionType::ActionIgnore; + result.set_result(index, ts::command_result{error::channel_invalid_password}); + break; + } + break; + } + + case ActionType::ActionSqlFailed: + case ActionType::ActionIgnore: + case ActionType::ActionDeleted: + default: + continue; + } + } +} + command_result ConnectedClient::handleCommandTokenAdd(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_add, 1); - TokenType ttype = static_cast(cmd["tokentype"].as()); - auto gId = cmd["tokenid1"].as(); - auto cId = cmd["tokenid2"].as(); - string description = cmd["tokendescription"].string(); - - { - auto group = this->server->groups->findGroup(gId); - if(!group) return command_result{error::group_invalid_id}; - if(group->type() == GroupType::GROUP_TYPE_TEMPLATE) return command_result{error::parameter_invalid, "invalid server group type"}; - - if(group->target() == GroupTarget::GROUPTARGET_SERVER) - ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_server_group_needed_member_add_power, permission::i_server_group_member_add_power, true); - else - ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_channel_group_needed_member_add_power, permission::i_channel_group_member_add_power, true); + auto client_tokens = this->server->tokenManager->client_token_count(this->getClientDatabaseId()); + if(!permission::v2::permission_granted(client_tokens + 1, this->calculate_permission(permission::i_virtualserver_token_limit, 0), true)) { + return ts::command_result{permission::i_virtualserver_token_limit}; } - if (ttype == TokenType::TOKEN_CHANNEL) { - if (!this->server->channelTree->findChannel(cId)) return command_result{error::channel_invalid_id, "Cant resolve channel"}; - } else if (ttype == TokenType::TOKEN_SERVER); - else return command_result{error::parameter_invalid, "invalid token target type"}; + std::vector actions{}; + actions.reserve(cmd.bulkCount()); - auto result = this->server->tokenManager->createToken(ttype, gId, description, cId, cmd["token"]); - if (!result) return command_result{error::vs_critical}; + std::optional token_description{}; + std::optional max_uses{}; + std::optional timestamp_expired{}; - Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifytokenadd" : ""); - notify["token"] = result->token; + ts::command_result_bulk result{}; + result.emplace_result_n(cmd.bulkCount(), error::ok); + + auto parse_result = parseTokenParameters( + result, + cmd, + token_description, + max_uses, + timestamp_expired, + actions + ); + + if(parse_result.has_error()) { + return parse_result; + } + parse_result.release_data(); + + check_token_action_permissions(this->ref(), result, actions); + auto token = this->server->getTokenManager().create_token( + this->getClientDatabaseId(), + token_description.value_or(""), + max_uses.value_or(0), + timestamp_expired.value_or(std::chrono::system_clock::time_point{}) + ); + + if(!token) { + return ts::command_result{error::vs_critical}; + } + + ts::command_builder notify{this->notify_response_command("notifytokenadd")}; + if(!actions.empty()) { + this->server->getTokenManager().add_token_actions(token->id, actions); + + for(size_t index{0}; index < actions.size(); index++) { + auto& action = actions[index]; + if(action.type == token::ActionType::ActionIgnore || action.type == token::ActionType::ActionDeleted) { + continue; + } else if(action.type == token::ActionType::ActionSqlFailed) { + result.set_result(index, ts::command_result{error::database_constraint}); + } else { + notify.put_unchecked(index, "action_id", action.id); + } + } + } + + notify.put_unchecked(0, "token", token->token); + notify.put_unchecked(0, "token_id", token->id); this->sendCommand(notify); - return command_result{error::ok}; + return command_result{std::move(result)}; +} + +command_result ConnectedClient::handleCommandTokenEdit(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(15); + + std::shared_ptr token{}; + if(cmd[0].has("token_id")) { + token = this->server->getTokenManager().load_token_by_id(cmd["token_id"].as(), true); + } else if(cmd[0].has("token")) { + token = this->server->getTokenManager().load_token(cmd["token"], true); + } + + if(!token) { + return ts::command_result{error::token_invalid_id}; + } + + if(token->issuer_database_id != this->getClientDatabaseId()) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_edit_all, 1); + } + + std::vector actions{}; + actions.reserve(cmd.bulkCount()); + + std::optional token_description{}; + std::optional max_uses{}; + std::optional timestamp_expired{}; + + ts::command_result_bulk result{}; + result.emplace_result_n(cmd.bulkCount(), error::ok); + + auto parse_result = parseTokenParameters( + result, + cmd, + token_description, + max_uses, + timestamp_expired, + actions + ); + + if(parse_result.has_error()) { + return parse_result; + } + parse_result.release_data(); + + /* filter actions for duplicates etc */ + { + std::deque current_actions{}; + if(!this->server->getTokenManager().query_token_actions(token->id, current_actions)) { + return ts::command_result{error::vs_critical}; + } + + + for(const auto& action : actions) { + if (action.type == ActionType::ActionDeleted) { + auto index = std::find_if(current_actions.begin(), current_actions.end(), [&](const auto& caction) { return caction.id == action.id; }); + if(index != current_actions.end()) { + current_actions.erase(index); + } + } + } + + size_t action_index{0}; + for(auto& action : actions) { + switch(action.type) { + case ActionType::AddServerGroup: + case ActionType::RemoveServerGroup: + case ActionType::SetChannelGroup: + case ActionType::AllowChannelJoin: { + auto index = std::find_if(current_actions.begin(), current_actions.end(), [&](const TokenAction& caction) { + if(caction.type != action.type) { + return false; + } + + if(caction.text != action.text) { + return false; + } + + if(caction.id1 != action.id1) { + return false; + } + + return caction.id2 == action.id2; + }); + + if(index != current_actions.end()) { + action.type = ActionType::ActionIgnore; + result.set_result(action_index, ts::command_result{error::database_no_modifications}); + break; + } + + current_actions.push_back(action); + break; + } + + case ActionType::ActionSqlFailed: + case ActionType::ActionIgnore: + case ActionType::ActionDeleted: + default: + break; + } + + action_index++; + } + } + + check_token_action_permissions(this->ref(), result, actions); + + if(token_description.has_value() || max_uses.has_value() || timestamp_expired.has_value()) { + token->description = token_description.value_or(token->description); + token->max_uses = max_uses.value_or(token->max_uses); + token->timestamp_expired = timestamp_expired.value_or(token->timestamp_expired); + + this->server->getTokenManager().save_token(token); + } + + if(!actions.empty()) { + size_t new_actions{0}; + + std::vector actions_removed{}; + actions_removed.reserve(actions.size()); + for(const auto& action : actions) { + if(action.type == ActionType::ActionDeleted) { + actions_removed.push_back(action.id); + } else if(action.type == ActionType::ActionIgnore) { + /* just ignore the action */ + } else { + new_actions++; + } + } + + if(!actions_removed.empty()) { + this->server->getTokenManager().delete_token_actions(token->id, actions_removed); + } + + if(new_actions > 0) { + this->server->getTokenManager().add_token_actions(token->id, actions); + + ts::command_builder notify{this->notify_response_command("notifytokenedit")}; + for(size_t index{0}; index < actions.size(); index++) { + auto& action = actions[index]; + if(action.type == token::ActionType::ActionIgnore || action.type == token::ActionType::ActionDeleted) { + continue; + } else if(action.type == token::ActionType::ActionSqlFailed) { + result.set_result(0, ts::command_result{error::database_constraint}); + } else { + notify.put_unchecked(index, "action_id", action.id); + } + } + this->sendCommand(notify); + } + } + + return command_result{std::move(result)}; } command_result ConnectedClient::handleCommandTokenUse(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_use, 1); + //ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_use, 1); - auto strToken = cmd["token"].string(); - auto token = this->server->tokenManager->findToken(strToken); - if (!token) return command_result{error::token_invalid_id, "Invalid token. (Token not registered)"}; - this->server->tokenManager->deleteToke(token->token); + auto& token_manager = this->server->getTokenManager(); + + auto token = token_manager.load_token(cmd["token"], true); + if(!token) { + return ts::command_result{error::token_invalid_id}; + } + + if(token->is_expired()) { + return ts::command_result{error::token_expired}; + } + + if(token->use_limit_reached()) { + return ts::command_result{error::token_use_limit_exceeded}; + } - auto serverGroup = this->server->groups->findGroup(token->groupId); - if (!serverGroup) return command_result{error::token_invalid_id, "Token invalid groupId"}; this->server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; //TODO test if its the default token - - std::shared_ptr channel = nullptr; - if (token->channelId != 0) { - channel = this->server->channelTree->findChannel(token->channelId); - if (!channel) return command_result{error::token_invalid_id, "Token invalid channelId"}; - - this->server->groups->setChannelGroup(this->getClientDatabaseId(), serverGroup, channel); - } else { - if(!this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), serverGroup)) - this->server->groups->addServerGroup(this->getClientDatabaseId(), serverGroup); - else { - return command_result{error::ok}; - } - } - - if (this->server->notifyClientPropertyUpdates(this->ref(), this->server->groups->update_server_group_property(this->ref(), true, this->getChannel()))) { - this->task_update_needed_permissions.enqueue(); - - { - for (auto &viewer : this->server->getClients()) { - if(viewer->isClientVisible(this->ref(), true)) - viewer->notifyServerGroupClientAdd(this->server->serverRoot, this->ref(), serverGroup); - } - } - this->notifyServerGroupClientAdd(this->server->serverRoot, this->ref(), serverGroup); - } + token_manager.log_token_use(token->id); + this->useToken(token->id); return command_result{error::ok}; } @@ -1121,24 +1601,33 @@ command_result ConnectedClient::handleCommandTokenDelete(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_delete, 1); - auto strToken = cmd["token"].string(); - auto token = this->server->tokenManager->findToken(strToken); - if (!token) return command_result{error::token_invalid_id, "Invalid token. (Token not registered)"}; - this->server->tokenManager->deleteToke(token->token); + std::shared_ptr token{}; + if(cmd[0].has("token_id")) { + token = this->server->getTokenManager().load_token_by_id(cmd["token_id"].as(), true); + } else if(cmd[0].has("token")) { + token = this->server->getTokenManager().load_token(cmd["token"], true); + } + + if(!token) { + return ts::command_result{error::token_invalid_id}; + } + + if(token->issuer_database_id != this->getClientDatabaseId()) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_delete_all, 1); + } + + this->server->getTokenManager().delete_token(token->id); + if(token->token == this->server->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY].as()) { this->server->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = ""; this->server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; logMessage(this->getServerId(), "{} Deleting the default server token. Don't ask anymore for this a token!", CLIENT_STR_LOG_PREFIX); } + return command_result{error::ok}; } -//start=0 duration=10 -//pattern=%asd% - - command_result ConnectedClient::handleCommandPluginCmd(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); diff --git a/server/src/client/command_handler/music.cpp b/server/src/client/command_handler/music.cpp index d35c68d..507d4e4 100644 --- a/server/src/client/command_handler/music.cpp +++ b/server/src/client/command_handler/music.cpp @@ -48,7 +48,6 @@ using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; -using namespace ts::token; command_result ConnectedClient::handleCommandMusicBotCreate(Command& cmd) { if(!config::music::enabled) return command_result{error::music_disabled}; diff --git a/server/src/client/command_handler/server.cpp b/server/src/client/command_handler/server.cpp index 463e40d..d312d2e 100644 --- a/server/src/client/command_handler/server.cpp +++ b/server/src/client/command_handler/server.cpp @@ -35,7 +35,6 @@ using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; -using namespace ts::token; #define QUERY_PASSWORD_LENGTH 12 @@ -584,6 +583,10 @@ command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) { }); } + if(this->server) { + this->server->tokenManager->handle_server_group_deleted(serverGroup->groupId()); + } + return command_result{error::ok}; } diff --git a/server/src/client/query/QueryClientCommands.cpp b/server/src/client/query/QueryClientCommands.cpp index 531c7a4..bfaaf34 100644 --- a/server/src/client/query/QueryClientCommands.cpp +++ b/server/src/client/query/QueryClientCommands.cpp @@ -696,11 +696,14 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) { } serverInstance->action_logger()->server_logger.log_server_create(server->getServerId(), this->ref(), log::ServerCreateReason::USER_ACTION); + size_t total_tokens{}; + auto tokens = server->tokenManager->list_tokens(total_tokens, std::nullopt, { 1 }, std::nullopt); + Command res(""); res["sid"] = server->getServerId(); res["error"] = startError; res["virtualserver_port"] = server->properties()[property::VIRTUALSERVER_PORT].as(); - res["token"] = server->tokenManager->avariableTokes().empty() ? "unknown" : server->tokenManager->avariableTokes()[0]->token; + res["token"] = tokens.empty() ? "unknown" : tokens[0]->token; res["time_create"] = time_create.count(); res["time_start"] = time_start.count(); res["time_global"] = time_global.count(); diff --git a/server/src/lincense/LicenseService.cpp b/server/src/lincense/LicenseService.cpp index 6045966..cad7ce8 100644 --- a/server/src/lincense/LicenseService.cpp +++ b/server/src/lincense/LicenseService.cpp @@ -520,12 +520,13 @@ void LicenseService::schedule_next_request(bool request_success) { #endif this->timings.next_request = this->timings.last_request + next_request; - if(this->verbose_) + if(this->verbose_) { logMessage(LOG_INSTANCE, strobf("Scheduling next check at {}").c_str(), format_time(this->timings.next_request)); + } } void LicenseService::execute_tick() { - std::unique_lock rlock{this->request_lock, std::try_to_lock}; /* It will be slightly blocking when its within the message hendeling */ + std::unique_lock rlock{this->request_lock, std::try_to_lock}; /* It will be slightly blocking when its within the message hendling */ if(!rlock) return; /* do it not above because if we might have a deadlock here we don't want to punish the user */ @@ -534,10 +535,11 @@ void LicenseService::execute_tick() { if(std::chrono::steady_clock::now() - difference > this->startup_timepoint_) { this->startup_timepoint_ = std::chrono::steady_clock::now(); /* shut down only once */ - if(config::license->isPremium()) + if(config::license->isPremium()) { logCritical(LOG_INSTANCE, strobf("Failed to validate license within 4 days.").string()); - else + } else { logCritical(LOG_INSTANCE, strobf("Failed to validate instance integrity within 7 days.").string()); + } logCritical(LOG_INSTANCE, strobf("Stopping server!").string()); ts::server::shutdownInstance(); return; @@ -552,6 +554,8 @@ void LicenseService::execute_tick() { return; } } - if(std::chrono::system_clock::now() > this->timings.next_request) + + if(std::chrono::system_clock::now() > this->timings.next_request) { this->begin_request(); + } } \ No newline at end of file diff --git a/server/src/manager/SqlDataManager.cpp b/server/src/manager/SqlDataManager.cpp index bc8b9b2..3867a34 100644 --- a/server/src/manager/SqlDataManager.cpp +++ b/server/src/manager/SqlDataManager.cpp @@ -44,8 +44,8 @@ if(!result && result.msg().find(ignore) == string::npos){ #define RESIZE_COLUMN(tblName, rowName, size) up vote EXECUTE("Could not change column size", "ALTER TABLE " tblName " ALTER COLUMN " rowName " varchar(" size ")"); -#define CURRENT_DATABASE_VERSION 17 -#define CURRENT_PERMISSION_VERSION 8 +#define CURRENT_DATABASE_VERSION 18 +#define CURRENT_PERMISSION_VERSION 9 #define CLIENT_UID_LENGTH "64" #define CLIENT_NAME_LENGTH "128" @@ -578,6 +578,54 @@ bool SqlDataManager::update_database(std::string &error) { db_version(17); } + case 17: { + constexpr static std::array kUpdateCommands{ + "CREATE TABLE tokens_new(" + " `token_id` INTEGER NOT NULL PRIMARY KEY [AUTO_INCREMENT]," + " `server_id` INT," + " `token` VARCHAR(32)," + " `description` VARCHAR(255)," + " `issuer_database_id` BIGINT," + " `max_uses` INT," + " `use_count` INT DEFAULT 0," + " `timestamp_created` INT," + " `timestamp_expired` INT" + ");", + + "CREATE TABLE token_actions(" + " `action_id` INTEGER NOT NULL PRIMARY KEY [AUTO_INCREMENT]," + " `server_id` INTEGER NOT NULL," + " `token_id` INTEGER NOT NULL," + " `type` INTEGER," + " `id1` BIGINT," + " `id2` BIGINT," + " `text` TEXT" + ");", + + "INSERT INTO `tokens_new`(`server_id`, `token`, `description`, `issuer_database_id`, `max_uses`, `timestamp_created`, `timestamp_expired`)" + " SELECT `serverId`, `token`, `description`, 0, 1, `created`, 0 FROM `tokens`;", + + "INSERT INTO `token_actions`(`server_id`, `token_id`, `type`, `id1`, `id2`, `text`)" + " SELECT `serverId`, `token_id`, 1, `targetGroup`, 0, \"\" FROM `tokens` LEFT JOIN `tokens_new` ON `tokens_new`.`token` = `tokens`.`token` WHERE `type` = 0;", + + "INSERT INTO `token_actions`(`server_id`, `token_id`, `type`, `id1`, `id2`, `text`)" + " SELECT `serverId`, `token_id`, 3, `targetGroup`, `targetChannel`, \"\" FROM `tokens` LEFT JOIN `tokens_new` ON `tokens_new`.`token` = `tokens`.`token` WHERE `type` = 1;", + + "DROP TABLE `tokens`;", + "ALTER TABLE `tokens_new` RENAME TO `tokens`;", + + "CREATE UNIQUE INDEX `tokens_token` ON `tokens` (`token`);", + "CREATE INDEX `tokens_client` ON `tokens` (`issuer_database_id`);", + "CREATE INDEX `tokens_server` ON `tokens` (`server_id`);", + "CREATE INDEX `token_actions_token_id` ON `token_actions` (`token_id`);", + }; + + if(!execute_commands(this->sql(), error, kUpdateCommands)) + return false; + + db_version(18); + } + default: break; } @@ -849,26 +897,26 @@ if(!auto_update(permission::update::type, name, {value, value != permNotGranted} do_auto_update(QUERY_ADMIN, "i_video_max_screen_streams", permNotGranted, 100); do_auto_update(QUERY_ADMIN, "i_video_max_camera_streams", permNotGranted, 100); - do_auto_update(SERVER_ADMIN, "b_video_screen", 1, 100); - do_auto_update(SERVER_ADMIN, "b_video_camera", 1, 100); - do_auto_update(SERVER_ADMIN, "i_video_max_kbps", 20 * 1000 * 1000, 100); - do_auto_update(SERVER_ADMIN, "i_video_max_streams", permNotGranted, 100); - do_auto_update(SERVER_ADMIN, "i_video_max_screen_streams", permNotGranted, 100); - do_auto_update(SERVER_ADMIN, "i_video_max_camera_streams", permNotGranted, 100); + do_auto_update(SERVER_ADMIN, "b_video_screen", 1, 75); + do_auto_update(SERVER_ADMIN, "b_video_camera", 1, 75); + do_auto_update(SERVER_ADMIN, "i_video_max_kbps", 20 * 1000 * 1000, 75); + do_auto_update(SERVER_ADMIN, "i_video_max_streams", permNotGranted, 75); + do_auto_update(SERVER_ADMIN, "i_video_max_screen_streams", permNotGranted, 75); + do_auto_update(SERVER_ADMIN, "i_video_max_camera_streams", permNotGranted, 75); - do_auto_update(SERVER_NORMAL, "b_video_screen", 1, 100); - do_auto_update(SERVER_NORMAL, "b_video_camera", 1, 100); - do_auto_update(SERVER_NORMAL, "i_video_max_kbps", 5 * 1000 * 1000, 100); - do_auto_update(SERVER_NORMAL, "i_video_max_streams", 20, 100); - do_auto_update(SERVER_NORMAL, "i_video_max_screen_streams", 2, 100); - do_auto_update(SERVER_NORMAL, "i_video_max_camera_streams", 20, 100); + do_auto_update(SERVER_NORMAL, "b_video_screen", 1, permNotGranted); + do_auto_update(SERVER_NORMAL, "b_video_camera", 1, permNotGranted); + do_auto_update(SERVER_NORMAL, "i_video_max_kbps", 5 * 1000 * 1000, permNotGranted); + do_auto_update(SERVER_NORMAL, "i_video_max_streams", 20, permNotGranted); + do_auto_update(SERVER_NORMAL, "i_video_max_screen_streams", 2, permNotGranted); + do_auto_update(SERVER_NORMAL, "i_video_max_camera_streams", 20, permNotGranted); - do_auto_update(SERVER_GUEST, "b_video_screen", 1, 100); - do_auto_update(SERVER_GUEST, "b_video_camera", 1, 100); - do_auto_update(SERVER_GUEST, "i_video_max_kbps", 2500 * 1000, 100); - do_auto_update(SERVER_GUEST, "i_video_max_streams", 8, 100); - do_auto_update(SERVER_GUEST, "i_video_max_screen_streams", 1, 100); - do_auto_update(SERVER_GUEST, "i_video_max_camera_streams", 8, 100); + do_auto_update(SERVER_GUEST, "b_video_screen", 1, permNotGranted); + do_auto_update(SERVER_GUEST, "b_video_camera", 1, permNotGranted); + do_auto_update(SERVER_GUEST, "i_video_max_kbps", 2500 * 1000, permNotGranted); + do_auto_update(SERVER_GUEST, "i_video_max_streams", 8, permNotGranted); + do_auto_update(SERVER_GUEST, "i_video_max_screen_streams", 1, permNotGranted); + do_auto_update(SERVER_GUEST, "i_video_max_camera_streams", 8, permNotGranted); perm_version(7); @@ -880,6 +928,23 @@ if(!auto_update(permission::update::type, name, {value, value != permNotGranted} } perm_version(8); + + case 8: + do_auto_update(QUERY_ADMIN, "b_virtualserver_token_list_all", 1, 100); + do_auto_update(SERVER_ADMIN, "b_virtualserver_token_list_all", 1, 75); + + do_auto_update(QUERY_ADMIN, "b_virtualserver_token_edit_all", 1, 100); + do_auto_update(SERVER_ADMIN, "b_virtualserver_token_edit_all", 1, 75); + + do_auto_update(QUERY_ADMIN, "b_virtualserver_token_delete_all", 1, 100); + do_auto_update(SERVER_ADMIN, "b_virtualserver_token_delete_all", 1, 75); + + do_auto_update(QUERY_ADMIN, "i_virtualserver_token_limit", -1, 100); + do_auto_update(SERVER_ADMIN, "i_virtualserver_token_limit", -1, 75); + do_auto_update(SERVER_NORMAL, "i_virtualserver_token_limit", 100, permNotGranted); + do_auto_update(SERVER_GUEST, "i_virtualserver_token_limit", 50, permNotGranted); + + perm_version(9); default: break; } diff --git a/server/src/manager/TokeManager.cpp b/server/src/manager/TokeManager.cpp index a4d5272..8306572 100644 --- a/server/src/manager/TokeManager.cpp +++ b/server/src/manager/TokeManager.cpp @@ -3,126 +3,348 @@ // #include -#include #include #include -#include #include #include #include "TokeManager.h" -#include "src/VirtualServer.h" using namespace std; using namespace std::chrono; using namespace ts; -using namespace ts::server; -using namespace ts::token; +using namespace ts::server::token; -TokenManager::TokenManager(server::VirtualServer* handle) : handle(handle) { +TokenManager::TokenManager(sql::SqlManager *sql, ServerId server_id) : sql_manager{sql}, virtual_server_id{server_id} {} +TokenManager::~TokenManager() {} +void TokenManager::initialize_cache() {} +void TokenManager::finalize_cache() {} + +#define TOKEN_LOAD_PROPERTIES "`token_id`, `token`, `description`, `issuer_database_id`, `max_uses`, `use_count`, `timestamp_created`, `timestamp_expired`" +#define TOKEN_USE_ABLE "(`max_uses` = 0 OR `max_uses` > `use_count`) AND (`timestamp_expired` = 0 OR `timestamp_expired` > :now)" + +void TokenManager::load_tokens(std::deque> &result, sql::command &command) { + command.query([&](int length, std::string* values, std::string* names) { + auto index{0}; + + auto& token = result.emplace_back(); + token = std::make_shared(); + try { + assert(names[index] == "token_id"); + token->id = std::stoull(values[index++]); + + assert(names[index] == "token"); + token->token = values[index++]; + + assert(names[index] == "description"); + token->description = values[index++]; + + assert(names[index] == "issuer_database_id"); + token->issuer_database_id = std::stoull(values[index++]); + + assert(names[index] == "max_uses"); + token->max_uses = std::stoull(values[index++]); + + assert(names[index] == "use_count"); + token->use_count = std::stoull(values[index++]); + + assert(names[index] == "timestamp_created"); + token->timestamp_created = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])}; + + assert(names[index] == "timestamp_expired"); + token->timestamp_expired = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])}; + + assert(index == length); + } catch (std::exception& ex) { + result.pop_back(); + logError(this->virtual_server_id, "Failed to parse token at index {}: {}", + index - 1, + ex.what() + ); + return; + } + }); } -TokenManager::~TokenManager() = default; +size_t TokenManager::client_token_count(ClientDbId client) { + sql::command command{this->sql_manager, "SELECT COUNT(`token_id`) FROM `tokens` WHERE `server_id` = :server_id AND `issuer_database_id` = :client AND " TOKEN_USE_ABLE}; + command.value(":server_id", this->virtual_server_id); + command.value(":client", client); + command.value(":now", std::chrono::floor(std::chrono::system_clock::now().time_since_epoch()).count()); -bool TokenManager::loadTokens() { - LOG_SQL_CMD(sql::command(handle->getSql(), "SELECT * FROM `tokens` WHERE `serverId` = :sid", variable{":sid", handle->getServerId()}).query(&TokenManager::loadTokenFromDb, this)); - return true; -} + size_t total_count{(size_t) -1}; + auto query_result = command.query([&](int, std::string* values, std::string*) { + total_count = std::stoull(values[0]); + }); -int TokenManager::loadTokenFromDb(int length, char **values, char **columns) { - std::string token, description; - auto type = static_cast(0xFF); - GroupId group = 0; - ChannelId chId = 0; - time_point created; - - for(int i = 0; i < length; i++){ - if(strcmp(columns[i], "type") == 0) - type = static_cast(stoi(values[i])); - else if(strcmp(columns[i], "token") == 0) - token = values[i]; - else if(strcmp(columns[i], "targetGroup") == 0) - group = static_cast(stoll(values[i])); - else if(strcmp(columns[i], "targetChannel") == 0) - chId = static_cast(stoll(values[i])); - else if(strcmp(columns[i], "description") == 0) - description = values[i]; - else if(strcmp(columns[i], "created") == 0) - created = time_point() + seconds(stoll(values[i])); - else if(strcmp(columns[i], "serverId") == 0); - else cerr << "Invalid column id `" << columns[i] << "`" << endl; + if(!query_result) { + LOG_SQL_CMD(query_result); } - assert(!token.empty()); - //assert(!description.empty()); - assert(type != 0xFF); - assert(group != 0); - assert(created.time_since_epoch().count() != 0); + return total_count; +} - auto token_entry = std::make_shared(token, type, group, chId, system_clock::now(), description); +std::deque> TokenManager::list_tokens(size_t &total_count, const std::optional &offset, + const std::optional &limit, const std::optional& owner) { { - std::lock_guard tlock{this->token_lock}; - this->tokens.push_back(token_entry); + std::string sql_command{}; + sql_command += "SELECT COUNT(`token_id`) FROM `tokens` WHERE `server_id` = :server_id AND " TOKEN_USE_ABLE; + if(owner.has_value()) { + sql_command += " AND `issuer_database_id` = :owner"; + } + + sql::command command{this->sql_manager, sql_command}; + command.value(":server_id", this->virtual_server_id); + command.value(":owner", owner.value_or(0)); + command.value(":now", std::chrono::floor(std::chrono::system_clock::now().time_since_epoch()).count()); + auto query_result = command.query([&](int, std::string* values, std::string*) { + total_count = std::stoull(values[0]); + }); + + if(!query_result) { + LOG_SQL_CMD(query_result); + total_count = 0; + } } - return 0; + + std::deque> result{}; + { + std::string sql_command{}; + sql_command += "SELECT " TOKEN_LOAD_PROPERTIES " FROM `tokens` WHERE `server_id` = :server_id AND " TOKEN_USE_ABLE; + if(owner.has_value()) { + sql_command += " AND `issuer_database_id` = :owner"; + } + sql_command += " LIMIT :offset, :limit"; + + sql::command command{this->sql_manager, sql_command}; + command.value(":server_id", this->virtual_server_id); + command.value(":owner", owner.value_or(0)); + command.value(":offset", offset.value_or(0)); + command.value(":limit", limit.has_value() ? (int64_t) *limit : -1); + command.value(":now", std::chrono::floor(std::chrono::system_clock::now().time_since_epoch()).count()); + + this->load_tokens(result, command); + } + + return result; } -std::shared_ptr TokenManager::createToken(TokenType type, GroupId group, const std::string& description, ChannelId chid, std::string token) { - token = token.empty() ? rnd_string(20) : token; +std::shared_ptr TokenManager::load_token(const std::string &token, bool ignore_limits) { + std::deque> result{}; - shared_ptr entry = make_shared(token, type, group, chid, system_clock::now(), description); - { - std::lock_guard tlock{this->token_lock}; - this->tokens.push_back(entry); + std::string sql_command{"SELECT " TOKEN_LOAD_PROPERTIES " FROM `tokens` WHERE `token` = :token"}; + if(!ignore_limits) { + sql_command += " AND " TOKEN_USE_ABLE; } - auto res = sql::command(handle->getSql(), "INSERT INTO `tokens` (`serverId`, `type`, `token`, `targetGroup`, `targetChannel`, `description`, `created`) VALUES (:sid, :type, :token, :targetGroup, :targetChannel, :desc, :created);", - variable{":sid", this->handle->getServerId()}, - variable{":type", entry->type}, - variable{":token", entry->token}, - variable{":targetGroup", entry->groupId}, - variable{":targetChannel", entry->channelId}, - variable{":desc", entry->description}, - variable{":created", duration_cast(entry->created.time_since_epoch()).count()} - ).execute(); - auto pf = LOG_SQL_CMD; - pf(res); + sql::command command{this->sql_manager, sql_command}; + command.value(":token", token); + command.value(":now", std::chrono::floor(std::chrono::system_clock::now().time_since_epoch()).count()); + this->load_tokens(result, command); - return entry; + return result.empty() ? nullptr : result.front(); } -bool TokenManager::deleteToke(const std::string& str) { - std::shared_ptr token; - { - std::lock_guard tlock{this->token_lock}; - auto it = std::find_if(this->tokens.begin(), this->tokens.end(), [&](const auto& token) { return token->token == str; }); - if(it == this->tokens.end()) - return false; - token = std::move(*it); - this->tokens.erase(it); +std::shared_ptr TokenManager::load_token_by_id(TokenId token_id, bool ignore_limits) { + std::deque> result{}; + + std::string sql_command{"SELECT " TOKEN_LOAD_PROPERTIES " FROM `tokens` WHERE `token_id` = :token_id"}; + if(!ignore_limits) { + sql_command += " AND " TOKEN_USE_ABLE; } - auto res = sql::command(handle->getSql(), "DELETE FROM `tokens` WHERE `token` = :token", variable{":token", token->token}).execute(); - auto pf = LOG_SQL_CMD; - pf(res); + sql::command command{this->sql_manager, sql_command}; + command.value(":token_id", token_id); + command.value(":now", std::chrono::floor(std::chrono::system_clock::now().time_since_epoch()).count()); + this->load_tokens(result, command); + + return result.empty() ? nullptr : result.front(); +} + +std::shared_ptr TokenManager::create_token( + ClientDbId issuer_database_id, + const std::string &description, + size_t max_uses, + const std::chrono::system_clock::time_point ×tamp_expired +) { + auto token = rnd_string(32); + auto created_timestamp = std::chrono::system_clock::now(); + + sql::command command{this->sql_manager, "INSERT INTO `tokens`(`server_id`, `token`, `description`, `issuer_database_id`, `max_uses`, `timestamp_created`, `timestamp_expired`)" + " VALUES (:server_id, :token, :description, :issuer_database_id, :max_uses, :timestamp_created, :timestamp_expired);"}; + command.value(":server_id", this->virtual_server_id); + command.value(":token", token); + command.value(":description", description); + command.value(":issuer_database_id", issuer_database_id); + command.value(":max_uses", max_uses); + command.value(":timestamp_created", std::chrono::floor(created_timestamp.time_since_epoch()).count()); + command.value(":timestamp_expired", std::chrono::floor(timestamp_expired.time_since_epoch()).count()); + + auto insert_result = command.execute(); + if(!insert_result) { + logError(this->virtual_server_id, "Failed to register new token: {}", insert_result.fmtStr()); + return nullptr; + } + + auto result = std::make_shared(); + result->id = insert_result.last_insert_rowid(); + result->token = token; + result->issuer_database_id = issuer_database_id; + + result->description = description; + result->max_uses = max_uses; + result->use_count = 0; + + result->timestamp_expired = timestamp_expired; + result->timestamp_created = created_timestamp; + + return result; +} + +bool TokenManager::query_token_actions(TokenId token_id, std::deque &actions) { + sql::command command{this->sql_manager, + "SELECT `action_id`, `type`, `id1`, `id2`, `text` FROM `token_actions` WHERE `token_id` = :token_id"}; + + command.value(":token_id", token_id); + command.query([&](int length, std::string* values, std::string* names) { + auto index{0}; + + auto& action = actions.emplace_back(); + try { + assert(names[index] == "action_id"); + action.id = std::stoull(values[index++]); + + assert(names[index] == "type"); + action.type = (ActionType) std::stoull(values[index++]); + + assert(names[index] == "id1"); + action.id1 = std::stoull(values[index++]); + + assert(names[index] == "id2"); + action.id2 = std::stoull(values[index++]); + + assert(names[index] == "text"); + action.text = values[index++]; + + assert(index == length); + } catch (std::exception& ex) { + actions.pop_back(); + logError(this->virtual_server_id, "Failed to parse token action at index {}: {}", + index - 1, + ex.what() + ); + return; + } + }); return true; } -void TokenManager::handleGroupDelete(GroupId group_id) { - std::lock_guard tlock{this->token_lock}; - this->tokens.erase(std::remove_if(this->tokens.begin(), this->tokens.end(), [&](const std::shared_ptr& token) { - return token->groupId == group_id; - }), this->tokens.end()); +void TokenManager::add_token_actions(TokenId token_id, std::vector &actions) { + sql::model insert{this->sql_manager, "INSERT INTO `token_actions`(`server_id`, `token_id`, `type`, `id1`, `id2`, `text`) VALUES (:server_id, :token_id, :type, :id1, :id2, :text)"}; + insert.value(":server_id", this->virtual_server_id); + insert.value(":token_id", token_id); + + for(auto& action : actions) { + if(action.type == ActionType::ActionDeleted || action.type == ActionType::ActionSqlFailed || action.type == ActionType::ActionIgnore) { + continue; + } + + auto command = insert.command(); + command.value(":type", (uint8_t) action.type); + command.value(":id1", action.id1); + command.value(":id2", action.id2); + command.value(":text", action.text); + + auto result = command.execute(); + if(!result) { + logError(this->virtual_server_id, "Failed to insert token action {} for token id {}: {}", (uint32_t) action.type, token_id, result.fmtStr()); + action.type = ActionType::ActionSqlFailed; + } else { + action.id = result.last_insert_rowid(); + } + } } -std::shared_ptr TokenManager::findToken(const std::string& str) { - std::lock_guard tlock{this->token_lock}; - for(const auto& elm : this->tokens) - if(elm->token == str) - return elm; - return nullptr; +void TokenManager::delete_token_actions(TokenId, const std::vector &actions) { + if(actions.empty()) { + return; + } + + std::string sql_command{}; + sql_command += "DELETE FROM `token_actions` WHERE `action_id` IN ("; + for(size_t index{0}; index < actions.size(); index++) { + if(index > 0) { + sql_command += ", "; + } + + sql_command += std::to_string(actions[index]); + } + sql_command += ");"; + + sql::command command{this->sql_manager, sql_command}; + command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete token actions"}); } -TokenEntry::TokenEntry(string token, TokenType type, GroupId groupId, ChannelId channelId, const time_point &created, string description) : token(std::move(token)), type(type), groupId(groupId), channelId(channelId), - created(created), description(std::move(description)) {} +void TokenManager::save_token(const std::shared_ptr &token) { + sql::command command{this->sql_manager, "UPDATE `tokens` SET `description` = :description, `max_uses` = :max_uses, `timestamp_expired` = :timestamp_expired WHERE `token_id` = :token_id;"}; + command.value(":token_id", token->id); + command.value(":description", token->description); + command.value(":max_uses", token->max_uses); + command.value(":timestamp_expired", std::chrono::floor(token->timestamp_expired.time_since_epoch()).count()); + command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed update token"}); +} + +void TokenManager::delete_token(TokenId token_id) { + { + sql::command command{this->sql_manager, "DELETE FROM `tokens` WHERE `token_id` = :token_id;"}; + command.value(":token_id", token_id); + command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed delete token"}); + } + + { + sql::command command{this->sql_manager, "DELETE FROM `token_actions` WHERE `token_id` = :token_id;"}; + command.value(":token_id", token_id); + command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed delete token actions"}); + } +} + +void TokenManager::log_token_use(TokenId token_id) { + sql::command command{this->sql_manager, "UPDATE `tokens` SET `use_count` = `use_count` + 1 WHERE `token_id` = :token_id;"}; + command.value(":token_id", token_id); + command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to log toke use"}); +} + +void TokenManager::handle_channel_deleted(ChannelId channel_id) { + sql::command command{this->sql_manager, "DELETE FROM `token_actions` WHERE `server_id` = :server_id AND `type` = :type AND `id2` = :id2;"}; + command.value(":server_id", this->virtual_server_id); + command.value(":type", (uint8_t) ActionType::SetChannelGroup); + command.value(":id2", channel_id); + command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed cleanup token actions for a deleted channel"}); +} + +void TokenManager::handle_channel_group_deleted(GroupId group_id) { + sql::command command{this->sql_manager, "DELETE FROM `token_actions` WHERE `server_id` = :server_id AND `type` = :type AND `id1` = :id1;"}; + command.value(":server_id", this->virtual_server_id); + command.value(":type", (uint8_t) ActionType::SetChannelGroup); + command.value(":id1", group_id); + command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed cleanup token actions for a deleted channel group"}); +} + +void TokenManager::handle_server_group_deleted(GroupId group_id) { + { + sql::command command{this->sql_manager, "DELETE FROM `token_actions` WHERE `server_id` = :server_id AND `type` = :type AND `id1` = :id1;"}; + command.value(":server_id", this->virtual_server_id); + command.value(":type", (uint8_t) ActionType::AddServerGroup); + command.value(":id1", group_id); + command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed cleanup token actions for a deleted server group"}); + } + + { + sql::command command{this->sql_manager, "DELETE FROM `token_actions` WHERE `server_id` = :server_id AND `type` = :type AND `id1` = :id1;"}; + command.value(":server_id", this->virtual_server_id); + command.value(":type", (uint8_t) ActionType::RemoveServerGroup); + command.value(":id1", group_id); + command.executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed cleanup token actions for a deleted server group"}); + } +} \ No newline at end of file diff --git a/server/src/manager/TokeManager.h b/server/src/manager/TokeManager.h index f984b88..ef6825b 100644 --- a/server/src/manager/TokeManager.h +++ b/server/src/manager/TokeManager.h @@ -4,48 +4,147 @@ #include #include #include +#include #include "Definitions.h" -namespace ts { - namespace server { - class VirtualServer; - } +namespace ts::server::token { + typedef uint64_t TokenId; + typedef uint64_t TokenActionId; - namespace token { - enum TokenType : uint8_t { - TOKEN_SERVER, - TOKEN_CHANNEL - }; - struct TokenEntry { - TokenEntry(std::string token, TokenType type, GroupId groupId, ChannelId channelId, const std::chrono::time_point &created, std::string description); + enum struct ActionType { + /** + * Add a server group to the client. + * `id1` will contain the server group id. + */ + AddServerGroup = 0x01, + /** + * Remove a server group from the client (if he is assigned to it) + * `id1` will contain the server group id. + */ + RemoveServerGroup = 0x02, + /** + * Set the channel group of the client for a channel. + * `id1` will contain the channel group id. + * `id2` will contain the target channel id + */ + SetChannelGroup = 0x03, + /** + * Allow the client to join (on server join) to a certain channel. + * `id2` will contain the target channel id + */ + AllowChannelJoin = 0x04, - std::string token; - TokenType type; //token_type - GroupId groupId; //token_id1 - ChannelId channelId; //token_id2 - std::chrono::time_point created; - std::string description; - }; + /** + * Sentinel for internal handling + */ + ActionSqlFailed = 0xFD, + ActionDeleted = 0xFE, + ActionIgnore = 0xFF + }; - class TokenManager { - public: - explicit TokenManager(server::VirtualServer*); - ~TokenManager(); - bool loadTokens(); + struct Token { + TokenId id{0}; - std::vector> avariableTokes(){ return tokens; } - std::shared_ptr findToken(const std::string&); - std::shared_ptr createToken(TokenType, GroupId, const std::string&, ChannelId = 0, std::string token = ""); - bool deleteToke(const std::string&); + std::string token{}; + std::string description{}; - void handleGroupDelete(GroupId); - private: - int loadTokenFromDb(int length, char** values, char** columns); - server::VirtualServer* handle; + ClientDbId issuer_database_id{0}; - std::mutex token_lock{}; - std::vector> tokens; - }; - } + size_t max_uses{0}; + size_t use_count{0}; + + std::chrono::system_clock::time_point timestamp_expired{}; + std::chrono::system_clock::time_point timestamp_created{}; + + [[nodiscard]] inline bool is_expired() const { + return this->timestamp_expired.time_since_epoch().count() > 0 && std::chrono::system_clock::now() >= this->timestamp_expired; + } + + [[nodiscard]] inline bool use_limit_reached() const { + return this->max_uses > 0 && this->use_count >= this->max_uses; + } + }; + + struct TokenAction { + TokenActionId id{0}; + ActionType type; + uint64_t id1{0}; + uint64_t id2{0}; + std::string text{}; + }; + + class TokenManager { + public: + explicit TokenManager(sql::SqlManager*, ServerId); + ~TokenManager(); + + void initialize_cache(); + void finalize_cache(); + + [[nodiscard]] std::shared_ptr load_token(const std::string& /* token */, bool /* ignore limits */); + [[nodiscard]] std::shared_ptr load_token_by_id(TokenId /* token id */, bool /* ignore limits */); + + void log_token_use(TokenId /* token id */); + + /** + * Create a new token + * @returns `nullptr` if some database errors occurred else the generated token. + */ + [[nodiscard]] std::shared_ptr create_token( + ClientDbId /* issuer */, + const std::string& /* description */, + size_t /* max uses */, + const std::chrono::system_clock::time_point& /* expire timestamp */ + ); + + /** + * List all tokens + * @return + */ + [[nodiscard]] std::deque> list_tokens( + size_t& /* total tokens */, + const std::optional& /* offset */, + const std::optional& /* limit */, + const std::optional& /* owner */ + ); + + [[nodiscard]] size_t client_token_count(ClientDbId /* client */); + + /** + * If the operation succeeds the new action id will be applied to the token. + * Note: Actions set to `ActionType::ActionDelete` or `ActionType::ActionIgnore` will be ignored. + * If the sql insert failed the type will be set to ActionSqlFailed + */ + void add_token_actions(TokenId /* token */, std::vector& /* actions */); + + /** + * Query all actions related to the token. + */ + [[nodiscard]] bool query_token_actions(TokenId /* token */, std::deque& /* result */); + + /** + * Delete an action from the token. + */ + void delete_token_actions(TokenId /* token */, const std::vector& /* actions */); + + /** + * Save any modifications made to the token. + * Editable fields: + * - `description` + * - `max_uses` + * - `timestamp_expired` + */ + void save_token(const std::shared_ptr& /* token */); + void delete_token(TokenId /* token id */); + + void handle_server_group_deleted(GroupId); + void handle_channel_group_deleted(GroupId); + void handle_channel_deleted(ChannelId); + private: + ServerId virtual_server_id; + sql::SqlManager* sql_manager; + + void load_tokens(std::deque>& /* result */, sql::command& /* command */); + }; } -DEFINE_VARIABLE_TRANSFORM(ts::token::TokenType, VARTYPE_INT, std::to_string((uint8_t) in), static_cast(in.as())); \ No newline at end of file +DEFINE_VARIABLE_TRANSFORM_ENUM(ts::server::token::ActionType, uint8_t); \ No newline at end of file diff --git a/server/src/server/VoiceIOManager.h b/server/src/server/VoiceIOManager.h index 16bf96d..8350212 100644 --- a/server/src/server/VoiceIOManager.h +++ b/server/src/server/VoiceIOManager.h @@ -72,6 +72,9 @@ namespace ts { } inline void push_dg_write_queue(server::udp::DatagramPacket* packet) { + assert(!packet->next_packet); + packet->next_packet = nullptr; + std::lock_guard lock(this->write_queue_lock); if(this->dg_write_queue_tail) { this->dg_write_queue_tail->next_packet = packet;