#include #include "../../InstanceHandler.h" #include "../../build.h" #include "../../manager/ActionLogger.h" #include "../../manager/ConversationManager.h" #include "../../manager/PermissionNameMapper.h" #include "../../server/QueryServer.h" #include "../../server/VoiceServer.h" #include "../../groups/GroupManager.h" #include "../ConnectedClient.h" #include "../InternalClient.h" #include "../music/MusicClient.h" #include "../query/QueryClient.h" #include "../voice/VoiceClient.h" #include "PermissionManager.h" #include #include #include #include "./bulk_parsers.h" #include "helpers.h" #include #include #include #include #include #include using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; /* TODO: Log missing permissions? {findError("parameter_invalid"), "could not resolve permission " + (cmd[index].has("permid") ? cmd[index]["permid"].as() : cmd[index]["permsid"].as())}; */ 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_description_view_power, channel->channelId()); if (!channel->permission_granted(permission::i_channel_needed_description_view_power, view_power, false)) return 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) { return this->handleCommandGroupAdd(cmd, GroupTarget::GROUPTARGET_CHANNEL); } command_result ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) { return this->handleCommandGroupCopy(cmd, GroupTarget::GROUPTARGET_CHANNEL); } command_result ConnectedClient::handleCommandChannelGroupRename(Command &cmd) { return this->handleCommandGroupRename(cmd, GroupTarget::GROUPTARGET_CHANNEL); } command_result ConnectedClient::handleCommandChannelGroupDel(Command &cmd) { return this->handleCommandGroupDel(cmd, GroupTarget::GROUPTARGET_CHANNEL); } 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); std::optional notify{}; this->notifyChannelGroupList(notify, this->getType() != ClientType::CLIENT_QUERY); 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; auto target_client_database_id = cmd[0].has("cldbid") ? cmd["cldbid"].as() : 0; auto target_group_id = cmd[0].has("cgid") ? cmd["cgid"].as() : 0; ACTION_REQUIRES_PERMISSION(permission::b_virtualserver_channelgroup_client_list, 1, target_channel_id); auto result = this->server->group_manager()->assignments().channel_group_list(target_group_id, target_channel_id, target_client_database_id); if(result.empty()) { return ts::command_result{error::database_empty_result}; } size_t index{0}; ts::command_builder notify{this->notify_response_command("notifychannelgroupclientlist"), 64, result.size()}; for(const auto& entry : result) { auto bulk = notify.bulk(index++); bulk.put_unchecked("cgid", std::get<0>(entry)); bulk.put_unchecked("cid", std::get<1>(entry)); bulk.put_unchecked("cldbid", std::get<2>(entry)); } this->sendCommand(notify); 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 group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); auto channelGroup = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, cmd["cgid"].as()); if (!channelGroup) { return command_result{error::group_invalid_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_id = cmd["cgid"].as(); std::shared_ptr owning_manager{}; auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); auto group = group_manager->channel_groups()->find_group_ext(owning_manager, groups::GroupCalculateMode::GLOBAL, group_id); if(!group) { return ts::command_result{error::group_invalid_id}; } ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); auto target_server = group_manager->channel_groups() == owning_manager ? this->server : nullptr; return this->executeGroupPermissionEdit(cmd, { group }, target_server, permission::v2::PermissionUpdateType::set_value); } command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); auto group_id = cmd["cgid"].as(); std::shared_ptr owning_manager{}; auto group_manager = this->server ? this->server->group_manager() : serverInstance->group_manager(); auto group = group_manager->channel_groups()->find_group_ext(owning_manager, groups::GroupCalculateMode::GLOBAL, group_id); if(!group) { return ts::command_result{error::group_invalid_id}; } ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true); auto target_server = group_manager->channel_groups() == owning_manager ? this->server : nullptr; return this->executeGroupPermissionEdit(cmd, { group }, target_server, permission::v2::PermissionUpdateType::delete_value); } //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); 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))) \ 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; } if (key == "cpid") { if(cmd[key].string().empty()) { continue; } 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); } new_values[property::CHANNEL_PID] = cmd[key].string(); continue; } 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 ((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") { if(cmd["channel_flag_default"].as()) { 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); new_values[property::CHANNEL_CONVERSATION_MODE] = std::to_string(CHANNELCONVERSATIONMODE_PRIVATE); } else { ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_public, 1, parent_channel_id); new_values[property::CHANNEL_CONVERSATION_MODE] = std::to_string(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()); } /* Fix since the default value of channel_flag_permanent is 1 which should be zero... */ if(!new_values.contains(property::CHANNEL_FLAG_PERMANENT)) { new_values[property::CHANNEL_FLAG_PERMANENT] = std::to_string(false); } 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}; } 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 { new_values[property::CHANNEL_DELETE_DELAY] = "60"; } } else { test_permission(cmd["channel_delete_delay"].as(), permission::i_channel_create_modify_with_temp_delete_delay); } { size_t created_total = 0, created_tmp = 0, created_semi = 0, created_perm = 0; auto own_cldbid = this->getClientDatabaseId(); 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_or(false)) { created_perm++; } else if (channel->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as_or(false)) { created_semi++; } else { created_tmp++; } } } if (this->server && created_total >= this->server->properties()[property::VIRTUALSERVER_MAX_CHANNELS].as_or(0)) return command_result{error::channel_limit_reached}; 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); 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); 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); if (max_channels.has_value) { if (!permission::v2::permission_granted(created_tmp + 1, max_channels)) { return command_result{permission::i_client_max_temporary_channels}; } } } } { 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); 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 (!new_values.contains(property::CHANNEL_ORDER)) { auto last = parent ? parent->child_head : target_tree->tree_head(); while (last && last->next) { last = last->next; } if (last) { new_values[property::CHANNEL_ORDER] = std::to_string(last->entry->channelId()); } else { new_values[property::CHANNEL_ORDER] = "0"; } } 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; } 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"}; } created_channel->properties()[property::CHANNEL_CREATED_BY] = this->getClientDatabaseId(); created_channel->properties()[property::CHANNEL_CREATED_AT] = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); { 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( 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); } /* log channel create */ { log::ChannelType log_channel_type; switch (created_channel->channelType()) { case ChannelType::permanent: log_channel_type = log::ChannelType::PERMANENT; break; case ChannelType::semipermanent: log_channel_type = log::ChannelType::SEMI_PERMANENT; break; case ChannelType::temporary: log_channel_type = log::ChannelType::TEMPORARY; break; } serverInstance->action_logger()->channel_logger.log_channel_create(this->getServerId(), this->ref(), created_channel->channelId(), log_channel_type); } if (this->server) { const auto self_lock = this->ref(); auto admin_group_id = this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].as_or(0); auto admin_group = this->server->group_manager()->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, admin_group_id); if(!admin_group) { logError(this->getServerId(), "Missing servers default channel admin group {}. Using the default channel gropup.", admin_group_id); admin_group = this->server->default_channel_group(); } /* admin_group might still be null since default_channel_group() could return nullptr */ if(admin_group) { this->server->group_manager()->assignments().set_channel_group(this->getClientDatabaseId(), created_channel->channelId(), admin_group->group_id(), false); serverInstance->action_logger()->group_assignment_logger.log_group_assignment_remove( this->getServerId(), this->server->getServerRoot(), log::GroupTarget::CHANNEL, admin_group->group_id(), admin_group->display_name(), this->getClientDatabaseId(), this->getDisplayName() ); } if (created_channel->channelType() == ChannelType::temporary && (this->getType() == ClientType::CLIENT_TEAMSPEAK || this->getType() == ClientType::CLIENT_WEB || this->getType() == ClientType::CLIENT_TEASPEAK)) { 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, nullptr, "channel created", ViewReasonId::VREASON_USER_ACTION, true, channel_tree_write_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, false); } else { auto deleted_channel_ids = channel_tree->deleteChannelRoot(channel); for (const auto &channelId : deleted_channel_ids) { serverInstance->action_logger()->channel_logger.log_channel_delete(0, this->ref(), channelId, channel->channelId() == channelId ? log::ChannelDeleteReason::USER_ACTION : log::ChannelDeleteReason::PARENT_DELETED); } this->notifyChannelDeleted(deleted_channel_ids, this->ref()); } return command_result{error::ok}; } 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); 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") { continue; } if (key == "return_code") { continue; } const auto &property = property::find(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); } else if (key == "channel_flag_default") { if(cmd["channel_flag_default"].as()) { ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_channel_modify_make_default, 1); } } else if (key == "channel_name") { ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_name, 1, channel_id); } 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); } else if (key == "channel_maxclients" || key == "channel_flag_maxclients_unlimited") { ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_maxclients, 1, channel_id); } 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); } 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); } 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); } else { ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_make_temporary, 1, channel_id); } } else if (key == "channel_delete_delay") { 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); } 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") { auto value = cmd["channel_flag_conversation_private"].as(); if (value) { ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_private, 1, channel_id); cmd[property::name(property::CHANNEL_CONVERSATION_MODE)] = CHANNELCONVERSATIONMODE_PRIVATE; } else { ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_public, 1, 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, channel_id); break; case ChannelConversationMode::CHANNELCONVERSATIONMODE_PUBLIC: ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_public, 1, channel_id); break; case ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE: ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_conversation_mode_none, 1, channel_id); break; default: return command_result{error::parameter_invalid, "channel_conversation_mode"}; } } else if(key == "channel_sidebar_mode") { ACTION_REQUIRES_PERMISSION(permission::b_channel_create_modify_sidebar_mode, 1, channel_id); } 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()); } 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_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. */ continue; } 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; } case property::CHANNEL_SIDEBAR_MODE: { switch (converter::from_string_view(value)) { case ChannelSidebarMode::CHANNELSIDEBARMODE_CONVERSATION: case ChannelSidebarMode::CHANNELSIDEBARMODE_DESCRIPTION: case ChannelSidebarMode::CHANNELSIDEBARMODE_FILE_TRANSFER: 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}}; } } 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}}; } 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); /* 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: case property::CHANNEL_SIDEBAR_MODE: 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: 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!", CLIENT_LOG_PREFIX, 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; if(updating_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 { target_channel_type = ChannelType::temporary; } } 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 { 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); } } } /* reverse the order so the tree is at any state valid */ std::reverse(child_channel_type_updates.begin(), child_channel_type_updates.end()); } 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(); for(const auto& [ key, value ] : changed_values) { if(key == property::CHANNEL_ICON_ID) { /* we've to change the permission as well */ 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 */ auto talk_power = converter::from_string_view(value); channel->permissions()->set_permission(permission::i_client_needed_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)); } } } } 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; } if(client->state != ConnectionState::CONNECTED || client->getType() == ClientType::CLIENT_INTERNAL) { /* these clients have no need to receive any updates */ continue; } 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); } 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{}; 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->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->task_update_channel_client_properties.enqueue(); } } /* Channel create was successful. Release delete struct. */ temporary_created_channel.channel = nullptr; 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}; auto old_parent_channel_id = channel->parent() ? channel->parent()->channelId() : 0; auto old_channel_order = channel->channelOrder(); bool change_parent{channel->parent() != parent}; bool change_order{(order ? order->channelId() : 0) != channel->channelOrder()}; if (change_parent) ACTION_REQUIRES_PERMISSION(permission::b_channel_modify_parent, 1, channel_id); if (change_order) 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()))); } /* 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, old_channel_order, channel->channelOrder()); for (const auto &type_update : channel_type_updates) { serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), type_update->channelId(), property::describe(property::CHANNEL_FLAG_PERMANENT), "", type_update->properties()[property::CHANNEL_FLAG_PERMANENT].value()); serverInstance->action_logger()->channel_logger.log_channel_edit(this->getServerId(), this->ref(), type_update->channelId(), property::describe(property::CHANNEL_FLAG_SEMI_PERMANENT), "", type_update->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].value()); } 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->ref()); 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); ts::command::bulk_parser::PermissionBulksParser pparser{cmd, true}; 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_or(false)) { family_contains_default_channel = true; } else { for(const auto& child_channel : channel_tree->channels(channel)) { if(child_channel->properties()[property::CHANNEL_FLAG_DEFAULT].as_or(false)) { 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; } if(ppermission.permission()->type == permission::i_channel_needed_view_power) { 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; auto channelId = channel->channelId(); for (const auto &ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::set_value); ppermission.log_update(serverInstance->action_logger()->permission_logger, this->getServerId(), this->ref(), log::PermissionTarget::CHANNEL, permission::v2::PermissionUpdateType::set_value, channelId, channel->name(), 0, ""); updateClients |= ppermission.is_client_view_property(); update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power; update_channel_properties |= channel->permission_require_property_update(ppermission.permission_type()); } if (update_channel_properties && this->server) this->server->update_channel_from_permissions(channel, this->ref()); if ((updateClients || update_join_permissions) && this->server) { this->server->forEachClient([&](std::shared_ptr cl) { if (updateClients && cl->currentChannel == channel) { cl->task_update_channel_client_properties.enqueue(); } if (update_join_permissions) { cl->join_state_id++; } }); } return pparser.build_command_result(); } 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); ts::command::bulk_parser::PermissionBulksParser pparser{cmd, false}; if (!pparser.validate(this->ref(), channel->channelId())) return pparser.build_command_result(); auto permission_manager = channel->permissions(); auto updateClients = false, update_join_permissions = false, update_channel_properties = false; auto channelId = channel->channelId(); for (const auto &ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::delete_value); ppermission.log_update(serverInstance->action_logger()->permission_logger, this->getServerId(), this->ref(), log::PermissionTarget::CHANNEL, permission::v2::PermissionUpdateType::delete_value, channelId, channel->name(), 0, ""); updateClients |= ppermission.is_client_view_property(); update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power; update_channel_properties |= channel->permission_require_property_update(ppermission.permission_type()); } if (update_channel_properties && this->server) this->server->update_channel_from_permissions(channel, this->ref()); if ((updateClients || update_join_permissions) && this->server) { this->server->forEachClient([&](std::shared_ptr cl) { if (updateClients && cl->currentChannel == channel) cl->task_update_channel_client_properties.enqueue(); if (update_join_permissions) cl->join_state_id++; }); } return pparser.build_command_result(); } 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->getServerId(), 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->getServerId(), 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); } ts::command::bulk_parser::PermissionBulksParser pparser{cmd, false}; if (!pparser.validate(this->ref(), channel->channelId())) return pparser.build_command_result(); bool update_view{false}; for (const auto &ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::delete_value, channel->channelId()); ppermission.log_update(serverInstance->action_logger()->permission_logger, this->getServerId(), this->ref(), log::PermissionTarget::CLIENT_CHANNEL, permission::v2::PermissionUpdateType::delete_value, cldbid, "", channel->channelId(), channel->name()); update_view |= ppermission.is_client_view_property(); } serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); auto onlineClients = this->server->findClientsByCldbId(cldbid); if (!onlineClients.empty()) { for (const auto &elm : onlineClients) { elm->task_update_needed_permissions.enqueue(); if (elm->currentChannel == channel) { elm->task_update_channel_client_properties.enqueue(); } 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 pparser.build_command_result(); } 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->getServerId(), 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); ts::command::bulk_parser::PermissionBulksParser pparser{cmd, true}; if (!pparser.validate(this->ref(), channel->channelId())) return pparser.build_command_result(); bool update_view{false}; for (const auto &ppermission : pparser.iterate_valid_permissions()) { ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::set_value, channel->channelId()); ppermission.log_update(serverInstance->action_logger()->permission_logger, this->getServerId(), this->ref(), log::PermissionTarget::CLIENT_CHANNEL, permission::v2::PermissionUpdateType::set_value, cldbid, "", channel->channelId(), channel->name()); update_view |= ppermission.is_client_view_property(); } serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr); auto onlineClients = this->server->findClientsByCldbId(cldbid); if (!onlineClients.empty()) for (const auto &elm : onlineClients) { elm->task_update_needed_permissions.enqueue(); if (elm->currentChannel == channel) { elm->task_update_channel_client_properties.enqueue(); } 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 pparser.build_command_result(); } 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}; }