diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 627ee98..1c1f46f 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -3,7 +3,7 @@ project(TeaSpeak-Server) set(CMAKE_VERBOSE_MAKEFILE ON) #--allow-multiple-definition -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive -Wall -Wno-reorder -Wno-sign-compare -static-libgcc -static-libstdc++ -g -Wl,--no-whole-archive -pthread ${MEMORY_DEBUG_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive -Wall -Wno-reorder -Wno-sign-compare -static-libgcc -static-libstdc++ -g -Wl,--no-whole-archive -pthread ${MEMORY_DEBUG_FLAGS} -Werror=return-type") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3") @@ -55,7 +55,12 @@ set(SERVER_SOURCE_FILES src/server/VoiceServer.cpp src/server/POWHandler.cpp src/client/voice/VoiceClientConnection.cpp - src/client/ConnectedClientCommandHandler.cpp + #src/client/ConnectedClientCommandHandler.cpp + src/client/command_handler/channel.cpp + src/client/command_handler/client.cpp + src/client/command_handler/server.cpp + src/client/command_handler/misc.cpp + src/client/ConnectedClientNotifyHandler.cpp src/ServerManager.cpp src/server/file/FileServer.cpp @@ -133,7 +138,7 @@ set(SERVER_SOURCE_FILES src/manager/ConversationManager.cpp src/client/SpeakingClientHandshake.cpp - ) + src/client/command_handler/music.cpp src/client/command_handler/file.cpp) if (COMPILE_WEB_CLIENT) add_definitions(-DCOMPILE_WEB_CLIENT) @@ -146,7 +151,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) endif () add_executable(PermHelper helpers/permgen.cpp) diff --git a/server/src/InstanceHandler.cpp b/server/src/InstanceHandler.cpp index 1d71bac..66d0028 100644 --- a/server/src/InstanceHandler.cpp +++ b/server/src/InstanceHandler.cpp @@ -881,4 +881,37 @@ void InstanceHandler::loadWebCertificate() { this->sslMgr->rename_context(strobf("web_default_new").string(), strobf("web_default").string()); this->web_cert_revision = revision; +} + + +permission::v2::PermissionFlaggedValue InstanceHandler::calculate_permission(permission::PermissionType permission, + ClientDbId cldbid, ClientType type, ChannelId channel, bool granted, std::shared_ptr cache) { + auto result = this->calculate_permissions({permission}, cldbid, type, channel, granted, cache); + if(result.empty()) return {0, false}; + return result.front().second; +} + +std::vector > InstanceHandler::calculate_permissions( + const std::deque &permissions, ClientDbId cldbid, ClientType type, ChannelId channel, bool granted, std::shared_ptr cache) { + std::vector> result{}; + + //TODO: Negate? + //TODO: May move this part to the instance? + + auto server_groups = this->getGroupManager()->getServerGroups(cldbid, type); + for(const auto& permission : permissions) { + permission::v2::PermissionFlaggedValue value{0, false}; + + for(const auto &gr : this->getGroupManager()->getServerGroups(cldbid, type)){ + auto group_permissions = gr->group->permissions(); + auto flagged_permissions = granted ? group_permissions->permission_granted_flagged(permission) : group_permissions->permission_value_flagged(permission); + if(flagged_permissions.has_value) + if(!value.has_value || flagged_permissions.value > value.value || flagged_permissions.value == -1) + value = flagged_permissions; + } + + result.emplace_back(permission, value); + } + + return result; } \ No newline at end of file diff --git a/server/src/InstanceHandler.h b/server/src/InstanceHandler.h index cc90543..d2e1f1c 100644 --- a/server/src/InstanceHandler.h +++ b/server/src/InstanceHandler.h @@ -72,6 +72,24 @@ namespace ts { std::shared_ptr getPermissionMapper() { return this->permission_mapper; } std::shared_ptr getConversationIo() { return this->conversation_io; } + + permission::v2::PermissionFlaggedValue calculate_permission( + permission::PermissionType, + ClientDbId, + ClientType type, + ChannelId channel, + bool granted = false, + std::shared_ptr cache = nullptr + ); + + std::vector> calculate_permissions( + const std::deque&, + ClientDbId, + ClientType type, + ChannelId channel, + bool granted = false, + std::shared_ptr cache = nullptr + ); private: std::mutex activeLock; std::condition_variable activeCon; diff --git a/server/src/ServerManagerSnapshotDeploy.cpp b/server/src/ServerManagerSnapshotDeploy.cpp index e14059a..f21fc99 100644 --- a/server/src/ServerManagerSnapshotDeploy.cpp +++ b/server/src/ServerManagerSnapshotDeploy.cpp @@ -8,6 +8,8 @@ #include "InstanceHandler.h" #include "InstanceHandler.h" +//TODO: When using the new command builder make sure you're using a std::deque as the underlying bulk type! + using namespace std; using namespace ts; using namespace ts::server; diff --git a/server/src/TS3ServerClientManager.cpp b/server/src/TS3ServerClientManager.cpp index 4e3d390..c9dcabc 100644 --- a/server/src/TS3ServerClientManager.cpp +++ b/server/src/TS3ServerClientManager.cpp @@ -182,10 +182,10 @@ bool TSServer::assignDefaultChannel(const shared_ptr& client, b else channel = this->channelTree->findChannelByPath(str); if (channel) { - if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission_value(permission::i_channel_join_power, channel->channelId()), false)) { + if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) { logMessage(this->serverId, "{} Client tried to connect to a channel which he hasn't permission for. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name()); channel = nullptr; - } else if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true) && client->permissionValue(permission::PERMTEST_ORDERED, permission::b_channel_join_ignore_password, channel) < 1) { + } else if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true) && !permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) { logMessage(this->serverId, "{} Client tried to connect to a channel which is password protected and he hasn't the right password. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name()); channel = nullptr; } @@ -531,10 +531,10 @@ void TSServer::client_move( auto i_source_channel = s_source_channel->channelId(); if(std::find(deleted.begin(), deleted.end(), i_source_channel) == deleted.end()) { - auto source_channel_sub_power = target->calculate_permission_value(permission::i_channel_subscribe_power, i_source_channel); + auto source_channel_sub_power = target->calculate_permission(permission::i_channel_subscribe_power, i_source_channel); if(!s_source_channel->permission_granted(permission::i_channel_needed_subscribe_power, source_channel_sub_power, false)) { - auto source_channel_sub_power_ignore = target->calculate_permission_value(permission::b_channel_ignore_subscribe_power, i_source_channel); - if(!DataClient::permission_granted(source_channel_sub_power_ignore, 1, true)) { + auto source_channel_sub_power_ignore = target->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel); + if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore, true)) { logTrace(this->serverId, "Force unsubscribing of client {} for channel {}/{}. (Channel switch and no permissions)", CLIENT_STR_LOG_PREFIX_(target), s_source_channel->name(), i_source_channel diff --git a/server/src/TSServer.cpp b/server/src/TSServer.cpp index 72e44c0..3ea4132 100644 --- a/server/src/TSServer.cpp +++ b/server/src/TSServer.cpp @@ -783,9 +783,9 @@ ts_always_inline bool channel_ignore_permission(ts::permission::PermissionType t return permission::i_icon_id == type; } -vector> TSServer::calculatePermissions2( - ClientDbId client_dbid, +vector> TSServer::calculate_permissions( const std::deque& permissions, + ClientDbId client_dbid, ClientType client_type, ChannelId channel_id, bool calculate_granted, @@ -1000,40 +1000,19 @@ vector> TSServer::calculatePermissions( - permission::PermissionTestType test_type, - ClientDbId client_dbid, - const std::deque& permissions, - ClientType client_type, - const std::shared_ptr &channel, +permission::v2::PermissionFlaggedValue TSServer::calculate_permission( + permission::PermissionType permission, + ClientDbId cldbid, + ClientType type, + ChannelId channel, + bool granted, std::shared_ptr cache) { - if(permissions.empty()) return {}; + auto result = this->calculate_permissions({permission}, cldbid, type, channel, granted, cache); + if(result.empty()) return {0, false}; - auto data = calculatePermissions2(client_dbid, permissions, client_type, channel ? channel->channelId() : 0, false, cache); - deque> result; - for(auto& entry : data) - result.emplace_back(entry.first, entry.second.has_value ? entry.second.value : permNotGranted); - return result; -} - -permission::v2::PermissionFlaggedValue TSServer::calculatePermission2(ts::permission::PermissionType permission, ts::ClientDbId cldbid, ts::server::ClientType type, ts::ChannelId channel, std::shared_ptr cache) { - auto result = this->calculatePermissions2(cldbid, {permission}, type, channel, false, cache); - if(result.empty()) return {permNotGranted, false}; return result.front().second; } -ts::permission::PermissionValue TSServer::calculatePermission(permission::PermissionTestType test, ClientDbId cldbid, permission::PermissionType permission, ClientType client_type, const std::shared_ptr& channel, std::shared_ptr cache) { - auto result = this->calculatePermissions(test, cldbid, {permission}, client_type, channel, cache); - if(result.empty()) return permNotGranted; - return result.front().second; -} - -ts::permission::PermissionValue TSServer::calculatePermissionGrant(permission::PermissionTestType test, ClientDbId cldbid, permission::PermissionType permission, ClientType client_type, const std::shared_ptr& channel) { - auto result = this->calculatePermissions2(cldbid, {permission}, client_type, channel ? channel->channelId() : 0, true, nullptr); - assert(!result.empty()); - return result[0].second.has_value ? result[0].second.value : permNotGranted; -} - bool TSServer::verifyServerPassword(std::string password, bool hashed) { if(!this->properties()[property::VIRTUALSERVER_FLAG_PASSWORD].as()) return true; if(password.empty()) return false; diff --git a/server/src/TSServer.h b/server/src/TSServer.h index 530987e..043aa1c 100644 --- a/server/src/TSServer.h +++ b/server/src/TSServer.h @@ -208,27 +208,24 @@ namespace ts { std::shared_ptr getVoiceServer(){ return this->udpVoiceServer; } WebControlServer* getWebServer(){ return this->webControlServer; } - std::deque> calculatePermissions( - permission::PermissionTestType, + /* calculate permissions for an client in this server */ + permission::v2::PermissionFlaggedValue calculate_permission( + permission::PermissionType, ClientDbId, - const std::deque&, ClientType type, - const std::shared_ptr& channel, - std::shared_ptr cache = nullptr); + ChannelId channel, + bool granted = false, + std::shared_ptr cache = nullptr + ); - std::vector> calculatePermissions2( - ClientDbId /* client db id */, - const std::deque& /* permissions to calculate */, - ClientType type /* client type for default permissions */, - ChannelId /* target channel id */, - bool /* calculate granted */, - std::shared_ptr cache = nullptr /* calculate cache */); - - permission::PermissionValue calculatePermission(permission::PermissionTestType, ClientDbId, permission::PermissionType, ClientType type, const std::shared_ptr& channel, std::shared_ptr cache = nullptr); - - permission::v2::PermissionFlaggedValue calculatePermission2(permission::PermissionType, ClientDbId, ClientType type, ChannelId channel, std::shared_ptr cache = nullptr); - - permission::PermissionValue calculatePermissionGrant(permission::PermissionTestType, ClientDbId, permission::PermissionType, ClientType type, const std::shared_ptr& channel); + std::vector> calculate_permissions( + const std::deque&, + ClientDbId, + ClientType type, + ChannelId channel, + bool granted = false, + std::shared_ptr cache = nullptr + ); bool verifyServerPassword(std::string, bool hashed = false); diff --git a/server/src/channel/ClientChannelView.cpp b/server/src/channel/ClientChannelView.cpp index 06d1bbe..16f4067 100644 --- a/server/src/channel/ClientChannelView.cpp +++ b/server/src/channel/ClientChannelView.cpp @@ -127,7 +127,7 @@ std::deque> ClientChannelView::insert_channels(shared std::deque> result; if(!cache && test_permissions) cache = make_shared(); - bool has_perm = !test_permissions || owner->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_view_power, 1, nullptr, true, cache); + bool has_perm = !test_permissions || permission::v2::permission_granted(1, owner->calculate_permission(permission::b_channel_ignore_view_power, 0, false, cache)); bool first = true; while(head) { if(!first && first_only) break; @@ -145,7 +145,7 @@ std::deque> ClientChannelView::insert_channels(shared } if(!has_perm) { - if(!channel->permission_granted(permission::i_channel_needed_view_power, this->owner->calculate_permission_value(permission::i_channel_view_power, channel->channelId()), false)) { + if(!channel->permission_granted(permission::i_channel_needed_view_power, this->owner->calculate_permission(permission::i_channel_view_power, channel->channelId()), false)) { head = head->next; debugMessage(this->getServerId(), "{}[CHANNEL] Dropping channel {} ({}) (No permissions)", CLIENT_STR_LOG_PREFIX_(this->owner), channel->channelId(), channel->name()); continue; @@ -245,7 +245,7 @@ std::deque> ClientChannelView::test_channel(std::shar if(!cache) cache = make_shared(); std::deque> result; - bool has_perm = owner->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_view_power, 1, nullptr, true, cache); + bool has_perm = permission::v2::permission_granted(1, owner->calculate_permission(permission::b_channel_ignore_view_power, 0, false, cache)); if(has_perm) return {}; deque> parents = {l_old}; @@ -267,7 +267,7 @@ std::deque> ClientChannelView::test_channel(std::shar auto channel = dynamic_pointer_cast(l_entry->entry); sassert(entry->channelId() == channel->channelId()); - if(!channel->permission_granted(permission::i_channel_needed_view_power, this->owner->calculate_permission_value(permission::i_channel_view_power, channel->channelId()), false)) { + if(!channel->permission_granted(permission::i_channel_needed_view_power, this->owner->calculate_permission(permission::i_channel_view_power, channel->channelId()), false)) { for(const auto& te : this->delete_entry(entry)) result.push_back(dynamic_pointer_cast(te)); @@ -288,7 +288,7 @@ std::deque>> ClientChannelView::updat std::deque>> ClientChannelView::update_channel_path(std::shared_ptr l_channel, std::shared_ptr l_own, shared_ptr cache, ssize_t length) { if(!cache) cache = make_shared(); std::deque>> result; - bool has_perm = owner->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_view_power, 1, nullptr, true, cache); + bool has_perm = permission::v2::permission_granted(1, owner->calculate_permission(permission::b_channel_ignore_view_power, 0, false, cache)); while(l_channel && length-- != 0) { auto b_channel = dynamic_pointer_cast(l_channel->entry); @@ -303,7 +303,7 @@ std::deque>> ClientChannelView::updat visible = true; if(!has_perm) { - if(!b_channel->permission_granted(permission::i_channel_needed_view_power, this->owner->calculate_permission_value(permission::i_channel_view_power, b_channel->channelId()), false)) { + if(!b_channel->permission_granted(permission::i_channel_needed_view_power, this->owner->calculate_permission(permission::i_channel_view_power, b_channel->channelId()), false)) { visible = false; } } @@ -341,9 +341,9 @@ std::dequepermissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_view_power, 1, nullptr); + bool has_perm = permission::v2::permission_granted(1, owner->calculate_permission(permission::b_channel_ignore_view_power, 0, false)); if(!has_perm) { - has_perm = dynamic_pointer_cast(channel->entry)->permission_granted(permission::i_channel_needed_view_power, this->owner->calculate_permission_value(permission::i_channel_view_power, dynamic_pointer_cast(channel->entry)->channelId()), false); + has_perm = dynamic_pointer_cast(channel->entry)->permission_granted(permission::i_channel_needed_view_power, this->owner->calculate_permission(permission::i_channel_view_power, dynamic_pointer_cast(channel->entry)->channelId()), false); } if(!has_perm) return {}; //Channel wasn't visible and he still has no permission for that :) diff --git a/server/src/channel/ClientChannelView.h b/server/src/channel/ClientChannelView.h index 7bf5b0a..258f539 100644 --- a/server/src/channel/ClientChannelView.h +++ b/server/src/channel/ClientChannelView.h @@ -23,7 +23,7 @@ namespace ts { bool subscribed = false; bool editable = false; - bool joinable = false; /* used within notify text message */ + permission::PermissionType join_permission_error = permission::unknown; /* used within notify text message */ ChannelId previous_channel = 0; uint16_t join_state_id = 0; /* the calculation id for the flag joinable. If this does not match with the join_state_id within the client the flag needs to be recalculated */ std::weak_ptr handle; diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 46c0c1b..28df428 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -85,20 +85,20 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool /* this->server may be null! */ shared_ptr server_ref = this->server; - auto permissions = this->permissionValues(permission::PERMTEST_ORDERED, { + auto permissions = this->calculate_permissions({ permission::i_client_talk_power, permission::b_client_is_priority_speaker, permission::b_client_ignore_antiflood, permission::i_channel_view_power, permission::b_channel_ignore_view_power - }, this->currentChannel); + }, this->currentChannel ? this->currentChannel->channelId() : 0); - permission::PermissionValue - permission_talk_power = permNotGranted, - permission_priority_speaker = permNotGranted, - permission_ignore_antiflood = permNotGranted, - permission_channel_view_power = permNotGranted, - permission_channel_ignore_view_power = permNotGranted; + permission::v2::PermissionFlaggedValue + permission_talk_power{0, false}, + permission_priority_speaker{0, false}, + permission_ignore_antiflood{0, false}, + permission_channel_view_power{0, false}, + permission_channel_ignore_view_power{0, false}; for(const auto& perm : permissions) { if(perm.first == permission::i_client_talk_power) permission_talk_power = perm.second; @@ -113,18 +113,15 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool else sassert(false); } - if(permission_talk_power < -2) permission_talk_power = -2; - else if(permission_talk_power == -2) permission_talk_power = 0; - deque notifyList; debugMessage(this->getServerId(), "{} Got a channel talk power of {} Talk power set is {}", CLIENT_STR_LOG_PREFIX, permission_talk_power, this->properties()[property::CLIENT_TALK_POWER].as()); - if(permission_talk_power != this->properties()[property::CLIENT_TALK_POWER].as()) { //We do not have to update tp if there's no channel - this->properties()[property::CLIENT_TALK_POWER] = permission_talk_power; + if((permission_talk_power.has_value ? permission_talk_power.value : 0) != this->properties()[property::CLIENT_TALK_POWER].as()) { //We do not have to update tp if there's no channel + this->properties()[property::CLIENT_TALK_POWER] = (permission_talk_power.has_value ? permission_talk_power.value : 0); notifyList.emplace_back(property::CLIENT_TALK_POWER); auto update = this->properties()[property::CLIENT_IS_TALKER].as() || this->properties()[property::CLIENT_TALK_REQUEST].as() > 0; if(update && this->currentChannel) { - if(this->currentChannel->talk_power_granted({permission_talk_power, permission_talk_power != permNotGranted})) { + if(this->currentChannel->talk_power_granted(permission_talk_power)) { this->properties()[property::CLIENT_IS_TALKER] = 0; this->properties()[property::CLIENT_TALK_REQUEST] = 0; this->properties()[property::CLIENT_TALK_REQUEST_MSG] = ""; @@ -164,16 +161,16 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool } } - auto pSpeaker = permission_priority_speaker > 0; + auto pSpeaker = permission_priority_speaker.has_value && permission_priority_speaker.value > 0; if(properties()[property::CLIENT_IS_PRIORITY_SPEAKER].as() != pSpeaker){ properties()[property::CLIENT_IS_PRIORITY_SPEAKER] = pSpeaker; notifyList.emplace_back(property::CLIENT_IS_PRIORITY_SPEAKER); } - block_flood = permission_ignore_antiflood <= 0 || permission_ignore_antiflood == permNotGranted; + block_flood = !!permission_ignore_antiflood.has_value || permission_ignore_antiflood.value <= 0; if(server_ref) server_ref->notifyClientPropertyUpdates(_this.lock(), notifyList, notify_self); - this->updateTalkRights(permission_talk_power); + this->updateTalkRights(permission_talk_power.has_value ? permission_talk_power.value : 0); if((this->channels_view_power != permission_channel_view_power || this->channels_ignore_view != permission_channel_ignore_view_power) && notify_self && this->currentChannel && server_ref) { this->channels_view_power = permission_channel_view_power; @@ -229,7 +226,7 @@ std::deque> ConnectedClient::subscribeChannel(cons if(!ref_server) return {}; - auto general_granted = enforce || this->permission_granted(this->permissionValue(permission::b_channel_ignore_subscribe_power, nullptr), 1, true); + auto general_granted = enforce || permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_subscribe_power, 0)); { shared_lock server_channel_lock(ref_server->channel_tree_lock, defer_lock); unique_lock client_channel_lock(this->channel_lock, defer_lock); @@ -245,10 +242,10 @@ std::deque> ConnectedClient::subscribeChannel(cons if(local_channel->subscribed) continue; //Already subscribed if(!general_granted && channel != this->currentChannel) { - auto granted_permission = this->calculate_permission_value(permission::i_channel_subscribe_power, channel->channelId()); + auto granted_permission = this->calculate_permission(permission::i_channel_subscribe_power, channel->channelId()); if(!channel->permission_granted(permission::i_channel_needed_subscribe_power, granted_permission, false)) { - auto ignore_power = this->calculate_permission_value(permission::b_channel_ignore_subscribe_power, channel->channelId()); + auto ignore_power = this->calculate_permission(permission::b_channel_ignore_subscribe_power, channel->channelId()); if(!ignore_power.has_value || ignore_power.value < 1) continue; } @@ -564,9 +561,9 @@ bool ConnectedClient::notifyClientNeededPermissions() { cache_lock.unlock(); for(const auto& value : permissions) { - if(value.second != permNotGranted || value.first == permission::b_client_force_push_to_talk) { + if(value.second.has_value) { cmd[index]["permid"] = value.first; - cmd[index++]["permvalue"] = value.second == permNotGranted ? 0 : value.second; + cmd[index++]["permvalue"] = value.second.value; } } if(permissions.empty()) { @@ -591,6 +588,8 @@ bool ConnectedClient::notifyError(const command_result& result, const std::strin } else { cmd["id"] = (int) result.error_code(); cmd["msg"] = findError(result.error_code()).message; + if(result.is_permission_error()) + cmd["failed_permid"] = result.permission_id(); } if(retCode.length() > 0) @@ -616,7 +615,7 @@ inline void send_channels(ConnectedClient* client, ChannelIT begin, const Channe if(begin == end) return; - Command channellist("channellist"); + ts::command_builder builder{"channellist", 512, 6}; size_t index = 0; while(begin != end) { @@ -625,9 +624,9 @@ inline void send_channels(ConnectedClient* client, ChannelIT begin, const Channe for (const auto &elm : channel->properties().list_properties(property::FLAG_CHANNEL_VIEW, client->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { if(elm.type() == property::CHANNEL_ORDER) - channellist[index][elm.type().name] = override_orderid ? 0 : (*begin)->previous_channel; + builder.put_unchecked(index, elm.type().name, override_orderid ? 0 : (*begin)->previous_channel); else - channellist[index][elm.type().name] = elm.as(); + builder.put_unchecked(index, elm.type().name, elm.as()); } begin++; @@ -636,9 +635,9 @@ inline void send_channels(ConnectedClient* client, ChannelIT begin, const Channe } if(dynamic_cast(client)) { auto vc = dynamic_cast(client); - vc->sendCommand0(channellist, false, true); /* we need to process this command directly so it will be processed before the channellistfinished stuff */ + vc->sendCommand0(builder.build(), false, true); /* we need to process this command directly so it will be processed before the channellistfinished stuff */ } else { - client->sendCommand(channellist); + client->sendCommand(builder); } if(begin != end) send_channels(client, begin, end, override_orderid); @@ -785,7 +784,7 @@ void ConnectedClient::sendServerInit() { command["acn"] = this->getDisplayName(); command["aclid"] = this->getClientId(); if(dynamic_cast(this)) { - dynamic_cast(this)->sendCommand0(command, false, true); /* process it directly so the order for the channellist entries is ensured. (First serverinit then everything else) */ + dynamic_cast(this)->sendCommand0(command.build(), false, true); /* process it directly so the order for the channellist entries is ensured. (First serverinit then everything else) */ } else { this->sendCommand(command); } @@ -851,7 +850,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) { } std::shared_ptr ConnectedClient::resolveActiveBan(const std::string& ip_address) { - if(this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_ignore_bans, 1)) return nullptr; + if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, 0))) return nullptr; //Check if manager banned auto banManager = serverInstance->banManager(); @@ -901,44 +900,37 @@ std::shared_ptr ConnectedClient::resolveActiveBan(const std::string& } bool ConnectedClient::update_cached_permissions() { - auto values = this->permissionValues(permission::PERMTEST_ORDERED, permission::neededPermissions, shared_ptr(this->currentChannel)); /* copy the channel here so it does not change */ + auto values = this->calculate_permissions(permission::neededPermissions, this->currentChannel? this->currentChannel->channelId() : 0); /* copy the channel here so it does not change */ auto updated = false; { lock_guard cached_lock(this->cached_permissions_lock); - vector old_permissions; - old_permissions.reserve(this->cached_permissions.size()); + auto old_cached_permissions{this->cached_permissions}; + this->cached_permissions = values; + std::sort(this->cached_permissions.begin(), this->cached_permissions.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); - for(const auto& value : this->cached_permissions) - old_permissions.push_back(value.first); - - for(const auto& value : values) { - auto value_it = cached_permissions.find(value.first); - if(value_it == cached_permissions.end()) { /* new entry */ - updated = true; - this->cached_permissions[value.first] = value.second; - continue; /* no need to remove that from old_permissions because it isn't there */ - } else if(value_it->second != value.second) { /* entry changed */ - updated = true; - value_it->second = value.second; - } - - { /* we've updated the value or verified it */ - auto old_it = find(old_permissions.begin(), old_permissions.end(), value.first); - if(old_it != old_permissions.end()) - old_permissions.erase(old_it); + if(this->cached_permissions.size() != old_cached_permissions.size()) + updated = true; + else { + for(auto oit = old_cached_permissions.begin(), nit = this->cached_permissions.begin(); oit != old_cached_permissions.end(); oit++, nit++) { + if(oit->first != nit->first || oit->second != nit->second) { + updated = true; + break; + } } } + } - for(const auto& left : old_permissions) { - auto value_it = cached_permissions.find(left); - if(value_it != cached_permissions.end()) { - cached_permissions.erase(value_it); - updated = true; - } - } + this->cpmerission_whisper_power = {0, false}; + this->cpmerission_needed_whisper_power = {0, false}; + 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) + this->cpmerission_needed_whisper_power = entry.second; } return updated; @@ -954,72 +946,47 @@ void ConnectedClient::sendTSPermEditorWarning() { } } -permission::v2::PermissionFlaggedValue ConnectedClient::calculate_permission_value(const ts::permission::PermissionType &permission, ts::ChannelId channel_id) { - if(channel_id == (this->currentChannel ? this->currentChannel->channelId() : 0) || channel_id == -1) { - std::lock_guard lock(this->cached_permissions_lock); - auto index = this->cached_permissions.find(permission); - if(index != this->cached_permissions.end()) - return {index->second, index->second != permNotGranted}; - } - - auto ref_server = this->server; - if(ref_server) { - auto result = this->server->calculatePermissions2(this->getClientDatabaseId(), {permission}, this->getType(), channel_id, false); - if(!result.empty()) /* it should never be empty! */ - return result.back().second; - } - - auto value = this->permissionValue(permission::PERMTEST_ORDERED, permission, nullptr); - return {value, value != permNotGranted}; -} - -#define RESULT(flag) \ +#define RESULT(perm_) \ do { \ ventry->join_state_id = this->join_state_id; \ - ventry->joinable = (flag); \ - return flag; \ + ventry->join_permission_error = (perm_); \ + return perm_; \ } while(0) -bool ConnectedClient::calculate_and_get_join_state(const std::shared_ptr& channel) { +permission::PermissionType ConnectedClient::calculate_and_get_join_state(const std::shared_ptr& channel) { shared_ptr ventry; { shared_lock view_lock(this->channel_lock); ventry = this->channel_view()->find_channel(channel); if(!ventry) - return false; + return permission::i_channel_view_power; } if(ventry->join_state_id == this->join_state_id) - return ventry->joinable; + return ventry->join_permission_error; + auto channel_id = channel->channelId(); auto permission_cache = make_shared(); switch(channel->channelType()) { case ChannelType::permanent: - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_permanent, 1, channel, true, permission_cache)) - RESULT(false); + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_permanent, channel_id))) + RESULT(permission::b_channel_join_permanent); break; case ChannelType::semipermanent: - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_semi_permanent, 1, channel, true, permission_cache)) - RESULT(false); + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_semi_permanent, channel_id))) + RESULT(permission::b_channel_join_semi_permanent); break; case ChannelType::temporary: - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_temporary, 1, channel, true, permission_cache)) - RESULT(false); + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_temporary, channel_id))) + RESULT(permission::b_channel_join_temporary); break; } - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_join_power, 1, channel, true, permission_cache)) { - auto result = this->server->calculatePermissions2(this->getClientDatabaseId(), {permission::i_channel_join_power}, this->getType(), channel->channelId(), false, permission_cache); - if(result.empty()) - RESULT(false); - - if(!channel->permission_granted(permission::i_channel_needed_join_power, result.back().second, false)) - RESULT(false); + if(!channel->permission_granted(permission::i_channel_needed_join_power, this->calculate_permission(permission::i_channel_join_power, channel_id), false)) { + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_join_power, channel_id))) + RESULT(permission::i_channel_join_power); } - auto val = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_is_sticky, this->currentChannel, permission_cache); - if (val != permNotGranted && val > 0) { - auto st = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_ignore_sticky, this->currentChannel, permission_cache); - if (st != 1) - RESULT(false); - } - RESULT(true); + if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_is_sticky, this->currentChannel ? this->currentChannel->channelId() : 0))) + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_sticky, channel_id))) + RESULT(permission::b_client_is_sticky); + RESULT(permission::unknown); } \ No newline at end of file diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index cb7e369..61381ed 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -6,6 +6,7 @@ #include "music/Song.h" #include "../channel/ClientChannelView.h" #include "DataClient.h" +#include "query/command3.h" #define CLIENT_STR_LOG_PREFIX_(this) (std::string("[") + this->getLoggingPeerIp() + ":" + std::to_string(this->getPeerPort()) + "/" + this->getDisplayName() + " | " + std::to_string(this->getClientId()) + "]") #define CLIENT_STR_LOG_PREFIX CLIENT_STR_LOG_PREFIX_(this) @@ -37,40 +38,6 @@ if(this->shouldFloodBlock()) return command_result{error::ban_flooding}; #define CMD_CHK_PARM_COUNT(count) \ if(cmd.bulkCount() != count) return command_result{error::parameter_invalid_count}; - - -#define PERM_CHECK_CHANNEL_CR(pr, required, channel, req, cache) \ -do { \ - if(!this->permissionGranted(permission::PERMTEST_ORDERED, pr, required, channel, req, cache)) {\ - return command_result{pr}; \ - }\ -} while(false) - -//If a permission is required it could not be -1 -#define PERM_CHECK_CHANNELR(pr, required, channel, req) PERM_CHECK_CHANNEL_CR(pr, required, channel, req, nullptr) - -//If a permission is required it could not be -1 -#define PERM_CHECK_CHANNEL(pr, required, channel) PERM_CHECK_CHANNELR(pr, required, channel, false) - -#define PERM_CHECKR(pr, required, req) PERM_CHECK_CHANNELR(pr, (required), nullptr, req) -#define PERM_CHECK(pr, required) PERM_CHECKR(pr, required, false) - -/* optional parameter required. By default true */ -#define CACHED_PERM_CHECK(permission_type, required, ...) \ -do { \ - if(!this->permission_granted(this->cached_permission_value(permission_type), required, #__VA_ARGS__)) \ - return command_result{permission_type}; \ -} while(0) - -//p = permission | target_permission = channel permission | channel = target channel | requires a power - -#define PERM_CHECK_CHANNEL_NEEDED_CR(p, target_permission, channel, req, cache) \ -this->permissionGranted(permission::PERMTEST_ORDERED, p, (channel)->permissions()->getPermissionValue(permission::PERMTEST_ORDERED, target_permission, nullptr), channel, req, cache) - -#define PERM_CHECK_CHANNEL_NEEDEDR(p, target_permission, channel, req) PERM_CHECK_CHANNEL_NEEDED_CR(p, target_permission, channel, req, nullptr) - -#define PERM_CHECK_CHANNEL_NEEDED(p, target_permission, channel) PERM_CHECK_CHANNEL_NEEDEDR(p, target_permission, channel, true) - namespace ts { class GroupManager; namespace connection { @@ -120,6 +87,7 @@ namespace ts { /* Note: Order is not guaranteed here! */ virtual void sendCommand(const ts::Command& command, bool low = false) = 0; + virtual void sendCommand(const ts::command_builder& command, bool low = false) = 0; //General manager stuff //FIXME cache the client id for speedup @@ -127,6 +95,7 @@ namespace ts { virtual void setClientId(uint16_t clId) { properties()[property::CLIENT_ID] = clId; } inline std::shared_ptr getChannel(){ return this->currentChannel; } + inline ChannelId getChannelId(){ auto channel = this->currentChannel; return channel ? channel->channelId() : 0; } inline std::shared_ptr getServer(){ return this->server; } inline ServerId getServerId(){ return this->server ? this->server->getServerId() : (ServerId) 0; } @@ -289,18 +258,19 @@ namespace ts { /* * permission stuff */ + /* inline permission::PermissionValue cached_permission_value(permission::PermissionType type) const { std::lock_guard lock(this->cached_permissions_lock); auto index = this->cached_permissions.find(type); if(index != this->cached_permissions.end()) return index->second; - /* We're only caching permissions which are granted to reduce memory */ + We're only caching permissions which are granted to reduce memory //logError(this->getServerId(), "{} Looked up cached permission, which hasn't been cached!", CLIENT_STR_LOG_PREFIX); return permNotGranted; } + */ bool update_cached_permissions(); - permission::v2::PermissionFlaggedValue calculate_permission_value(const permission::PermissionType& /* permission type */, ChannelId /* target channel */); protected: std::weak_ptr _this; @@ -309,7 +279,6 @@ namespace ts { ConnectionState state = ConnectionState::UNKNWON; sockaddr_storage remote_address; - std::shared_ptr currentChannel = nullptr; bool allowedToTalk = false; threads::Mutex disconnectLock; @@ -358,23 +327,16 @@ namespace ts { std::shared_mutex channel_lock; std::mutex cached_permissions_lock; - std::map cached_permissions; /* contains all needed permissions which are set */ - #pragma pack(push, 1) - struct CachedPermission { - bool flag_skip : 1; /* could be enabled by server / channel or client group. If this flag is set we need no lookup for channel permissions */ - bool flag_value : 1; /* says if we have a value or not */ - permission::PermissionValue value; - }; - static_assert(sizeof(CachedPermission) == 5); - #pragma pack(pop) + std::vector> cached_permissions; /* contains all needed permissions which are set */ - - permission::PermissionValue channels_view_power = permNotGranted; - permission::PermissionValue channels_ignore_view = permNotGranted; + permission::v2::PermissionFlaggedValue channels_view_power{0, false}; + permission::v2::PermissionFlaggedValue channels_ignore_view{0, false}; + permission::v2::PermissionFlaggedValue cpmerission_whisper_power{0, false}; + permission::v2::PermissionFlaggedValue cpmerission_needed_whisper_power{0, false}; bool subscribeToAll = false; uint16_t join_state_id = 1; /* default channel value is 0 and by default we need to calculate at least once, so we use 1 */ - bool calculate_and_get_join_state(const std::shared_ptr&); + permission::PermissionType calculate_and_get_join_state(const std::shared_ptr&); std::weak_ptr selectedBot; std::weak_ptr subscribed_bot; @@ -435,13 +397,13 @@ namespace ts { command_result handleCommandServerGroupAutoAddPerm(Command&); command_result handleCommandServerGroupAutoDelPerm(Command&); - command_result handleCommandClientAddPerm(Command&); //TODO: Use cached permission values - command_result handleCommandClientDelPerm(Command&); //TODO: Use cached permission values - command_result handleCommandClientPermList(Command&); //TODO: Use cached permission values + command_result handleCommandClientAddPerm(Command&); + command_result handleCommandClientDelPerm(Command&); + command_result handleCommandClientPermList(Command&); - command_result handleCommandChannelClientAddPerm(Command&); //TODO: Use cached permission values - command_result handleCommandChannelClientDelPerm(Command&); //TODO: Use cached permission values - command_result handleCommandChannelClientPermList(Command&); //TODO: Use cached permission values + command_result handleCommandChannelClientAddPerm(Command&); + command_result handleCommandChannelClientDelPerm(Command&); + command_result handleCommandChannelClientPermList(Command&); command_result handleCommandChannelGroupAdd(Command&); command_result handleCommandChannelGroupCopy(Command&); @@ -459,12 +421,12 @@ namespace ts { command_result handleCommandClientChatClosed(Command&); //File transfare commands - command_result handleCommandFTGetFileList(Command&); //TODO: Use cached permission values - command_result handleCommandFTCreateDir(Command&); //TODO: Use cached permission values - command_result handleCommandFTDeleteFile(Command&); //TODO: Use cached permission values - command_result handleCommandFTInitUpload(Command&); //TODO: Use cached permission values - command_result handleCommandFTInitDownload(Command&); //TODO: Use cached permission values - command_result handleCommandFTGetFileInfo(Command&); //TODO: Use cached permission values + command_result handleCommandFTGetFileList(Command&); + command_result handleCommandFTCreateDir(Command&); + command_result handleCommandFTDeleteFile(Command&); + command_result handleCommandFTInitUpload(Command&); + command_result handleCommandFTInitDownload(Command&); + command_result handleCommandFTGetFileInfo(Command&); //CMD_TODO handleCommandFTGetFileInfo -> 5 points //CMD_TODO handleCommandFTStop -> 5 points //CMD_TODO handleCommandFTRenameFile -> 5 points @@ -527,20 +489,20 @@ namespace ts { command_result handleCommandPermFind(Command&); command_result handleCommandPermOverview(Command&); - command_result handleCommandChannelFind(Command&); //TODO: Use cached permission values - command_result handleCommandChannelInfo(Command&); //TODO: Use cached permission values + command_result handleCommandChannelFind(Command&); + command_result handleCommandChannelInfo(Command&); - command_result handleCommandMusicBotCreate(Command&); //TODO: Use cached permission values - command_result handleCommandMusicBotDelete(Command&); //TODO: Use cached permission values - command_result handleCommandMusicBotSetSubscription(Command&); //TODO: Use cached permission values + command_result handleCommandMusicBotCreate(Command&); + command_result handleCommandMusicBotDelete(Command&); + command_result handleCommandMusicBotSetSubscription(Command&); - command_result handleCommandMusicBotPlayerInfo(Command&); //TODO: Use cached permission values - command_result handleCommandMusicBotPlayerAction(Command&); //TODO: Use cached permission values + command_result handleCommandMusicBotPlayerInfo(Command&); + command_result handleCommandMusicBotPlayerAction(Command&); - command_result handleCommandMusicBotQueueList(Command&); //TODO: Use cached permission values - command_result handleCommandMusicBotQueueAdd(Command&); //TODO: Use cached permission values - command_result handleCommandMusicBotQueueRemove(Command&); //TODO: Use cached permission values - command_result handleCommandMusicBotQueueReorder(Command&); //TODO: Use cached permission values + command_result handleCommandMusicBotQueueList(Command&); + command_result handleCommandMusicBotQueueAdd(Command&); + command_result handleCommandMusicBotQueueRemove(Command&); + command_result handleCommandMusicBotQueueReorder(Command&); command_result handleCommandMusicBotPlaylistAssign(Command&); @@ -561,9 +523,9 @@ namespace ts { command_result handleCommandPlaylistSongReorder(Command&); command_result handleCommandPlaylistSongRemove(Command&); - command_result handleCommandPermReset(Command&); //TODO: Use cached permission values + command_result handleCommandPermReset(Command&); - command_result handleCommandHelp(Command&); //TODO: Use cached permission values + command_result handleCommandHelp(Command&); command_result handleCommandUpdateMyTsId(Command&); command_result handleCommandUpdateMyTsData(Command&); diff --git a/server/src/client/ConnectedClientCommandHandler.cpp b/server/src/client/ConnectedClientCommandHandler.cpp index 143a6d8..4ab4011 100644 --- a/server/src/client/ConnectedClientCommandHandler.cpp +++ b/server/src/client/ConnectedClientCommandHandler.cpp @@ -96,7 +96,7 @@ if (!l_channel && (channel_id != 0 || force)) return command_result{error::chann /* the "newest" channel permission access test */ #define CHANNEL_PERMISSION_TEST(permission_type, permission_needed_type, _channel, permission_required) \ do { \ - auto permission_granted = this->calculate_permission_value(permission_type, _channel->channelId()); \ + auto permission_granted = this->calculate_permission(permission_type, _channel->channelId()); \ if(!channel->permission_granted(permission_needed_type, permission_granted, permission_required)) \ return command_result{permission_type}; \ } while(0) @@ -104,7 +104,7 @@ do { \ /* the "newest" group permission access test */ #define GROUP_PERMISSION_TEST(permission_type, permission_needed_type, _group, permission_required) \ do { \ - auto permission_granted = this->calculate_permission_value(permission_type, 0); \ + auto permission_granted = this->calculate_permission(permission_type, 0); \ if(!_group->permission_granted(permission_needed_type, permission_granted, permission_required)) \ return command_result{permission_type}; \ } while(0) @@ -375,7 +375,7 @@ command_result ConnectedClient::handleCommand(Command &cmd) { if (this->getType() == ClientType::CLIENT_QUERY) return command_result{error::command_not_found}; //Dont log query invalid commands if (this->getType() == ClientType::CLIENT_TEAMSPEAK) if (command.empty() || command.find_first_not_of(' ') == -1) { - if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_allow_invalid_packet, 1, this->currentChannel)) + if (!this->permissionGranted(permission::b_client_allow_invalid_packet, 1, this->currentChannel)) ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_command, this->server ? this->server->serverAdmin : static_pointer_cast(serverInstance->getInitialServerAdmin()), true); } @@ -391,19 +391,19 @@ command_result ConnectedClient::handleCommandServerGetVariables(Command &cmd) { #define SERVEREDIT_CHK_PROP(name, perm, type)\ else if(key == name) { \ - if(!permissionGranted(permission::PERMTEST_HIGHEST, perm, 1, nullptr, true, cache, target_server, true)) return command_result{perm}; \ + if(!permissionGranted(perm, 1, nullptr, true, cache, target_server, true)) return command_result{perm}; \ if(toApplay.count(key) == 0) toApplay[key] = cmd[key].as(); \ if(!cmd[0][key].castable()) return command_result{error::parameter_invalid}; #define SERVEREDIT_CHK_PROP_CACHED(name, perm, type)\ else if(key == name) { \ - if(!this->permission_granted(this->cached_permission_value(perm), 1)) return command_result{perm}; \ + if(!this->permission_granted(this->calculate_permission(perm, 0), 1)) return command_result{perm}; \ if(toApplay.count(key) == 0) toApplay[key] = cmd[key].as(); \ if(!cmd[0][key].castable()) return command_result{error::parameter_invalid}; #define SERVEREDIT_CHK_PROP2(name, perm, type_a, type_b)\ else if(key == name) { \ - if(!permissionGranted(permission::PERMTEST_HIGHEST, perm, 1, nullptr, true, cache, target_server, true)) return command_result{perm}; \ + if(!permissionGranted(perm, 1, nullptr, true, cache, target_server, true)) return command_result{perm}; \ if(toApplay.count(key) == 0) toApplay[key] = cmd[key].as(); \ if(!cmd[0][key].castable() && !!cmd[0][key].castable()) return command_result{error::parameter_invalid}; @@ -619,11 +619,11 @@ command_result ConnectedClient::handleCommandClientKick(Command &cmd) { auto type = cmd["reasonid"].as(); if (type == ViewReasonId::VREASON_CHANNEL_KICK) { auto channel = client->currentChannel; - PERM_CHECK_CHANNELR(permission::i_client_kick_from_channel_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_kick_from_channel_power, channel), channel, true); + PERM_CHECK_CHANNELR(permission::i_client_kick_from_channel_power, client->permissionValue(permission::i_client_needed_kick_from_channel_power, channel), channel, true); targetChannel = this->server->channelTree->getDefaultChannel(); } else if (type == ViewReasonId::VREASON_SERVER_KICK) { auto channel = client->currentChannel; - PERM_CHECK_CHANNELR(permission::i_client_kick_from_server_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_kick_from_server_power, channel), channel, true); + PERM_CHECK_CHANNELR(permission::i_client_kick_from_server_power, client->permissionValue(permission::i_client_needed_kick_from_server_power, channel), channel, true); targetChannel = nullptr; } else return command_result{error::not_implemented}; @@ -643,7 +643,7 @@ command_result ConnectedClient::handleCommandChannelGetDescription(Command &cmd) auto channel = dynamic_pointer_cast(l_channel->entry); assert(channel); - if(!this->permission_granted(this->permissionValue(permission::b_channel_ignore_description_view_power, channel), 1, true)) { + if(!this->permission_granted(this->calculate_permission(permission::b_channel_ignore_description_view_power, channel->channelId()), 1, true)) { CHANNEL_PERMISSION_TEST(permission::i_channel_description_view_power, permission::i_channel_needed_description_view_power, channel, false); } @@ -663,7 +663,7 @@ command_result ConnectedClient::handleCommandGetConnectionInfo(Command &cmd) { if (info) { this->notifyConnectionInfo(client, info); } else if(send_temp) { - this->notifyConnectionInfo(client, nullptr); + return command_result{error::no_cached_connection_info}; } return command_result{error::ok}; @@ -1034,7 +1034,7 @@ command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) { return command_result{result}; } - if(!target_group->permission_granted(permission::i_channel_group_needed_modify_power, this->calculate_permission_value(permission::i_channel_group_modify_power, 0), true)) + if(!target_group->permission_granted(permission::i_channel_group_needed_modify_power, this->calculate_permission(permission::i_channel_group_modify_power, 0), true)) return command_result{permission::i_channel_group_modify_power}; if(!group_manager->copyGroupPermissions(source_group, target_group)) @@ -1230,7 +1230,7 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; GROUP_PERMISSION_TEST(permission::i_channel_group_modify_power, permission::i_channel_group_needed_modify_power, channelGroup, true); - auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); + auto maxValue = this->getPermissionGrantValue(permission::i_permission_modify_power, this->currentChannel); bool ignoreGrant = this->permission_granted(this->cached_permission_value(permission::b_permission_modify_power_ignore), 1); bool updateList = false; bool conOnError = cmd[0].has("continueonerror"); @@ -1243,7 +1243,7 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { if(permission_require_granted_value(permType) && val > maxValue) return command_result{permission::i_permission_modify_power}; - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) return command_result{permission::i_permission_modify_power}; if (grant) { @@ -1299,7 +1299,7 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd) - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) return command_result{permission::i_permission_modify_power}; if (grant) { permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); @@ -1362,7 +1362,7 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { if(!cmd[0].has("cpw")) cmd["cpw"] = ""; if (!channel->passwordMatch(cmd["cpw"], true)) - if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_ignore_password, 1, channel, true, permission_cache)) + if (!this->permissionGranted(permission::b_channel_join_ignore_password, 1, channel, true, permission_cache)) return command_result{error::channel_invalid_password}; switch(channel->channelType()) { @@ -1377,7 +1377,7 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { break; } if (!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as() || !channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as()) { - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_ignore_maxclients, 1, channel, true, permission_cache)) { + if(!this->permissionGranted(permission::b_channel_join_ignore_maxclients, 1, channel, true, permission_cache)) { if(!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as()) { auto maxClients = channel->properties()[property::CHANNEL_MAXCLIENTS].as(); if (maxClients >= 0 && maxClients <= this->server->getClientsByChannel(channel).size()) @@ -1401,23 +1401,23 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) { } } - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_join_power, 1, channel, true, permission_cache)) { + if(!this->permissionGranted(permission::b_channel_ignore_join_power, 1, channel, true, permission_cache)) { CHANNEL_PERMISSION_TEST(permission::i_channel_join_power, permission::i_channel_needed_join_power, channel, false); } if (target_client == this) { auto permission_cache_current = make_shared(); - auto val = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_is_sticky, this->currentChannel, permission_cache_current); + auto val = this->permissionValue(permission::b_client_is_sticky, this->currentChannel, permission_cache_current); if (val != permNotGranted && val > 0) { - auto st = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_ignore_sticky, this->currentChannel, permission_cache_current); + auto st = this->permissionValue(permission::b_client_ignore_sticky, this->currentChannel, permission_cache_current); if (st != 1) return command_result{permission::b_client_is_sticky}; } } if (target_client != this) { - PERM_CHECK_CHANNELR(permission::i_client_move_power, target_client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_move_power, target_client->getChannel()), target_client->getChannel(), true); - PERM_CHECK_CHANNEL_CR(permission::i_client_move_power, target_client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_move_power, channel), channel, true, permission_cache); + PERM_CHECK_CHANNELR(permission::i_client_move_power, target_client->permissionValue(permission::i_client_needed_move_power, target_client->getChannel()), target_client->getChannel(), true); + PERM_CHECK_CHANNEL_CR(permission::i_client_move_power, target_client->permissionValue(permission::i_client_needed_move_power, channel), channel, true, permission_cache); } server_channel_r_lock.unlock(); @@ -1524,7 +1524,7 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { } - auto max_channels = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_channels, nullptr, permission_cache); + auto max_channels = this->permissionValue(permission::i_client_max_channels, nullptr, permission_cache); if(max_channels >= 0) { if(max_channels <= created_perm + created_semi + created_tmp) @@ -1532,21 +1532,21 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { } if (cmd[0]["channel_flag_permanent"].as()) { - max_channels = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_permanent_channels, nullptr, permission_cache); + max_channels = this->permissionValue(permission::i_client_max_permanent_channels, nullptr, permission_cache); if(max_channels >= 0) { if(max_channels <= created_perm) return command_result{permission::i_client_max_permanent_channels}; } } else if (cmd[0]["channel_flag_semi_permanent"].as()) { - max_channels = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_semi_channels, nullptr, permission_cache); + max_channels = this->permissionValue(permission::i_client_max_semi_channels, nullptr, permission_cache); if(max_channels >= 0) { if(max_channels <= created_semi) return command_result{permission::i_client_max_semi_channels}; } } else { - max_channels = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_temporary_channels, nullptr, permission_cache); + max_channels = this->permissionValue(permission::i_client_max_temporary_channels, nullptr, permission_cache); if(max_channels >= 0) { if(max_channels <= created_tmp) return command_result{permission::i_client_max_temporary_channels}; @@ -1567,8 +1567,8 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { { - auto min_channel_deep = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_min_depth, nullptr, permission_cache); - auto max_channel_deep = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_max_depth, nullptr, permission_cache); + auto min_channel_deep = this->permissionValue(permission::i_channel_min_depth, nullptr, permission_cache); + auto max_channel_deep = this->permissionValue(permission::i_channel_max_depth, nullptr, permission_cache); if(min_channel_deep >= 0 || max_channel_deep >= 0) { auto channel_deep = 0; @@ -1622,13 +1622,13 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { auto permission_manager = created_channel->permissions(); permission_manager->set_permission( permission::i_channel_needed_modify_power, - {this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_modify_power, this->currentChannel, permission_cache), 0}, + {this->permissionValue(permission::i_channel_modify_power, this->currentChannel, permission_cache), 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing ); permission_manager->set_permission( permission::i_channel_needed_delete_power, - {this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_delete_power, this->currentChannel, permission_cache), 0}, + {this->permissionValue(permission::i_channel_delete_power, this->currentChannel, permission_cache), 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing ); @@ -1849,13 +1849,13 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { CHANNEL_PERM_TEST(permission::b_channel_modify_topic, 1, true); } else if (key == "channel_description") { CHANNEL_PERM_TEST(permission::b_channel_modify_description, 1, true); - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_use_bbcode_any, 1, this->currentChannel)) { + if(!this->permissionGranted(permission::b_client_use_bbcode_any, 1, this->currentChannel)) { auto bbcode_image = bbcode::sloppy::has_image(cmd[key]); auto bbcode_url = bbcode::sloppy::has_url(cmd[key]); debugMessage(this->getServerId(), "Channel description contains bb codes: Image: {} URL: {}", bbcode_image, bbcode_url); - if(bbcode_image && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_use_bbcode_image, 1, this->currentChannel)) + if(bbcode_image && !this->permissionGranted(permission::b_client_use_bbcode_image, 1, this->currentChannel)) return command_result{permission::b_client_use_bbcode_image}; - if(bbcode_url && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_use_bbcode_url, 1, this->currentChannel)) + if(bbcode_url && !this->permissionGranted(permission::b_client_use_bbcode_url, 1, this->currentChannel)) return command_result{permission::b_client_use_bbcode_url}; } } else if (key == "channel_codec") { @@ -2270,8 +2270,8 @@ command_result ConnectedClient::handleCommandChannelMove(Command &cmd) { { - auto min_channel_deep = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_min_depth, nullptr, nullptr); - auto max_channel_deep = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_max_depth, nullptr, nullptr); + auto min_channel_deep = this->permissionValue(permission::i_channel_min_depth, nullptr, nullptr); + auto max_channel_deep = this->permissionValue(permission::i_channel_max_depth, nullptr, nullptr); if(min_channel_deep >= 0 || max_channel_deep >= 0) { auto channel_deep = 0; @@ -2355,7 +2355,7 @@ command_result ConnectedClient::handleCommandClientPoke(Command &cmd) { auto client = this->server->findClient(cmd["clid"].as()); if (!client) return command_result{error::client_invalid_id}; if (client->getType() == CLIENT_MUSIC) return command_result{error::client_invalid_type}; - CACHED_PERM_CHECK(permission::i_client_poke_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_poke_power, client->currentChannel), false); + CACHED_PERM_CHECK(permission::i_client_poke_power, client->permissionValue(permission::i_client_needed_poke_power, client->currentChannel), false); client->notifyClientPoke(_this.lock(), cmd["msg"]); return command_result{error::ok}; @@ -2432,8 +2432,8 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) { CHANNEL_PERMISSION_TEST(permission::i_channel_permission_modify_power, permission::i_channel_needed_permission_modify_power, channel, true); - auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, channel); - bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, channel); + auto maxValue = this->getPermissionGrantValue(permission::i_permission_modify_power, channel); + bool ignoreGrant = this->permissionGranted(permission::b_permission_modify_power_ignore, 1, channel); auto updateClients = false, update_view = false, update_channel_properties = false; bool conOnError = cmd[0].has("continueonerror"); @@ -2449,7 +2449,7 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) { return command_result{permission::i_permission_modify_power}; } - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, channel)) { + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, channel)) { if(conOnError) continue; return command_result{permission::i_permission_modify_power}; @@ -2536,7 +2536,7 @@ command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) { assert(channel); CHANNEL_PERMISSION_TEST(permission::i_channel_permission_modify_power, permission::i_channel_needed_permission_modify_power, channel, true); - bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, channel); + bool ignoreGrant = this->permissionGranted(permission::b_permission_modify_power_ignore, 1, channel); bool conOnError = cmd[0].has("continueonerror"); auto updateClients = false, update_view = false, update_channel_properties = false; @@ -2544,7 +2544,7 @@ command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) { for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, channel)) + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, channel)) return command_result{permission::i_permission_modify_power}; if (grant) { @@ -2681,7 +2681,7 @@ command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) { return command_result{result}; } - if(!target_group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission_value(permission::i_server_group_modify_power, 0), true)) + if(!target_group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission(permission::i_server_group_modify_power, 0), true)) return command_result{permission::i_server_group_modify_power}; if(!group_manager->copyGroupPermissions(source_group, target_group)) @@ -2836,7 +2836,7 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd) auto target_cldbid = cmd["cldbid"].as(); if (!serverInstance->databaseHelper()->validClientDatabaseId(target_server, cmd["cldbid"])) return command_result{error::client_invalid_id, "invalid cldbid"}; - auto needed_client_permission = this->server->calculatePermission(permission::PERMTEST_ORDERED, target_cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr); + auto needed_client_permission = this->server->calculatePermission(target_cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr); if(needed_client_permission != permNotGranted) { if(!this->permission_granted(this->permissionValue(permission::i_client_permission_modify_power), needed_client_permission)) return command_result{permission::i_client_needed_permission_modify_power}; @@ -2848,8 +2848,8 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd) auto continue_on_error = cmd.hasParm("continueonerror"); { - auto permission_add_power = this->calculate_permission_value(permission::i_server_group_member_add_power, -1); - auto permission_self_add_power = this->calculate_permission_value(permission::i_server_group_member_add_power, -1); + auto permission_add_power = this->calculate_permission(permission::i_server_group_member_add_power, -1); + auto permission_self_add_power = this->calculate_permission(permission::i_server_group_member_add_power, -1); for(auto index = 0; index < cmd.bulkCount(); index++) { auto group_id = cmd[index]["sgid"]; @@ -2952,7 +2952,7 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd) auto target_cldbid = cmd["cldbid"].as(); if (!serverInstance->databaseHelper()->validClientDatabaseId(target_server, cmd["cldbid"])) return command_result{error::client_invalid_id, "invalid cldbid"}; - auto needed_client_permission = this->server->calculatePermission(permission::PERMTEST_ORDERED, target_cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr); + auto needed_client_permission = this->server->calculatePermission(target_cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr); if(needed_client_permission != permNotGranted) { if(!this->permission_granted(this->permissionValue(permission::i_client_permission_modify_power), needed_client_permission)) return command_result{permission::i_client_needed_permission_modify_power}; @@ -2964,8 +2964,8 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd) auto continue_on_error = cmd.hasParm("continueonerror"); { - auto permission_remove_power = this->calculate_permission_value(permission::i_server_group_member_remove_power, -1); - auto permission_self_remove_power = this->calculate_permission_value(permission::i_server_group_member_remove_power, -1); + auto permission_remove_power = this->calculate_permission(permission::i_server_group_member_remove_power, -1); + auto permission_self_remove_power = this->calculate_permission(permission::i_server_group_member_remove_power, -1); for(auto index = 0; index < cmd.bulkCount(); index++) { auto group_id = cmd[index]["sgid"]; @@ -3104,7 +3104,7 @@ command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) { return command_result{permission::b_serverinstance_modify_templates}; } - auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); + auto maxValue = this->getPermissionGrantValue(permission::i_permission_modify_power, this->currentChannel); bool ignoreGrant = this->permission_granted(this->cached_permission_value(permission::b_permission_modify_power_ignore), 1); bool conOnError = cmd[0].has("continueonerror"); bool checkTp = false; @@ -3121,7 +3121,7 @@ command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) { return command_result{permission::i_permission_modify_power}; } - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) { + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) { if(conOnError) continue; return command_result{permission::i_permission_modify_power}; } @@ -3194,7 +3194,7 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) { for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) { + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) { if(conOnError) continue; return command_result{permission::i_permission_modify_power}; } @@ -3250,13 +3250,13 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& deque> groups; for(const auto& group : group_manager->availableGroups(false)) { if(group->updateType() == cmd["sgtype"].as() && group->target() == GROUPTARGET_SERVER) { - if(group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission_value(permission::i_server_group_modify_power, 0), true)) { + if(group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission(permission::i_server_group_modify_power, 0), true)) { auto type = group->type(); if(type == GroupType::GROUP_TYPE_QUERY) { - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_serverinstance_modify_querygroup, 1)) + if(!this->permissionGranted(permission::b_serverinstance_modify_querygroup, 1)) continue; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_serverinstance_modify_templates, 1)) + if(!this->permissionGranted(permission::b_serverinstance_modify_templates, 1)) continue; } groups.push_back(group);//sgtype @@ -3267,9 +3267,9 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& if(groups.empty()) return command_result{error::ok}; - auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); + auto maxValue = this->getPermissionGrantValue(permission::i_permission_modify_power, this->currentChannel); - bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1); + bool ignoreGrant = this->permissionGranted(permission::b_permission_modify_power_ignore, 1); bool conOnError = cmd[0].has("continueonerror"); bool checkTp = false; bool sgroupUpdate = false; @@ -3284,7 +3284,7 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& return command_result{permission::i_permission_modify_power}; } - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) { + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) { if(conOnError) continue; return command_result{permission::i_permission_modify_power}; } @@ -3347,13 +3347,13 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& deque> groups; for(const auto& group : group_manager->availableGroups(false)) { if(group->updateType() == cmd["sgtype"].as() && group->target() == GROUPTARGET_SERVER) { - if(group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission_value(permission::i_server_group_modify_power, 0), true)) { + if(group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission(permission::i_server_group_modify_power, 0), true)) { auto type = group->type(); if(type == GroupType::GROUP_TYPE_QUERY) { - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_serverinstance_modify_querygroup, 1)) + if(!this->permissionGranted(permission::b_serverinstance_modify_querygroup, 1)) continue; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_serverinstance_modify_templates, 1)) + if(!this->permissionGranted(permission::b_serverinstance_modify_templates, 1)) continue; } groups.push_back(group);//sgtype @@ -3363,14 +3363,14 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& if(groups.empty()) return command_result{error::ok}; - bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1); + bool ignoreGrant = this->permissionGranted(permission::b_permission_modify_power_ignore, 1); bool conOnError = cmd[0].has("continueonerror"); bool checkTp = false; auto sgroupUpdate = false; for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) { + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) { if(conOnError) continue; return command_result{permission::i_permission_modify_power}; } @@ -3441,18 +3441,18 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) auto target_cldbid = cmd["cldbid"].as(); { - auto channel_group_member_add_power = this->calculate_permission_value(permission::i_channel_group_member_add_power, channel_id); + auto channel_group_member_add_power = this->calculate_permission(permission::i_channel_group_member_add_power, channel_id); if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_add_power, channel_group_member_add_power, true)) { if(target_cldbid != this->getClientDatabaseId()) return command_result{permission::i_channel_group_member_add_power}; - auto channel_group_self_add_power = this->calculate_permission_value(permission::i_channel_group_self_add_power, channel_id); + auto channel_group_self_add_power = this->calculate_permission(permission::i_channel_group_self_add_power, channel_id); if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_add_power, channel_group_self_add_power, true)) return command_result{permission::i_channel_group_self_add_power}; } - auto client_permission_modify_power = this->calculate_permission_value(permission::i_client_permission_modify_power, channel_id); + auto client_permission_modify_power = this->calculate_permission(permission::i_client_permission_modify_power, channel_id); auto client_needed_permission_modify_power = this->server->calculatePermission2( permission::i_client_needed_permission_modify_power, target_cldbid, ClientType::CLIENT_TEAMSPEAK, channel_id); @@ -3466,12 +3466,12 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) { auto old_group = this->server->groups->getChannelGroupExact(target_cldbid, channel, false); if(old_group) { - auto channel_group_member_remove_power = this->calculate_permission_value(permission::i_channel_group_member_remove_power, channel_id); + auto channel_group_member_remove_power = this->calculate_permission(permission::i_channel_group_member_remove_power, channel_id); if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_member_remove_power, true)) { if(target_cldbid != this->getClientDatabaseId()) return command_result{permission::i_channel_group_member_remove_power}; - auto channel_group_self_remove_power = this->calculate_permission_value(permission::i_channel_group_self_remove_power, channel_id); + auto channel_group_self_remove_power = this->calculate_permission(permission::i_channel_group_self_remove_power, channel_id); if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_self_remove_power, true)) return command_result{permission::i_channel_group_self_remove_power}; } @@ -3534,7 +3534,7 @@ command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) { PERM_CHECK_CHANNELR(permission::b_client_even_textmessage_send, 1, this->currentChannel, true); } - PERM_CHECK_CHANNELR(permission::i_client_private_textmessage_power, target->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_private_textmessage_power, target->currentChannel), this->currentChannel, false); + PERM_CHECK_CHANNELR(permission::i_client_private_textmessage_power, target->permissionValue(permission::i_client_needed_private_textmessage_power, target->currentChannel), this->currentChannel, false); { @@ -3568,7 +3568,7 @@ command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) { channel_tree_read_lock.lock(); } - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_channel_textmessage_send, 1, channel, false)) + if(!this->permissionGranted(permission::b_client_channel_textmessage_send, 1, channel, false)) return command_result{permission::b_client_channel_textmessage_send}; bool conversation_private = channel->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as(); @@ -3715,7 +3715,7 @@ command_result ConnectedClient::handleCommandFTGetFileList(Command &cmd) { if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; - if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) + if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; CHANNEL_PERMISSION_TEST(permission::i_ft_file_browse_power, permission::i_ft_needed_file_browse_power, channel, true); cmd_filelist_append_files( @@ -3759,7 +3759,7 @@ command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) { auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; - if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) + if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; CHANNEL_PERMISSION_TEST(permission::i_ft_directory_create_power, permission::i_ft_needed_directory_create_power, channel, true); @@ -3780,7 +3780,7 @@ command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; - if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) + if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; CHANNEL_PERMISSION_TEST(permission::i_ft_file_delete_power, permission::i_ft_needed_file_delete_power, channel, true); for (int index = 0; index < cmd.bulkCount(); index++) @@ -3834,17 +3834,17 @@ command_result ConnectedClient::handleCommandFTInitUpload(Command &cmd) { if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; - if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) + if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; CHANNEL_PERMISSION_TEST(permission::i_ft_file_upload_power, permission::i_ft_needed_file_upload_power, channel, true); directory = serverInstance->getFileServer()->resolveDirectory(this->server, channel); } else { if (cmd["path"].as().empty() && cmd["name"].as().find("/icon_") == 0) { - auto max_size = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_max_icon_filesize, this->currentChannel); + auto max_size = this->permissionValue(permission::i_max_icon_filesize, this->currentChannel); if(max_size != -1 && max_size < (ssize_t) cmd["size"].as()) return command_result{permission::i_max_icon_filesize}; directory = serverInstance->getFileServer()->iconDirectory(this->server); } else if (cmd["path"].as().empty() && cmd["name"].string() == "/avatar") { - auto max_size = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_avatar_filesize, this->currentChannel); + auto max_size = this->permissionValue(permission::i_client_max_avatar_filesize, this->currentChannel); if(max_size != -1 && max_size < (ssize_t) cmd["size"].as()) return command_result{permission::i_client_max_avatar_filesize}; directory = serverInstance->getFileServer()->avatarDirectory(this->server); @@ -3870,7 +3870,7 @@ command_result ConnectedClient::handleCommandFTInitUpload(Command &cmd) { server_used_quota += trans->remaining_bytes(); if(server_quota >= 0 && server_quota * 1024 * 1024 < (int64_t) server_used_quota) return command_result{error::file_transfer_server_quota_exceeded}; - auto client_quota = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_ft_quota_mb_upload_per_client, this->currentChannel); + auto client_quota = this->permissionValue(permission::i_ft_quota_mb_upload_per_client, this->currentChannel); auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].as(); client_used_quota += cmd["size"].as(); for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers(_this.lock())) @@ -3932,7 +3932,7 @@ command_result ConnectedClient::handleCommandFTInitDownload(Command &cmd) { if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; CHANNEL_PERMISSION_TEST(permission::i_ft_file_download_power, permission::i_ft_needed_file_download_power, channel, true); - if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) + if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; directory = serverInstance->getFileServer()->resolveDirectory(this->server, channel); @@ -3970,7 +3970,7 @@ command_result ConnectedClient::handleCommandFTInitDownload(Command &cmd) { if(server_quota >= 0 && server_quota * 1024 * 1024 < (int64_t) server_used_quota) return command_result{error::file_transfer_server_quota_exceeded}; - auto client_quota = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_ft_quota_mb_download_per_client, this->currentChannel); + auto client_quota = this->permissionValue(permission::i_ft_quota_mb_download_per_client, this->currentChannel); auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); client_used_quota += key->size; for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers(_this.lock())) @@ -4042,7 +4042,7 @@ command_result ConnectedClient::handleCommandFTGetFileInfo(ts::Command &cmd) { if (request.has("cid") && request["cid"].as() != 0) { //Channel auto channel = this->server->channelTree->findChannel(request["cid"].as()); if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; - if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) + if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; CHANNEL_PERMISSION_TEST(permission::i_ft_file_browse_power, permission::i_ft_needed_file_browse_power, channel, true); @@ -4097,7 +4097,7 @@ command_result ConnectedClient::handleCommandBanList(Command &cmd) { auto server = serverInstance->getVoiceServerManager()->findServerById(sid); if (!server) return command_result{error::parameter_invalid}; - if (server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_client_ban_list, this->getType(), nullptr) != 1) + if (server->calculatePermission(this->getClientDatabaseId(), permission::b_client_ban_list, this->getType(), nullptr) != 1) return command_result{permission::b_client_ban_list}; } @@ -4159,7 +4159,7 @@ command_result ConnectedClient::handleCommandBanAdd(Command &cmd) { } else { auto server = serverInstance->getVoiceServerManager()->findServerById(sid); if (!server) return command_result{error::parameter_invalid}; - if (server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_client_ban_create, this->getType(), nullptr) != 1) + if (server->calculatePermission(this->getClientDatabaseId(), permission::b_client_ban_create, this->getType(), nullptr) != 1) return command_result{permission::b_client_ban_create_global}; } @@ -4210,7 +4210,7 @@ command_result ConnectedClient::handleCommandBanEdit(Command &cmd) { auto server = serverInstance->getVoiceServerManager()->findServerById(sid); if (!server) return command_result{error::parameter_invalid}; - if (server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_client_ban_edit, this->getType(), nullptr) != 1) return command_result{permission::b_client_ban_edit}; + if (server->calculatePermission(this->getClientDatabaseId(), permission::b_client_ban_edit, this->getType(), nullptr) != 1) return command_result{permission::b_client_ban_edit}; } /* ip name uid reason time hwid */ @@ -4298,9 +4298,9 @@ command_result ConnectedClient::handleCommandBanClient(Command &cmd) { return command_result{error::client_unknown}; } if (target_dbid != 0) { - if (this->server->calculatePermission(permission::PERMTEST_ORDERED, target_dbid, permission::i_client_needed_ban_power, ClientType::CLIENT_TEAMSPEAK, nullptr) > this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_ban_power)) + if (this->server->calculatePermission(target_dbid, permission::i_client_needed_ban_power, ClientType::CLIENT_TEAMSPEAK, nullptr) > this->permissionValue(permission::i_client_ban_power)) return command_result{permission::i_client_ban_power}; - if (this->server->calculatePermission(permission::PERMTEST_ORDERED, target_dbid, permission::b_client_ignore_bans, ClientType::CLIENT_TEAMSPEAK, nullptr) >= 1) return command_result{permission::b_client_ignore_bans}; + if (this->server->calculatePermission(target_dbid, permission::b_client_ignore_bans, ClientType::CLIENT_TEAMSPEAK, nullptr) >= 1) return command_result{permission::b_client_ignore_bans}; } deque ban_ids; auto _id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, uid, "", "", "", until); @@ -4376,7 +4376,7 @@ command_result ConnectedClient::handleCommandBanDel(Command &cmd) { if (!server) return command_result{error::parameter_invalid}; auto perm = ban->invokerDbId == this->getClientDatabaseId() ? permission::b_client_ban_delete_own : permission::b_client_ban_delete; - if (server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), perm, this->getType(), nullptr) != 1) return command_result{perm}; + if (server->calculatePermission(this->getClientDatabaseId(), perm, this->getType(), nullptr) != 1) return command_result{perm}; } serverInstance->banManager()->unban(ban); return command_result{error::ok}; @@ -4763,7 +4763,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std: PERM_CHECKR(permission::b_client_modify_own_description, 1, true); else if(client->getType() == ClientType::CLIENT_MUSIC) { if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { - CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); + CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::i_client_music_needed_modify_power, client->currentChannel), true); } } else { PERM_CHECKR(permission::b_client_modify_description, 1, true); @@ -4784,7 +4784,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std: if(!self) { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { - CACHED_PERM_CHECK(permission::i_client_music_rename_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_rename_power, client->currentChannel), true); + CACHED_PERM_CHECK(permission::i_client_music_rename_power, client->permissionValue(permission::i_client_music_needed_rename_power, client->currentChannel), true); } } @@ -4792,7 +4792,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std: if (count_characters(name) < 3) return command_result{error::parameter_invalid, "Invalid name length. A minimum of 3 characters is required!"}; if (count_characters(name) > 30) return command_result{error::parameter_invalid, "Invalid name length. A maximum of 30 characters is allowed!"}; - auto banIgnore = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_ignore_bans, 1, this->currentChannel); + auto banIgnore = this->permissionGranted(permission::b_client_ignore_bans, 1, this->currentChannel); if (!banIgnore) { auto banRecord = serverInstance->banManager()->findBanByName(this->getServerId(), name); if (banRecord) @@ -4817,7 +4817,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std: } else if(*info == property::CLIENT_PLAYER_VOLUME) { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { - CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); + CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::i_client_music_needed_modify_power, client->currentChannel), true); } auto bot = dynamic_pointer_cast(client); assert(bot); @@ -4831,7 +4831,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std: if(!self) { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { - CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); + CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::i_client_music_needed_modify_power, client->currentChannel), true); } } @@ -4844,7 +4844,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std: if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { - CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); + CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::i_client_music_needed_modify_power, client->currentChannel), true); } } @@ -4871,7 +4871,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std: index++; } while (index < str.length() && index != 0); if (badgesTags >= 2) { - if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_allow_invalid_badges, 1, this->currentChannel)) + if (!this->permissionGranted(permission::b_client_allow_invalid_badges, 1, this->currentChannel)) ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_badges, this->server ? this->server->serverAdmin : dynamic_pointer_cast(serverInstance->getInitialServerAdmin()), true); return command_result{error::parameter_invalid, "Invalid badges"}; } @@ -4879,27 +4879,27 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std: } else if(!self && key == "client_version") { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { - CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); + CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::i_client_music_needed_modify_power, client->currentChannel), true); } } else if(!self && key == "client_platform") { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { - CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); + CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::i_client_music_needed_modify_power, client->currentChannel), true); } } else if(!self && key == "client_country") { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { - CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); + CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::i_client_music_needed_modify_power, client->currentChannel), true); } } else if(!self && (*info == property::CLIENT_FLAG_NOTIFY_SONG_CHANGE/* || *info == property::CLIENT_NOTIFY_SONG_MESSAGE*/)) { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { - CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); + CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::i_client_music_needed_modify_power, client->currentChannel), true); } } else if(!self && key == "client_uptime_mode") { if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { - CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); + CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::i_client_music_needed_modify_power, client->currentChannel), true); } if(cmd[key].as() == MusicClient::UptimeMode::TIME_SINCE_SERVER_START) { @@ -5253,10 +5253,10 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) { return command_result{error::client_invalid_id}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); - PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); + PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); - auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); - bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, this->currentChannel); + auto maxValue = this->getPermissionGrantValue(permission::i_permission_modify_power, this->currentChannel); + bool ignoreGrant = this->permissionGranted(permission::b_permission_modify_power_ignore, 1, this->currentChannel); bool conOnError = cmd[0].has("continueonerror"); auto update_channels = false; for (int index = 0; index < cmd.bulkCount(); index++) { @@ -5266,7 +5266,7 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) { if(permission_require_granted_value(permType) && val > maxValue) return command_result{permission::i_permission_modify_power}; - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) return command_result{permission::i_permission_modify_power}; if (grant) { @@ -5299,16 +5299,16 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) { if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) return command_result{error::client_invalid_id}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); - PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); + PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); - bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, this->currentChannel); + bool ignoreGrant = this->permissionGranted(permission::b_permission_modify_power_ignore, 1, this->currentChannel); bool conOnError = cmd[0].has("continueonerror"); auto onlineClients = this->server->findClientsByCldbId(cmd["cldbid"]); auto update_channel = false; for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd) - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) return command_result{permission::i_permission_modify_power}; @@ -5411,19 +5411,19 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) return command_result{error::parameter_invalid, "Invalid manager db id"}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); - PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); + PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); RESOLVE_CHANNEL_R(cmd["cid"], true); auto channel = dynamic_pointer_cast(l_channel->entry); if(!channel) return command_result{error::vs_critical}; - bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, this->currentChannel); + bool ignoreGrant = this->permissionGranted(permission::b_permission_modify_power_ignore, 1, this->currentChannel); bool conOnError = cmd[0].has("continueonerror"), update_view = false; auto cll = this->server->findClientsByCldbId(cldbid); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) return command_result{permission::i_permission_modify_power}; if (grant) { @@ -5479,11 +5479,11 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) if(!channel) return command_result{error::vs_critical}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); - PERM_CHECK_CHANNELR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, channel), channel, true); + PERM_CHECK_CHANNELR(permission::i_client_permission_modify_power, this->server->calculatePermission(cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, channel), channel, true); - auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); - bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, this->currentChannel); + auto maxValue = this->getPermissionGrantValue(permission::i_permission_modify_power, this->currentChannel); + bool ignoreGrant = this->permissionGranted(permission::b_permission_modify_power_ignore, 1, this->currentChannel); bool conOnError = cmd[0].has("continueonerror"); auto onlineClientInstances = this->server->findClientsByCldbId(cldbid); bool update_view = false; @@ -5494,7 +5494,7 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) if(permission_require_granted_value(permType) && val > maxValue) return command_result{permission::i_permission_modify_power}; - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) return command_result{permission::i_permission_modify_power}; @@ -5872,7 +5872,7 @@ command_result ConnectedClient::handleCommandPermGet(Command &cmd) { } int index = 0; - for(const auto& entry : this->permissionValues(permission::PERMTEST_ORDERED, requrested, this->currentChannel)) { + for(const auto& entry : this->permissionValues(requrested, this->currentChannel)) { res[index]["permsid"] = permission_mapper->permission_name(type, entry.first);; res[index]["permid"] = entry.first; res[index++]["permvalue"] = entry.second; @@ -6374,7 +6374,7 @@ command_result ConnectedClient::handleCommandComplainAdd(Command &cmd) { auto cl = this->server->findClientsByCldbId(target); if (cl.empty()) return command_result{error::client_invalid_id}; - PERM_CHECKR(permission::i_client_complain_power, cl[0]->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_complain_power), true); + PERM_CHECKR(permission::i_client_complain_power, cl[0]->permissionValue(permission::i_client_needed_complain_power), true); /* if(!serverInstance->databaseHelper()->validClientDatabaseId(target)) @@ -6488,7 +6488,7 @@ command_result ConnectedClient::handleCommandMusicBotCreate(Command& cmd) { } - auto permissions_list = this->permissionValues(permission::PERMTEST_ORDERED, { + auto permissions_list = this->permissionValues({ permission::i_client_music_limit, permission::b_client_music_create_permanent, permission::b_client_music_create_semi_permanent, @@ -6543,7 +6543,7 @@ command_result ConnectedClient::handleCommandMusicBotCreate(Command& cmd) { if(cmd[0].has("cid")) return command_result{error::channel_invalid_id}; } else { CHANNEL_PERMISSION_TEST(permission::i_channel_description_view_power, permission::i_channel_needed_description_view_power, channel, false); - auto permission_granted = this->calculate_permission_value(permission::i_channel_join_power, channel->channelId()); + auto permission_granted = this->calculate_permission(permission::i_channel_join_power, channel->channelId()); if(!channel->permission_granted(permission::i_channel_needed_join_power, permission_granted, false)) channel = nullptr; } @@ -6600,7 +6600,7 @@ command_result ConnectedClient::handleCommandMusicBotDelete(Command& cmd) { auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return command_result{error::music_invalid_id}; - bool permPower = this->permissionGranted(permission::PERMTEST_ORDERED, permission::i_client_music_delete_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_delete_power)); + bool permPower = this->permissionGranted(permission::i_client_music_delete_power, bot->permissionValue(permission::i_client_music_needed_delete_power)); if(bot->getOwner() != this->getClientDatabaseId()) { if(!permPower) return command_result{permission::i_client_music_delete_power}; } @@ -6697,7 +6697,7 @@ command_result ConnectedClient::handleCommandMusicBotPlayerAction(Command& cmd) 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_play_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); + PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); if(cmd["action"] == 0) { bot->stopMusic(); @@ -6963,7 +6963,7 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) { if(permission_require_granted_value(permType) && val > maxValue) return command_result{permission::i_permission_modify_power}; - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) return command_result{permission::i_permission_modify_power}; if (grant) { @@ -6994,7 +6994,7 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) { for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); - if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) + if(!ignoreGrant && !this->permissionGrantGranted(permType, 1, this->currentChannel)) return command_result{permission::i_permission_modify_power}; if (grant) { @@ -7128,7 +7128,7 @@ command_result ConnectedClient::handleCommandMusicBotQueueList(Command& cmd) { 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::PERMTEST_ORDERED, permission::i_client_music_needed_info, bot->currentChannel), this->currentChannel, true); + PERM_CHECK_CHANNELR(permission::i_client_music_info, bot->permissionValue(permission::i_client_music_needed_info, bot->currentChannel), this->currentChannel, true); bool bulked = cmd.hasParm("bulk") || cmd.hasParm("balk") || cmd.hasParm("pipe") || cmd.hasParm("bar") || cmd.hasParm("paypal"); @@ -7209,7 +7209,7 @@ command_result ConnectedClient::handleCommandMusicBotQueueAdd(Command& cmd) { 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_play_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); + PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); MusicClient::loader_t loader; auto& type = cmd[0]["type"]; @@ -7245,7 +7245,7 @@ command_result ConnectedClient::handleCommandMusicBotQueueRemove(Command& cmd) { 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_play_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); + PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); std::deque> songs; for(int index = 0; index < cmd.bulkCount(); index++) { @@ -7277,7 +7277,7 @@ command_result ConnectedClient::handleCommandMusicBotQueueReorder(Command& cmd) 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_play_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); + PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); auto entry = bot->queue()->find_queue(cmd["song_id"]); if(!entry) return command_result{error::database_empty_result}; @@ -7299,7 +7299,7 @@ command_result ConnectedClient::handleCommandMusicBotPlaylistAssign(ts::Command auto bot = ref_server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return command_result{error::music_invalid_id}; if(bot->getOwner() != this->getClientDatabaseId()) - PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); + PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist && cmd["playlist_id"] != 0) return command_result{error::playlist_invalid_id}; @@ -7530,8 +7530,8 @@ command_result ConnectedClient::handleCommandQueryList(ts::Command &cmd) { if(!server && server_id != EmptyServerId && server_id != 0) return command_result{error::server_invalid_id}; - auto global_list = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_list, 1, nullptr, true, nullptr, server, true); - auto own_list = global_list || this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_list_own, 1, nullptr, true, nullptr, server, true); + auto global_list = this->permissionGranted(permission::b_client_query_list, 1, nullptr, true, nullptr, server, true); + auto own_list = global_list || this->permissionGranted(permission::b_client_query_list_own, 1, nullptr, true, nullptr, server, true); if(!own_list && !global_list) return command_result{permission::b_client_query_list}; @@ -7575,7 +7575,7 @@ command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) { if(!server && server_id != EmptyServerId && server_id != 0) return command_result{error::server_invalid_id}; - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_create, 1, nullptr, true, nullptr, server, true)) + if(!this->permissionGranted(permission::b_client_query_create, 1, nullptr, true, nullptr, server, true)) return command_result{permission::b_client_query_create}; auto username = cmd["client_login_name"].as(); @@ -7613,8 +7613,8 @@ command_result ConnectedClient::handleCommandQueryDelete(ts::Command &cmd) { return command_result{error::server_invalid_id}; */ - auto delete_all = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_delete, 1, nullptr, true, nullptr, server, true); - auto delete_own = delete_all || this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_delete_own, 1, nullptr, true, nullptr, server, true); + auto delete_all = this->permissionGranted(permission::b_client_query_delete, 1, nullptr, true, nullptr, server, true); + auto delete_own = delete_all || this->permissionGranted(permission::b_client_query_delete_own, 1, nullptr, true, nullptr, server, true); if(account->unique_id == this->getUid()) { if(!delete_own) @@ -7643,8 +7643,8 @@ command_result ConnectedClient::handleCommandQueryRename(ts::Command &cmd) { if(!server && account->bound_server != 0) return command_result{error::server_invalid_id}; - auto rename_all = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_rename, 1, nullptr, true, nullptr, server, true); - auto rename_own = rename_all || this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_rename_own, 1, nullptr, true, nullptr, server, true); + auto rename_all = this->permissionGranted(permission::b_client_query_rename, 1, nullptr, true, nullptr, server, true); + auto rename_own = rename_all || this->permissionGranted(permission::b_client_query_rename_own, 1, nullptr, true, nullptr, server, true); if(account->unique_id == this->getUid()) { if(!rename_own) @@ -7676,8 +7676,8 @@ command_result ConnectedClient::handleCommandQueryChangePassword(ts::Command &cm if(!server && account->bound_server != 0) return command_result{error::server_invalid_id}; - auto change_all = this->permissionGranted(permission::PERMTEST_ORDERED, server ? permission::b_client_query_change_password : permission::b_client_query_change_password_global, 1, nullptr, true, nullptr, server, true); - auto change_own = change_all || this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_change_own_password, 1, nullptr, true, nullptr, server, true); + auto change_all = this->permissionGranted(server ? permission::b_client_query_change_password : permission::b_client_query_change_password_global, 1, nullptr, true, nullptr, server, true); + auto change_own = change_all || this->permissionGranted(permission::b_client_query_change_own_password, 1, nullptr, true, nullptr, server, true); auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as() : ""; @@ -7707,7 +7707,7 @@ command_result ConnectedClient::handleCommandDummy_IpChange(ts::Command &cmd) { CMD_REF_SERVER(server); logMessage(this->getServerId(), "[{}] Address changed from {} to {}", CLIENT_STR_LOG_PREFIX, cmd["old_ip"].string(), cmd["new_ip"].string()); - if(geoloc::provider_vpn && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_ignore_vpn, 1)) { + if(geoloc::provider_vpn && !this->permissionGranted(permission::b_client_ignore_vpn, 1)) { auto provider = this->isAddressV4() ? geoloc::provider_vpn->resolveInfoV4(this->getPeerIp(), true) : geoloc::provider_vpn->resolveInfoV6(this->getPeerIp(), true); if(provider) { this->disconnect(strvar::transform(ts::config::messages::kick_vpn, strvar::StringValue{"provider.name", provider->name}, strvar::StringValue{"provider.website", provider->side})); @@ -7764,10 +7764,10 @@ command_result ConnectedClient::handleCommandConversationHistory(ts::Command &co command[0]["cpw"] = ""; if (!channel->passwordMatch(command["cpw"], true)) - if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_ignore_password, 1, channel, true)) + if (!this->permissionGranted(permission::b_channel_join_ignore_password, 1, channel, true)) return command_result{error::channel_invalid_password, "invalid password"}; - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_join_power, 1, channel, true)) { + if(!this->permissionGranted(permission::b_channel_ignore_join_power, 1, channel, true)) { CHANNEL_PERMISSION_TEST(permission::i_channel_join_power, permission::i_channel_needed_join_power, channel, false); } } @@ -7894,15 +7894,15 @@ command_result ConnectedClient::handleCommandConversationFetch(ts::Command &cmd) bulk["cpw"] = ""; if (!channel->passwordMatch(bulk["cpw"], true)) - if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_ignore_password, 1, channel, true)) { + if (!this->permissionGranted(permission::b_channel_join_ignore_password, 1, channel, true)) { auto error = findError("channel_invalid_password"); result_bulk["error_id"] = error.errorId; result_bulk["error_msg"] = error.message; continue; } - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_join_power, 1, channel, true)) { - auto permission_granted = this->calculate_permission_value(permission::i_channel_join_power, channel->channelId()); + if(!this->permissionGranted(permission::b_channel_ignore_join_power, 1, channel, true)) { + auto permission_granted = this->calculate_permission(permission::i_channel_join_power, channel->channelId()); if(!channel->permission_granted(permission::i_channel_needed_join_power, permission_granted, false)) { auto error = findError("server_insufficeient_permissions"); result_bulk["error_id"] = error.errorId; @@ -7966,14 +7966,14 @@ command_result ConnectedClient::handleCommandConversationMessageDelete(ts::Comma bulk["cpw"] = ""; if (!channel->passwordMatch(bulk["cpw"], true)) - if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_ignore_password, 1, channel, true)) + if (!this->permissionGranted(permission::b_channel_join_ignore_password, 1, channel, true)) return command_result{error::channel_invalid_password}; - if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_conversation_message_delete, 1, channel)) + if (!this->permissionGranted(permission::b_channel_conversation_message_delete, 1, channel)) return command_result{permission::b_channel_conversation_message_delete}; - if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_join_power, 1, channel, true)) { - auto permission_granted = this->calculate_permission_value(permission::i_channel_join_power, channel->channelId()); + if(!this->permissionGranted(permission::b_channel_ignore_join_power, 1, channel, true)) { + auto permission_granted = this->calculate_permission(permission::i_channel_join_power, channel->channelId()); if(!channel->permission_granted(permission::i_channel_needed_join_power, permission_granted, false)) return command_result{permission::i_channel_needed_join_power}; } diff --git a/server/src/client/ConnectedClientNotifyHandler.cpp b/server/src/client/ConnectedClientNotifyHandler.cpp index bb0977d..8c34a1c 100644 --- a/server/src/client/ConnectedClientNotifyHandler.cpp +++ b/server/src/client/ConnectedClientNotifyHandler.cpp @@ -13,6 +13,7 @@ #include #include #include "./web/WebClient.h" +#include "query/command3.h" using namespace std::chrono; using namespace std; @@ -35,6 +36,18 @@ do { \ } \ } while(0) +#define INVOKER_NEW(command, invoker) \ +do { \ + if(invoker) { \ + command.put_unchecked(0, "invokerid", invoker->getClientId()); \ + command.put_unchecked(0, "invokername", invoker->getDisplayName()); \ + command.put_unchecked(0, "invokeruid", invoker->getUid()); \ + } else { \ + command.put_unchecked(0, "invokerid", "0"); \ + command.put_unchecked(0, "invokername", "undefined"); \ + command.put_unchecked(0, "invokeruid", "undefined"); \ + } \ +} while(0) bool ConnectedClient::notifyServerGroupList() { Command cmd(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyservergrouplist" : ""); @@ -65,30 +78,34 @@ bool ConnectedClient::notifyServerGroupList() { bool ConnectedClient::notifyGroupPermList(const std::shared_ptr& group, bool as_sid) { - Command cmd(this->getExternalType() == CLIENT_TEAMSPEAK ? group->target() == GROUPTARGET_SERVER ? "notifyservergrouppermlist" : "notifychannelgrouppermlist" : ""); + ts::command_builder result{this->getExternalType() == CLIENT_TEAMSPEAK ? group->target() == GROUPTARGET_SERVER ? "notifyservergrouppermlist" : "notifychannelgrouppermlist" : ""}; + if (group->target() == GROUPTARGET_SERVER) - cmd["sgid"] = group->groupId(); + result.put_unchecked(0, "sgid", group->groupId()); else - cmd["cgid"] = group->groupId(); + result.put_unchecked(0, "cgid", group->groupId()); int index = 0; auto permissions = group->permissions()->permissions(); auto permission_mapper = serverInstance->getPermissionMapper(); auto client_type = this->getType(); + + result.reserve_bulks(permissions.size() * 2); for (const auto &permission_data : permissions) { auto& permission = get<1>(permission_data); if(!permission.flags.value_set) continue; if(as_sid) { - cmd[index]["permsid"] = permission_mapper->permission_name(client_type, get<0>(permission_data)); + result.put_unchecked(index, "permsid", permission_mapper->permission_name(client_type, get<0>(permission_data))); } else { - cmd[index]["permid"] = (uint16_t) get<0>(permission_data); + result.put_unchecked(index, "permid", (uint16_t) get<0>(permission_data)); } - cmd[index]["permvalue"] = permission.values.value; - cmd[index]["permnegated"] = permission.flags.negate; - cmd[index]["permskip"] = permission.flags.skip; + + result.put_unchecked(index, "permvalue", permission.values.value); + result.put_unchecked(index, "permnegated", permission.flags.negate); + result.put_unchecked(index, "permskip", permission.flags.skip); index++; } for (const auto &permission_data : permissions) { @@ -96,21 +113,23 @@ bool ConnectedClient::notifyGroupPermList(const std::shared_ptr& group, b if(!permission.flags.grant_set) continue; + if(as_sid) { - cmd[index]["permsid"] = permission_mapper->permission_name_grant(client_type, get<0>(permission_data)); + result.put_unchecked(index, "permsid", permission_mapper->permission_name_grant(client_type, get<0>(permission_data))); } else { - cmd[index]["permid"] = (uint16_t) (get<0>(permission_data) | PERM_ID_GRANT); + result.put_unchecked(index, "permid", (uint16_t) (get<0>(permission_data) | PERM_ID_GRANT)); } - cmd[index]["permvalue"] = permission.values.grant; - cmd[index]["permnegated"] = permission.flags.negate; - cmd[index]["permskip"] = permission.flags.skip; + + result.put_unchecked(index, "permvalue", permission.values.value); + result.put_unchecked(index, "permnegated", permission.flags.negate); + result.put_unchecked(index, "permskip", permission.flags.skip); index++; } if (index == 0) return false; - this->sendCommand(cmd); + this->sendCommand(result); //Need hack return true; } @@ -341,7 +360,7 @@ bool ConnectedClient::notifyConnectionInfo(const shared_ptr &ta } } - if(target->getClientId() == this->getClientId() || this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_remoteaddress_view, 1, target->currentChannel, true)) { + if(target->getClientId() == this->getClientId() || permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, this->getChannelId()))) { notify["connection_client_ip"] = target->getLoggingPeerIp(); notify["connection_client_port"] = target->getPeerPort(); } @@ -569,29 +588,29 @@ bool ConnectedClient::notifyClientEnterView(const std::shared_ptrchannelId() : 0; - cmd["ctid"] = to ? to->channelId() : 0; - cmd["reasonid"] = reasonId; - INVOKER(cmd, invoker); + builder.put_unchecked(0, "cfid", from ? from->channelId() : 0); + builder.put_unchecked(0, "ctid", to ? to->channelId() : 0); + builder.put_unchecked(0, "reasonid", reasonId); + INVOKER_NEW(builder, invoker); switch (reasonId) { case ViewReasonId::VREASON_MOVED: case ViewReasonId::VREASON_BAN: case ViewReasonId::VREASON_CHANNEL_KICK: case ViewReasonId::VREASON_SERVER_KICK: - cmd["reasonmsg"] = reason; + builder.put_unchecked(0, "reasonmsg", reason); break; default: break; } for (const auto &elm : client->properties()->list_properties(property::FLAG_CLIENT_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { - cmd[elm.type().name] = elm.value(); + builder.put_unchecked(0, elm.type().name, elm.value()); } visibleClients.emplace_back(client); - this->sendCommand(cmd); + this->sendCommand(builder); return true; } diff --git a/server/src/client/ConnectedClientTextCommandHandler.cpp b/server/src/client/ConnectedClientTextCommandHandler.cpp index b8cc42a..f2e0f40 100644 --- a/server/src/client/ConnectedClientTextCommandHandler.cpp +++ b/server/src/client/ConnectedClientTextCommandHandler.cpp @@ -66,8 +66,8 @@ inline string filterUrl(string in){ } inline void permissionableCommand(ConnectedClient* client, stringstream& ss, const std::string& cmd, permission::PermissionType perm, permission::PermissionType sec = permission::unknown) { - auto permA = perm == permission::unknown || client->permissionGranted(permission::PERMTEST_ORDERED, perm, 1, client->getChannel(), true); - auto permB = permA || (sec != permission::unknown && client->permissionGranted(permission::PERMTEST_ORDERED, sec, 1, client->getChannel(), true)); + auto permA = perm == permission::unknown || permission::v2::permission_granted(1, client->calculate_permission(perm, client->getChannelId())); + auto permB = permA || (sec != permission::unknown && permission::v2::permission_granted(1, client->calculate_permission(sec, client->getChannelId()))); if(!(permA || permB)) { ss << "[color=red]" << cmd << "[/color]" << endl; } else { @@ -98,7 +98,8 @@ bool ConnectedClient::handleTextMessage(ChatMessageMode mode, std::string text, #define PERM_CHECK_BOT(perm, reqperm, err) \ - if(bot->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId() && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::perm, this->permissionValue(permission::PERMTEST_ORDERED, permission::reqperm, this->currentChannel), this->currentChannel, true)) { \ + if(bot->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId() && \ + !permission::v2::permission_granted(bot->calculate_permission(permission::reqperm, bot->getChannelId()), this->calculate_permission(permission::perm, bot->getChannelId()))) { \ send_message(serverInstance->musicRoot(), err); \ return true; \ } @@ -170,7 +171,7 @@ bool ConnectedClient::handle_text_command( server = true; string locationStr = server ? "on this server" : "in this channel"; - if(!this->permissionGranted(permission::PERMTEST_ORDERED, server ? permission::b_client_music_server_list : permission::b_client_music_channel_list, 1, this->currentChannel, true)) { + if(!permission::v2::permission_granted(1, this->calculate_permission(server ? permission::b_client_music_server_list : permission::b_client_music_channel_list, this->getChannelId()))) { send_message(serverInstance->musicRoot(), "You don't have the permission to list all music bots " + locationStr); return true; } @@ -197,7 +198,9 @@ bool ConnectedClient::handle_text_command( auto botId = static_cast(stoll(arguments[1])); auto bot = this->server->musicManager->findBotById(botId); if (!bot) ERR(serverInstance->musicRoot(), "Could not find target bot"); - if(bot->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId() && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_music_channel_list, 1, this->currentChannel, true) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_music_server_list, 1, this->currentChannel, true)) { //No perms for listing + if(bot->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId() && + !permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_music_channel_list, this->getChannelId())) && + !permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_music_server_list, this->getChannelId()))) { //No perms for listing send_message(serverInstance->musicRoot(), "You don't have the permission to select a music bot"); return true; } @@ -430,9 +433,9 @@ bool ConnectedClient::handle_text_command( return true; } - auto max_volume = this->cached_permission_value(permission::i_client_music_create_modify_max_volume); - if(max_volume != permNotGranted && !this->permission_granted(max_volume, volume, true)) { - send_message(bot, "You don't have the permission to use higher volumes that " + to_string(max_volume) + "%"); + auto max_volume = this->calculate_permission(permission::i_client_music_create_modify_max_volume, 0); + if(max_volume.has_value && !permission::v2::permission_granted(volume, max_volume)) { + send_message(bot, "You don't have the permission to use higher volumes that " + to_string(max_volume.value) + "%"); return true; } @@ -567,8 +570,8 @@ bool ConnectedClient::handle_text_command( stringstream ss; ss << "Available music bot commands: ([color=green]green[/color] = permission granted | [color=red]red[/color] = insufficient permissions)" << endl; - bool has_list_server = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_music_server_list, 1, this->currentChannel); - bool has_list_channel = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_music_channel_list, 1, this->currentChannel); + bool has_list_server = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_music_server_list, this->getChannelId())); + bool has_list_channel = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_music_channel_list, this->getChannelId())); permissionableCommand(this, ss, string() + " .mbot list [<[color=" + (has_list_server ? "green" : "red") + "]server[/color]|[color=" + (has_list_channel ? "green" : "red") + "]channel[/color]>]", permission::b_client_music_channel_list, permission::b_client_music_server_list); permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot select ", permission::b_client_music_channel_list, permission::b_client_music_server_list); permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot formats", permission::unknown); diff --git a/server/src/client/DataClient.cpp b/server/src/client/DataClient.cpp index ca02344..fea2240 100644 --- a/server/src/client/DataClient.cpp +++ b/server/src/client/DataClient.cpp @@ -130,126 +130,32 @@ bool DataClient::loadDataForCurrentServer() { //TODO for query return true; } -permission::v2::PermissionFlaggedValue DataClient::permissionValueFlagged(permission::PermissionType type, const std::shared_ptr& target, std::shared_ptr cache, std::shared_ptr server, bool server_defined) { - if(!server_defined && !server) - server = this->server; +std::vector> DataClient::calculate_permissions( + const std::deque &permissions, + ChannelId channel, + bool granted, + std::shared_ptr cache) { + if(permissions.empty()) return {}; - if(server) { - auto result = server->calculatePermissions2(this->getClientDatabaseId(), {type}, this->getType(), target ? target->channelId() : 0, false, cache); - if(result.empty() || result[0].first != type) - return permission::v2::empty_permission_flagged_value; - return result[0].second; - } else { - /* if you're not bound to a channel then you cant be bound to a server as well. */ - bool permission_set = false; - permission::PermissionValue result = permNotGranted; - for(const auto &gr : serverInstance->getGroupManager()->getServerGroups(this->getClientDatabaseId(), this->getType())){ - auto group_permissions = gr->group->permissions(); - auto flagged_permissions = group_permissions->permission_value_flagged(type); - if(flagged_permissions.has_value) { - if(flagged_permissions.value > result || flagged_permissions.value == -1) { - result = flagged_permissions.value; - permission_set = true; - } - } - } - return {result, permission_set}; - } + if(!cache) + cache = std::make_shared(); + if(!cache->client_permissions) /* so we don't have to load that shit later */ + cache->client_permissions = this->clientPermissions; + if(channel == -1) + channel = this->currentChannel ? this->currentChannel->channelId() : 0; + + auto ref_server = this->server; + if(ref_server) + return ref_server->calculate_permissions(permissions, this->getClientDatabaseId(), this->getType(), channel, granted, cache); + else + return serverInstance->calculate_permissions(permissions, this->getClientDatabaseId(), this->getType(), channel, granted, cache); } -permission::PermissionValue DataClient::permissionValue(permission::PermissionTestType test, permission::PermissionType type, const std::shared_ptr& target, std::shared_ptr cache, std::shared_ptr server, bool server_defined) { - auto result = this->permissionValueFlagged(type, target, cache, server, server_defined); - if(result.has_value) - return result.value; - return permNotGranted; -} - -std::deque> DataClient::permissionValues(permission::PermissionTestType test, const std::deque& permissions, const std::shared_ptr &channel, std::shared_ptr cache, std::shared_ptr server, bool server_defined) { - if(!server_defined && !server) server = this->server; - - if(server) return server->calculatePermissions(test, this->getClientDatabaseId(), permissions, this->getType(), channel, cache); - deque> result; - - /* when you're not bound to any channel cou could only have server group permissions */ - /* we're loading here all server groups */ - { - auto server_groups = serverInstance->getGroupManager()->getServerGroups(this->getClientDatabaseId(), this->getType()); - for(const auto& permission : permissions) { - permission::PermissionValue value = permNotGranted; - - for(const auto &gr : serverInstance->getGroupManager()->getServerGroups(this->getClientDatabaseId(), this->getType())){ - auto group_permissions = gr->group->permissions(); - auto flagged_permissions = group_permissions->permission_value_flagged(permission); - if(flagged_permissions.has_value) - if(flagged_permissions.value > value || flagged_permissions.value == -1) value = flagged_permissions.value; - } - - result.emplace_back(permission, value); - } - } - - return result; -} - -permission::PermissionValue DataClient::getPermissionGrantValue(permission::PermissionTestType test, permission::PermissionType type, const std::shared_ptr& target) { - permission::PermissionValue result = permNotGranted; - - if(this->server) { - return this->server->calculatePermissionGrant(test, this->getClientDatabaseId(), type, this->getType(), target); - } else { - /* if you're not bound to a channel then you cant be bound to a server as well. */ - //TODO: Implement negate flag! - for(const auto &gr : serverInstance->getGroupManager()->getServerGroups(this->getClientDatabaseId(), this->getType())){ - auto group_permissions = gr->group->permissions(); - auto flagged_permissions = group_permissions->permission_granted_flagged(type); - if(flagged_permissions.has_value) - if(flagged_permissions.value > result || flagged_permissions.value == -1) result = flagged_permissions.value; - } - } - - return result; -} - -bool DataClient::permissionGranted(PermissionTestType test, permission::PermissionType type, permission::PermissionValue required, const shared_ptr& target, bool force_granted, std::shared_ptr cache, std::shared_ptr server, bool server_defined) { - auto value = this->permissionValue(test, type, target, cache, server, server_defined); - bool result = this->permission_granted(value, required, force_granted); - -#ifdef DEBUG_PERMISSION - { - auto serverId = 0; - auto client = dynamic_cast(this); - if(client) - serverId = client->getServerId(); - debugMessage(serverId, "[Permission] Value test result for test type {}.", test); - debugMessage(serverId, - "[Permission] Permission: {} Required value: {} Gained value: {} Force required: {} Channel: {} Result: {}", - permission::resolvePermissionData(type)->name, - required, value, force_granted, (target ? target->name() : "none"), result - ); - }; -#endif - return result; -} - -bool DataClient::permissionGrantGranted(PermissionTestType test, permission::PermissionType type, permission::PermissionValue required, const shared_ptr& target, bool force_granted) { - auto value = this->getPermissionGrantValue(test, type, target); - bool result = this->permission_granted(value, required, force_granted); - -#ifdef DEBUG_PERMISSION - { - auto serverId = 0; - auto client = dynamic_cast(this); - if(client) - serverId = client->getServerId(); - debugMessage(serverId, "[Permission] Grant test result for test type {}.", test); - debugMessage(serverId, - "[Permission] Permission: {} Required value: {} Gained value: {} Force required: {} Channel: {} Result: {}", - permission::resolvePermissionData(type)->name, - required, value, force_granted, (target ? target->name() : "none"), result - ); - }; -#endif - return result; +permission::v2::PermissionFlaggedValue DataClient::calculate_permission( + permission::PermissionType permission, ChannelId channel, bool granted, std::shared_ptr cache) { + auto result = this->calculate_permissions({permission}, channel, granted, cache); + if(result.empty()) return {0, false}; + return result.back().second; } std::vector> DataClient::assignedServerGroups() { @@ -278,4 +184,4 @@ bool DataClient::channelGroupAssigned(const shared_ptr &group, const shar std::string DataClient::getAvatarId() { return hex::hex(base64::validate(this->getUid()) ? base64::decode(this->getUid()) : this->getUid(), 'a', 'q'); -} \ No newline at end of file +} diff --git a/server/src/client/DataClient.h b/server/src/client/DataClient.h index d3e9e0b..e6e1d20 100644 --- a/server/src/client/DataClient.h +++ b/server/src/client/DataClient.h @@ -57,69 +57,20 @@ namespace ts { PropertyWrapper properties(){ return { this->_properties }; } - virtual std::deque> permissionValues(permission::PermissionTestType test, - const std::deque&, - const std::shared_ptr& channel = nullptr, - std::shared_ptr cache = nullptr, - std::shared_ptr server = nullptr, - bool server_defined = false); - inline permission::PermissionValue permissionValue(permission::PermissionType type, const std::shared_ptr& targetChannel = nullptr) { - return this->permissionValue(permission::PERMTEST_ORDERED, type, targetChannel); - } - - virtual permission::PermissionValue permissionValue(permission::PermissionTestType test, - permission::PermissionType, - const std::shared_ptr& channel = nullptr, - std::shared_ptr cache = nullptr, - std::shared_ptr server = nullptr, - bool server_defined = false); - - virtual permission::v2::PermissionFlaggedValue permissionValueFlagged( - permission::PermissionType, - const std::shared_ptr& channel = nullptr, - std::shared_ptr cache = nullptr, - std::shared_ptr server = nullptr, - bool server_defined = false + /* main permission calculate function */ + permission::v2::PermissionFlaggedValue calculate_permission( + permission::PermissionType, + ChannelId channel, + bool granted = false, + std::shared_ptr cache = nullptr ); - virtual bool permissionGranted(permission::PermissionTestType test, - permission::PermissionType, - permission::PermissionValue, - const std::shared_ptr& channel = nullptr, - bool required = true, - std::shared_ptr cache = nullptr, - std::shared_ptr server = nullptr, - bool server_defined = false); - - inline permission::PermissionValue getPermissionGrantValue(permission::PermissionType type, const std::shared_ptr& targetChannel = nullptr) { - return this->getPermissionGrantValue(permission::PERMTEST_ORDERED, type, targetChannel); - } - - virtual permission::PermissionValue getPermissionGrantValue(permission::PermissionTestType test, permission::PermissionType, const std::shared_ptr& targetChannel = nullptr); - virtual bool permissionGrantGranted(permission::PermissionTestType test, permission::PermissionType, permission::PermissionValue, const std::shared_ptr& targetChannel = nullptr, bool required = true); - - template - inline bool permission_granted(T, permission::PermissionValue, bool = false) = delete; /* only permission values */ - [[deprecated]] __always_inline bool permission_granted(ts::permission::PermissionValue value, ts::permission::PermissionValue required, bool enforce_required = true) { - return DataClient::permission_granted({value, value != permNotGranted}, required, enforce_required); - } - - static inline bool permission_granted(ts::permission::v2::PermissionFlaggedValue value, ts::permission::PermissionValue required, bool enforce_required = true) { - if(required == permNotGranted || required == 0) { - if(enforce_required) - return value.value == -1 || value.value > 0; - else - return true; - } else if(!value.has_value) { - return false; - } else { - if(value.value == -1) - return true; - else if(value.value >= required) - return true; - } - return false; - } + std::vector> calculate_permissions( + const std::deque&, + ChannelId channel, + bool granted = false, + std::shared_ptr cache = nullptr + ); virtual std::vector> assignedServerGroups(); virtual std::shared_ptr assignedChannelGroup(const std::shared_ptr &); @@ -153,6 +104,8 @@ namespace ts { std::shared_ptr clientPermissions = nullptr; std::shared_ptr _properties; + + std::shared_ptr currentChannel = nullptr; }; } } \ No newline at end of file diff --git a/server/src/client/InternalClient.cpp b/server/src/client/InternalClient.cpp index d3eecb3..8efa87d 100644 --- a/server/src/client/InternalClient.cpp +++ b/server/src/client/InternalClient.cpp @@ -22,9 +22,8 @@ InternalClient::~InternalClient() { memtrack::freed(this); } -void InternalClient::sendCommand(const ts::Command &command, bool low) { - -} +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) { logError(this->getServerId(), "Internal client is force to disconnect?"); diff --git a/server/src/client/InternalClient.h b/server/src/client/InternalClient.h index d2ce769..d95387d 100644 --- a/server/src/client/InternalClient.h +++ b/server/src/client/InternalClient.h @@ -15,6 +15,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 disconnect(const std::string &reason) override; protected: diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index 3570fd8..55b15fb 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -39,12 +39,7 @@ bool SpeakingClient::shouldReceiveVoiceWhisper(const std::shared_ptrshouldReceiveVoice(sender)) return false; - auto required_permission = this->cached_permission_value(permission::i_client_needed_whisper_power); - if(required_permission == permNotGranted) - return true; - - auto granted_permission = sender->cached_permission_value(permission::i_client_whisper_power); - return granted_permission >= required_permission; + return permission::v2::permission_granted(this->cpmerission_needed_whisper_power, sender->cpmerission_whisper_power); } void SpeakingClient::handlePacketVoice(const pipes::buffer_view& data, bool head, bool fragmented) { @@ -445,7 +440,7 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) { debugMessage(this->getServerId(), "{} Got client init. (HWID: {})", CLIENT_STR_LOG_PREFIX, this->getHardwareId()); TIMING_STEP(timings, "props apply"); - auto permissions_list = this->permissionValues(permission::PERMTEST_ORDERED, { + auto permissions_list = this->calculate_permissions({ permission::b_virtualserver_join_ignore_password, permission::b_client_ignore_bans, permission::b_client_ignore_vpn, @@ -457,17 +452,17 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) { permission::b_client_enforce_valid_hwid, permission::b_client_use_reserved_slot - }, nullptr); - auto permissions = map(permissions_list.begin(), permissions_list.end()); + }, 0); + auto permissions = map(permissions_list.begin(), permissions_list.end()); TIMING_STEP(timings, "perm calc 1"); - if(geoloc::provider_vpn && permissions[permission::b_client_ignore_vpn] == permNotGranted) { + if(geoloc::provider_vpn && !permission::v2::permission_granted(1, permissions[permission::b_client_ignore_vpn])) { auto provider = this->isAddressV4() ? geoloc::provider_vpn->resolveInfoV4(this->getPeerIp(), true) : geoloc::provider_vpn->resolveInfoV6(this->getPeerIp(), true); if(provider) return command_result{error::server_connect_banned, strvar::transform(ts::config::messages::kick_vpn, strvar::StringValue{"provider.name", provider->name}, strvar::StringValue{"provider.website", provider->side})}; } - if(this->getType() == ClientType::CLIENT_TEAMSPEAK && (permissions[permission::b_client_enforce_valid_hwid] == permNotGranted && permissions[permission::b_client_enforce_valid_hwid] == 0)) { + if(this->getType() == ClientType::CLIENT_TEAMSPEAK && permission::v2::permission_granted(1, permissions[permission::b_client_enforce_valid_hwid])) { auto hwid = this->properties()[property::CLIENT_HARDWARE_ID].as(); if( !std::regex_match(hwid, regex_hwid_windows) && @@ -480,8 +475,7 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) { } TIMING_STEP(timings, "valid hw ip"); - auto ignorePassword = permissions[permission::b_virtualserver_join_ignore_password] > 0 && permissions[permission::b_virtualserver_join_ignore_password] != permNotGranted; - if(!ignorePassword) + if(!permission::v2::permission_granted(1, permissions[permission::b_virtualserver_join_ignore_password])) if(!this->server->verifyServerPassword(cmd["client_server_password"].string(), true)) return command_result{error::server_invalid_password}; @@ -500,17 +494,17 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) { clones_hwid++; }); - if(permissions[permission::i_client_max_clones_uid] > 0 && clones_uid >= permissions[permission::i_client_max_clones_uid]) { + if(clones_uid > 0 && permissions[permission::i_client_max_clones_uid].has_value && !permission::v2::permission_granted(clones_uid, permissions[permission::i_client_max_clones_uid])) { logMessage(this->getServerId(), "{} Disconnecting because there are already {} uid clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_uid, permissions[permission::i_client_max_clones_uid]); return command_result{error:: client_too_many_clones_connected, "too many clones connected (uid)"}; } - if(permissions[permission::i_client_max_clones_ip] > 0 && clones_ip >= permissions[permission::i_client_max_clones_ip]) { + if(clones_ip > 0 && permissions[permission::i_client_max_clones_ip].has_value && !permission::v2::permission_granted(clones_ip, permissions[permission::i_client_max_clones_ip])) { logMessage(this->getServerId(), "{} Disconnecting because there are already {} ip clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_ip, permissions[permission::i_client_max_clones_ip]); return command_result{error:: client_too_many_clones_connected, "too many clones connected (ip)"}; } - if(permissions[permission::i_client_max_clones_hwid] > 0 && clones_hwid >= permissions[permission::i_client_max_clones_hwid] && !_own_hwid.empty()) { + if(clones_hwid > 0 && permissions[permission::i_client_max_clones_hwid].has_value && !permission::v2::permission_granted(clones_hwid, permissions[permission::i_client_max_clones_hwid])) { logMessage(this->getServerId(), "{} Disconnecting because there are already {} hwid clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_hwid, permissions[permission::i_client_max_clones_hwid]); return command_result{error:: client_too_many_clones_connected, "too many clones connected (hwid)"}; } @@ -581,7 +575,7 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) { auto maxClients = this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as(); auto reserved = this->server->properties()[property::VIRTUALSERVER_RESERVED_SLOTS].as(); - bool allowReserved = permissions[permission::b_client_use_reserved_slot] != permNotGranted && permissions[permission::b_client_use_reserved_slot] != 0; + bool allowReserved = permission::v2::permission_granted(1, permissions[permission::b_client_use_reserved_slot]); if(reserved > maxClients){ if(!allowReserved) return command_result{error::server_maxclients_reached}; @@ -782,7 +776,7 @@ void SpeakingClient::tick(const std::chrono::system_clock::time_point &time) { void SpeakingClient::updateChannelClientProperties(bool channel_lock, bool notify) { ConnectedClient::updateChannelClientProperties(channel_lock, notify); - this->max_idle_time = this->permissionValueFlagged(permission::i_client_max_idletime, this->currentChannel); + this->max_idle_time = this->calculate_permission(permission::i_client_max_idletime, this->currentChannel ? this->currentChannel->channelId() : 0); } command_result SpeakingClient::handleCommand(Command &command) { diff --git a/server/src/client/command_handler/channel.cpp b/server/src/client/command_handler/channel.cpp new file mode 100644 index 0000000..275484f --- /dev/null +++ b/server/src/client/command_handler/channel.cpp @@ -0,0 +1,1900 @@ +#include + +#include +#include +#include "../../build.h" +#include "../ConnectedClient.h" +#include "../InternalClient.h" +#include "../../server/file/FileServer.h" +#include "../../server/VoiceServer.h" +#include "../voice/VoiceClient.h" +#include "PermissionManager.h" +#include "../../InstanceHandler.h" +#include "../../server/QueryServer.h" +#include "../file/FileClient.h" +#include "../music/MusicClient.h" +#include "../query/QueryClient.h" +#include "../../weblist/WebListManager.h" +#include "../../manager/ConversationManager.h" +#include "../../manager/PermissionNameMapper.h" +#include + +#include "helpers.h" + +#include +#include +#include +#include +#include +#include + +using namespace std::chrono; +using namespace std; +using namespace ts; +using namespace ts::server; +using namespace ts::token; + +//{findError("parameter_invalid"), "could not resolve permission " + (cmd[index].has("permid") ? cmd[index]["permid"].as() : cmd[index]["permsid"].as())}; \ +//TODO: Log missing permissions? + +command_result ConnectedClient::handleCommandChannelGetDescription(Command &cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(0); + RESOLVE_CHANNEL_R(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + assert(channel); + + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_description_view_power, channel->channelId()))) { + auto view_power = this->calculate_permission(permission::i_channel_needed_description_view_power, channel->channelId()); + if(!channel->permission_granted(permission::i_channel_description_view_power, view_power, false)) + command_result{permission::i_channel_description_view_power}; + } + + this->sendChannelDescription(channel, true); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelSubscribe(Command &cmd) { + CMD_REF_SERVER(ref_server); + CMD_RESET_IDLE; + + bool flood_points = false; + deque> channels; + + { + shared_lock server_channel_lock(this->server->channel_tree_lock); + unique_lock client_channel_lock(this->channel_lock); + + for (int index = 0; index < cmd.bulkCount(); index++) { + auto local_channel = this->channel_view()->find_channel(cmd[index]["cid"].as()); + if(!local_channel) + return command_result{error::channel_invalid_id, "Cant resolve channel"}; + + auto channel = this->server->channelTree->findChannel(cmd[index]["cid"].as()); + if (!channel) + return command_result{error::channel_invalid_id, "Cant resolve channel"}; + + channels.push_back(channel); + if(!flood_points && system_clock::now() - local_channel->view_timestamp > seconds(5)) { + flood_points = true; + CMD_CHK_AND_INC_FLOOD_POINTS(15); + } + } + + if(!channels.empty()) + this->subscribeChannel(channels, false, false); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelSubscribeAll(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(20); + + { + shared_lock server_channel_lock(this->server->channel_tree_lock); + unique_lock client_channel_lock(this->channel_lock); + this->subscribeChannel(this->server->channelTree->channels(), false, false); + this->subscribeToAll = true; + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelUnsubscribe(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + { + shared_lock server_channel_lock(this->server->channel_tree_lock); + unique_lock client_channel_lock(this->channel_lock); + + deque> channels; + for(int index = 0; index < cmd.bulkCount(); index++) { + auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); + if(!channel) continue; + channels.push_front(channel); + } + + this->unsubscribeChannel(channels, false); + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelUnsubscribeAll(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + { + shared_lock server_channel_lock(this->server->channel_tree_lock); + unique_lock client_channel_lock(this->channel_lock); + this->unsubscribeChannel(this->server->channelTree->channels(), false); + this->subscribeToAll = false; + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelGroupAdd(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_create, 1); + + auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); + if(cmd["type"].as() == GroupType::GROUP_TYPE_NORMAL && !this->server) return command_result{error::parameter_invalid, "You cant create normal channel groups on the template server"}; + if(cmd["name"].string().empty()) return command_result{error::parameter_invalid, "invalid group name"}; + for(const auto& gr : group_manager->availableServerGroups(true)) + if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "Group already exists"}; + auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_CHANNEL, cmd["type"].as(), cmd["name"].string()); + if (group) { + group->permissions()->set_permission(permission::b_group_is_permanent, {1, 0}, permission::v2::set_value, permission::v2::do_nothing); + if(this->server) + this->server->forEachClient([](shared_ptr cl) { + cl->notifyChannelGroupList(); + }); + } else return command_result{error::group_invalid_id}; + return command_result{error::ok}; +} + +//name=Channel\sAdmin scgid=5 tcgid=4 type=1 +command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_create, 1); + + auto ref_server = this->server; + + auto group_manager = this->server ? this->server->groups : serverInstance->getGroupManager().get(); + + auto source_group_id = cmd["scgid"].as(); + auto source_group = group_manager->findGroup(source_group_id); + + if(!source_group || source_group->target() != GROUPTARGET_CHANNEL) + return command_result{error::group_invalid_id, "invalid source group"}; + + const auto group_type_modificable = [&](GroupType type) { + switch(type) { + case GroupType::GROUP_TYPE_TEMPLATE: + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_templates, 0))) + return permission::b_serverinstance_modify_templates; + break; + case GroupType::GROUP_TYPE_QUERY: + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_querygroup, 0))) + return permission::b_serverinstance_modify_querygroup; + break; + + default: + break; + } + return permission::undefined; + }; + + { + auto result = group_type_modificable(source_group->type()); + if(result != permission::undefined) + return command_result{result}; + } + + auto global_update = false; + if(cmd[0].has("tcgid") && cmd["tcgid"].as() != 0) { + //Copy an existing group + auto target_group = group_manager->findGroup(cmd["tcgid"]); + if(!target_group || target_group->target() != GROUPTARGET_CHANNEL) + return command_result{error::group_invalid_id, "invalid target group"}; + + { + auto result = group_type_modificable(target_group->type()); + if(result != permission::undefined) + return command_result{result}; + } + + if(!target_group->permission_granted(permission::i_channel_group_needed_modify_power, this->calculate_permission(permission::i_channel_group_modify_power, 0), true)) + return command_result{permission::i_channel_group_modify_power}; + + if(!group_manager->copyGroupPermissions(source_group, target_group)) + return command_result{error::vs_critical, "failed to copy group permissions"}; + + global_update = !this->server || !group_manager->isLocalGroup(target_group); + } else { + //Copy a new group + auto target_type = cmd["type"].as(); + + { + auto result = group_type_modificable(target_type); + if(result != permission::undefined) + return command_result{result}; + } + + if(!ref_server && target_type == GroupType::GROUP_TYPE_NORMAL) + return command_result{error::parameter_invalid, "You cant create normal groups on the template server!"}; + + if(!group_manager->findGroup(GroupTarget::GROUPTARGET_CHANNEL, cmd["name"].string()).empty()) + return command_result{error::group_name_inuse, "You cant create normal groups on the template server!"}; + + auto target_group_id = group_manager->copyGroup(source_group, target_type, cmd["name"], target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId()); + if(target_group_id == 0) + return command_result{error::vs_critical, "failed to copy group"}; + + if(this->getType() == ClientType::CLIENT_QUERY) { + Command notify(""); + notify["cgid"] = target_group_id; + this->sendCommand(notify); + } + + global_update = !this->server || !group_manager->isLocalGroup(group_manager->findGroup(target_group_id)); + } + + for(const auto& server : (global_update ? serverInstance->getVoiceServerManager()->serverInstances() : deque>{this->server})) + if(server) + server->forEachClient([](shared_ptr cl) { + cl->notifyChannelGroupList(); + }); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelGroupRename(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); + auto channel_group = group_manager->findGroup(cmd["cgid"].as()); + if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; + ACTION_REQUIRES_GROUP_PERMISSION(channel_group, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); + + group_manager->renameGroup(channel_group, cmd["name"].string()); + if(this->server) + this->server->forEachClient([](shared_ptr cl) { + cl->notifyChannelGroupList(); + }); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelGroupDel(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_delete, 1); + + auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); + auto channel_group = group_manager->findGroup(cmd["cgid"].as()); + if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; + + if(this->server) { + if(this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] == channel_group->groupId()) + return command_result{error::parameter_invalid, "Could not delete default channel group!"}; + if(this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] == channel_group->groupId()) + return command_result{error::parameter_invalid, "Could not delete default channel admin group!"}; + } + if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP] == channel_group->groupId()) + return command_result{error::parameter_invalid, "Could not delete instance default channel group!"}; + if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP] == channel_group->groupId()) + return command_result{error::parameter_invalid, "Could not delete instance default channel admin group!"}; + + if (!cmd["force"].as()) + if (!group_manager->listGroupMembers(channel_group, false).empty()) + return command_result{error::database_empty_result, "group not empty!"}; + + if (group_manager->deleteGroup(channel_group) && this->server) { + this->server->forEachClient([&](shared_ptr cl) { + if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) { + if(cl->update_cached_permissions()) /* update cached calculated permissions */ + cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + } + cl->notifyChannelGroupList(); + }); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelGroupList(Command &) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_list, 1); + + this->notifyChannelGroupList(); + this->command_times.servergrouplist = system_clock::now(); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelGroupClientList(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + auto target_channel_id = cmd[0].has("cid") ? cmd["cid"].as() : 0; + if(target_channel_id > 0) { + ACTION_REQUIRES_PERMISSION(permission::b_virtualserver_channelgroup_client_list, 1, target_channel_id); + } else { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_client_list, 1); + } + Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifychannelgroupclientlist" : ""); + + deque variables{variable{":sid", this->getServerId()}}; + string query = "SELECT `groupId`, `cldbid`, `until`, `channelId` FROM `assignedGroups` WHERE `serverId` = :sid"; + + if(cmd[0].has("cgid") && cmd["cgid"].as() > 0) { + auto group = this->server->getGroupManager()->findGroup(cmd["cgid"]); + if(!group || group->target() != GroupTarget::GROUPTARGET_CHANNEL) + return command_result{error::parameter_invalid, "invalid channel group id"}; + query += " AND `groupId` = :groupId"; + variables.push_back({":groupId", cmd["cgid"].as()}); + } else { + query += " AND `groupId` IN (SELECT `groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target)"; + variables.push_back({":target", GroupTarget::GROUPTARGET_CHANNEL}); + } + if(cmd[0].has("cldbid") && cmd["cldbid"].as() > 0) { + query += " AND `cldbid` = :cldbid"; + variables.push_back({":cldbid", cmd["cldbid"].as()}); + } + if(cmd[0].has("cid") && cmd["cid"].as() > 0) { + auto channel = this->server->getChannelTree()->findChannel(cmd["cid"]); + if(!channel) + return command_result{error::parameter_invalid, "invalid channel id"}; + query += " AND `channelId` = :cid"; + variables.push_back({":cid", cmd["cid"].as()}); + } + debugMessage(this->getServerId(), "Command channelgroupclientlist sql: {}", query); + + auto command = sql::command(this->sql, query); + for(const auto& variable : variables) + command.value(variable); + + int index = 0; + command.query([&](Command& command, int& index, int length, string* values, string* names) { + GroupId group_id = 0; + ChannelId channel_id = 0; + ClientDbId cldbid = 0; + for(int i = 0; i < length; i++) { + try { + if(names[i] == "groupId") + group_id = stoll(values[i]); + else if(names[i] == "cldbid") + cldbid = stoll(values[i]); + else if(names[i] == "channelId") + channel_id = stoll(values[i]); + } catch(std::exception& ex) { + logError(this->getServerId(), "Failed to parse db field {}", names[i]); + } + } + result[index]["cid"] = channel_id; + result[index]["cgid"] = group_id; + result[index]["cldbid"] = cldbid; + index++; + }, result, index); + if (index == 0) return command_result{error::database_empty_result}; + this->sendCommand(result); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelGroupPermList(Command &cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channelgroup_permission_list, 1); + + auto channelGroup = (this->server ? this->server->groups : serverInstance->getGroupManager().get())->findGroup(cmd["cgid"].as()); + if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; + if (!this->notifyGroupPermList(channelGroup, cmd.hasParm("permsid"))) return command_result{error::database_empty_result}; + + if(this->getType() == ClientType::CLIENT_TEAMSPEAK && this->command_times.last_notify + this->command_times.notify_timeout < system_clock::now()) { + this->sendTSPermEditorWarning(); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); + auto channelGroup = group_manager->findGroup(cmd["cgid"].as()); + if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; + ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); + + 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)); + + auto conOnError = cmd[0].has("continueonerror"); + bool updateList = false; + + auto permission_manager = channelGroup->permissions(); + 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) { + permission_manager->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); + } else { + permission_manager->set_permission( + permType, + {cmd[index]["permvalue"], 0}, + permission::v2::PermissionUpdateType::set_value, + permission::v2::PermissionUpdateType::do_nothing, + + cmd[index]["permskip"].as() ? 1 : 0, + cmd[index]["permnegated"].as() ? 1 : 0 + ); + updateList |= permission_is_group_property(permType); + } + } + + + if(updateList) + channelGroup->apply_properties_from_permissions(); + + if(this->server) { + if(updateList) + this->server->forEachClient([](shared_ptr cl) { + cl->notifyChannelGroupList(); + }); + this->server->forEachClient([channelGroup](shared_ptr cl) { + unique_lock client_channel_lock(cl->channel_lock); /* while we're updating groups we dont want to change anything! */ + if (cl->channelGroupAssigned(channelGroup, cl->getChannel())) { + if(cl->update_cached_permissions()) + cl->sendNeededPermissions(false); /* update the needed permissions */ + cl->updateChannelClientProperties(false, true); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } + }); + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(5); + auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); + auto channelGroup = group_manager->findGroup(cmd["cgid"].as()); + if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"}; + ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); + + auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0)); + + bool updateList = false; + bool conOnError = cmd[0].has("continueonerror"); + + auto permission_manager = channelGroup->permissions(); + 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) { + permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); + } else { + permission_manager->set_permission( + permType, + permission::v2::empty_permission_values, + permission::v2::PermissionUpdateType::delete_value, + permission::v2::PermissionUpdateType::do_nothing + ); + updateList |= permission_is_group_property(permType); + } + } + + if(updateList) + channelGroup->apply_properties_from_permissions(); + + if(this->server) { + if(updateList) + this->server->forEachClient([](shared_ptr cl) { + cl->notifyChannelGroupList(); + }); + this->server->forEachClient([channelGroup](shared_ptr cl) { + unique_lock client_channel_lock(cl->channel_lock); /* while we're updating groups we dont want to change anything! */ + if (cl->channelGroupAssigned(channelGroup, cl->getChannel())) { + if(cl->update_cached_permissions()) /* update cached calculated permissions */ + cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + cl->updateChannelClientProperties(false, false); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } + }); + } + return command_result{error::ok}; +} + +//TODO: Test if parent or previous is deleted! +command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + CMD_CHK_PARM_COUNT(1); + + //TODO: Use for this here the cache as well! + auto permission_cache = make_shared(); + if (cmd[0].has("cpid") && cmd["cpid"].as() != 0) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_child, 1, permission_cache); + if (cmd[0].has("channel_order")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_sortorder, 1, permission_cache); + + if(!cmd[0].has("channel_flag_permanent")) cmd[0]["channel_flag_permanent"] = false; + if(!cmd[0].has("channel_flag_semi_permanent")) cmd[0]["channel_flag_semi_permanent"] = false; + if(!cmd[0].has("channel_flag_default")) cmd[0]["channel_flag_default"] = false; + if(!cmd[0].has("channel_flag_password")) cmd[0]["channel_flag_password"] = false; + + if (cmd[0]["channel_flag_permanent"].as()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_permanent, 1, permission_cache); + else if (cmd[0]["channel_flag_semi_permanent"].as()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_semi_permanent, 1, permission_cache); + else ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_temporary, 1, permission_cache); + + if (!cmd[0]["channel_flag_permanent"].as() && !this->server) return command_result{error::parameter_invalid, "You can only create a permanent channel"}; + + if (cmd[0]["channel_flag_default"].as()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_default, 1, permission_cache); + if (cmd[0]["channel_flag_password"].as()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_password, 1, permission_cache); + else if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_create_modify_with_force_password, 0, false, permission_cache))) + return command_result{permission::b_channel_create_modify_with_force_password}; + + if(cmd[0].has("channel_password") && this->getType() == ClientType::CLIENT_QUERY) + cmd["channel_password"] = base64::decode(digest::sha1(cmd["channel_password"].string())); + if (cmd[0].has("channel_description")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_description, 1, permission_cache); + if (cmd[0].has("channel_maxclients") || (cmd[0].has("channel_flag_maxclients_unlimited") && !cmd["channel_flag_maxclients_unlimited"].as())) { + ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_maxclients, 1, permission_cache); + if(!cmd[0]["channel_flag_permanent"].as() && !cmd[0]["channel_flag_semi_permanent"].as()) { + cmd["channel_maxclients"] = -1; + cmd["channel_flag_maxclients_unlimited"] = 1; + } + } + if (cmd[0].has("channel_maxfamilyclients")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_maxfamilyclients, 1, permission_cache); + if (cmd[0].has("channel_needed_talk_power")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_needed_talk_power, 1, permission_cache); + if (cmd[0].has("channel_topic")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_topic, 1, permission_cache); + + auto target_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get(); + auto& tree_lock = this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock(); + unique_lock tree_channel_lock(tree_lock); + + if(cmd[0].has("channel_conversation_history_length")) { + auto value = cmd["channel_conversation_history_length"].as(); + if(value == 0) { + ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_modify_conversation_history_unlimited, 1, permission_cache); + } else { + ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::i_channel_create_modify_conversation_history_length, 1, permission_cache); + } + } + + { + auto delete_delay = cmd[0].has("channel_delete_delay") ? cmd["channel_delete_delay"].as() : 0UL; + if(delete_delay == 0) { + if(this->server) + cmd["channel_delete_delay"] = this->server->properties()[property::VIRTUALSERVER_CHANNEL_TEMP_DELETE_DELAY_DEFAULT].as(); + else + cmd["channel_delete_delay"] = 0; + } else { + ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::i_channel_create_modify_with_temp_delete_delay, cmd["channel_delete_delay"].as(), permission_cache); + } + } + + { + size_t created_tmp = 0, created_semi = 0, created_perm = 0; + auto own_cldbid = this->getClientDatabaseId(); + for(const auto& channel : target_tree->channels()) { + if(channel->properties()[property::CHANNEL_CREATED_BY] == own_cldbid) { + if(channel->properties()[property::CHANNEL_FLAG_PERMANENT].as()) + created_perm++; + else if(channel->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as()) + created_semi++; + else + created_tmp++; + } + } + + + auto max_channels = this->calculate_permission(permission::i_client_max_channels, 0, false, permission_cache); + if(max_channels.has_value) { + if(!permission::v2::permission_granted(created_perm + created_semi + created_tmp + 1, max_channels)) + return command_result{permission::i_client_max_channels}; + } + + if (cmd[0]["channel_flag_permanent"].as()) { + max_channels = this->calculate_permission(permission::i_client_max_permanent_channels, 0, false, permission_cache); + + if(max_channels.has_value) { + if(!permission::v2::permission_granted(created_perm + 1, max_channels)) + return command_result{permission::i_client_max_permanent_channels}; + } + } + else if (cmd[0]["channel_flag_semi_permanent"].as()) { + max_channels = this->calculate_permission(permission::i_client_max_semi_channels, 0, false, permission_cache); + + if(max_channels.has_value) { + if(!permission::v2::permission_granted(created_semi + 1, max_channels)) + return command_result{permission::i_client_max_semi_channels}; + } + } + else { + max_channels = this->calculate_permission(permission::i_client_max_temporary_channels, 0, false, permission_cache); + + if(max_channels.has_value) { + if(!permission::v2::permission_granted(created_tmp + 1, max_channels)) + return command_result{permission::i_client_max_temporary_channels}; + } + } + } + + //TODO check voice (opus etc) + std::shared_ptr parent = nullptr; + std::shared_ptr created_channel = nullptr, old_default_channel; + + //bool enforce_permanent_parent = cmd[0]["channel_flag_default"].as(); //TODO check parents here + { //Checkout the parent(s) + if (cmd[0].has("cpid") && cmd["cpid"].as() != 0 && cmd["cpid"].as() != -1) { + parent = target_tree->findLinkedChannel(cmd["cpid"].as()); + if (!parent) return command_result{error::channel_invalid_id, "Cant resolve parent channel"}; + } + + { + + auto min_channel_deep = this->calculate_permission(permission::i_channel_min_depth, 0, false, permission_cache); + auto max_channel_deep = this->calculate_permission(permission::i_channel_max_depth, 0, false, permission_cache); + + if(min_channel_deep.has_value || max_channel_deep.has_value) { + auto channel_deep = 0; + auto local_parent = parent; + while(local_parent) { + channel_deep++; + { + const auto typed_parent = dynamic_pointer_cast(local_parent->entry); + if(typed_parent->deleted) return command_result{error::channel_is_deleted, "One of the parents has been deleted"}; + } + local_parent = local_parent->parent.lock(); + } + + if(min_channel_deep.has_value && (channel_deep < min_channel_deep.value && !min_channel_deep.has_infinite_power())) return command_result{permission::i_channel_min_depth}; + if(max_channel_deep.has_value && !permission::v2::permission_granted(channel_deep, max_channel_deep)) return command_result{permission::i_channel_max_depth}; + } + } + } + + if(!cmd[0].has("channel_order")) { + auto last = parent ? parent->child_head : target_tree->tree_head(); + while(last && last->next) + last = last->next; + if(last) + cmd["channel_order"] = last->entry->channelId(); + } else { + + } + + if (cmd["channel_name"].string().length() < 1) return command_result{error::channel_name_inuse, "Invalid channel name"}; + + { + if (target_tree->findChannel(cmd["channel_name"].as(), parent ? dynamic_pointer_cast(parent->entry) : nullptr)) return command_result{error::channel_name_inuse, "Name already in use"}; + + created_channel = target_tree->createChannel(parent ? parent->entry->channelId() : (ChannelId) 0, cmd[0].has("channel_order") ? cmd["channel_order"].as() : (ChannelId) 0, cmd["channel_name"].as()); + } + + if (!created_channel) return command_result{error::channel_invalid_flags, "Could not create channel"}; + + auto created_linked_channel = target_tree->findLinkedChannel(created_channel->channelId()); + sassert(created_linked_channel); + + if (cmd[0].has("channel_flag_default") && cmd["channel_flag_default"].as()) { + old_default_channel = target_tree->getDefaultChannel(); + target_tree->setDefaultChannel(created_channel); + } + + created_channel->properties()[property::CHANNEL_CREATED_BY] = this->getClientDatabaseId(); + + { + auto default_modify_power = this->calculate_permission(permission::i_channel_modify_power, 0, false, permission_cache); + auto default_delete_power = this->calculate_permission(permission::i_channel_delete_power, 0, false, permission_cache); + + auto permission_manager = created_channel->permissions(); + permission_manager->set_permission( + permission::i_channel_needed_modify_power, + {default_modify_power.has_value ? default_modify_power.value : 0, 0}, + permission::v2::PermissionUpdateType::set_value, + permission::v2::PermissionUpdateType::do_nothing + ); + permission_manager->set_permission( + permission::i_channel_needed_delete_power, + {default_delete_power.has_value ? default_delete_power.value : 0, 0}, + permission::v2::PermissionUpdateType::set_value, + permission::v2::PermissionUpdateType::do_nothing + ); + } + + for (auto &prop : cmd[0].keys()) { + if (prop == "channel_flag_default") continue; + if (prop == "channel_order") continue; + if (prop == "channel_name") continue; + if (prop == "cpid") continue; + if (prop == "cid") continue; + + const auto &property = property::info(prop); + if(*property == property::CHANNEL_UNDEFINED) { + logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + prop); + continue; + } + + if(!property->validate_input(cmd[prop].as())) { + logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as() + "', Property: '" + property->name + "')"); + continue; + } + created_channel->properties()[property] = cmd[prop].as(); + } + if(created_channel->parent()) { + if(created_channel->parent()->channelType() > created_channel->channelType()) + created_channel->setChannelType(created_channel->parent()->channelType()); + } + + if(this->server) { + const auto self_lock = _this.lock(); + this->server->forEachClient([&, created_channel](const shared_ptr& client) { + unique_lock client_channel_lock(client->channel_lock); + auto result = client->channels->add_channel(created_linked_channel); + if(!result) return; + + client->notifyChannelCreate(created_channel, result->previous_channel, self_lock); + if(client == self_lock && this->getType() == ClientType::CLIENT_QUERY) { + Command notify(""); + notify["cid"] = created_channel->channelId(); + this->sendCommand(notify); + } + client->notifyChannelDescriptionChanged(created_channel); + if (client->subscribeToAll) + client->subscribeChannel({created_channel}, false, true); + + if(old_default_channel) { + //TODO: Reminder: client channel tree must be at least read locked here! + client->notifyChannelEdited(old_default_channel, {property::CHANNEL_FLAG_DEFAULT}, self_lock, false); + } + }); + + GroupId adminGroup = this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP]; + auto channel_admin_group = this->server->groups->findGroup(adminGroup); + if (!channel_admin_group) { + logError(this->getServerId(), "Missing server's default channel admin group! Using default channel group!"); + channel_admin_group = this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL); + } + this->server->groups->setChannelGroup(this->getClientDatabaseId(), channel_admin_group, created_channel); + + if (created_channel->channelType() == ChannelType::temporary && (this->getType() == ClientType::CLIENT_TEAMSPEAK || this->getType() == ClientType::CLIENT_WEB)) + this->server->client_move( + this->ref(), + created_channel, + nullptr, + "channel created", + ViewReasonId::VREASON_USER_ACTION, + true, + tree_channel_lock + ); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelDelete(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + RESOLVE_CHANNEL_W(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + assert(channel); + if(channel->deleted) /* channel gets already removed */ + return command_result{error::ok}; + + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_delete_power, permission::i_channel_delete_power, true); + for(const auto& ch : channel_tree->channels(channel)) { + if(ch->defaultChannel()) + return command_result{error::channel_can_not_delete_default}; + } + + if(this->server) { + auto clients = this->server->getClientsByChannelRoot(channel, false); + if(!clients.empty()) + ACTION_REQUIRES_PERMISSION(permission::b_channel_delete_flag_force, 1, channel->channelId()); + + this->server->delete_channel(channel, this->ref(), "channel deleted", channel_tree_write_lock); + } else { + auto channel_ids = channel_tree->deleteChannelRoot(channel); + this->notifyChannelDeleted(channel_ids, this->ref()); + } + return command_result{error::ok}; +} + +/* + * 1. check for permission and basic requirements + * 2. Lock the channel tree in write mode if required + * 3. Apply changed, test for advanced requirements like channel name etc + * 4. notify everyone + */ +command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + RESOLVE_CHANNEL_R(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + assert(channel); + if(channel->deleted) { + /* channel gets already removed */ + return command_result{error::ok}; + } + + std::deque> keys; + bool require_write_lock = false; + bool update_max_clients = false; + bool update_max_family_clients = false; + bool update_clients_in_channel = false; + bool update_password = false; + bool update_name = false; + /* Step 1 */ + + bool target_channel_type_changed = false; + ChannelType::ChannelType target_channel_type = channel->channelType(); + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_modify_power, permission::i_channel_modify_power, true); + + for (const auto &key : cmd[0].keys()) { + if(key == "cid") + continue; + if(key == "return_code") + continue; + + const auto &property = property::info(key); + if(*property == property::CHANNEL_UNDEFINED) { + logError(this->getServerId(), R"({} Tried to edit a not existing channel property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); + continue; + } + + if((property->flags & property::FLAG_USER_EDITABLE) == 0) { + logError(this->getServerId(), "{} Tried to change a channel property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); + continue; + } + + if(!property->validate_input(cmd[key].as())) { + logError(this->getServerId(), "{} Tried to change a channel property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); + continue; + } + + if(channel->properties()[*property].as() == cmd[key].as()) + continue; /* we dont need to update stuff which is the same */ + + if(key == "channel_icon_id") { + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true); + } else if (key == "channel_order") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_sortorder, 1, channel_id); + require_write_lock = true; + } else if (key == "channel_flag_default") { + if(!cmd["channel_flag_default"].as()) + return command_result{error::channel_invalid_flags}; + + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_channel_modify_make_default, 1); + require_write_lock = true; + } else if (key == "channel_name") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_name, 1, channel_id); + if (count_characters(cmd["channel_name"]) < 1) + return command_result{error::channel_name_invalid}; + if (count_characters(cmd["channel_name"]) > 40) + return command_result{error::channel_name_invalid}; + require_write_lock = true; + update_name = true; + } else if (key == "channel_name_phonetic") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_name, 1, channel_id); + } else if (key == "channel_topic") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_topic, 1, channel_id); + } else if (key == "channel_description") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_description, 1, channel_id); + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_use_bbcode_any, channel_id))) { + auto bbcode_image = bbcode::sloppy::has_image(cmd[key]); + auto bbcode_url = bbcode::sloppy::has_url(cmd[key]); + debugMessage(this->getServerId(), "Channel description contains bb codes: Image: {} URL: {}", bbcode_image, bbcode_url); + if(bbcode_image && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_use_bbcode_image, channel_id))) + return command_result{permission::b_client_use_bbcode_image}; + if(bbcode_url && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_use_bbcode_url, channel_id))) + return command_result{permission::b_client_use_bbcode_url}; + } + } else if (key == "channel_codec") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_codec, 1, channel_id); + } else if (key == "channel_codec_quality") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_codec_quality, 1, channel_id); + } else if (key == "channel_codec_is_unencrypted") { + if (cmd["channel_codec_is_unencrypted"].as()) + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_make_codec_encrypted, 1, channel_id); + } else if (key == "channel_needed_talk_power") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_needed_talk_power, 1, channel_id); + update_clients_in_channel = true; + } else if (key == "channel_maxclients" || key == "channel_flag_maxclients_unlimited") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_maxclients, 1, channel_id); + require_write_lock = true; + update_max_clients = true; + } else if(key == "channel_maxfamilyclients" || key == "channel_flag_maxfamilyclients_unlimited" || key == "channel_flag_maxfamilyclients_inherited") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_maxfamilyclients, 1, channel_id); + require_write_lock = true; + update_max_family_clients = true; + } else if (key == "channel_flag_permanent" || key == "channel_flag_semi_permanent") { + if (cmd[0].has("channel_flag_permanent") && cmd["channel_flag_permanent"].as()) { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_make_permanent, 1, channel_id); + target_channel_type = ChannelType::permanent; + } else if (cmd[0].has("channel_flag_semi_permanent") && cmd["channel_flag_semi_permanent"].as()) { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_make_semi_permanent, 1, channel_id); + target_channel_type = ChannelType::semipermanent; + } else { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_make_temporary, 1, channel_id); + target_channel_type = ChannelType::temporary; + } + target_channel_type_changed = true; + require_write_lock = true; + } else if (key == "channel_delete_delay") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_temp_delete_delay, cmd["channel_delete_delay"].as(), channel_id); + } else if (key == "channel_password" || key == "channel_flag_password") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_password, 1, channel_id); + update_password = true; + } else if (key == "channel_conversation_history_length") { + auto value = cmd["channel_conversation_history_length"].as(); + if(value == 0) { + ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_history_unlimited, 1, channel_id); + } else { + ACTION_REQUIRES_PERMISSION(permission::i_channel_create_modify_conversation_history_length, 1, channel_id); + } + } else if (key == "channel_flag_conversation_private") { + ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_private, 1, channel_id); + } else { + logCritical( + this->getServerId(), + "The client " + this->getDisplayName() + " tried to change a editable channel property but we haven't found a permission. Please report this error. (Channel property: {})", + key + ); + continue; + } + keys.push_back(property); + } + + unique_lock server_channel_w_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock(), defer_lock); + if(require_write_lock) { + channel_tree_read_lock.unlock(); + server_channel_w_lock.lock(); + + /* not that while we're waiting to edit the server the channel got deleted... fuck my english */ + if(channel->deleted) + return command_result{error::ok}; + } + + /* test the password parameters */ + if(update_password) { + if(!cmd[0].has("channel_password")) { + if(cmd[0].has("channel_flag_password") && cmd["channel_flag_password"].as()) + return command_result{error::parameter_missing}; + else + cmd["channel_password"] = ""; /* no password set */ + keys.push_back(property::info(property::CHANNEL_PASSWORD)); + } + if(!cmd[0].has("channel_flag_password")) { + cmd["channel_flag_password"] = !cmd["channel_password"].string().empty(); + keys.push_back(property::info(property::CHANNEL_FLAG_PASSWORD)); + } + + if(cmd["channel_flag_password"].as()) { + if(cmd["channel_password"].string().empty()) + return command_result{error::channel_invalid_flags}; /* we cant enable a password without a given password */ + + /* we've to "encode" the password */ + if(this->getType() == ClientType ::CLIENT_QUERY) + cmd["channel_password"] = base64::encode(digest::sha1(cmd["channel_password"].string())); + } else { + cmd["channel_password"] = ""; /* flag password if false so we set the password to empty */ + } + } + + /* test the default channel update */ + if(cmd[0].has("channel_flag_default") || channel->defaultChannel()) { + if(target_channel_type != ChannelType::permanent) + return command_result{error::channel_default_require_permanent}; /* default channel is not allowed to be non permanent */ + + if((cmd[0].has("channel_flag_password") && cmd["channel_flag_password"].as()) || channel->properties()[property::CHANNEL_FLAG_PASSWORD]) { + cmd["channel_flag_password"] = false; + cmd["channel_password"] = ""; + keys.push_back(property::info(property::CHANNEL_FLAG_PASSWORD)); + } + + if(cmd[0].has("channel_flag_default")) { + cmd["channel_maxclients"] = -1; + cmd["channel_flag_maxclients_unlimited"] = true; + keys.push_back(property::info(property::CHANNEL_MAXCLIENTS)); + keys.push_back(property::info(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED)); + update_max_clients = true; + + cmd["channel_maxfamilyclients"] = -1; + cmd["channel_flag_maxfamilyclients_inherited"] = true; + keys.push_back(property::info(property::CHANNEL_MAXFAMILYCLIENTS)); + keys.push_back(property::info(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED)); + update_max_family_clients = true; + } + } + + /* "fix" max client for temporary channels */ + if(target_channel_type_changed) { + if(target_channel_type == ChannelType::temporary) { + if(channel->properties()[property::CHANNEL_MAXCLIENTS].as() != -1) { + cmd["channel_maxclients"] = -1; + cmd["channel_flag_maxclients_unlimited"] = true; + keys.push_back(property::info(property::CHANNEL_MAXCLIENTS)); + keys.push_back(property::info(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED)); + update_max_clients = true; + } + if(channel->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as() != -1) { + cmd["channel_maxfamilyclients"] = -1; + cmd["channel_flag_maxfamilyclients_inherited"] = true; + keys.push_back(property::info(property::CHANNEL_MAXFAMILYCLIENTS)); + keys.push_back(property::info(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED)); + update_max_family_clients = true; + } + } + + if(target_channel_type != ChannelType::permanent) { + /* test if any child is the default channel */ + for(const auto& child : channel_tree->channels(channel)) + if(child->defaultChannel()) + return command_result{error::channel_default_require_permanent}; /* default channel is not allowed to be non permanent */ + } + auto parent = channel->parent(); + if(parent && parent->channelType() > target_channel_type) + return command_result{error::channel_parent_not_permanent}; + } + + /* test the max clients parameters */ + if(update_max_clients) { + if(!cmd[0].has("channel_maxclients")) { + if(cmd[0].has("channel_flag_maxclients_unlimited") && cmd["channel_flag_maxclients_unlimited"].as()) + cmd["channel_maxclients"] = -1; + else + return command_result{error::parameter_missing, "channel_maxclients"}; /* max clients must be specified */ + keys.push_back(property::info(property::CHANNEL_MAXCLIENTS)); + } + + if(!cmd[0].has("channel_flag_maxclients_unlimited")) { + cmd["channel_flag_maxclients_unlimited"] = cmd["channel_maxclients"].as() < 0; + keys.push_back(property::info(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED)); + } + + if(cmd["channel_flag_maxclients_unlimited"].as() && cmd["channel_maxclients"].as() != -1) + return command_result{error::channel_invalid_flags}; /* channel cant have a max client settings AND be unlimited as well */ + + if(!cmd["channel_flag_maxclients_unlimited"].as() && target_channel_type == ChannelType::temporary) + return command_result{error::channel_invalid_flags}; /* temporary channels cant have a limited user count */ + } + + /* test the max family clients parameters */ + if(update_max_family_clients) { + if(!cmd[0].has("channel_maxfamilyclients")) { + if(cmd[0].has("channel_flag_maxfamilyclients_unlimited")) { + if(cmd["channel_flag_maxfamilyclients_unlimited"].as()) + cmd["channel_flag_maxfamilyclients_inherited"] = false; + else + cmd["channel_flag_maxfamilyclients_inherited"] = true; + keys.push_back(property::info(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED)); + } else if(cmd[0].has("channel_flag_maxfamilyclients_inherited")) { + if(cmd["channel_flag_maxfamilyclients_inherited"].as()) + cmd["channel_flag_maxfamilyclients_unlimited"] = false; + else + cmd["channel_flag_maxfamilyclients_unlimited"] = true; + keys.push_back(property::info(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED)); + } else /* not really possible */ + return command_result{error::parameter_missing, "channel_maxfamilyclients"}; /* family max clients must be */ + cmd["channel_maxfamilyclients"] = -1; + keys.push_back(property::info(property::CHANNEL_MAXFAMILYCLIENTS)); + } + //keep this order because this command: "channeledit cid= channel_maxfamilyclients=-1" should set max family clients mode to inherited + if(!cmd[0].has("channel_flag_maxfamilyclients_inherited")) { + auto flag_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as(); + if(flag_unlimited) + cmd["channel_flag_maxfamilyclients_inherited"] = false; + else + cmd["channel_flag_maxfamilyclients_inherited"] = cmd["channel_maxfamilyclients"].as() < 0; + } + if(!cmd[0].has("channel_flag_maxfamilyclients_unlimited")) { + auto flag_inherited = cmd[0].has("channel_flag_maxfamilyclients_inherited") && cmd["channel_flag_maxfamilyclients_inherited"].as(); + if(flag_inherited) + cmd["channel_flag_maxfamilyclients_unlimited"] = false; + else + cmd["channel_flag_maxfamilyclients_unlimited"] = cmd["channel_maxfamilyclients"].as() < 0; + } + + if(cmd["channel_flag_maxfamilyclients_inherited"].as() && cmd["channel_flag_maxfamilyclients_unlimited"].as()) + return command_result{error::channel_invalid_flags}; /* both at the same time are not possible */ + + if(cmd["channel_flag_maxfamilyclients_inherited"].as() && cmd["channel_maxfamilyclients"].as() != -1) + return command_result{error::channel_invalid_flags}; /* flag inherited required max users to be -1 */ + + if(cmd["channel_flag_maxfamilyclients_unlimited"].as() && cmd["channel_maxfamilyclients"].as() != -1) + return command_result{error::channel_invalid_flags}; /* flag unlimited required max users to be -1 */ + + if(cmd["channel_maxfamilyclients"].as() != -1 && target_channel_type == ChannelType::temporary) + return command_result{error::channel_invalid_flags}; /* temporary channels cant have a limited user count */ + } + + /* test the channel name */ + if(update_name) { + auto named_channel = channel_tree->findChannel(cmd["channel_name"].string(), channel->parent()); + if (named_channel) + return command_result{error::channel_name_inuse}; + } + + auto self_ref = this->ref(); + shared_ptr old_default_channel; + deque> child_channel_updated; + for(const std::shared_ptr& key : keys) { + if(*key == property::CHANNEL_ORDER) { + /* TODO: May move that up because if it fails may some other props have already be applied */ + if (!channel_tree->change_order(channel, cmd[key->name])) + return command_result{error::channel_invalid_order, "Can't change order id"}; + + if(this->server) { + auto parent = channel->hasParent() ? channel_tree->findLinkedChannel(channel->parent()->channelId()) : nullptr; + auto previous = channel_tree->findLinkedChannel(channel->previousChannelId()); + + this->server->forEachClient([&](const shared_ptr& cl) { + unique_lock client_channel_lock(cl->channel_lock); + + auto actions = cl->channels->change_order(l_channel, parent, previous); + std::deque deletions; + for(const auto& action : actions) { + switch (action.first) { + case ClientChannelView::NOTHING: + continue; + case ClientChannelView::ENTER_VIEW: + cl->notifyChannelShow(action.second->channel(), action.second->previous_channel); + break; + case ClientChannelView::DELETE_VIEW: + deletions.push_back(action.second->channelId()); + break; + case ClientChannelView::MOVE: + cl->notifyChannelMoved(action.second->channel(), action.second->previous_channel, this->ref()); + break; + case ClientChannelView::REORDER: + cl->notifyChannelEdited(action.second->channel(), {property::CHANNEL_ORDER}, self_ref, false); + break; + } + } + if(!deletions.empty()) { + cl->notifyChannelHide(deletions, false); + return; //Channel got deleted so we dont have to send the updates + } + }); + } + } else if(*key == property::CHANNEL_FLAG_DEFAULT) { + old_default_channel = channel_tree->getDefaultChannel(); + if(old_default_channel == channel) { + old_default_channel = nullptr; + continue; + } + + if(!cmd[key->name].as()) { + old_default_channel = nullptr; + continue; + } + + channel_tree->setDefaultChannel(channel); + + deque> updated_channels; + shared_ptr current_channel = channel; + do { + if(current_channel->channelType() == ChannelType::permanent) + break; + + current_channel->setChannelType(ChannelType::permanent); + if(current_channel != channel) + updated_channels.push_back(channel); + } while ((current_channel = current_channel->parent())); + + if(this->server && !updated_channels.empty()) { + std::reverse(updated_channels.begin(), updated_channels.end()); + auto this_ref = this->ref(); + this->server->forEachClient([&](const shared_ptr& client) { + unique_lock client_channel_lock(client->channel_lock); + for(const auto& channel : updated_channels) { + client->notifyChannelEdited(channel, {property::CHANNEL_FLAG_PERMANENT, property::CHANNEL_FLAG_SEMI_PERMANENT}, this_ref, false); + } + }); + } + } else if(*key == property::CHANNEL_FLAG_PERMANENT || *key == property::CHANNEL_FLAG_SEMI_PERMANENT) { + if(target_channel_type_changed) { /* must be true else the key would not appere here */ + target_channel_type_changed = false; /* we only need to check all subchannels once! */ + + + { /* check channel children */ + deque> channel_to_test = {channel}; + + while(!channel_to_test.empty()) { + auto current_channel = channel_to_test.front(); + channel_to_test.pop_front(); + + for(const auto& child : channel_tree->channels(current_channel, 1)) { + if(child == current_channel) + continue; + + if(child->channelType() < target_channel_type) { + child->setChannelType(target_channel_type); + channel_to_test.push_back(child); + child_channel_updated.push_back(child); + } + } + } + } + } + } else if(*key == property::CHANNEL_CONVERSATION_HISTORY_LENGTH) { + //channel_conversation_history_length + auto conversation_manager = this->server->conversation_manager(); + if(conversation_manager) { + auto conversation = conversation_manager->get(channel->channelId()); + if(conversation) + conversation->set_history_length(cmd[key->name]); + } + } + + channel->properties()[key] = cmd[key->name].string(); + } + if(this->server) { + vector key_vector; + key_vector.reserve(keys.size()); + for(const auto& key : keys) + key_vector.push_back((property::ChannelProperties) key->property_index); + + auto self_rev = this->ref(); + this->server->forEachClient([&](const shared_ptr& client) { + unique_lock client_channel_lock(client->channel_lock); + + for(const auto& channel : child_channel_updated) + client->notifyChannelEdited(channel, {property::CHANNEL_FLAG_PERMANENT, property::CHANNEL_FLAG_SEMI_PERMANENT}, self_rev, false); + + client->notifyChannelEdited(channel, key_vector, self_rev, false); + + if(old_default_channel) /* clients need to have one or more defualt channels... */ + client->notifyChannelEdited(old_default_channel, {property::CHANNEL_FLAG_DEFAULT}, self_rev, false); + }); + } + + if(server_channel_w_lock.owns_lock()) + server_channel_w_lock.unlock(); + + if(!channel_tree_read_lock.owns_lock()) + channel_tree_read_lock.lock(); + + if(update_clients_in_channel && this->server) { + for(const auto& client : this->server->getClientsByChannel(channel)) + client->updateChannelClientProperties(true, true); //TODO: May only update the talk power and not all? + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelMove(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + RESOLVE_CHANNEL_W(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + assert(channel); + if(channel->deleted) + return command_result{error::channel_is_deleted}; + + if(!cmd[0].has("order")) + cmd["order"] = 0; + + auto l_parent = channel_tree->findLinkedChannel(cmd["cpid"]); + shared_ptr l_order; + if(cmd[0].has("order")) { + l_order = channel_tree->findLinkedChannel(cmd["order"]); + if (!l_order && cmd["order"].as() != 0) return command_result{error::channel_invalid_id}; + } else { + l_order = l_parent ? l_parent->child_head : (this->server ? this->server->getChannelTree() : serverInstance->getChannelTree().get())->tree_head(); + while(l_order && l_order->next) + l_order = l_order->next; + } + + auto parent = l_parent ? dynamic_pointer_cast(l_parent->entry) : nullptr; + auto order = l_order ? dynamic_pointer_cast(l_order->entry) : nullptr; + + if((parent && parent->deleted) || (order && order->deleted)) + return command_result{error::channel_is_deleted, "parent channel order previous channel has been deleted"}; + + if(channel->parent() == parent && channel->channelOrder() == (order ? order->channelId() : 0)) + return command_result{error::ok}; + + if (channel->parent() != parent) + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_parent, 1, channel_id); + if ((order ? order->channelId() : 0) != channel->channelOrder()) + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_sortorder, 1, channel_id); + + { + auto min_channel_deep = this->calculate_permission(permission::i_channel_min_depth, 0, false); + auto max_channel_deep = this->calculate_permission(permission::i_channel_max_depth, 0, false); + + if(min_channel_deep.has_value || max_channel_deep.has_value) { + auto channel_deep = 0; + auto local_parent = l_parent; + while(local_parent) { + channel_deep++; + local_parent = local_parent->parent.lock(); + } + + if(min_channel_deep.has_value && (channel_deep < min_channel_deep.value && !min_channel_deep.has_infinite_power())) return command_result{permission::i_channel_min_depth}; + if(max_channel_deep.has_value && !permission::v2::permission_granted(channel_deep, max_channel_deep)) return command_result{permission::i_channel_max_depth}; + } + } + { + auto name = channel_tree->findChannel(channel->name(), parent); + if(name && name != channel) return command_result{error::channel_name_inuse}; + } + debugMessage(this->getServerId(), "Moving channel {} from old [{} | {}] to [{} | {}]", channel->name(), channel->channelOrder(), channel->parent() ? channel->parent()->channelId() : 0, order ? order->channelId() : 0, parent ? parent->channelId() : 0); + + if (!channel_tree->move_channel(channel, parent, order)) return command_result{error::channel_invalid_order, "Cant change order id"}; + + deque> channel_type_updates; + { + auto flag_default = channel->defaultChannel(); + auto current_channel = channel; + do { + if(flag_default) { + if(current_channel->channelType() != ChannelType::permanent) { + current_channel->setChannelType(ChannelType::permanent); + channel_type_updates.push_front(current_channel); + } + } else if(current_channel->hasParent()) { + if(current_channel->channelType() < current_channel->parent()->channelType()) { + current_channel->setChannelType(current_channel->parent()->channelType()); + channel_type_updates.push_front(current_channel); + } + } + } while ((current_channel = dynamic_pointer_cast(current_channel->parent()))); + } + + if(this->server) { + auto self_rev = this->ref(); + this->server->forEachClient([&](const shared_ptr& client) { + unique_lock channel_lock(client->channel_lock); + for(const auto& type_update : channel_type_updates) + client->notifyChannelEdited(type_update, {property::CHANNEL_FLAG_PERMANENT, property::CHANNEL_FLAG_SEMI_PERMANENT}, self_rev, false); + + auto actions = client->channels->change_order(l_channel, l_parent, l_order); + std::deque deletions; + for(const auto& action : actions) { + switch (action.first) { + case ClientChannelView::NOTHING: + continue; + case ClientChannelView::ENTER_VIEW: + client->notifyChannelShow(action.second->channel(), action.second->previous_channel); + break; + case ClientChannelView::DELETE_VIEW: + deletions.push_back(action.second->channelId()); + break; + case ClientChannelView::MOVE: + client->notifyChannelMoved(action.second->channel(), action.second->previous_channel, _this.lock()); + break; + case ClientChannelView::REORDER: + client->notifyChannelEdited(action.second->channel(), {property::CHANNEL_ORDER}, self_rev, false); + break; + } + } + if(!deletions.empty()) + client->notifyChannelHide(deletions, false); + }); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelPermList(Command &cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + RESOLVE_CHANNEL_R(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + assert(channel); + + ACTION_REQUIRES_PERMISSION(permission::b_virtualserver_channel_permission_list, 1, channel->channelId()); + + if(this->getType() == ClientType::CLIENT_TEAMSPEAK && this->command_times.last_notify + this->command_times.notify_timeout < system_clock::now()) { + this->sendTSPermEditorWarning(); + } + + auto sids = cmd.hasParm("permsid"); + + Command result(this->notify_response_command("notifychannelpermlist")); + int index = 0; + result["cid"] = channel->channelId(); + + auto permission_mapper = serverInstance->getPermissionMapper(); + auto type = this->getType(); + auto permission_manager = channel->permissions(); + for (const auto &permission_data : permission_manager->permissions()) { + auto& permission = std::get<1>(permission_data); + if(permission.flags.value_set) { + if(sids) { + result[index]["permsid"] = permission_mapper->permission_name(type, std::get<0>(permission_data)); + } else { + result[index]["permid"] = std::get<0>(permission_data); + } + + result[index]["permvalue"] = permission.values.value; + result[index]["permnegated"] = permission.flags.negate; + result[index]["permskip"] = permission.flags.skip; + index++; + } + if(permission.flags.grant_set) { + if(sids) { + result[index]["permsid"] = permission_mapper->permission_name_grant(type, std::get<0>(permission_data)); + } else { + result[index]["permid"] = (uint16_t) (std::get<0>(permission_data) | PERM_ID_GRANT); + } + result[index]["permvalue"] = permission.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}; +} + +// +//channel_icon_id=18446744073297259750 +//channel_name +//channel_topic +//Desctiption has no extra parm +command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + RESOLVE_CHANNEL_R(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + assert(channel); + + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true); + + auto max_value = this->calculate_permission(permission::i_permission_modify_power, channel_id, 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, channel_id)); + auto updateClients = false, update_view = false, update_channel_properties = false; + + bool conOnError = cmd[0].has("continueonerror"); + + auto permission_manager = channel->permissions(); + 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, channel_id, true))) { + if(conOnError) continue; + return command_result{permission::i_permission_modify_power}; + } + + + if (grant) { + permission_manager->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); + } else { + permission_manager->set_permission( + permType, + {cmd[index]["permvalue"], 0}, + permission::v2::PermissionUpdateType::set_value, + permission::v2::PermissionUpdateType::do_nothing, + + cmd[index]["permskip"].as() ? 1 : 0, + cmd[index]["permnegated"].as() ? 1 : 0 + ); + updateClients |= permission_is_client_property(permType); + update_view |= permType == permission::i_channel_needed_view_power; + update_channel_properties |= channel->permission_require_property_update(permType); + + if (permType == permission::i_icon_id) { + if(this->server) { + auto self_ref = this->ref(); + this->server->forEachClient([&](std::shared_ptr cl) { + shared_lock client_channel_lock(cl->channel_lock); + cl->notifyChannelEdited(channel, {property::CHANNEL_ICON_ID}, self_ref, false); + }); + } + continue; + } + } + } + + /* broadcast the updated channel properties */ + if(update_channel_properties) { + auto updates = channel->update_properties_from_permissions(); + if(!updates.empty() && this->server){ + auto self_ref = this->ref(); + this->server->forEachClient([&](std::shared_ptr cl) { + shared_lock client_channel_lock(cl->channel_lock); + cl->notifyChannelEdited(channel, updates, self_ref, false); + }); + } + } + + if(updateClients && this->server) + for(const auto& client : this->server->getClientsByChannel(channel)) { + /* let them lock the server channel tree as well (read lock so does not matter) */ + client->updateChannelClientProperties(true, true); + client->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } + + if(update_view && this->server) { + auto l_source = this->server->channelTree->findLinkedChannel(channel->channelId()); + this->server->forEachClient([&](const shared_ptr& cl) { + /* server tree read lock still active */ + auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId()); + sassert(l_source); + if(cl->currentChannel) sassert(l_target); + + { + unique_lock client_channel_lock(cl->channel_lock); + + deque deleted; + for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) { + if(update_entry.first) + cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); + else deleted.push_back(update_entry.second->channelId()); + } + if(!deleted.empty()) + cl->notifyChannelHide(deleted, false); + } + }); + } + return command_result{error::ok};; +} + +command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) { + CMD_RESET_IDLE; + + RESOLVE_CHANNEL_R(cmd["cid"], true) + auto channel = dynamic_pointer_cast(l_channel->entry); + assert(channel); + + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true); + + auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, channel_id)); + bool conOnError = cmd[0].has("continueonerror"); + auto updateClients = false, update_view = false, update_channel_properties = false; + + auto permission_manager = channel->permissions(); + for (int index = 0; index < cmd.bulkCount(); index++) { + PARSE_PERMISSION(cmd); + + if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, channel_id, true))) { + if(conOnError) continue; + return command_result{permission::i_permission_modify_power}; + } + + if (grant) { + permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); + } else { + permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing); + updateClients |= permission_is_client_property(permType); + update_view |= permType == permission::i_channel_needed_view_power; + update_channel_properties |= channel->permission_require_property_update(permType); + } + } + + /* broadcast the updated channel properties */ + if(update_channel_properties) { + auto updates = channel->update_properties_from_permissions(); + if(!updates.empty() && this->server){ + auto self_ref = this->ref(); + this->server->forEachClient([&](std::shared_ptr cl) { + shared_lock client_channel_lock(cl->channel_lock); + cl->notifyChannelEdited(channel, updates, self_ref, false); + }); + } + } + + if(updateClients && this->server) + this->server->forEachClient([&](std::shared_ptr cl) { + if(cl->currentChannel == channel) { + cl->updateChannelClientProperties(true, true); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } + }); + if(update_view && this->server) { + this->server->forEachClient([&](std::shared_ptr cl) { + /* server tree read lock still active */ + auto l_source = cl->server->channelTree->findLinkedChannel(channel->channelId()); + auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId()); + sassert(l_source); + if(cl->currentChannel) sassert(l_target); + + { + unique_lock client_channel_lock(cl->channel_lock); + + deque deleted; + for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) { + if(update_entry.first) + cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); + else deleted.push_back(update_entry.second->channelId()); + } + if(!deleted.empty()) + cl->notifyChannelHide(deleted, false); + } + }); + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelClientPermList(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + RESOLVE_CHANNEL_R(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + if(!channel) return command_result{error::vs_critical}; + ACTION_REQUIRES_PERMISSION(permission::b_virtualserver_channelclient_permission_list, 1, channel_id); + + if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return command_result{error::client_invalid_id}; + auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"].as()); + + Command res(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifychannelclientpermlist" : ""); + + auto permissions = mgr->channel_permissions(channel->channelId()); + if(permissions.empty()) + return command_result{error::database_empty_result}; + + int index = 0; + res[index]["cid"] = channel->channelId(); + res[index]["cldbid"] = cmd["cldbid"].as(); + + auto sids = cmd.hasParm("permsid"); + auto permission_mapper = serverInstance->getPermissionMapper(); + auto type = this->getType(); + + for (const auto &permission_data : permissions) { + auto& permission = std::get<1>(permission_data); + if(permission.flags.value_set) { + if (sids) + res[index]["permsid"] = permission_mapper->permission_name(type, get<0>(permission_data)); + else + res[index]["permid"] = get<0>(permission_data); + res[index]["permvalue"] = permission.values.value; + + res[index]["permnegated"] = permission.flags.negate; + res[index]["permskip"] = permission.flags.skip; + index++; + } + + + if(permission.flags.grant_set) { + if (sids) + res[index]["permsid"] = permission_mapper->permission_name_grant(type, get<0>(permission_data)); + else + res[index]["permid"] = (get<0>(permission_data) | PERM_ID_GRANT); + res[index]["permvalue"] = permission.values.grant; + res[index]["permnegated"] = 0; + res[index]["permskip"] = 0; + index++; + } + } + + this->sendCommand(res); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) { + CMD_REF_SERVER(server_ref); + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto cldbid = cmd["cldbid"].as(); + if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) + return command_result{error::parameter_invalid, "Invalid manager db id"}; + + RESOLVE_CHANNEL_R(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + if(!channel) return command_result{error::vs_critical}; + + auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); + { + auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id); + ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id); + } + + auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, channel_id)); + + bool conOnError = cmd[0].has("continueonerror"), update_view = false; + auto cll = this->server->findClientsByCldbId(cldbid); + for (int index = 0; index < cmd.bulkCount(); index++) { + PARSE_PERMISSION(cmd); + + if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, channel_id, true))) { + if(conOnError) continue; + return command_result{permission::i_permission_modify_power}; + } + + if (grant) { + mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value); + } else { + mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing); + update_view = permType == permission::b_channel_ignore_view_power || permType == permission::i_channel_view_power; + } + } + + serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); + if (!cll.empty()) { + for (const auto &elm : cll) { + if(elm->update_cached_permissions()) /* update cached calculated permissions */ + elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + + if(elm->currentChannel == channel) { + elm->updateChannelClientProperties(true, true); + } else if(update_view) { + unique_lock client_channel_lock(this->channel_lock); + + auto elm_channel = elm->currentChannel; + if(elm_channel) { + deque deleted; + for(const auto& update_entry : elm->channels->update_channel_path(l_channel, this->server->channelTree->findLinkedChannel(elm->currentChannel->channelId()))) { + if(update_entry.first) + elm->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); + else deleted.push_back(update_entry.second->channelId()); + } + if(!deleted.empty()) + elm->notifyChannelHide(deleted, false); /* we've locked the tree before */ + } + } + + elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) { + CMD_REF_SERVER(server_ref); + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto cldbid = cmd["cldbid"].as(); + if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) + return command_result{error::parameter_invalid, "Invalid client db id"}; + + RESOLVE_CHANNEL_R(cmd["cid"], true); + auto channel = dynamic_pointer_cast(l_channel->entry); + if(!channel) return command_result{error::vs_critical}; + + auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); + { + auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id); + ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id); + } + + auto max_value = this->calculate_permission(permission::i_permission_modify_power, channel_id, 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, channel_id)); + auto update_view = false; + + bool conOnError = cmd[0].has("continueonerror"); + auto onlineClientInstances = this->server->findClientsByCldbId(cldbid); + + 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, channel_id, true))) { + if(conOnError) continue; + return command_result{permission::i_permission_modify_power}; + } + + if (grant) { + mgr->set_channel_permission(permType, channel->channelId(), {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); + } else { + mgr->set_channel_permission(permType, channel->channelId(), {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0); + update_view = permType == permission::b_channel_ignore_view_power || permType == permission::i_channel_view_power; + } + } + + serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); + if (!onlineClientInstances.empty()) + for (const auto &elm : onlineClientInstances) { + if (elm->update_cached_permissions()) /* update cached calculated permissions */ + elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + + if(elm->currentChannel == channel) { + elm->updateChannelClientProperties(true, true); + } else if(update_view) { + unique_lock client_channel_lock(this->channel_lock); + + auto elm_channel = elm->currentChannel; + if(elm_channel) { + deque deleted; + for(const auto& update_entry : elm->channels->update_channel_path(l_channel, this->server->channelTree->findLinkedChannel(elm->currentChannel->channelId()))) { + if(update_entry.first) + elm->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); + else deleted.push_back(update_entry.second->channelId()); + } + if(!deleted.empty()) + elm->notifyChannelHide(deleted, false); /* we've locked the tree before */ + } + } + elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } + + return command_result{error::ok}; +} + + +command_result ConnectedClient::handleCommandChannelFind(Command &cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + string pattern = cmd["pattern"]; + std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); + + Command res(""); + int index = 0; + for (const auto &cl : (this->server ? this->server->channelTree : serverInstance->getChannelTree().get())->channels()) { + string name = cl->name(); + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + if (name.find(pattern) != std::string::npos) { + res[index]["cid"] = cl->channelId(); + res[index]["channel_name"] = cl->name(); + index++; + } + } + if (index == 0) return command_result{error::database_empty_result}; + this->sendCommand(res); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandChannelInfo(Command &cmd) { + std::shared_ptr channel = (this->server ? this->server->channelTree : serverInstance->getChannelTree().get())->findChannel(cmd["cid"].as()); + if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; + + Command res(""); + + for (const auto &prop : channel->properties().list_properties(property::FLAG_CHANNEL_VIEW | property::FLAG_CHANNEL_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) + res[prop.type().name] = prop.value(); + + res["seconds_empty"] = channel->emptySince(); + res["pid"] = res["cpid"].string(); + this->sendCommand(res); + + return command_result{error::ok}; +} + + + + + + + + + + + + + + diff --git a/server/src/client/command_handler/client.cpp b/server/src/client/command_handler/client.cpp new file mode 100644 index 0000000..934c5bd --- /dev/null +++ b/server/src/client/command_handler/client.cpp @@ -0,0 +1,1214 @@ +#include + +#include + +#include +#include +#include +#include +#include "../../build.h" +#include "../ConnectedClient.h" +#include "../InternalClient.h" +#include "../../server/file/FileServer.h" +#include "../../server/VoiceServer.h" +#include "../voice/VoiceClient.h" +#include "PermissionManager.h" +#include "../../InstanceHandler.h" +#include "../../server/QueryServer.h" +#include "../file/FileClient.h" +#include "../music/MusicClient.h" +#include "../query/QueryClient.h" +#include "../../weblist/WebListManager.h" +#include "../../manager/ConversationManager.h" +#include "../../manager/PermissionNameMapper.h" +#include +#include +#include + +#include "helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::experimental::filesystem; +using namespace std::chrono; +using namespace std; +using namespace ts; +using namespace ts::server; +using namespace ts::token; + +#define QUERY_PASSWORD_LENGTH 12 + +command_result ConnectedClient::handleCommandClientGetVariables(Command &cmd) { + CMD_REQ_SERVER; + auto client = this->server->findClient(cmd["clid"].as()); + shared_lock tree_lock(this->channel_lock); + + if (!client || (client != this && !this->isClientVisible(client, false))) + return command_result{error::client_invalid_id, ""}; + + deque> props; + for (auto &prop : client->properties()->list_properties(property::FLAG_CLIENT_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { + props.push_back(property::info((property::ClientProperties) prop.type().property_index)); + } + + this->notifyClientUpdated(client, props, false); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientKick(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto client = this->server->findClient(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; + 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; + 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); + } else { + this->server->notify_client_kick(client, this->ref(), cmd["reasonmsg"].as(), nullptr); + client->closeConnection(system_clock::now() + seconds(1)); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientGetIds(Command &cmd) { + CMD_REQ_SERVER; + + bool error = false; + bool found = false; + auto client_list = this->server->getClients(); + + Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientids" : ""); + int result_index = 0; + + for(int index = 0; index < cmd.bulkCount(); index++) { + auto unique_id = cmd[index]["cluid"].as(); + for(const auto& entry : client_list) { + if(entry->getUid() == unique_id) { + if(!config::server::show_invisible_clients_as_online && !this->channels->channel_visible(entry->currentChannel, nullptr)) + continue; + + notify[result_index]["name"] = entry->getDisplayName(); + notify[result_index]["clid"] = entry->getClientId(); + notify[result_index]["cluid"] = entry->getUid(); + result_index++; + found = true; + } + } + if(found) found = false; + else error = false; + } + string uid = cmd["cluid"]; + + if(result_index > 0) { + this->sendCommand(notify); + } + if(error) { + return command_result{error::database_empty_result}; + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientMove(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(10); + + 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); + if(!target_client) { + return command_result{error::client_invalid_id, "Invalid target clid"}; + } + + if(!target_client->currentChannel) { + if(target_client != this) + return command_result{error::client_invalid_id, "Invalid target clid"}; + } + auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); + if (!channel) { + return command_result{error::channel_invalid_id}; + } + + auto permission_cache = make_shared(); + if(!cmd[0].has("cpw")) + cmd["cpw"] = ""; + if (!channel->passwordMatch(cmd["cpw"], true)) + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) + return command_result{error::channel_invalid_password}; + + auto permission_error = this->calculate_and_get_join_state(channel); + if(permission_error != permission::unknown) return command_result{permission_error}; + + if (!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as() || !channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as()) { + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_maxclients, channel->channelId()))) { + if(!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as()) { + auto maxClients = channel->properties()[property::CHANNEL_MAXCLIENTS].as(); + if (maxClients >= 0 && maxClients <= this->server->getClientsByChannel(channel).size()) + return command_result{error::channel_maxclients_reached}; + } + if(!channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as()) { + shared_ptr family_root; + + if(channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as()) { + family_root = channel; + while(family_root && family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as()) family_root = family_root->parent(); + } + if(family_root && !family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED]) { //Could not be CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED + auto maxClients = family_root->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as(); + auto clients = 0; + for(const auto& entry : this->server->getClientsByChannelRoot(channel, false)) if(entry.get() != this) clients++; //Dont count the client itself + if (maxClients >= 0 && maxClients <= clients) + return command_result{error::channel_maxfamily_reached}; + } + } + } + } + + if (target_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, + channel, + target_client == this ? nullptr : _this.lock(), + "", + target_client == this ? ViewReasonId::VREASON_USER_ACTION : ViewReasonId::VREASON_MOVED, + true, + server_channel_w_lock + ); + + if(oldChannel) { + if(!server_channel_w_lock.owns_lock()) + server_channel_w_lock.lock(); + if(oldChannel->channelType() == ChannelType::temporary && oldChannel->properties()[property::CHANNEL_DELETE_DELAY].as() == 0) + if(this->server->getClientsByChannelRoot(oldChannel, false).empty()) + this->server->delete_channel(dynamic_pointer_cast(oldChannel), this->ref(), "temporary auto delete", server_channel_w_lock); + if(server_channel_w_lock.owns_lock()) + server_channel_w_lock.unlock(); + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientPoke(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto client = this->server->findClient(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()); + + client->notifyClientPoke(_this.lock(), cmd["msg"]); + return command_result{error::ok}; +} + + +command_result ConnectedClient::handleCommandClientChatComposing(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(0); + + auto client = this->server->findClient(cmd["clid"].as()); + if (!client) return command_result{error::client_invalid_id}; + + client->notifyClientChatComposing(_this.lock()); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientChatClosed(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto client = this->server->findClient(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) { + return weak.lock() == client; + }), this->openChats.end()); + } + { + unique_lock channel_lock(client->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()); + } + client->notifyClientChatClosed(_this.lock()); + return command_result{error::ok}; +} + +//ftgetfilelist cid=1 cpw path=\/ return_code=1:x +//Answer: +//1 .. n +// notifyfilelist cid=1 path=\/ return_code=1:x name=testFile size=35256 datetime=1509459767 type=1|name=testDir size=0 datetime=1509459741 type=0|name=testDir_2 size=0 datetime=1509459763 type=0 +//notifyfilelistfinished cid=1 path=\/ +inline void cmd_filelist_append_files(ServerId sid, Command &command, vector> files) { + int index = 0; + + logTrace(sid, "Sending file list for path {}", command["path"].string()); + for (const auto& fileEntry : files) { + logTrace(sid, " - {} ({})", fileEntry->name, fileEntry->type == file::FileType::FILE ? "file" : "directory"); + + command[index]["name"] = fileEntry->name; + command[index]["datetime"] = std::chrono::duration_cast(fileEntry->lastChanged.time_since_epoch()).count(); + command[index]["type"] = fileEntry->type; + if (fileEntry->type == file::FileType::FILE) + command[index]["size"] = static_pointer_cast(fileEntry)->fileSize; + else + command[index]["size"] = 0; + index++; + } +} + +#define CMD_REQ_FSERVER if(!serverInstance->getFileServer()) return command_result{error::vs_critical, "file server not started yet!"} + +//start=0 duration=10 +//pattern=%asd% + +struct ClientDbArgs { + shared_ptr server; + int index = 0; + int offset = 0; + int resultIndex = 0; + bool showIp = false; + bool largeInfo = false; + Command *result = nullptr; +}; + +command_result ConnectedClient::handleCommandClientDbList(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dblist, 1); + + Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdblist" : ""); + + if (!cmd[0].has("start")) + cmd["start"] = 0; + if (!cmd[0].has("duration")) + cmd["duration"] = 20; + if (cmd[0]["duration"].as() > 2000) cmd["duration"] = 2000; + if (cmd[0]["duration"].as() < 1) cmd["duration"] = 1; + + auto maxIndex = cmd["start"].as() + cmd["duration"].as(); + ClientDbArgs args; + args.server = this->server; + args.offset = cmd["start"].as(); + args.result = ¬ify; + args.resultIndex = 0; + args.index = 0; + args.showIp = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); + args.largeInfo = cmd.hasParm("details"); + + (LOG_SQL_CMD)(sql::command(this->server->getSql(), "SELECT * FROM `clients` WHERE `serverId` = :sid ORDER BY `cldbid` ASC" + (maxIndex > 0 ? " LIMIT " + to_string(maxIndex) : ""), variable{":sid", this->server->getServerId()}).query( + [](ClientDbArgs *pArgs, int length, char **values, char **column) { + pArgs->index++; + if (pArgs->offset < pArgs->index) { + ClientDbId id = 0; + string uid, name, ip; + string created = "0", lastConnected = "0", connections = "0"; + for (int index = 0; index < length; index++) { + string key = column[index]; + if (key == "cldbid") + id = stoll(values[index]); + else if (key == "clientUid") + uid = values[index]; + else if (key == "firstConnect") + created = values[index]; + else if (key == "lastConnect") + lastConnected = values[index]; + else if (key == "connections") + connections = values[index]; + else if (key == "lastName") + name = values[index]; + } + + pArgs->result->operator[](pArgs->resultIndex)["cldbid"] = id; + pArgs->result->operator[](pArgs->resultIndex)["client_unique_identifier"] = uid; + pArgs->result->operator[](pArgs->resultIndex)["client_nickname"] = name; + pArgs->result->operator[](pArgs->resultIndex)["client_created"] = created; + pArgs->result->operator[](pArgs->resultIndex)["client_lastconnected"] = lastConnected; + pArgs->result->operator[](pArgs->resultIndex)["client_totalconnections"] = connections; + pArgs->result->operator[](pArgs->resultIndex)["client_description"] = ""; + + auto props = serverInstance->databaseHelper()->loadClientProperties(pArgs->server, id, ClientType::CLIENT_TEAMSPEAK); + if (props) { + pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as(); + pArgs->result->operator[](pArgs->resultIndex)["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as(); + + if (pArgs->largeInfo) { + pArgs->result->operator[](pArgs->resultIndex)["client_badges"] = (*props)[property::CLIENT_BADGES].as(); + pArgs->result->operator[](pArgs->resultIndex)["client_version"] = (*props)[property::CLIENT_VERSION].as(); + pArgs->result->operator[](pArgs->resultIndex)["client_platform"] = (*props)[property::CLIENT_PLATFORM].as(); + pArgs->result->operator[](pArgs->resultIndex)["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as(); + } + } + if (!pArgs->showIp) + pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = "hidden"; + pArgs->resultIndex++; + } + return 0; + }, &args)); + + if (args.resultIndex == 0) return command_result{error::database_empty_result}; + if (cmd.hasParm("count")) { + size_t result = 0; + sql::command(this->server->getSql(), "SELECT COUNT(*) AS `count` FROM `clients` WHERE `serverId` = :sid", variable{":sid", this->server->getServerId()}).query([](size_t *ptr, int, char **v, char **) { + *ptr = static_cast(stoll(v[0])); + return 0; + }, &result); + notify[0]["count"] = result; + } + this->sendCommand(notify); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientDBEdit(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_modify_dbproperties, 1); + + if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return command_result{error::database_empty_result, "invalid cldbid"}; + auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK); + + for (auto &elm : cmd[0].keys()) { + if (elm == "cldbid") continue; + + auto info = property::info(elm); + if(*info == property::CLIENT_UNDEFINED) { + logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change someone's db entry, but the entry in unknown: " + elm); + continue; + } + if(!info->validate_input(cmd[elm].as())) { + logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[elm].as() + "', Property: '" + info->name + "')"); + continue; + } + (*props)[info] = cmd[elm].string(); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientEdit(ts::Command &cmd) { + CMD_REQ_SERVER; + + auto client = this->server->findClient(cmd["clid"].as()); + if (!client) return command_result{error::client_invalid_id}; + return this->handleCommandClientEdit(cmd, client); +} + +command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std::shared_ptr& client) { + assert(client); + auto self = client == this; + CMD_CHK_AND_INC_FLOOD_POINTS(self ? 15 : 25); + CMD_RESET_IDLE; + + + bool update_talk_rights = false; + unique_ptr> nickname_lock; + deque> keys; + for(const auto& key : cmd[0].keys()) { + if(key == "return_code") continue; + if(key == "clid") continue; + + const auto &info = property::info(key); + if(*info == property::CLIENT_UNDEFINED) { + logError(this->getServerId(), R"([{}] Tried to change a not existing client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string()); + continue; + } + + if((info->flags & property::FLAG_USER_EDITABLE) == 0) { + logError(this->getServerId(), R"([{}] Tried to change a not user editable client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string()); + continue; + } + + if(!info->validate_input(cmd[key].as())) { + logError(this->getServerId(), R"([{}] Tried to change a client property to an invalid value for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string()); + continue; + } + if(client->properties()[info].as() == cmd[key].as()) continue; + + if (*info == property::CLIENT_DESCRIPTION) { + if (self) { + ACTION_REQUIRES_PERMISSION(permission::b_client_modify_own_description, 1, client->getChannelId()); + } else if(client->getType() == ClientType::CLIENT_MUSIC) { + if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); + } + } else { + ACTION_REQUIRES_PERMISSION(permission::b_client_modify_description, 1, client->getChannelId()); + } + + string value = cmd["client_description"].string(); + if (count_characters(value) > 200) return command_result{error::parameter_invalid, "Invalid description length. A maximum of 200 characters is allowed!"}; + } else if (*info == property::CLIENT_IS_TALKER) { + ACTION_REQUIRES_PERMISSION(permission::b_client_set_flag_talker, 1, client->getChannelId()); + cmd["client_is_talker"] = cmd["client_is_talker"].as(); + cmd["client_talk_request"] = 0; + update_talk_rights = true; + + keys.emplace_back(property::CLIENT_IS_TALKER, "client_is_talker"); + keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request"); + continue; + } else if(*info == property::CLIENT_NICKNAME) { + if(!self) { + if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; + if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_rename_power, client->calculate_permission(permission::i_client_music_needed_rename_power, client->getChannelId()), client->getChannelId()); + } + } + + string name = cmd["client_nickname"].string(); + if (count_characters(name) < 3) return command_result{error::parameter_invalid, "Invalid name length. A minimum of 3 characters is required!"}; + if (count_characters(name) > 30) return command_result{error::parameter_invalid, "Invalid name length. A maximum of 30 characters is allowed!"}; + + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, client->getClientId()))) { + auto banRecord = serverInstance->banManager()->findBanByName(this->getServerId(), name); + if (banRecord) + return command_result{error::client_nickname_inuse, string() + "This nickname is " + (banRecord->serverId == 0 ? "globally " : "") + "banned for the reason: " + banRecord->reason}; + } + if (this->server) { + nickname_lock = std::make_unique>(this->server->client_nickname_lock); + bool self = false; + for (const auto &cl : this->server->getClients()) { + if (cl->getDisplayName() == cmd["client_nickname"].string()) { + if(cl == this) + self = true; + else + return command_result{error::client_nickname_inuse, "This nickname is already in use"}; + } + } + if(self) { + nickname_lock.reset(); + continue; + } + } + } else if(*info == property::CLIENT_PLAYER_VOLUME) { + if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; + if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); + } + auto bot = dynamic_pointer_cast(client); + assert(bot); + + auto volume = cmd["player_volume"].as(); + + auto max_volume = this->calculate_permission(permission::i_client_music_create_modify_max_volume, client->getClientId()); + if(max_volume.has_value && !permission::v2::permission_granted(volume * 100, max_volume)) + return command_result{permission::i_client_music_create_modify_max_volume}; + + bot->volume_modifier(cmd["player_volume"]); + } else if(*info == property::CLIENT_IS_CHANNEL_COMMANDER) { + if(!self) { + if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; + if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); + } + } + + if(cmd["client_is_channel_commander"].as()) + ACTION_REQUIRES_PERMISSION(permission::b_client_use_channel_commander, 1, client->getChannelId()); + } else if(*info == property::CLIENT_IS_PRIORITY_SPEAKER) { + //FIXME allow other to remove this thing + if(!self) { + if(client->getType() != ClientType::CLIENT_MUSIC) + return command_result{error::client_invalid_type}; + if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) + ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getClientId()), client->getClientId()); + } + + if(cmd["client_is_priority_speaker"].as()) + ACTION_REQUIRES_PERMISSION(permission::b_client_use_priority_speaker, 1, client->getChannelId()); + } else if (self && key == "client_talk_request") { + CMD_CHK_AND_INC_FLOOD_POINTS(20); + ACTION_REQUIRES_PERMISSION(permission::b_client_request_talker, 1, client->getChannelId()); + + if (cmd["client_talk_request"].as()) + cmd["client_talk_request"] = duration_cast(system_clock::now().time_since_epoch()).count(); + else + cmd["client_talk_request"] = 0; + keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request"); + continue; + } else if (self && key == "client_badges") { + std::string str = cmd[key]; + size_t index = 0; + int badgesTags = 0; + do { + index = str.find("badges", index); + if (index < str.length()) badgesTags++; + index++; + } while (index < str.length() && index != 0); + if (badgesTags >= 2) { + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_allow_invalid_badges, client->getClientId()))) + ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_badges, this->server ? this->server->serverAdmin : dynamic_pointer_cast(serverInstance->getInitialServerAdmin()), true); + return command_result{error::parameter_invalid, "Invalid badges"}; + } + //FIXME stuff here + } else if(!self && key == "client_version") { + if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; + if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); + } + } else if(!self && key == "client_platform") { + if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; + if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); + } + } else if(!self && key == "client_country") { + if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; + if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); + } + } else if(!self && (*info == property::CLIENT_FLAG_NOTIFY_SONG_CHANGE/* || *info == property::CLIENT_NOTIFY_SONG_MESSAGE*/)) { + if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; + if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); + } + } else if(!self && key == "client_uptime_mode") { + if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type}; + if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); + } + + if(cmd[key].as() == MusicClient::UptimeMode::TIME_SINCE_SERVER_START) { + cmd["client_lastconnected"] = duration_cast(this->server->startTimestamp.time_since_epoch()).count(); + } else { + string value = client->properties()[property::CLIENT_CREATED]; + if(value.empty()) + value = "0"; + cmd["client_lastconnected"] = value; + } + + keys.emplace_back(property::CLIENT_LASTCONNECTED, "client_lastconnected"); + } else if(!self && *info == property::CLIENT_BOT_TYPE) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId()); + auto type = cmd["client_bot_type"].as(); + if(type == MusicClient::Type::TEMPORARY) { + ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_temporary, 1, client->getChannelId()); + } else if(type == MusicClient::Type::SEMI_PERMANENT) { + ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_semi_permanent, 1, client->getChannelId()); + } else if(type == MusicClient::Type::PERMANENT) { + ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_permanent, 1, client->getChannelId()); + } else + return command_result{error::parameter_invalid}; + } else if(*info == property::CLIENT_AWAY_MESSAGE) { + if(!self) continue; + + if(cmd["client_away_message"].string().length() > 256) + return command_result{error::parameter_invalid}; + } else if(!self) { /* dont edit random properties of other clients. For us self its allowed to edit the rest without permissions */ + continue; + } + + keys.emplace_back((property::ClientProperties) info->property_index, key); + } + + deque updates; + for(const auto& key : keys) { + if(key.first == property::CLIENT_IS_PRIORITY_SPEAKER) { + client->clientPermissions->set_permission(permission::b_client_is_priority_speaker, {1, 0}, cmd["client_is_priority_speaker"].as() ? permission::v2::PermissionUpdateType::set_value : permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing); + } + client->properties()[key.first] = cmd[0][key.second].value(); + updates.push_back(key.first); + } + if(update_talk_rights) + client->updateTalkRights(client->properties()[property::CLIENT_TALK_POWER]); + + if(this->server) + this->server->notifyClientPropertyUpdates(client, updates); + nickname_lock.reset(); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientUpdate(Command &cmd) { + return this->handleCommandClientEdit(cmd, _this.lock()); +} + +command_result ConnectedClient::handleCommandClientMute(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + + auto client = this->server->findClient(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); + } + + if (config::voice::notifyMuted) + client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), 0, system_clock::now(), config::messages::mute_notify_message); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientUnmute(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + + auto client = this->server->findClient(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) { + auto c = weak.lock(); + return !c || c == client; + }), this->mutedClients.end()); + } + + if (config::voice::notifyMuted) + client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), 0, system_clock::now(), config::messages::unmute_notify_message); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientList(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + + bool allow_ip = false; + if (cmd.hasParm("ip")) + allow_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); + Command result(""); + + int index = 0; + this->server->forEachClient([&](shared_ptr client) { + if (client->getType() == ClientType::CLIENT_INTERNAL) return; + + result[index]["clid"] = client->getClientId(); + if (client->getChannel()) + result[index]["cid"] = client->getChannel()->channelId(); + else result[index]["cid"] = 0; + result[index]["client_database_id"] = client->getClientDatabaseId(); + result[index]["client_nickname"] = client->getDisplayName(); + result[index]["client_type"] = client->getType(); + + if (cmd.hasParm("uid")) + result[index]["client_unique_identifier"] = client->getUid(); + if (cmd.hasParm("away")) { + result[index]["client_away"] = client->properties()[property::CLIENT_AWAY].as(); + result[index]["client_away_message"] = client->properties()[property::CLIENT_AWAY_MESSAGE].as(); + } + if (cmd.hasParm("groups")) { + result[index]["client_channel_group_id"] = client->properties()[property::CLIENT_CHANNEL_GROUP_ID].as(); + result[index]["client_servergroups"] = client->properties()[property::CLIENT_SERVERGROUPS].as(); + result[index]["client_channel_group_inherited_channel_id"] = client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID].as(); + } + if (cmd.hasParm("times")) { + result[index]["client_idle_time"] = duration_cast(system_clock::now() - client->idleTimestamp).count(); + result[index]["client_total_online_time"] = client->properties()[property::CLIENT_TOTAL_ONLINE_TIME].as() + duration_cast(system_clock::now() - client->lastOnlineTimestamp).count(); + result[index]["client_month_online_time"] = client->properties()[property::CLIENT_MONTH_ONLINE_TIME].as() + duration_cast(system_clock::now() - client->lastOnlineTimestamp).count(); + result[index]["client_idle_time"] = duration_cast(system_clock::now() - client->idleTimestamp).count(); + result[index]["client_created"] = client->properties()[property::CLIENT_CREATED].as(); + result[index]["client_lastconnected"] = client->properties()[property::CLIENT_LASTCONNECTED].as(); + } + if (cmd.hasParm("info")) { + result[index]["client_version"] = client->properties()[property::CLIENT_VERSION].as(); + result[index]["client_platform"] = client->properties()[property::CLIENT_PLATFORM].as(); + } + + if (cmd.hasParm("badges")) + result[index]["client_badges"] = client->properties()[property::CLIENT_BADGES].as(); + if (cmd.hasParm("country")) + result[index]["client_country"] = client->properties()[property::CLIENT_COUNTRY].as(); + if (cmd.hasParm("ip")) + result[index]["connection_client_ip"] = allow_ip ? client->properties()[property::CONNECTION_CLIENT_IP].as() : "hidden"; + if (cmd.hasParm("icon")) + result[index]["client_icon_id"] = client->properties()[property::CLIENT_ICON_ID].as(); + + if (cmd.hasParm("voice")) { + result[index]["client_talk_power"] = client->properties()[property::CLIENT_TALK_POWER].as(); + result[index]["client_flag_talking"] = client->properties()[property::CLIENT_FLAG_TALKING].as(); + result[index]["client_input_muted"] = client->properties()[property::CLIENT_INPUT_MUTED].as(); + result[index]["client_output_muted"] = client->properties()[property::CLIENT_OUTPUT_MUTED].as(); + result[index]["client_input_hardware"] = client->properties()[property::CLIENT_INPUT_HARDWARE].as(); + result[index]["client_output_hardware"] = client->properties()[property::CLIENT_OUTPUT_HARDWARE].as(); + result[index]["client_is_talker"] = client->properties()[property::CLIENT_IS_TALKER].as(); + result[index]["client_is_priority_speaker"] = client->properties()[property::CLIENT_IS_PRIORITY_SPEAKER].as(); + result[index]["client_is_recording"] = client->properties()[property::CLIENT_IS_RECORDING].as(); + result[index]["client_is_channel_commander"] = client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as(); + + } + index++; + }); + this->sendCommand(result); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientGetDBIDfromUID(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + + deque unique_ids; + for(int index = 0; index < cmd.bulkCount(); index++) + unique_ids.push_back(cmd[index]["cluid"].as()); + + auto res = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->server, unique_ids); + if (res.empty()) return command_result{error::database_empty_result}; + + Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdbidfromuid" : ""); + int result_index = 0; + for(auto& info : res) { + result[result_index]["cluid"] = info->uniqueId; + result[result_index]["cldbid"] = info->cldbid; + result_index++; + } + this->sendCommand(result); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientGetNameFromDBID(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + + deque dbids; + for(int index = 0; index < cmd.bulkCount(); index++) + dbids.push_back(cmd[index]["cldbid"].as()); + + auto res = serverInstance->databaseHelper()->queryDatabaseInfo(this->server, dbids); + if (res.empty()) return command_result{error::database_empty_result}; + + Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetnamefromdbid" : ""); + int result_index = 0; + for(auto& info : res) { + result[result_index]["cluid"] = info->uniqueId; + result[result_index]["cldbid"] = info->cldbid; + result[result_index]["name"] = info->lastName; + result[result_index]["clname"] = info->lastName; + result_index++; + } + this->sendCommand(result); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientGetNameFromUid(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + + deque unique_ids; + for(int index = 0; index < cmd.bulkCount(); index++) + unique_ids.push_back(cmd[index]["cluid"].as()); + + auto res = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->server, unique_ids); + if (res.empty()) return command_result{error::database_empty_result}; + + Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientnamefromuid" : ""); + int result_index = 0; + for(auto& info : res) { + result[result_index]["cluid"] = info->uniqueId; + result[result_index]["cldbid"] = info->cldbid; + result[result_index]["name"] = info->lastName; + result[result_index]["clname"] = info->lastName; + result_index++; + } + this->sendCommand(result); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientGetUidFromClid(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + + bool error = false; + bool found = false; + auto client_list = this->server->getClients(); + + Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetuidfromclid" : ""); + int result_index = 0; + + for(int index = 0; index < cmd.bulkCount(); index++) { + auto client_id = cmd[index]["clid"].as(); + for(const auto& entry : client_list) { + if(entry->getClientId() == client_id) { + notify[result_index]["clname"] = entry->getDisplayName(); + notify[result_index]["clid"] = entry->getClientId(); + notify[result_index]["cluid"] = entry->getUid(); + notify[result_index]["cldbid"] = entry->getClientDatabaseId(); + result_index++; + found = true; + } + } + if(found) found = false; + else error = false; + } + + if(result_index > 0) + this->sendCommand(notify); + if(error) + return command_result{error::database_empty_result}; + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto cldbid = cmd["cldbid"].as(); + if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) + return command_result{error::client_invalid_id}; + auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); + + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0)); + 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"); + auto update_channels = false; + 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) { + mgr->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); + } else { + mgr->set_permission(permType, {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0); + update_channels |= permission_is_client_property(permType); + } + } + serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); + auto onlineClients = this->server->findClientsByCldbId(cldbid); + if (!onlineClients.empty()) + for (const auto &elm : onlineClients) { + if(elm->update_cached_permissions()) /* update cached calculated permissions */ + elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + if(update_channels) + elm->updateChannelClientProperties(true, true); + elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto cldbid = cmd["cldbid"].as(); + if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) + return command_result{error::client_invalid_id}; + auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0)); + + 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"); + auto onlineClients = this->server->findClientsByCldbId(cmd["cldbid"]); + auto update_channel = false; + 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) { + mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value); + } else { + mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing); + update_channel |= permission_is_client_property(permType); + } + } + + serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); + if (!onlineClients.empty()) + for (const auto &elm : onlineClients) { + if(elm->update_cached_permissions()) /* update cached calculated permissions */ + elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + if(update_channel) + elm->updateChannelClientProperties(true, true); + elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientPermList(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_permission_list, 1); + + if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return command_result{error::client_invalid_id}; + auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]); + if (!this->notifyClientPermList(cmd["cldbid"], mgr, cmd.hasParm("permsid"))) return command_result{error::database_empty_result}; + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientDbInfo(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dbinfo, 1); + + deque cldbids; + for(int index = 0; index < cmd.bulkCount(); index++) + cldbids.push_back(cmd[index]["cldbid"]); + + auto basic = serverInstance->databaseHelper()->queryDatabaseInfo(this->server, cldbids); + if (basic.empty()) return command_result{error::database_empty_result}; + + auto allow_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); + Command res(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyclientdbinfo" : ""); + + size_t index = 0; + for(const auto& info : basic) { + res[index]["client_base64HashClientUID"] = hex::hex(base64::validate(info->uniqueId) ? base64::decode(info->uniqueId) : info->uniqueId, 'a', 'q'); + res[index]["client_unique_identifier"] = info->uniqueId; + res[index]["client_nickname"] = info->lastName; + res[index]["client_database_id"] = info->cldbid; + res[index]["client_created"] = chrono::duration_cast(info->created.time_since_epoch()).count(); + res[index]["client_lastconnected"] = chrono::duration_cast(info->lastjoin.time_since_epoch()).count(); + res[index]["client_totalconnections"] = info->connections; + res[index]["client_database_id"] = info->cldbid; + + auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, info->cldbid, ClientType::CLIENT_TEAMSPEAK); + if (allow_ip) + res[index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as(); + else + res[index]["client_lastip"] = "hidden"; + + res[index]["client_icon_id"] = (*props)[property::CLIENT_ICON_ID].as(); + res[index]["client_badges"] = (*props)[property::CLIENT_BADGES].as(); + res[index]["client_version"] = (*props)[property::CLIENT_VERSION].as(); + res[index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as(); + res[index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as(); + res[index]["client_total_bytes_downloaded"] = (*props)[property::CLIENT_TOTAL_BYTES_DOWNLOADED].as(); + res[index]["client_total_bytes_uploaded"] = (*props)[property::CLIENT_TOTAL_BYTES_UPLOADED].as(); + res[index]["client_month_bytes_downloaded"] = (*props)[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); + res[index]["client_month_bytes_uploaded"] = (*props)[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); + res[index]["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as(); + res[index]["client_flag_avatar"] = (*props)[property::CLIENT_FLAG_AVATAR].as(); + + res[index]["client_month_online_time"] = (*props)[property::CLIENT_MONTH_ONLINE_TIME].as(); + res[index]["client_total_online_time"] = (*props)[property::CLIENT_TOTAL_ONLINE_TIME].as(); + index++; + } + + this->sendCommand(res); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientDBDelete(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_delete_dbproperties, 1); + + ClientDbId id = cmd["cldbid"]; + if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, id)) return command_result{error::database_empty_result}; + serverInstance->databaseHelper()->deleteClient(this->server, id); + + return command_result{error::ok}; +} + +struct DBFindArgs { + int index = 0; + bool full = false; + bool ip = false; + Command cmd{""}; +}; + +command_result ConnectedClient::handleCommandClientDBFind(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dbsearch, 1); + + bool uid = cmd.hasParm("uid"); + string pattern = cmd["pattern"]; + + DBFindArgs args{}; + args.cmd = Command(this->getType() == CLIENT_QUERY ? "" : "notifyclientdbfind"); + args.full = cmd.hasParm("details"); + args.ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); + auto res = sql::command(this->sql, string() + "SELECT * FROM `clients` WHERE `serverId` = :sid AND `" + (uid ? "clientUid" : "lastName") + "` LIKE '" + pattern + "' LIMIT 50", variable{":sid", this->server->getServerId()}).query( + [&](DBFindArgs *ptr, int len, char **values, char **names) { + for (int index = 0; index < len; index++) + if (strcmp(names[index], "cldbid") == 0) + ptr->cmd[ptr->index]["cldbid"] = values[index]; + else if (strcmp(names[index], "clientUid") == 0 && ptr->full) + ptr->cmd[ptr->index]["client_unique_identifier"] = values[index]; + else if (strcmp(names[index], "lastConnect") == 0 && ptr->full) + ptr->cmd[ptr->index]["client_lastconnected"] = values[index]; + else if (strcmp(names[index], "connections") == 0 && ptr->full) + ptr->cmd[ptr->index]["client_totalconnections"] = values[index]; + else if (strcmp(names[index], "lastName") == 0 && ptr->full) + ptr->cmd[ptr->index]["client_nickname"] = values[index]; + if (ptr->full) { + auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, ptr->cmd[ptr->index]["cldbid"], ClientType::CLIENT_TEAMSPEAK); + if (props) { + if (ptr->ip) { + ptr->cmd[ptr->index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as(); + } else { + ptr->cmd[ptr->index]["client_lastip"] = "hidden"; + } + ptr->cmd[ptr->index]["client_badges"] = (*props)[property::CLIENT_BADGES].as(); + ptr->cmd[ptr->index]["client_version"] = (*props)[property::CLIENT_VERSION].as(); + ptr->cmd[ptr->index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as(); + ptr->cmd[ptr->index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as(); + } + } + ptr->index++; + return 0; + }, &args); + auto pf = LOG_SQL_CMD; + pf(res); + if (args.index == 0) return command_result{error::database_empty_result}; + + this->sendCommand(args.cmd); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientInfo(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + + Command res(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyclientinfo" : ""); + bool trigger_error = false; + bool view_remote = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); + + int result_index = 0; + for(int index = 0; index < cmd.bulkCount(); index++) { + auto client_id = cmd[index]["clid"].as(); + if(client_id == 0) continue; + + auto client = this->server->findClient(client_id); + if(!client) { + trigger_error = true; + continue; + } + + for (const auto &key : client->properties()->list_properties(property::FLAG_CLIENT_VIEW | property::FLAG_CLIENT_VARIABLE | property::FLAG_CLIENT_INFO, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) + res[result_index][key.type().name] = key.value(); + if(view_remote) + res[result_index]["connection_client_ip"] = client->properties()[property::CONNECTION_CLIENT_IP].as(); + else + res[result_index]["connection_client_ip"] = "hidden"; + res[result_index]["client_idle_time"] = duration_cast(system_clock::now() - client->idleTimestamp).count(); + res[result_index]["connection_connected_time"] = duration_cast(system_clock::now() - client->connectTimestamp).count(); + { + auto channel = client->currentChannel; + if(channel) + res[result_index]["cid"] = channel->channelId(); + else + res[result_index]["cid"] = 0; + } + + result_index++; + } + + + if(result_index > 0) { + this->sendCommand(res); + } + + if(trigger_error || result_index == 0) + return command_result{error::client_invalid_id}; + else + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientFind(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + string pattern = cmd["pattern"]; + std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); + + Command res(""); + int index = 0; + for (const auto &cl : this->server->getClients()) { + string name = cl->getDisplayName(); + std::transform(name.begin(), name.end(), name.begin(), ::tolower); + if (name.find(pattern) != std::string::npos) { + res[index]["clid"] = cl->getClientId(); + res[index]["client_nickname"] = cl->getDisplayName(); + index++; + } + } + if (index == 0) return command_result{error::database_empty_result}; + this->sendCommand(res); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandClientSetServerQueryLogin(Command &cmd) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_create_modify_serverquery_login, 1); + + if(!cmd[0].has("client_login_password")) cmd["client_login_password"] = ""; + + std::string password = cmd["client_login_password"]; + if(password.empty()) + password = rnd_string(QUERY_PASSWORD_LENGTH); + + auto old = serverInstance->getQueryServer()->find_query_account_by_name(cmd["client_login_name"]); + if (old) { + if(old->unique_id == this->getUid()) { + serverInstance->getQueryServer()->change_query_password(old, password); + } else { + return command_result{error::client_not_logged_in}; + } + } else { + serverInstance->getQueryServer()->create_query_account(cmd["client_login_name"], this->getServerId(), this->getUid(), password); + } + + Command res(this->notify_response_command("notifyclientserverqueryloginpassword")); + res["client_login_password"] = password; + this->sendCommand(res); + + return command_result{error::ok}; +} + + + + + + + diff --git a/server/src/client/command_handler/file.cpp b/server/src/client/command_handler/file.cpp new file mode 100644 index 0000000..cb08cbf --- /dev/null +++ b/server/src/client/command_handler/file.cpp @@ -0,0 +1,486 @@ +// +// Created by wolverindev on 26.01.20. +// + +#include + +#include + +#include +#include +#include +#include +#include "../../build.h" +#include "../ConnectedClient.h" +#include "../InternalClient.h" +#include "../../server/file/FileServer.h" +#include "../../server/VoiceServer.h" +#include "../voice/VoiceClient.h" +#include "PermissionManager.h" +#include "../../InstanceHandler.h" +#include "../../server/QueryServer.h" +#include "../file/FileClient.h" +#include "../music/MusicClient.h" +#include "../query/QueryClient.h" +#include "../../weblist/WebListManager.h" +#include "../../manager/ConversationManager.h" +#include "../../manager/PermissionNameMapper.h" +#include +#include +#include + +#include "helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::experimental::filesystem; +using namespace std::chrono; +using namespace std; +using namespace ts; +using namespace ts::server; +using namespace ts::token; + +#define QUERY_PASSWORD_LENGTH 12 + +//ftgetfilelist cid=1 cpw path=\/ return_code=1:x +//Answer: +//1 .. n +// notifyfilelist cid=1 path=\/ return_code=1:x name=testFile size=35256 datetime=1509459767 type=1|name=testDir size=0 datetime=1509459741 type=0|name=testDir_2 size=0 datetime=1509459763 type=0 +//notifyfilelistfinished cid=1 path=\/ +inline void cmd_filelist_append_files(ServerId sid, Command &command, vector> files) { + int index = 0; + + logTrace(sid, "Sending file list for path {}", command["path"].string()); + for (const auto& fileEntry : files) { + logTrace(sid, " - {} ({})", fileEntry->name, fileEntry->type == file::FileType::FILE ? "file" : "directory"); + + command[index]["name"] = fileEntry->name; + command[index]["datetime"] = std::chrono::duration_cast(fileEntry->lastChanged.time_since_epoch()).count(); + command[index]["type"] = fileEntry->type; + if (fileEntry->type == file::FileType::FILE) + command[index]["size"] = static_pointer_cast(fileEntry)->fileSize; + else + command[index]["size"] = 0; + index++; + } +} + +#define CMD_REQ_FSERVER if(!serverInstance->getFileServer()) return command_result{error::vs_critical, "file server not started yet!"} + +command_result ConnectedClient::handleCommandFTGetFileList(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_REQ_FSERVER; + std::string code = cmd["return_code"].size() > 0 ? cmd["return_code"].string() : ""; + + Command fileList(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyfilelist" : ""); + Command fileListFinished("notifyfilelistfinished"); + + fileList["path"] = cmd["path"].as(); + if(!code.empty()) fileList["return_code"] = code; + fileListFinished["path"] = cmd["path"].as(); + fileList["cid"] = cmd["cid"].as(); + fileListFinished["cid"] = cmd["cid"].as(); + + if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel + auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); + if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; + if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) + return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_browse_power, permission::i_ft_file_browse_power, true); + + cmd_filelist_append_files( + this->getServerId(), + fileList, + serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->resolveDirectory(this->server, channel, cmd["path"].as())) + ); + } else { + if (cmd["path"].as() == "/icons" || cmd["path"].as() == "/icons/") { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_icon_manage, 1); + cmd_filelist_append_files(this->getServerId(), fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->iconDirectory(this->server))); + } else if (cmd["path"].as() == "/") { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_icon_manage, 1); + cmd_filelist_append_files(this->getServerId(), fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->avatarDirectory(this->server))); + } else { + logMessage(this->getServerId(), "{} Requested file list for unknown path/name: path: {} name: {}", cmd["path"].string(), cmd["name"].string()); + return command_result{error::not_implemented}; + } + } + + if (fileList[0].has("name")) { + if(dynamic_cast(this)) { + dynamic_cast(this)->sendCommand0(fileList.build(), false, true); /* We need to process this directly else the order could get shuffled up! */ + this->sendCommand(fileListFinished); + } else { + this->sendCommand(fileList); + if(this->getType() != CLIENT_QUERY) + this->sendCommand(fileListFinished); + } + return command_result{error::ok}; + } else { + return command_result{error::database_empty_result}; + } +} + +//ftcreatedir cid=4 cpw dirname=\/TestDir return_code=1:17 +command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + CMD_REQ_FSERVER; + + auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); + if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; + if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) + return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_directory_create_power, permission::i_ft_directory_create_power, true); + + auto dir = serverInstance->getFileServer()->createDirectory(cmd["dirname"], serverInstance->getFileServer()->resolveDirectory(this->server, channel, cmd["path"].as())); + if (!dir) return command_result{error::file_invalid_path, "could not create dir"}; + + return command_result{error::ok}; +} + + +command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + CMD_REQ_FSERVER; + + std::vector> files; + + if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel + auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); + if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; + if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) + return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_delete_power, permission::i_ft_file_delete_power, true); + + for (int index = 0; index < cmd.bulkCount(); index++) + files.push_back(serverInstance->getFileServer()->findFile(cmd[index]["name"].as(), serverInstance->getFileServer()->resolveDirectory(this->server, channel))); + } else { + if (cmd["name"].string().find("/icon_") == 0 && cmd["path"].string().empty()) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_icon_manage, 1); + files.push_back(serverInstance->getFileServer()->findFile(cmd["name"].string(), serverInstance->getFileServer()->iconDirectory(this->server))); + } else if (cmd["name"].string().find("/avatar_") == 0 && cmd["path"].string().empty()) { + if (cmd["name"].string() != "/avatar_") { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_avatar_delete_other, 1); + + auto uid = cmd["name"].string().substr(strlen("/avatar_")); + auto avId = hex::hex(base64::decode(uid), 'a', 'q'); + + auto cls = this->server->findClientsByUid(uid); + for (const auto &cl : cls) { + cl->properties()[property::CLIENT_FLAG_AVATAR] = ""; + this->server->notifyClientPropertyUpdates(cl, deque{property::CLIENT_FLAG_AVATAR}); + } + + cmd["name"] = "/avatar_" + avId; + } else { + cmd["name"] = "/avatar_" + this->getAvatarId(); + this->properties()[property::CLIENT_FLAG_AVATAR] = ""; + this->server->notifyClientPropertyUpdates(_this.lock(), deque{property::CLIENT_FLAG_AVATAR}); + } + files.push_back(serverInstance->getFileServer()->findFile(cmd["name"].string(), serverInstance->getFileServer()->avatarDirectory(this->server))); + } else { + logError(this->getServerId(), "Unknown requested file to delete: {}", cmd["path"].as()); + return command_result{error::not_implemented}; + } + } + + for (const auto &file : files) { + if (!file) continue; + if (!serverInstance->getFileServer()->deleteFile(file)) { + logCritical(this->getServerId(), "Could not delete file {}/{}", file->path, file->name); + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandFTInitUpload(Command &cmd) { + CMD_REQ_SERVER; + CMD_REQ_FSERVER; + + std::shared_ptr directory = nullptr; + + if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel + auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); + if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; + if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) + return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_upload_power, permission::i_ft_file_upload_power, true); + directory = serverInstance->getFileServer()->resolveDirectory(this->server, channel); + } else { + if (cmd["path"].as().empty() && cmd["name"].as().find("/icon_") == 0) { + auto max_size = this->calculate_permission(permission::i_max_icon_filesize, 0); + if(max_size.has_value && !max_size.has_infinite_power() && (max_size.value < 0 || max_size.value < cmd["size"].as())) + return command_result{permission::i_max_icon_filesize}; + directory = serverInstance->getFileServer()->iconDirectory(this->server); + } else if (cmd["path"].as().empty() && cmd["name"].string() == "/avatar") { + auto max_size = this->calculate_permission(permission::i_client_max_avatar_filesize, 0); + if(max_size.has_value && !max_size.has_infinite_power() && (max_size.value < 0 || max_size.value < cmd["size"].as())) + return command_result{permission::i_client_max_avatar_filesize}; + + directory = serverInstance->getFileServer()->avatarDirectory(this->server); + cmd["name"] = "/avatar_" + this->getAvatarId(); + } else { + cerr << "Unknown requested directory: " << cmd["path"].as() << endl; + return command_result{error::not_implemented}; + } + } + + if (!directory || directory->type != file::FileType::DIRECTORY) { //Should not happen + cerr << "Invalid upload file path!" << endl; + return command_result{error::file_invalid_path, "could not resolve directory"}; + } + + { + auto server_quota = this->server->properties()[property::VIRTUALSERVER_UPLOAD_QUOTA].as(); + auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as(); + server_used_quota += cmd["size"].as(); + for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers()) + server_used_quota += trans->size; + for(const auto& trans : serverInstance->getFileServer()->running_file_transfers()) + server_used_quota += trans->remaining_bytes(); + if(server_quota >= 0 && server_quota * 1024 * 1024 < (int64_t) server_used_quota) return command_result{error::file_transfer_server_quota_exceeded}; + + auto client_quota = this->calculate_permission(permission::i_ft_quota_mb_upload_per_client, 0); + auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].as(); + client_used_quota += cmd["size"].as(); + for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers(_this.lock())) + client_used_quota += trans->size; + for(const auto& trans : serverInstance->getFileServer()->running_file_transfers(_this.lock())) + client_used_quota += trans->remaining_bytes(); + + if(client_quota.has_value && !client_quota.has_infinite_power() && (client_quota.value < 0 || client_quota.value * 1024 * 1024 < (int64_t) client_used_quota)) + return command_result{error::file_transfer_client_quota_exceeded}; + } + + if (cmd["overwrite"].as() && cmd["resume"].as()) return command_result{error::file_overwrite_excludes_resume}; + if (serverInstance->getFileServer()->findFile(cmd["name"].as(), directory) && !(cmd["overwrite"].as() || cmd["resume"].as())) + return command_result{error::file_already_exists, "file already exists"}; + + //Request: clientftfid=1 serverftfid=6 ftkey=itRNdsIOvcBiBg\/Xj4Ge51ZSrsShHuid port=30033 seekpos=0 + //Reqpose: notifystartupload clientftfid=4079 serverftfid=1 ftkey=aX9CFQbfaddHpOYxhQiSLu\/BumfVtPyP port=30033 seekpos=0 proto=1 + string error = "success"; + auto key = serverInstance->getFileServer()->generateUploadTransferKey(error, directory->path + "/" + directory->name + cmd["name"].string(), cmd["size"].as(), 0, _this.lock()); //TODO implement resume! + if (!key) return command_result{error::vs_critical}; + + Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifystartupload" : ""); + result["clientftfid"] = cmd["clientftfid"].as(); + result["ftkey"] = key->key; + + auto bindings = serverInstance->getFileServer()->list_bindings(); + if(!bindings.empty()) { + result["port"] = net::port(bindings[0]->address); + string ip = ""; + for(auto& entry : bindings) { + if(net::is_anybind(entry->address)) { + ip = ""; + break; + } + ip += net::to_string(entry->address, false) + ","; + } + if(!ip.empty()) + result["ip"] = ip; + } else { + return command_result{error::server_is_not_running, "file server is not bound to any address"}; + } + result["seekpos"] = 0; + result["proto"] = 1; + result["serverftfid"] = key->key_id; //TODO generate! + this->sendCommand(result); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandFTInitDownload(Command &cmd) { + CMD_REQ_SERVER; + CMD_REQ_FSERVER; + + std::shared_ptr directory = nullptr; + + if(!cmd[0].has("path")) cmd["path"] = ""; + + if (cmd[0].has("cid") && cmd["cid"] != (ChannelId) 0) { //Channel + auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); + if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; + + if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) + return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_download_power, permission::i_ft_file_download_power, true); + + directory = serverInstance->getFileServer()->resolveDirectory(this->server, channel); + } else { + if (cmd["path"].as().empty() && cmd["name"].as().find("/icon_") == 0) { + directory = serverInstance->getFileServer()->iconDirectory(this->server); + } else if (cmd["path"].as().empty() && cmd["name"].as().find("/avatar_") == 0) { //TODO fix avatar download not working + directory = serverInstance->getFileServer()->avatarDirectory(this->server); + } else { + cerr << "Unknown requested directory: " << cmd["path"].as() << endl; + return command_result{error::not_implemented}; + } + } + + if (!directory || directory->type != file::FileType::DIRECTORY) { //Should not happen + cerr << "Invalid download file path!" << endl; + return command_result{error::file_invalid_path, "could not resolve directory"}; + } + + if (!serverInstance->getFileServer()->findFile(cmd["name"].as(), directory)) + return command_result{error::file_not_found, "file not exists"}; + + string error = "success"; + auto key = serverInstance->getFileServer()->generateDownloadTransferKey(error, directory->path + "/" + directory->name + cmd["name"].as(), 0, _this.lock()); //TODO implement resume! + if (!key) return command_result{error::vs_critical, "cant generate key (" + error + ")"}; + + { + auto server_quota = this->server->properties()[property::VIRTUALSERVER_DOWNLOAD_QUOTA].as(); + auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as(); + server_used_quota += key->size; + for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers()) + server_used_quota += trans->size; + for(const auto& trans : serverInstance->getFileServer()->running_file_transfers()) + server_used_quota += trans->remaining_bytes(); + if(server_quota >= 0 && server_quota * 1024 * 1024 < (int64_t) server_used_quota) return command_result{error::file_transfer_server_quota_exceeded}; + + + auto client_quota = this->calculate_permission(permission::i_ft_quota_mb_download_per_client, 0); + auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); + client_used_quota += key->size; + for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers(_this.lock())) + client_used_quota += trans->size; + for(const auto& trans : serverInstance->getFileServer()->running_file_transfers(_this.lock())) + client_used_quota += trans->remaining_bytes(); + + if(client_quota.has_value && !client_quota.has_infinite_power() && (client_quota.value < 0 || client_quota.value * 1024 * 1024 < (int64_t) client_used_quota)) + return command_result{error::file_transfer_client_quota_exceeded}; + } + + Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifystartdownload" : ""); + result["clientftfid"] = cmd["clientftfid"].as(); + result["proto"] = 1; + result["serverftfid"] = key->key_id; + result["ftkey"] = key->key; + + auto bindings = serverInstance->getFileServer()->list_bindings(); + if(!bindings.empty()) { + result["port"] = net::port(bindings[0]->address); + string ip = ""; + for(auto& entry : bindings) { + if(net::is_anybind(entry->address)) { + ip = ""; + break; + } + ip += net::to_string(entry->address, false) + ","; + } + if(!ip.empty()) + result["ip"] = ip; + } else { + return command_result{error::server_is_not_running, "file server is not bound to any address"}; + } + + result["size"] = key->size; + this->sendCommand(result); + + return command_result{error::ok}; +} + +/* + * Usage: ftgetfileinfo cid={channelID} cpw={channelPassword} name={filePath}... + +Permissions: + i_ft_file_browse_power + i_ft_needed_file_browse_power + +Description: + Displays detailed information about one or more specified files stored in a + channels file repository. + +Example: + ftgetfileinfo cid=2 cpw= path=\/Pic1.PNG|cid=2 cpw= path=\/Pic2.PNG + cid=2 path=\/ name=Stuff size=0 datetime=1259415210 type=0|name=Pic1.PNG size=563783 datetime=1259425462 type=1|name=Pic2.PNG ... + error id=0 msg=ok + + */ + +command_result ConnectedClient::handleCommandFTGetFileInfo(ts::Command &cmd) { + CMD_REQ_SERVER; + CMD_REQ_FSERVER; + CMD_RESET_IDLE; + + Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyfileinfo" : ""); + + int result_index = 0; + for(int index = 0; index < cmd.bulkCount(); index++) { + auto& request = cmd[index]; + std::shared_ptr file; + if (request.has("cid") && request["cid"].as() != 0) { //Channel + auto channel = this->server->channelTree->findChannel(request["cid"].as()); + if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; + if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId()))) + return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password}; + + ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_browse_power, permission::i_ft_file_browse_power, true); + file = serverInstance->getFileServer()->findFile(request["name"],serverInstance->getFileServer()->resolveDirectory(this->server, channel, request["path"])); + } else { + std::shared_ptr directory; + if (!request.has("path") || request["path"].as() == "/") { + directory = serverInstance->getFileServer()->avatarDirectory(this->server); + } else if (request["path"].as() == "/icons" || request["path"].as() == "/icons/") { + directory = serverInstance->getFileServer()->iconDirectory(this->server); + } else { + cerr << "Unknown requested directory: '" << request["path"].as() << "'" << endl; + return command_result{error::not_implemented}; + } + + if(!directory) continue; + file = serverInstance->getFileServer()->findFile(cmd["name"].as(), directory); + } + if(!file) continue; + + result[result_index]["cid"] = request["cid"].as(); + result[result_index]["name"] = request["name"]; + result[result_index]["path"] = request["path"]; + result[result_index]["type"] = file->type; + result[result_index]["datetime"] = duration_cast(file->lastChanged.time_since_epoch()).count(); + if (file->type == file::FileType::FILE) + result[result_index]["size"] = static_pointer_cast(file)->fileSize; + else + result[result_index]["size"] = 0; + result_index++; + } + + if(result_index == 0) + return command_result{error::database_empty_result}; + this->sendCommand(result); + + return command_result{error::ok}; +} + + + + + + + + + + + + + + + + + diff --git a/server/src/client/command_handler/helpers.h b/server/src/client/command_handler/helpers.h new file mode 100644 index 0000000..a46b3d3 --- /dev/null +++ b/server/src/client/command_handler/helpers.h @@ -0,0 +1,193 @@ +#pragma once + +/** Permission tests against required values **/ +/* use this for any action which will do something with the server */ +#define ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(required_permission_type, required_value, cache) \ +do { \ + if(!permission::v2::permission_granted(required_value, this->calculate_permission(required_permission_type, 0, false, cache))) \ + return command_result{required_permission_type}; \ +} while(0) + +#define ACTION_REQUIRES_GLOBAL_PERMISSION(required_permission_type, required_value) \ + ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(required_permission_type, required_value, nullptr) + +//TODO: Fixme: Really check for instance permissions! +#define ACTION_REQUIRES_INSTANCE_PERMISSION(required_permission_type, required_value) \ + ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(required_permission_type, required_value, nullptr) + +/* use this for anything which will do something local in relation to the target channel */ +#define ACTION_REQUIRES_PERMISSION(required_permission_type, required_value, channel_id) \ +do { \ + if(!permission::v2::permission_granted(required_value, this->calculate_permission(required_permission_type, channel_id))) \ + return command_result{required_permission_type}; \ +} while(0) + +/** Permission tests against groups **/ +/* use this when testing a permission against a group */ +#define ACTION_REQUIRES_GROUP_PERMISSION(group, required_permission_type, own_permission_type, is_required) \ +do { \ + auto _permission_granted = this->calculate_permission(own_permission_type, 0); \ + if(!(group)->permission_granted(required_permission_type, _permission_granted, is_required)) \ + return command_result{own_permission_type}; \ +} while(0) + +/** Permission tests against channels **/ +/* use this when testing a permission against a group */ +#define ACTION_REQUIRES_CHANNEL_PERMISSION(channel, required_permission_type, own_permission_type, is_required) \ +do { \ + auto _permission_granted = this->calculate_permission(own_permission_type, channel ? channel->channelId() : 0); \ + if(!(channel)->permission_granted(required_permission_type, _permission_granted, is_required)) \ + return command_result{own_permission_type}; \ +} while(0) + + +/* Helper methods for channel resolve */ +#define RESOLVE_CHANNEL_R(command, force) \ +auto channel_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get();\ +shared_lock channel_tree_read_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock());\ +auto channel_id = command.as(); \ +auto l_channel = channel_id ? channel_tree->findLinkedChannel(command.as()) : nullptr; \ +if (!l_channel && (channel_id != 0 || force)) return command_result{error::channel_invalid_id, "Cant resolve channel"}; \ + +#define RESOLVE_CHANNEL_W(command, force) \ +auto channel_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get();\ +unique_lock channel_tree_write_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock());\ +auto channel_id = command.as(); \ +auto l_channel = channel_id ? channel_tree->findLinkedChannel(command.as()) : nullptr; \ +if (!l_channel && (channel_id != 0 || force)) return command_result{error::channel_invalid_id, "Cant resolve channel"}; \ + + + +//TODO: Map permsid! +#define PARSE_PERMISSION(cmd) \ +permission::PermissionType permType = permission::PermissionType::unknown; \ +bool grant = false; \ +if (cmd[index].has("permid")) { \ + permType = cmd[index]["permid"].as(); \ + if ((permType & PERM_ID_GRANT) != 0) { \ + grant = true; \ + permType &= ~PERM_ID_GRANT; \ + } \ +} else if (cmd[index].has("permsid")) { \ + auto resv = permission::resolvePermissionData(cmd[index]["permsid"].as()); \ + permType = resv->type; \ + if (resv->grantName() == cmd[index]["permsid"].as()) grant = true; \ +} \ +if (permission::resolvePermissionData(permType)->type == permission::PermissionType::unknown) { \ + if (conOnError) continue; \ + return ts::command_result{error::parameter_invalid}; \ +} + + + +inline bool permission_require_granted_value(ts::permission::PermissionType type) { + using namespace ts; + switch (type) { + case permission::i_permission_modify_power: + + case permission::i_channel_group_member_add_power: + case permission::i_channel_group_member_remove_power: + case permission::i_channel_group_modify_power: + + case permission::i_channel_group_needed_member_add_power: + case permission::i_channel_group_needed_member_remove_power: + case permission::i_channel_group_needed_modify_power: + + + case permission::i_server_group_member_add_power: + case permission::i_server_group_member_remove_power: + case permission::i_server_group_modify_power: + + case permission::i_server_group_needed_member_add_power: + case permission::i_server_group_needed_member_remove_power: + case permission::i_server_group_needed_modify_power: + + case permission::i_displayed_group_member_add_power: + case permission::i_displayed_group_member_remove_power: + case permission::i_displayed_group_modify_power: + + case permission::i_displayed_group_needed_member_add_power: + case permission::i_displayed_group_needed_member_remove_power: + case permission::i_displayed_group_needed_modify_power: + + case permission::i_channel_permission_modify_power: + case permission::i_channel_needed_permission_modify_power: + case permission::i_client_permission_modify_power: + case permission::i_client_needed_permission_modify_power: + + case permission::i_client_needed_kick_from_server_power: + case permission::i_client_needed_kick_from_channel_power: + case permission::i_client_kick_from_channel_power: + case permission::i_client_kick_from_server_power: + return true; + default: + return false; + } +} + +inline bool permission_is_group_property(ts::permission::PermissionType type) { + using namespace ts; + switch (type) { + case permission::i_icon_id: + case permission::i_group_show_name_in_tree: + case permission::i_group_sort_id: + case permission::b_group_is_permanent: + case permission::i_displayed_group_needed_modify_power: + case permission::i_displayed_group_needed_member_add_power: + case permission::i_displayed_group_needed_member_remove_power: + return true; + default: + return false; + } +} + +inline bool permission_is_client_property(ts::permission::PermissionType type) { + using namespace ts; + switch (type) { + case permission::i_icon_id: + case permission::i_client_talk_power: + case permission::i_client_max_idletime: + case permission::i_group_sort_id: + case permission::i_channel_view_power: + case permission::b_channel_ignore_view_power: + case permission::b_client_is_priority_speaker: + return true; + default: + return false; + } +} + + +inline ssize_t count_characters(const std::string& in) { + size_t index = 0; + size_t count = 0; + while(index < in.length()){ + count++; + + auto current = (uint8_t) in[index]; + if(current >= 128) { //UTF8 check + if(current >= 192 && (current <= 193 || current >= 245)) { + return -1; + } else if(current >= 194 && current <= 223) { + if(in.length() - index <= 1) return -1; + else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191) index += 1; //Valid + else return -1; + } else if(current >= 224 && current <= 239) { + if(in.length() - index <= 2) return -1; + else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 && + (uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191) index += 2; //Valid + else return -1; + } else if(current >= 240 && current <= 244) { + if(in.length() - index <= 3) return -1; + else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 && + (uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191 && + (uint8_t) in[index + 3] >= 128 && (uint8_t) in[index + 3] <= 191) index += 3; //Valid + else return -1; + } else { + return -1; + } + } + index++; + } + return count; +} \ No newline at end of file diff --git a/server/src/client/command_handler/misc.cpp b/server/src/client/command_handler/misc.cpp new file mode 100644 index 0000000..1807e5f --- /dev/null +++ b/server/src/client/command_handler/misc.cpp @@ -0,0 +1,2579 @@ +// +// Created by wolverindev on 26.01.20. +// + +#include + +#include + +#include +#include +#include +#include +#include "../../build.h" +#include "../ConnectedClient.h" +#include "../InternalClient.h" +#include "../../server/file/FileServer.h" +#include "../../server/VoiceServer.h" +#include "../voice/VoiceClient.h" +#include "PermissionManager.h" +#include "../../InstanceHandler.h" +#include "../../server/QueryServer.h" +#include "../file/FileClient.h" +#include "../music/MusicClient.h" +#include "../query/QueryClient.h" +#include "../../weblist/WebListManager.h" +#include "../../manager/ConversationManager.h" +#include "../../manager/PermissionNameMapper.h" +#include +#include +#include + +#include "helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::experimental::filesystem; +using namespace std::chrono; +using namespace std; +using namespace ts; +using namespace ts::server; +using namespace ts::token; + +#define QUERY_PASSWORD_LENGTH 12 + +command_result ConnectedClient::handleCommand(Command &cmd) { + threads::MutexLock l2(this->command_lock); + auto command = cmd.command(); + if (command == "servergetvariables") return this->handleCommandServerGetVariables(cmd); + else if (command == "serverrequestconnectioninfo") return this->handleCommandServerRequestConnectionInfo(cmd); + else if (command == "getconnectioninfo") return this->handleCommandGetConnectionInfo(cmd); + else if (command == "setconnectioninfo") return this->handleCommandSetConnectionInfo(cmd); + else if (command == "clientgetvariables") return this->handleCommandClientGetVariables(cmd); + else if (command == "serveredit") return this->handleCommandServerEdit(cmd); + else if (command == "clientedit") return this->handleCommandClientEdit(cmd); + else if (command == "channelgetdescription") return this->handleCommandChannelGetDescription(cmd); + else if (command == "connectioninfoautoupdate") return this->handleCommandConnectionInfoAutoUpdate(cmd); + else if (command == "permissionlist") return this->handleCommandPermissionList(cmd); + else if (command == "propertylist") return this->handleCommandPropertyList(cmd); + + //Server group + else if (command == "servergrouplist") return this->handleCommandServerGroupList(cmd); + else if (command == "servergroupadd") return this->handleCommandServerGroupAdd(cmd); + else if (command == "servergroupcopy") return this->handleCommandServerGroupCopy(cmd); + else if (command == "servergroupdel") return this->handleCommandServerGroupDel(cmd); + else if (command == "servergrouprename") return this->handleCommandServerGroupRename(cmd); + else if (command == "servergroupclientlist") return this->handleCommandServerGroupClientList(cmd); + else if (command == "servergroupaddclient" || command == "clientaddservergroup") return this->handleCommandServerGroupAddClient(cmd); + else if (command == "servergroupdelclient" || command == "clientdelservergroup") return this->handleCommandServerGroupDelClient(cmd); + else if (command == "servergrouppermlist") return this->handleCommandServerGroupPermList(cmd); + else if (command == "servergroupaddperm") return this->handleCommandServerGroupAddPerm(cmd); + else if (command == "servergroupdelperm") return this->handleCommandServerGroupDelPerm(cmd); + + else if (command == "setclientchannelgroup") return this->handleCommandSetClientChannelGroup(cmd); + + //Channel basic actions + else if (command == "channelcreate") return this->handleCommandChannelCreate(cmd); + else if (command == "channelmove") return this->handleCommandChannelMove(cmd); + else if (command == "channeledit") return this->handleCommandChannelEdit(cmd); + else if (command == "channeldelete") return this->handleCommandChannelDelete(cmd); + //Find a channel and get informations + else if (command == "channelfind") return this->handleCommandChannelFind(cmd); + else if (command == "channelinfo") return this->handleCommandChannelInfo(cmd); + //Channel perm actions + else if (command == "channelpermlist") return this->handleCommandChannelPermList(cmd); + else if (command == "channeladdperm") return this->handleCommandChannelAddPerm(cmd); + else if (command == "channeldelperm") return this->handleCommandChannelDelPerm(cmd); + //Channel group actions + else if (command == "channelgroupadd") return this->handleCommandChannelGroupAdd(cmd); + else if (command == "channelgroupcopy") return this->handleCommandChannelGroupCopy(cmd); + else if (command == "channelgrouprename") return this->handleCommandChannelGroupRename(cmd); + else if (command == "channelgroupdel") return this->handleCommandChannelGroupDel(cmd); + else if (command == "channelgrouplist") return this->handleCommandChannelGroupList(cmd); + else if (command == "channelgroupclientlist") return this->handleCommandChannelGroupClientList(cmd); + else if (command == "channelgrouppermlist") return this->handleCommandChannelGroupPermList(cmd); + else if (command == "channelgroupaddperm") return this->handleCommandChannelGroupAddPerm(cmd); + else if (command == "channelgroupdelperm") return this->handleCommandChannelGroupDelPerm(cmd); + //Channel sub/unsubscribe + else if (command == "channelsubscribe") return this->handleCommandChannelSubscribe(cmd); + else if (command == "channelsubscribeall") return this->handleCommandChannelSubscribeAll(cmd); + else if (command == "channelunsubscribe") return this->handleCommandChannelUnsubscribe(cmd); + else if (command == "channelunsubscribeall") return this->handleCommandChannelUnsubscribeAll(cmd); + //manager channel permissions + else if (command == "channelclientpermlist") return this->handleCommandChannelClientPermList(cmd); + else if (command == "channelclientaddperm") return this->handleCommandChannelClientAddPerm(cmd); + else if (command == "channelclientdelperm") return this->handleCommandChannelClientDelPerm(cmd); + //Client actions + else if (command == "clientupdate") return this->handleCommandClientUpdate(cmd); + else if (command == "clientmove") return this->handleCommandClientMove(cmd); + else if (command == "clientgetids") return this->handleCommandClientGetIds(cmd); + else if (command == "clientkick") return this->handleCommandClientKick(cmd); + else if (command == "clientpoke") return this->handleCommandClientPoke(cmd); + else if (command == "sendtextmessage") return this->handleCommandSendTextMessage(cmd); + else if (command == "clientchatcomposing") return this->handleCommandClientChatComposing(cmd); + else if (command == "clientchatclosed") return this->handleCommandClientChatClosed(cmd); + + else if (command == "clientfind") return this->handleCommandClientFind(cmd); + else if (command == "clientinfo") return this->handleCommandClientInfo(cmd); + + else if (command == "clientaddperm") return this->handleCommandClientAddPerm(cmd); + else if (command == "clientdelperm") return this->handleCommandClientDelPerm(cmd); + else if (command == "clientpermlist") return this->handleCommandClientPermList(cmd); + //File transfare + else if (command == "ftgetfilelist") return this->handleCommandFTGetFileList(cmd); + else if (command == "ftcreatedir") return this->handleCommandFTCreateDir(cmd); + else if (command == "ftdeletefile") return this->handleCommandFTDeleteFile(cmd); + else if (command == "ftinitupload") return this->handleCommandFTInitUpload(cmd); + else if (command == "ftinitdownload") return this->handleCommandFTInitDownload(cmd); + else if (command == "ftgetfileinfo") return this->handleCommandFTGetFileInfo(cmd); + //Banlist + else if (command == "banlist") return this->handleCommandBanList(cmd); + else if (command == "banadd") return this->handleCommandBanAdd(cmd); + else if (command == "banedit") return this->handleCommandBanEdit(cmd); + else if (command == "banclient") return this->handleCommandBanClient(cmd); + else if (command == "bandel") return this->handleCommandBanDel(cmd); + else if (command == "bandelall") return this->handleCommandBanDelAll(cmd); + else if (command == "bantriggerlist") return this->handleCommandBanTriggerList(cmd); + //Tokens + else if (command == "tokenlist" || command == "privilegekeylist") return this->handleCommandTokenList(cmd); + else if (command == "tokenadd" || command == "privilegekeyadd") return this->handleCommandTokenAdd(cmd); + else if (command == "tokenuse" || command == "privilegekeyuse") return this->handleCommandTokenUse(cmd); + else if (command == "tokendelete" || command == "privilegekeydelete") return this->handleCommandTokenDelete(cmd); + + //DB stuff + else if (command == "clientdblist") return this->handleCommandClientDbList(cmd); + else if (command == "clientdbinfo") return this->handleCommandClientDbInfo(cmd); + else if (command == "clientdbedit") return this->handleCommandClientDBEdit(cmd); + else if (command == "clientdbfind") return this->handleCommandClientDBFind(cmd); + else if (command == "clientdbdelete") return this->handleCommandClientDBDelete(cmd); + else if (command == "plugincmd") return this->handleCommandPluginCmd(cmd); + + else if (command == "clientmute") return this->handleCommandClientMute(cmd); + else if (command == "clientunmute") return this->handleCommandClientUnmute(cmd); + + else if (command == "clientlist") return this->handleCommandClientList(cmd); + else if (command == "whoami") return this->handleCommandWhoAmI(cmd); + else if (command == "servergroupsbyclientid") return this->handleCommandServerGroupsByClientId(cmd); + + else if (command == "clientgetdbidfromuid") return this->handleCommandClientGetDBIDfromUID(cmd); + else if (command == "clientgetnamefromdbid") return this->handleCommandClientGetNameFromDBID(cmd); + else if (command == "clientgetnamefromuid") return this->handleCommandClientGetNameFromUid(cmd); + else if (command == "clientgetuidfromclid") return this->handleCommandClientGetUidFromClid(cmd); + + + else if (command == "complainadd") return this->handleCommandComplainAdd(cmd); + else if (command == "complainlist") return this->handleCommandComplainList(cmd); + else if (command == "complaindel") return this->handleCommandComplainDel(cmd); + else if (command == "complaindelall") return this->handleCommandComplainDelAll(cmd); + + else if (command == "version") return this->handleCommandVersion(cmd); + + else if (command == "verifyserverpassword") return this->handleCommandVerifyServerPassword(cmd); + else if (command == "verifychannelpassword") return this->handleCommandVerifyChannelPassword(cmd); + + else if (command == "messagelist") return this->handleCommandMessageList(cmd); + else if (command == "messageadd") return this->handleCommandMessageAdd(cmd); + else if (command == "messageget") return this->handleCommandMessageGet(cmd); + else if (command == "messagedel") return this->handleCommandMessageDel(cmd); + else if (command == "messageupdateflag") return this->handleCommandMessageUpdateFlag(cmd); + + else if (command == "permget") return this->handleCommandPermGet(cmd); + else if (command == "permfind") return this->handleCommandPermFind(cmd); + else if (command == "permidgetbyname") return this->handleCommandPermIdGetByName(cmd); + else if (command == "permoverview") return this->handleCommandPermOverview(cmd); + else if (command == "permreset") return this->handleCommandPermReset(cmd); + + else if (command == "clientsetserverquerylogin") return this->handleCommandClientSetServerQueryLogin(cmd); + + //Music stuff + else if (command == "musicbotcreate") return this->handleCommandMusicBotCreate(cmd); + else if (command == "musicbotdelete") return this->handleCommandMusicBotDelete(cmd); + else if (command == "musicbotsetsubscription") return this->handleCommandMusicBotSetSubscription(cmd); + else if (command == "musicbotplayerinfo") return this->handleCommandMusicBotPlayerInfo(cmd); + else if (command == "musicbotplayeraction") return this->handleCommandMusicBotPlayerAction(cmd); + else if (command == "musicbotqueuelist") return this->handleCommandMusicBotQueueList(cmd); + else if (command == "musicbotqueueadd") return this->handleCommandMusicBotQueueAdd(cmd); + else if (command == "musicbotqueueremove") return this->handleCommandMusicBotQueueRemove(cmd); + else if (command == "musicbotqueuereorder") return this->handleCommandMusicBotQueueReorder(cmd); + else if (command == "musicbotplaylistassign") return this->handleCommandMusicBotPlaylistAssign(cmd); + + else if (command == "help") return this->handleCommandHelp(cmd); + + else if (command == "logview") return this->handleCommandLogView(cmd); + else if (command == "servergroupautoaddperm") return this->handleCommandServerGroupAutoAddPerm(cmd); + else if (command == "servergroupautodelperm") return this->handleCommandServerGroupAutoDelPerm(cmd); + + else if (command == "updatemytsid") return this->handleCommandUpdateMyTsId(cmd); + else if (command == "updatemytsdata") return this->handleCommandUpdateMyTsData(cmd); + + else if (command == "querycreate") return this->handleCommandQueryCreate(cmd); + else if (command == "querydelete") return this->handleCommandQueryDelete(cmd); + else if (command == "querylist") return this->handleCommandQueryList(cmd); + else if (command == "queryrename") return this->handleCommandQueryRename(cmd); + else if (command == "querychangepassword") return this->handleCommandQueryChangePassword(cmd); + + else if (command == "playlistlist") return this->handleCommandPlaylistList(cmd); + else if (command == "playlistcreate") return this->handleCommandPlaylistCreate(cmd); + else if (command == "playlistdelete") return this->handleCommandPlaylistDelete(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 == "playlistinfo") return this->handleCommandPlaylistInfo(cmd); + else if (command == "playlistedit") return this->handleCommandPlaylistEdit(cmd); + + else if (command == "playlistsonglist") return this->handleCommandPlaylistSongList(cmd); + else if (command == "playlistsongadd") return this->handleCommandPlaylistSongAdd(cmd); + else if (command == "playlistsongreorder" || command == "playlistsongmove") return this->handleCommandPlaylistSongReorder(cmd); + else if (command == "playlistsongremove") return this->handleCommandPlaylistSongRemove(cmd); + + else if (command == "dummy_ipchange") return this->handleCommandDummy_IpChange(cmd); + else if (command == "conversationhistory") return this->handleCommandConversationHistory(cmd); + else if (command == "conversationfetch") return this->handleCommandConversationFetch(cmd); + else if (command == "conversationmessagedelete") return this->handleCommandConversationMessageDelete(cmd); + + if (this->getType() == ClientType::CLIENT_QUERY) return command_result{error::command_not_found}; //Dont log query invalid commands + if (this->getType() == ClientType::CLIENT_TEAMSPEAK) + if (command.empty() || command.find_first_not_of(' ') == -1) { + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_allow_invalid_packet, this->getChannelId()))) + ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_command, this->server ? this->server->serverAdmin : static_pointer_cast(serverInstance->getInitialServerAdmin()), true); + } + + logError(this->getServerId(), "Missing command '{}'", command); + return command_result{error::command_not_found}; +}; + +command_result ConnectedClient::handleCommandGetConnectionInfo(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto client = this->server->findClient(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); + } else if(send_temp) { + return command_result{error::no_cached_connection_info}; + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandSetConnectionInfo(Command &cmd) { + auto info = std::make_shared(); + info->timestamp = chrono::system_clock::now(); + for (const auto &key : cmd[0].keys()) + info->properties.insert({key, cmd[key].string()}); + + /* + CONNECTION_FILETRANSFER_BANDWIDTH_SENT, //how many bytes per second are currently being sent by file transfers + CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, //how many bytes per second are currently being received by file transfers + CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, //how many bytes we received in total through file transfers + CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, //how many bytes we sent in total through file transfers + */ + + deque> receivers; + { + lock_guard info_lock(this->connection_info.lock); + for(const auto& weak_receiver : this->connection_info.receiver) { + auto receiver = weak_receiver.lock(); + if(!receiver) continue; + + receivers.push_back(receiver); + } + this->connection_info.receiver.clear(); + this->connection_info.data = info; + this->connection_info.data_age = system_clock::now(); + } + for(const auto& receiver : receivers) + receiver->notifyConnectionInfo(_this.lock(), info); + return command_result{error::ok}; +} + +//connectioninfoautoupdate connection_server2client_packetloss_speech=0.0000 connection_server2client_packetloss_keepalive=0.0010 connection_server2client_packetloss_control=0.0000 connection_server2client_packetloss_total=0.0009 +command_result ConnectedClient::handleCommandConnectionInfoAutoUpdate(Command &cmd) { + this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE] = cmd["connection_server2client_packetloss_keepalive"].as(); + this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL] = cmd["connection_server2client_packetloss_control"].as(); + this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH] = cmd["connection_server2client_packetloss_speech"].as(); + this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL] = cmd["connection_server2client_packetloss_total"].as(); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPermissionList(Command &cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + static std::string permission_list_string[ClientType::MAX]; + static std::mutex permission_list_string_lock; + + static auto build_permission_list = [](const std::string& command, const ClientType& type) { + Command list_builder(command); + int index = 0; + for (auto group : permission::availableGroups) + list_builder[index++]["group_id_end"] = group; + + auto avPerms = permission::availablePermissions; + std::sort(avPerms.begin(), avPerms.end(), [](const std::shared_ptr &a, const std::shared_ptr &b) { + return a->type < b->type; + }); + + auto mapper = serverInstance->getPermissionMapper(); + for (const auto& permission : avPerms) { + if (!permission->clientSupported) continue; + + auto &blk = list_builder[index++]; + + blk["permname"] = permission->name; + blk["permname"] = mapper->permission_name(type, permission->type); + blk["permdesc"] = permission->description; + blk["permid"] = permission->type; + } + return list_builder; + }; + + auto type = this->getType(); + if(type == CLIENT_TEASPEAK || type == CLIENT_TEAMSPEAK || type == CLIENT_QUERY) { + Command response(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifypermissionlist" : ""); + { + lock_guard lock(permission_list_string_lock); + if(permission_list_string[type].empty()) + permission_list_string[type] = build_permission_list("", type).build(); + response[0][""] = permission_list_string[type]; + } + this->sendCommand(response); + } else { + this->sendCommand(build_permission_list(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifypermissionlist" : "", type)); + } + return command_result{error::ok}; +} + + +#define M(ptype) \ +do { \ + for(const auto& prop : property::impl::list()) { \ + if((prop->flags & property::FLAG_INTERNAL) > 0) continue; \ + response[index]["name"] = prop->name; \ + response[index]["flags"] = prop->flags; \ + response[index]["type"] = property::PropertyType_Names[prop->type_property]; \ + index++; \ + } \ +} while(0) + +command_result ConnectedClient::handleCommandPropertyList(ts::Command& cmd) { + Command response(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifypropertylist" : ""); + + { + string pattern; + for (auto flag_name : property::flag_names) + pattern = flag_name + string("|") + pattern; + pattern = pattern.substr(0, pattern.length() - 1); + response["flag_set"] = pattern; + } + + int index = 0; + if(cmd.hasParm("all") || cmd.hasParm("server")) + M(property::VirtualServerProperties); + if(cmd.hasParm("all") || cmd.hasParm("channel")) + M(property::ChannelProperties); + if(cmd.hasParm("all") || cmd.hasParm("client")) + M(property::ClientProperties); + if(cmd.hasParm("all") || cmd.hasParm("instance")) + M(property::InstanceProperties); + if(cmd.hasParm("all") || cmd.hasParm("group")) + M(property::GroupProperties); + if(cmd.hasParm("all") || cmd.hasParm("connection")) + M(property::ConnectionProperties); + + this->sendCommand(response); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto serverGroup = this->server->groups->findGroup(cmd["cgid"].as()); + if (!serverGroup && cmd["cgid"].as() == 0) + serverGroup = this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL); + + if (!serverGroup || serverGroup->target() != GROUPTARGET_CHANNEL) + return command_result{error::group_invalid_id}; + + shared_lock server_channel_lock(this->server->channel_tree_lock); /* ensure we dont get moved or somebody could move us */ + auto channel_id = cmd["cid"].as(); + auto channel = this->server->channelTree->findChannel(channel_id); + if (!channel) return command_result{error::channel_invalid_id}; + + auto target_cldbid = cmd["cldbid"].as(); + { + auto channel_group_member_add_power = this->calculate_permission(permission::i_channel_group_member_add_power, channel_id); + if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_add_power, channel_group_member_add_power, true)) { + if(target_cldbid != this->getClientDatabaseId()) + return command_result{permission::i_channel_group_member_add_power}; + + auto channel_group_self_add_power = this->calculate_permission(permission::i_channel_group_self_add_power, channel_id); + if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_add_power, channel_group_self_add_power, true)) + return command_result{permission::i_channel_group_self_add_power}; + } + + + auto client_permission_modify_power = this->calculate_permission(permission::i_client_permission_modify_power, channel_id); + auto client_needed_permission_modify_power = this->server->calculate_permission( + permission::i_client_needed_permission_modify_power, target_cldbid, ClientType::CLIENT_TEAMSPEAK, channel_id); + + + if(client_needed_permission_modify_power.has_value) { + if(!permission::v2::permission_granted(client_needed_permission_modify_power, client_permission_modify_power)) + return command_result{permission::i_client_permission_modify_power}; + } + } + + { + auto old_group = this->server->groups->getChannelGroupExact(target_cldbid, channel, false); + if(old_group) { + auto channel_group_member_remove_power = this->calculate_permission(permission::i_channel_group_member_remove_power, channel_id); + if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_member_remove_power, true)) { + if(target_cldbid != this->getClientDatabaseId()) + return command_result{permission::i_channel_group_member_remove_power}; + + auto channel_group_self_remove_power = this->calculate_permission(permission::i_channel_group_self_remove_power, channel_id); + if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_self_remove_power, true)) + return command_result{permission::i_channel_group_self_remove_power}; + } + } + } + + this->server->groups->setChannelGroup(target_cldbid, serverGroup, channel); + for (const auto &targetClient : this->server->findClientsByCldbId(target_cldbid)) { + unique_lock client_channel_lock_w(targetClient->channel_lock); + auto updates = this->server->groups->update_server_group_property(targetClient, false, targetClient->getChannel()); /* needs a write lock */ + client_channel_lock_w.unlock(); + shared_lock client_channel_lock_r(targetClient->channel_lock); + auto result = this->server->notifyClientPropertyUpdates(targetClient, updates); + if (result) { + if(targetClient->update_cached_permissions()) /* update cached calculated permissions */ + targetClient->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + + if(targetClient->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] == channel->channelId()) { //Only if group assigned over the channel + for (const auto &viewer : this->server->getClients()) { + /* if in view will be tested within that method */ + shared_lock viewer_channel_lock(viewer->channel_lock, defer_lock); + if(viewer != targetClient) + viewer_channel_lock.lock(); + viewer->notifyClientChannelGroupChanged(_this.lock(), targetClient, targetClient->getChannel(), channel, serverGroup, false); + } + } + } + targetClient->updateChannelClientProperties(false, true); + } + + return command_result{error::ok}; +} + +//sendtextmessage targetmode=1 <1 = direct | 2 = channel | 3 = server> msg=asd target=1 +command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto timestamp = system_clock::now(); + if (cmd["targetmode"].as() == ChatMessageMode::TEXTMODE_PRIVATE) { + auto target = this->server->findClient(cmd["target"].as()); + if (!target) return command_result{error::client_invalid_id}; + + bool chat_open = false; + { + + shared_lock channel_lock(this->channel_lock); + this->openChats.erase(remove_if(this->openChats.begin(), this->openChats.end(), [](const weak_ptr& weak) { return !weak.lock(); }), this->openChats.end()); + for(const auto& entry : this->openChats) { + if(entry.lock() == target) { + chat_open = true; + break; + } + } + } + + if(!chat_open) { + if (target == 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); + target->openChats.push_back(_this); + } + + { + unique_lock channel_lock(this->channel_lock); + this->openChats.push_back(target); + } + } + + if(this->handleTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, cmd["msg"], target)) 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) { + if(!cmd[0].has("cid")) + cmd["cid"] = 0; + RESOLVE_CHANNEL_R(cmd["cid"], false); + auto channel = l_channel ? dynamic_pointer_cast(l_channel->entry) : nullptr; + if(!channel) { + CMD_REQ_CHANNEL; + channel = this->currentChannel; + channel_id = this->currentChannel->channelId(); + } + if(channel == this->currentChannel) { + channel_tree_read_lock.unlock(); //Method may creates a music bot which modifies the channel tree + if(this->handleTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, cmd["msg"], nullptr)) + return command_result{error::ok}; + channel_tree_read_lock.lock(); + } + + ACTION_REQUIRES_PERMISSION(permission::b_client_channel_textmessage_send, 1, channel_id); + return command_result{permission::b_client_channel_textmessage_send}; + + bool conversation_private = channel->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as(); + if(channel != this->currentChannel) { + if(conversation_private) + return command_result{error::conversation_is_private}; + + if(!this->calculate_and_get_join_state(channel)) + return command_result{permission::b_client_channel_textmessage_send}; /* You're not allowed to send messages :) */ + } + + auto message = cmd["msg"].string(); + auto _this = this->_this.lock(); + auto client_id = this->getClientId(); + + auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as(); + for(const auto& client : this->server->getClients()) { + if(client->connectionState() != ConnectionState::CONNECTED) + continue; + + auto type = client->getType(); + if(type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC) + continue; + + auto own_channel = client->currentChannel == this->currentChannel; + if(conversation_private && !own_channel) + continue; + + if(type != ClientType::CLIENT_TEAMSPEAK || own_channel) { + if(!own_channel && &*client != this) { + if(flag_password) + continue; /* TODO: Send notification about new message. The client then could request messages via message history */ + + if(!client->calculate_and_get_join_state(channel)) + continue; + } + client->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, _this, client_id, channel_id, timestamp, message); + } + } + + if(!conversation_private) { + auto conversations = this->server->conversation_manager(); + auto conversation = conversations->get_or_create(channel->channelId()); + conversation->register_message(this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), timestamp, cmd["msg"].string()); + } + } else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_SERVER) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_server_textmessage_send, 1); + + if(this->handleTextMessage(ChatMessageMode::TEXTMODE_SERVER, cmd["msg"], nullptr)) return command_result{error::ok}; + for(const auto& client : this->server->getClients()) { + if (client->connectionState() != ConnectionState::CONNECTED) + continue; + + auto type = client->getType(); + if (type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC) + continue; + + client->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, _this.lock(), this->getClientId(), 0, timestamp, cmd["msg"].string()); + } + + { + auto conversations = this->server->conversation_manager(); + auto conversation = conversations->get_or_create(0); + conversation->register_message(this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), timestamp, cmd["msg"].string()); + } + } else return command_result{error::parameter_invalid, "invalid target mode"}; + + return command_result{error::ok}; +} + +//notifybanlist banid=3 ip name uid=zbex8X3bFRTIKLI7mzeyJGZsh64= lastnickname=Wolf\sC++\sXXXX created=1510357269 duration=3600 invokername=WolverinDEV invokercldbid=5 invokeruid=xxjnc14LmvTk+Lyrm8OOeo4tOqw= reason=Prefix\sFake\s\p\sName enforcements=3 +command_result ConnectedClient::handleCommandBanList(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + ServerId sid = this->getServerId(); + if (cmd[0].has("sid")) + sid = cmd["sid"]; + + if (sid == 0) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_ban_list_global, 1); + } else { + auto server = serverInstance->getVoiceServerManager()->findServerById(sid); + if (!server) return command_result{error::parameter_invalid}; + + if (!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_ban_list, this->getClientDatabaseId(), this->getType(), 0))) + return command_result{permission::b_client_ban_list}; + } + + //When empty: return command_result{error::database_empty_result}; + auto banList = serverInstance->banManager()->listBans(sid); + if (banList.empty()) return command_result{error::database_empty_result}; + + auto allow_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); + Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifybanlist" : ""); + int index = 0; + for (const auto &elm : banList) { + notify[index]["sid"] = elm->serverId; + notify[index]["banid"] = elm->banId; + if(allow_ip) + notify[index]["ip"] = elm->ip; + else + notify[index]["ip"] = "hidden"; + notify[index]["name"] = elm->name; + notify[index]["uid"] = elm->uid; + notify[index]["hwid"] = elm->hwid; + notify[index]["lastnickname"] = elm->name; //Maybe update? + + notify[index]["created"] = chrono::duration_cast(elm->created.time_since_epoch()).count(); + if (elm->until.time_since_epoch().count() != 0) + notify[index]["duration"] = chrono::duration_cast(elm->until - elm->created).count(); + else + notify[index]["duration"] = 0; + + notify[index]["reason"] = elm->reason; + notify[index]["enforcements"] = elm->triggered; + + notify[index]["invokername"] = elm->invokerName; + notify[index]["invokercldbid"] = elm->invokerDbId; + notify[index]["invokeruid"] = elm->invokerUid; + index++; + } + this->sendCommand(notify); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandBanAdd(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + string ip = cmd[0].has("ip") ? cmd["ip"].string() : ""; + string name = cmd[0].has("name") ? cmd["name"].string() : ""; + string uid = cmd[0].has("uid") ? cmd["uid"].string() : ""; + string hwid = cmd[0].has("hwid") ? cmd["hwid"].string() : ""; + string banreason = cmd[0].has("banreason") ? cmd["banreason"].string() : "No reason set"; + auto time = cmd[0].has("time") ? cmd["time"].as() : 0L; + + ServerId sid = this->getServerId(); + if (cmd[0].has("sid")) + sid = cmd["sid"]; + + if (sid == 0) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_ban_create_global, 1); + } else { + auto server = serverInstance->getVoiceServerManager()->findServerById(sid); + if (!server) return command_result{error::parameter_invalid}; + if (!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_ban_create, this->getClientDatabaseId(), this->getType(), 0))) + return command_result{permission::b_client_ban_create}; + } + + auto max_ban_time = server->calculate_permission(permission::i_client_ban_max_bantime, this->getClientDatabaseId(), this->getType(), 0); + if (max_ban_time.has_value && !max_ban_time.has_infinite_power()) { + if (max_ban_time.value < time) + return command_result{permission::i_client_ban_max_bantime}; + } + + chrono::time_point until = time > 0 ? chrono::system_clock::now() + chrono::seconds(time) : chrono::time_point(); + + auto existing = serverInstance->banManager()->findBanExact(sid, banreason, uid, ip, name, hwid); + bool banned = false; + if(existing) { + if(existing->invokerDbId == this->getClientDatabaseId()) { + if(existing->until == until) return command_result{error::database_duplicate_entry}; + else { + existing->until = until; + serverInstance->banManager()->updateBan(existing); + banned = true; + } + } else if(!banned) { + serverInstance->banManager()->unban(existing); + } + } + if(!banned) serverInstance->banManager()->registerBan(sid, this->getClientDatabaseId(), banreason, uid, ip, name, hwid, until); + + for(auto server : (this->server ? std::deque>{this->server} : serverInstance->getVoiceServerManager()->serverInstances())) + server->testBanStateChange(_this.lock()); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandBanEdit(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + ServerId sid = this->getServerId(); + if (cmd[0].has("sid")) + sid = cmd["sid"]; + + auto ban = serverInstance->banManager()->findBanById(sid, cmd["banid"].as()); + if (!ban) return command_result{error::database_empty_result}; + + if (sid == 0) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_ban_edit_global, 1); + } else { + auto server = serverInstance->getVoiceServerManager()->findServerById(sid); + if (!server) return command_result{error::parameter_invalid}; + + if (!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_ban_edit, this->getClientDatabaseId(), this->getType(), 0))) + return command_result{permission::b_client_ban_edit}; + } + + /* ip name uid reason time hwid */ + bool changed = false; + if (cmd[0].has("ip")) { + ban->ip = cmd["ip"].as(); + changed |= true; + } + + if (cmd[0].has("name")) { + ban->name = cmd["name"].as(); + changed |= true; + } + + if (cmd[0].has("uid")) { + ban->uid = cmd["uid"].as(); + changed |= true; + } + + if (cmd[0].has("reason")) { + ban->reason = cmd["reason"].as(); + changed |= true; + } + if (cmd[0].has("banreason")) { + ban->reason = cmd["banreason"].as(); + changed |= true; + } + + if (cmd[0].has("time")) { + ban->until = ban->created + seconds(cmd["time"].as()); + changed |= true; + } + + if (cmd[0].has("hwid")) { + ban->hwid = cmd["hwid"].as(); + changed |= true; + } + + if (changed) + serverInstance->banManager()->updateBan(ban); + else return command_result{error::parameter_invalid}; + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandBanClient(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + string uid; + string reason = cmd[0].has("banreason") ? cmd["banreason"].string() : ""; + auto time = cmd[0].has("time") ? cmd["time"].as() : 0UL; + chrono::time_point until = time > 0 ? chrono::system_clock::now() + chrono::seconds(time) : chrono::time_point(); + + const auto no_nickname = cmd.hasParm("no-nickname"); + const auto no_hwid = cmd.hasParm("no-hardware-id"); + const auto no_ip = cmd.hasParm("no-ip"); + + deque> target_clients; + if (cmd[0].has("uid")) { + target_clients = this->server->findClientsByUid(uid = cmd["uid"].string()); + for(const auto& client : target_clients) + 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())}; + if(!target_clients[0]) { + return command_result{error::client_invalid_id, "Could not find target client"}; + } + if(target_clients[0]->getType() == ClientType::CLIENT_MUSIC) { + return command_result{error::client_invalid_id, "You cant ban a music bot!"}; + } + uid = target_clients[0]->getUid(); + } + + ClientDbId target_dbid = 0; + if (!target_clients.empty()) { + target_dbid = target_clients[0]->getClientDatabaseId(); + } else { + auto info = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->getServer(), {uid}); + if (!info.empty()) + target_dbid = info[0]->cldbid; + else + return command_result{error::client_unknown}; + } + + if(!permission::v2::permission_granted(this->calculate_permission(permission::i_client_ban_power, 0), this->server->calculate_permission(permission::i_client_needed_ban_power, target_dbid, ClientType::CLIENT_TEAMSPEAK, 0))) + return command_result{permission::i_client_ban_power}; + + if (permission::v2::permission_granted(1, this->server->calculate_permission(permission::b_client_ignore_bans, target_dbid, ClientType::CLIENT_TEAMSPEAK, 0))) + return command_result{permission::b_client_ignore_bans}; + + deque ban_ids; + auto _id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, uid, "", "", "", until); + ban_ids.push_back(_id); + + auto b_ban_name = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_name, 0), false); + auto b_ban_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_ip, 0), false); + auto b_ban_hwid = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_hwid, 0), false); + + auto max_ban_time = this->calculate_permission(permission::i_client_ban_max_bantime, 0); + if (max_ban_time.has_value && !max_ban_time.has_infinite_power()) { + if (max_ban_time.value < time) + return command_result{permission::i_client_ban_max_bantime}; + } + + for (const auto &client : target_clients) { + 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)); + + string entry_name, entry_ip, entry_hardware_id; + if(b_ban_name && !no_nickname) { + entry_name = client->getDisplayName(); + } + if(b_ban_ip && !no_ip && !config::server::disable_ip_saving) { + entry_ip = client->getPeerIp(); + } + if(b_ban_hwid && !no_hwid) { + entry_hardware_id = client->getHardwareId(); + } + auto exact = serverInstance->banManager()->findBanExact(this->getServer()->getServerId(), reason, "", entry_ip, entry_name, entry_hardware_id); + if(exact) { + exact->until = until; + exact->invokerDbId = this->getClientDatabaseId(); + serverInstance->banManager()->updateBan(exact); + ban_ids.push_back(exact->banId); + } else { + auto id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, "", entry_ip, entry_name, entry_hardware_id, until); + ban_ids.push_back(id); + } + } + this->server->testBanStateChange(_this.lock()); + + if (this->getType() == CLIENT_QUERY) { + Command notify(""); + int index = 0; + for(const auto& ban_id : ban_ids) + notify[index++]["banid"] = ban_id; + this->sendCommand(notify); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandBanDel(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + ServerId sid = this->getServerId(); + if (cmd[0].has("sid")) + sid = cmd["sid"]; + + auto ban = serverInstance->banManager()->findBanById(sid, cmd["banid"].as()); + if (!ban) return command_result{error::database_empty_result}; + + if (sid == 0) { + const auto permission = ban->invokerDbId == this->getClientDatabaseId() ? permission::b_client_ban_delete_own_global : permission::b_client_ban_delete_global; + ACTION_REQUIRES_GLOBAL_PERMISSION(permission, 1); + } else { + auto server = serverInstance->getVoiceServerManager()->findServerById(sid); + if (!server) return command_result{error::parameter_invalid}; + + auto perm = ban->invokerDbId == this->getClientDatabaseId() ? permission::b_client_ban_delete_own : permission::b_client_ban_delete; + if (!permission::v2::permission_granted(1, server->calculate_permission(perm, this->getClientDatabaseId(), this->getType(), 0))) + return command_result{perm}; + } + serverInstance->banManager()->unban(ban); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandBanDelAll(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_ban_delete, 1); + + serverInstance->banManager()->deleteAllBans(server->getServerId()); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandBanTriggerList(ts::Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_ban_trigger_list, 1); + + CMD_REQ_PARM("banid"); + + auto record = serverInstance->banManager()->findBanById(this->getServerId(), cmd["banid"]); + if(!record) return command_result{error::parameter_invalid, "Invalid ban id"}; + + Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifybantriggerlist" : ""); + notify["banid"] = record->banId; + notify["serverid"] = record->serverId; + + auto allow_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0)); + int index = 0; + for (auto &entry : serverInstance->banManager()->trigger_list(record, this->getServerId(), cmd[0].has("offset") ? cmd["offset"].as() : 0, cmd[0].has("limit") ? cmd["limit"].as() : -1)) { + notify[index]["client_unique_identifier"] = entry->unique_id; + notify[index]["client_hardware_identifier"] = entry->hardware_id; + notify[index]["client_nickname"] = entry->name; + if(allow_ip) + notify[index]["connection_client_ip"] = entry->ip; + else + notify[index]["connection_client_ip"] = "hidden"; + notify[index]["timestamp"] = duration_cast(entry->timestamp.time_since_epoch()).count(); + index++; + } + if (index == 0) return command_result{error::database_empty_result}; + + this->sendCommand(notify); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandTokenList(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_list, 1); + + Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifytokenlist" : ""); + int index = 0; + for (auto &token : this->server->tokenManager->avariableTokes()) { + notify[index]["token"] = token->token; + notify[index]["token_type"] = token->type; + notify[index]["token_id1"] = token->groupId; + notify[index]["token_id2"] = token->channelId; + notify[index]["token_created"] = chrono::duration_cast(token->created.time_since_epoch()).count(); + notify[index]["token_description"] = token->description; + index++; + } + if (index == 0) return command_result{error::database_empty_result}; + + this->sendCommand(notify); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandTokenAdd(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_add, 1); + + TokenType ttype = static_cast(cmd["tokentype"].as()); + auto gId = cmd["tokenid1"].as(); + auto cId = cmd["tokenid2"].as(); + string description = cmd["tokendescription"].string(); + + { + auto group = this->server->groups->findGroup(gId); + if(!group) return command_result{error::group_invalid_id}; + if(group->type() == GroupType::GROUP_TYPE_TEMPLATE) return command_result{error::parameter_invalid, "invalid server group type"}; + + if(group->target() == GroupTarget::GROUPTARGET_SERVER) + ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_server_group_needed_member_add_power, permission::i_server_group_member_add_power, true); + else + ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_channel_group_needed_member_add_power, permission::i_channel_group_member_add_power, true); + } + if (ttype == TokenType::TOKEN_CHANNEL) { + if (!this->server->channelTree->findChannel(cId)) return command_result{error::channel_invalid_id, "Cant resolve channel"}; + } else if (ttype == TokenType::TOKEN_SERVER); + else return command_result{error::parameter_invalid, "invalid token target type"}; + + + auto result = this->server->tokenManager->createToken(ttype, gId, description, cId, cmd["token"]); + if (!result) return command_result{error::vs_critical}; + + Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifytokenadd" : ""); + notify["token"] = result->token; + this->sendCommand(notify); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandTokenUse(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_use, 1); + + auto strToken = cmd["token"].string(); + auto token = this->server->tokenManager->findToken(strToken); + if (!token) return command_result{error::token_invalid_id, "Invalid token. (Token not registered)"}; + this->server->tokenManager->deleteToke(token->token); + + auto serverGroup = this->server->groups->findGroup(token->groupId); + if (!serverGroup) return command_result{error::token_invalid_id, "Token invalid groupId"}; + this->server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; //TODO test if its the default token + + std::shared_ptr channel = nullptr; + if (token->channelId != 0) { + channel = this->server->channelTree->findChannel(token->channelId); + if (!channel) return command_result{error::token_invalid_id, "Token invalid channelId"}; + + this->server->groups->setChannelGroup(this->getClientDatabaseId(), serverGroup, channel); + } else { + if(!this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), serverGroup)) + this->server->groups->addServerGroup(this->getClientDatabaseId(), serverGroup); + else { + return command_result{error::ok}; + } + } + + if (this->server->notifyClientPropertyUpdates(_this.lock(), this->server->groups->update_server_group_property(_this.lock(), true, this->getChannel()))) { + if(this->update_cached_permissions()) /* update cached calculated permissions */ + this->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + + { + for (auto &viewer : this->server->getClients()) { + if(viewer->isClientVisible(_this.lock(), true)) + viewer->notifyServerGroupClientAdd(this->server->serverRoot, _this.lock(), serverGroup); + } + } + this->notifyServerGroupClientAdd(this->server->serverRoot, _this.lock(), serverGroup); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandTokenDelete(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_delete, 1); + + auto strToken = cmd["token"].string(); + auto token = this->server->tokenManager->findToken(strToken); + if (!token) return command_result{error::token_invalid_id, "Invalid token. (Token not registered)"}; + this->server->tokenManager->deleteToke(token->token); + if(token->token == this->server->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY].as()) { + this->server->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = ""; + this->server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; + logMessage(this->getServerId(), "{} Deleting the default server token. Don't ask anymore for this a token!", CLIENT_STR_LOG_PREFIX); + } + return command_result{error::ok}; +} + +//start=0 duration=10 +//pattern=%asd% + + +command_result ConnectedClient::handleCommandPluginCmd(Command &cmd) { + CMD_REQ_SERVER; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto mode = cmd["targetmode"].as(); + + if (mode == PluginTargetMode::PLUGINCMD_CURRENT_CHANNEL) { + CMD_REQ_CHANNEL; + for (auto &cl : this->server->getClientsByChannel(this->currentChannel)) + cl->notifyPluginCmd(cmd["name"], cmd["data"], _this.lock()); + } else if (mode == PluginTargetMode::PLUGINCMD_SUBSCRIBED_CLIENTS) { + for (auto &cl : this->server->getClients()) + if (cl->isClientVisible(_this.lock(), true)) + cl->notifyPluginCmd(cmd["name"], cmd["data"], _this.lock()); + } else if (mode == PluginTargetMode::PLUGINCMD_SERVER) { + for (auto &cl : this->server->getClients()) + cl->notifyPluginCmd(cmd["name"], cmd["data"], _this.lock()); + } 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"}; + cl->notifyPluginCmd(cmd["name"], cmd["data"], _this.lock()); + } + } + + /* + else if(mode == PluginTargetMode::PLUGINCMD_SEND_COMMAND) { + auto target = _this.lock(); + if(cmd[0].has("target")) + target = this->server->findClient(cmd["target"].as()); + if(!target) return command_result{error::client_invalid_id, "invalid target id"}; + + target->sendCommand(Command(cmd["command"].string()), cmd[0].has("low") ? cmd["low"] : false); + } + */ + + else return command_result{error::not_implemented}; + return command_result{error::ok}; +} + + +command_result ConnectedClient::handleCommandWhoAmI(Command &cmd) { + CMD_RESET_IDLE; + + Command result(""); + + if (this->server) { + result["virtualserver_status"] = ServerState::string(this->getServer()->state); + result["virtualserver_id"] = this->server->getServerId(); + result["virtualserver_unique_identifier"] = this->server->properties()[property::VIRTUALSERVER_UNIQUE_IDENTIFIER].as(); + result["virtualserver_port"] = 0; + if (this->server->udpVoiceServer) { + result["virtualserver_port"] = this->server->properties()[property::VIRTUALSERVER_PORT].as_save(); + } + } else { + result["virtualserver_status"] = "template"; + result["virtualserver_id"] = "0"; + result["virtualserver_unique_identifier"] = ""; //TODO generate uid + result["virtualserver_port"] = "0"; + } + + result["client_id"] = this->getClientId(); + result["client_channel_id"] = this->currentChannel ? this->currentChannel->channelId() : 0; + result["client_nickname"] = this->getDisplayName(); + result["client_database_id"] = this->getClientDatabaseId(); + result["client_login_name"] = this->properties()[property::CLIENT_LOGIN_NAME].as(); + result["client_unique_identifier"] = this->getUid(); + + { + auto query = dynamic_cast(this); + if(query) { + auto account = query->getQueryAccount(); + result["client_origin_server_id"] = account ? account->bound_server : 0; + } else + result["client_origin_server_id"] = 0; + } + + this->sendCommand(result); + return command_result{error::ok}; +} + +struct DBFindArgs { + int index = 0; + bool full = false; + bool ip = false; + Command cmd{""}; +}; + +command_result ConnectedClient::handleCommandVersion(Command &) { + CMD_RESET_IDLE; + + Command res(""); + res["version"] = build::version()->string(false); + res["build_count"] = build::buildCount(); + res["build"] = duration_cast(build::version()->timestamp.time_since_epoch()).count(); +#ifdef WIN32 + res["platform"] = "Windows"; +#else + res["platform"] = "Linux"; +#endif + this->sendCommand(res); + return command_result{error::ok}; +} + +//cid=%d password=%s +command_result ConnectedClient::handleCommandVerifyChannelPassword(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + std::shared_ptr channel = (this->server ? this->server->channelTree : serverInstance->getChannelTree().get())->findChannel(cmd["cid"].as()); + if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"}; + + std::string password = cmd["password"]; + if (!channel->passwordMatch(password, false)) return command_result{error::server_invalid_password}; + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandVerifyServerPassword(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + std::string password = cmd["password"]; + if (!this->server->verifyServerPassword(password, false)) return command_result{error::server_invalid_password}; + return command_result{error::ok}; +} + +//msgid=2 cluid=IkBXingb46\/z1Q3hhMvJEweb3lw= subject=The\sSubject timestamp=1512224138 flag_read=0 +//notifymessagelist msgid=2 cluid=IkBXingb46\/z1Q3hhMvJEweb3lw= subject=The\sSubject timestamp=1512224138 flag_read=0 +command_result ConnectedClient::handleCommandMessageList(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto msgList = this->server->letters->avariableLetters(this->getUid()); + if (msgList.empty()) return command_result{error::database_empty_result, "no letters available"}; + + Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifymessagelist" : ""); + + int index = 0; + for (const auto &elm : msgList) { + notify[index]["msgid"] = elm->id; + notify[index]["cluid"] = elm->sender; + notify[index]["subject"] = elm->subject; + notify[index]["timestamp"] = duration_cast(elm->created.time_since_epoch()).count(); + notify[index]["flag_read"] = elm->read; + index++; + } + + this->sendCommand(notify); + return command_result{error::ok}; +} + +//messageadd cluid=ePHuXhcai9nk\/4Fd\/xkxrokvnNk= subject=Test message=Message +command_result ConnectedClient::handleCommandMessageAdd(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_offline_textmessage_send, 1); + + this->server->letters->createLetter(this->getUid(), cmd["cluid"], cmd["subject"], cmd["message"]); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandMessageGet(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(10); + + auto letter = this->server->letters->getFullLetter(cmd["msgid"]); + + //msgid=2 cluid=IkBXingb46\/z1Q3hhMvJEweb3lw= subject=The\sSubject message=The\sbody timestamp=1512224138 + Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifymessage" : ""); + notify["msgid"] = cmd["msgid"]; + notify["cluid"] = letter->sender; + notify["subject"] = letter->subject; + notify["message"] = letter->message; + notify["timestamp"] = duration_cast(letter->created.time_since_epoch()).count(); + this->sendCommand(notify); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandMessageUpdateFlag(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + this->server->letters->updateReadFlag(cmd["msgid"], cmd["flag"]); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandMessageDel(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + this->server->letters->deleteLetter(cmd["msgid"]); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPermGet(Command &cmd) { + CMD_RESET_IDLE; + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_permissionoverview_own, 1); + + Command res(""); + + deque requrested; + auto permission_mapper = serverInstance->getPermissionMapper(); + auto type = this->getType(); + for (int index = 0; index < cmd.bulkCount(); index++) { + permission::PermissionType permType = permission::unknown; + if (cmd[index].has("permid")) + permType = cmd[index]["permid"].as(); + else if (cmd[index].has("permsid")) + permType = permission::resolvePermissionData(cmd[index]["permsid"].as())->type; //TODO: Map the other way around! + if (permission::resolvePermissionData(permType)->type == permission::PermissionType::unknown) return command_result{error::parameter_invalid, "could not resolve permission"}; + + requrested.push_back(permType); + } + + int index = 0; + for(const auto& entry : this->calculate_permissions(requrested, this->getChannelId())) { + if(!entry.second.has_value) continue; + + res[index]["permsid"] = permission_mapper->permission_name(type, entry.first);; + res[index]["permid"] = entry.first; + res[index++]["permvalue"] = entry.second.value; + } + if(index == 0) + return command_result{error::database_empty_result}; + this->sendCommand(res); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPermIdGetByName(Command &cmd) { + auto found = permission::resolvePermissionData(cmd["permsid"].as()); //TODO: Map the other way around + Command res(""); + res["permid"] = found->type; + this->sendCommand(res); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPermFind(Command &cmd) { + struct PermissionEntry { + permission::PermissionType permission_type; + permission::PermissionValue permission_value; + permission::PermissionSqlType type; + + GroupId group_id; + ChannelId channel_id; + ClientDbId client_id; + + bool negate; + bool skip; + }; + + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_permission_find, 1); + + deque, bool>> permissions; + std::shared_ptr permission; + for(size_t index = 0; index < cmd.bulkCount(); index++) { + bool granted = false; + if (cmd[index].has("permid")) { + permission = permission::resolvePermissionData((permission::PermissionType) (cmd[index]["permid"].as() & (~PERM_ID_GRANT))); + granted = (cmd[index]["permid"].as() & PERM_ID_GRANT) > 0; + + if(permission->type == permission::PermissionType::unknown) + return command_result{error::parameter_invalid, "could not resolve permission (id=" + cmd[index]["permid"].string() + ")"}; + } else if (cmd[index].has("permsid")) { + permission = permission::resolvePermissionData(cmd[index]["permsid"].as()); //TODO: Map the other way around + granted = permission->grant_name == cmd[index]["permsid"].as(); + + if(permission->type == permission::PermissionType::unknown) + return command_result{error::parameter_invalid, "could not resolve permission (id=" + cmd[index]["permid"].string() + ")"}; + } else { + continue; + } + + permissions.emplace_back(pair, bool>{{permission->name, permission->type}, granted}); + } + + if(permissions.empty()) + return command_result{error::database_empty_result}; + + map flags; + map quick_mapping; + string query_string; + for(const auto& entry : permissions) { + if(flags[entry.first.first] == 0) { + quick_mapping[entry.first.first] = entry.first.second; + query_string += string(query_string.empty() ? "" : " OR ") + "`permId` = '" + entry.first.first + "'"; + } + + flags[entry.first.first] |= entry.second ? 2 : 1; + } + + deque> entries; + //`serverId` INT NOT NULL, `type` INT, `id` INT, `channelId` INT, `permId` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` INT, `grant` INT + sql::command(this->sql, "SELECT `permId`, `type`, `id`, `channelId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :sid AND (" + query_string + ") AND `type` != :playlist", + variable{":sid", this->server->getServerId()}, + variable{":playlist", permission::SQL_PERM_PLAYLIST} + ).query([&](int length, string* values, string* columns) { + permission::PermissionSqlType type = permission::SQL_PERM_GROUP; + uint64_t id = 0; + ChannelId channel_id = 0; + permission::PermissionValue value = 0, + granted_value = 0; + string permission_name; + bool negate = false, skip = false; + for (int index = 0; index < length; index++) { + try { + if(columns[index] == "type") + type = static_cast(stoll(values[index])); + else if(columns[index] == "permId") + permission_name = values[index]; + else if(columns[index] == "id") + id = static_cast(stoll(values[index])); + else if(columns[index] == "channelId") + channel_id = static_cast(stoll(values[index])); + else if(columns[index] == "value") + value = static_cast(stoll(values[index])); + else if(columns[index] == "grant") + granted_value = static_cast(stoll(values[index])); + else if(columns[index] == "flag_negate") + negate = !values[index].empty() && stol(values[index]) == 1; + else if(columns[index] == "flag_skip") + skip = !values[index].empty() && stol(values[index]) == 1; + } catch(std::exception& ex) { + debugMessage(this->getServerId(), "[{}] 'permfind' iterates over invalid permission entry. Key: {}, Value: {}, Error: {}", CLIENT_STR_LOG_PREFIX, columns[index], values[index], ex.what()); + return 0; + } + } + + /* value */ + if((flags[permission_name] & 0x1) > 0 && value > 0) { + auto result = make_unique(); + result->permission_type = quick_mapping[permission_name]; + result->permission_value = value; + result->type = type; + result->channel_id = channel_id; + result->negate = negate; + result->skip = skip; + if (type == permission::SQL_PERM_GROUP) { + auto gr = this->server->groups->findGroup(id); + if (!gr) return 0; + + result->group_id = id; + if(gr->target() == GROUPTARGET_CHANNEL) + result->channel_id = 1; + } else if(type == permission::SQL_PERM_USER) { + result->client_id = id; + } + + if(result) + entries.push_back(std::move(result)); + } + + /* granted */ + if((flags[permission_name] & 0x2) > 0 && granted_value > 0) { + auto result = make_unique(); + result->permission_type = (permission::PermissionType) (quick_mapping[permission_name] | PERM_ID_GRANT); + result->permission_value = granted_value; + result->type = type; + result->channel_id = channel_id; + result->negate = negate; + result->skip = skip; + + if (type == permission::SQL_PERM_GROUP) { + auto gr = this->server->groups->findGroup(id); + if (!gr) return 0; + + result->group_id = id; + if(gr->target() == GROUPTARGET_CHANNEL) + result->channel_id = 1; + } else if(type == permission::SQL_PERM_USER) { + result->client_id = id; + } + + if(result) + entries.push_back(std::move(result)); + } + return 0; + }); + + struct CommandPerm { + permission::PermissionType p; + permission::PermissionValue v; + int64_t id1; + int64_t id2; + uint8_t t; + }; + + std::deque perms; + perms.resize(entries.size()); + size_t index = 0; + for(const auto& entry : entries) { + auto& perm = perms[index++]; + + perm.p = entry->permission_type; + perm.v = entry->permission_value; + + if(entry->type == permission::SQL_PERM_USER) { + if(entry->channel_id > 0) { + perm.id1 = entry->client_id; + perm.id2 = entry->channel_id; + perm.t = 4; /* client channel */ + } else { + perm.id1 = 0; + perm.id2 = entry->client_id; + perm.t = 1; /* client server */ + } + } else if(entry->type == permission::SQL_PERM_CHANNEL) { + perm.id1 = 0; + perm.id2 = entry->channel_id; + perm.t = 2; /* channel permission */ + } else if(entry->type == permission::SQL_PERM_GROUP) { + if(entry->channel_id > 0) { + perm.id1 = entry->group_id; + perm.id2 = 0; + perm.t = 3; /* channel group */ + } else { + perm.id1 = entry->group_id; + perm.id2 = 0; + perm.t = 0; /* server group */ + } + } + } + + + sort(perms.begin(), perms.end(), [](const CommandPerm& a, const CommandPerm& b) { + if(a.t < b.t) return true; + else if(b.t < a.t) return false; + + if(a.id1 < b.id1) return true; + else if(b.id1 < a.id1) return false; + + if(a.id2 < b.id2) return true; + else if(b.id2 < a.id2) return false; + + if(a.p < b.p) return true; + else if(b.p < a.p) return false; + + return &a > &b; + }); + + Command result(""); + index = 0; + + // http://yat.qa/ressourcen/server-query-kommentare/#permfind + for(const auto& e : perms) { + result[index]["p"] = e.p; + result[index]["v"] = e.v; + result[index]["id1"] = e.id1; + result[index]["id2"] = e.id2; + result[index]["t"] = e.t; + index++; + } + + if(index == 0) return command_result{error::database_empty_result}; + this->sendCommand(result); + return command_result{error::ok}; +} + +/* + * - Alle rechte der aktuellen server gruppen vom client + * - Alle client rechte | channel cleint rechte + * - Alle rechte des channels + */ +command_result ConnectedClient::handleCommandPermOverview(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto client_dbid = cmd["cldbid"].as(); + if(!serverInstance->databaseHelper()->validClientDatabaseId(this->getServer(), client_dbid)) return command_result{error::client_invalid_id}; + + if(client_dbid == this->getClientDatabaseId()) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_permissionoverview_own, 1); + } else { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_permissionoverview_view, 1); + } + + string channel_query, perm_query; + + auto channel = this->server ? this->server->channelTree->findChannel(cmd["cid"]) : serverInstance->getChannelTree()->findChannel(cmd["cid"]); + if(!channel) return command_result{error::channel_invalid_id}; + + auto server_groups = this->server->getGroupManager()->getServerGroups(client_dbid, ClientType::CLIENT_TEAMSPEAK); + auto channel_group = this->server->getGroupManager()->getChannelGroup(client_dbid, channel, true); + auto permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServer(), client_dbid); + + Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifypermoverview" : ""); + size_t index = 0; + result["cldbid"] = client_dbid; + result["cid"] = channel->channelId(); + if(cmd["return_code"].size() > 0) + result["return_code"] = cmd["return_code"].string(); + + for(const auto& server_group : server_groups) { + auto permission_manager = server_group->group->permissions(); + for(const auto& permission_data : permission_manager->permissions()) { + auto& permission = std::get<1>(permission_data); + if(permission.flags.value_set) { + result[index]["t"] = 0; /* server group */ + result[index]["id1"] = server_group->group->groupId(); + result[index]["id2"] = 0; + + result[index]["p"] = std::get<0>(permission_data); + result[index]["v"] = permission.values.value; + result[index]["n"] = permission.flags.negate; + result[index]["s"] = permission.flags.skip; + index++; + } + if(permission.flags.grant_set) { + result[index]["t"] = 0; /* server group */ + result[index]["id1"] = server_group->group->groupId(); + result[index]["id2"] = 0; + + result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); + result[index]["v"] = permission.values.grant; + result[index]["n"] = false; + result[index]["s"] = false; + index++; + } + } + } + + { + for(const auto& permission_data : permission_manager->permissions()) { + auto& permission = std::get<1>(permission_data); + if(permission.flags.value_set) { + result[index]["t"] = 1; /* client */ + result[index]["id1"] = client_dbid; + result[index]["id2"] = 0; + + result[index]["p"] = std::get<0>(permission_data); + result[index]["v"] = permission.values.value; + result[index]["n"] = permission.flags.negate; + result[index]["s"] = permission.flags.skip; + index++; + } + if(permission.flags.grant_set) { + result[index]["t"] = 1; /* client */ + result[index]["id1"] = client_dbid; + result[index]["id2"] = 0; + + result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); + result[index]["v"] = permission.values.grant; + result[index]["n"] = false; + result[index]["s"] = false; + index++; + } + } + } + + { + auto permission_manager = channel->permissions(); + for(const auto& permission_data : permission_manager->permissions()) { + auto& permission = std::get<1>(permission_data); + if(permission.flags.value_set) { + result[index]["t"] = 2; /* server channel */ + result[index]["id1"] = channel->channelId(); + result[index]["id2"] = 0; + + result[index]["p"] = std::get<0>(permission_data); + result[index]["v"] = permission.values.value; + result[index]["n"] = permission.flags.negate; + result[index]["s"] = permission.flags.skip; + index++; + } + if(permission.flags.grant_set) { + result[index]["t"] = 2; /* server channel */ + result[index]["id1"] = channel->channelId(); + result[index]["id2"] = 0; + + result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); + result[index]["v"] = permission.values.grant; + result[index]["n"] = false; + result[index]["s"] = false; + index++; + } + } + } + + { + auto permission_manager = channel_group->group->permissions(); + for(const auto& permission_data : permission_manager->permissions()) { + auto& permission = std::get<1>(permission_data); + if(permission.flags.value_set) { + result[index]["t"] = 3; /* channel group */ + result[index]["id1"] = channel_group->channelId; + result[index]["id2"] = channel_group->group->groupId(); + + result[index]["p"] = std::get<0>(permission_data); + result[index]["v"] = permission.values.value; + result[index]["n"] = permission.flags.negate; + result[index]["s"] = permission.flags.skip; + index++; + } + if(permission.flags.grant_set) { + result[index]["t"] = 3; /* channel group */ + result[index]["id1"] = channel_group->channelId; + result[index]["id2"] = channel_group->group->groupId(); + + result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); + result[index]["v"] = permission.values.grant; + result[index]["n"] = false; + result[index]["s"] = false; + index++; + } + } + } + + { + for(const auto& permission_data : permission_manager->channel_permissions()) { + auto& permission = std::get<2>(permission_data); + if(permission.flags.value_set) { + result[index]["t"] = 4; /* client channel */ + result[index]["id1"] = std::get<1>(permission_data); + result[index]["id2"] = client_dbid; + + result[index]["p"] = std::get<0>(permission_data); + result[index]["v"] = permission.values.value; + result[index]["n"] = permission.flags.negate; + result[index]["s"] = permission.flags.skip; + index++; + } + if(permission.flags.grant_set) { + result[index]["t"] = 1; /* client */ + result[index]["id1"] = std::get<1>(permission_data); + result[index]["id2"] = client_dbid; + + result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); + result[index]["v"] = permission.values.grant; + result[index]["n"] = false; + result[index]["s"] = false; + index++; + } + } + } + + if (index == 0) return command_result{error::database_empty_result}; + this->sendCommand(result); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandComplainAdd(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + ClientDbId target = cmd["tcldbid"]; + std::string msg = cmd["message"]; + + auto cl = this->server->findClientsByCldbId(target); + if (cl.empty()) return command_result{error::client_invalid_id}; + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_complain_power, cl[0]->calculate_permission(permission::i_client_needed_complain_power, 0)); + + /* + if(!serverInstance->databaseHelper()->validClientDatabaseId(target)) + return command_result{error::client_invalid_id, "invalid database id"}; + */ + + for (const auto &elm : this->server->complains->findComplainsFromTarget(target)) + if (elm->invoker == this->getClientDatabaseId()) + return command_result{error::database_duplicate_entry, "you already send a complain"}; + + if (!this->server->complains->createComplain(target, this->getClientDatabaseId(), msg)) return command_result{error::vs_critical, "could not create complains"}; + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandComplainList(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_complain_list, 1); + + ClientDbId id = cmd[0].has("tcldbid") ? cmd["tcldbid"].as() : 0; + auto list = id == 0 ? this->server->complains->complains() : this->server->complains->findComplainsFromTarget(id); + if (list.empty()) return command_result{error::database_empty_result}; + + deque nameQuery; + for (const auto &elm : list) { + if (std::find(nameQuery.begin(), nameQuery.end(), elm->invoker) == nameQuery.end()) + nameQuery.push_back(elm->invoker); + if (std::find(nameQuery.begin(), nameQuery.end(), elm->target) == nameQuery.end()) + nameQuery.push_back(elm->target); + } + + auto dbInfo = serverInstance->databaseHelper()->queryDatabaseInfo(this->server, nameQuery); + + Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifycomplainlist" : ""); + int index = 0; + for (const auto &elm : list) { + result[index]["tcldbid"] = elm->target; + result[index]["tname"] = "unknown"; + + result[index]["fcldbid"] = elm->invoker; + result[index]["fname"] = "unknown"; + + result[index]["message"] = elm->reason; + result[index]["timestamp"] = chrono::duration_cast(elm->created.time_since_epoch()).count(); + + for (const auto &e : dbInfo) { + if (e->cldbid == elm->target) + result[index]["tname"] = e->lastName; + if (e->cldbid == elm->invoker) + result[index]["fname"] = e->lastName; + } + index++; + } + + this->sendCommand(result); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandComplainDel(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + ClientDbId tid = cmd["tcldbid"]; + ClientDbId fid = cmd["fcldbid"]; + + shared_ptr entry; + for (const auto &elm : this->server->complains->findComplainsFromTarget(tid)) + if (elm->invoker == fid) { + entry = elm; + break; + } + if (!entry) return command_result{error::database_empty_result}; + + if (entry->invoker == this->getClientDatabaseId()) + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_complain_delete_own, 1); + else + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_complain_delete, 1); + + this->server->complains->deleteComplain(entry); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandComplainDelAll(Command &cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_complain_delete, 1); + + ClientDbId tid = cmd["tcldbid"]; + if (!this->server->complains->deleteComplainsFromTarget(tid)) return command_result{error::database_empty_result}; + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandHelp(Command& cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_help_view, 1); + + string command = cmd[0].has("command") ? cmd["command"].as() : ""; + if(command.empty()) + for(const auto& key : cmd[0].keys()) { + command = key; + break; + } + if(command.empty()) + command = "help"; + std::transform(command.begin(), command.end(), command.begin(), ::tolower); + + auto file = fs::u8path("commanddocs/" + command + ".txt"); + if(!fs::exists(file)) return command_result{error::file_not_found}; + + string line; + ifstream stream(file); + if(!stream) return command_result{error::file_io_error}; + while(getline(stream, line)) + this->sendCommand(Command{line}); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPermReset(ts::Command& cmd) { + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(50); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_permission_reset, 1); + + string token; + if(!this->server->resetPermissions(token)) + return command_result{error::vs_critical}; + + Command result(""); + result["token"] = token; + this->sendCommand(result); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(50); + + auto lagacy = this->getType() == CLIENT_TEAMSPEAK || cmd.hasParm("lagacy") || cmd.hasParm("legacy"); + string log_path; + ServerId target_server = cmd[0].has("instance") && cmd["instance"].as() ? (ServerId) 0 : this->getServerId(); + string server_identifier; + if(target_server > 0) + server_identifier = to_string(target_server); + else server_identifier = "[A-Z]{0,7}"; + + if(target_server == 0) + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_view, 1); + else + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_view, 1); + + for(const auto& sink : logger::logger(target_server)->sinks()) { + if(dynamic_pointer_cast(sink)) { + log_path = dynamic_pointer_cast(sink)->filename(); + break; + } else if(dynamic_pointer_cast(sink)) { + log_path = dynamic_pointer_cast(sink)->filename(); + break; + } + } + if(log_path.empty()) + return command_result{error::file_not_found, "Cant find log file (May log disabled?)"}; + + { //Replace " within the log path + size_t index = 0; + while((index = log_path.find('"', index)) != string::npos) { + log_path.replace(index, 1, "\\\""); + index += 2; + } + } + string command = "cat \"" + log_path + "\""; + command += " | grep -E "; + command += "\"\\] \\[.*\\]( ){0,6}?" + server_identifier + " \\|\""; + + size_t beginpos = cmd[0].has("begin_pos") ? cmd["begin_pos"].as() : 0ULL; //TODO test it? + size_t file_index = 0; + size_t max_lines = cmd[0].has("lines") ? cmd["lines"].as() : 100ULL; //TODO bounds? + deque> lines; + { + debugMessage(target_server, "Logview command: \"{}\"", command); + + array buffer{}; + string line_buffer; + + std::shared_ptr pipe(popen(command.c_str(), "r"), pclose); + if (!pipe) return command_result{error::file_io_error, "Could not execute command"}; + while (!feof(pipe.get())) { + auto read = fread(buffer.data(), 1, buffer.size(), pipe.get()); + if(read > 0) { + if(beginpos == 0 || file_index < beginpos) { + if(beginpos != 0 && file_index + read > beginpos) { //We're done we just want to get the size later + line_buffer += string(buffer.data(), beginpos - file_index); + + lines.push_back({file_index, line_buffer}); + if(lines.size() > max_lines) lines.pop_front(); + //debugMessage(LOG_GENERAL, "Final line {}", line_buffer); + line_buffer = ""; + } else { + line_buffer += string(buffer.data(), read); + + size_t index; + size_t length; + size_t cut_offset = 0; + while((index = line_buffer.find("\n")) != string::npos || (index = line_buffer.find("\r")) != string::npos) { + length = 0; + if(index > 0) { + if(line_buffer[index - 1] == '\r' || line_buffer[index - 1] == '\n') { + length = 2; + index--; + } + } + if(length == 0) { + if(index + 1 < line_buffer.length()) { + if(line_buffer[index + 1] == '\r' || line_buffer[index + 1] == '\n') { + length = 2; + } + } + } + if(length == 0) length = 1; + + //debugMessage(LOG_GENERAL, "Got line {}", line_buffer.substr(0, index)); + lines.push_back({file_index + cut_offset, line_buffer.substr(0, index)}); + if(lines.size() > max_lines) lines.pop_front(); + + cut_offset += index + length; + line_buffer = line_buffer.substr(index + length); + } + } + } + file_index += read; + } else if(read < 0) return command_result{error::file_io_error, "fread(...) returned " + to_string(read) + " (" + to_string(errno) + ")"}; + } + + if(!line_buffer.empty()) { + lines.push_back({file_index - line_buffer.length(), line_buffer}); + if(lines.size() > max_lines) lines.pop_front(); + } + } + //last_pos=1558 file_size=1764 l + if(lines.empty()) return command_result{error::database_empty_result}; + Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyserverlog" : ""); + result["last_pos"] = lines.front().first; + result["file_size"] = file_index; + + if(!(cmd.hasParm("reverse") && cmd["revers"].as())) + std::reverse(lines.begin(), lines.end()); + + int index = 0; + for(const auto& index_line : lines) { + auto line = index_line.second; + //2018-07-15 21:01:46.488639 + //TeamSpeak format: + //YYYY-MM-DD hh:mm:ss.millis|{:<8}|{:<14}|{:<3}|.... + //2018-07-15 21:01:47.066367|INFO |VirtualServer |1 |listening on 0.0.0.0:9989, [::]:9989 + + //TeaSpeak: + //[2018-07-15 23:21:47] [ERROR] Timer sql_test tick needs more than 9437 microseconds. Max allowed was 5000 microseconds. + if(lagacy) { + string ts = line.substr(1, 19) + ".000000|"; + + { + string type = "unknown"; + + auto idx = line.find_first_of('[', 2); + if(idx != string::npos) { + type = line.substr(idx + 1, line.find(']', idx + 1) - idx - 1); + } + + ts += type + " | | |" + line.substr(line.find('|') + 1); + } + result[index++]["l"] = ts; + } else { + result[index++]["l"] = line; + } + } + this->sendCommand(result); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandUpdateMyTsId(ts::Command &) { + if(config::voice::suppress_myts_warnings) return command_result{error::ok}; + return command_result{error::not_implemented}; +} + +command_result ConnectedClient::handleCommandUpdateMyTsData(ts::Command &) { + if(config::voice::suppress_myts_warnings) return command_result{error::ok}; + return command_result{error::not_implemented}; +} + + +command_result ConnectedClient::handleCommandQueryList(ts::Command &cmd) { + OptionalServerId server_id = EmptyServerId; + if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) + server_id = this->getServerId(); + + if(cmd[0].has("server_id")) + server_id = cmd["server_id"]; + if(cmd[0].has("sid")) + server_id = cmd["sid"]; + + auto server = server_id == EmptyServerId ? nullptr : serverInstance->getVoiceServerManager()->findServerById(server_id); + if(!server && server_id != EmptyServerId && server_id != 0) + return command_result{error::server_invalid_id}; + + auto global_list = permission::v2::permission_granted(1, + server ? server->calculate_permission(permission::b_client_query_list, this->getClientDatabaseId(), this->getType(), 0) : + serverInstance->calculate_permission(permission::b_client_query_list, this->getClientDatabaseId(), this->getType(), 0) + ); + auto own_list = global_list || permission::v2::permission_granted(1, + server ? server->calculate_permission(permission::b_client_query_list_own, this->getClientDatabaseId(), this->getType(), 0) : + serverInstance->calculate_permission(permission::b_client_query_list_own, this->getClientDatabaseId(), this->getType(), 0) + ); + + if(!own_list && !global_list) + return command_result{permission::b_client_query_list}; + + auto accounts = serverInstance->getQueryServer()->list_query_accounts(server_id); + if(!global_list) { + accounts.erase(remove_if(accounts.begin(), accounts.end(), [&](const std::shared_ptr& account) { + return account->unique_id != this->getUid(); + }), accounts.end()); + } + + if(accounts.empty()) + return command_result{error::database_empty_result}; + + Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyquerylist" : ""); + result["server_id"] = server_id; + result["flag_own"] = own_list; + result["flag_all"] = global_list; + + size_t index = 0; + for(const auto& account : accounts) { + result[index]["client_unique_identifier"] = account->unique_id; + result[index]["client_login_name"] = account->username; + result[index]["client_bound_server"] = account->bound_server; + index++; + } + + this->sendCommand(result); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) { + OptionalServerId server_id = this->getServerId(); + if(cmd[0].has("server_id")) + server_id = cmd["server_id"]; + if(cmd[0].has("sid")) + server_id = cmd["sid"]; + + auto server = server_id == EmptyServerId ? nullptr : serverInstance->getVoiceServerManager()->findServerById(server_id); + if(!server && server_id != EmptyServerId && server_id != 0) + return command_result{error::server_invalid_id}; + + if(server) { + if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0))) + return command_result{permission::b_client_query_create}; + } else { + if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0))) + return command_result{permission::b_client_query_create}; + } + + auto username = cmd["client_login_name"].as(); + auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as() : ""; + + if(password.empty()) + password = rnd_string(QUERY_PASSWORD_LENGTH); + + auto account = serverInstance->getQueryServer()->find_query_account_by_name(username); + if(account) return command_result{error::query_already_exists}; + + account = serverInstance->getQueryServer()->create_query_account(username, server_id, this->getUid(), password); + if(!account) + return command_result{error::vs_critical}; + + Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyquerycreated" : ""); + result["client_unique_identifier"] = account->unique_id; + result["client_login_name"] = account->username; + result["client_login_password"] = password; + result["client_bound_server"] = account->bound_server; + this->sendCommand(result); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandQueryDelete(ts::Command &cmd) { + auto username = cmd["client_login_name"].as(); + auto account = serverInstance->getQueryServer()->find_query_account_by_name(username); + if(!account) + return command_result{error::query_not_exists}; + + auto server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server); + /* If the server is not existing anymore, we're asking for global permissions + if(!server && account->bounded_server != 0) + return command_result{error::server_invalid_id}; + */ + + auto delete_all = permission::v2::permission_granted(1, + server ? server->calculate_permission(permission::b_client_query_delete, this->getClientDatabaseId(), this->getType(), 0) : + serverInstance->calculate_permission(permission::b_client_query_delete, this->getClientDatabaseId(), this->getType(), 0) + ); + auto delete_own = delete_all || permission::v2::permission_granted(1, + server ? server->calculate_permission(permission::b_client_query_delete_own, this->getClientDatabaseId(), this->getType(), 0) : + serverInstance->calculate_permission(permission::b_client_query_delete_own, this->getClientDatabaseId(), this->getType(), 0) + ); + + if(account->unique_id == this->getUid()) { + if(!delete_own) + return command_result{permission::b_client_query_delete_own}; + } else { + if(!delete_all) + return command_result{permission::b_client_query_delete}; + } + if(account->unique_id == "serveradmin" && account->username == "serveradmin") + return command_result{error::vs_critical}; + + serverInstance->getQueryServer()->delete_query_account(account); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandQueryRename(ts::Command &cmd) { + auto username = cmd["client_login_name"].as(); + auto new_username = cmd["client_new_login_name"].as(); + + auto account = serverInstance->getQueryServer()->find_query_account_by_name(username); + if(!account) + return command_result{error::query_not_exists}; + + auto server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server); + if(!server && account->bound_server != 0) + return command_result{error::server_invalid_id}; + + auto rename_all = permission::v2::permission_granted(1, + server ? server->calculate_permission(permission::b_client_query_rename, this->getClientDatabaseId(), this->getType(), 0) : + serverInstance->calculate_permission(permission::b_client_query_rename, this->getClientDatabaseId(), this->getType(), 0) + ); + auto rename_own = rename_all || permission::v2::permission_granted(1, + server ? server->calculate_permission(permission::b_client_query_rename_own, this->getClientDatabaseId(), this->getType(), 0) : + serverInstance->calculate_permission(permission::b_client_query_rename_own, this->getClientDatabaseId(), this->getType(), 0) + ); + + if(account->unique_id == this->getUid()) { + if(!rename_own) + return command_result{permission::b_client_query_rename_own}; + } else { + if(!rename_all) + return command_result{permission::b_client_query_rename}; + } + + auto target_account = serverInstance->getQueryServer()->find_query_account_by_name(new_username); + if(target_account) return command_result{error::query_already_exists}; + + if(account->unique_id == "serveradmin" && account->username == "serveradmin") + return command_result{error::parameter_invalid}; + + if(!serverInstance->getQueryServer()->rename_query_account(account, new_username)) + return command_result{error::vs_critical}; + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandQueryChangePassword(ts::Command &cmd) { + auto username = cmd["client_login_name"].as(); + auto account = serverInstance->getQueryServer()->find_query_account_by_name(username); + if(!account) + return command_result{error::query_not_exists}; + + auto server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server); + if(!server && account->bound_server != 0) + return command_result{error::server_invalid_id}; + + auto change_all = permission::v2::permission_granted(1, + server ? server->calculate_permission(permission::b_client_query_change_password, this->getClientDatabaseId(), this->getType(), 0) : + serverInstance->calculate_permission(permission::b_client_query_change_password_global, this->getClientDatabaseId(), this->getType(), 0) + ); + auto change_own = change_all || permission::v2::permission_granted(1, + server ? server->calculate_permission(permission::b_client_query_change_own_password, this->getClientDatabaseId(), this->getType(), 0) : + serverInstance->calculate_permission(permission::b_client_query_change_own_password, this->getClientDatabaseId(), this->getType(), 0) + ); + + auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as() : ""; + + if(password.empty()) + password = rnd_string(QUERY_PASSWORD_LENGTH); + + if(account->unique_id == this->getUid()) { + if(!change_own) + return command_result{permission::b_client_query_change_own_password}; + } else { + if(!change_all) + return command_result{server ? permission::b_client_query_change_password : permission::b_client_query_change_password_global}; + } + + if(!serverInstance->getQueryServer()->change_query_password(account, password)) + return command_result{error::vs_critical}; + + Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyquerypasswordchanges" : ""); + result["client_login_name"] = account->username; + result["client_login_password"] = password; + this->sendCommand(result); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandDummy_IpChange(ts::Command &cmd) { + CMD_REF_SERVER(server); + logMessage(this->getServerId(), "[{}] Address changed from {} to {}", CLIENT_STR_LOG_PREFIX, cmd["old_ip"].string(), cmd["new_ip"].string()); + + if(geoloc::provider_vpn && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_vpn, 0))) { + auto provider = this->isAddressV4() ? geoloc::provider_vpn->resolveInfoV4(this->getPeerIp(), true) : geoloc::provider_vpn->resolveInfoV6(this->getPeerIp(), true); + if(provider) { + this->disconnect(strvar::transform(ts::config::messages::kick_vpn, strvar::StringValue{"provider.name", provider->name}, strvar::StringValue{"provider.website", provider->side})); + return command_result{error::ok}; + } + } + + string new_country = config::geo::countryFlag; + if(geoloc::provider) { + auto loc = this->isAddressV4() ? geoloc::provider->resolveInfoV4(this->getPeerIp(), false) : geoloc::provider->resolveInfoV6(this->getPeerIp(), false); + if(loc) { + logError(this->getServerId(), "[{}] Received new ip location. IP {} traced to {} ({}).", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp(), loc->name, loc->identifier); + this->properties()[property::CLIENT_COUNTRY] = loc->identifier; + server->notifyClientPropertyUpdates(_this.lock(), deque{property::CLIENT_COUNTRY}); + new_country = loc->identifier; + } else { + logError(this->getServerId(), "[{}] Failed to resolve ip location for IP {}.", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp()); + } + } + + this->properties()[property::CONNECTION_CLIENT_IP] = this->getLoggingPeerIp(); + return command_result{error::ok}; +} + +//conversationhistory cid=1 [cpw=xxx] [timestamp_begin] [timestamp_end (0 := no end)] [message_count (default 25| max 100)] [-merge] +command_result ConnectedClient::handleCommandConversationHistory(ts::Command &command) { + CMD_REF_SERVER(ref_server); + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + if(!command[0].has("cid") || !command[0]["cid"].castable()) + return command_result{error::conversation_invalid_id}; + + auto conversation_id = command[0]["cid"].as(); + /* test if we have access to the conversation */ + { + /* test if we're able to see the channel */ + { + shared_lock channel_view_lock(this->channel_lock); + auto channel = this->channel_view()->find_channel(conversation_id); + if(!channel) + return command_result{error::conversation_invalid_id}; + if(channel->channel()->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as()) + return command_result{error::conversation_is_private}; + } + + /* test if there is a channel password or join power which denies that we see the conversation */ + { + shared_lock channel_view_lock(ref_server->channel_tree_lock); + auto channel = ref_server->getChannelTree()->findChannel(conversation_id); + if(!channel) /* should never happen! */ + return command_result{error::conversation_invalid_id}; + + if(!command[0].has("cpw")) + command[0]["cpw"] = ""; + + if (!channel->passwordMatch(command["cpw"], true)) + ACTION_REQUIRES_PERMISSION(permission::b_channel_join_ignore_password, 1, channel->channelId()); + + auto error = this->calculate_and_get_join_state(channel); + if(error) return command_result{error}; + } + } + + auto conversation_manager = ref_server->conversation_manager(); + auto conversation = conversation_manager->get(conversation_id); + if(!conversation) + return command_result{error::database_empty_result}; + + system_clock::time_point timestamp_begin = system_clock::now(); + system_clock::time_point timestamp_end; + size_t message_count = 25; + + if(command[0].has("timestamp_begin")) + timestamp_begin = system_clock::time_point{} + milliseconds(command[0]["timestamp_begin"].as()); + + if(command[0].has("timestamp_end")) + timestamp_end = system_clock::time_point{} + milliseconds(command[0]["timestamp_end"].as()); + + if(command[0].has("message_count")) + message_count = command[0]["message_count"].as(); + + if(timestamp_begin < timestamp_end) + return command_result{error::parameter_invalid}; + if(message_count > 100) + message_count = 100; + + auto messages = conversation->message_history(timestamp_begin, message_count + 1, timestamp_end); /* query one more to test for more data */ + if(messages.empty()) + return command_result{error::database_empty_result}; + bool more_data = messages.size() > message_count; + if(more_data) + messages.pop_back(); + + Command notify(this->notify_response_command("notifyconversationhistory")); + size_t index = 0; + size_t length = 0; + bool merge = command.hasParm("merge"); + + for(auto it = messages.rbegin(); it != messages.rend(); it++) { + if(index == 0) { + notify[index]["cid"] = conversation_id; + notify[index]["flag_volatile"] = conversation->volatile_only(); + } + + auto& message = *it; + notify[index]["timestamp"] = duration_cast(message->message_timestamp.time_since_epoch()).count(); + notify[index]["sender_database_id"] = message->sender_database_id; + notify[index]["sender_unique_id"] = message->sender_unique_id; + notify[index]["sender_name"] = message->sender_name; + + notify[index]["msg"] = message->message; + length += message->message.size(); + length += message->sender_name.size(); + length += message->sender_unique_id.size(); + if(length > 1024 * 8 || !merge) { + index = 0; + this->sendCommand(notify); + notify = Command{this->notify_response_command("notifyconversationhistory")}; + } else + index++; + } + if(index > 0) + this->sendCommand(notify); + + if(more_data) + return command_result{error::conversation_more_data}; + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandConversationFetch(ts::Command &cmd) { + CMD_REF_SERVER(ref_server); + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + + Command result(this->notify_response_command("notifyconversationindex")); + size_t result_index = 0; + + auto conversation_manager = ref_server->conversation_manager(); + for(size_t index = 0; index < cmd.bulkCount(); index++) { + auto& bulk = cmd[index]; + + if(!bulk.has("cid") || !bulk["cid"].castable()) + continue; + auto conversation_id = bulk["cid"].as(); + + auto& result_bulk = result[result_index++]; + result_bulk["cid"] = conversation_id; + + /* test if we have access to the conversation */ + { + /* test if we're able to see the channel */ + { + shared_lock channel_view_lock(this->channel_lock); + auto channel = this->channel_view()->find_channel(conversation_id); + if(!channel) { + auto error = findError("conversation_invalid_id"); + result_bulk["error_id"] = error.errorId; + result_bulk["error_msg"] = error.message; + continue; + } + if(channel->channel()->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as()) { + auto error = findError("conversation_is_private"); + result_bulk["error_id"] = error.errorId; + result_bulk["error_msg"] = error.message; + continue; + } + } + + /* test if there is a channel password or join power which denies that we see the conversation */ + { + shared_lock channel_view_lock(ref_server->channel_tree_lock); + auto channel = ref_server->getChannelTree()->findChannel(conversation_id); + if(!channel) { /* should never happen! */ + auto error = findError("conversation_invalid_id"); + result_bulk["error_id"] = error.errorId; + result_bulk["error_msg"] = error.message; + continue; + } + + if(!bulk.has("cpw")) + bulk["cpw"] = ""; + + if (!channel->passwordMatch(bulk["cpw"], true)) + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, 1, channel->channelId()))) { + auto error = findError("channel_invalid_password"); + result_bulk["error_id"] = error.errorId; + result_bulk["error_msg"] = error.message; + continue; + } + + if(auto error_perm = this->calculate_and_get_join_state(channel); error_perm) { + auto error = findError("server_insufficeient_permissions"); + result_bulk["error_id"] = error.errorId; + result_bulk["error_msg"] = error.message; + result_bulk["failed_permid"] = (int) error_perm; + continue; + } + } + } + + auto conversation = conversation_manager->get(conversation_id); + if(conversation) + result_bulk["timestamp"] = duration_cast(conversation->last_message().time_since_epoch()).count(); + else + result_bulk["timestamp"] = 0; + result_bulk["flag_volatile"] = conversation->volatile_only(); + } + if(result_index == 0) + return command_result{error::database_empty_result}; + this->sendCommand(result); + + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandConversationMessageDelete(ts::Command &cmd) { + CMD_REF_SERVER(ref_server); + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto conversation_manager = ref_server->conversation_manager(); + std::shared_ptr current_conversation; + ChannelId current_conversation_id = 0; + + for(size_t index = 0; index < cmd.bulkCount(); index++) { + auto &bulk = cmd[index]; + + if(!bulk.has("cid") || !bulk["cid"].castable()) + continue; + + /* test if we have access to the conversation */ + if(current_conversation_id != bulk["cid"].as()) { + current_conversation_id = bulk["cid"].as(); + + /* test if we're able to see the channel */ + { + shared_lock channel_view_lock(this->channel_lock); + auto channel = this->channel_view()->find_channel(current_conversation_id); + if(!channel) + return command_result{error::conversation_invalid_id}; + } + + /* test if there is a channel password or join power which denies that we see the conversation */ + { + shared_lock channel_view_lock(ref_server->channel_tree_lock); + auto channel = ref_server->getChannelTree()->findChannel(current_conversation_id); + if(!channel) + return command_result{error::conversation_invalid_id}; + + if(!bulk.has("cpw")) + bulk["cpw"] = ""; + + if (!channel->passwordMatch(bulk["cpw"], true)) + ACTION_REQUIRES_PERMISSION(permission::b_channel_join_ignore_password, 1, channel->channelId()); + + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_conversation_message_delete, 1, channel->channelId()))) + return command_result{permission::b_channel_conversation_message_delete}; + + if(auto error_perm = this->calculate_and_get_join_state(channel); error_perm != permission::b_client_is_sticky) + return command_result{error_perm}; + } + } + + current_conversation = conversation_manager->get(current_conversation_id); + if(!current_conversation) continue; + + auto timestamp_begin = system_clock::time_point{} + milliseconds{bulk["timestamp_begin"]}; + auto timestamp_end = system_clock::time_point{} + milliseconds{bulk.has("timestamp_end") ? bulk["timestamp_end"].as() : 0}; + auto limit = bulk.has("limit") ? bulk["limit"].as() : 1; + if(limit > 100) + limit = 100; + auto delete_count = current_conversation->delete_messages(timestamp_end, limit, timestamp_begin, bulk["cldbid"]); + if(delete_count > 0) { + for(const auto& client : ref_server->getClients()) { + if(client->connectionState() != ConnectionState::CONNECTED) + continue; + + auto type = client->getType(); + if(type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC) + continue; + + client->notifyConversationMessageDelete(current_conversation_id, timestamp_begin, timestamp_end, bulk["cldbid"], delete_count); + } + } + } + + return command_result{error::ok}; +} + + + + + + + + + + + + + + + + + + + diff --git a/server/src/client/command_handler/music.cpp b/server/src/client/command_handler/music.cpp new file mode 100644 index 0000000..8a1c4ae --- /dev/null +++ b/server/src/client/command_handler/music.cpp @@ -0,0 +1,924 @@ +// +// Created by wolverindev on 26.01.20. +// + +#include + +#include + +#include +#include +#include +#include +#include "../../build.h" +#include "../ConnectedClient.h" +#include "../InternalClient.h" +#include "../../server/file/FileServer.h" +#include "../../server/VoiceServer.h" +#include "../voice/VoiceClient.h" +#include "PermissionManager.h" +#include "../../InstanceHandler.h" +#include "../../server/QueryServer.h" +#include "../file/FileClient.h" +#include "../music/MusicClient.h" +#include "../query/QueryClient.h" +#include "../../weblist/WebListManager.h" +#include "../../manager/ConversationManager.h" +#include "../../manager/PermissionNameMapper.h" +#include +#include +#include + +#include "helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::experimental::filesystem; +using namespace std::chrono; +using namespace std; +using namespace ts; +using namespace ts::server; +using namespace ts::token; + +command_result ConnectedClient::handleCommandMusicBotCreate(Command& cmd) { + if(!config::music::enabled) return command_result{error::music_disabled}; + + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + if(this->server->musicManager->max_bots() != -1 && this->server->musicManager->max_bots() <= this->server->musicManager->current_bot_count()){ + if(config::license->isPremium()) + return command_result{error::music_limit_reached}; + else + return command_result{error::music_limit_reached, strobf("You reached the server music bot limit. You could increase this limit by extend your server with a premium license.").string()}; + } + + + auto permissions_list = this->calculate_permissions({ + permission::i_client_music_limit, + permission::b_client_music_create_permanent, + permission::b_client_music_create_semi_permanent, + permission::b_client_music_create_temporary, + permission::i_channel_join_power, + permission::i_client_music_delete_power, + permission::i_client_music_create_modify_max_volume + }, this->getChannelId()); + + auto permissions = map(permissions_list.begin(), permissions_list.end()); + + auto max_bots = permissions[permission::i_client_music_limit]; + if(max_bots.has_value) { + auto ownBots = this->server->musicManager->listBots(this->getClientDatabaseId()); + if(!permission::v2::permission_granted(ownBots.size() + 1, max_bots)) + return command_result{error::music_client_limit_reached}; + } + + MusicClient::Type::value create_type; + if(cmd[0].has("type")) { + create_type = cmd["type"].as(); + switch(create_type) { + case MusicClient::Type::PERMANENT: + if(!permission::v2::permission_granted(1, permissions[permission::b_client_music_create_permanent])) + return command_result{permission::b_client_music_create_permanent}; + break; + case MusicClient::Type::SEMI_PERMANENT: + if(!permission::v2::permission_granted(1, permissions[permission::b_client_music_create_semi_permanent])) + return command_result{permission::b_client_music_create_semi_permanent}; + break; + case MusicClient::Type::TEMPORARY: + if(!permission::v2::permission_granted(1, permissions[permission::b_client_music_create_temporary])) + return command_result{permission::b_client_music_create_temporary}; + break; + default: + return command_result{error::vs_critical}; + } + } else { + if(permission::v2::permission_granted(1, permissions[permission::b_client_music_create_permanent])) + create_type = MusicClient::Type::PERMANENT; + else if(permission::v2::permission_granted(1, permissions[permission::b_client_music_create_semi_permanent])) + create_type = MusicClient::Type::SEMI_PERMANENT; + else if(permission::v2::permission_granted(1, permissions[permission::b_client_music_create_temporary])) + create_type = MusicClient::Type::TEMPORARY; + else + return command_result{permission::b_client_music_create_temporary}; + } + + shared_lock server_channel_lock(this->server->channel_tree_lock); + auto channel = cmd[0].has("cid") ? this->server->channelTree->findChannel(cmd["cid"]) : this->currentChannel; + if(!channel) { + if(cmd[0].has("cid")) return command_result{error::channel_invalid_id}; + } else { + if(!this->calculate_and_get_join_state(channel)) + channel = nullptr; + } + if(!channel) + channel = this->server->channelTree->getDefaultChannel(); + + auto bot = this->server->musicManager->createBot(this->getClientDatabaseId()); + if(!bot) return command_result{error::vs_critical}; + bot->set_bot_type(create_type); + if(permissions[permission::i_client_music_create_modify_max_volume].has_value) { + auto max_volume = min(100, max(0, permissions[permission::i_client_music_create_modify_max_volume].value)); + if(max_volume >= 0) + bot->volume_modifier(max_volume / 100.f); + } + this->selectedBot = bot; + + { + server_channel_lock.unlock(); + unique_lock server_channel_w_lock(this->server->channel_tree_lock); + this->server->client_move( + bot, + channel, + nullptr, + "music bot created", + ViewReasonId::VREASON_USER_ACTION, + false, + server_channel_w_lock + ); + } + bot->properties()[property::CLIENT_LAST_CHANNEL] = channel ? channel->channelId() : 0; + bot->properties()[property::CLIENT_COUNTRY] = config::geo::countryFlag; + + if(permissions[permission::i_client_music_delete_power].has_value && permissions[permission::i_client_music_delete_power].value >= 0) { + bot->clientPermissions->set_permission(permission::i_client_music_needed_delete_power, {permissions[permission::i_client_music_delete_power].value,0}, permission::v2::set_value, permission::v2::do_nothing); + } + + Command notify(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifymusiccreated" : ""); + notify["bot_id"] = bot->getClientDatabaseId(); + this->sendCommand(notify); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandMusicBotDelete(Command& cmd) { + if(!config::music::enabled) return command_result{error::music_disabled}; + + 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}; + + if(bot->getOwner() != this->getClientDatabaseId()) { + ACTION_REQUIRES_PERMISSION(permission::i_client_music_delete_power, bot->calculate_permission(permission::i_client_music_needed_delete_power, bot->getChannelId()), this->getChannelId()); + } + + this->server->musicManager->deleteBot(bot); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandMusicBotSetSubscription(ts::Command &cmd) { + if(!config::music::enabled) return command_result{error::music_disabled}; + + auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); + if(!bot && cmd["bot_id"].as() != 0) return command_result{error::music_invalid_id}; + + { + auto old_bot = this->subscribed_bot.lock(); + if(old_bot) + old_bot->remove_subscriber(_this.lock()); + } + + if(bot) { + bot->add_subscriber(_this.lock()); + this->subscribed_bot = bot; + } + + return command_result{error::ok}; +} + +void apply_song(Command& command, const std::shared_ptr& element, int index = 0) { + if(!element) return; + + command[index]["song_id"] = element ? element->getSongId() : 0; + command[index]["song_url"] = element ? element->getUrl() : ""; + command[index]["song_invoker"] = element ? element->getInvoker() : 0; + command[index]["song_loaded"] = false; + + auto entry = dynamic_pointer_cast(element); + if(entry) { + auto data = entry->song_loaded_data(); + command[index]["song_loaded"] = entry->song_loaded() && data; + + if(entry->song_loaded() && data) { + command[index]["song_title"] = data->title; + command[index]["song_description"] = data->description; + command[index]["song_thumbnail"] = data->thumbnail; + command[index]["song_length"] = data->length.count(); + } + } +} + +command_result ConnectedClient::handleCommandMusicBotPlayerInfo(Command& cmd) { + if(!config::music::enabled) return command_result{error::music_disabled}; + + CMD_REQ_SERVER; + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); + if(!bot) return command_result{error::music_invalid_id}; + + Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifymusicplayerinfo" : ""); + result["bot_id"] = bot->getClientDatabaseId(); + + result["player_state"] =(int) bot->player_state(); + if(bot->current_player()) { + result["player_buffered_index"] = bot->current_player()->bufferedUntil().count(); + result["player_replay_index"] = bot->current_player()->currentIndex().count(); + result["player_max_index"] = bot->current_player()->length().count(); + result["player_seekable"] = bot->current_player()->seek_supported(); + + result["player_title"] = bot->current_player()->songTitle(); + result["player_description"] = bot->current_player()->songDescription(); + } else { + result["player_buffered_index"] = 0; + result["player_replay_index"] = 0; + result["player_max_index"] = 0; + result["player_seekable"] = 0; + result["player_title"] = ""; + result["player_description"] = ""; + } + + apply_song(result, bot->current_song()); + this->sendCommand(result); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandMusicBotPlayerAction(Command& cmd) { + if(!config::music::enabled) return command_result{error::music_disabled}; + + 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}; + ACTION_REQUIRES_PERMISSION(permission::i_client_music_play_power, bot->calculate_permission(permission::i_client_music_needed_play_power, bot->getChannelId()), this->getChannelId()); + + if(cmd["action"] == 0) { + bot->stopMusic(); + } else if(cmd["action"] == 1) { + bot->playMusic(); + } else if(cmd["action"] == 2) { + bot->player_pause(); + } else if(cmd["action"] == 3) { + bot->forwardSong(); + } else if(cmd["action"] == 4) { + bot->rewindSong(); + } else if(cmd["action"] == 5) { + if(!bot->current_player()) return command_result{error::music_no_player}; + bot->current_player()->forward(::music::PlayerUnits(cmd["units"].as())); + } else if(cmd["action"] == 6) { + if(!bot->current_player()) return command_result{error::music_no_player}; + bot->current_player()->rewind(::music::PlayerUnits(cmd["units"].as())); + } else return command_result{error::music_invalid_action}; + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistList(ts::Command &cmd) { + CMD_REQ_SERVER; + 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 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);; + }), playlists.end()); + + if(playlists.empty()) + return command_result{error::database_empty_result}; + + Command notify(this->notify_response_command("notifyplaylistlist")); + + size_t index = 0; + for(const auto& entry : playlists) { + notify[index]["playlist_id"] = entry->playlist_id(); + auto bot = entry->current_bot(); + notify[index]["playlist_bot_id"] = bot ? bot->getClientDatabaseId() : 0; + notify[index]["playlist_title"] = entry->properties()[property::PLAYLIST_TITLE].value(); + 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); + index++; + } + + this->sendCommand(notify); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistCreate(ts::Command &cmd) { + CMD_REF_SERVER(ref_server); + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_playlist_create, 1); + + { + auto max_playlists = this->calculate_permission(permission::i_max_playlists, 0); + if(max_playlists.has_value) { + auto playlists = ref_server->musicManager->find_playlists_by_client(this->getClientDatabaseId()); + if(!permission::v2::permission_granted(playlists.size(), max_playlists)) + return command_result{permission::i_max_playlists}; + } + } + + auto playlist = ref_server->musicManager->create_playlist(this->getClientDatabaseId(), this->getDisplayName()); + if(!playlist) return command_result{error::vs_critical}; + + playlist->properties()[property::PLAYLIST_TYPE] = music::Playlist::Type::GENERAL; + + { + auto max_songs = this->calculate_permission(permission::i_max_playlist_size, 0); + if(max_songs.has_value && max_songs.value >= 0) + playlist->properties()[property::PLAYLIST_MAX_SONGS] = max_songs.value; + } + + 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); + + 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); + + 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); + + 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); + + Command notify(this->notify_response_command("notifyplaylistcreated")); + notify["playlist_id"] = playlist->playlist_id(); + this->sendCommand(notify); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistDelete(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}; + + 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)); + + string error; + if(!ref_server->musicManager->delete_playlist(playlist->playlist_id(), error)) { + logError(this->getServerId(), "Failed to delete playlist with id {}. Error: {}", playlist->playlist_id(), error); + return command_result{error::vs_critical}; + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistInfo(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}; + + 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)); + + + Command notify(this->notify_response_command("notifyplaylistinfo")); + for(const auto& property : playlist->properties().list_properties(property::FLAG_PLAYLIST_VARIABLE)) { + notify[property.type().name] = property.value(); + } + this->sendCommand(notify); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistEdit(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}; + + 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)); + + deque, string>> properties; + + for(const auto& key : cmd[0].keys()) { + if(key == "playlist_id") continue; + if(key == "return_code") continue; + + auto property = property::info(key); + if(*property == property::PLAYLIST_UNDEFINED) { + logError(this->getServerId(), R"([{}] Tried to edit a not existing playlist property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); + continue; + } + + if((property->flags & property::FLAG_USER_EDITABLE) == 0) { + logError(this->getServerId(), "[{}] Tried to change a playlist property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); + continue; + } + + if(!property->validate_input(cmd[key].as())) { + logError(this->getServerId(), "[{}] Tried to change a playlist property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); + continue; + } + + if(*property == property::PLAYLIST_CURRENT_SONG_ID) { + auto song_id = cmd[key].as(); + auto song = song_id > 0 ? playlist->find_song(song_id) : nullptr; + if(song_id != 0 && !song) + return command_result{error::playlist_invalid_song_id}; + } else if(*property == property::PLAYLIST_MAX_SONGS) { + auto value = cmd[key].as(); + auto max_value = this->calculate_permission(permission::i_max_playlist_size, this->getChannelId()); + if(max_value.has_value && !permission::v2::permission_granted(value, max_value)) + return command_result{permission::i_max_playlist_size}; + } + + properties.emplace_back(property, key); + } + for(const auto& property : properties) { + if(*property.first == property::PLAYLIST_CURRENT_SONG_ID) { + playlist->set_current_song(cmd[property.second]); + continue; + } + + playlist->properties()[property.first] = cmd[property.second].string(); + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistPermList(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}; + + if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_playlist_permission_list, 1); + + auto permissions = playlist->permissions()->listPermissions(PERM_FLAG_PUBLIC); + if(permissions.empty()) + return command_result{error::vs_critical}; + + Command result(this->notify_response_command("notifyplaylistpermlist")); + int index = 0; + result["playlist_id"] = playlist->playlist_id(); + for (const auto &elm : permissions) { + if(elm->hasValue()) { + result[index]["permid"] = elm->type->type; + + result[index]["permvalue"] = elm->value; + result[index]["permnegated"] = elm->flag_negate; + result[index]["permskip"] = elm->flag_skip; + index++; + } + if(elm->hasGrant()) { + result[index]["permid"] = (uint16_t) (elm->type->type | PERM_ID_GRANT); + result[index]["permvalue"] = elm->granted; + result[index]["permnegated"] = 0; + result[index]["permskip"] = 0; + index++; + } + } + this->sendCommand(result); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistAddPerm(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}; + + 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)); + + 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->permissions()->setPermissionGranted(permType, cmd[index]["permvalue"], nullptr); + } else { + + playlist->permissions()->setPermission(permType, cmd[index]["permvalue"], nullptr, cmd[index]["permskip"], cmd[index]["permnegated"]); + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistDelPerm(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}; + + 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)); + + 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->permissions()->setPermissionGranted(permType, permNotGranted, nullptr); + } else { + playlist->permissions()->deletePermission(permType, nullptr); + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistSongList(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}; + + 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)); + + auto songs = playlist->list_songs(); + if(songs.empty()) + return command_result{error::database_empty_result}; + + Command notify(this->notify_response_command("notifyplaylistsonglist")); + notify["playlist_id"] = playlist->playlist_id(); + + size_t index = 0; + for(const auto& song : songs) { + notify[index]["song_id"] = song->id; + notify[index]["song_invoker"] = song->invoker; + notify[index]["song_previous_song_id"] = song->previous_song_id; + notify[index]["song_url"] = song->url; + notify[index]["song_url_loader"] = song->url_loader; + notify[index]["song_loaded"] = song->loaded; + notify[index]["song_metadata"] = song->metadata; + index++; + } + this->sendCommand(notify); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistSongAdd(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}; + + 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(!cmd[0].has("invoker")) + cmd["invoker"] = ""; + if(!cmd[0].has("previous")) { + auto songs = playlist->list_songs(); + if(songs.empty()) + cmd["previous"] = "0"; + else + cmd["previous"] = songs.back()->id; + } + + + auto song = playlist->add_song(_this.lock(), cmd["url"], cmd["invoker"], cmd["previous"]); + if(!song) return command_result{error::vs_critical}; + + Command notify(this->notify_response_command("notifyplaylistsongadd")); + notify["song_id"] = song->id; + this->sendCommand(notify); + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistSongReorder(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}; + + 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)); + + SongId song_id = cmd["song_id"]; + SongId previous_id = cmd["song_previous_song_id"]; + + auto song = playlist->find_song(song_id); + if(!song) return command_result{error::playlist_invalid_song_id}; + + if(!playlist->reorder_song(song_id, previous_id)) + return command_result{error::vs_critical}; + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandPlaylistSongRemove(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}; + + 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)); + + SongId song_id = cmd["song_id"]; + + auto song = playlist->find_song(song_id); + if(!song) return command_result{error::playlist_invalid_song_id}; + + if(!playlist->delete_song(song_id)) + return command_result{error::vs_critical}; + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandMusicBotQueueList(Command& cmd) { + return command_result{error::not_implemented}; //FIXME + + /* + 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); + + + 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" : ""); + + apply_song(notify, history[index - 1], command_index); + notify[command_index]["queue_index"] = -index; + + if(!bulked) + this->sendCommand(notify); + else + command_index++; + } + } + + { + if(!bulked) + notify = Command(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : ""); + + auto song = bot->queue()->currentSong(); + apply_song(notify, song, command_index); + notify[command_index]["queue_index"] = 0; + + if(!bulked) + this->sendCommand(notify); + else if(song) + 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 }; + } + + if(this->getExternalType() == CLIENT_VOICE) { + Command notify("notifymusicqueuefinish"); + notify["bot_id"] = bot->getClientDatabaseId(); + this->sendCommand(notify); + } + + return command_result{error::ok}; + */ +} + +command_result ConnectedClient::handleCommandMusicBotQueueAdd(Command& cmd) { + return command_result{error::not_implemented}; //FIXME + + /* + 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_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); + + MusicClient::loader_t loader; + auto& type = cmd[0]["type"]; + if((type.castable() && type.as() == 0) || type.as() == "yt") { + loader = bot->ytLoader(this->getServer()); + } else if((type.castable() && type.as() == 1) || type.as() == "ffmpeg") { + loader = bot->ffmpegLoader(this->getServer()); + } else if((type.castable() && type.as() == 2) || type.as() == "channel") { + loader = bot->channelLoader(this->getServer()); + } else if((type.castable() && type.as() == -1) || type.as() == "any") { + loader = bot->providerLoader(this->getServer(), ""); + } + if(!loader) return command_result{error::music_invalid_action}; + + auto entry = bot->queue()->insertEntry(cmd["url"], _this.lock(), loader); + if(!entry) return command_result{error::vs_critical}; + + this->server->forEachClient([&](shared_ptr client) { + client->notifyMusicQueueAdd(bot, entry, bot->queue()->queueEntries().size() - 1, _this.lock()); + }); + + return command_result{error::ok}; + */ +} + +command_result ConnectedClient::handleCommandMusicBotQueueRemove(Command& cmd) { + return command_result{error::not_implemented}; //FIXME + + /* + 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_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); + + std::deque> songs; + for(int index = 0; index < cmd.bulkCount(); index++) { + auto entry = bot->queue()->find_queue(cmd["song_id"]); + if(!entry) { + if(cmd.hasParm("skip_error")) continue; + return command_result{error::database_empty_result}; + } + + songs.push_back(move(entry)); + } + + for(const auto& entry : songs) + bot->queue()->deleteEntry(dynamic_pointer_cast(entry)); + this->server->forEachClient([&](shared_ptr client) { + client->notifyMusicQueueRemove(bot, songs, _this.lock()); + }); + return command_result{error::ok}; + */ +} + +command_result ConnectedClient::handleCommandMusicBotQueueReorder(Command& cmd) { + return command_result{error::not_implemented}; //FIXME + + /* + 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_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); + + auto entry = bot->queue()->find_queue(cmd["song_id"]); + if(!entry) return command_result{error::database_empty_result}; + + auto order = bot->queue()->changeOrder(entry, cmd["index"]); + if(order < 0) return command_result{error::vs_critical}; + this->server->forEachClient([&](shared_ptr client) { + client->notifyMusicQueueOrderChange(bot, entry, order, _this.lock()); + }); + return command_result{error::ok}; + */ +} + +command_result ConnectedClient::handleCommandMusicBotPlaylistAssign(ts::Command &cmd) { + CMD_REF_SERVER(ref_server); + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto bot = ref_server->musicManager->findBotById(cmd["bot_id"]); + if(!bot) return command_result{error::music_invalid_id}; + if(bot->getOwner() != this->getClientDatabaseId()) + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_music_play_power, bot->calculate_permission(permission::i_client_music_needed_play_power, 0)); + + auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); + if(!playlist && cmd["playlist_id"] != 0) return command_result{error::playlist_invalid_id}; + + 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(!ref_server->musicManager->assign_playlist(bot, playlist)) + return command_result{error::vs_critical}; + + return command_result{error::ok}; +} + + + + + + + + + + + + + + + + diff --git a/server/src/client/command_handler/server.cpp b/server/src/client/command_handler/server.cpp new file mode 100644 index 0000000..6f42af6 --- /dev/null +++ b/server/src/client/command_handler/server.cpp @@ -0,0 +1,1167 @@ +// +// Created by wolverindev on 26.01.20. +// + +#include + +#include + +#include +#include +#include +#include +#include "../../build.h" +#include "../ConnectedClient.h" +#include "../InternalClient.h" +#include "../../server/file/FileServer.h" +#include "../../server/VoiceServer.h" +#include "../voice/VoiceClient.h" +#include "PermissionManager.h" +#include "../../InstanceHandler.h" +#include "../../server/QueryServer.h" +#include "../file/FileClient.h" +#include "../music/MusicClient.h" +#include "../query/QueryClient.h" +#include "../../weblist/WebListManager.h" +#include "../../manager/ConversationManager.h" +#include "../../manager/PermissionNameMapper.h" +#include +#include +#include + +#include "helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::experimental::filesystem; +using namespace std::chrono; +using namespace std; +using namespace ts; +using namespace ts::server; +using namespace ts::token; + +#define QUERY_PASSWORD_LENGTH 12 + +command_result ConnectedClient::handleCommandServerGetVariables(Command &cmd) { + CMD_REQ_SERVER; + this->notifyServerUpdated(_this.lock()); + return command_result{error::ok}; +} + +#define SERVEREDIT_CHK_PROP_CACHED(name, perm, type)\ +else if(key == name) { \ + ACTION_REQUIRES_GLOBAL_PERMISSION(perm, 1); \ + if(toApplay.count(key) == 0) toApplay[key] = cmd[key].as(); \ + if(!cmd[0][key].castable()) return command_result{error::parameter_invalid}; + +#define SERVEREDIT_CHK_PROP2(name, perm, type_a, type_b)\ +else if(key == name) { \ + ACTION_REQUIRES_GLOBAL_PERMISSION(perm, 1); \ + if(toApplay.count(key) == 0) toApplay[key] = cmd[key].as(); \ + if(!cmd[0][key].castable() && !!cmd[0][key].castable()) return command_result{error::parameter_invalid}; + +command_result ConnectedClient::handleCommandServerEdit(Command &cmd) { + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + if (cmd[0].has("sid") && this->getServerId() != cmd["sid"].as()) + return command_result{error::server_invalid_id}; + + auto target_server = this->server; + if(cmd[0].has("sid")) { + target_server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]); + if(!target_server && cmd["sid"].as() != 0) return command_result{error::server_invalid_id}; + } + + auto cache = make_shared(); + map toApplay; + for (auto &key : cmd[0].keys()) { + if (key == "sid") continue; + SERVEREDIT_CHK_PROP_CACHED("virtualserver_name", permission::b_virtualserver_modify_name, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_name_phonetic", permission::b_virtualserver_modify_name, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_maxclients", permission::b_virtualserver_modify_maxclients, size_t) + if (cmd["virtualserver_maxclients"].as() > 1024) + return command_result{error::accounting_slot_limit_reached, "Do you really need more that 1024 slots?"}; + } SERVEREDIT_CHK_PROP_CACHED("virtualserver_reserved_slots", permission::b_virtualserver_modify_reserved_slots, size_t) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_icon_id", permission::b_virtualserver_modify_icon_id, int64_t) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_channel_temp_delete_delay_default", permission::b_virtualserver_modify_channel_temp_delete_delay_default, ChannelId) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_codec_encryption_mode", permission::b_virtualserver_modify_codec_encryption_mode, int) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_server_group", permission::b_virtualserver_modify_default_servergroup, GroupId) + if(target_server) { + auto group = target_server->groups->findGroup(cmd["virtualserver_default_server_group"].as()); + if (!group || group->target() != GROUPTARGET_SERVER) return command_result{error::parameter_invalid}; + } + } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_channel_group", permission::b_virtualserver_modify_default_channelgroup, GroupId) + if(target_server) { + auto group = target_server->groups->findGroup(cmd["virtualserver_default_channel_group"].as()); + if (!group || group->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid}; + } + } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_channel_admin_group", permission::b_virtualserver_modify_default_channeladmingroup, GroupId) + if(target_server) { + auto group = target_server->groups->findGroup(cmd["virtualserver_default_channel_admin_group"].as()); + if (!group || group->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid}; + } + } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_music_group", permission::b_virtualserver_modify_default_musicgroup, GroupId) + if(target_server) { + auto group = target_server->groups->findGroup(cmd["virtualserver_default_music_group"].as()); + if (!group || group->target() != GROUPTARGET_SERVER) return command_result{error::parameter_invalid}; + } + } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_priority_speaker_dimm_modificator", permission::b_virtualserver_modify_priority_speaker_dimm_modificator, float) } + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_port", permission::b_virtualserver_modify_port, uint16_t) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_host", permission::b_virtualserver_modify_host, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_web_host", permission::b_virtualserver_modify_port, uint16_t) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_web_port", permission::b_virtualserver_modify_host, string) } + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbanner_url", permission::b_virtualserver_modify_hostbanner, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbanner_gfx_url", permission::b_virtualserver_modify_hostbanner, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbanner_gfx_interval", permission::b_virtualserver_modify_hostbanner, int) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbanner_mode", permission::b_virtualserver_modify_hostbanner, int) } + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbutton_tooltip", permission::b_virtualserver_modify_hostbutton, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbutton_url", permission::b_virtualserver_modify_hostbutton, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbutton_gfx_url", permission::b_virtualserver_modify_hostbutton, string) } + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostmessage", permission::b_virtualserver_modify_hostmessage, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostmessage_mode", permission::b_virtualserver_modify_hostmessage, int) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_welcomemessage", permission::b_virtualserver_modify_welcomemessage, string) } + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_weblist_enabled", permission::b_virtualserver_modify_weblist, bool) + if (target_server && target_server->running()) { + if (cmd["virtualserver_weblist_enabled"].as()) + serverInstance->getWebList()->enable_report(target_server); + else + serverInstance->getWebList()->disable_report(target_server); + debugMessage(target_server->getServerId(), "Changed weblist state to -> {}", + cmd["virtualserver_weblist_enabled"].as() ? "activated" : "disabled"); + } + } SERVEREDIT_CHK_PROP_CACHED("virtualserver_needed_identity_security_level", permission::b_virtualserver_modify_needed_identity_security_level, int) } + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_antiflood_points_tick_reduce", permission::b_virtualserver_modify_antiflood, uint64_t) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_antiflood_points_needed_command_block", permission::b_virtualserver_modify_antiflood, uint64_t) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_antiflood_points_needed_ip_block", permission::b_virtualserver_modify_antiflood, uint64_t) } + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_complain_autoban_count", permission::b_virtualserver_modify_complain, uint64_t) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_complain_autoban_time", permission::b_virtualserver_modify_complain, uint64_t) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_complain_remove_time", permission::b_virtualserver_modify_complain, uint64_t) } + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_autostart", permission::b_virtualserver_modify_autostart, bool) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_min_clients_in_channel_before_forced_silence", permission::b_virtualserver_modify_channel_forced_silence, int) } + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_client", permission::b_virtualserver_modify_log_settings, bool) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_query", permission::b_virtualserver_modify_log_settings, bool) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_channel", permission::b_virtualserver_modify_log_settings, bool) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_permissions", permission::b_virtualserver_modify_log_settings, bool) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_server", permission::b_virtualserver_modify_log_settings, bool) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_filetransfer", permission::b_virtualserver_modify_log_settings, bool) } + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_min_client_version", permission::b_virtualserver_modify_min_client_version, uint64_t) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_min_android_version", permission::b_virtualserver_modify_min_client_version, uint64_t) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_min_ios_version", permission::b_virtualserver_modify_min_client_version, uint64_t) } + + + SERVEREDIT_CHK_PROP_CACHED("virtualserver_music_bot_limit", permission::b_virtualserver_modify_music_bot_limit, int) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_flag_password", permission::b_virtualserver_modify_password, bool) + if (cmd["virtualserver_flag_password"].as() && !cmd[0].has("virtualserver_password")) + return command_result{error::parameter_invalid}; + } SERVEREDIT_CHK_PROP_CACHED("virtualserver_password", permission::b_virtualserver_modify_password, string) + if(cmd["virtualserver_password"].string().empty()) { + toApplay["virtualserver_flag_password"] = "0"; + } else { + toApplay["virtualserver_flag_password"] = "1"; + if(this->getType() == CLIENT_QUERY) + toApplay["virtualserver_password"] = base64::encode(digest::sha1(cmd["virtualserver_password"].string())); + } + } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_client_description", permission::b_virtualserver_modify_default_messages, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_channel_description", permission::b_virtualserver_modify_default_messages, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_channel_topic", permission::b_virtualserver_modify_default_messages, string) } + SERVEREDIT_CHK_PROP_CACHED("virtualserver_country_code", permission::b_virtualserver_modify_country_code, string) } + SERVEREDIT_CHK_PROP2("virtualserver_max_download_total_bandwidth", permission::b_virtualserver_modify_ft_settings, uint64_t, int64_t) + if(cmd["virtualserver_max_download_total_bandwidth"].string().find('-') == string::npos) + toApplay["virtualserver_max_download_total_bandwidth"] = to_string((int64_t) cmd["virtualserver_max_download_total_bandwidth"].as()); + else + toApplay["virtualserver_max_download_total_bandwidth"] = to_string(cmd["virtualserver_max_download_total_bandwidth"].as()); + } + SERVEREDIT_CHK_PROP2("virtualserver_max_upload_total_bandwidth", permission::b_virtualserver_modify_ft_settings, uint64_t, int64_t) + if(cmd["virtualserver_max_upload_total_bandwidth"].string().find('-') == string::npos) + toApplay["virtualserver_max_upload_total_bandwidth"] = to_string((int64_t) cmd["virtualserver_max_upload_total_bandwidth"].as()); + else + toApplay["virtualserver_max_upload_total_bandwidth"] = to_string(cmd["virtualserver_max_upload_total_bandwidth"].as()); + } + SERVEREDIT_CHK_PROP2("virtualserver_download_quota", permission::b_virtualserver_modify_ft_quotas, uint64_t, int64_t) + if(cmd["virtualserver_download_quota"].string().find('-') == string::npos) + toApplay["virtualserver_download_quota"] = to_string((int64_t) cmd["virtualserver_download_quota"].as()); + else + toApplay["virtualserver_download_quota"] = to_string(cmd["virtualserver_download_quota"].as()); + } + SERVEREDIT_CHK_PROP2("virtualserver_upload_quota", permission::b_virtualserver_modify_ft_quotas, uint64_t, int64_t) + if(cmd["virtualserver_upload_quota"].string().find('-') == string::npos) + toApplay["virtualserver_upload_quota"] = to_string((int64_t) cmd["virtualserver_upload_quota"].as()); + else + toApplay["virtualserver_upload_quota"] = to_string(cmd["virtualserver_upload_quota"].as()); + } + else { + logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a not existing server properties. (" + key + ")"); + //return command_result{error::not_implemented}; + } + } + + std::deque keys; + bool group_update = false; + for (const auto& elm : toApplay) { + auto info = property::impl::info(elm.first); + if(*info == property::VIRTUALSERVER_UNDEFINED) { + logCritical(target_server ? target_server->getServerId() : 0, "Missing server property " + elm.first); + continue; + } + + if(!info->validate_input(elm.second)) { + logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + info->name + "')"); + continue; + } + if(target_server) + target_server->properties()[info] = elm.second; + else + (*serverInstance->getDefaultServerProperties())[info] = elm.second; + keys.push_back(elm.first); + + group_update |= *info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || *info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || *info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP; + } + + if(target_server) { + if (group_update) + target_server->forEachClient([&](const shared_ptr& client) { + if(target_server->notifyClientPropertyUpdates(client, target_server->groups->update_server_group_property(client, true, client->getChannel()))) { + if(client->update_cached_permissions()) /* update cached calculated permissions */ + client->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + } + }); + + if (!keys.empty()) + target_server->notifyServerEdited(_this.lock(), keys); + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerRequestConnectionInfo(Command &) { + CMD_REQ_SERVER; + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_connectioninfo_view, 1); + + Command notify("notifyserverconnectioninfo"); + + auto statistics = this->server->getServerStatistics()->statistics(); + auto report = this->server->getServerStatistics()->dataReport(); + + notify[0]["connection_filetransfer_bandwidth_sent"] = report.file_send; + notify[0]["connection_filetransfer_bandwidth_received"] = report.file_recv; + + notify[0]["connection_filetransfer_bytes_sent_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as(); + notify[0]["connection_filetransfer_bytes_received_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as(); + + notify[0]["connection_filetransfer_bytes_sent_month"] = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as(); + notify[0]["connection_filetransfer_bytes_received_month"] = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as(); + + notify[0]["connection_packets_sent_total"] = (*statistics)[property::CONNECTION_PACKETS_SENT_TOTAL].as(); + notify[0]["connection_bytes_sent_total"] = (*statistics)[property::CONNECTION_BYTES_SENT_TOTAL].as(); + notify[0]["connection_packets_received_total"] = (*statistics)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].as(); + notify[0]["connection_bytes_received_total"] = (*statistics)[property::CONNECTION_BYTES_RECEIVED_TOTAL].as(); + + notify[0]["connection_bandwidth_sent_last_second_total"] = report.send_second; + notify[0]["connection_bandwidth_sent_last_minute_total"] = report.send_minute; + notify[0]["connection_bandwidth_received_last_second_total"] = report.recv_second; + notify[0]["connection_bandwidth_received_last_minute_total"] = report.recv_minute; + + notify[0]["connection_connected_time"] = this->server->properties()[property::VIRTUALSERVER_UPTIME].as(); + notify[0]["connection_packetloss_total"] = this->server->averagePacketLoss(); + notify[0]["connection_ping"] = this->server->averagePing(); + + this->sendCommand(notify); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupList(Command &) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_list, 1); + + this->notifyServerGroupList(); + this->command_times.servergrouplist = system_clock::now(); + return command_result{error::ok}; +} + +//servergroupadd name=TestGroup type=1 +command_result ConnectedClient::handleCommandServerGroupAdd(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_create, 1); + + if(cmd["name"].string().empty()) return command_result{error::parameter_invalid}; + + if(cmd["type"].as() == GroupType::GROUP_TYPE_QUERY) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + } else if(cmd["type"].as() == GroupType::GROUP_TYPE_TEMPLATE) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + } else if(!this->server) return command_result{error::parameter_invalid, "you cant create normal groups on the template server!"}; + + auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); + for(const auto& gr : group_manager->availableServerGroups(true)) + if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_SERVER) return command_result{error::parameter_invalid, "Group already exists"}; + auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_SERVER, cmd["type"].as(), cmd["name"].string()); + if (group) { + group->permissions()->set_permission(permission::b_group_is_permanent, {1,0}, permission::v2::set_value, permission::v2::do_nothing); + if(this->server) + this->server->forEachClient([](shared_ptr cl) { + cl->notifyServerGroupList(); + }); + } else return command_result{error::vs_critical}; + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupCopy(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto ref_server = this->server; + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_create, 1); + + auto group_manager = this->server ? this->server->groups : serverInstance->getGroupManager().get(); + + auto source_group_id = cmd["ssgid"].as(); + auto source_group = group_manager->findGroup(source_group_id); + + if(!source_group || source_group->target() != GROUPTARGET_SERVER) + return command_result{error::group_invalid_id}; + + const auto group_type_modificable = [&](GroupType type) { + switch(type) { + case GroupType::GROUP_TYPE_TEMPLATE: + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_templates, 0))) + return permission::b_serverinstance_modify_templates; + break; + case GroupType::GROUP_TYPE_QUERY: + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_querygroup, 0))) + return permission::b_serverinstance_modify_querygroup; + break; + + default: + break; + } + return permission::undefined; + }; + + { + auto result = group_type_modificable(source_group->type()); + if(result != permission::undefined) + return command_result{result}; + } + + auto global_update = false; + if(cmd[0].has("tsgid") && cmd["tsgid"].as() != 0) { + //Copy an existing group + auto target_group = group_manager->findGroup(cmd["tsgid"]); + if(!target_group || target_group->target() != GROUPTARGET_SERVER) + return command_result{error::group_invalid_id}; + + { + auto result = group_type_modificable(target_group->type()); + if(result != permission::undefined) + return command_result{result}; + } + + if(!target_group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission(permission::i_server_group_modify_power, 0), true)) + return command_result{permission::i_server_group_modify_power}; + + if(!group_manager->copyGroupPermissions(source_group, target_group)) + return command_result{error::vs_critical, "failed to copy group permissions"}; + + global_update = !this->server || !group_manager->isLocalGroup(target_group); + } else { + //Copy a new group + auto target_type = cmd["type"].as(); + + { + auto result = group_type_modificable(target_type); + if(result != permission::undefined) + return command_result{result}; + } + + if(!ref_server && target_type == GroupType::GROUP_TYPE_NORMAL) + return command_result{error::parameter_invalid, "You cant create normal groups on the template server!"}; + + if(!group_manager->findGroup(GroupTarget::GROUPTARGET_SERVER, cmd["name"].string()).empty()) + return command_result{error::group_name_inuse, "You cant create normal groups on the template server!"}; + + auto target_group_id = group_manager->copyGroup(source_group, target_type, cmd["name"], target_type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId()); + if(target_group_id == 0) + return command_result{error::vs_critical, "failed to copy group"}; + + if(this->getType() == ClientType::CLIENT_QUERY) { + Command notify(""); + notify["sgid"] = target_group_id; + this->sendCommand(notify); + } + + global_update = !this->server || !group_manager->isLocalGroup(group_manager->findGroup(target_group_id)); + } + + for(const auto& server : (global_update ? serverInstance->getVoiceServerManager()->serverInstances() : deque>{this->server})) + if(server) + server->forEachClient([](shared_ptr cl) { + cl->notifyServerGroupList(); + }); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupRename(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); + + auto serverGroup = group_manager->findGroup(cmd["sgid"].as()); + if (!serverGroup || serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::group_invalid_id}; + ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, true); + + auto type = serverGroup->type(); + if(type == GroupType::GROUP_TYPE_QUERY) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + } + + group_manager->renameGroup(serverGroup, cmd["name"].string()); + if(this->server) + this->server->forEachClient([](shared_ptr cl) { + cl->notifyServerGroupList(); + }); + + return command_result{error::ok}; +} + +//servergroupdel sgid=2 force=0 +command_result ConnectedClient::handleCommandServerGroupDel(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_delete, 1); + + auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); + auto serverGroup = group_manager->findGroup(cmd["sgid"].as()); + if (!serverGroup || serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::group_invalid_id}; + + if(this->server && this->server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] == serverGroup->groupId()) + return command_result{error::parameter_invalid, "Could not delete default server group!"}; + if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP] == serverGroup->groupId()) + return command_result{error::parameter_invalid, "Could not delete instance default server admin group!"}; + if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP] == serverGroup->groupId()) + return command_result{error::parameter_invalid, "Could not delete instance default server group!"}; + if(serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] == serverGroup->groupId()) + return command_result{error::parameter_invalid, "Could not delete instance default guest server query group!"}; + + auto type = serverGroup->type(); + if(type == GroupType::GROUP_TYPE_QUERY) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + } + + if (!cmd["force"].as()) + if (!group_manager->listGroupMembers(serverGroup, false).empty()) + return command_result{error::database_empty_result, "group not empty!"}; + + if (group_manager->deleteGroup(serverGroup)) { + if(this->server) + this->server->forEachClient([&](shared_ptr cl) { + if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true, cl->getChannel()))) { + if(cl->update_cached_permissions()) /* update cached calculated permissions */ + cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + } + cl->notifyServerGroupList(); + }); + } + + return command_result{error::ok}; +} + +//servergroupclientlist sgid=2 +//notifyservergroupclientlist sgid=6 cldbid=2 client_nickname=WolverinDEV client_unique_identifier=xxjnc14LmvTk+Lyrm8OOeo4tOqw= +command_result ConnectedClient::handleCommandServerGroupClientList(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_client_list, 1); + + auto server = cmd[0].has("sid") && cmd["sid"] == 0 ? nullptr : this->server; + auto groupManager = server ? this->server->groups : serverInstance->getGroupManager().get(); + + auto serverGroup = groupManager->findGroup(cmd["sgid"].as()); + if (!serverGroup || serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::group_invalid_id}; + + Command notify(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyservergroupclientlist" : ""); + notify["sgid"] = cmd["sgid"].as(); + int index = 0; + for (const auto &clientEntry : groupManager->listGroupMembers(serverGroup)) { + notify[index]["cldbid"] = clientEntry->cldbId; + notify[index]["client_nickname"] = clientEntry->displayName; + notify[index]["client_unique_identifier"] = clientEntry->uid; + index++; + } + this->sendCommand(notify); + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto target_server = cmd[0].has("sid") && cmd["sid"] == 0 ? nullptr : this->server; + auto group_manager = target_server ? this->server->groups : serverInstance->getGroupManager().get(); + + auto target_cldbid = cmd["cldbid"].as(); + if (!serverInstance->databaseHelper()->validClientDatabaseId(target_server, cmd["cldbid"])) return command_result{error::client_invalid_id, "invalid cldbid"}; + + auto needed_client_permission = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, target_cldbid, ClientType::CLIENT_TEAMSPEAK, 0); + if(needed_client_permission.has_value) { + if(!permission::v2::permission_granted(needed_client_permission, this->calculate_permission(permission::i_client_permission_modify_power, 0))) + return command_result{permission::i_client_needed_permission_modify_power}; + } + + vector> target_groups; + vector> applied_groups; + target_groups.reserve(cmd.bulkCount()); + + auto continue_on_error = cmd.hasParm("continueonerror"); + { + auto permission_add_power = this->calculate_permission(permission::i_server_group_member_add_power, -1); + auto permission_self_add_power = this->calculate_permission(permission::i_server_group_member_add_power, -1); + + for(auto index = 0; index < cmd.bulkCount(); index++) { + auto group_id = cmd[index]["sgid"]; + if(!group_id.castable() && continue_on_error) + continue; + + auto gid = group_id.as(); + auto group = group_manager->findGroup(gid); + if(!group) { + if(continue_on_error) + continue; + + return command_result{error::group_invalid_id}; + } + + if(!target_server && group->target() != GroupTarget::GROUPTARGET_SERVER && group->type() != GroupType::GROUP_TYPE_QUERY) + return command_result{error::parameter_invalid}; + + if(find(target_groups.begin(), target_groups.end(), group) != target_groups.end()) { + if(continue_on_error) + continue; + + return command_result{error::client_is_already_member_of_group}; + } + + /* permission tests */ + if(!group->permission_granted(permission::i_server_group_needed_member_add_power, permission_add_power, true)) { + if(target_cldbid != this->getClientDatabaseId()) { + if(continue_on_error) + continue; + + return command_result{permission::i_server_group_member_add_power}; + } + + if(!group->permission_granted(permission::i_server_group_needed_member_add_power, permission_self_add_power, true)) { + if(continue_on_error) + continue; + + return command_result{permission::i_server_group_self_add_power}; + } + } + + target_groups.push_back(std::move(group)); + } + } + + applied_groups.reserve(target_groups.size()); + if(target_groups.empty()) return command_result{error::ok}; + else if(target_groups.size() == 1) { + /* speed up thing, don't try to load any cache */ + auto group = target_groups[0]; + if(group_manager->hasServerGroupAssigned(target_cldbid, group)) + return command_result{error::client_is_already_member_of_group}; + + group_manager->addServerGroup(target_cldbid, group); + applied_groups.push_back(group); + } else { + group_manager->enableCache(target_cldbid); + scope_exit_callback cache_disable{[group_manager, target_cldbid]{ + group_manager->disableCache(target_cldbid); + }}; + + for(const auto& group : target_groups) { + if(group_manager->hasServerGroupAssigned(target_cldbid, group)) { + if(continue_on_error) + continue; + return command_result{error::client_is_already_member_of_group}; + } + + group_manager->addServerGroup(target_cldbid, group); + applied_groups.push_back(group); + } + } + + for(const auto& _server : target_server ? std::deque>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) { + for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) { + if (_server->notifyClientPropertyUpdates(targetClient, _server->groups->update_server_group_property(targetClient, true, targetClient->getChannel()))) { + for (const auto &client : _server->getClients()) { + if(client->isClientVisible(targetClient, true) || client == targetClient) + for(const auto& group : applied_groups) + client->notifyServerGroupClientAdd(_this.lock(), targetClient, group); + } + if(targetClient->update_cached_permissions()) /* update cached calculated permissions */ + targetClient->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + targetClient->updateChannelClientProperties(true, true); + } + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto target_server = cmd[0].has("sid") && cmd["sid"] == 0 ? nullptr : this->server; + auto group_manager = target_server ? this->server->groups : serverInstance->getGroupManager().get(); + + auto target_cldbid = cmd["cldbid"].as(); + if (!serverInstance->databaseHelper()->validClientDatabaseId(target_server, cmd["cldbid"])) return command_result{error::client_invalid_id, "invalid cldbid"}; + + auto needed_client_permission = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, target_cldbid, ClientType::CLIENT_TEAMSPEAK, 0); + if(needed_client_permission.has_value) { + if(!permission::v2::permission_granted(needed_client_permission, this->calculate_permission(permission::i_client_permission_modify_power, 0))) + return command_result{permission::i_client_needed_permission_modify_power}; + } + + vector> target_groups; + vector> applied_groups; + target_groups.reserve(cmd.bulkCount()); + + auto continue_on_error = cmd.hasParm("continueonerror"); + { + auto permission_remove_power = this->calculate_permission(permission::i_server_group_member_remove_power, -1); + auto permission_self_remove_power = this->calculate_permission(permission::i_server_group_member_remove_power, -1); + + for(auto index = 0; index < cmd.bulkCount(); index++) { + auto group_id = cmd[index]["sgid"]; + if(!group_id.castable() && continue_on_error) + continue; + + auto gid = group_id.as(); + auto group = group_manager->findGroup(gid); + if(!group) { + if(continue_on_error) + continue; + + return command_result{error::group_invalid_id}; + } + + if(!target_server && group->target() != GroupTarget::GROUPTARGET_SERVER && group->type() != GroupType::GROUP_TYPE_QUERY) + return command_result{error::group_invalid_id}; + + if(find(target_groups.begin(), target_groups.end(), group) != target_groups.end()) { + if(continue_on_error) + continue; + + return command_result{error::parameter_invalid, "duplicate server group for id " + to_string(gid)}; + } + + /* permission tests */ + if(!group->permission_granted(permission::i_server_group_needed_member_remove_power, permission_remove_power, true)) { + if(target_cldbid != this->getClientDatabaseId()) { + if(continue_on_error) + continue; + + return command_result{permission::i_server_group_member_remove_power}; + } + + if(!group->permission_granted(permission::i_server_group_needed_member_remove_power, permission_self_remove_power, true)) { + if(continue_on_error) + continue; + + return command_result{permission::i_server_group_self_remove_power}; + } + } + + target_groups.push_back(std::move(group)); + } + } + + applied_groups.reserve(target_groups.size()); + if(target_groups.empty()) return command_result{error::ok}; + else if(target_groups.size() == 1) { + /* speed up thing, don't try to load any cache */ + auto group = target_groups[0]; + auto assignment = group_manager->get_group_assignment(target_cldbid, group); + if(!assignment) { + return command_result{error::client_is_not_member_of_group}; + } + if(assignment->server != (target_server ? target_server->getServerId() : 0)) { + return command_result{error::group_not_assigned_over_this_server}; + } + + group_manager->removeServerGroup(target_cldbid, group); + applied_groups.push_back(group); + } else { + group_manager->enableCache(target_cldbid); + scope_exit_callback cache_disable{[group_manager, target_cldbid]{ + group_manager->disableCache(target_cldbid); + }}; + + for(const auto& group : target_groups) { + auto assignment = group_manager->get_group_assignment(target_cldbid, group); + if(!assignment) { + if(continue_on_error) + continue; + return command_result{error::client_is_not_member_of_group}; + } + if(assignment->server != (target_server ? target_server->getServerId() : 0)) { + if(continue_on_error) + continue; + return command_result{error::group_not_assigned_over_this_server}; + } + + applied_groups.push_back(group); + group_manager->removeServerGroup(target_cldbid, group); + } + } + + for(const auto& _server : target_server ? std::deque>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) { + for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) { + if (_server->notifyClientPropertyUpdates(targetClient, _server->groups->update_server_group_property(targetClient, true, targetClient->getChannel()))) { + for (const auto &client : _server->getClients()) { + if(client->isClientVisible(targetClient, true) || client == targetClient) + for(const auto& group : applied_groups) + client->notifyServerGroupClientRemove(_this.lock(), targetClient, group); + } + if(targetClient->update_cached_permissions()) /* update cached calculated permissions */ + targetClient->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + targetClient->updateChannelClientProperties(true, true); + } + } + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupPermList(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_servergroup_permission_list, 1); + + auto serverGroup = (this->server ? this->server->groups : serverInstance->getGroupManager().get())->findGroup(cmd["sgid"].as()); + if (!serverGroup) return command_result{error::group_invalid_id}; + + if(this->getType() == ClientType::CLIENT_TEAMSPEAK && this->command_times.last_notify + this->command_times.notify_timeout < system_clock::now()) { + this->sendTSPermEditorWarning(); + } + if (!this->notifyGroupPermList(serverGroup, cmd.hasParm("permsid"))) return command_result{error::database_empty_result}; + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto serverGroup = (this->server ? this->server->groups : serverInstance->getGroupManager().get())->findGroup(cmd["sgid"].as()); + if (!serverGroup) return command_result{error::group_invalid_id}; + if (serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::parameter_invalid}; + ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, 1); + + auto type = serverGroup->type(); + if(type == GroupType::GROUP_TYPE_QUERY) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + } + + + 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"); + bool checkTp = false; + bool sgroupUpdate = false; + + auto permissions = serverGroup->permissions(); + for (int index = 0; index < cmd.bulkCount(); index++) { + PARSE_PERMISSION(cmd); + //permvalue='1' permnegated='0' permskip='0' + + 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) { + permissions->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); + } else { + permissions->set_permission( + permType, + {cmd[index]["permvalue"], 0}, + permission::v2::PermissionUpdateType::set_value, + permission::v2::PermissionUpdateType::do_nothing, + + cmd[index]["permskip"].as() ? 1 : 0, + cmd[index]["permnegated"].as() ? 1 : 0 + ); + sgroupUpdate |= permission_is_group_property(permType); + checkTp |= permission_is_client_property(permType); + } + } + + if(sgroupUpdate) + serverGroup->apply_properties_from_permissions(); + + //TODO may update for every server? + if(this->server) { + auto lock = this->_this.lock(); + auto server = this->server; + threads::Thread([checkTp, sgroupUpdate, serverGroup, lock, server]() { + if(sgroupUpdate) + server->forEachClient([](shared_ptr cl) { + cl->notifyServerGroupList(); + }); + server->forEachClient([serverGroup, checkTp](shared_ptr cl) { + if (cl->serverGroupAssigned(serverGroup)) { + if(cl->update_cached_permissions()) /* update cached calculated permissions */ + cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + if (checkTp) + cl->updateChannelClientProperties(true, true); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } + }); + }).detach(); + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(5); + + auto serverGroup = (this->server ? this->server->groups : serverInstance->getGroupManager().get())->findGroup(cmd["sgid"].as()); + if (!serverGroup) return command_result{error::group_invalid_id}; + if (serverGroup->target() != GROUPTARGET_SERVER) return command_result{error::parameter_invalid}; + ACTION_REQUIRES_GROUP_PERMISSION(serverGroup, permission::i_server_group_needed_modify_power, permission::i_server_group_modify_power, 1); + + auto type = serverGroup->type(); + if(type == GroupType::GROUP_TYPE_QUERY) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_querygroup, 1); + } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1); + } + + 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"); + bool checkTp = false; + auto sgroupUpdate = false; + 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) { + serverGroup->permissions()->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); + } else { + serverGroup->permissions()->set_permission( + permType, + permission::v2::empty_permission_values, + permission::v2::PermissionUpdateType::delete_value, + permission::v2::PermissionUpdateType::do_nothing + ); + sgroupUpdate |= permission_is_group_property(permType); + checkTp |= permission_is_client_property(permType); + } + } + + if(sgroupUpdate) + serverGroup->apply_properties_from_permissions(); + + if(this->server) { + auto lock = this->_this.lock(); + auto server = this->server; + threads::Thread([checkTp, sgroupUpdate, serverGroup, lock, server]() { + if(sgroupUpdate) + server->forEachClient([](shared_ptr cl) { + cl->notifyServerGroupList(); + }); + server->forEachClient([serverGroup, checkTp](shared_ptr cl) { + if (cl->serverGroupAssigned(serverGroup)) { + + if(cl->update_cached_permissions()) /* update cached calculated permissions */ + cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + if (checkTp) + cl->updateChannelClientProperties(true, true); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + } + }); + }).detach(); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto ref_server = this->server; + auto group_manager = ref_server ? this->server->groups : &*serverInstance->getGroupManager(); + + deque> groups; + for(const auto& group : group_manager->availableGroups(false)) { + if(group->updateType() == cmd["sgtype"].as() && group->target() == GROUPTARGET_SERVER) { + if(group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission(permission::i_server_group_modify_power, 0), true)) { + auto type = group->type(); + if(type == GroupType::GROUP_TYPE_QUERY) { + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_querygroup, 0))) + continue; + } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_templates, 0))) + continue; + } + groups.push_back(group);//sgtype + } + } + } + + if(groups.empty()) + return command_result{error::ok}; + + 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"); + bool checkTp = false; + bool sgroupUpdate = false; + + for (int index = 0; index < cmd.bulkCount(); index++) { + PARSE_PERMISSION(cmd); + //permvalue='1' permnegated='0' permskip='0'end + + 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}; + } + + for(const auto& serverGroup : groups) { + if (grant) { + serverGroup->permissions()->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); + } else { + serverGroup->permissions()->set_permission( + permType, + {cmd[index]["permvalue"], 0}, + permission::v2::PermissionUpdateType::set_value, + permission::v2::PermissionUpdateType::do_nothing, + + cmd[index]["permskip"].as() ? 1 : 0, + cmd[index]["permnegated"].as() ? 1 : 0 + ); + } + } + sgroupUpdate |= permission_is_group_property(permType); + checkTp |= permission_is_client_property(permType); + } + + if(sgroupUpdate) + for(auto& group : groups) + group->apply_properties_from_permissions(); + + auto lock = this->_this.lock(); + if(ref_server) { + threads::Thread([checkTp, sgroupUpdate, groups, lock, ref_server]() { + if(sgroupUpdate) + ref_server->forEachClient([](shared_ptr cl) { + cl->notifyServerGroupList(); + }); + ref_server->forEachClient([groups, checkTp](shared_ptr cl) { + for(const auto& serverGroup : groups) { + if (cl->serverGroupAssigned(serverGroup)) { + if(cl->update_cached_permissions()) {/* update cached calculated permissions */ + cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + } + if (checkTp) { + cl->updateChannelClientProperties(true, true); + } + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate if needed */ + break; + } + } + }); + }).detach(); + } + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& cmd) { + CMD_RESET_IDLE; + CMD_CHK_AND_INC_FLOOD_POINTS(25); + + auto ref_server = this->server; + auto group_manager = ref_server ? this->server->groups : &*serverInstance->getGroupManager(); + + deque> groups; + for(const auto& group : group_manager->availableGroups(false)) { + if(group->updateType() == cmd["sgtype"].as() && group->target() == GROUPTARGET_SERVER) { + if(group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission(permission::i_server_group_modify_power, 0), true)) { + auto type = group->type(); + if(type == GroupType::GROUP_TYPE_QUERY) { + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_querygroup, 0))) + continue; + } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { + if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_serverinstance_modify_templates, 0))) + continue; + } + groups.push_back(group);//sgtype + } + } + } + + if(groups.empty()) return command_result{error::ok}; + + 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"); + bool checkTp = false; + auto sgroupUpdate = false; + 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}; + } + + for(const auto& serverGroup : groups) { + if (grant) { + serverGroup->permissions()->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); + } else { + serverGroup->permissions()->set_permission( + permType, + permission::v2::empty_permission_values, + permission::v2::PermissionUpdateType::delete_value, + permission::v2::PermissionUpdateType::do_nothing + ); + sgroupUpdate |= permission_is_group_property(permType); + } + } + checkTp |= permission_is_client_property(permType); + } + + + if(sgroupUpdate) { + for(auto& group : groups) + group->apply_properties_from_permissions(); + } + + if(ref_server) { + auto lock = this->_this.lock(); + threads::Thread([checkTp, sgroupUpdate, groups, lock, ref_server]() { + if(sgroupUpdate) + ref_server->forEachClient([](shared_ptr cl) { + cl->notifyServerGroupList(); + }); + ref_server->forEachClient([groups, checkTp](shared_ptr cl) { + for(const auto& serverGroup : groups) { + if (cl->serverGroupAssigned(serverGroup)) { + if(cl->update_cached_permissions()) /* update cached calculated permissions */ + cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ + if (checkTp) + cl->updateChannelClientProperties(true, true); + cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */ + break; + } + } + }); + }).detach(); + } + + return command_result{error::ok}; +} + +command_result ConnectedClient::handleCommandServerGroupsByClientId(Command &cmd) { + CMD_RESET_IDLE; + + ClientDbId cldbid = cmd["cldbid"]; + if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) return command_result{error::client_invalid_id}; + + Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyservergroupsbyclientid" : ""); + + int index = 0; + if (this->server) { + for (const auto &group : this->server->groups->getAssignedServerGroups(cldbid)) { + result[index]["name"] = group->group->name(); + result[index]["sgid"] = group->group->groupId(); + result[index]["cldbid"] = cldbid; + index++; + } + } else { + for (const auto &group : serverInstance->getGroupManager()->getAssignedServerGroups(cldbid)) { + result[index]["name"] = group->group->name(); + result[index]["sgid"] = group->group->groupId(); + result[index]["cldbid"] = cldbid; + index++; + } + } + + if (index == 0) return command_result{error::database_empty_result}; + this->sendCommand(result); + return command_result{error::ok}; +} + + + + + + + + + + + + + + + + + diff --git a/server/src/client/music/MusicClient.cpp b/server/src/client/music/MusicClient.cpp index 1eff2c2..f9cf16f 100644 --- a/server/src/client/music/MusicClient.cpp +++ b/server/src/client/music/MusicClient.cpp @@ -94,6 +94,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&) { logError(this->getServerId(), "Music manager is forced to disconnect!"); diff --git a/server/src/client/music/MusicClient.h b/server/src/client/music/MusicClient.h index 9c2b49a..94a5d9f 100644 --- a/server/src/client/music/MusicClient.h +++ b/server/src/client/music/MusicClient.h @@ -54,6 +54,8 @@ namespace ts { //Basic TeaSpeak stuff void sendCommand(const ts::Command &command, bool low) override; + 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; void initialize_bot(); diff --git a/server/src/client/query/QueryClient.cpp b/server/src/client/query/QueryClient.cpp index 2c34c04..086765f 100644 --- a/server/src/client/query/QueryClient.cpp +++ b/server/src/client/query/QueryClient.cpp @@ -529,6 +529,11 @@ void QueryClient::sendCommand(const ts::Command &command, bool) { logTrace(LOG_QUERY, "Send command {}", cmd); } +void QueryClient::sendCommand(const ts::command_builder &command, bool) { + writeMessage(command.build() + config::query::newlineCharacter); + logTrace(LOG_QUERY, "Send command {}", command.build()); +} + void QueryClient::tick(const std::chrono::system_clock::time_point &time) { ConnectedClient::tick(time); } diff --git a/server/src/client/query/QueryClient.h b/server/src/client/query/QueryClient.h index ebb1f91..a12da05 100644 --- a/server/src/client/query/QueryClient.h +++ b/server/src/client/query/QueryClient.h @@ -26,6 +26,7 @@ namespace ts { void writeMessage(const std::string&); void sendCommand(const ts::Command &command, bool low = false) override; + 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; diff --git a/server/src/client/query/QueryClientCommands.cpp b/server/src/client/query/QueryClientCommands.cpp index 55748ea..f3778fd 100644 --- a/server/src/client/query/QueryClientCommands.cpp +++ b/server/src/client/query/QueryClientCommands.cpp @@ -11,6 +11,8 @@ #include #include +#include "src/client/command_handler/helpers.h" + using namespace std; using namespace std::chrono; using namespace ts; @@ -219,7 +221,7 @@ command_result QueryClient::handleCommandLogin(Command& cmd) { unique_lock tree_lock(this->server->channel_tree_lock); if(joined_channel) this->server->client_move(this->ref(), joined_channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, tree_lock); - } else if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_virtualserver_select_godmode, 1)) + } else if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_select_godmode, 1))) this->server->assignDefaultChannel(this->ref(), true); else this->update_cached_permissions(); @@ -271,7 +273,7 @@ command_result QueryClient::handleCommandLogout(Command &) { if(joined) { unique_lock server_channel_w_lock(this->server->channel_tree_lock, defer_lock); this->server->client_move(this->ref(), joined, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_w_lock); - } else if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_virtualserver_select_godmode, 1)) { + } else if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_select_godmode, 1))) { this->server->assignDefaultChannel(this->ref(), true); } else { this->update_cached_permissions(); @@ -310,8 +312,8 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) { if(target->getServerId() != this->query_account->bound_server) return command_result{error::server_invalid_id, "You're a server bound query, and the target server isn't your origin."}; } else { - auto allowed = target->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_virtualserver_select, this->getType(), nullptr); - if(allowed != -1 && allowed <= 0) return command_result{permission::b_virtualserver_select}; + auto allowed = target->calculate_permission(permission::b_virtualserver_select, this->getClientDatabaseId(), this->getType(), 0); + if(!permission::v2::permission_granted(1, allowed)) return command_result{permission::b_virtualserver_select}; } } @@ -356,7 +358,7 @@ command_result QueryClient::handleCommandServerSelect(Command &cmd) { this->subscribeChannel(this->server->channelTree->channels(), false, false); } - auto negated_enforce_join = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_virtualserver_select_godmode, 1); + auto negated_enforce_join = permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_select_godmode, 1)); if(!negated_enforce_join) this->server->assignDefaultChannel(this->ref(), true); else @@ -387,8 +389,8 @@ command_result QueryClient::handleCommandLeft(Command&) { CMD_REQ_SERVER; CMD_REQ_CHANNEL; CMD_RESET_IDLE; + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_select_godmode, 1); - PERM_CHECK_CHANNELR(permission::b_virtualserver_select_godmode, 1, nullptr, 1); unique_lock server_channel_lock(this->server->channel_tree_lock); this->server->client_move(this->ref(), nullptr, nullptr, "leaving", ViewReasonId::VREASON_SERVER_LEFT, true, server_channel_lock); return command_result{error::ok}; @@ -396,7 +398,7 @@ command_result QueryClient::handleCommandLeft(Command&) { command_result QueryClient::handleCommandServerInfo(Command &) { CMD_RESET_IDLE; - PERM_CHECKR(permission::b_virtualserver_info_view, 1, true); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_info_view, 1); Command cmd(""); @@ -409,7 +411,7 @@ command_result QueryClient::handleCommandServerInfo(Command &) { cmd["virtualserver_status"] = this->server ? ServerState::string(this->server->state) : "template"; - if(this->server && this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_virtualserver_connectioninfo_view, 1, nullptr, true)) { + if(this->server && permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_connectioninfo_view, 0))) { auto stats = this->server->getServerStatistics()->statistics(); auto report = this->server->serverStatistics->dataReport(); cmd["connection_bandwidth_sent_last_second_total"] = report.send_second; @@ -478,8 +480,8 @@ command_result QueryClient::handleCommandServerInfo(Command &) { } command_result QueryClient::handleCommandChannelList(Command& cmd) { - PERM_CHECKR(permission::b_virtualserver_channel_list, 1, true); CMD_RESET_IDLE; + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_channel_list, 1); Command result(""); @@ -497,7 +499,7 @@ command_result QueryClient::handleCommandChannelList(Command& cmd) { bulk["channel_name"] = channel->name(); bulk["channel_order"] = channel->channelOrder(); bulk["total_clients"] = this->server ? this->server->getClientsByChannel(channel).size() : 0; - /* bulk["channel_needed_subscribe_power"] = channel->permissions()->getPermissionValue(permission::PERMTEST_ORDERED, permission::i_channel_needed_subscribe_power, channel, 0); */ + /* bulk["channel_needed_subscribe_power"] = channel->permissions()->getPermissionValue(permission::i_channel_needed_subscribe_power, channel, 0); */ if(cmd.hasParm("flags")){ bulk["channel_flag_default"] = channel->properties()[property::CHANNEL_FLAG_DEFAULT].as(); @@ -540,7 +542,8 @@ command_result QueryClient::handleCommandChannelList(Command& cmd) { command_result QueryClient::handleCommandServerList(Command& cmd) { CMD_RESET_IDLE; - PERM_CHECKR(permission::b_serverinstance_virtualserver_list, 1, true); + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_virtualserver_list, 1); + Command res(""); int index = 0; @@ -573,7 +576,7 @@ command_result QueryClient::handleCommandServerList(Command& cmd) { command_result QueryClient::handleCommandServerCreate(Command& cmd) { CMD_RESET_IDLE; - PERM_CHECKR(permission::b_virtualserver_create, 1, true); + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_create, 1); if(serverInstance->getVoiceServerManager()->getState() != ServerManager::STARTED) { return command_result{error::vs_critical, "Server manager isn't started yet or not finished starting"}; @@ -650,7 +653,7 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) { command_result QueryClient::handleCommandServerDelete(Command& cmd) { CMD_RESET_IDLE; - PERM_CHECKR(permission::b_virtualserver_delete, 1, true); + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_delete, 1); if(serverInstance->getVoiceServerManager()->getState() != ServerManager::STARTED) return command_result{error::vs_critical, "Server manager isn't started yet or not finished starting"}; @@ -664,7 +667,6 @@ command_result QueryClient::handleCommandServerDelete(Command& cmd) { command_result QueryClient::handleCommandServerStart(Command& cmd) { CMD_RESET_IDLE; - PERM_CHECKR(permission::b_virtualserver_start_any, 1, true); if(serverInstance->getVoiceServerManager()->getState() != ServerManager::STARTED) return command_result{error::vs_critical, "Server manager isn't started yet or not finished starting"}; @@ -672,10 +674,8 @@ command_result QueryClient::handleCommandServerStart(Command& cmd) { if(!server) return command_result{error::server_invalid_id, "invalid bounded server"}; if(server->running()) return command_result{error::server_running, "server already running"}; - if(server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_virtualserver_start, ClientType::CLIENT_QUERY, nullptr) != 1) { - if(!this->permissionGranted(permission::PERMTEST_HIGHEST, permission::b_virtualserver_start_any, 1)) - return command_result{permission::b_virtualserver_start}; - } + if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_virtualserver_start, this->getClientDatabaseId(), ClientType::CLIENT_QUERY, 0))) + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_start_any, 1); string err; if(!server->start(err)) return command_result{error::vs_critical, err}; @@ -684,7 +684,7 @@ command_result QueryClient::handleCommandServerStart(Command& cmd) { command_result QueryClient::handleCommandServerStop(Command& cmd) { CMD_RESET_IDLE; - PERM_CHECKR(permission::b_virtualserver_stop, 1, true); + if(serverInstance->getVoiceServerManager()->getState() != ServerManager::STARTED) return command_result{error::vs_critical, "Server manager isn't started yet or not finished starting"}; @@ -692,10 +692,8 @@ command_result QueryClient::handleCommandServerStop(Command& cmd) { if(!server) return command_result{error::server_invalid_id, "invalid bounded server"}; if(!server->running()) return command_result{error::server_is_not_running, "server is not running"}; - if(server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_virtualserver_stop, ClientType::CLIENT_QUERY, nullptr) != 1) { - if(!this->permissionGranted(permission::PERMTEST_HIGHEST, permission::b_virtualserver_stop_any, 1)) - return command_result{permission::b_virtualserver_stop}; - } + if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_virtualserver_stop, this->getClientDatabaseId(), ClientType::CLIENT_QUERY, 0))) + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_stop_any, 1); server->stop("server stopped"); return command_result{error::ok}; @@ -703,7 +701,7 @@ command_result QueryClient::handleCommandServerStop(Command& cmd) { command_result QueryClient::handleCommandInstanceInfo(Command& cmd) { - PERM_CHECKR(permission::b_serverinstance_info_view, 1, true); + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_info_view, 1); Command res(""); for(const auto& e : serverInstance->properties().list_properties(property::FLAG_INSTANCE_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) @@ -716,7 +714,7 @@ command_result QueryClient::handleCommandInstanceInfo(Command& cmd) { } command_result QueryClient::handleCommandInstanceEdit(Command& cmd) { - PERM_CHECKR(permission::b_serverinstance_modify_settings, 1, true); + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_modify_settings, 1); for(const auto &key : cmd[0].keys()){ auto info = property::impl::info(key); @@ -739,7 +737,7 @@ command_result QueryClient::handleCommandInstanceEdit(Command& cmd) { } command_result QueryClient::handleCommandHostInfo(Command &) { - PERM_CHECKR(permission::b_serverinstance_info_view, 1, true); + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_info_view, 1); Command res(""); res["instance_uptime"] = duration_cast(system_clock::now() - serverInstance->getStartTimestamp()).count(); @@ -774,7 +772,7 @@ command_result QueryClient::handleCommandHostInfo(Command &) { } command_result QueryClient::handleCommandGlobalMessage(Command& cmd) { - PERM_CHECKR(permission::b_serverinstance_textmessage_send, 1, true); + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_textmessage_send, 1); for(const auto &server : serverInstance->getVoiceServerManager()->serverInstances()) if(server->running()) server->broadcastMessage(server->getServerRoot(), cmd["msg"]); @@ -801,7 +799,6 @@ command_result QueryClient::handleCommandBindingList(Command& cmd) { //TODO with mapping! command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) { - PERM_CHECKR(permission::b_virtualserver_snapshot_deploy, 1, true); CMD_RESET_IDLE; auto start = system_clock::now(); @@ -809,8 +806,11 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) { string host = "0.0.0.0"; uint16_t port = 0; if(this->server) { + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1); host = this->server->properties()[property::VIRTUALSERVER_HOST].as(); port = this->server->properties()[property::VIRTUALSERVER_PORT].as(); + } else { + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1); } auto hash = cmd["hash"].string(); @@ -862,7 +862,7 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) { } command_result QueryClient::handleCommandServerSnapshotCreate(Command& cmd) { - PERM_CHECKR(permission::b_virtualserver_snapshot_create, 1, true); + ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_create, 1); CMD_RESET_IDLE; CMD_REQ_SERVER; @@ -887,7 +887,7 @@ command_result QueryClient::handleCommandServerSnapshotCreate(Command& cmd) { extern bool mainThreadActive; command_result QueryClient::handleCommandServerProcessStop(Command& cmd) { - PERM_CHECKR(permission::b_serverinstance_stop, 1, true); + ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_stop, 1); if(cmd[0].has("type")) { if(cmd["type"] == "cancel") { diff --git a/server/src/client/voice/VoiceClient.cpp b/server/src/client/voice/VoiceClient.cpp index 945170c..3673f30 100644 --- a/server/src/client/voice/VoiceClient.cpp +++ b/server/src/client/voice/VoiceClient.cpp @@ -47,8 +47,7 @@ VoiceClient::~VoiceClient() { memtrack::freed(this); } -void VoiceClient::sendCommand0(const ts::Command &command, bool low, bool direct, std::unique_ptr> listener) { - auto cmd = command.build(); +void VoiceClient::sendCommand0(const std::string_view& cmd, bool low, bool direct, std::unique_ptr> listener) { if(cmd.empty()) { logCritical(this->getServerId(), "{} Attempted to send an empty command!", CLIENT_STR_LOG_PREFIX); return; @@ -65,7 +64,7 @@ void VoiceClient::sendCommand0(const ts::Command &command, bool low, bool direct this->connection->sendPacket(packet, false, direct); #ifdef PKT_LOG_CMD - logTrace(this->getServerId(), "{}[Command][Server -> Client] Sending command {}. Command low: {}. Full command: {}", CLIENT_STR_LOG_PREFIX, command.command(), low, cmd); + logTrace(this->getServerId(), "{}[Command][Server -> Client] Sending command {}. Command low: {}. Full command: {}", CLIENT_STR_LOG_PREFIX, cmd.substr(0, cmd.find(' ')), low, cmd); #endif } void VoiceClient::sendAcknowledge(uint16_t packetId, bool low) { @@ -173,7 +172,7 @@ bool VoiceClient::disconnect(ts::ViewReasonId reason_id, const std::string &reas self->closeConnection(); }, system_clock::now() + seconds(5)); - this->sendCommand0(cmd, false, false, std::move(listener)); + this->sendCommand0(cmd.build(), false, false, std::move(listener)); return true; } diff --git a/server/src/client/voice/VoiceClient.h b/server/src/client/voice/VoiceClient.h index 61154ab..a4cb3e1 100644 --- a/server/src/client/voice/VoiceClient.h +++ b/server/src/client/voice/VoiceClient.h @@ -54,9 +54,11 @@ namespace ts { bool disconnect(const std::string&) override; bool disconnect(ViewReasonId /* reason type */, const std::string& /* reason */, const std::shared_ptr& /* invoker */, bool /* notify viewer */); - virtual void sendCommand(const ts::Command &command, bool low = false) { return this->sendCommand0(command, low); } + virtual void sendCommand(const ts::Command &command, bool low = false) { return this->sendCommand0(command.build(), low); } + virtual void sendCommand(const ts::command_builder &command, bool low) { return this->sendCommand0(command.build(), low); } + /* Note: Order is only guaranteed if progressDirectly is on! */ - virtual void sendCommand0(const ts::Command &command, bool low = false, bool progressDirectly = false, std::unique_ptr> listener = nullptr); + virtual void sendCommand0(const std::string_view& /* data */, bool low = false, bool progressDirectly = false, std::unique_ptr> listener = nullptr); virtual void sendAcknowledge(uint16_t packetId, bool low = false); connection::VoiceClientConnection* getConnection(){ return connection; } diff --git a/server/src/client/voice/VoiceClientHandschake.cpp b/server/src/client/voice/VoiceClientHandschake.cpp index 3d14763..692f74e 100644 --- a/server/src/client/voice/VoiceClientHandschake.cpp +++ b/server/src/client/voice/VoiceClientHandschake.cpp @@ -153,7 +153,7 @@ ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) { } else { this->handshake.state = HandshakeState::SUCCEEDED; /* we're doing the verify via TeamSpeak */ } - this->sendCommand0(initivexpand, false, true); //If we setup the encryption now + this->sendCommand0(initivexpand.build(), false, true); //If we setup the encryption now } { diff --git a/server/src/client/web/WebClient.cpp b/server/src/client/web/WebClient.cpp index 06dcb6a..1ce7a61 100644 --- a/server/src/client/web/WebClient.cpp +++ b/server/src/client/web/WebClient.cpp @@ -157,6 +157,19 @@ void WebClient::sendCommand(const ts::Command &command, bool low) { this->sendJson(value); } +void WebClient::sendCommand(const ts::command_builder &command, bool low) { +#if false + Json::Value value{}; + value["type"] = "command2"; + value["payload"] = command.build(); + this->sendJson(value); +#else + auto data = command.build(); + Command parsed_command = Command::parse(pipes::buffer_view{data.data(), data.length()}, true, false); + this->sendCommand(parsed_command, low); +#endif +} + bool WebClient::closeConnection(const std::chrono::system_clock::time_point& timeout) { bool flushing = timeout.time_since_epoch().count() > 0; diff --git a/server/src/client/web/WebClient.h b/server/src/client/web/WebClient.h index 0442082..2caea1f 100644 --- a/server/src/client/web/WebClient.h +++ b/server/src/client/web/WebClient.h @@ -26,6 +26,7 @@ namespace ts { void sendJson(const Json::Value&); void sendCommand(const ts::Command &command, bool low = false) override; + 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; diff --git a/shared b/shared index d42ec40..23a9385 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit d42ec40a58892f4c7ff1da6dd4e69df94e2aa848 +Subproject commit 23a9385afebbbdb2cba88c5ea5f84daf4c85acc4