From a21a28fede8567aef10245fea8468ce5edd291e7 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Tue, 1 Dec 2020 10:07:37 +0100 Subject: [PATCH] Updated the channel edit and create functions --- server/src/channel/ServerChannel.cpp | 1 + server/src/client/ConnectedClient.cpp | 9 +- server/src/client/ConnectedClient.h | 7 + server/src/client/command_handler/channel.cpp | 1525 +++++++++++------ shared | 2 +- 5 files changed, 1000 insertions(+), 544 deletions(-) diff --git a/server/src/channel/ServerChannel.cpp b/server/src/channel/ServerChannel.cpp index 905bfea..ae42645 100644 --- a/server/src/channel/ServerChannel.cpp +++ b/server/src/channel/ServerChannel.cpp @@ -90,6 +90,7 @@ std::shared_ptr ServerChannelTree::createChannel(ChannelId parentI std::shared_ptr channel = BasicChannelTree::createChannel(parentId, orderId, name); if(!channel) return channel; + /* TODO: Speed up (skip the database query) */ auto properties = serverInstance->databaseHelper()->loadChannelProperties(this->server_ref.lock(), channel->channelId()); for(const auto& prop : channel->properties().list_properties()) { if(prop.isModified()) { //Copy the already set properties diff --git a/server/src/client/ConnectedClient.cpp b/server/src/client/ConnectedClient.cpp index 5222937..06c2b36 100644 --- a/server/src/client/ConnectedClient.cpp +++ b/server/src/client/ConnectedClient.cpp @@ -195,12 +195,15 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool if(this->currentChannel) { deque deleted; for(const auto& update_entry : this->channels->update_channel_path(server_ref->channelTree->tree_head(), server_ref->channelTree->find_linked_entry(this->currentChannel->channelId()))) { - if(update_entry.first) + if(update_entry.first) { this->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); - else deleted.push_back(update_entry.second->channelId()); + } else { + deleted.push_back(update_entry.second->channelId()); + } } - if(!deleted.empty()) + if(!deleted.empty()) { this->notifyChannelHide(deleted, false); /* we've locked the tree before */ + } } } } diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index 62c9021..91aace8 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -640,6 +640,13 @@ namespace ts { const std::shared_ptr& /* sender target */ ); + /* Function to execute the channel edit. We're not checking for any permissions */ + ts::command_result execute_channel_edit( + ChannelId& /* channel id */, + const std::map& /* values */, + bool /* is channel create */ + ); + inline std::string notify_response_command(const std::string_view& notify) { if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) return std::string(notify); diff --git a/server/src/client/command_handler/channel.cpp b/server/src/client/command_handler/channel.cpp index d3588b3..aa37989 100644 --- a/server/src/client/command_handler/channel.cpp +++ b/server/src/client/command_handler/channel.cpp @@ -612,83 +612,180 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(25); CMD_CHK_PARM_COUNT(1); - std::shared_ptr parent = nullptr; - std::shared_ptr created_channel = nullptr, old_default_channel; - - 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("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"}; - } - ChannelId parent_channel_id = parent ? parent->entry->channelId() : 0; + auto target_tree = this->server ? this->server->channelTree : &*serverInstance->getChannelTree(); + std::shared_lock channel_tree_read_lock{this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock()}; + ChannelId parent_channel_id = cmd[0].has("cpid") ? cmd["cpid"].as() : 0; #define test_permission(required, permission_type) \ do { \ - if (!permission::v2::permission_granted(required, this->calculate_permission(permission_type, parent_channel_id, false, permission_cache))) \ + if (!permission::v2::permission_granted(required, this->calculate_permission(permission_type, parent_channel_id, false))) \ return command_result{permission_type}; \ } while (0) + std::shared_ptr parent; + std::map new_values{}; + for (const auto &key : cmd[0].keys()) { + if (key == "return_code") { + continue; + } - //TODO: Use for this here the cache as well! - auto permission_cache = make_shared(); - if (parent) test_permission(1, permission::b_channel_create_child); - if (cmd[0].has("channel_order")) test_permission(1, permission::b_channel_create_with_sortorder); - 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 (key == "cpid") { + if(cmd[key].string().empty()) { + continue; + } - if (cmd[0]["channel_flag_permanent"].as()) test_permission(1, permission::b_channel_create_permanent); - else if (cmd[0]["channel_flag_semi_permanent"].as()) - test_permission(1, permission::b_channel_create_semi_permanent); - else - test_permission(1, permission::b_channel_create_temporary); + auto value = cmd["cpid"].as(); + if(value > 0) { + parent = target_tree->findLinkedChannel(value); + if(!parent) { + return ts::command_result{error::channel_invalid_id}; + } + test_permission(1, permission::b_channel_create_child); + } + } - if (!cmd[0]["channel_flag_permanent"].as() && !this->server) return command_result{error::parameter_invalid, "You can only create a permanent channel"}; + const auto &property = property::find(key); + if (property == property::CHANNEL_UNDEFINED) { + logError(this->getServerId(), R"({} Tried to create a channel with a non property channel property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); + continue; + } - if (cmd[0]["channel_flag_default"].as()) test_permission(1, permission::b_channel_create_with_default); - if (cmd[0]["channel_flag_password"].as()) test_permission(1, permission::b_channel_create_with_password); - else if (permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_create_modify_with_force_password, parent_channel_id, false, permission_cache))) + if ((property.flags & property::FLAG_USER_EDITABLE) == 0) { + logError(this->getServerId(), "{} Tried to create a channel with a 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 create a channel with a property with an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); + continue; + } + + if (key == "channel_icon_id") { + /* the creator has the motify permission by default */ + } else if (key == "channel_order") { + if(cmd[key].string().empty()) { + continue; + } + + test_permission(true, permission::b_channel_create_with_sortorder); + } else if (key == "channel_flag_default") { + test_permission(true, permission::b_channel_create_with_default); + } else if (key == "channel_name" ||key == "channel_name_phonetic") { + /* channel create requires a name */ + } else if (key == "channel_topic") { + if(!cmd[key].string().empty()) { + test_permission(true, permission::b_channel_create_with_topic); + } + } else if (key == "channel_description") { + auto value = cmd[key].string(); + if(!value.empty()) { + test_permission(true, permission::b_channel_create_with_description); + + if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_use_bbcode_any, parent_channel_id))) { + auto bbcode_image = bbcode::sloppy::has_image(value); + auto bbcode_url = bbcode::sloppy::has_url(value); + 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, parent_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, parent_channel_id))) { + return command_result{permission::b_client_use_bbcode_url}; + } + } + } + } else if (key == "channel_codec") { + /* TODO: Test for the codec itself! */ + } else if (key == "channel_codec_quality") { + /* TODO: Test for the value itself! */ + } else if (key == "channel_codec_is_unencrypted") { + if (cmd["channel_codec_is_unencrypted"].as()) { + test_permission(true, permission::b_channel_modify_make_codec_encrypted); + } + } else if (key == "channel_needed_talk_power") { + test_permission(true, permission::b_channel_create_with_needed_talk_power); + } else if (key == "channel_maxclients" || key == "channel_flag_maxclients_unlimited") { + test_permission(true, permission::b_channel_create_with_maxclients); + } else if (key == "channel_maxfamilyclients" || key == "channel_flag_maxfamilyclients_unlimited" || key == "channel_flag_maxfamilyclients_inherited") { + test_permission(true, permission::b_channel_create_with_maxfamilyclients); + } else if (key == "channel_flag_permanent" || key == "channel_flag_semi_permanent") { + if (cmd[0].has("channel_flag_permanent") && cmd["channel_flag_permanent"].as()) { + test_permission(true, permission::b_channel_create_permanent); + } else if (cmd[0].has("channel_flag_semi_permanent") && cmd["channel_flag_semi_permanent"].as()) { + test_permission(true, permission::b_channel_create_semi_permanent); + } else { + test_permission(true, permission::b_channel_create_temporary); + } + } else if (key == "channel_delete_delay") { + ACTION_REQUIRES_PERMISSION(permission::i_channel_create_modify_with_temp_delete_delay, cmd["channel_delete_delay"].as(), parent_channel_id); + } else if (key == "channel_password" || key == "channel_flag_password") { + /* no need to test right now */ + } 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, parent_channel_id); + } else { + ACTION_REQUIRES_PERMISSION(permission::i_channel_create_modify_conversation_history_length, 1, parent_channel_id); + } + } else if (key == "channel_flag_conversation_private") { + auto value = cmd["channel_flag_conversation_private"].as(); + if (value) { + ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_private, 1, parent_channel_id); + cmd[property::name(property::CHANNEL_CONVERSATION_MODE)] = CHANNELCONVERSATIONMODE_PRIVATE; + } else { + ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_public, 1, parent_channel_id); + cmd[property::name(property::CHANNEL_CONVERSATION_MODE)] = CHANNELCONVERSATIONMODE_PUBLIC; + } + continue; + } else if (key == "channel_conversation_mode") { + auto value = cmd["channel_conversation_mode"].as(); + switch (value) { + case ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE: + ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_private, 1, parent_channel_id); + break; + case ChannelConversationMode::CHANNELCONVERSATIONMODE_PUBLIC: + ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_public, 1, parent_channel_id); + break; + case ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE: + ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_none, 1, parent_channel_id); + break; + default: + return command_result{error::parameter_invalid, "channel_conversation_mode"}; + } + } else { + logCritical( + this->getServerId(), + "{} Tried to change a editable channel property but we haven't found a permission. Please report this error. (Channel property: {})", + CLIENT_STR_LOG_PREFIX, + key + ); + continue; + } + + new_values.emplace((property::ChannelProperties) property.property_index, cmd[key].string()); + } + + 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].has("channel_flag_password") && cmd[0]["channel_flag_password"].as()) { + test_permission(1, permission::b_channel_create_with_password); + } else if (permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_create_modify_with_force_password, parent_channel_id, false))) { 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")) test_permission(1, permission::b_channel_create_with_description); - if (cmd[0].has("channel_maxclients") || (cmd[0].has("channel_flag_maxclients_unlimited") && !cmd["channel_flag_maxclients_unlimited"].as())) { - test_permission(1, permission::b_channel_create_with_maxclients); - 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")) test_permission(1, permission::b_channel_create_with_maxfamilyclients); - if (cmd[0].has("channel_needed_talk_power")) test_permission(1, permission::b_channel_create_with_needed_talk_power); - if (cmd[0].has("channel_topic")) test_permission(1, permission::b_channel_create_with_topic); - if (cmd[0].has("channel_conversation_history_length")) { - auto value = cmd["channel_conversation_history_length"].as(); - if (value == 0) { - test_permission(1, permission::b_channel_create_modify_conversation_history_unlimited); + auto delete_delay = cmd[0].has("channel_delete_delay") ? cmd["channel_delete_delay"].as() : 0UL; + if (delete_delay == 0) { + if (this->server) { + new_values[property::CHANNEL_DELETE_DELAY] = this->server->properties()[property::VIRTUALSERVER_CHANNEL_TEMP_DELETE_DELAY_DEFAULT].value(); } else { - test_permission(1, permission::i_channel_create_modify_conversation_history_length); + new_values[property::CHANNEL_DELETE_DELAY] = "60"; } + } else { + test_permission(cmd["channel_delete_delay"].as(), permission::i_channel_create_modify_with_temp_delete_delay); } - { - 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 { - test_permission(cmd["channel_delete_delay"].as(), permission::i_channel_create_modify_with_temp_delete_delay); - } - } -#undef test_permission { size_t created_total = 0, created_tmp = 0, created_semi = 0, created_perm = 0; @@ -696,40 +793,41 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { for (const auto &channel : target_tree->channels()) { created_total++; if (channel->properties()[property::CHANNEL_CREATED_BY] == own_cldbid) { - if (channel->properties()[property::CHANNEL_FLAG_PERMANENT].as()) + if (channel->properties()[property::CHANNEL_FLAG_PERMANENT].as()) { created_perm++; - else if (channel->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as()) + } else if (channel->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as()) { created_semi++; - else + } else { created_tmp++; + } } } if (this->server && created_total >= this->server->properties()[property::VIRTUALSERVER_MAX_CHANNELS].as()) return command_result{error::channel_limit_reached}; - auto max_channels = this->calculate_permission(permission::i_client_max_channels, parent_channel_id, false, permission_cache); + auto max_channels = this->calculate_permission(permission::i_client_max_channels, parent_channel_id, false); 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, parent_channel_id, false, permission_cache); + max_channels = this->calculate_permission(permission::i_client_max_permanent_channels, parent_channel_id, false); 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, parent_channel_id, false, permission_cache); + max_channels = this->calculate_permission(permission::i_client_max_semi_channels, parent_channel_id, false); 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, parent_channel_id, false, permission_cache); + max_channels = this->calculate_permission(permission::i_client_max_temporary_channels, parent_channel_id, false); if (max_channels.has_value) { if (!permission::v2::permission_granted(created_tmp + 1, max_channels)) @@ -738,74 +836,57 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { } } - //TODO check voice (opus etc) - //bool enforce_permanent_parent = cmd[0]["channel_flag_default"].as(); //TODO check parents here - {//Checkout the parent(s) - { + { + auto min_channel_deep = this->calculate_permission(permission::i_channel_min_depth, parent_channel_id, false); + auto max_channel_deep = this->calculate_permission(permission::i_channel_max_depth, parent_channel_id, false); - auto min_channel_deep = this->calculate_permission(permission::i_channel_min_depth, parent_channel_id, false, permission_cache); - auto max_channel_deep = this->calculate_permission(permission::i_channel_max_depth, parent_channel_id, 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 || 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"}; } - - 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}; + 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")) { + if (!new_values.contains(property::CHANNEL_ORDER)) { auto last = parent ? parent->child_head : target_tree->tree_head(); - while (last && last->next) + while (last && last->next) { last = last->next; - if (last) - cmd["channel_order"] = last->entry->channelId(); - } else { + } + + if (last) { + new_values[property::CHANNEL_ORDER] = std::to_string(last->entry->channelId()); + } else { + new_values[property::CHANNEL_ORDER] = "0"; + } } - if (cmd["channel_name"].string().length() < 1) { - return command_result{error::channel_name_invalid}; + channel_tree_read_lock.unlock(); + + ChannelId channel_id; + auto result = this->execute_channel_edit(channel_id, new_values, true); + if(result.has_error()) { + return result; } - if (cmd[0].has("channel_name") && count_characters(cmd["channel_name"]) > 40) { - return command_result{error::channel_name_invalid}; - } - - if (count_characters(cmd["channel_name_phonetic"]) > 40) { - return command_result{error::channel_name_invalid}; + channel_tree_read_lock.lock(); + auto created_channel = target_tree->findChannel(channel_id); + if(!created_channel) { + return ts::command_result{error::vs_critical, "failed to find created channel"}; } { - 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, parent_channel_id, false, permission_cache); - auto default_delete_power = this->calculate_permission(permission::i_channel_delete_power, parent_channel_id, false, permission_cache); + auto default_modify_power = this->calculate_permission(permission::i_channel_modify_power, parent_channel_id, false); + auto default_delete_power = this->calculate_permission(permission::i_channel_delete_power, parent_channel_id, false); auto permission_manager = created_channel->permissions(); permission_manager->set_permission( @@ -813,6 +894,7 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { {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}, @@ -840,58 +922,8 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { serverInstance->action_logger()->channel_logger.log_channel_create(this->getServerId(), this->ref(), created_channel->channelId(), log_channel_type); } - for (auto &property_name : cmd[0].keys()) { - if (property_name == "channel_flag_default") continue; - if (property_name == "channel_order") continue; - if (property_name == "channel_name") continue; - if (property_name == "cpid") continue; - if (property_name == "cid") continue; - - const auto &property = property::find(property_name); - if (property == property::CHANNEL_UNDEFINED) { - logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + property_name); - continue; - } - - if (!property.validate_input(cmd[property_name].as())) { - logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[property_name].as() + "', Property: '" + std::string{property.name} + "')"); - continue; - } - auto prop = created_channel->properties()[property]; - auto old_value = prop.value(); - auto new_value = cmd[property_name].as(); - if (old_value == new_value) - continue; - prop = new_value; - serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), created_channel->channelId(), property, old_value, new_value); - } - 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); @@ -903,7 +935,10 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { /* FIXME: Log group assignment */ 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)) + if (created_channel->channelType() == ChannelType::temporary && (this->getType() == ClientType::CLIENT_TEAMSPEAK || this->getType() == ClientType::CLIENT_WEB)) { + channel_tree_read_lock.unlock(); + + std::unique_lock channel_tree_write_lock{this->server->channel_tree_lock}; this->server->client_move( this->ref(), created_channel, @@ -911,7 +946,8 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) { "channel created", ViewReasonId::VREASON_USER_ACTION, true, - tree_channel_lock); + channel_tree_write_lock); + } } return command_result{error::ok}; @@ -950,12 +986,6 @@ command_result ConnectedClient::handleCommandChannelDelete(Command &cmd) { 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); @@ -963,29 +993,18 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { 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); + std::map new_values{}; for (const auto &key : cmd[0].keys()) { - if (key == "cid") + if (key == "cid") { continue; - if (key == "return_code") + } + + if (key == "return_code") { continue; + } const auto &property = property::find(key); if (property == property::CHANNEL_UNDEFINED) { @@ -1003,34 +1022,19 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { continue; } - if (channel->properties()[property].as() == cmd[key].as()) + 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") { - if (count_characters(cmd["channel_name_phonetic"]) > 40) { - return command_result{error::parameter_invalid, "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); @@ -1040,51 +1044,41 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { 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))) + 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))) + } + + 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); - auto value = cmd[key].as(); - if (!(value >= 4 && value <= 5)) { - return command_result{error::parameter_invalid, "channel_codec"}; - } } 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()) + 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); + ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_temp_delete_delay, 1, channel_id); + ACTION_REQUIRES_PERMISSION(permission::i_channel_create_modify_with_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) { @@ -1101,7 +1095,6 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_public, 1, channel_id); cmd[property::name(property::CHANNEL_CONVERSATION_MODE)] = CHANNELCONVERSATIONMODE_PUBLIC; } - keys.push_back(&property::describe(property::CHANNEL_CONVERSATION_MODE)); continue; } else if (key == "channel_conversation_mode") { auto value = cmd["channel_conversation_mode"].as(); @@ -1121,351 +1114,773 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { } 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); + "{} Tried to change a editable channel property but we haven't found a permission. Please report this error. (Channel property: {})", + CLIENT_STR_LOG_PREFIX, + key + ); continue; } - keys.push_back(&property); - } - if (keys.empty()) - return command_result{error::ok}; - 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}; + new_values.emplace((property::ChannelProperties) property.property_index, cmd[key].string()); } - /* 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::describe(property::CHANNEL_PASSWORD)); + channel_tree_read_lock.unlock(); + return this->execute_channel_edit(channel_id, new_values, false); +} + +/* + * 1. Basic value validation. + * 2. Lock the server channel tree in write mode and collect changes + * 2.1. Apply changes + * 3. Test if any other channels have been affected and change them (e. g. default channel) + * 4. notify everyone + */ +ts::command_result ConnectedClient::execute_channel_edit(ChannelId& channel_id, const std::map& values, bool is_channel_create) { + auto server = this->getServer(); + + /* + * Flags which categories are getting changed. + * These flags will be set in 2.1. + */ + bool updating_name{false}; + bool updating_default{false}; + bool updating_password{false}; + bool updating_max_clients{false}; + bool updating_max_family_clients{false}; + bool updating_talk_power{false}; + bool updating_type{false}; + bool updating_conversation{false}; + bool updating_sort_order{false}; + + /* Step 1: Parse all values which are possible and validate them without any context. */ + for(const auto& [ property, value ] : values) { + try { + switch(property) { + case property::CHANNEL_ID: + continue; + + case property::CHANNEL_PID: + case property::CHANNEL_ORDER: { + converter::from_string_view(value); + break; + } + + case property::CHANNEL_NAME: + case property::CHANNEL_NAME_PHONETIC: { + auto length = count_characters(value); + if(length > 40) { + /* max channel name length is 40 */ + return ts::command_result{error::parameter_invalid, std::string{property::describe(property).name}}; + } + + if(length == 0 && property == property::CHANNEL_NAME) { + return ts::command_result{error::parameter_invalid, std::string{property::describe(property).name}}; + } + break; + } + + case property::CHANNEL_TOPIC: + if(value.length() > 255) { + /* max channel name length is 255 bytes, everythign else will disconnect the client */ + return ts::command_result{error::parameter_invalid, std::string{property::describe(property).name}}; + } + break; + + case property::CHANNEL_DESCRIPTION: + if(value.length() > 8192) { + /* max channel name length is 8192 bytes, everythign else will disconnect the client */ + return ts::command_result{error::parameter_invalid, std::string{property::describe(property).name}}; + } + break; + + case property::CHANNEL_PASSWORD: + updating_password = true; + break; + + case property::CHANNEL_CODEC: { + auto codec_value = converter::from_string_view(value); + if (!(codec_value >= 4 && codec_value <= 5)) { + return command_result{error::parameter_invalid, std::string{property::describe(property).name}}; + } + break; + } + + case property::CHANNEL_CODEC_QUALITY: { + auto codec_value = converter::from_string_view(value); + if (codec_value > 10) { + return command_result{error::parameter_invalid, std::string{property::describe(property).name}}; + } + break; + } + + case property::CHANNEL_MAXCLIENTS: + case property::CHANNEL_MAXFAMILYCLIENTS: { + auto max_clients = converter::from_string_view(value); + if(max_clients < -1) { + return command_result{error::parameter_invalid, std::string{property::describe(property).name}}; + } + break; + } + + case property::CHANNEL_FLAG_PERMANENT: + case property::CHANNEL_FLAG_SEMI_PERMANENT: + case property::CHANNEL_CODEC_IS_UNENCRYPTED: + case property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED: + case property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED: + case property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED: { + /* validate value */ + converter::from_string_view(value); + break; + } + + case property::CHANNEL_FLAG_PASSWORD: { + converter::from_string_view(value); + updating_password = true; + break; + } + + case property::CHANNEL_FLAG_DEFAULT: { + if(!converter::from_string_view(value)) { + /* The default channel flag can only be enabled. "Disabling" will be done by enabling the default flag somewhere else. */ + return command_result{error::parameter_invalid, std::string{property::describe(property).name}}; + } + break; + } + + case property::CHANNEL_DELETE_DELAY: { + converter::from_string_view(value); + break; + } + + case property::CHANNEL_ICON_ID: { + converter::from_string_view(value); + break; + } + + case property::CHANNEL_CONVERSATION_HISTORY_LENGTH: { + converter::from_string_view(value); + break; + } + + case property::CHANNEL_CONVERSATION_MODE: { + switch(converter::from_string_view(value)) { + case ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE: + case ChannelConversationMode::CHANNELCONVERSATIONMODE_PUBLIC: + case ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE: + break; + + default: + return command_result{error::parameter_invalid, std::string{property::describe(property).name}}; + } + break; + } + + /* non editable properties */ + case property::CHANNEL_FLAG_ARE_SUBSCRIBED: + case property::CHANNEL_FORCED_SILENCE: + case property::CHANNEL_UNDEFINED: + case property::CHANNEL_CODEC_LATENCY_FACTOR: + case property::CHANNEL_SECURITY_SALT: + case property::CHANNEL_FILEPATH: + case property::CHANNEL_FLAG_PRIVATE: + case property::CHANNEL_LAST_LEFT: + case property::CHANNEL_CREATED_AT: + case property::CHANNEL_CREATED_BY: + case property::CHANNEL_ENDMARKER: + break; + + case property::CHANNEL_NEEDED_TALK_POWER: + converter::from_string_view(value); + break; + + default: + debugMessage(this->getServerId(), "{} Tried to edit an unknown channel property: {}", (int) property); + continue; + } + } catch(std::exception&) { + return command_result{error::parameter_invalid, std::string{property::describe(property).name}}; } - if (!cmd[0].has("channel_flag_password")) { - cmd["channel_flag_password"] = !cmd["channel_password"].string().empty(); - keys.push_back(&property::describe(property::CHANNEL_FLAG_PASSWORD)); + } + + auto channel_tree = server ? server->getChannelTree() : &*serverInstance->getChannelTree(); + std::unique_lock channel_tree_lock{server ? server->channel_tree_lock : serverInstance->getChannelTreeLock()}; + + struct TemporaryCreatedChannel { + ServerChannelTree* channel_tree; + std::shared_ptr channel; + + ~TemporaryCreatedChannel() { + if(!this->channel) { return; } + + channel_tree->deleteChannelRoot(this->channel); + } + } temporary_created_channel{channel_tree, nullptr}; + + std::shared_ptr linked_channel; + std::shared_ptr channel; + if(is_channel_create) { + if(!values.contains(property::CHANNEL_NAME)) { + return command_result{error::parameter_invalid, std::string{property::describe(property::CHANNEL_NAME).name}}; } - 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 */ + auto parent_id = values.contains(property::CHANNEL_PID) ? converter::from_string_view(values.at(property::CHANNEL_PID)) : 0; + auto order_id = values.contains(property::CHANNEL_ORDER) ? converter::from_string_view(values.at(property::CHANNEL_ORDER)) : 0; + auto channel_name = values.at(property::CHANNEL_NAME); - /* we've to "encode" the password */ - if (this->getType() == ClientType ::CLIENT_QUERY) - cmd["channel_password"] = base64::encode(digest::sha1(cmd["channel_password"].string())); + /* checking if the channel name is unique */ + { + std::shared_ptr parent_channel{}; + if(parent_id > 0) { + parent_channel = channel_tree->findChannel(parent_id); + if(!parent_channel) { + return command_result{error::channel_invalid_id}; + } + } + + if(channel_tree->findChannel(channel_name, parent_channel)) { + return ts::command_result{error::channel_name_inuse}; + } + } + + channel = dynamic_pointer_cast(channel_tree->createChannel(parent_id, order_id, channel_name)); + if(!channel) { + return command_result{error::vs_critical, "channel create failed"}; + } + temporary_created_channel.channel = channel; + + linked_channel = channel_tree->findLinkedChannel(channel->channelId()); + if(!linked_channel) { + return command_result{error::vs_critical, "missing linked channel"}; + } + + channel_id = channel->channelId(); + } else { + linked_channel = channel_tree->findLinkedChannel(channel_id); + channel = dynamic_pointer_cast(linked_channel->entry); + } + + if(!channel || channel->deleted) { + /* channel has not been found or deleted */ + return ts::command_result{error::channel_invalid_id}; + } + + /* Step 2: Remove all not changed properties and test the updates */ + auto& channel_properties = channel->properties(); + std::map changed_values{}; + + for(auto& [ key, value ] : values) { + if(channel_properties[key].value() == value) { + continue; + } + + switch(key) { + case property::CHANNEL_NAME: + updating_name = true; + break; + + case property::CHANNEL_NAME_PHONETIC: + /* even though this is a name update, the phonetic name must not be unique */ + case property::CHANNEL_ICON_ID: + case property::CHANNEL_TOPIC: + case property::CHANNEL_DESCRIPTION: + case property::CHANNEL_CODEC: + case property::CHANNEL_CODEC_QUALITY: + case property::CHANNEL_CODEC_IS_UNENCRYPTED: + case property::CHANNEL_DELETE_DELAY: + break; + + case property::CHANNEL_PASSWORD: + case property::CHANNEL_FLAG_PASSWORD: + updating_password = true; + break; + + + case property::CHANNEL_MAXCLIENTS: + case property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED: + updating_max_clients = true; + break; + + + case property::CHANNEL_MAXFAMILYCLIENTS: + case property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED: + case property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED: + updating_max_family_clients = true; + break; + + + case property::CHANNEL_ORDER: + if(is_channel_create) { + return ts::command_result{error::vs_critical, "having channel order update but channel has been created"}; + } + updating_sort_order = true; + break; + + case property::CHANNEL_FLAG_PERMANENT: + case property::CHANNEL_FLAG_SEMI_PERMANENT: + updating_type = true; + break; + + + case property::CHANNEL_FLAG_DEFAULT: + updating_default = true; + break; + + case property::CHANNEL_NEEDED_TALK_POWER: + updating_talk_power = true; + break; + + case property::CHANNEL_CONVERSATION_HISTORY_LENGTH: + case property::CHANNEL_CONVERSATION_MODE: + updating_conversation = true; + break; + + /* non updatable properties */ + case property::CHANNEL_FLAG_ARE_SUBSCRIBED: + case property::CHANNEL_FORCED_SILENCE: + case property::CHANNEL_UNDEFINED: + case property::CHANNEL_ID: + case property::CHANNEL_PID: + case property::CHANNEL_CODEC_LATENCY_FACTOR: + case property::CHANNEL_SECURITY_SALT: + case property::CHANNEL_FILEPATH: + case property::CHANNEL_FLAG_PRIVATE: + case property::CHANNEL_LAST_LEFT: + case property::CHANNEL_CREATED_AT: + case property::CHANNEL_CREATED_BY: + case property::CHANNEL_ENDMARKER: + break; + + default: + logCritical(this->getServerId(), "{} Channel property {} reached context validation context but we don't know how to handle it. Please report this bug!", property::describe(key).name); + continue; + } + + changed_values.emplace(key, std::move(value)); + } + + if(changed_values.empty() && !is_channel_create) { + /* nothing to change */ + return ts::command_result{error::database_no_modifications}; + } + + auto target_channel_property_value = [&](property::ChannelProperties property) { + return changed_values.contains(property) ? std::string{changed_values.at(property)} : channel_properties[property].value(); + }; + + auto reset_client_limitations = [&]{ + /* Updating the max clients */ + if(!converter::from_string_view(target_channel_property_value(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED))) { + updating_max_clients = true; + changed_values[property::CHANNEL_MAXCLIENTS] = "-1"; + changed_values[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED] = "1"; + } + + if(!converter::from_string_view(target_channel_property_value(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED))) { + updating_max_family_clients = true; + changed_values[property::CHANNEL_MAXFAMILYCLIENTS] = "-1"; + changed_values[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED] = "0"; + changed_values[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED] = "1"; + } + }; + + if(updating_name) { + auto new_name = changed_values[property::CHANNEL_NAME]; + if(channel_tree->findChannel(new_name, channel->parent())) { + return ts::command_result{error::channel_name_inuse}; + } + } + + if(updating_password) { + auto password_provided = changed_values.contains(property::CHANNEL_PASSWORD) && !changed_values[property::CHANNEL_PASSWORD].empty(); + if(values.contains(property::CHANNEL_FLAG_PASSWORD)) { + auto has_password = converter::from_string_view(changed_values[property::CHANNEL_FLAG_PASSWORD]); + if(has_password && !password_provided) { + return command_result{error::parameter_missing, std::string{property::describe(property::CHANNEL_PASSWORD).name}}; + } else if(!has_password && password_provided) { + return command_result{error::parameter_invalid, std::string{property::describe(property::CHANNEL_FLAG_PASSWORD).name}}; + } + } else if(password_provided) { + /* we've a password but the remote was too lazy to set the password flag */ + changed_values[property::CHANNEL_FLAG_PASSWORD] = "1"; + } + } + + ChannelType::ChannelType target_channel_type; + { + auto flag_permanent = converter::from_string_view(target_channel_property_value(property::CHANNEL_FLAG_PERMANENT)); + auto flag_semi_permanent = converter::from_string_view(target_channel_property_value(property::CHANNEL_FLAG_SEMI_PERMANENT)); + + if(flag_permanent) { + if(flag_semi_permanent) { + /* we can't be permanent and semi permanent */ + + if(changed_values.contains(property::CHANNEL_FLAG_PERMANENT)) { + return command_result{error::channel_invalid_flags, std::string{property::describe(property::CHANNEL_FLAG_PERMANENT).name}}; + } else { + return command_result{error::channel_invalid_flags, std::string{property::describe(property::CHANNEL_FLAG_SEMI_PERMANENT).name}}; + } + } + + target_channel_type = ChannelType::permanent; + } else if(flag_semi_permanent) { + target_channel_type = ChannelType::semipermanent; } else { - cmd["channel_password"] = ""; /* flag password if false so we set the password to empty */ + target_channel_type = ChannelType::temporary; } } - /* test the default channel update */ - const auto target_will_be_default = cmd[0].has("channel_flag_default") ? cmd["channel_flag_default"].as() : channel->defaultChannel(); - if (target_will_be_default) { - 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::describe(property::CHANNEL_FLAG_PASSWORD)); - } - - if (target_will_be_default) { - cmd["channel_maxclients"] = -1; - cmd["channel_flag_maxclients_unlimited"] = true; - keys.push_back(&property::describe(property::CHANNEL_MAXCLIENTS)); - keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED)); - update_max_clients = true; - - cmd["channel_maxfamilyclients"] = -1; - cmd["channel_flag_maxfamilyclients_inherited"] = false; - keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS)); - keys.push_back(&property::describe(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::describe(property::CHANNEL_MAXCLIENTS)); - keys.push_back(&property::describe(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::describe(property::CHANNEL_MAXFAMILYCLIENTS)); - keys.push_back(&property::describe(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::describe(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::describe(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) { - //auto channel_maxfamilyclients = cmd[0].has("channel_maxfamilyclients") ? std::optional{cmd["channel_maxfamilyclients"].as()} : std::nullopt; - //auto channel_flag_maxfamilyclients_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") ? std::optional{cmd["channel_flag_maxfamilyclients_unlimited"].as()} : std::nullopt; - //auto channel_flag_maxfamilyclients_inherited = cmd[0].has("channel_flag_maxfamilyclients_inherited") ? std::optional{cmd["channel_flag_maxfamilyclients_inherited"].as()} : std::nullopt; - - /* update actual count from flags */ - if (!cmd[0].has("channel_maxfamilyclients")) { - if (cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as()) { - cmd["channel_maxfamilyclients"] = -1; - keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS)); - } else if (cmd[0].has("channel_flag_maxfamilyclients_inherited") && cmd["channel_flag_maxfamilyclients_inherited"].as()) { - cmd["channel_maxfamilyclients"] = -1; - keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS)); + if(updating_max_clients) { + if(changed_values.contains(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED)) { + /* The user explicitly toggled the max clients unlimited flag */ + auto unlimited = converter::from_string_view(changed_values[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED]); + if(unlimited) { + /* + * Change the max clients if not already done by the user. + * We may should test if the user really set them to -1 but nvm. + * + * Since we must come from a limited channel, else we would not have a change, we can ensure that the previous value isn't -1. + * This means that we've definatifly a change. + */ + changed_values[property::CHANNEL_MAXCLIENTS] = "-1"; + } else if(!changed_values.contains(property::CHANNEL_MAXCLIENTS)) { + /* if the user enabled max clients it should also provide a value */ + return ts::command_result{error::parameter_missing, std::string{property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED).name}}; } else { - return command_result{error::parameter_missing, "channel_maxfamilyclients"}; /* since its not unlimited or inherited, channel_maxfamilyclients must be specified */ + auto value = converter::from_string_view(changed_values[property::CHANNEL_MAXCLIENTS]); + if(value < 0) { + return ts::command_result{error::parameter_invalid, std::string{property::describe(property::CHANNEL_MAXCLIENTS).name}}; + } + + /* everything is fine */ + } + } else if(changed_values.contains(property::CHANNEL_MAXCLIENTS)) { + /* the user was too lazy to set the flag max clients unlimited property accordingly */ + auto value = converter::from_string_view(changed_values[property::CHANNEL_MAXCLIENTS]); + if(value >= 0) { + changed_values[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED] = "0"; + } else { + changed_values[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED] = "1"; + } + } else { + assert(false); + logCritical(this->getServerId(), "updating_max_clients has been set without a changed max client channel property."); + } + } + + if(updating_max_family_clients) { + auto unlimited = converter::from_string_view(target_channel_property_value(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED)); + auto inherited = converter::from_string_view(target_channel_property_value(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED)); + + if(changed_values.contains(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED) && unlimited) { + /* The user explicitly enabled the max family clients unlimited flag */ + + /* + * Change the max family clients and the inherited flag if not already done by the user. + * We may should test if the user really set them to -1 but nvm. + */ + if(target_channel_property_value(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED) != "0") { + changed_values[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED] = "0"; + } + if(target_channel_property_value(property::CHANNEL_MAXFAMILYCLIENTS) != "-1") { + changed_values[property::CHANNEL_MAXFAMILYCLIENTS] = "-1"; + } + } else if(changed_values.contains(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED) && inherited) { + /* The user explicitly enabled the max family clients inherited flag */ + + /* + * Change the max family clients and the unlimized flag if not already done by the user. + * We may should test if the user really set them to -1 but nvm. + */ + if(target_channel_property_value(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED) != "0") { + changed_values[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED] = "0"; + } + + if(target_channel_property_value(property::CHANNEL_MAXFAMILYCLIENTS) != "-1") { + changed_values[property::CHANNEL_MAXFAMILYCLIENTS] = "-1"; + } + } else if(changed_values.contains(property::CHANNEL_MAXFAMILYCLIENTS)) { + /* The user explicitly enabled max channel clients */ + auto value = converter::from_string_view(changed_values[property::CHANNEL_MAXFAMILYCLIENTS]); + if(value < 0) { + return ts::command_result{error::parameter_invalid, std::string{property::describe(property::CHANNEL_MAXFAMILYCLIENTS).name}}; + } + + /* everythign is fine */ + } else { + /* If enabling a channel family client limit the user must supply the amount of max clients */ + return ts::command_result{error::parameter_missing, "channel_maxfamilyclients"}; + } + } + + /* + * Validating the target channel type. + * This check required that the max (family) clients have been validated and the flags have been set correctly. + * Attention: Sub channels will not be checked. If their type does not match they will be updated automatically. + */ + switch(target_channel_type) { + case ChannelType::permanent: { + auto parent = channel->parent(); + if(parent && parent->channelType() != ChannelType::permanent) { + return ts::command_result{error::channel_parent_not_permanent}; + } + break; + } + + case ChannelType::semipermanent: { + auto parent = channel->parent(); + if(parent && parent->channelType() > ChannelType::semipermanent) { + return ts::command_result{error::channel_parent_not_permanent}; + } + break; + } + + case ChannelType::temporary: { + /* max (family) client should not be set */ + reset_client_limitations(); + break; + } + + default: + assert(false); + break; + } + + /* + * Validating the default channel change. + * This check required that the channel type and max (family) clients have been validated and the flags have been set correctly. + */ + if(updating_default) { + if(target_channel_type != ChannelType::permanent) { + return command_result{error::channel_default_require_permanent}; + } + + /* validate tree visibility */ + std::shared_ptr current_channel{channel}; + while(current_channel) { + auto permission = current_channel->permissions()->permission_value_flagged(permission::i_channel_needed_view_power); + if(permission.has_value) { + return ts::command_result{error::channel_family_not_visible}; + } + + current_channel = current_channel->parent(); + } + + /* + * Note: all changes of the channel flags happen after the validation. + * This implies all changes made must ensure that the overall channel flags are valid! + */ + + /* Remove the password if there is any */ + if(converter::from_string_view(target_channel_property_value(property::CHANNEL_FLAG_PASSWORD))) { + updating_password = true; + changed_values[property::CHANNEL_FLAG_PASSWORD] = "0"; + changed_values[property::CHANNEL_PASSWORD] = ""; + } + + /* Remove all client restrictions */ + reset_client_limitations(); + } + + ChannelId previous_channel_id{0}; + if(updating_sort_order) { + previous_channel_id = converter::from_string_view(changed_values[property::CHANNEL_ORDER]); + auto previous_channel = channel_tree->findChannel(previous_channel_id); + if(!previous_channel && previous_channel_id != 0) { + return command_result{error::channel_invalid_id}; + } + + if(!channel_tree->move_channel(channel, channel->parent(), previous_channel)) { + return ts::command_result{error::vs_critical, "failed to move channel"}; + } + } + + std::deque> child_channel_type_updates{}; + + /* updating all child channels */ + { + std::deque> children_left{channel}; + + while (!children_left.empty()) { + auto current_channel = children_left.front(); + children_left.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); + + children_left.push_back(child); + child_channel_type_updates.push_back(child); + } } } - //Update the flags from channel_maxfamilyclients if needed - 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[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; - } - - /* final checkup */ - 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 */ + /* reverse the order so the tree is at any state valid */ + std::reverse(child_channel_type_updates.begin(), child_channel_type_updates.end()); } - /* 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}; + std::shared_ptr old_default_channel{}; + if(updating_default) { + old_default_channel = channel_tree->getDefaultChannel(); + if(old_default_channel) { + old_default_channel->properties()[property::CHANNEL_FLAG_DEFAULT] = false; + } } auto self_ref = this->ref(); - shared_ptr old_default_channel; - deque> child_channel_updated; - for (const property::PropertyDescription *key : keys) { - if (*key == property::CHANNEL_ORDER) { - /* TODO: May move that up because if it fails may some other props have already be applied */ - auto old_channel_order = channel->channelOrder(); - if (!channel_tree->change_order(channel, cmd[std::string{key->name}])) - return command_result{error::channel_invalid_order, "Can't change order id"}; + for(const auto& [ key, value ] : changed_values) { + if(key == property::CHANNEL_ICON_ID) { + /* we've to change the permission as well */ - auto channel_parent = channel->parent() ? channel->parent()->channelId() : 0; - serverInstance->action_logger()->channel_logger.log_channel_move(this->getServerId(), this->ref(), channel->channelId(), - channel_parent, channel_parent, - old_channel_order, channel->channelOrder()); + auto icon_id = converter::from_string_view(value); + channel->permissions()->set_permission(permission::i_icon_id, + { (permission::PermissionValue) icon_id, (permission::PermissionValue) icon_id }, + permission::v2::PermissionUpdateType::set_value, + permission::v2::PermissionUpdateType::do_nothing, + false, + false); + } else if(key == property::CHANNEL_NEEDED_TALK_POWER) { + /* we've to change the permission as well */ - 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 - } - }); - } - - /* property has already been updated as well the log has also been written */ - continue; - } 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[std::string{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); - } - } + auto talk_power = converter::from_string_view(value); + channel->permissions()->set_permission(permission::i_client_talk_power, + { talk_power, talk_power }, + permission::v2::PermissionUpdateType::set_value, + permission::v2::PermissionUpdateType::do_nothing, + false, + false); + } else if(key == property::CHANNEL_CONVERSATION_HISTORY_LENGTH) { + if(server) { + auto conversation_manager = server->conversation_manager(); + if (conversation_manager) { + auto conversation = conversation_manager->get(channel->channelId()); + if (conversation) { + conversation->set_history_length(converter::from_string_view(value)); } } } - } 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[std::string{key->name}]); - } - } else if (*key == property::CHANNEL_NEEDED_TALK_POWER) { - channel->permissions()->set_permission(permission::i_client_needed_talk_power, {cmd[key->name].as(), 0}, permission::v2::set_value, permission::v2::do_nothing); } - auto prop = channel->properties()[*key]; - auto old_value = prop.value(); - auto new_value = cmd[std::string{key->name}].string(); - if (old_value == new_value) + auto old_value = channel_properties[key].value(); + channel_properties[key] = value; + serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), self_ref, channel_id, property::describe(key), old_value, value); + } + + std::vector> clients{}; + if(server) { + clients = server->getClients(); + } else { + clients.push_back(this->ref()); + } + + std::shared_ptr linked_parent_channel{}; + std::shared_ptr linked_previous_channel{}; + if(updating_sort_order) { + auto parent = channel->parent(); + + linked_parent_channel = parent ? channel_tree->findLinkedChannel(parent->channelId()) : nullptr; + linked_previous_channel = channel_tree->findLinkedChannel(channel->previousChannelId()); + + assert(!parent || linked_parent_channel); + assert(linked_previous_channel || channel->previousChannelId() == 0); + } + + std::vector child_channel_type_property_updates{ + property::CHANNEL_FLAG_PERMANENT, + property::CHANNEL_FLAG_SEMI_PERMANENT + }; + std::vector default_channel_property_updates{ + property::CHANNEL_FLAG_DEFAULT, + }; + + std::vector changed_properties{}; + changed_properties.reserve(changed_values.size()); + for(const auto& [ key, _ ] : changed_values) { + changed_properties.push_back(key); + } + + for(const auto& client : clients) { + std::shared_lock disconnect_lock{client->finalDisconnectLock, std::try_to_lock}; + if(!disconnect_lock.owns_lock()) { + /* client is already disconnecting */ continue; + } - prop = new_value; - serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), channel->channelId(), *key, old_value, new_value); - } - 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); + if(client->state != ConnectionState::CONNECTED || client->getType() == ClientType::CLIENT_INTERNAL) { + /* these clients have no need to receive any updates */ + continue; + } - auto self_rev = this->ref(); - this->server->forEachClient([&](const shared_ptr &client) { - unique_lock client_channel_lock(client->channel_lock); + std::unique_lock client_tree_lock{client->channel_lock}; + for(const auto& child_channel : child_channel_type_updates) { + client->notifyChannelEdited(child_channel, child_channel_type_property_updates, self_ref, false); + } - for (const auto &channel : child_channel_updated) - client->notifyChannelEdited(channel, {property::CHANNEL_FLAG_PERMANENT, property::CHANNEL_FLAG_SEMI_PERMANENT}, self_rev, false); + if(is_channel_create) { + auto client_view_channel = client->channel_view()->add_channel(linked_channel); + if(client_view_channel) { + client->notifyChannelCreate(channel, client_view_channel->previousChannelId(), self_ref); + } else { + /* channel will not be visible for the target client */ + continue; + } + } else { + if(updating_sort_order) { + auto actions = client->channel_view()->change_order(linked_channel, linked_parent_channel, linked_previous_channel); + std::deque deletions{}; - client->notifyChannelEdited(channel, key_vector, self_rev, false); + for (const auto &action : actions) { + switch (action.first) { + case ClientChannelView::NOTHING: + continue; - 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); - }); + 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->ref()); + break; + + case ClientChannelView::REORDER: + client->notifyChannelEdited(action.second->channel(), {property::CHANNEL_ORDER}, self_ref, false); + break; + } + } + + if (!deletions.empty()) { + /* + * This should not happen since in worst case we're just moving the channel. + * This should not have an effect on the channel visibility itself. + */ + client->notifyChannelHide(deletions, false); + continue; + } + } + + client->notifyChannelEdited(channel, changed_properties, self_ref, false); + } + + if(old_default_channel) { + client->notifyChannelEdited(old_default_channel, default_channel_property_updates, self_ref, false); + } + + if(updating_talk_power) { + client->updateChannelClientProperties(false, true); + } } - 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? - } + /* Channel create was successfull. Release delete struct. */ + temporary_created_channel.channel = nullptr; return command_result{error::ok}; -} +}; command_result ConnectedClient::handleCommandChannelMove(Command &cmd) { CMD_RESET_IDLE; @@ -1554,6 +1969,8 @@ command_result ConnectedClient::handleCommandChannelMove(Command &cmd) { } while ((current_channel = dynamic_pointer_cast(current_channel->parent()))); } + /* FIXME: Test if the new channel family isn't visible by default and if we're currently moving the default channel */ + /* log all the updates */ serverInstance->action_logger()->channel_logger.log_channel_move(this->getServerId(), this->ref(), channel->channelId(), old_parent_channel_id, channel->parent() ? channel->parent()->channelId() : 0, @@ -1676,8 +2093,36 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) { ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true); command::bulk_parser::PermissionBulksParser pparser{cmd}; - if (!pparser.validate(this->ref(), channel->channelId())) + if (!pparser.validate(this->ref(), channel->channelId())) { return pparser.build_command_result(); + } + + /* test if we've the default channel */ + bool family_contains_default_channel{false}; + if(channel->properties()[property::CHANNEL_FLAG_DEFAULT].as()) { + family_contains_default_channel = true; + } else { + for(const auto& child_channel : channel_tree->channels(channel)) { + if(child_channel->properties()[property::CHANNEL_FLAG_DEFAULT].as()) { + family_contains_default_channel = true; + break; + } + } + } + + if(family_contains_default_channel) { + for (const auto &ppermission : pparser.iterate_valid_permissions()) { + if(ppermission.is_grant_permission()) { + continue; + } + + if(!ppermission.has_value()) { + continue; + } + + return ts::command_result{error::channel_default_require_visible}; + } + } auto permission_manager = channel->permissions(); auto updateClients = false, update_join_permissions = false, update_channel_properties = false; diff --git a/shared b/shared index 2cc8a42..5b74992 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit 2cc8a42ce7639efa2cdf6eb30a2a02f099ce1154 +Subproject commit 5b74992beb5f4523485c8a4b6749c7dd6bbf282a