diff --git a/MusicBot/CMakeLists.txt b/MusicBot/CMakeLists.txt index 47e27e6..d3c227c 100644 --- a/MusicBot/CMakeLists.txt +++ b/MusicBot/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.6) project(TeaMusic) set(CMAKE_VERBOSE_MAKEFILE ON) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -Wall -Wno-sign-compare -Wno-reorder -static-libgcc -static-libstdc++ -Wl,--enable-new-dtags,--export-dynamic") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -Wall -Wno-sign-compare -Wno-reorder -static-libgcc -static-libstdc++") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/enviroment/) @@ -14,8 +14,8 @@ add_definitions(-DLTM_DESC) #The basic library add_library(TeaMusic SHARED src/MusicPlayer.cpp) -target_link_libraries(TeaMusic PUBLIC TeaSpeak) +target_link_libraries(TeaMusic PUBLIC TeaSpeak libevent::core libevent::pthreads) #The test file -add_executable(TeaMusicTest ${MUSIC_SOURCE_FILES} main.cpp) -target_link_libraries(TeaMusicTest ProviderFFMpeg ProviderYT ProviderOpus TeaMusic pthread ThreadPool opus asound opusfile stdc++fs dl mpg123 avformat avcodec avutil TeaSpeak CXXTerminal event jsoncpp) +#add_executable(TeaMusicTest ${MUSIC_SOURCE_FILES} main.cpp) +#target_link_libraries(TeaMusicTest ProviderFFMpeg ProviderYT ProviderOpus TeaMusic pthread ThreadPool opus asound opusfile stdc++fs dl mpg123 avformat avcodec avutil TeaSpeak CXXTerminal event jsoncpp) diff --git a/MusicBot/src/MusicPlayer.cpp b/MusicBot/src/MusicPlayer.cpp index 9f3b88b..852799c 100644 --- a/MusicBot/src/MusicPlayer.cpp +++ b/MusicBot/src/MusicPlayer.cpp @@ -15,12 +15,12 @@ void log::log(const Level& lvl, const std::string& msg) { } void AbstractMusicPlayer::registerEventHandler(const std::string& key, const std::function& function) { - threads::MutexLock lock(this->eventLock); + std::lock_guard lock(this->eventLock); this->eventHandlers.emplace_back(key, function); } void AbstractMusicPlayer::unregisterEventHandler(const std::string& string) { - threads::MutexLock lock(this->eventLock); + std::lock_guard lock(this->eventLock); for(const auto& entry : this->eventHandlers){ if(entry.first == string) { this->eventHandlers.erase(find_if(this->eventHandlers.begin(), this->eventHandlers.end(), [string](const std::pair>& elm){ return elm.first == string; })); @@ -30,7 +30,7 @@ void AbstractMusicPlayer::unregisterEventHandler(const std::string& string) { } void AbstractMusicPlayer::fireEvent(MusicEvent event) { - threads::MutexLock lock(this->eventLock); + std::lock_guard lock(this->eventLock); auto listCopy = this->eventHandlers; //Copy for remove while fire for(const auto& entry : listCopy) entry.second(event); @@ -38,18 +38,18 @@ void AbstractMusicPlayer::fireEvent(MusicEvent event) { const char* music::stateNames[] = {"uninitialised", "playing", "paused", "stopped"}; -static threads::Mutex staticLock; +static std::mutex staticLock; static std::deque> types; std::deque> manager::registeredTypes(){ return types; } void registerType(const std::shared_ptr& provider) { - threads::MutexLock l(staticLock); + std::lock_guard l(staticLock); types.push_back(provider); } //empty for not set std::shared_ptr manager::resolveProvider(const std::string& provName, const std::string& str) { - threads::MutexLock l(staticLock); + std::lock_guard l(staticLock); vector> provs; for(const auto& prov : types){ auto p = prov.get(); @@ -111,11 +111,11 @@ void manager::loadProviders(const std::string& path) { } void manager::register_provider(const std::shared_ptr &provider) { - threads::MutexLock l(staticLock); + std::lock_guard l(staticLock); types.push_back(provider); } void manager::finalizeProviders() { - threads::MutexLock l(staticLock); + std::lock_guard l(staticLock); types.clear(); } \ No newline at end of file diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index ee3926d..50639a4 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -152,7 +152,7 @@ if (COMPILE_WEB_CLIENT) src/client/web/WSWebClient.cpp src/client/web/SampleHandler.cpp src/client/web/VoiceBridge.cpp - src/client/command_handler/helpers.h) + src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h) endif () add_executable(PermHelper helpers/permgen.cpp) diff --git a/server/src/DatabaseHelper.cpp b/server/src/DatabaseHelper.cpp index 1fea6f4..40aea73 100644 --- a/server/src/DatabaseHelper.cpp +++ b/server/src/DatabaseHelper.cpp @@ -232,7 +232,7 @@ inline sql::result load_permissions(const std::shared_ptr& server auto time = end - start; logTrace(server ? server->getServerId() : 0, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast(time).count()); } -inline sql::result load_permissions_v2(const std::shared_ptr& server, v2::PermissionManager* manager, sql::command& command, bool resolve_channel /* only used for client permissions (client channel permissions) */) { +inline sql::result load_permissions_v2(const std::shared_ptr& server, v2::PermissionManager* manager, sql::command& command, bool test_channel /* only used for client permissions (client channel permissions) */) { auto start = system_clock::now(); auto server_id = server ? server->getServerId() : 0; @@ -240,9 +240,9 @@ inline sql::result load_permissions_v2(const std::shared_ptr& ser permission::PermissionType key = permission::PermissionType::undefined; permission::PermissionValue value = permNotGranted, granted = permNotGranted; bool negated = false, skipped = false; - std::shared_ptr channel; - + ChannelId channel_id{0}; int index; + try { for(index = 0; index < length; index++) { if(strcmp(names[index], "permId") == 0) { @@ -256,18 +256,7 @@ inline sql::result load_permissions_v2(const std::shared_ptr& ser return 0; } } else if(strcmp(names[index], "channelId") == 0) { - if(resolve_channel) { - auto channelId = stoull(values[index]); - if(channelId > 0) { - if(!server) - logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]); - else { - channel = server->getChannelTree()->findChannel(channelId); - if(!channel) - logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]); - } - } - } + channel_id = stoull(values[index]); } else if(strcmp(names[index], "value") == 0) { value = stoi(values[index]); } else if(strcmp(names[index], "grant") == 0) { @@ -281,16 +270,26 @@ inline sql::result load_permissions_v2(const std::shared_ptr& ser logError(server_id, "[SQL] Cant load permissions! Failed to parse value! Command: {} Message: {} Key: {} Value: {}", command.sqlCommand(), ex.what(),names[index], values[index]); return 0; } + if(channel_id > 0 && test_channel) { + if(!server) + logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]); + else { + auto channel = server->getChannelTree()->findChannel(channel_id); + if(!channel) + logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]); + } + } + if(key == permission::undefined) { debugMessage(server_id, "[SQL] Permission entry misses permission type! Command: {}", command.sqlCommand()); return 0; } - if(!resolve_channel || !channel) + if(channel_id == 0) manager->load_permission(key, {value, granted}, skipped, negated, value != permNotGranted, granted != permNotGranted); else - manager->load_permission(key, {value, granted}, channel->channelId(), skipped, negated, value != permNotGranted, granted != permNotGranted); + manager->load_permission(key, {value, granted}, channel_id, skipped, negated, value != permNotGranted, granted != permNotGranted); return 0; }); @@ -476,8 +475,8 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptr DatabaseHelper::loadPlaylistPermissions(const std::shared_ptr &server, ts::PlaylistId playlist_id) { - shared_ptr result; +std::shared_ptr DatabaseHelper::loadPlaylistPermissions(const std::shared_ptr &server, ts::PlaylistId playlist_id) { + shared_ptr result; if(this->use_startup_cache && server) { shared_ptr entry; { @@ -490,71 +489,59 @@ std::shared_ptr DatabaseHelper::loadPlaylistPermi } } if(entry) { - result = std::make_shared(); + result = std::make_shared(); for(const auto& perm : entry->permissions) { if(perm->type == permission::SQL_PERM_PLAYLIST && perm->id == playlist_id) { - auto permission = result->registerPermission(perm->permission, perm->value, server->getChannelTree()->findChannel(perm->channelId)); - permission->granted = perm->grant; - permission->value = perm->value; - permission->dbReference = true; - permission->flag_negate = perm->flag_negate; - permission->flag_skip = perm->flag_skip; + result->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted); } } } + + return result; } - if(!result) { - result = std::make_shared(); - auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", - variable{":serverId", server ? server->getServerId() : 0}, - variable{":type", permission::SQL_PERM_PLAYLIST}, - variable{":id", playlist_id}); - LOG_SQL_CMD(load_permissions(server, result.get(), command)); - } + result = std::make_shared(); + auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", + variable{":serverId", server ? server->getServerId() : 0}, + variable{":type", permission::SQL_PERM_PLAYLIST}, + variable{":id", playlist_id}); + LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false)); + return result; +} + +void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr &server, PlaylistId pid, const std::shared_ptr &permissions) { + const auto updates = permissions->flush_db_updates(); + if(updates.empty()) + return; - assert(result); - weak_ptr weak_server = server; auto server_id = server ? server->getServerId() : 0; + for(auto& update : updates) { + std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND); - result->registerUpdateHandler([&, weak_server, server_id, playlist_id](std::shared_ptr permission) { - auto server = weak_server.lock(); - if(!server && server_id != 0) { - logError(server_id, "Tried to update a playlist permission of a expired server!"); - return; - } - - std::string query; - - /* - if(permission->deleted){ - query = "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `id` = :id AND `type`= :type AND `permId`= :permId AND `channelId` = :chId"; - } else - */ - if(permission->dbReference){ - query = UPDATE_COMMAND; - } else { - permission->dbReference = true; - query = INSERT_COMMAND; - } - - logTrace(server ? server->getServerId() : 0, "[SQL] Executing group permission command: {}", query); + auto permission_data = permission::resolvePermissionData(update.permission); + auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value; + auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant; + logTrace(server_id, "[PLAYLIST] Updating playlist permission for playlist {}: {}. New value: {}. New grant: {}. Query: {}", + pid, + permission_data->name, + value, + grant, + query + ); sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, - variable{":id", playlist_id}, + variable{":id", pid}, + variable{":chId", 0}, variable{":type", permission::SQL_PERM_PLAYLIST}, - variable{":chId", permission->channelId()}, - variable{":permId", permission->type->name}, - variable{":value", permission->value}, - variable{":grant", permission->granted}, - variable{":flag_skip", permission->flag_skip}, - variable{":flag_negate", permission->flag_negate}) - .executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to save playlist permission " + to_string(playlist_id)}); - }); - - return result; + variable{":permId", permission_data->name}, + variable{":value", value}, + variable{":grant", grant}, + variable{":flag_skip", update.flag_skip}, + variable{":flag_negate", update.flag_negate}) + .executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "future error"}); + } } std::shared_ptr DatabaseHelper::loadChannelPermissions(const std::shared_ptr& server, ts::ChannelId channel) { diff --git a/server/src/DatabaseHelper.h b/server/src/DatabaseHelper.h index 0ec3d55..7e3c4aa 100644 --- a/server/src/DatabaseHelper.h +++ b/server/src/DatabaseHelper.h @@ -91,16 +91,17 @@ namespace ts { std::deque> queryDatabaseInfo(const std::shared_ptr&, const std::deque&); std::deque> queryDatabaseInfoByUid(const std::shared_ptr &, std::deque); - std::shared_ptr loadClientPermissionManager(const std::shared_ptr&, ClientDbId); //Just and write + std::shared_ptr loadClientPermissionManager(const std::shared_ptr&, ClientDbId); void saveClientPermissions(const std::shared_ptr&, ClientDbId , const std::shared_ptr& /* permission manager */); - std::shared_ptr loadChannelPermissions(const std::shared_ptr&, ChannelId); //Just read + std::shared_ptr loadChannelPermissions(const std::shared_ptr&, ChannelId); void saveChannelPermissions(const std::shared_ptr&, ChannelId, const std::shared_ptr& /* permission manager */); - std::shared_ptr loadGroupPermissions(const std::shared_ptr&, GroupId); //Just read + std::shared_ptr loadGroupPermissions(const std::shared_ptr&, GroupId); void saveGroupPermissions(const std::shared_ptr&, GroupId, const std::shared_ptr& /* permission manager */); - std::shared_ptr loadPlaylistPermissions(const std::shared_ptr&, PlaylistId /* playlist id */); //Read and write + std::shared_ptr loadPlaylistPermissions(const std::shared_ptr&, PlaylistId /* playlist id */); + void savePlaylistPermissions(const std::shared_ptr&, PlaylistId, const std::shared_ptr& /* permission manager */); std::shared_ptr loadServerProperties(const std::shared_ptr&); //Read and write std::shared_ptr loadPlaylistProperties(const std::shared_ptr&, PlaylistId); //Read and write diff --git a/server/src/TS3ServerClientManager.cpp b/server/src/TS3ServerClientManager.cpp index 8f32204..9a60e19 100644 --- a/server/src/TS3ServerClientManager.cpp +++ b/server/src/TS3ServerClientManager.cpp @@ -215,7 +215,7 @@ void VirtualServer::testBanStateChange(const std::shared_ptr& i logMessage(this->getServerId(), "Client {} was online, but had an ban whcih effect him has been registered. Disconnecting client.", CLIENT_STR_LOG_PREFIX_(client)); auto entryTime = ban->until.time_since_epoch().count() > 0 ? (uint64_t) chrono::ceil(ban->until - system_clock::now()).count() : 0UL; this->notify_client_ban(client, invoker, ban->reason, entryTime); - client->closeConnection(system_clock::now() + seconds(1)); + client->close_connection(system_clock::now() + seconds(1)); } }); } diff --git a/server/src/TS3ServerHeartbeat.cpp b/server/src/TS3ServerHeartbeat.cpp index cd17fd7..a27c801 100644 --- a/server/src/TS3ServerHeartbeat.cpp +++ b/server/src/TS3ServerHeartbeat.cpp @@ -5,6 +5,7 @@ #include "InstanceHandler.h" #include "VirtualServer.h" #include "./manager/ConversationManager.h" +#include "./music/MusicBotManager.h" using namespace std; using namespace std::chrono; @@ -12,7 +13,6 @@ using namespace ts::server; using namespace ts::protocol; using namespace ts::buffer; -extern InstanceHandler* serverInstance; inline void banClientFlood(VirtualServer* server, const shared_ptr& cl, time_point until){ auto time = until.time_since_epoch().count() == 0 ? 0L : chrono::ceil(until - system_clock::now()).count(); @@ -21,7 +21,7 @@ inline void banClientFlood(VirtualServer* server, const shared_ptrfindClientsByUid(cl->getUid())) { server->notify_client_ban(client, server->getServerRoot(), reason, time); - client->closeConnection(system_clock::now() + seconds(1)); + client->close_connection(system_clock::now() + seconds(1)); } } @@ -56,7 +56,7 @@ void VirtualServer::executeServerTick() { lastTick = system_clock::now(); system_clock::time_point timing_begin, timing_end; - milliseconds timing_update_states, timing_client_tick, timing_channel, timing_statistic, timing_groups, timing_ccache; + milliseconds timing_update_states, timing_client_tick, timing_channel, timing_statistic, timing_groups, timing_ccache, music_manager; auto client_list = this->getClients(); @@ -269,6 +269,12 @@ void VirtualServer::executeServerTick() { END_TIMINGS(timing_ccache); } + { + BEGIN_TIMINGS(); + this->musicManager->execute_tick(); + END_TIMINGS(music_manager); + } + if(system_clock::now() - lastTick > milliseconds(100)) { //milliseconds timing_update_states, timing_client_tick, timing_channel, timing_statistic; logError(this->serverId, "Server tick tooks to long ({}ms => Status updates: {}ms Client tick: {}ms, Channel tick: {}ms, Statistic tick: {}ms, Groups: {}ms, Conversation cache: {}ms)", diff --git a/server/src/VirtualServer.cpp b/server/src/VirtualServer.cpp index 550e93e..5a542f5 100644 --- a/server/src/VirtualServer.cpp +++ b/server/src/VirtualServer.cpp @@ -479,7 +479,7 @@ void VirtualServer::stop(const std::string& reason) { for(const auto& cl : this->getClients()) { //start disconnecting if(cl->getType() == CLIENT_TEAMSPEAK || cl->getType() == CLIENT_TEASPEAK || cl->getType() == CLIENT_WEB) { - cl->closeConnection(chrono::system_clock::now() + chrono::seconds(1)); + cl->close_connection(chrono::system_clock::now() + chrono::seconds(1)); } else if(cl->getType() == CLIENT_QUERY){ threads::MutexLock lock(cl->command_lock); cl->currentChannel = nullptr; @@ -575,7 +575,7 @@ OnlineClientReport VirtualServer::onlineStats() { return response; } -std::shared_ptr VirtualServer::findClient(uint16_t client_id) { +std::shared_ptr VirtualServer::find_client_by_id(uint16_t client_id) { lock_guard lock(this->clients.lock); if(this->clients.clients.size() > client_id) return this->clients.clients[client_id]; diff --git a/server/src/VirtualServer.h b/server/src/VirtualServer.h index fb6caf1..e474d2c 100644 --- a/server/src/VirtualServer.h +++ b/server/src/VirtualServer.h @@ -149,7 +149,7 @@ namespace ts { size_t onlineClients(); OnlineClientReport onlineStats(); size_t onlineChannels(){ return this->channelTree->channel_count(); } - std::shared_ptr findClient(ClientId clientId); + std::shared_ptr find_client_by_id(ClientId /* client id */); std::deque> findClientsByCldbId(ClientDbId cldbId); std::deque> findClientsByUid(ClientUid uid); std::shared_ptr findClient(std::string name, bool ignoreCase = true); diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 9d117da..460ad44 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -835,7 +835,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) { this->notifyError(result, cmd["return_code"].size() > 0 ? cmd["return_code"].first().as() : ""); if(result.error_code() != error::ok && this->state == ConnectionState::INIT_HIGH) - this->closeConnection(system_clock::now()); //Disconnect now + this->close_connection(system_clock::now()); //Disconnect now for (const auto& handler : postCommandHandler) handler(); @@ -931,8 +931,7 @@ bool ConnectedClient::update_cached_permissions() { for(const auto& entry : values) { if(entry.first == permission::i_client_whisper_power) this->cpmerission_whisper_power = entry.second; - else - if(entry.first == permission::i_client_needed_whisper_power) + else if(entry.first == permission::i_client_needed_whisper_power) this->cpmerission_needed_whisper_power = entry.second; } diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index 6531d7b..98ddc8e 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -232,7 +232,10 @@ namespace ts { virtual bool notifyConversationMessageDelete(const ChannelId /* conversation id */, const std::chrono::system_clock::time_point& /* begin timestamp */, const std::chrono::system_clock::time_point& /* begin end */, ClientDbId /* client id */, size_t /* messages */); - virtual bool closeConnection(const std::chrono::system_clock::time_point &timeout = std::chrono::system_clock::time_point()) = 0; + /* this method should be callable from everywhere; the method is non blocking! */ + virtual bool close_connection(const std::chrono::system_clock::time_point &timeout) = 0; + + /* this method should be callable from everywhere; the method is non blocking! */ virtual bool disconnect(const std::string& reason) = 0; void resetIdleTime(); @@ -255,6 +258,7 @@ namespace ts { inline std::shared_ptr ref() { return _this.lock(); } + std::shared_mutex& get_channel_lock() { return this->channel_lock; } /* * permission stuff */ @@ -271,18 +275,36 @@ namespace ts { } */ bool update_cached_permissions(); + + std::shared_lock require_connected_state(bool blocking = false) { + //try_to_lock_t + std::shared_lock disconnect_lock{}; + if(__glibc_unlikely(blocking)) + disconnect_lock = std::shared_lock{this->finalDisconnectLock}; + else + disconnect_lock = std::shared_lock{this->finalDisconnectLock, std::try_to_lock}; + + if(__glibc_unlikely(!disconnect_lock)) + return disconnect_lock; + + { + std::lock_guard state_lock{this->state_lock}; + if(this->state != ConnectionState::CONNECTED) + return {}; + } + return disconnect_lock; + } protected: std::weak_ptr _this; + sockaddr_storage remote_address; //General states std::mutex state_lock; - ConnectionState state = ConnectionState::UNKNWON; - sockaddr_storage remote_address; + ConnectionState state{ConnectionState::UNKNWON}; bool allowedToTalk = false; - threads::Mutex disconnectLock; - std::shared_mutex finalDisconnectLock; + std::shared_mutex finalDisconnectLock; /* locked before state lock! */ std::vector cached_server_groups{}; /* variable locked with channel_lock */ GroupId cached_channel_group = 0; /* variable locked with channel_lock */ @@ -510,10 +532,15 @@ namespace ts { command_result handleCommandPlaylistList(Command&); command_result handleCommandPlaylistCreate(Command&); command_result handleCommandPlaylistDelete(Command&); + command_result handleCommandPlaylistPermList(Command&); command_result handleCommandPlaylistAddPerm(Command&); command_result handleCommandPlaylistDelPerm(Command&); + command_result handleCommandPlaylistClientPermList(Command&); + command_result handleCommandPlaylistClientAddPerm(Command&); + command_result handleCommandPlaylistClientDelPerm(Command&); + /* playlist properties */ command_result handleCommandPlaylistInfo(Command&); command_result handleCommandPlaylistEdit(Command&); @@ -595,5 +622,45 @@ namespace ts { return ""; } }; + + template + struct ConnectedLockedClient { + explicit ConnectedLockedClient(std::shared_ptr client) : client{std::move(client)} { + if(this->client) + this->connection_lock = this->client->require_connected_state(); + } + + inline ConnectedLockedClient &operator=(const ConnectedLockedClient& other) { + this->client = other.client; + if(other) + this->connection_lock = std::shared_lock{*other.connection_lock.mutex()}; /* if the other is true (state locked & client) than we could easily acquire a shared lock */ + } + + inline ConnectedLockedClient &operator=(ConnectedLockedClient&& other) { + this->client = std::move(other.client); + this->connection_lock = std::move(other.connection_lock); + } + + inline bool valid() const { return !!this->client && !!this->connection_lock; } + + inline operator bool() const { return this->valid(); } + + inline bool operator!() const { return !this->valid(); } + + template + inline bool operator==(const std::shared_ptr<_T>& other) { return this->client.get() == other.get(); } + + T* operator->() { return &*this->client; } + + T &operator*() { return *this->client; } + + std::shared_ptr client; + std::shared_lock connection_lock{}; + }; } +} + +template +inline bool operator==(const std::shared_ptr& a, const ts::server::ConnectedLockedClient& b) { + return a.get() == b.client.get(); } \ No newline at end of file diff --git a/server/src/client/InternalClient.cpp b/server/src/client/InternalClient.cpp index 2b65241..a73126e 100644 --- a/server/src/client/InternalClient.cpp +++ b/server/src/client/InternalClient.cpp @@ -25,7 +25,7 @@ InternalClient::~InternalClient() { void InternalClient::sendCommand(const ts::Command &command, bool low) { } void InternalClient::sendCommand(const ts::command_builder &command, bool low) { } -bool InternalClient::closeConnection(const std::chrono::system_clock::time_point& timeout) { +bool InternalClient::close_connection(const std::chrono::system_clock::time_point& timeout) { logError(this->getServerId(), "Internal client is force to disconnect?"); if(this->server) diff --git a/server/src/client/InternalClient.h b/server/src/client/InternalClient.h index dce5335..ffc562c 100644 --- a/server/src/client/InternalClient.h +++ b/server/src/client/InternalClient.h @@ -16,7 +16,7 @@ namespace ts { void sendCommand(const ts::Command &command, bool low) override; void sendCommand(const ts::command_builder &command, bool low) override; - bool closeConnection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override; + bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override; bool disconnect(const std::string &reason) override; protected: void tick(const std::chrono::system_clock::time_point &time) override; diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index 3a50d90..42ba4ba 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -602,13 +602,12 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) { this->postCommandHandler.emplace_back([&](){ auto self = dynamic_pointer_cast(_this.lock()); std::thread([self](){ - threads::MutexLock l1(self->disconnectLock); if(self->state != ConnectionState::INIT_HIGH) return; try { self->processJoin(); } catch (std::exception& ex) { logError(self->getServerId(), "Failed to proceed client join for {}. Got exception with message {}", CLIENT_STR_LOG_PREFIX_(self), ex.what()); - self->closeConnection(); + self->close_connection(chrono::system_clock::now() + chrono::seconds{5}); } }).detach(); }); @@ -670,7 +669,7 @@ void SpeakingClient::processJoin() { auto result = command_result{error::vs_critical, "Could not assign default channel!"}; this->notifyError(result); result.release_details(); - this->closeConnection(system_clock::now() + seconds(1)); + this->close_connection(system_clock::now() + seconds(1)); return; } TIMING_STEP(timings, "assign chan"); @@ -767,7 +766,7 @@ void SpeakingClient::tick(const std::chrono::system_clock::time_point &time) { auto max_idle = this->max_idle_time.value; if(max_idle > 0 && this->idleTimestamp.time_since_epoch().count() > 0 && duration_cast(time - this->idleTimestamp).count() > max_idle) { this->server->notify_client_kick(this->ref(), this->server->getServerRoot(), ts::config::messages::idle_time_exceeded, nullptr); - this->closeConnection(system_clock::now() + seconds(1)); + this->close_connection(system_clock::now() + seconds(1)); } } @@ -792,7 +791,7 @@ command_result SpeakingClient::handleCommand(Command &command) { if(result.error_code()) this->postCommandHandler.push_back([&]{ - this->closeConnection(system_clock::now() + seconds(1)); + this->close_connection(system_clock::now() + seconds(1)); }); return result; } diff --git a/server/src/client/command_handler/client.cpp b/server/src/client/command_handler/client.cpp index c67290e..39982e4 100644 --- a/server/src/client/command_handler/client.cpp +++ b/server/src/client/command_handler/client.cpp @@ -50,10 +50,10 @@ using namespace ts::token; command_result ConnectedClient::handleCommandClientGetVariables(Command &cmd) { CMD_REQ_SERVER; - auto client = this->server->findClient(cmd["clid"].as()); + ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; shared_lock tree_lock(this->channel_lock); - if (!client || (client != this && !this->isClientVisible(client, false))) + if (!client || (client.client != this && !this->isClientVisible(client.client, false))) return command_result{error::client_invalid_id, ""}; deque> props; @@ -61,7 +61,7 @@ command_result ConnectedClient::handleCommandClientGetVariables(Command &cmd) { props.push_back(property::info((property::ClientProperties) prop.type().property_index)); } - this->notifyClientUpdated(client, props, false); + this->notifyClientUpdated(client.client, props, false); return command_result{error::ok}; } @@ -69,26 +69,26 @@ command_result ConnectedClient::handleCommandClientKick(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(25); - auto client = this->server->findClient(cmd["clid"].as()); + ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; if (client->getType() == CLIENT_MUSIC) return command_result{error::client_invalid_type, "You cant kick a music bot!"}; std::shared_ptr targetChannel = nullptr; auto type = cmd["reasonid"].as(); if (type == ViewReasonId::VREASON_CHANNEL_KICK) { - auto channel = client->currentChannel; + auto channel = client->getChannel(); ACTION_REQUIRES_PERMISSION(permission::i_client_kick_from_channel_power, client->calculate_permission(permission::i_client_needed_kick_from_channel_power, client->getChannelId()), client->getChannelId()); targetChannel = this->server->channelTree->getDefaultChannel(); } else if (type == ViewReasonId::VREASON_SERVER_KICK) { - auto channel = client->currentChannel; + auto channel = client->getChannel(); ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_kick_from_server_power, client->calculate_permission(permission::i_client_needed_kick_from_server_power, client->getChannelId())); targetChannel = nullptr; } else return command_result{error::not_implemented}; if (targetChannel) { - this->server->notify_client_kick(client, this->ref(), cmd["reasonmsg"].as(), targetChannel); + this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as(), targetChannel); } else { - this->server->notify_client_kick(client, this->ref(), cmd["reasonmsg"].as(), nullptr); - client->closeConnection(system_clock::now() + seconds(1)); + this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as(), nullptr); + client->close_connection(system_clock::now() + seconds(1)); } return command_result{error::ok}; @@ -139,13 +139,13 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { shared_lock server_channel_r_lock(this->server->channel_tree_lock); auto target_client_id = cmd["clid"].as(); - auto target_client = target_client_id == 0 ? this->ref() : this->server->findClient(target_client_id); + ConnectedLockedClient target_client{target_client_id == 0 ? this->ref() : this->server->find_client_by_id(target_client_id)}; if(!target_client) { return command_result{error::client_invalid_id, "Invalid target clid"}; } - if(!target_client->currentChannel) { - if(target_client != this) + if(!target_client->getChannel()) { + if(target_client.client != this) return command_result{error::client_invalid_id, "Invalid target clid"}; } auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); @@ -188,18 +188,18 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { } } - if (target_client != this) + if (target_client.client != this) ACTION_REQUIRES_PERMISSION(permission::i_client_move_power, target_client->calculate_permission(permission::i_client_needed_move_power, target_client->getChannelId()), target_client->getChannelId()); server_channel_r_lock.unlock(); unique_lock server_channel_w_lock(this->server->channel_tree_lock); auto oldChannel = target_client->getChannel(); this->server->client_move( - target_client, + target_client.client, channel, - target_client == this ? nullptr : _this.lock(), + target_client.client == this ? nullptr : _this.lock(), "", - target_client == this ? ViewReasonId::VREASON_USER_ACTION : ViewReasonId::VREASON_MOVED, + target_client.client == this ? ViewReasonId::VREASON_USER_ACTION : ViewReasonId::VREASON_MOVED, true, server_channel_w_lock ); @@ -221,7 +221,7 @@ command_result ConnectedClient::handleCommandClientPoke(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); - auto client = this->server->findClient(cmd["clid"].as()); + ConnectedLockedClient client{ this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; if (client->getType() == CLIENT_MUSIC) return command_result{error::client_invalid_type}; ACTION_REQUIRES_PERMISSION(permission::i_client_poke_power, client->calculate_permission(permission::i_client_needed_poke_power, client->getChannelId()), client->getChannelId()); @@ -235,7 +235,7 @@ command_result ConnectedClient::handleCommandClientChatComposing(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(0); - auto client = this->server->findClient(cmd["clid"].as()); + ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; client->notifyClientChatComposing(_this.lock()); @@ -246,16 +246,16 @@ command_result ConnectedClient::handleCommandClientChatClosed(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); - auto client = this->server->findClient(cmd["clid"].as()); + ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; { unique_lock channel_lock(this->channel_lock); - this->openChats.erase(remove_if(this->openChats.begin(), this->openChats.end(), [client](const weak_ptr& weak) { + this->openChats.erase(remove_if(this->openChats.begin(), this->openChats.end(), [&](const weak_ptr& weak) { return weak.lock() == client; }), this->openChats.end()); } { - unique_lock channel_lock(client->channel_lock); + unique_lock channel_lock(client->get_channel_lock()); client->openChats.erase(remove_if(client->openChats.begin(), client->openChats.end(), [&](const weak_ptr& weak) { return weak.lock().get() == this; }), client->openChats.end()); @@ -421,9 +421,9 @@ command_result ConnectedClient::handleCommandClientDBEdit(Command &cmd) { command_result ConnectedClient::handleCommandClientEdit(ts::Command &cmd) { CMD_REQ_SERVER; - auto client = this->server->findClient(cmd["clid"].as()); + ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; - return this->handleCommandClientEdit(cmd, client); + return this->handleCommandClientEdit(cmd, client.client); } command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std::shared_ptr& client) { @@ -657,14 +657,14 @@ command_result ConnectedClient::handleCommandClientMute(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; - auto client = this->server->findClient(cmd["clid"].as()); + ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client || client->getClientId() == this->getClientId()) return command_result{error::client_invalid_id}; { unique_lock channel_lock(this->channel_lock); for(const auto& weak : this->mutedClients) if(weak.lock() == client) return command_result{error::ok}; - this->mutedClients.push_back(client); + this->mutedClients.push_back(client.client); } if (config::voice::notifyMuted) @@ -677,12 +677,12 @@ command_result ConnectedClient::handleCommandClientUnmute(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; - auto client = this->server->findClient(cmd["clid"].as()); + ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client || client->getClientId() == this->getClientId()) return command_result{error::client_invalid_id}; { unique_lock channel_lock(this->channel_lock); - this->mutedClients.erase(std::remove_if(this->mutedClients.begin(), this->mutedClients.end(), [client](const weak_ptr& weak) { + this->mutedClients.erase(std::remove_if(this->mutedClients.begin(), this->mutedClients.end(), [&](const weak_ptr& weak) { auto c = weak.lock(); return !c || c == client; }), this->mutedClients.end()); @@ -1119,7 +1119,7 @@ command_result ConnectedClient::handleCommandClientInfo(Command &cmd) { auto client_id = cmd[index]["clid"].as(); if(client_id == 0) continue; - auto client = this->server->findClient(client_id); + ConnectedLockedClient client{this->server->find_client_by_id(client_id)}; if(!client) { trigger_error = true; continue; diff --git a/server/src/client/command_handler/misc.cpp b/server/src/client/command_handler/misc.cpp index 57e7518..3b0cfd6 100644 --- a/server/src/client/command_handler/misc.cpp +++ b/server/src/client/command_handler/misc.cpp @@ -228,6 +228,9 @@ command_result ConnectedClient::handleCommand(Command &cmd) { else if (command == "playlistpermlist") return this->handleCommandPlaylistPermList(cmd); else if (command == "playlistaddperm") return this->handleCommandPlaylistAddPerm(cmd); else if (command == "playlistdelperm") return this->handleCommandPlaylistDelPerm(cmd); + else if (command == "playlistclientpermlist") return this->handleCommandPlaylistClientPermList(cmd); + else if (command == "playlistclientaddperm") return this->handleCommandPlaylistClientAddPerm(cmd); + else if (command == "playlistclientdelperm") return this->handleCommandPlaylistClientDelPerm(cmd); else if (command == "playlistinfo") return this->handleCommandPlaylistInfo(cmd); else if (command == "playlistedit") return this->handleCommandPlaylistEdit(cmd); @@ -256,13 +259,13 @@ command_result ConnectedClient::handleCommandGetConnectionInfo(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); - auto client = this->server->findClient(cmd["clid"].as()); + ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as())}; if (!client) return command_result{error::client_invalid_id}; bool send_temp; auto info = client->request_connection_info(_this.lock(), send_temp); if (info) { - this->notifyConnectionInfo(client, info); + this->notifyConnectionInfo(client.client, info); } else if(send_temp) { return command_result{error::no_cached_connection_info}; } @@ -489,7 +492,7 @@ command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) { auto timestamp = system_clock::now(); if (cmd["targetmode"].as() == ChatMessageMode::TEXTMODE_PRIVATE) { - auto target = this->server->findClient(cmd["target"].as()); + ConnectedLockedClient target{this->server->find_client_by_id(cmd["target"].as())}; if (!target) return command_result{error::client_invalid_id}; bool chat_open = false; @@ -506,24 +509,24 @@ command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) { } if(!chat_open) { - if (target == this) + if (target.client == this) ACTION_REQUIRES_PERMISSION(permission::b_client_even_textmessage_send, 1, this->getChannelId()); if(!permission::v2::permission_granted(target->calculate_permission(permission::i_client_needed_private_textmessage_power, target->getClientId()), this->calculate_permission(permission::i_client_private_textmessage_power, this->getClientId()), false)) return command_result{permission::i_client_private_textmessage_power}; { - unique_lock channel_lock(target->channel_lock); + unique_lock channel_lock(target->get_channel_lock()); target->openChats.push_back(_this); } { unique_lock channel_lock(this->channel_lock); - this->openChats.push_back(target); + this->openChats.push_back(target.client); } } - if(this->handleTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, cmd["msg"], target)) return command_result{error::ok}; + if(this->handleTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, cmd["msg"], target.client)) return command_result{error::ok}; target->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), 0, timestamp, cmd["msg"].string()); this->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), 0, timestamp, cmd["msg"].string()); } else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_CHANNEL) { @@ -809,7 +812,7 @@ command_result ConnectedClient::handleCommandBanClient(Command &cmd) { if(client->getType() == ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_id, "You cant ban a music bot!"}; } else { - target_clients = {this->server->findClient(cmd["clid"].as())}; + target_clients = {this->server->find_client_by_id(cmd["clid"].as())}; if(!target_clients[0]) { return command_result{error::client_invalid_id, "Could not find target client"}; } @@ -855,7 +858,7 @@ command_result ConnectedClient::handleCommandBanClient(Command &cmd) { if (client->getType() != CLIENT_TEAMSPEAK && client->getType() != CLIENT_QUERY) continue; //Remember if you add new type you have to change stuff here this->server->notify_client_ban(client, this->ref(), reason, time); - client->closeConnection(system_clock::now() + seconds(2)); + client->close_connection(system_clock::now() + seconds(2)); string entry_name, entry_ip, entry_hardware_id; if(b_ban_name && !no_nickname) { @@ -1108,8 +1111,8 @@ command_result ConnectedClient::handleCommandPluginCmd(Command &cmd) { } else if (mode == PluginTargetMode::PLUGINCMD_CLIENT) { for (int index = 0; index < cmd.bulkCount(); index++) { auto target = cmd[index]["target"].as(); - auto cl = this->server->findClient(target); - if (!cl) return command_result{error::client_invalid_id, "invalid target id"}; + ConnectedLockedClient cl{this->server->find_client_by_id(target)}; + if (!cl) return command_result{error::client_invalid_id}; cl->notifyPluginCmd(cmd["name"], cmd["data"], _this.lock()); } } diff --git a/server/src/client/command_handler/music.cpp b/server/src/client/command_handler/music.cpp index 8a1c4ae..f096128 100644 --- a/server/src/client/command_handler/music.cpp +++ b/server/src/client/command_handler/music.cpp @@ -296,16 +296,11 @@ command_result ConnectedClient::handleCommandPlaylistList(ts::Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); - auto self_dbid = this->getClientDatabaseId(); - auto playlist_view_power = this->calculate_permission(permission::i_playlist_view_power, 0); + auto self_ref = this->ref(); auto playlists = this->server->musicManager->playlists(); playlists.erase(find_if(playlists.begin(), playlists.end(), [&](const shared_ptr& playlist) { - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] == self_dbid) - return false; - - auto needed_view_power = playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power); - return !permission::v2::permission_granted(needed_view_power, playlist_view_power, false);; + return playlist->client_has_permissions(self_ref, permission::i_playlist_needed_view_power, permission::i_playlist_view_power, music::PlaylistPermissions::do_no_require_granted) != permission::ok; }), playlists.end()); if(playlists.empty()) @@ -322,12 +317,25 @@ command_result ConnectedClient::handleCommandPlaylistList(ts::Command &cmd) { notify[index]["playlist_type"] = entry->properties()[property::PLAYLIST_TYPE].value(); notify[index]["playlist_owner_dbid"] = entry->properties()[property::PLAYLIST_OWNER_DBID].value(); notify[index]["playlist_owner_name"] = entry->properties()[property::PLAYLIST_OWNER_NAME].value(); - notify[index]["needed_power_modify"] = entry->permissions()->getPermissionValue(permission::i_playlist_needed_modify_power); - notify[index]["needed_power_permission_modify"] = entry->permissions()->getPermissionValue(permission::i_playlist_needed_permission_modify_power); - notify[index]["needed_power_delete"] = entry->permissions()->getPermissionValue(permission::i_playlist_needed_delete_power); - notify[index]["needed_power_song_add"] = entry->permissions()->getPermissionValue(permission::i_playlist_song_needed_add_power); - notify[index]["needed_power_song_move"] = entry->permissions()->getPermissionValue(permission::i_playlist_song_needed_move_power); - notify[index]["needed_power_song_remove"] = entry->permissions()->getPermissionValue(permission::i_playlist_song_needed_remove_power); + + auto permissions = entry->permission_manager(); + auto permission = permissions->permission_value_flagged(permission::i_playlist_needed_modify_power); + notify[index]["needed_power_modify"] = permission.has_value ? permission.value : 0; + + permission = permissions->permission_value_flagged(permission::i_playlist_needed_permission_modify_power); + notify[index]["needed_power_permission_modify"] = permission.has_value ? permission.value : 0; + + permission = permissions->permission_value_flagged(permission::i_playlist_needed_delete_power); + notify[index]["needed_power_delete"] = permission.has_value ? permission.value : 0; + + permission = permissions->permission_value_flagged(permission::i_playlist_song_needed_add_power); + notify[index]["needed_power_song_add"] = permission.has_value ? permission.value : 0; + + permission = permissions->permission_value_flagged(permission::i_playlist_song_needed_move_power); + notify[index]["needed_power_song_move"] = permission.has_value ? permission.value : 0; + + permission = permissions->permission_value_flagged(permission::i_playlist_song_needed_remove_power); + notify[index]["needed_power_song_remove"] = permission.has_value ? permission.value : 0; index++; } @@ -363,19 +371,19 @@ command_result ConnectedClient::handleCommandPlaylistCreate(ts::Command &cmd) { auto power = this->calculate_permission(permission::i_playlist_song_remove_power, 0); if(power.has_value && power.value >= 0) - playlist->permissions()->setPermission(permission::i_playlist_song_needed_remove_power, power.value, nullptr); + playlist->permission_manager()->set_permission(permission::i_playlist_song_needed_remove_power, {power.value, 0}, permission::v2::set_value, permission::v2::do_nothing); power = this->calculate_permission(permission::i_playlist_delete_power, 0); if(power.has_value && power.value >= 0) - playlist->permissions()->setPermission(permission::i_playlist_needed_delete_power, power.value, nullptr); + playlist->permission_manager()->set_permission(permission::i_playlist_needed_delete_power, {power.value, 0}, permission::v2::set_value, permission::v2::do_nothing); power = this->calculate_permission(permission::i_playlist_modify_power, 0); if(power.has_value && power.value >= 0) - playlist->permissions()->setPermission(permission::i_playlist_needed_modify_power, power.value, nullptr); + playlist->permission_manager()->set_permission(permission::i_playlist_needed_modify_power, {power.value, 0}, permission::v2::set_value, permission::v2::do_nothing); power = this->calculate_permission(permission::i_playlist_permission_modify_power, 0); if(power.has_value && power.value >= 0) - playlist->permissions()->setPermission(permission::i_playlist_needed_permission_modify_power, power.value, nullptr); + playlist->permission_manager()->set_permission(permission::i_playlist_needed_permission_modify_power, {power.value, 0}, permission::v2::set_value, permission::v2::do_nothing); Command notify(this->notify_response_command("notifyplaylistcreated")); notify["playlist_id"] = playlist->playlist_id(); @@ -392,8 +400,8 @@ command_result ConnectedClient::handleCommandPlaylistDelete(ts::Command &cmd) { auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_delete_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_delete_power)); + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_delete_power, permission::i_playlist_delete_power); perr) + return command_result{perr}; string error; if(!ref_server->musicManager->delete_playlist(playlist->playlist_id(), error)) { @@ -412,8 +420,8 @@ command_result ConnectedClient::handleCommandPlaylistInfo(ts::Command &cmd) { auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_view_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power)); + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power, music::PlaylistPermissions::do_no_require_granted); perr) + return command_result{perr}; Command notify(this->notify_response_command("notifyplaylistinfo")); @@ -433,8 +441,8 @@ command_result ConnectedClient::handleCommandPlaylistEdit(ts::Command &cmd) { auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_modify_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_modify_power)); + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_modify_power, permission::i_playlist_modify_power); perr) + return command_result{perr}; deque, string>> properties; @@ -491,28 +499,41 @@ command_result ConnectedClient::handleCommandPlaylistPermList(ts::Command &cmd) auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_playlist_permission_list, 1); + { + auto value = playlist->calculate_client_specific_permissions(permission::b_virtualserver_playlist_permission_list, this->ref()); + if(!permission::v2::permission_granted(1, value)) + return command_result{permission::b_virtualserver_playlist_permission_list}; + } - auto permissions = playlist->permissions()->listPermissions(PERM_FLAG_PUBLIC); + auto permissions = playlist->permission_manager()->permissions(); if(permissions.empty()) - return command_result{error::vs_critical}; + return command_result{error::database_empty_result}; Command result(this->notify_response_command("notifyplaylistpermlist")); + + auto permission_mapper = serverInstance->getPermissionMapper(); + auto perm_sid = cmd.hasParm("permsid") || cmd.hasParm("names"); int index = 0; result["playlist_id"] = playlist->playlist_id(); - for (const auto &elm : permissions) { - if(elm->hasValue()) { - result[index]["permid"] = elm->type->type; + for (const auto &[perm, value] : permissions) { + if(value.flags.value_set) { + if (perm_sid) + result[index]["permsid"] = permission_mapper->permission_name(this->getType(), perm); + else + result[index]["permid"] = perm; - result[index]["permvalue"] = elm->value; - result[index]["permnegated"] = elm->flag_negate; - result[index]["permskip"] = elm->flag_skip; + result[index]["permvalue"] = value.values.value; + result[index]["permnegated"] = value.flags.negate; + result[index]["permskip"] = value.flags.skip; index++; } - if(elm->hasGrant()) { - result[index]["permid"] = (uint16_t) (elm->type->type | PERM_ID_GRANT); - result[index]["permvalue"] = elm->granted; + if(value.flags.grant_set) { + if (perm_sid) + result[index]["permsid"] = permission_mapper->permission_name_grant(this->getType(), perm); + else + result[index]["permid"] = perm | PERM_ID_GRANT; + + result[index]["permvalue"] = value.values.grant; result[index]["permnegated"] = 0; result[index]["permskip"] = 0; index++; @@ -531,8 +552,8 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) { auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_permission_modify_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_permission_modify_power)); + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) + return command_result{perr}; auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, true); if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; @@ -556,10 +577,9 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) { } if (grant) { - playlist->permissions()->setPermissionGranted(permType, cmd[index]["permvalue"], nullptr); + playlist->permission_manager()->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); } else { - - playlist->permissions()->setPermission(permType, cmd[index]["permvalue"], nullptr, cmd[index]["permskip"], cmd[index]["permnegated"]); + playlist->permission_manager()->set_permission(permType, {cmd[index]["permvalue"],0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"].as(), cmd[index]["permnegated"].as()); } } @@ -574,8 +594,8 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) { auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_permission_modify_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_permission_modify_power)); + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) + return command_result{perr}; auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); bool conOnError = cmd[0].has("continueonerror"); @@ -588,10 +608,154 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) { return command_result{permission::i_permission_modify_power}; } + if (grant) { - playlist->permissions()->setPermissionGranted(permType, permNotGranted, nullptr); + playlist->permission_manager()->set_permission(permType, {0, 0}, permission::v2::do_nothing, permission::v2::delete_value); } else { - playlist->permissions()->deletePermission(permType, nullptr); + playlist->permission_manager()->set_permission(permType, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing); + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistClientPermList(ts::Command &cmd) { + CMD_REF_SERVER(ref_server); + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); + if(!playlist) return command_result{error::playlist_invalid_id}; + + { + auto value = playlist->calculate_client_specific_permissions(permission::b_virtualserver_playlist_permission_list, this->ref()); + if(!permission::v2::permission_granted(1, value)) + return command_result{permission::b_virtualserver_playlist_permission_list}; + } + + auto permissions = playlist->permission_manager()->channel_permissions(); + if(permissions.empty()) + return command_result{error::database_empty_result}; + + Command result(this->notify_response_command("notifyplaylistclientpermlist")); + + auto client_id = cmd.hasParm("cldbid") ? cmd[0]["cldbid"].as() : 0; + auto permission_mapper = serverInstance->getPermissionMapper(); + auto perm_sid = cmd.hasParm("permsid") || cmd.hasParm("names"); + + int index = 0; + ClientDbId last_client_id{0}; + result["playlist_id"] = playlist->playlist_id(); + result["cldbid"] = client_id; + for (const auto &[perm, client, value] : permissions) { + if(client_id > 0 && client != client_id) continue; + + if(last_client_id != client) + result[index]["cldbid"] = client; + if(value.flags.value_set) { + if (perm_sid) + result[index]["permsid"] = permission_mapper->permission_name(this->getType(), perm); + else + result[index]["permid"] = perm; + + result[index]["permvalue"] = value.values.value; + result[index]["permnegated"] = value.flags.negate; + result[index]["permskip"] = value.flags.skip; + index++; + } + if(value.flags.grant_set) { + if (perm_sid) + result[index]["permsid"] = permission_mapper->permission_name_grant(this->getType(), perm); + else + result[index]["permid"] = perm | PERM_ID_GRANT; + + result[index]["permvalue"] = value.values.grant; + result[index]["permnegated"] = 0; + result[index]["permskip"] = 0; + index++; + } + } + if(index == 0) + return command_result{error::database_empty_result}; + this->sendCommand(result); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistClientAddPerm(ts::Command &cmd) { + CMD_REF_SERVER(ref_server); + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); + if(!playlist) return command_result{error::playlist_invalid_id}; + + auto client_id = cmd[0]["cldbid"].as(); + if(client_id == 0) return command_result{error::client_invalid_id}; + + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) + return command_result{perr}; + + auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, true); + if(!max_value.has_value) return command_result{permission::i_permission_modify_power}; + + auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); + + bool conOnError = cmd[0].has("continueonerror"); + for (int index = 0; index < cmd.bulkCount(); index++) { + PARSE_PERMISSION(cmd); + + auto val = cmd[index]["permvalue"].as(); + if(permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) { + if(conOnError) continue; + return command_result{permission::i_permission_modify_power}; + } + + if(!ignore_granted_values && !permission::v2::permission_granted(val, this->calculate_permission(permType, 0, true))) { + if(conOnError) continue; + return command_result{permission::i_permission_modify_power}; + } + + if (grant) { + playlist->permission_manager()->set_channel_permission(permType, client_id, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); + } else { + playlist->permission_manager()->set_channel_permission(permType, client_id, {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"].as(), cmd[index]["permnegated"].as()); + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &cmd) { + CMD_REF_SERVER(ref_server); + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); + if(!playlist) return command_result{error::playlist_invalid_id}; + + auto client_id = cmd[0]["cldbid"].as(); + if(client_id == 0) return command_result{error::client_invalid_id}; + + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr) + return command_result{perr}; + + auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); + bool conOnError = cmd[0].has("continueonerror"); + + for (int index = 0; index < cmd.bulkCount(); index++) { + PARSE_PERMISSION(cmd); + + if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) { + if(conOnError) continue; + return command_result{permission::i_permission_modify_power}; + } + + + if (grant) { + playlist->permission_manager()->set_channel_permission(permType, client_id, {0, 0}, permission::v2::do_nothing, permission::v2::delete_value); + } else { + playlist->permission_manager()->set_channel_permission(permType, client_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing); } } @@ -606,8 +770,8 @@ command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd) auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_view_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power)); + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power); perr) + return command_result{perr}; auto songs = playlist->list_songs(); if(songs.empty()) @@ -640,8 +804,8 @@ command_result ConnectedClient::handleCommandPlaylistSongAdd(ts::Command &cmd) { auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_song_add_power, playlist->permissions()->getPermissionValue(permission::i_playlist_song_needed_add_power)); + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_song_needed_add_power, permission::i_playlist_song_add_power); perr) + return command_result{perr}; if(!cmd[0].has("invoker")) cmd["invoker"] = ""; @@ -672,8 +836,8 @@ command_result ConnectedClient::handleCommandPlaylistSongReorder(ts::Command &cm auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_song_move_power, playlist->permissions()->getPermissionValue(permission::i_playlist_song_needed_move_power)); + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_song_needed_move_power, permission::i_playlist_song_move_power); perr) + return command_result{perr}; SongId song_id = cmd["song_id"]; SongId previous_id = cmd["song_previous_song_id"]; @@ -695,8 +859,8 @@ command_result ConnectedClient::handleCommandPlaylistSongRemove(ts::Command &cmd auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return command_result{error::playlist_invalid_id}; - if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_song_remove_power, playlist->permissions()->getPermissionValue(permission::i_playlist_song_needed_remove_power)); + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_song_needed_remove_power, permission::i_playlist_song_remove_power); perr) + return command_result{perr}; SongId song_id = cmd["song_id"]; @@ -709,85 +873,63 @@ command_result ConnectedClient::handleCommandPlaylistSongRemove(ts::Command &cmd return command_result{error::ok}; } -command_result ConnectedClient::handleCommandMusicBotQueueList(Command& cmd) { - return command_result{error::not_implemented}; //FIXME - /* +/****** legacy ******/ +command_result ConnectedClient::handleCommandMusicBotQueueList(Command& cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return command_result{error::music_invalid_id}; - PERM_CHECK_CHANNELR(permission::i_client_music_info, bot->permissionValue(permission::i_client_music_needed_info, bot->currentChannel), this->currentChannel, true); + auto playlist = dynamic_pointer_cast(bot->playlist()); + if(!playlist) return command_result{error::vs_critical}; + + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power); perr) + return command_result{perr}; bool bulked = cmd.hasParm("bulk") || cmd.hasParm("balk") || cmd.hasParm("pipe") || cmd.hasParm("bar") || cmd.hasParm("paypal"); int command_index = 0; - Command notify(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : ""); - { - auto history = bot->queue()->history(); - for(int index = history.size(); index > 0; index--) { - if(!bulked) - notify = Command(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : ""); + Command notify(this->notify_response_command("notifymusicqueueentry")); + auto songs = playlist->list_songs(); + int begin_index{0}; + for(auto it = songs.begin(); it != songs.end(); it++) + if((*it)->id == playlist->currently_playing()) + break; + else + begin_index--; - apply_song(notify, history[index - 1], command_index); - notify[command_index]["queue_index"] = -index; - - if(!bulked) - this->sendCommand(notify); - else - command_index++; - } - } - - { + for(auto it = songs.begin(); it != songs.end(); it++) { if(!bulked) - notify = Command(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : ""); + notify = Command(this->notify_response_command("notifymusicqueueentry")); - auto song = bot->queue()->currentSong(); - apply_song(notify, song, command_index); - notify[command_index]["queue_index"] = 0; + //TODO! + //apply_song(notify, *it, command_index); + notify[command_index]["queue_index"] = begin_index++; if(!bulked) this->sendCommand(notify); - else if(song) + else command_index++; } - - { - auto queue = bot->queue()->queueEntries(); - for(int index = 0; index < queue.size(); index++) { - if(!bulked) - notify = Command(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : ""); - - apply_song(notify, queue[index], command_index); - notify[command_index]["queue_index"] = index + 1; - - if(!bulked) - this->sendCommand(notify); - else - command_index++; - } - } - debugMessage(this->getServerId(),"Send: {}",notify.build()); if(bulked) { if(command_index > 0) { this->sendCommand(notify); - } else return { ErrorType::DBEmpty }; + } else + return command_result{error::database_empty_result}; } - if(this->getExternalType() == CLIENT_VOICE) { + if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) { Command notify("notifymusicqueuefinish"); notify["bot_id"] = bot->getClientDatabaseId(); this->sendCommand(notify); } return command_result{error::ok}; - */ } command_result ConnectedClient::handleCommandMusicBotQueueAdd(Command& cmd) { @@ -898,8 +1040,8 @@ command_result ConnectedClient::handleCommandMusicBotPlaylistAssign(ts::Command if(ref_server->musicManager->find_bot_by_playlist(playlist)) return command_result{error::playlist_already_in_use}; - if(playlist && playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) - ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_view_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power)); + if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_view_power, permission::i_playlist_view_power); perr) + return command_result{perr}; if(!ref_server->musicManager->assign_playlist(bot, playlist)) return command_result{error::vs_critical}; diff --git a/server/src/client/music/MusicClient.cpp b/server/src/client/music/MusicClient.cpp index 9d704e3..854b9d3 100644 --- a/server/src/client/music/MusicClient.cpp +++ b/server/src/client/music/MusicClient.cpp @@ -96,7 +96,7 @@ MusicClient::~MusicClient() { void MusicClient::sendCommand(const ts::Command &command, bool low) { } void MusicClient::sendCommand(const ts::command_builder &command, bool low) { } -bool MusicClient::closeConnection(const std::chrono::system_clock::time_point&) { +bool MusicClient::close_connection(const std::chrono::system_clock::time_point&) { logError(this->getServerId(), "Music manager is forced to disconnect!"); /* @@ -148,7 +148,7 @@ void MusicClient::initialize_bot() { auto channel = this->server->getChannelTree()->findChannel(last); if(!channel) channel = this->server->getChannelTree()->getDefaultChannel(); - if(this->getClientId() == 0 || this->server->findClient(this->getClientId()) != _this.lock()) { + if(this->getClientId() == 0 || this->server->find_client_by_id(this->getClientId()) != _this.lock()) { this->server->registerClient(_this.lock()); } { diff --git a/server/src/client/music/MusicClient.h b/server/src/client/music/MusicClient.h index d7dca06..c91e270 100644 --- a/server/src/client/music/MusicClient.h +++ b/server/src/client/music/MusicClient.h @@ -57,7 +57,7 @@ namespace ts { void sendCommand(const ts::command_builder &command, bool low) override; bool disconnect(const std::string &reason) override; - bool closeConnection(const std::chrono::system_clock::time_point& = std::chrono::system_clock::time_point()) override; + bool close_connection(const std::chrono::system_clock::time_point& = std::chrono::system_clock::time_point()) override; void initialize_bot(); //Music stuff diff --git a/server/src/client/query/QueryClient.cpp b/server/src/client/query/QueryClient.cpp index 086765f..c5a7808 100644 --- a/server/src/client/query/QueryClient.cpp +++ b/server/src/client/query/QueryClient.cpp @@ -135,10 +135,10 @@ bool QueryClient::disconnect(const std::string &reason) { cmd["reason"] = reason; this->sendCommand(cmd); } - return this->closeConnection(system_clock::now() + seconds(1)); + return this->close_connection(system_clock::now() + seconds(1)); } -bool QueryClient::closeConnection(const std::chrono::system_clock::time_point& flushTimeout) { +bool QueryClient::close_connection(const std::chrono::system_clock::time_point& flushTimeout) { auto ownLock = dynamic_pointer_cast(_this.lock()); if(!ownLock) return false; @@ -285,7 +285,7 @@ void QueryClient::handleMessageWrite(int fd, short, void *) { } else { logError(LOG_QUERY, "{} Failed to write message: {} ({} => {})", CLIENT_STR_LOG_PREFIX, length, errno, strerror(errno)); - threads::Thread([=](){ ownLock->closeConnection(); }).detach(); + threads::Thread([=](){ ownLock->close_connection(chrono::system_clock::now() + chrono::seconds{5}); }).detach(); return; } } else { @@ -321,12 +321,12 @@ void QueryClient::handleMessageRead(int fd, short, void *) { logMessage(LOG_QUERY, "{} Connection closed. Client disconnected.", CLIENT_STR_LOG_PREFIX); event_del_noblock(this->readEvent); std::thread([ownLock]{ - ownLock->closeConnection(); + ownLock->close_connection(); }).detach(); } else { logError(LOG_QUERY, "{} Failed to read! Code: {} errno: {} message: {}", CLIENT_STR_LOG_PREFIX, length, errno, strerror(errno)); event_del_noblock(this->readEvent); - threads::Thread(THREAD_SAVE_OPERATIONS, [ownLock](){ ownLock->closeConnection(); }).detach(); + threads::Thread(THREAD_SAVE_OPERATIONS, [ownLock](){ ownLock->close_connection(); }).detach(); } return; } @@ -542,7 +542,7 @@ void QueryClient::queryTick() { lock_guard lock_tick(this->lock_query_tick); if(this->idleTimestamp.time_since_epoch().count() > 0 && system_clock::now() - this->idleTimestamp > minutes(5)){ debugMessage(LOG_QUERY, "Dropping client " + this->getLoggingPeerIp() + "|" + this->getDisplayName() + ". (Timeout)"); - this->closeConnection(system_clock::now() + seconds(1)); + this->close_connection(system_clock::now() + seconds(1)); } if(this->connectionType == ConnectionType::UNKNOWN && system_clock::now() - milliseconds(500) > connectedTimestamp) { diff --git a/server/src/client/query/QueryClient.h b/server/src/client/query/QueryClient.h index a12da05..f4f7c85 100644 --- a/server/src/client/query/QueryClient.h +++ b/server/src/client/query/QueryClient.h @@ -29,7 +29,7 @@ namespace ts { void sendCommand(const ts::command_builder &command, bool low) override; bool disconnect(const std::string &reason) override; - bool closeConnection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override; + bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override; void disconnectFinal(); bool eventActive(QueryEventGroup, QueryEventSpecifier); diff --git a/server/src/client/query/QueryClientCommands.cpp b/server/src/client/query/QueryClientCommands.cpp index 00c19b7..e8063aa 100644 --- a/server/src/client/query/QueryClientCommands.cpp +++ b/server/src/client/query/QueryClientCommands.cpp @@ -121,7 +121,7 @@ command_result QueryClient::handleCommand(Command& cmd) { command_result QueryClient::handleCommandExit(Command &) { logMessage(LOG_QUERY, "[Query] {}:{} disconnected. (Requested by client)", this->getLoggingPeerIp(), this->getPeerPort()); - this->closeConnection(system_clock::now() + seconds(1)); + this->close_connection(system_clock::now() + seconds(1)); return command_result{error::ok}; } @@ -154,7 +154,7 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { if(this->handle->loginAttempts[this->getPeerIp()] > 3) { this->handle->queryBann[this->getPeerIp()] = system_clock::now() + seconds(serverInstance->properties()[property::SERVERINSTANCE_SERVERQUERY_BAN_TIME].as()); //TODO configurable | Disconnect all others? this->postCommandHandler.emplace_back([&](){ - this->closeConnection(system_clock::now() + seconds(1)); + this->close_connection(system_clock::now() + seconds(1)); }); return command_result{error::ban_flooding}; } diff --git a/server/src/client/voice/PrecomputedPuzzles.cpp b/server/src/client/voice/PrecomputedPuzzles.cpp index 620fbaf..17d8bdf 100644 --- a/server/src/client/voice/PrecomputedPuzzles.cpp +++ b/server/src/client/voice/PrecomputedPuzzles.cpp @@ -53,6 +53,19 @@ inline bool solvePuzzle(Puzzle *puzzle){ return true; } +inline bool write_bin_data(mp_int& data, uint8_t* result, size_t length) { + ulong n{length}; + if(auto err = mp_to_unsigned_bin_n(&data, result, &n); err) + return false; + + if(n != length) { + auto off = length - n; + memcpy(result + off, result, n); + memset(result, 0, off); + } + return true; +} + void PuzzleManager::generatePuzzle() { auto puzzle = new Puzzle{}; @@ -74,12 +87,14 @@ void PuzzleManager::generatePuzzle() { if(!valid_n || !valid_x || !valid_result) goto generate_new; - memset(puzzle->data_x, 0, 64); - memset(puzzle->data_n, 0, 64); - memset(puzzle->data_result, 0, 64); - mp_to_unsigned_bin(&puzzle->x, puzzle->data_x); - mp_to_unsigned_bin(&puzzle->n, puzzle->data_n); - mp_to_unsigned_bin(&puzzle->result, puzzle->data_result); + if(!write_bin_data(puzzle->x, puzzle->data_x, 64)) + goto generate_new; + + if(!write_bin_data(puzzle->n, puzzle->data_n, 64)) + goto generate_new; + + if(!write_bin_data(puzzle->result, puzzle->data_result, 64)) + goto generate_new; this->cached.push_back(shared_ptr(puzzle, [](Puzzle* elm){ mp_clear_multi(&elm->n, &elm->x, &elm->result, nullptr); diff --git a/server/src/client/voice/VoiceClient.cpp b/server/src/client/voice/VoiceClient.cpp index 21d30e4..a4b89e0 100644 --- a/server/src/client/voice/VoiceClient.cpp +++ b/server/src/client/voice/VoiceClient.cpp @@ -116,7 +116,7 @@ void VoiceClient::tick(const std::chrono::system_clock::time_point &time) { this->state == ConnectionState::INIT_HIGH ? "INIT_HIGH" : "INIT_LOW", duration_cast(time - this->last_packet_handshake).count() ); - this->closeConnection(system_clock::now() + seconds(1)); + this->close_connection(system_clock::now() + seconds(1)); } } } @@ -128,115 +128,130 @@ bool VoiceClient::disconnect(const std::string &reason) { } bool VoiceClient::disconnect(ts::ViewReasonId reason_id, const std::string &reason, const std::shared_ptr& invoker, bool notify_viewer) { + /* + * We don't have to lock the disconnect lock here, because we're not really unregistering the client. + * Its only for the clients own flavour and everything which the client receives after will be ignored :) + */ + + ConnectionState old_state{}; { - threads::MutexLock disconnect_lock(this->disconnectLock); - if(this->state == ConnectionState::DISCONNECTING || this->state == ConnectionState::DISCONNECTED) return false; //Already disconnecting/disconnected + std::lock_guard state_lock{this->state_lock}; + if(this->state == ConnectionState::DISCONNECTING || this->state == ConnectionState::DISCONNECTED) + return false; //Already disconnecting/disconnected + + old_state = this->state; this->state = ConnectionState::DISCONNECTING; } - Command cmd("notifyclientleftview"); - cmd["reasonmsg"] = reason; - cmd["reasonid"] = reason_id; - cmd["clid"] = this->getClientId(); - cmd["cfid"] = this->currentChannel ? this->currentChannel->channelId() : 0; //Failed when cid = 0???? - cmd["ctid"] = 0; + if(old_state == ConnectionState::CONNECTED) { + /* Client has been successflly initialized; Send normal disconnect. */ - if (invoker) { - cmd["invokerid"] = invoker->getClientId(); - cmd["invokername"] = invoker->getDisplayName(); - cmd["invokeruid"] = invoker->getUid(); - } + Command cmd("notifyclientleftview"); + cmd["reasonmsg"] = reason; + cmd["reasonid"] = reason_id; + cmd["clid"] = this->getClientId(); + cmd["cfid"] = this->currentChannel ? this->currentChannel->channelId() : 0; //Failed when cid = 0???? + cmd["ctid"] = 0; - if(notify_viewer && this->server) { - unique_lock channel_lock(this->server->channel_tree_lock); - this->server->client_move(this->ref(), nullptr, invoker, reason, reason_id, false, channel_lock); + if (invoker) { + cmd["invokerid"] = invoker->getClientId(); + cmd["invokername"] = invoker->getDisplayName(); + cmd["invokeruid"] = invoker->getUid(); + } + + if(notify_viewer && this->server) { + unique_lock channel_lock(this->server->channel_tree_lock); + this->server->client_move(this->ref(), nullptr, invoker, reason, reason_id, false, channel_lock); + } else { + threads::MutexLock lock(this->command_lock); + auto server_channel = dynamic_pointer_cast(this->currentChannel); + if(server_channel) + server_channel->unregister_client(_this.lock()); + this->currentChannel = nullptr; + } + + auto listener = make_unique>(); + auto weak_self = this->_this; + listener->waitAndGetLater([weak_self](bool* success) { + if(weak_self.expired()) return; + auto self = weak_self.lock(); + if(!self) return; + + if(!success || !*success) { + debugMessage(self->getServerId(), "{} Failed to receive disconnect acknowledge!", CLIENT_STR_LOG_PREFIX_(self)); + } else + debugMessage(self->getServerId(), "{} Received disconnect acknowledge!", CLIENT_STR_LOG_PREFIX_(self)); + + self->close_connection(chrono::system_clock::time_point{}); /* we received the ack, we do not need to flush anything */ + }, system_clock::now() + seconds(5)); + this->sendCommand0(cmd.build(), false, false, std::move(listener)); } else { - threads::MutexLock lock(this->command_lock); - auto server_channel = dynamic_pointer_cast(this->currentChannel); - if(server_channel) - server_channel->unregister_client(_this.lock()); - this->currentChannel = nullptr; + //TODO: Extra case for INIT_HIGH? + this->close_connection(chrono::system_clock::now() + chrono::seconds{5}); } - - auto listener = make_unique>(); - auto weak_self = this->_this; - listener->waitAndGetLater([weak_self](bool* success) { - if(weak_self.expired()) return; - auto self = weak_self.lock(); - if(!self || self->state != DISCONNECTING) return; - - if(!success || !*success) { - debugMessage(self->getServerId(), "{} Failed to receive disconnect acknowledge!", CLIENT_STR_LOG_PREFIX_(self)); - } else - debugMessage(self->getServerId(), "{} Received disconnect acknowledge!", CLIENT_STR_LOG_PREFIX_(self)); - - self->closeConnection(); - }, system_clock::now() + seconds(5)); - this->sendCommand0(cmd.build(), false, false, std::move(listener)); return true; } -bool VoiceClient::closeConnection(const system_clock::time_point &timeout) { +bool VoiceClient::close_connection(const system_clock::time_point &timeout) { auto self_lock = dynamic_pointer_cast(_this.lock()); assert(self_lock); //Should never happen! - threads::MutexLock disconnect_lock(this->disconnectLock); bool flush = timeout.time_since_epoch().count() > 0; - if((this->state == ConnectionState::DISCONNECTING && flush && this->flushing_thread) || this->state == ConnectionState::DISCONNECTED){ - debugMessage(this->getServerId(), "{} Tried to disconnect, but isn't connected anymore! State: {}", CLIENT_STR_LOG_PREFIX, this->state); - return false; + { + std::lock_guard state_lock{this->state_lock}; + + if(this->state == ConnectionState::DISCONNECTED) return false; + else if(this->state == ConnectionState::DISCONNECTING) { + /* here is nothing to pay attention for */ + } else if(this->state == ConnectionState::DISCONNECTING_FLUSHING) { + if(!flush) { + this->state = ConnectionState::DISCONNECTED; + return true; /* the flush thread will execute the final disconnect */ + } else { + //TODO: May update the flush timeout if its less then the other one? + return true; + } + } + this->state = flush ? ConnectionState::DISCONNECTING_FLUSHING : ConnectionState::DISCONNECTED; } - this->state = flush ? ConnectionState::DISCONNECTING : ConnectionState::DISCONNECTED; debugMessage(this->getServerId(), "{} Closing voice client connection. (Flush: {})", CLIENT_STR_LOG_PREFIX, flush); - if(flush) { - this->flushing_thread = std::make_shared(THREAD_SAVE_OPERATIONS | THREAD_EXECUTE_LATER, [this, self_lock, timeout](){ - //We could use this here cause its locked by self_locked + //TODO: Move this out into a thread pool? + this->flushing_thread = std::make_shared(THREAD_SAVE_OPERATIONS | THREAD_EXECUTE_LATER, [this, self_lock, timeout, flush]{ + { + /* Await that all commands have been processed. It does not make sense to unregister the client while command handling. */ + std::lock_guard cmd_lock{this->command_lock}; + } + + if(flush) { debugMessage(this->getServerId(), "{} Awaiting write prepare, write and acknowledge queue flushed", CLIENT_STR_LOG_PREFIX); - auto connection = this->getConnection(); - - this->getConnection()->wait_empty_write_and_prepare_queue(timeout); - debugMessage(this->getServerId(), "{} Write prepare queue progressed", CLIENT_STR_LOG_PREFIX); - while(this->state == DISCONNECTING){ + while(this->state == DISCONNECTING_FLUSHING) { if(system_clock::now() > timeout){ - debugMessage(this->getServerId(), "{} Cant flush io!", CLIENT_STR_LOG_PREFIX); + auto write_queue_flushed = this->connection->wait_empty_write_and_prepare_queue(timeout); + auto acknowledge_received = connection->acknowledge_handler.awaiting_acknowledge() == 0; - if(!this->connection->wait_empty_write_and_prepare_queue(timeout)) { - debugMessage(this->getServerId(), "{} Write queue not empty!", CLIENT_STR_LOG_PREFIX); - } - { - auto reminding = connection->acknowledge_handler.awaiting_acknowledge(); - if(reminding > 0) - debugMessage(this->getServerId(), "{} Could not get acknowledge for all commands before disconnecting. Acknowledges left: {}", CLIENT_STR_LOG_PREFIX, reminding); - } + if(write_queue_flushed && acknowledge_received) + break; + + debugMessage(this->getServerId(), "{} Failed to flush pending messages. Acknowledges pending: {} Buffers pending: {}", CLIENT_STR_LOG_PREFIX, acknowledge_received, write_queue_flushed); break; } if(!this->connection->wait_empty_write_and_prepare_queue(timeout)) continue; - { - if(connection->acknowledge_handler.awaiting_acknowledge() > 0) { - usleep(5000); - continue; - } + if(connection->acknowledge_handler.awaiting_acknowledge() > 0) { + usleep(5000); + continue; } debugMessage(this->getServerId(), "{} Write and acknowledge queue are flushed", CLIENT_STR_LOG_PREFIX); break; } - if(this->state != DISCONNECTING) return; - this->finalDisconnect(); - }); - flushing_thread->name("Flush thread VC").execute(); - return true; - } else { - this->state = DISCONNECTED; - auto f_thread = this->flushing_thread; - if(f_thread) { - threads::NegatedMutexLock l(this->disconnectLock); //Unlock the close lock again until the flush queue has finished (may with close may by interupt) - f_thread->join(); } - this->finalDisconnect(); - } + + if(this->state > DISCONNECTING) /* it could happen that the client "reconnects" while flushing this shit */ + this->finalDisconnect(); + }); + flushing_thread->name("Flush thread VC").execute(); return true; } @@ -244,7 +259,6 @@ void VoiceClient::finalDisconnect() { auto ownLock = dynamic_pointer_cast(_this.lock()); assert(ownLock); - threads::MutexLock disconnect_lock(this->disconnectLock); lock_guard disconnect_lock_final(this->finalDisconnectLock); if(this->final_disconnected) { logError(this->getServerId(), "Tried to final disconnect {}/{} twice", this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()), this->getDisplayName()); diff --git a/server/src/client/voice/VoiceClient.h b/server/src/client/voice/VoiceClient.h index 60d2f79..c7309ef 100644 --- a/server/src/client/voice/VoiceClient.h +++ b/server/src/client/voice/VoiceClient.h @@ -50,7 +50,7 @@ namespace ts { VoiceClient(const std::shared_ptr& server,const sockaddr_storage*); ~VoiceClient(); - bool closeConnection(const std::chrono::system_clock::time_point &timeout = std::chrono::system_clock::time_point()) override; + bool close_connection(const std::chrono::system_clock::time_point &timeout) override; bool disconnect(const std::string&) override; bool disconnect(ViewReasonId /* reason type */, const std::string& /* reason */, const std::shared_ptr& /* invoker */, bool /* notify viewer */); diff --git a/server/src/client/voice/VoiceClientCommandHandler.cpp b/server/src/client/voice/VoiceClientCommandHandler.cpp index 78671e6..a27499e 100644 --- a/server/src/client/voice/VoiceClientCommandHandler.cpp +++ b/server/src/client/voice/VoiceClientCommandHandler.cpp @@ -46,8 +46,8 @@ inline bool calculate_security_level(int& result, ecc_key* pubKey, size_t offset if(shaBuffer[i] == 0) zeroBits += 8; else break; if(i < SHA_DIGEST_LENGTH) { - for(int bit = 0; bit < 8; bit++) { - if((shaBuffer[i] & (1 << bit)) == 0) zeroBits++; + for(uint8_t bit = 0; bit < 8; bit++) { + if((shaBuffer[i] & (1U << bit)) == 0) zeroBits++; else break; } } @@ -78,7 +78,14 @@ command_result VoiceClient::handleCommandClientInit(Command &cmd) { command_result VoiceClient::handleCommandClientDisconnect(Command& cmd) { auto reason = cmd["reasonmsg"].size() > 0 ? cmd["reasonmsg"].as() : ""; - this->disconnect(VREASON_SERVER_LEFT, reason, nullptr, true); + this->notifyClientLeftView(this->ref(), nullptr, VREASON_SERVER_LEFT, reason, nullptr, false); //Before we're moving us out of the channel tree! + if(this->state == CONNECTED) { + unique_lock channel_lock(this->server->channel_tree_lock); + this->server->client_move(this->ref(), nullptr, nullptr, reason, VREASON_SERVER_LEFT, true, channel_lock); + } logMessage(this->getServerId(), "{} Got remote disconnect with the reason '{}'", CLIENT_STR_LOG_PREFIX, reason); + this->postCommandHandler.push_back([&]{ + this->close_connection(std::chrono::system_clock::now() + std::chrono::seconds{1}); /* send acknowledge and close connection */ + }); return command_result{error::ok}; } \ No newline at end of file diff --git a/server/src/client/voice/VoiceClientConnection.cpp b/server/src/client/voice/VoiceClientConnection.cpp index 9f0d205..4cc0685 100644 --- a/server/src/client/voice/VoiceClientConnection.cpp +++ b/server/src/client/voice/VoiceClientConnection.cpp @@ -222,7 +222,7 @@ bool VoiceClientConnection::verify_encryption(const pipes::buffer_view &buffer / } void VoiceClientConnection::execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */) { - if(this->client->state == ConnectionState::DISCONNECTED || !this->client->getServer()) + if(this->client->state >= ConnectionState::DISCONNECTING || !this->client->getServer()) return; //TODO: Remove the buffer_execute_lock and use the one within the this->client->handlePacketCommand method @@ -420,7 +420,7 @@ bool VoiceClientConnection::prepare_packet_for_write(vector &resu string error = "success"; - if(packet->type().compressable() && !packet->memory_state.fragment_entry && false) { + if(packet->type().compressable() && !packet->memory_state.fragment_entry) { packet->enable_flag(PacketFlag::Compressed); if(!this->compress_handler.progressPacketOut(packet.get(), error)) { logError(this->getClient()->getServerId(), "{} Could not compress outgoing packet.\nThis could cause fatal failed for the client.\nError: {}", error); @@ -627,8 +627,8 @@ bool VoiceClientConnection::wait_empty_write_and_prepare_queue(chrono::time_poin if(until.time_since_epoch().count() != 0 && system_clock::now() > until) return false; - }; - threads::self::sleep_for(milliseconds(5)); + threads::self::sleep_for(milliseconds(5)); + } return true; } diff --git a/server/src/client/voice/VoiceClientHandschake.cpp b/server/src/client/voice/VoiceClientHandschake.cpp index 725d391..a5d0316 100644 --- a/server/src/client/voice/VoiceClientHandschake.cpp +++ b/server/src/client/voice/VoiceClientHandschake.cpp @@ -25,6 +25,7 @@ inline void generate_random(uint8_t *destination, size_t length) { ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) { this->last_packet_handshake = system_clock::now(); + std::unique_lock state_lock{this->state_lock}; if(this->state == ConnectionState::CONNECTED) { /* we've a reconnect */ if(system_clock::now() - this->lastPingResponse < seconds(5)) { logMessage(this->getServerId(), "{} Client initialized session reconnect, but last ping response is not older then 5 seconds ({}). Ignoring attempt", @@ -43,20 +44,32 @@ ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) { CLIENT_STR_LOG_PREFIX, duration_cast(system_clock::now() - this->lastPingResponse).count() ); + + state_lock.unlock(); + { unique_lock server_channel_lock(this->server->channel_tree_lock); /* we cant get moved if this is locked! */ - if(this->currentChannel) { + if(this->currentChannel) this->server->client_move(this->ref(), nullptr, nullptr, config::messages::timeout::connection_reinitialized, ViewReasonId::VREASON_TIMEOUT, false, server_channel_lock); - } } this->finalDisconnect(); - } else if(this->state == ConnectionState::DISCONNECTING) { - this->closeConnection(); /* executing final disconnect right now! */ + state_lock.lock(); + } else if(this->state >= ConnectionState::DISCONNECTING) { + state_lock.unlock(); + std::shared_lock disconnect_finish{this->finalDisconnectLock}; /* await until the last disconnect has been processed */ + state_lock.lock(); + this->state = ConnectionState::INIT_HIGH; + } else if(this->state == ConnectionState::INIT_HIGH) { + logTrace(this->getServerId(), "{} Received a duplicated initiv. It seems like our initivexpand2 hasn't yet reached the client. The acknowledge handle should handle this issue for us.", CLIENT_STR_LOG_PREFIX); + return command_result{error::ok}; + } else { + this->state = ConnectionState::INIT_HIGH; } + state_lock.unlock(); + this->connection->reset(); this->connection->register_initiv_packet(); - this->state = ConnectionState::INIT_HIGH; this->crypto.protocol_encrypted = false; bool use_teaspeak = command.hasParm("teaspeak"); diff --git a/server/src/client/web/WSWebClient.cpp b/server/src/client/web/WSWebClient.cpp index d7ba2c1..3cbf0a6 100644 --- a/server/src/client/web/WSWebClient.cpp +++ b/server/src/client/web/WSWebClient.cpp @@ -39,7 +39,7 @@ void WebClient::handleMessageWrite(int fd, short, void *) { debugMessage(this->getServerId(), "[{}] Failed to write message (length {}, errno {}, message {}) Disconnecting client.", CLIENT_STR_LOG_PREFIX, written, errno, strerror(errno)); } - this->closeConnection(system_clock::now()); /* close connection in a new thread */ + this->close_connection(system_clock::now()); /* close connection in a new thread */ return; } @@ -76,7 +76,7 @@ void WebClient::handleMessageRead(int fd, short, void *) { if(this->readEvent) event_del_noblock(this->readEvent); } - self_lock->closeConnection(system_clock::now()); /* direct close, but from another thread */ + self_lock->close_connection(system_clock::now()); /* direct close, but from another thread */ } return; } diff --git a/server/src/client/web/WebClient.cpp b/server/src/client/web/WebClient.cpp index 4aac09a..2cb2229 100644 --- a/server/src/client/web/WebClient.cpp +++ b/server/src/client/web/WebClient.cpp @@ -170,7 +170,7 @@ void WebClient::sendCommand(const ts::command_builder &command, bool low) { #endif } -bool WebClient::closeConnection(const std::chrono::system_clock::time_point& timeout) { +bool WebClient::close_connection(const std::chrono::system_clock::time_point& timeout) { bool flushing = timeout.time_since_epoch().count() > 0; auto self_lock = dynamic_pointer_cast(_this.lock()); @@ -250,7 +250,7 @@ command_result WebClient::handleCommand(Command &command) { if(command.command() == "clientinit") { auto result = this->handleCommandClientInit(command); if(result.error_code()) - this->closeConnection(system_clock::now() + seconds(1)); + this->close_connection(system_clock::now() + seconds(1)); return result; } } @@ -317,7 +317,7 @@ void WebClient::onWSDisconnected(const string& error) { } debugMessage(this->getServerId(), "{} WS disconnected ({}). Application data: {}", CLIENT_STR_LOG_PREFIX, close_code, message); - this->closeConnection(); //TODO? + this->close_connection(); //TODO? } void WebClient::onWSMessage(const pipes::WSMessage &message) { @@ -634,7 +634,7 @@ bool WebClient::disconnect(const std::string &reason) { this->server->client_move(this->ref(), nullptr, this->server->serverRoot, reason, ViewReasonId::VREASON_SERVER_KICK, false, server_channel_lock); this->server->unregisterClient(_this.lock(), "disconnected", server_channel_lock); } - return this->closeConnection(system_clock::now() + seconds(1)); + return this->close_connection(system_clock::now() + seconds(1)); } command_result WebClient::handleCommandClientInit(Command &command) { diff --git a/server/src/client/web/WebClient.h b/server/src/client/web/WebClient.h index 2caea1f..cd69d58 100644 --- a/server/src/client/web/WebClient.h +++ b/server/src/client/web/WebClient.h @@ -29,7 +29,7 @@ namespace ts { void sendCommand(const ts::command_builder &command, bool low) override; bool disconnect(const std::string &reason) override; - bool closeConnection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override; + bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override; bool shouldReceiveVoice(const std::shared_ptr &sender) override; diff --git a/server/src/music/MusicBotManager.cpp b/server/src/music/MusicBotManager.cpp index 9f0d906..48acb47 100644 --- a/server/src/music/MusicBotManager.cpp +++ b/server/src/music/MusicBotManager.cpp @@ -385,4 +385,17 @@ bool MusicBotManager::delete_playlist(ts::PlaylistId id, std::string &error) { return false; } return true; +} + +void MusicBotManager::execute_tick() { + auto vs = this->handle.lock(); + if(!vs) return; + + unique_lock playlist_lock(this->playlists_lock); + auto playlists = this->playlists_list; + playlist_lock.unlock(); + + auto db_helper = serverInstance->databaseHelper(); + for(auto& playlist : playlists) + db_helper->savePlaylistPermissions(vs, playlist->playlist_id(), playlist->permission_manager()); } \ No newline at end of file diff --git a/server/src/music/MusicBotManager.h b/server/src/music/MusicBotManager.h index 003e69e..87be6ca 100644 --- a/server/src/music/MusicBotManager.h +++ b/server/src/music/MusicBotManager.h @@ -57,6 +57,8 @@ namespace ts { std::shared_ptr create_playlist(ClientDbId /* owner */, const std::string& /* owner name */); bool delete_playlist(PlaylistId /* id */, std::string& /* error */); + void execute_tick(); + inline std::shared_ptr ref_server() { return this->handle.lock(); } inline std::shared_ptr ref() { return this->_self.lock(); } private: diff --git a/server/src/music/MusicPlaylist.cpp b/server/src/music/MusicPlaylist.cpp index 24a0001..1a069f6 100644 --- a/server/src/music/MusicPlaylist.cpp +++ b/server/src/music/MusicPlaylist.cpp @@ -12,10 +12,11 @@ using namespace ts::music; using namespace std; using namespace std::chrono; -Playlist::Playlist(const std::shared_ptr &manager, const std::shared_ptr &properties, const std::shared_ptr& permissions) : _properties(properties), _permissions(permissions), manager(manager) { } +Playlist::Playlist(const std::shared_ptr &manager, const std::shared_ptr &properties, std::shared_ptr permissions) : + PlaylistPermissions{std::move(permissions)}, _properties(properties), manager(manager) { } Playlist::~Playlist() { - this->destory_tree(); + this->destroy_tree(); } void Playlist::set_self_ref(const std::shared_ptr &ref) { @@ -336,7 +337,7 @@ bool Playlist::build_tree(deque> entries) { return true; } -void Playlist::destory_tree() { +void Playlist::destroy_tree() { unique_lock list_lock(this->playlist_lock); auto element = this->playlist_head; while(element) { diff --git a/server/src/music/MusicPlaylist.h b/server/src/music/MusicPlaylist.h index 4083147..b9b259a 100644 --- a/server/src/music/MusicPlaylist.h +++ b/server/src/music/MusicPlaylist.h @@ -6,6 +6,7 @@ #include #include #include "MusicBotManager.h" +#include "PlaylistPermissions.h" namespace ts { namespace server { @@ -61,7 +62,7 @@ namespace ts { }; //TODO add some kind of play history? - class Playlist { + class Playlist : public PlaylistPermissions { friend class MusicBotManager; public: struct Type { @@ -71,7 +72,7 @@ namespace ts { }; }; - Playlist(const std::shared_ptr& /* manager */, const std::shared_ptr& /* properties */, const std::shared_ptr& /* permissions */); + Playlist(const std::shared_ptr& /* manager */, const std::shared_ptr& /* properties */, std::shared_ptr /* permissions */); virtual ~Playlist(); void load_songs(); @@ -84,13 +85,7 @@ namespace ts { virtual bool delete_song(SongId /* song */); virtual bool reorder_song(SongId /* song */, SongId /* new id */); - inline Properties& properties() { - return *this->_properties; - } - - inline std::shared_ptr permissions() { - return this->_permissions; - } + inline Properties& properties() const { return *this->_properties; } inline PlaylistId playlist_id() { return this->properties()[property::PLAYLIST_ID].as(); @@ -110,10 +105,9 @@ namespace ts { inline std::shared_ptr ref() { return std::dynamic_pointer_cast(this->_self.lock()); } protected: virtual void set_self_ref(const std::shared_ptr& /* playlist */); + bool is_playlist_owner(ClientDbId database_id) const override { return this->properties()[property::PLAYLIST_OWNER_DBID].as_save() == database_id; } std::atomic current_id; - /* channel id's are here client database ids! */ - std::shared_ptr _permissions; std::shared_ptr _properties; std::weak_ptr manager; std::weak_ptr _self; @@ -140,7 +134,7 @@ namespace ts { std::deque> load_entries(); bool build_tree(std::deque> /* entries */); - void destory_tree(); + void destroy_tree(); bool sql_remove(const std::shared_ptr& /* entry */); bool sql_add(const std::shared_ptr& /* entry */); /* also assigns an ID */ diff --git a/server/src/music/PlayablePlaylist.cpp b/server/src/music/PlayablePlaylist.cpp index 776a222..e65cf8d 100644 --- a/server/src/music/PlayablePlaylist.cpp +++ b/server/src/music/PlayablePlaylist.cpp @@ -10,7 +10,7 @@ using namespace ts::music; using namespace std; using namespace std::chrono; -PlayablePlaylist::PlayablePlaylist(const std::shared_ptr &handle, const std::shared_ptr &props, const std::shared_ptr& perms) : Playlist(handle, props, perms) { } +PlayablePlaylist::PlayablePlaylist(const std::shared_ptr &handle, const std::shared_ptr &props, const std::shared_ptr& perms) : Playlist(handle, props, perms) { } PlayablePlaylist::~PlayablePlaylist() {} diff --git a/server/src/music/PlayablePlaylist.h b/server/src/music/PlayablePlaylist.h index 0ac3d76..10ae540 100644 --- a/server/src/music/PlayablePlaylist.h +++ b/server/src/music/PlayablePlaylist.h @@ -24,7 +24,7 @@ namespace ts { SHUFFLE }; }; - PlayablePlaylist(const std::shared_ptr& /* manager */, const std::shared_ptr& /* properties */, const std::shared_ptr& /* permissions */); + PlayablePlaylist(const std::shared_ptr& /* manager */, const std::shared_ptr& /* properties */, const std::shared_ptr& /* permissions */); virtual ~PlayablePlaylist(); inline ReplayMode::value replay_mode() { return this->properties()[property::PLAYLIST_REPLAY_MODE].as(); } diff --git a/server/src/music/PlaylistPermissions.cpp b/server/src/music/PlaylistPermissions.cpp new file mode 100644 index 0000000..cb815fa --- /dev/null +++ b/server/src/music/PlaylistPermissions.cpp @@ -0,0 +1,29 @@ +// +// Created by wolverindev on 30.01.20. +// + +#include "./PlaylistPermissions.h" +#include "../client/ConnectedClient.h" + +using namespace ts; +using namespace ts::music; + + +PlaylistPermissions::PlaylistPermissions(std::shared_ptr permissions) : _permissions{std::move(permissions)} {} + +permission::v2::PermissionFlaggedValue PlaylistPermissions::calculate_client_specific_permissions(ts::permission::PermissionType permission, const std::shared_ptr& client) { + auto val = this->_permissions->channel_permission(permission, client->getClientDatabaseId()); + if(val.flags.value_set) + return {val.values.value, true}; + return client->calculate_permission(permission, 0); +} + +permission::PermissionType PlaylistPermissions::client_has_permissions( + const std::shared_ptr &client, ts::permission::PermissionType needed_permission, ts::permission::PermissionType granted_permission, uint8_t flags) { + if(this->is_playlist_owner(client->getClientDatabaseId())) return permission::ok; + + return permission::v2::permission_granted( + this->permission_manager()->permission_value_flagged(needed_permission), + this->calculate_client_specific_permissions(granted_permission, client), + flags & do_no_require_granted) ? permission::ok : needed_permission; +} \ No newline at end of file diff --git a/server/src/music/PlaylistPermissions.h b/server/src/music/PlaylistPermissions.h new file mode 100644 index 0000000..b73a7fc --- /dev/null +++ b/server/src/music/PlaylistPermissions.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace ts::server { + class VirtualServer; + class ConnectedClient; +} + +namespace ts::music { + class PlaylistPermissions { + public: + enum permission_flags { + ignore_playlist_owner, + do_no_require_granted + }; + PlaylistPermissions(std::shared_ptr permissions); + + inline const std::shared_ptr& permission_manager() const { return this->_permissions; } + + /* returns permission::ok if client has permissions */ + permission::PermissionType client_has_permissions(const std::shared_ptr& client, permission::PermissionType needed_permission, permission::PermissionType granted_permission, uint8_t /* ignore playlist owner */ = 0); + permission::v2::PermissionFlaggedValue calculate_client_specific_permissions(permission::PermissionType /* permission */, const std::shared_ptr& /* client */); + protected: + const std::shared_ptr _permissions; + + virtual bool is_playlist_owner(ClientDbId /* database id */) const = 0; + private: + }; +} \ No newline at end of file diff --git a/server/src/server/POWHandler.cpp b/server/src/server/POWHandler.cpp index 1e645c0..d1063eb 100644 --- a/server/src/server/POWHandler.cpp +++ b/server/src/server/POWHandler.cpp @@ -258,7 +258,6 @@ void POWHandler::handle_puzzle_solve(const std::shared_ptrrsa_challenge->data_result, &buffer[4 + 1 + 2 * 64 + 04 + 100], 64) != 0) { #ifdef POW_ERROR debugMessage(this->get_server_id(), "[POW][{}][Puzzle] Received an invalid puzzle solution! Resetting client", net::to_string(client->address)); @@ -302,7 +301,7 @@ shared_ptr POWHandler::register_verified_client(const std::shared_p voice_client->initialize(); voice_client->socket = client->socket; - voice_client->state = ConnectionState::INIT_HIGH; + voice_client->state = ConnectionState::INIT_LOW; memcpy(&voice_client->address_info, &client->address_info, sizeof(client->address_info)); { diff --git a/server/src/server/VoiceServer.cpp b/server/src/server/VoiceServer.cpp index 68a2aec..88916bf 100644 --- a/server/src/server/VoiceServer.cpp +++ b/server/src/server/VoiceServer.cpp @@ -95,7 +95,6 @@ bool VoiceServer::start(const std::deque>& b if(bindings.empty()) { error = "Failed to bind any address!"; this->running = false; - this->bindings.empty(); return false; } @@ -162,13 +161,14 @@ void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &no if(client->state == ConnectionState::CONNECTED) { client->disconnect(ViewReasonId::VREASON_TIMEOUT, config::messages::timeout::packet_resend_failed, nullptr, true); } else { - client->closeConnection(system_clock::now() + seconds(1)); + client->close_connection(system_clock::now() + seconds(1)); } } else if(!buffers.empty()) { { lock_guard client_write_lock(connection->write_queue_lock); connection->write_queue.insert(connection->write_queue.end(), buffers.begin(), buffers.end()); } + //logTrace(client->getServerId(), "{} Resending {} packets.", CLIENT_STR_LOG_PREFIX_(client), buffers.size()); buffers.clear(); connection->triggerWrite(); } @@ -183,7 +183,7 @@ bool VoiceServer::stop(const std::chrono::milliseconds& flushTimeout) { auto list = this->activeConnections; this->connectionLock.unlock(); for(const auto &e : list) - e->closeConnection(system_clock::now() + seconds(1)); + e->close_connection(system_clock::now() + seconds(1)); auto beg = system_clock::now(); while(!this->activeConnections.empty() && flushTimeout.count() != 0 && system_clock::now() - beg < flushTimeout) @@ -332,7 +332,7 @@ void VoiceServer::handleMessageRead(int fd, short events, void *_event_handle) { } else { auto client_id = (ClientId) be2le16(&raw_read_buffer[10]); if(client_id > 0) { - client = dynamic_pointer_cast(voice_server->server->findClient(client_id)); + client = dynamic_pointer_cast(voice_server->server->find_client_by_id(client_id)); } else { client = voice_server->findClient(&remote_address, true); } diff --git a/server/src/server/WebServer.cpp b/server/src/server/WebServer.cpp index 39c6449..6b8684b 100644 --- a/server/src/server/WebServer.cpp +++ b/server/src/server/WebServer.cpp @@ -189,7 +189,7 @@ void WebControlServer::stop() { this->clientLock.unlock(); for(const auto& e : clList) { - e->closeConnection(system_clock::now()); + e->close_connection(system_clock::now()); } diff --git a/server/src/terminal/CommandHandler.cpp b/server/src/terminal/CommandHandler.cpp index 6506a5c..170b938 100644 --- a/server/src/terminal/CommandHandler.cpp +++ b/server/src/terminal/CommandHandler.cpp @@ -230,11 +230,12 @@ namespace terminal { break; case ChatMessageMode::TEXTMODE_PRIVATE: { - auto client = server->findClient(cmd.arguments[2].as()); + ConnectedLockedClient client{server->find_client_by_id(cmd.arguments[2].as())}; if(!client){ logError("Cloud not find manager from clid"); return false; } + client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, server->getServerRoot(), client->getClientId(), 0, system_clock::now(), message); } break; diff --git a/shared b/shared index a8843f5..0466698 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit a8843f53113f814e7218f76683190072851b17df +Subproject commit 04666982d937a1f14fe7fff22aa735afe7563525