#include #include #include #include #include #include "../build.h" #include "ConnectedClient.h" #include "InternalClient.h" #include "../server/file/FileServer.h" #include "../server/VoiceServer.h" #include "voice/VoiceClient.h" #include "PermissionManager.h" #include "../InstanceHandler.h" #include "../server/QueryServer.h" #include "file/FileClient.h" #include "music/MusicClient.h" #include "query/QueryClient.h" #include "../weblist/WebListManager.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::experimental::filesystem; using namespace std::chrono; using namespace std; using namespace ts; using namespace ts::server; using namespace ts::token; extern ts::server::InstanceHandler *serverInstance; #define QUERY_PASSWORD_LENGTH 12 #define PARSE_PERMISSION(cmd) \ permission::PermissionType permType = permission::PermissionType::unknown; \ bool grant = false; \ if (cmd[index].has("permid")) { \ permType = cmd[index]["permid"].as(); \ if ((permType & PERM_ID_GRANT) != 0) { \ grant = true; \ permType &= ~PERM_ID_GRANT; \ } \ } else if (cmd[index].has("permsid")) { \ auto resv = permission::resolvePermissionData(cmd[index]["permsid"].as()); \ permType = resv->type; \ if (resv->grantName() == cmd[index]["permsid"].as()) grant = true; \ } \ if (permission::resolvePermissionData(permType)->type == permission::PermissionType::unknown) { \ if (conOnError) continue; \ return {findError("parameter_invalid"), "could not resolve permission " + (cmd[index].has("permid") ? cmd[index]["permid"].as() : cmd[index]["permsid"].as())}; \ } #define CHANNEL_PERM_TEST_INIT \ auto current_channel = channel == this->getChannel() #define CHANNEL_PERM_TEST(permission_type, required, enfore_required) \ do { \ if(current_channel) \ PERM_CHECK_CHANNELR(permission_type, required, channel, enfore_required); \ else \ PERM_CHECK_CHANNELR(permission_type, required, channel, enfore_required); \ } while(0) #define RESOLVE_CHANNEL_R(command, force) \ auto channel_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get();\ shared_lock channel_tree_read_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock());\ auto channel_id = command.as(); \ auto l_channel = channel_id ? channel_tree->findLinkedChannel(command.as()) : nullptr; \ if (!l_channel && (channel_id != 0 || force)) return {findError("channel_invalid_id"), "Cant resolve channel"}; \ #define RESOLVE_CHANNEL_W(command, force) \ auto channel_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get();\ unique_lock channel_tree_write_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock());\ auto channel_id = command.as(); \ auto l_channel = channel_id ? channel_tree->findLinkedChannel(command.as()) : nullptr; \ if (!l_channel && (channel_id != 0 || force)) return {findError("channel_invalid_id"), "Cant resolve channel"}; \ /* the "newest" channel permission access test */ #define CHANNEL_PERMISSION_TEST(permission_type, permission_needed_type, _channel, permission_required) \ do { \ auto permission_granted = this->calculate_permission_value(permission_type, _channel->channelId()); \ if(!channel->permission_granted(permission_needed_type, permission_granted, permission_required)) \ return CommandResultPermissionError{permission_type}; \ } while(0) /* the "newest" group permission access test */ #define GROUP_PERMISSION_TEST(permission_type, permission_needed_type, _group, permission_required) \ do { \ auto permission_granted = this->calculate_permission_value(permission_type, 0); \ if(!_group->permission_granted(permission_needed_type, permission_granted, permission_required)) \ return CommandResultPermissionError{permission_type}; \ } while(0) inline bool permission_require_granted_value(permission::PermissionType type) { switch (type) { case permission::i_permission_modify_power: case permission::i_channel_group_member_add_power: case permission::i_channel_group_member_remove_power: case permission::i_channel_group_modify_power: case permission::i_server_group_member_add_power: case permission::i_server_group_member_remove_power: case permission::i_server_group_modify_power: case permission::i_displayed_group_member_add_power: case permission::i_displayed_group_member_remove_power: case permission::i_displayed_group_modify_power: case permission::i_channel_permission_modify_power: case permission::i_client_permission_modify_power: case permission::i_client_needed_kick_from_server_power: case permission::i_client_needed_kick_from_channel_power: case permission::i_client_kick_from_channel_power: case permission::i_client_kick_from_server_power: return true; default: return false; } } inline bool permission_is_group_property(permission::PermissionType type) { switch (type) { case permission::i_icon_id: case permission::i_group_show_name_in_tree: case permission::i_group_sort_id: case permission::b_group_is_permanent: case permission::i_displayed_group_needed_modify_power: case permission::i_displayed_group_needed_member_add_power: case permission::i_displayed_group_needed_member_remove_power: return true; default: return false; } } inline bool permission_is_client_property(permission::PermissionType type) { switch (type) { case permission::i_icon_id: case permission::i_client_talk_power: case permission::i_client_max_idletime: case permission::i_group_sort_id: case permission::i_channel_view_power: case permission::b_channel_ignore_view_power: case permission::b_client_is_priority_speaker: return true; default: return false; } } CommandResult ConnectedClient::handleCommand(Command &cmd) { threads::MutexLock l2(this->command_lock); auto command = cmd.command(); if (command == "servergetvariables") return this->handleCommandServerGetVariables(cmd); else if (command == "serverrequestconnectioninfo") return this->handleCommandServerRequestConnectionInfo(cmd); else if (command == "getconnectioninfo") return this->handleCommandGetConnectionInfo(cmd); else if (command == "setconnectioninfo") return this->handleCommandSetConnectionInfo(cmd); else if (command == "clientgetvariables") return this->handleCommandClientGetVariables(cmd); else if (command == "serveredit") return this->handleCommandServerEdit(cmd); else if (command == "clientedit") return this->handleCommandClientEdit(cmd); else if (command == "channelgetdescription") return this->handleCommandChannelGetDescription(cmd); else if (command == "connectioninfoautoupdate") return this->handleCommandConnectionInfoAutoUpdate(cmd); else if (command == "permissionlist") return this->handleCommandPermissionList(cmd); else if (command == "propertylist") return this->handleCommandPropertyList(cmd); //Server group else if (command == "servergrouplist") return this->handleCommandServerGroupList(cmd); else if (command == "servergroupadd") return this->handleCommandServerGroupAdd(cmd); else if (command == "servergroupcopy") return this->handleCommandServerGroupCopy(cmd); else if (command == "servergroupdel") return this->handleCommandServerGroupDel(cmd); else if (command == "servergrouprename") return this->handleCommandServerGroupRename(cmd); else if (command == "servergroupclientlist") return this->handleCommandServerGroupClientList(cmd); else if (command == "servergroupaddclient") return this->handleCommandServerGroupAddClient(cmd); else if (command == "servergroupdelclient") return this->handleCommandServerGroupDelClient(cmd); else if (command == "servergrouppermlist") return this->handleCommandServerGroupPermList(cmd); else if (command == "servergroupaddperm") return this->handleCommandServerGroupAddPerm(cmd); else if (command == "servergroupdelperm") return this->handleCommandServerGroupDelPerm(cmd); else if (command == "setclientchannelgroup") return this->handleCommandSetClientChannelGroup(cmd); //Channel basic actions else if (command == "channelcreate") return this->handleCommandChannelCreate(cmd); else if (command == "channelmove") return this->handleCommandChannelMove(cmd); else if (command == "channeledit") return this->handleCommandChannelEdit(cmd); else if (command == "channeldelete") return this->handleCommandChannelDelete(cmd); //Find a channel and get informations else if (command == "channelfind") return this->handleCommandChannelFind(cmd); else if (command == "channelinfo") return this->handleCommandChannelInfo(cmd); //Channel perm actions else if (command == "channelpermlist") return this->handleCommandChannelPermList(cmd); else if (command == "channeladdperm") return this->handleCommandChannelAddPerm(cmd); else if (command == "channeldelperm") return this->handleCommandChannelDelPerm(cmd); //Channel group actions else if (command == "channelgroupadd") return this->handleCommandChannelGroupAdd(cmd); else if (command == "channelgroupcopy") return this->handleCommandChannelGroupCopy(cmd); else if (command == "channelgrouprename") return this->handleCommandChannelGroupRename(cmd); else if (command == "channelgroupdel") return this->handleCommandChannelGroupDel(cmd); else if (command == "channelgrouplist") return this->handleCommandChannelGroupList(cmd); else if (command == "channelgroupclientlist") return this->handleCommandChannelGroupClientList(cmd); else if (command == "channelgrouppermlist") return this->handleCommandChannelGroupPermList(cmd); else if (command == "channelgroupaddperm") return this->handleCommandChannelGroupAddPerm(cmd); else if (command == "channelgroupdelperm") return this->handleCommandChannelGroupDelPerm(cmd); //Channel sub/unsubscribe else if (command == "channelsubscribe") return this->handleCommandChannelSubscribe(cmd); else if (command == "channelsubscribeall") return this->handleCommandChannelSubscribeAll(cmd); else if (command == "channelunsubscribe") return this->handleCommandChannelUnsubscribe(cmd); else if (command == "channelunsubscribeall") return this->handleCommandChannelUnsubscribeAll(cmd); //manager channel permissions else if (command == "channelclientpermlist") return this->handleCommandChannelClientPermList(cmd); else if (command == "channelclientaddperm") return this->handleCommandChannelClientAddPerm(cmd); else if (command == "channelclientdelperm") return this->handleCommandChannelClientDelPerm(cmd); //Client actions else if (command == "clientupdate") return this->handleCommandClientUpdate(cmd); else if (command == "clientmove") return this->handleCommandClientMove(cmd); else if (command == "clientgetids") return this->handleCommandClientGetIds(cmd); else if (command == "clientkick") return this->handleCommandClientKick(cmd); else if (command == "clientpoke") return this->handleCommandClientPoke(cmd); else if (command == "sendtextmessage") return this->handleCommandSendTextMessage(cmd); else if (command == "clientchatcomposing") return this->handleCommandClientChatComposing(cmd); else if (command == "clientchatclosed") return this->handleCommandClientChatClosed(cmd); else if (command == "clientfind") return this->handleCommandClientFind(cmd); else if (command == "clientinfo") return this->handleCommandClientInfo(cmd); else if (command == "clientaddperm") return this->handleCommandClientAddPerm(cmd); else if (command == "clientdelperm") return this->handleCommandClientDelPerm(cmd); else if (command == "clientpermlist") return this->handleCommandClientPermList(cmd); //File transfare else if (command == "ftgetfilelist") return this->handleCommandFTGetFileList(cmd); else if (command == "ftcreatedir") return this->handleCommandFTCreateDir(cmd); else if (command == "ftdeletefile") return this->handleCommandFTDeleteFile(cmd); else if (command == "ftinitupload") return this->handleCommandFTInitUpload(cmd); else if (command == "ftinitdownload") return this->handleCommandFTInitDownload(cmd); else if (command == "ftgetfileinfo") return this->handleCommandFTGetFileInfo(cmd); //Banlist else if (command == "banlist") return this->handleCommandBanList(cmd); else if (command == "banadd") return this->handleCommandBanAdd(cmd); else if (command == "banedit") return this->handleCommandBanEdit(cmd); else if (command == "banclient") return this->handleCommandBanClient(cmd); else if (command == "bandel") return this->handleCommandBanDel(cmd); else if (command == "bandelall") return this->handleCommandBanDelAll(cmd); else if (command == "bantriggerlist") return this->handleCommandBanTriggerList(cmd); //Tokens else if (command == "tokenlist" || command == "privilegekeylist") return this->handleCommandTokenList(cmd); else if (command == "tokenadd" || command == "privilegekeyadd") return this->handleCommandTokenAdd(cmd); else if (command == "tokenuse" || command == "privilegekeyuse") return this->handleCommandTokenUse(cmd); else if (command == "tokendelete" || command == "privilegekeydelete") return this->handleCommandTokenDelete(cmd); //DB stuff else if (command == "clientdblist") return this->handleCommandClientDbList(cmd); else if (command == "clientdbinfo") return this->handleCommandClientDbInfo(cmd); else if (command == "clientdbedit") return this->handleCommandClientDBEdit(cmd); else if (command == "clientdbfind") return this->handleCommandClientDBFind(cmd); else if (command == "clientdbdelete") return this->handleCommandClientDBDelete(cmd); else if (command == "plugincmd") return this->handleCommandPluginCmd(cmd); else if (command == "clientmute") return this->handleCommandClientMute(cmd); else if (command == "clientunmute") return this->handleCommandClientUnmute(cmd); else if (command == "clientlist") return this->handleCommandClientList(cmd); else if (command == "whoami") return this->handleCommandWhoAmI(cmd); else if (command == "servergroupsbyclientid") return this->handleCommandServerGroupsByClientId(cmd); else if (command == "clientgetdbidfromuid") return this->handleCommandClientGetDBIDfromUID(cmd); else if (command == "clientgetnamefromdbid") return this->handleCommandClientGetNameFromDBID(cmd); else if (command == "clientgetnamefromuid") return this->handleCommandClientGetNameFromUid(cmd); else if (command == "clientgetuidfromclid") return this->handleCommandClientGetUidFromClid(cmd); else if (command == "complainadd") return this->handleCommandComplainAdd(cmd); else if (command == "complainlist") return this->handleCommandComplainList(cmd); else if (command == "complaindel") return this->handleCommandComplainDel(cmd); else if (command == "complaindelall") return this->handleCommandComplainDelAll(cmd); else if (command == "version") return this->handleCommandVersion(cmd); else if (command == "verifyserverpassword") return this->handleCommandVerifyServerPassword(cmd); else if (command == "verifychannelpassword") return this->handleCommandVerifyChannelPassword(cmd); else if (command == "messagelist") return this->handleCommandMessageList(cmd); else if (command == "messageadd") return this->handleCommandMessageAdd(cmd); else if (command == "messageget") return this->handleCommandMessageGet(cmd); else if (command == "messagedel") return this->handleCommandMessageDel(cmd); else if (command == "messageupdateflag") return this->handleCommandMessageUpdateFlag(cmd); else if (command == "permget") return this->handleCommandPermGet(cmd); else if (command == "permfind") return this->handleCommandPermFind(cmd); else if (command == "permidgetbyname") return this->handleCommandPermIdGetByName(cmd); else if (command == "permoverview") return this->handleCommandPermOverview(cmd); else if (command == "permreset") return this->handleCommandPermReset(cmd); else if (command == "clientsetserverquerylogin") return this->handleCommandClientSetServerQueryLogin(cmd); //Music stuff else if (command == "musicbotcreate") return this->handleCommandMusicBotCreate(cmd); else if (command == "musicbotdelete") return this->handleCommandMusicBotDelete(cmd); else if (command == "musicbotsetsubscription") return this->handleCommandMusicBotSetSubscription(cmd); else if (command == "musicbotplayerinfo") return this->handleCommandMusicBotPlayerInfo(cmd); else if (command == "musicbotplayeraction") return this->handleCommandMusicBotPlayerAction(cmd); else if (command == "musicbotqueuelist") return this->handleCommandMusicBotQueueList(cmd); else if (command == "musicbotqueueadd") return this->handleCommandMusicBotQueueAdd(cmd); else if (command == "musicbotqueueremove") return this->handleCommandMusicBotQueueRemove(cmd); else if (command == "musicbotqueuereorder") return this->handleCommandMusicBotQueueReorder(cmd); else if (command == "musicbotplaylistassign") return this->handleCommandMusicBotPlaylistAssign(cmd); else if (command == "help") return this->handleCommandHelp(cmd); else if (command == "logview") return this->handleCommandLogView(cmd); else if (command == "servergroupautoaddperm") return this->handleCommandServerGroupAutoAddPerm(cmd); else if (command == "servergroupautodelperm") return this->handleCommandServerGroupAutoDelPerm(cmd); else if (command == "updatemytsid") return this->handleCommandUpdateMyTsId(cmd); else if (command == "updatemytsdata") return this->handleCommandUpdateMyTsData(cmd); else if (command == "querycreate") return this->handleCommandQueryCreate(cmd); else if (command == "querydelete") return this->handleCommandQueryDelete(cmd); else if (command == "querylist") return this->handleCommandQueryList(cmd); else if (command == "queryrename") return this->handleCommandQueryRename(cmd); else if (command == "querychangepassword") return this->handleCommandQueryChangePassword(cmd); else if (command == "playlistlist") return this->handleCommandPlaylistList(cmd); else if (command == "playlistcreate") return this->handleCommandPlaylistCreate(cmd); else if (command == "playlistdelete") return this->handleCommandPlaylistDelete(cmd); else if (command == "playlistpermlist") return this->handleCommandPlaylistPermList(cmd); else if (command == "playlistaddperm") return this->handleCommandPlaylistAddPerm(cmd); else if (command == "playlistdelperm") return this->handleCommandPlaylistDelPerm(cmd); else if (command == "playlistinfo") return this->handleCommandPlaylistInfo(cmd); else if (command == "playlistedit") return this->handleCommandPlaylistEdit(cmd); else if (command == "playlistsonglist") return this->handleCommandPlaylistSongList(cmd); else if (command == "playlistsongadd") return this->handleCommandPlaylistSongAdd(cmd); else if (command == "playlistsongreorder" || command == "playlistsongmove") return this->handleCommandPlaylistSongReorder(cmd); else if (command == "playlistsongremove") return this->handleCommandPlaylistSongRemove(cmd); else if (command == "dummy_ipchange") return this->handleCommandDummy_IpChange(cmd); if (this->getType() == ClientType::CLIENT_QUERY) return CommandResult::NotImplemented; //Dont log query invalid commands if (this->getType() == ClientType::CLIENT_TEAMSPEAK) if (command.empty() || command.find_first_not_of(' ') == -1) { if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_allow_invalid_packet, 1, this->currentChannel)) ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_command, this->server ? this->server->serverAdmin : static_pointer_cast(serverInstance->getInitalServerAdmin()), true); } logError(this->getServerId(), "Missing command '{}'", command); return CommandResult::NotImplemented; } CommandResult ConnectedClient::handleCommandServerGetVariables(Command &cmd) { CMD_REQ_SERVER; this->notifyServerUpdated(_this.lock()); return CommandResult::Success; } #define SERVEREDIT_CHK_PROP(name, perm, type)\ else if(key == name) { \ if(!permissionGranted(permission::PERMTEST_HIGHEST, perm, 1, nullptr, true, cache, target_server, true)) return CommandResultPermissionError(perm); \ if(toApplay.count(key) == 0) toApplay[key] = cmd[key].as(); \ if(!cmd[0][key].castable()) return {findError("parameter_invalid"), "Invalid type for " + key}; #define SERVEREDIT_CHK_PROP_CACHED(name, perm, type)\ else if(key == name) { \ if(!this->permission_granted(this->cached_permission_value(perm), 1)) return CommandResultPermissionError(perm); \ if(toApplay.count(key) == 0) toApplay[key] = cmd[key].as(); \ if(!cmd[0][key].castable()) return {findError("parameter_invalid"), "Invalid type for " + key}; #define SERVEREDIT_CHK_PROP2(name, perm, type_a, type_b)\ else if(key == name) { \ if(!permissionGranted(permission::PERMTEST_HIGHEST, perm, 1, nullptr, true, cache, target_server, true)) return CommandResultPermissionError(perm); \ if(toApplay.count(key) == 0) toApplay[key] = cmd[key].as(); \ if(!cmd[0][key].castable() && !!cmd[0][key].castable()) return {findError("parameter_invalid"), "Invalid type for " + key}; CommandResult ConnectedClient::handleCommandServerEdit(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); if (cmd[0].has("sid") && this->getServerId() != cmd["sid"].as()) return {findError("server_invalid_id"), "Invalid server id! You can just edit the server where your're bound on"}; auto target_server = this->server; if(cmd[0].has("sid")) { target_server = serverInstance->getVoiceServerManager()->findServerById(cmd["sid"]); if(!target_server && cmd["sid"].as() != 0) return {findError("server_invalid_id")}; } auto cache = make_shared(); map toApplay; for (auto &key : cmd[0].keys()) { if (key == "sid") continue; SERVEREDIT_CHK_PROP_CACHED("virtualserver_name", permission::b_virtualserver_modify_name, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_name_phonetic", permission::b_virtualserver_modify_name, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_maxclients", permission::b_virtualserver_modify_maxclients, size_t) if (cmd["virtualserver_maxclients"].as() > 1024) return {findError("accounting_slot_limit_reached"), "Do you really need more that 1024 slots?"}; } SERVEREDIT_CHK_PROP_CACHED("virtualserver_reserved_slots", permission::b_virtualserver_modify_reserved_slots, size_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_icon_id", permission::b_virtualserver_modify_icon_id, int64_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_channel_temp_delete_delay_default", permission::b_virtualserver_modify_channel_temp_delete_delay_default, ChannelId) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_codec_encryption_mode", permission::b_virtualserver_modify_codec_encryption_mode, int) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_server_group", permission::b_virtualserver_modify_default_servergroup, GroupId) if(target_server) { auto group = target_server->groups->findGroup(cmd["virtualserver_default_server_group"].as()); if (!group || group->target() != GROUPTARGET_SERVER) return {findError("parameter_invalid"), "Invalid default server group!"}; } } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_channel_group", permission::b_virtualserver_modify_default_channelgroup, GroupId) if(target_server) { auto group = target_server->groups->findGroup(cmd["virtualserver_default_channel_group"].as()); if (!group || group->target() != GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "Invalid default channel group!"}; } } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_channel_admin_group", permission::b_virtualserver_modify_default_channeladmingroup, GroupId) if(target_server) { auto group = target_server->groups->findGroup(cmd["virtualserver_default_channel_admin_group"].as()); if (!group || group->target() != GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "Invalid default channel admin group!"}; } } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_music_group", permission::b_virtualserver_modify_default_musicgroup, GroupId) if(target_server) { auto group = target_server->groups->findGroup(cmd["virtualserver_default_music_group"].as()); if (!group || group->target() != GROUPTARGET_SERVER) return {findError("parameter_invalid"), "Invalid default music group!"}; } } SERVEREDIT_CHK_PROP_CACHED("virtualserver_priority_speaker_dimm_modificator", permission::b_virtualserver_modify_priority_speaker_dimm_modificator, float) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_port", permission::b_virtualserver_modify_port, uint16_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_host", permission::b_virtualserver_modify_host, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_web_host", permission::b_virtualserver_modify_port, uint16_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_web_port", permission::b_virtualserver_modify_host, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbanner_url", permission::b_virtualserver_modify_hostbanner, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbanner_gfx_url", permission::b_virtualserver_modify_hostbanner, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbanner_gfx_interval", permission::b_virtualserver_modify_hostbanner, int) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbanner_mode", permission::b_virtualserver_modify_hostbanner, int) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbutton_tooltip", permission::b_virtualserver_modify_hostbutton, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbutton_url", permission::b_virtualserver_modify_hostbutton, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostbutton_gfx_url", permission::b_virtualserver_modify_hostbutton, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostmessage", permission::b_virtualserver_modify_hostmessage, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_hostmessage_mode", permission::b_virtualserver_modify_hostmessage, int) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_welcomemessage", permission::b_virtualserver_modify_welcomemessage, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_weblist_enabled", permission::b_virtualserver_modify_weblist, bool) if (target_server && target_server->running()) { if (cmd["virtualserver_weblist_enabled"].as()) serverInstance->getWebList()->enable_report(target_server); else serverInstance->getWebList()->disable_report(target_server); debugMessage(string() + "Changed weblist state to -> " + (cmd["virtualserver_weblist_enabled"].as() ? "activated" : "disabled")); } } SERVEREDIT_CHK_PROP_CACHED("virtualserver_needed_identity_security_level", permission::b_virtualserver_modify_needed_identity_security_level, int) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_antiflood_points_tick_reduce", permission::b_virtualserver_modify_antiflood, uint64_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_antiflood_points_needed_command_block", permission::b_virtualserver_modify_antiflood, uint64_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_antiflood_points_needed_ip_block", permission::b_virtualserver_modify_antiflood, uint64_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_complain_autoban_count", permission::b_virtualserver_modify_complain, uint64_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_complain_autoban_time", permission::b_virtualserver_modify_complain, uint64_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_complain_remove_time", permission::b_virtualserver_modify_complain, uint64_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_autostart", permission::b_virtualserver_modify_autostart, bool) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_min_clients_in_channel_before_forced_silence", permission::b_virtualserver_modify_channel_forced_silence, int) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_client", permission::b_virtualserver_modify_log_settings, bool) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_query", permission::b_virtualserver_modify_log_settings, bool) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_channel", permission::b_virtualserver_modify_log_settings, bool) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_permissions", permission::b_virtualserver_modify_log_settings, bool) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_server", permission::b_virtualserver_modify_log_settings, bool) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_log_filetransfer", permission::b_virtualserver_modify_log_settings, bool) } SERVEREDIT_CHK_PROP("virtualserver_min_client_version", permission::b_virtualserver_modify_min_client_version, uint64_t) } SERVEREDIT_CHK_PROP("virtualserver_min_android_version", permission::b_virtualserver_modify_min_client_version, uint64_t) } SERVEREDIT_CHK_PROP("virtualserver_min_ios_version", permission::b_virtualserver_modify_min_client_version, uint64_t) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_music_bot_limit", permission::b_virtualserver_modify_music_bot_limit, int) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_flag_password", permission::b_virtualserver_modify_password, bool) if (cmd["virtualserver_flag_password"].as() && !cmd[0].has("virtualserver_password")) return {findError("parameter_invalid"), "Invalid password flag"}; } SERVEREDIT_CHK_PROP_CACHED("virtualserver_password", permission::b_virtualserver_modify_password, string) if(cmd["virtualserver_password"].string().empty()) { toApplay["virtualserver_flag_password"] = "0"; } else { toApplay["virtualserver_flag_password"] = "1"; if(this->getType() == CLIENT_QUERY) toApplay["virtualserver_password"] = base64::encode(digest::sha1(cmd["virtualserver_password"].string())); } } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_client_description", permission::b_virtualserver_modify_default_messages, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_channel_description", permission::b_virtualserver_modify_default_messages, string) } SERVEREDIT_CHK_PROP_CACHED("virtualserver_default_channel_topic", permission::b_virtualserver_modify_default_messages, string) } SERVEREDIT_CHK_PROP2("virtualserver_max_download_total_bandwidth", permission::b_virtualserver_modify_ft_settings, uint64_t, int64_t) if(cmd["virtualserver_max_download_total_bandwidth"].string().find('-') == string::npos) toApplay["virtualserver_max_download_total_bandwidth"] = to_string((int64_t) cmd["virtualserver_max_download_total_bandwidth"].as()); else toApplay["virtualserver_max_download_total_bandwidth"] = to_string(cmd["virtualserver_max_download_total_bandwidth"].as()); } SERVEREDIT_CHK_PROP2("virtualserver_max_upload_total_bandwidth", permission::b_virtualserver_modify_ft_settings, uint64_t, int64_t) if(cmd["virtualserver_max_upload_total_bandwidth"].string().find('-') == string::npos) toApplay["virtualserver_max_upload_total_bandwidth"] = to_string((int64_t) cmd["virtualserver_max_upload_total_bandwidth"].as()); else toApplay["virtualserver_max_upload_total_bandwidth"] = to_string(cmd["virtualserver_max_upload_total_bandwidth"].as()); } SERVEREDIT_CHK_PROP2("virtualserver_download_quota", permission::b_virtualserver_modify_ft_quotas, uint64_t, int64_t) if(cmd["virtualserver_download_quota"].string().find('-') == string::npos) toApplay["virtualserver_download_quota"] = to_string((int64_t) cmd["virtualserver_download_quota"].as()); else toApplay["virtualserver_download_quota"] = to_string(cmd["virtualserver_download_quota"].as()); } SERVEREDIT_CHK_PROP2("virtualserver_upload_quota", permission::b_virtualserver_modify_ft_quotas, uint64_t, int64_t) if(cmd["virtualserver_upload_quota"].string().find('-') == string::npos) toApplay["virtualserver_upload_quota"] = to_string((int64_t) cmd["virtualserver_upload_quota"].as()); else toApplay["virtualserver_upload_quota"] = to_string(cmd["virtualserver_upload_quota"].as()); } else { logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a not existing server properties. (" + key + ")"); //return CommandResult::NotImplemented; } } std::deque keys; bool group_update = false; for (const auto& elm : toApplay) { auto info = property::impl::info(elm.first); if(*info == property::VIRTUALSERVER_UNDEFINED) { logCritical(target_server ? target_server->getServerId() : 0, "Missing server property " + elm.first); continue; } if(!info->validate_input(elm.second)) { logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + info->name + "')"); continue; } if(target_server) target_server->properties()[info] = elm.second; else (*serverInstance->getDefaultServerProperties())[info] = elm.second; keys.push_back(elm.first); group_update |= *info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || *info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || *info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP; } if(target_server) { if (group_update) target_server->forEachClient([&](const shared_ptr& client) { if(target_server->notifyClientPropertyUpdates(client, target_server->groups->update_server_group_property(client, true))) { if(client->update_cached_permissions()) /* update cached calculated permissions */ client->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ } }); if (!keys.empty()) target_server->notifyServerEdited(_this.lock(), keys); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientGetVariables(Command &cmd) { CMD_REQ_SERVER; auto client = this->server->findClient(cmd["clid"].as()); shared_lock tree_lock(this->channel_lock); if (!client || (client != this && !this->isClientVisible(client, false))) return {findError("client_invalid_id"), ""}; deque> props; for (auto &prop : client->properties()->list_properties(property::FLAG_CLIENT_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) { props.push_back(property::info((property::ClientProperties) prop.type().property_index)); } this->notifyClientUpdated(client, props, false); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientKick(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto client = this->server->findClient(cmd["clid"].as()); if (!client) return {findError("client_invalid_id"), "invalid client id"}; if (client->getType() == CLIENT_MUSIC) return {findError("client_invalid_type"), "You cant kick a music bot!"}; std::shared_ptr targetChannel = nullptr; auto type = cmd["reasonid"].as(); if (type == ViewReasonId::VREASON_CHANNEL_KICK) { auto channel = client->currentChannel; PERM_CHECK_CHANNELR(permission::i_client_kick_from_channel_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_kick_from_channel_power, channel), channel, true); targetChannel = this->server->channelTree->getDefaultChannel(); } else if (type == ViewReasonId::VREASON_SERVER_KICK) { auto channel = client->currentChannel; PERM_CHECK_CHANNELR(permission::i_client_kick_from_server_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_kick_from_server_power, channel), channel, true); targetChannel = nullptr; } else return CommandResult::NotImplemented; if (targetChannel) { this->server->notify_client_kick(client, this->ref(), cmd["reasonmsg"].as(), targetChannel); } else { this->server->notify_client_kick(client, this->ref(), cmd["reasonmsg"].as(), nullptr); client->closeConnection(system_clock::now() + seconds(1)); } return CommandResult::Success; } CommandResult 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(!this->permission_granted(this->permissionValue(permission::b_channel_ignore_description_view_power, channel), 1, true)) { CHANNEL_PERMISSION_TEST(permission::i_channel_description_view_power, permission::i_channel_needed_description_view_power, channel, false); } this->sendChannelDescription(channel, true); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandGetConnectionInfo(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto client = this->server->findClient(cmd["clid"].as()); if (!client) return {findError("client_invalid_id"), "invalid client id"}; auto info = client->requestConnectionInfo(_this.lock()); if (info) this->notifyConnectionInfo(client, info); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandSetConnectionInfo(Command &cmd) { auto info = std::make_shared(); info->timestamp = chrono::system_clock::now(); for (const auto &key : cmd[0].keys()) info->properties.insert({key, cmd[key].string()}); /* CONNECTION_FILETRANSFER_BANDWIDTH_SENT, //how many bytes per second are currently being sent by file transfers CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, //how many bytes per second are currently being received by file transfers CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, //how many bytes we received in total through file transfers CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, //how many bytes we sent in total through file transfers */ deque> receivers; { lock_guard info_lock(this->connection_info.lock); for(const auto& weak_receiver : this->connection_info.receiver) { auto receiver = weak_receiver.lock(); if(!receiver) continue; receivers.push_back(receiver); } this->connection_info.receiver.clear(); this->connection_info.data = info; this->connection_info.data_age = system_clock::now(); } for(const auto& receiver : receivers) receiver->notifyConnectionInfo(_this.lock(), info); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandServerRequestConnectionInfo(Command &) { CMD_REQ_SERVER; CACHED_PERM_CHECK(permission::b_virtualserver_connectioninfo_view, 1, true); Command notify("notifyserverconnectioninfo"); auto statistics = this->server->getServerStatistics()->statistics(); auto report = this->server->getServerStatistics()->dataReport(); notify[0]["connection_filetransfer_bandwidth_sent"] = report.file_send; notify[0]["connection_filetransfer_bandwidth_received"] = report.file_recv; notify[0]["connection_filetransfer_bytes_sent_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as(); notify[0]["connection_filetransfer_bytes_received_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as(); notify[0]["connection_packets_sent_total"] = (*statistics)[property::CONNECTION_PACKETS_SENT_TOTAL].as(); notify[0]["connection_bytes_sent_total"] = (*statistics)[property::CONNECTION_BYTES_SENT_TOTAL].as(); notify[0]["connection_packets_received_total"] = (*statistics)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].as(); notify[0]["connection_bytes_received_total"] = (*statistics)[property::CONNECTION_BYTES_RECEIVED_TOTAL].as(); notify[0]["connection_bandwidth_sent_last_second_total"] = report.send_second; notify[0]["connection_bandwidth_sent_last_minute_total"] = report.send_minute; notify[0]["connection_bandwidth_received_last_second_total"] = report.recv_second; notify[0]["connection_bandwidth_received_last_minute_total"] = report.recv_minute; notify[0]["connection_connected_time"] = this->server->properties()[property::VIRTUALSERVER_UPTIME].as(); notify[0]["connection_packetloss_total"] = this->server->averagePacketLoss(); notify[0]["connection_ping"] = this->server->averagePing(); this->sendCommand(notify); return CommandResult::Success; } //connectioninfoautoupdate connection_server2client_packetloss_speech=0.0000 connection_server2client_packetloss_keepalive=0.0010 connection_server2client_packetloss_control=0.0000 connection_server2client_packetloss_total=0.0009 CommandResult ConnectedClient::handleCommandConnectionInfoAutoUpdate(Command &cmd) { this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE] = cmd["connection_server2client_packetloss_keepalive"].as(); this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL] = cmd["connection_server2client_packetloss_control"].as(); this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH] = cmd["connection_server2client_packetloss_speech"].as(); this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL] = cmd["connection_server2client_packetloss_total"].as(); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientGetIds(Command &cmd) { CMD_REQ_SERVER; bool error = false; bool found = false; auto client_list = this->server->getClients(); Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientids" : ""); int result_index = 0; for(int index = 0; index < cmd.bulkCount(); index++) { auto unique_id = cmd[index]["cluid"].as(); for(const auto& entry : client_list) { if(entry->getUid() == unique_id) { if(!config::server::show_invisible_clients_as_online && !this->channels->channel_visible(entry->currentChannel, nullptr)) continue; notify[result_index]["name"] = entry->getDisplayName(); notify[result_index]["clid"] = entry->getClientId(); notify[result_index]["cluid"] = entry->getUid(); result_index++; found = true; } } if(found) found = false; else error = false; } string uid = cmd["cluid"]; if(result_index > 0) { this->sendCommand(notify); } if(error) { return {findError("database_empty_result"), "empty!"}; } return CommandResult::Success; } CommandResult 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 {findError("channel_invalid_id"), "Cant resolve channel"}; auto channel = this->server->channelTree->findChannel(cmd[index]["cid"].as()); if (!channel) return {findError("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 CommandResult::Success; } CommandResult 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 CommandResult::Success; } CommandResult 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 CommandResult::Success; } CommandResult 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 CommandResult::Success; } CommandResult ConnectedClient::handleCommandPermissionList(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); static std::string permission_list_string; static std::mutex permission_list_string_lock; static auto build_permission_list = [](const std::string& command) { Command list_builder(command); int index = 0; for (auto group : permission::availableGroups) list_builder[index++]["group_id_end"] = group; auto avPerms = permission::availablePermissions; std::sort(avPerms.begin(), avPerms.end(), [](const std::shared_ptr &a, const std::shared_ptr &b) { return a->type < b->type; }); for (const auto& permission : avPerms) { if (!permission->clientSupported) continue; auto &blk = list_builder[index++]; blk["permname"] = permission->name; blk["permdesc"] = permission->description; blk["permid"] = permission->type; } return list_builder; }; if(this->getType() == CLIENT_TEASPEAK || this->getType() == CLIENT_TEAMSPEAK || this->getType() == CLIENT_QUERY) { Command response(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifypermissionlist" : ""); { lock_guard lock(permission_list_string_lock); if(permission_list_string.empty()) permission_list_string = build_permission_list("").build(); response[0][""] = permission_list_string; } this->sendCommand(response); } else { this->sendCommand(build_permission_list(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifypermissionlist" : "")); } return CommandResult::Success; } #define M(ptype) \ do { \ for(const auto& prop : property::impl::list()) { \ if((prop->flags & property::FLAG_INTERNAL) > 0) continue; \ response[index]["name"] = prop->name; \ response[index]["flags"] = prop->flags; \ response[index]["type"] = property::PropertyType_Names[prop->type_property]; \ index++; \ } \ } while(0) CommandResult ConnectedClient::handleCommandPropertyList(ts::Command& cmd) { Command response(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifypropertylist" : ""); { string pattern; for (auto flag_name : property::flag_names) pattern = flag_name + string("|") + pattern; pattern = pattern.substr(0, pattern.length() - 1); response["flag_set"] = pattern; } int index = 0; if(cmd.hasParm("all") || cmd.hasParm("server")) M(property::VirtualServerProperties); if(cmd.hasParm("all") || cmd.hasParm("channel")) M(property::ChannelProperties); if(cmd.hasParm("all") || cmd.hasParm("client")) M(property::ClientProperties); if(cmd.hasParm("all") || cmd.hasParm("instance")) M(property::InstanceProperties); if(cmd.hasParm("all") || cmd.hasParm("group")) M(property::GroupProperties); if(cmd.hasParm("all") || cmd.hasParm("connection")) M(property::ConnectionProperties); this->sendCommand(response); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandServerGroupList(Command &) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_servergroup_list, 1, true); this->notifyServerGroupList(); this->command_times.servergrouplist = system_clock::now(); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandChannelGroupAdd(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_channelgroup_create, 1, true); auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); if(cmd["type"].as() == GroupType::GROUP_TYPE_NORMAL && !this->server) return {findError("parameter_invalid"), "You cant create normal channel groups on the template server"}; if(cmd["name"].string().empty()) return {findError("parameter_invalid"), "invalid group name"}; for(const auto& gr : group_manager->availableServerGroups(true)) if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "Group already exists"}; auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_CHANNEL, cmd["type"].as(), cmd["name"].string()); if (group) { group->permissions()->set_permission(permission::b_group_is_permanent, {1, 0}, permission::v2::set_value, permission::v2::do_nothing); if(this->server) this->server->forEachClient([](shared_ptr cl) { cl->notifyChannelGroupList(); }); } else return {findError("parameter_invalid"), "invalid server group id"}; return CommandResult::Success; } //name=Channel\sAdmin scgid=5 tcgid=4 type=1 CommandResult ConnectedClient::handleCommandChannelGroupCopy(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_channelgroup_create, 1, true); auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); auto src = group_manager->findGroup(cmd["scgid"].as()); if (!src || src->target() != GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "invalid channel group id"}; bool global = false; if(cmd[0].has("tcgid")&& cmd["tcgid"].as() != 0) { auto target = group_manager->findGroup(cmd["tcgid"].as()); if (!target || target->target() != GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "invalid target channel group id"}; auto result = group_manager->copyGroupPermissions(src, target); if(!result) return {findError("vs_critical"), "could not copy group!"}; global = !this->server || group_manager->isLocalGroup(target); } else { auto type = cmd["type"].as(); if(type == GroupType::GROUP_TYPE_QUERY) CACHED_PERM_CHECK(permission::b_serverinstance_modify_querygroup, 1, true); if(type == GroupType::GROUP_TYPE_TEMPLATE) CACHED_PERM_CHECK(permission::b_serverinstance_modify_templates, 1, true); if(type == GroupType::GROUP_TYPE_NORMAL && !this->server) return {findError("parameter_invalid"), "You cant create normal channel groups on the template server"}; auto result = group_manager->copyGroup(src, cmd["type"], cmd["name"], type == GROUP_TYPE_NORMAL ? this->getServerId() : 0); //TODO maybe check by name? No duplicated groups? if (!result) return {findError("vs_critical"), "could not copy group!"}; global = !this->server || type != GroupType::GROUP_TYPE_NORMAL; if(this->getType() == ClientType::CLIENT_QUERY) { Command notify(""); notify["cgid"] = group_manager->availableChannelGroups(false).back()->groupId(); this->sendCommand(notify); } } for(const auto& server : (global ? serverInstance->getVoiceServerManager()->serverInstances() : deque>{this->server})) if(server) server->forEachClient([](shared_ptr cl) { cl->notifyChannelGroupList(); }); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandChannelGroupRename(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); auto serverGroup = group_manager->findGroup(cmd["cgid"].as()); if (!serverGroup || serverGroup->target() != GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "invalid channel group id"}; GROUP_PERMISSION_TEST(permission::i_channel_group_modify_power, permission::i_channel_group_needed_modify_power, serverGroup, true); group_manager->renameGroup(serverGroup, cmd["name"].string()); if(this->server) this->server->forEachClient([](shared_ptr cl) { cl->notifyChannelGroupList(); }); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandChannelGroupDel(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); auto channel_group = group_manager->findGroup(cmd["cgid"].as()); if (!channel_group || channel_group->target() != GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "invalid channel group id"}; CACHED_PERM_CHECK(permission::b_virtualserver_channelgroup_delete, 1, true); if(this->server) { if(this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] == channel_group->groupId()) return {findError("parameter_invalid"), "Could not delete default channel group!"}; if(this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] == channel_group->groupId()) return {findError("parameter_invalid"), "Could not delete default channel admin group!"}; } if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP] == channel_group->groupId()) return {findError("parameter_invalid"), "Could not delete instance default channel group!"}; if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP] == channel_group->groupId()) return {findError("parameter_invalid"), "Could not delete instance default channel admin group!"}; if (!cmd["force"].as()) if (!group_manager->listGroupMembers(channel_group, false).empty()) return {findError("database_empty_result"), "group not empty!"}; if (group_manager->deleteGroup(channel_group) && this->server) { this->server->forEachClient([&](shared_ptr cl) { if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true))) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ } cl->notifyChannelGroupList(); }); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandChannelGroupList(Command &) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_channelgroup_list, 1, true); this->notifyChannelGroupList(); this->command_times.servergrouplist = system_clock::now(); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandChannelGroupClientList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_channelgroup_client_list, 1, true); Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifychannelgroupclientlist" : ""); deque variables{variable{":sid", this->getServerId()}}; string query = "SELECT `groupId`, `cldbid`, `until`, `channelId` FROM `assignedGroups` WHERE `serverId` = :sid"; if(cmd[0].has("cgid") && cmd["cgid"].as() > 0) { auto group = this->server->getGroupManager()->findGroup(cmd["cgid"]); if(!group || group->target() != GroupTarget::GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "invalid channel group id"}; query += " AND `groupId` = :groupId"; variables.push_back({":groupId", cmd["cgid"].as()}); } else { query += " AND `groupId` IN (SELECT `groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target)"; variables.push_back({":target", GroupTarget::GROUPTARGET_CHANNEL}); } if(cmd[0].has("cldbid") && cmd["cldbid"].as() > 0) { query += " AND `cldbid` = :cldbid"; variables.push_back({":cldbid", cmd["cldbid"].as()}); } if(cmd[0].has("cid") && cmd["cid"].as() > 0) { auto channel = this->server->getChannelTree()->findChannel(cmd["cid"]); if(!channel) return {findError("parameter_invalid"), "invalid channel id"}; query += " AND `channelId` = :cid"; variables.push_back({":cid", cmd["cid"].as()}); } debugMessage(this->getServerId(), "Command channelgroupclientlist sql: {}", query); auto command = sql::command(this->sql, query); for(const auto& variable : variables) command.value(variable); int index = 0; command.query([&](Command& command, int& index, int length, string* values, string* names) { GroupId group_id = 0; ChannelId channel_id = 0; ClientDbId cldbid = 0; for(int i = 0; i < length; i++) { try { if(names[i] == "groupId") group_id = stoll(values[i]); else if(names[i] == "cldbid") cldbid = stoll(values[i]); else if(names[i] == "channelId") channel_id = stoll(values[i]); } catch(std::exception& ex) { logError(this->getServerId(), "Failed to parse db field {}", names[i]); } } result[index]["cid"] = channel_id; result[index]["cgid"] = group_id; result[index]["cldbid"] = cldbid; index++; }, result, index); if (index == 0) return {findError("database_empty_result"), "empty"}; this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandChannelGroupPermList(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_channelgroup_permission_list, 1, true); auto channelGroup = (this->server ? this->server->groups : serverInstance->getGroupManager().get())->findGroup(cmd["cgid"].as()); if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "invalid channel group id"}; if (!this->notifyGroupPermList(channelGroup, cmd.hasParm("permsid"))) return {findError("database_empty_result"), "empty"}; if(this->getType() == ClientType::CLIENT_TEAMSPEAK && this->command_times.last_notify + this->command_times.notify_timeout < system_clock::now()) { this->sendTSPermEditorWarning(); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); auto channelGroup = group_manager->findGroup(cmd["cgid"].as()); if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "invalid channel group id"}; GROUP_PERMISSION_TEST(permission::i_channel_group_modify_power, permission::i_channel_group_needed_modify_power, channelGroup, true); auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); bool ignoreGrant = this->permission_granted(this->cached_permission_value(permission::b_permission_modify_power_ignore), 1); bool updateList = false; bool conOnError = cmd[0].has("continueonerror"); auto permission_manager = channelGroup->permissions(); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); auto val = cmd[index]["permvalue"].as(); if(permission_require_granted_value(permType) && val > maxValue) return CommandResultPermissionError{permission::i_permission_modify_power}; if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) return CommandResultPermissionError{permission::i_permission_modify_power}; if (grant) { permission_manager->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); } else { permission_manager->set_permission( permType, {cmd[index]["permvalue"], 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing, cmd[index]["permskip"].as() ? 1 : 0, cmd[index]["permnegated"].as() ? 1 : 0 ); updateList |= permission_is_group_property(permType); } } if(updateList) channelGroup->apply_properties_from_permissions(); if(this->server) { if(updateList) this->server->forEachClient([](shared_ptr cl) { cl->notifyChannelGroupList(); }); this->server->forEachClient([channelGroup](shared_ptr cl) { unique_lock client_channel_lock(cl->channel_lock); /* while we're updating groups we dont want to change anything! */ if (cl->channelGroupAssigned(channelGroup, cl->getChannel())) { if(cl->update_cached_permissions()) cl->sendNeededPermissions(false); /* update the needed permissions */ cl->updateChannelClientProperties(true, true); } }); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(5); auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); auto channelGroup = group_manager->findGroup(cmd["cgid"].as()); if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "invalid channel group id"}; GROUP_PERMISSION_TEST(permission::i_channel_group_modify_power, permission::i_channel_group_needed_modify_power, channelGroup, true); bool ignoreGrant = this->permission_granted(this->cached_permission_value(permission::b_permission_modify_power_ignore), 1); bool updateList = false; bool conOnError = cmd[0].has("continueonerror"); auto permission_manager = channelGroup->permissions(); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd) if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) return CommandResultPermissionError{permission::i_permission_modify_power}; if (grant) { permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); } else { permission_manager->set_permission( permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing ); updateList |= permission_is_group_property(permType); } } if(updateList) channelGroup->apply_properties_from_permissions(); if(this->server) { if(updateList) this->server->forEachClient([](shared_ptr cl) { cl->notifyChannelGroupList(); }); this->server->forEachClient([channelGroup](shared_ptr cl) { unique_lock client_channel_lock(cl->channel_lock); /* while we're updating groups we dont want to change anything! */ if (cl->channelGroupAssigned(channelGroup, cl->getChannel())) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ cl->updateChannelClientProperties(true, false); } }); } return CommandResult::Success; } using namespace std::chrono; CommandResult ConnectedClient::handleCommandClientMove(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(10); shared_lock server_channel_r_lock(this->server->channel_tree_lock); auto target_client_id = cmd["clid"].as(); auto target_client = target_client_id == 0 ? this->ref() : this->server->findClient(target_client_id); if(!target_client) { return {findError("client_invalid_id"), "Invalid target clid"}; } if(!target_client->currentChannel) { if(target_client != this) return {findError("client_invalid_id"), "Invalid target clid"}; } auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) { return {findError("channel_invalid_id")}; } auto permission_cache = make_shared(); if(!cmd[0].has("cpw")) cmd["cpw"] = ""; if (!channel->passwordMatch(cmd["cpw"], true)) if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_ignore_password, 1, channel, true, permission_cache)) return {findError("channel_invalid_password"), "invalid password"}; switch(channel->channelType()) { case ChannelType::permanent: PERM_CHECK_CHANNEL_CR(permission::b_channel_join_permanent, 1, channel, true, permission_cache); break; case ChannelType::semipermanent: PERM_CHECK_CHANNEL_CR(permission::b_channel_join_semi_permanent, 1, channel, true, permission_cache); break; case ChannelType::temporary: PERM_CHECK_CHANNEL_CR(permission::b_channel_join_temporary, 1, channel, true, permission_cache); break; } if (!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as() || !channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as()) { if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_join_ignore_maxclients, 1, channel, true, permission_cache)) { if(!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as()) { auto maxClients = channel->properties()[property::CHANNEL_MAXCLIENTS].as(); if (maxClients >= 0 && maxClients <= this->server->getClientsByChannel(channel).size()) return {findError("channel_maxclients_reached"), ""}; } if(!channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as()) { shared_ptr family_root; if(channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as()) { family_root = channel; while(family_root && family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as()) family_root = family_root->parent(); } if(family_root && !family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED]) { //Could not be CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED auto maxClients = family_root->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as(); auto clients = 0; for(const auto& entry : this->server->getClientsByChannelRoot(channel, false)) if(entry.get() != this) clients++; //Dont count the client itself if (maxClients >= 0 && maxClients <= clients) return {findError("channel_maxfamily_reached"), ""}; } } } } if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_channel_ignore_join_power, 1, channel, true, permission_cache)) { CHANNEL_PERMISSION_TEST(permission::i_channel_join_power, permission::i_channel_needed_join_power, channel, false); if (target_client == this) { auto permission_cache_current = make_shared(); auto val = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_is_sticky, this->currentChannel, permission_cache_current); if (val != permNotGranted && val > 0) { auto st = this->permissionValue(permission::PERMTEST_ORDERED, permission::b_client_ignore_sticky, this->currentChannel, permission_cache_current); if (st != 1) return CommandResultPermissionError{permission::b_client_is_sticky}; } } } if (target_client != this) { PERM_CHECK_CHANNELR(permission::i_client_move_power, target_client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_move_power, target_client->getChannel()), target_client->getChannel(), true); PERM_CHECK_CHANNEL_CR(permission::i_client_move_power, target_client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_move_power, channel), channel, true, permission_cache); } server_channel_r_lock.unlock(); unique_lock server_channel_w_lock(this->server->channel_tree_lock); auto oldChannel = target_client->getChannel(); this->server->client_move( target_client, channel, target_client == this ? nullptr : _this.lock(), "", target_client == this ? ViewReasonId::VREASON_USER_ACTION : ViewReasonId::VREASON_MOVED, true, server_channel_w_lock ); if(oldChannel) { if(!server_channel_w_lock.owns_lock()) server_channel_w_lock.lock(); if(oldChannel->channelType() == ChannelType::temporary && oldChannel->properties()[property::CHANNEL_DELETE_DELAY].as() == 0) if(this->server->getClientsByChannelRoot(oldChannel, false).empty()) this->server->delete_channel(dynamic_pointer_cast(oldChannel), this->ref(), "temporary auto delete", server_channel_w_lock); if(server_channel_w_lock.owns_lock()) server_channel_w_lock.unlock(); } return CommandResult::Success; } //TODO: Test if parent or previous is deleted! CommandResult ConnectedClient::handleCommandChannelCreate(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); CMD_CHK_PARM_COUNT(1); auto permission_cache = make_shared(); if (cmd[0].has("cpid") && cmd["cpid"].as() != 0) CACHED_PERM_CHECK(permission::b_channel_create_child, 1); if (cmd[0].has("channel_order")) CACHED_PERM_CHECK(permission::b_channel_create_with_sortorder, 1); if(!cmd[0].has("channel_flag_permanent")) cmd[0]["channel_flag_permanent"] = false; if(!cmd[0].has("channel_flag_semi_permanent")) cmd[0]["channel_flag_semi_permanent"] = false; if(!cmd[0].has("channel_flag_default")) cmd[0]["channel_flag_default"] = false; if(!cmd[0].has("channel_flag_password")) cmd[0]["channel_flag_password"] = false; if (cmd[0]["channel_flag_permanent"].as()) CACHED_PERM_CHECK(permission::b_channel_create_permanent, 1); else if (cmd[0]["channel_flag_semi_permanent"].as()) CACHED_PERM_CHECK(permission::b_channel_create_semi_permanent, 1); else CACHED_PERM_CHECK(permission::b_channel_create_temporary, 1); if (!cmd[0]["channel_flag_permanent"].as() && !this->server) return {findError("parameter_invalid"), "You can only create a permanent channel"}; if (cmd[0]["channel_flag_default"].as()) CACHED_PERM_CHECK(permission::b_channel_create_with_default, 1); if (cmd[0]["channel_flag_password"].as()) CACHED_PERM_CHECK(permission::b_channel_create_with_password, 1); else if(this->permission_granted(this->cached_permission_value(permission::b_channel_create_modify_with_force_password), 1)) return CommandResultPermissionError{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")) CACHED_PERM_CHECK(permission::b_channel_create_with_description, 1); if (cmd[0].has("channel_maxclients") || (cmd[0].has("channel_flag_maxclients_unlimited") && !cmd["channel_flag_maxclients_unlimited"].as())) { CACHED_PERM_CHECK(permission::b_channel_create_with_maxclients, 1); 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")) CACHED_PERM_CHECK(permission::b_channel_create_with_maxfamilyclients, 1); if (cmd[0].has("channel_needed_talk_power")) CACHED_PERM_CHECK(permission::b_channel_create_with_needed_talk_power, 1); if (cmd[0].has("channel_topic")) CACHED_PERM_CHECK(permission::b_channel_create_with_topic, 1); 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); { 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 { CACHED_PERM_CHECK(permission::i_channel_create_modify_with_temp_delete_delay, cmd["channel_delete_delay"].as()); } } { size_t created_tmp = 0, created_semi = 0, created_perm = 0; auto own_cldbid = this->getClientDatabaseId(); for(const auto& channel : target_tree->channels()) { if(channel->properties()[property::CHANNEL_CREATED_BY] == own_cldbid) { if(channel->properties()[property::CHANNEL_FLAG_PERMANENT].as()) created_perm++; else if(channel->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as()) created_semi++; else created_tmp++; } } auto max_channels = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_channels, nullptr, permission_cache); if(max_channels >= 0) { if(max_channels <= created_perm + created_semi + created_tmp) return CommandResultPermissionError{permission::i_client_max_channels}; } if (cmd[0]["channel_flag_permanent"].as()) { max_channels = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_permanent_channels, nullptr, permission_cache); if(max_channels >= 0) { if(max_channels <= created_perm) return CommandResultPermissionError{permission::i_client_max_permanent_channels}; } } else if (cmd[0]["channel_flag_semi_permanent"].as()) { max_channels = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_semi_channels, nullptr, permission_cache); if(max_channels >= 0) { if(max_channels <= created_semi) return CommandResultPermissionError{permission::i_client_max_semi_channels}; } } else { max_channels = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_temporary_channels, nullptr, permission_cache); if(max_channels >= 0) { if(max_channels <= created_tmp) return CommandResultPermissionError{permission::i_client_max_temporary_channels}; } } } //TODO check voice (opus etc) std::shared_ptr parent = nullptr; std::shared_ptr created_channel = nullptr, old_default_channel; //bool enforce_permanent_parent = cmd[0]["channel_flag_default"].as(); //TODO check parents here { //Checkout the parent(s) if (cmd[0].has("cpid") && cmd["cpid"].as() != 0 && cmd["cpid"].as() != -1) { parent = target_tree->findLinkedChannel(cmd["cpid"].as()); if (!parent) return {findError("channel_invalid_id"), "Cant resolve parent channel"}; } { auto min_channel_deep = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_min_depth, nullptr, permission_cache); auto max_channel_deep = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_max_depth, nullptr, permission_cache); if(min_channel_deep >= 0 || max_channel_deep >= 0) { 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 {findError("channel_is_deleted"), "Oneof parent is deleted"}; } local_parent = local_parent->parent.lock(); } if(min_channel_deep >= 0 && channel_deep < min_channel_deep) return CommandResultPermissionError{permission::i_channel_min_depth}; if(max_channel_deep >= 0 && channel_deep > max_channel_deep) return CommandResultPermissionError{permission::i_channel_max_depth}; } } } if(!cmd[0].has("channel_order")) { auto last = parent ? parent->child_head : target_tree->tree_head(); while(last && last->next) last = last->next; if(last) cmd["channel_order"] = last->entry->channelId(); } else { } if (cmd["channel_name"].string().length() < 1) return {findError("channel_name_inuse"), "Invalid channel name"}; { if (target_tree->findChannel(cmd["channel_name"].as(), parent ? dynamic_pointer_cast(parent->entry) : nullptr)) return {findError("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 {findError("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 permission_manager = created_channel->permissions(); permission_manager->set_permission( permission::i_channel_needed_modify_power, {this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_modify_power, this->currentChannel, permission_cache), 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing ); permission_manager->set_permission( permission::i_channel_needed_delete_power, {this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_delete_power, this->currentChannel, permission_cache), 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing ); } for (auto &prop : cmd[0].keys()) { if (prop == "channel_flag_default") continue; if (prop == "channel_order") continue; if (prop == "channel_name") continue; if (prop == "cpid") continue; if (prop == "cid") continue; const auto &property = property::info(prop); if(*property == property::CHANNEL_UNDEFINED) { logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + prop); continue; } if(!property->validate_input(cmd[prop].as())) { logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as() + "', Property: '" + property->name + "')"); continue; } created_channel->properties()[property] = cmd[prop].as(); } if(created_channel->parent()) { if(created_channel->parent()->channelType() > created_channel->channelType()) created_channel->setChannelType(created_channel->parent()->channelType()); } if(this->server) { const auto self_lock = _this.lock(); this->server->forEachClient([&, created_channel](const shared_ptr& client) { unique_lock client_channel_lock(client->channel_lock); auto result = client->channels->add_channel(created_linked_channel); if(!result) return; client->notifyChannelCreate(created_channel, result->previous_channel, self_lock); if(client == self_lock && this->getType() == ClientType::CLIENT_QUERY) { Command notify(""); notify["cid"] = created_channel->channelId(); this->sendCommand(notify); } client->notifyChannelDescriptionChanged(created_channel); if (client->subscribeToAll) client->subscribeChannel({created_channel}, false, true); if(old_default_channel) { //TODO: Reminder: client channel tree must be at least read locked here! client->notifyChannelEdited(old_default_channel, {"channel_flag_default"}, this); } }); GroupId adminGroup = this->server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP]; auto channel_admin_group = this->server->groups->findGroup(adminGroup); if (!channel_admin_group) { logError(this->getServerId(), "Missing server's default channel admin group! Using default channel group!"); channel_admin_group = this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL); } this->server->groups->setChannelGroup(this->getClientDatabaseId(), channel_admin_group, created_channel); if (created_channel->channelType() == ChannelType::temporary && (this->getType() == ClientType::CLIENT_TEAMSPEAK || this->getType() == ClientType::CLIENT_WEB)) this->server->client_move( this->ref(), created_channel, nullptr, "channel created", ViewReasonId::VREASON_USER_ACTION, true, tree_channel_lock ); } return CommandResult::Success; } CommandResult 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 CommandResult::Success; CHANNEL_PERMISSION_TEST(permission::i_channel_delete_power, permission::i_channel_needed_delete_power, channel, true); for(const auto& ch : channel_tree->channels(channel)) { if(ch->defaultChannel()) return {findError("channel_can_not_delete_default"), "The channel tree you're going to delete contains the default channel!"}; } if(this->server) { auto clients = this->server->getClientsByChannelRoot(channel, false); if(!clients.empty()) PERM_CHECK_CHANNELR(permission::b_channel_delete_flag_force, 1, channel, true); this->server->delete_channel(channel, this->ref(), "channel deleted", channel_tree_write_lock); } else { auto channel_ids = channel_tree->deleteChannelRoot(channel); this->notifyChannelDeleted(channel_ids, this->ref()); } return CommandResult::Success; } inline ssize_t count_characters(const std::string& in) { size_t index = 0; size_t count = 0; while(index < in.length()){ count++; auto current = (uint8_t) in[index]; if(current >= 128) { //UTF8 check if(current >= 192 && (current <= 193 || current >= 245)) { return -1; } else if(current >= 194 && current <= 223) { if(in.length() - index <= 1) return -1; else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191) index += 1; //Valid else return -1; } else if(current >= 224 && current <= 239) { if(in.length() - index <= 2) return -1; else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 && (uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191) index += 2; //Valid else return -1; } else if(current >= 240 && current <= 244) { if(in.length() - index <= 3) return -1; else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 && (uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191 && (uint8_t) in[index + 3] >= 128 && (uint8_t) in[index + 3] <= 191) index += 3; //Valid else return -1; } else { return -1; } } index++; } return count; } /* * 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 */ CommandResult ConnectedClient::handleCommandChannelEdit(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); RESOLVE_CHANNEL_R(cmd["cid"], true); auto channel = dynamic_pointer_cast(l_channel->entry); assert(channel); if(channel->deleted) { /* channel gets already removed */ return CommandResult::Success; } 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(); CHANNEL_PERM_TEST_INIT; CHANNEL_PERMISSION_TEST(permission::i_channel_modify_power, permission::i_channel_needed_modify_power, channel, true); for (const auto &key : cmd[0].keys()) { if(key == "cid") continue; if(key == "return_code") continue; const auto &property = property::info(key); if(*property == property::CHANNEL_UNDEFINED) { logError(this->getServerId(), R"({} Tried to edit a not existing channel property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); continue; } if((property->flags & property::FLAG_USER_EDITABLE) == 0) { logError(this->getServerId(), "{} Tried to change a channel property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); continue; } if(!property->validate_input(cmd[key].as())) { logError(this->getServerId(), "{} Tried to change a channel property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); continue; } if(channel->properties()[*property].as() == cmd[key].as()) continue; /* we dont need to update stuff which is the same */ if(key == "channel_icon_id") { CHANNEL_PERMISSION_TEST(permission::i_channel_permission_modify_power, permission::i_channel_needed_permission_modify_power, channel, true); } else if (key == "channel_order") { CHANNEL_PERM_TEST(permission::b_channel_modify_sortorder, 1, true); require_write_lock = true; } else if (key == "channel_flag_default") { if(!cmd["channel_flag_default"].as()) return {findError("channel_invalid_flags")}; CHANNEL_PERM_TEST(permission::b_channel_modify_make_default, 1, true); require_write_lock = true; } else if (key == "channel_name") { CHANNEL_PERM_TEST(permission::b_channel_modify_name, 1, true); if (count_characters(cmd["channel_name"]) < 1) return {findError("channel_name_inuse"), "Invalid channel name (too short)"}; if (count_characters(cmd["channel_name"]) > 40) return {findError("channel_name_inuse"), "Invalid channel name (too long)"}; require_write_lock = true; update_name = true; } else if (key == "channel_name_phonetic") { CHANNEL_PERM_TEST(permission::b_channel_modify_name, 1, true); } else if (key == "channel_topic") { CHANNEL_PERM_TEST(permission::b_channel_modify_topic, 1, true); } else if (key == "channel_description") { CHANNEL_PERM_TEST(permission::b_channel_modify_description, 1, true); if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_use_bbcode_any, 1, this->currentChannel)) { auto bbcode_image = bbcode::sloppy::has_image(cmd[key]); auto bbcode_url = bbcode::sloppy::has_url(cmd[key]); debugMessage(this->getServerId(), "Channel description contains bb codes: Image: {} URL: {}", bbcode_image, bbcode_url); if(bbcode_image && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_use_bbcode_image, 1, this->currentChannel)) return CommandResultPermissionError{permission::b_client_use_bbcode_image}; if(bbcode_url && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_use_bbcode_url, 1, this->currentChannel)) return CommandResultPermissionError{permission::b_client_use_bbcode_url}; } } else if (key == "channel_codec") { CHANNEL_PERM_TEST(permission::b_channel_modify_codec, 1, true); } else if (key == "channel_codec_quality") { CHANNEL_PERM_TEST(permission::b_channel_modify_codec_quality, 1, true); } else if (key == "channel_codec_is_unencrypted") { if (cmd["channel_codec_is_unencrypted"].as()) CHANNEL_PERM_TEST(permission::b_channel_modify_make_codec_encrypted, 1, true); } else if (key == "channel_needed_talk_power") { CHANNEL_PERM_TEST(permission::b_channel_modify_needed_talk_power, 1, true); update_clients_in_channel = true; } else if (key == "channel_maxclients" || key == "channel_flag_maxclients_unlimited") { CHANNEL_PERM_TEST(permission::b_channel_modify_maxclients, 1, true); require_write_lock = true; update_max_clients = true; } else if(key == "channel_maxfamilyclients" || key == "channel_flag_maxfamilyclients_unlimited" || key == "channel_flag_maxfamilyclients_inherited") { CHANNEL_PERM_TEST(permission::b_channel_modify_maxfamilyclients, 1, true); 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()) { CHANNEL_PERM_TEST(permission::b_channel_modify_make_permanent, 1, true); target_channel_type = ChannelType::permanent; } else if (cmd[0].has("channel_flag_semi_permanent") && cmd["channel_flag_semi_permanent"].as()) { CHANNEL_PERM_TEST(permission::b_channel_modify_make_semi_permanent, 1, true); target_channel_type = ChannelType::semipermanent; } else { CHANNEL_PERM_TEST(permission::b_channel_modify_make_temporary, 1, true); target_channel_type = ChannelType::temporary; } target_channel_type_changed = true; require_write_lock = true; } else if (key == "channel_delete_delay") { CHANNEL_PERM_TEST(permission::b_channel_modify_temp_delete_delay, cmd["channel_delete_delay"].as(), true); } else if (key == "channel_password" || key == "channel_flag_password") { CHANNEL_PERM_TEST(permission::b_channel_modify_password, 1, true); update_password = true; } else { logCritical( this->getServerId(), "The client " + this->getDisplayName() + " tried to change a editable channel property but we haven't found a permission. Please report this error. (Channel property: {})", key ); continue; } keys.push_back(property); } unique_lock server_channel_w_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock(), defer_lock); if(require_write_lock) { channel_tree_read_lock.unlock(); server_channel_w_lock.lock(); /* not that while we're waiting to edit the server the channel got deleted... fuck my english */ if(channel->deleted) return CommandResult::Success; } /* 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 {findError("parameter_missing"), ""}; else cmd["channel_password"] = ""; /* no password set */ keys.push_back(property::info(property::CHANNEL_PASSWORD)); } if(!cmd[0].has("channel_flag_password")) { cmd["channel_flag_password"] = !cmd["channel_password"].string().empty(); keys.push_back(property::info(property::CHANNEL_FLAG_PASSWORD)); } if(cmd["channel_flag_password"].as()) { if(cmd["channel_password"].string().empty()) return {findError("channel_invalid_flags")}; /* we cant enable a password without a given password */ /* we've to "encode" the password */ if(this->getType() == ClientType ::CLIENT_QUERY) cmd["channel_password"] = base64::encode(digest::sha1(cmd["channel_password"].string())); } else { cmd["channel_password"] = ""; /* flag password if false so we set the password to empty */ } } /* test the default channel update */ if(cmd[0].has("channel_flag_default") || channel->defaultChannel()) { if(target_channel_type != ChannelType::permanent) return {findError("channel_default_require_permanent")}; /* default channel is not allowed to be non permanent */ if((cmd[0].has("channel_flag_password") && cmd["channel_flag_password"].as()) || channel->properties()[property::CHANNEL_FLAG_PASSWORD]) { cmd["channel_flag_password"] = false; cmd["channel_password"] = ""; keys.push_back(property::info(property::CHANNEL_FLAG_PASSWORD)); } if(cmd[0].has("channel_flag_default")) { cmd["channel_maxclients"] = -1; cmd["channel_flag_maxclients_unlimited"] = true; keys.push_back(property::info(property::CHANNEL_MAXCLIENTS)); keys.push_back(property::info(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED)); update_max_clients = true; cmd["channel_maxfamilyclients"] = -1; cmd["channel_flag_maxfamilyclients_inherited"] = true; keys.push_back(property::info(property::CHANNEL_MAXFAMILYCLIENTS)); keys.push_back(property::info(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED)); update_max_family_clients = true; } } /* "fix" max client for temporary channels */ if(target_channel_type_changed) { if(target_channel_type == ChannelType::temporary) { if(channel->properties()[property::CHANNEL_MAXCLIENTS].as() != -1) { cmd["channel_maxclients"] = -1; cmd["channel_flag_maxclients_unlimited"] = true; keys.push_back(property::info(property::CHANNEL_MAXCLIENTS)); keys.push_back(property::info(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED)); update_max_clients = true; } if(channel->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as() != -1) { cmd["channel_maxfamilyclients"] = -1; cmd["channel_flag_maxfamilyclients_inherited"] = true; keys.push_back(property::info(property::CHANNEL_MAXFAMILYCLIENTS)); keys.push_back(property::info(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED)); update_max_family_clients = true; } } if(target_channel_type != ChannelType::permanent) { /* test if any child is the default channel */ for(const auto& child : channel_tree->channels(channel)) if(child->defaultChannel()) return {findError("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 {findError("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 {findError("parameter_missing"), "channel_maxclients"}; /* max clients must be specified */ keys.push_back(property::info(property::CHANNEL_MAXCLIENTS)); } if(!cmd[0].has("channel_flag_maxclients_unlimited")) { cmd["channel_flag_maxclients_unlimited"] = cmd["channel_maxclients"].as() < 0; keys.push_back(property::info(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED)); } if(cmd["channel_flag_maxclients_unlimited"].as() && cmd["channel_maxclients"].as() != -1) return {findError("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 {findError("channel_invalid_flags")}; /* temporary channels cant have a limited user count */ } /* test the max family clients parameters */ if(update_max_family_clients) { if(!cmd[0].has("channel_maxfamilyclients")) { if(cmd[0].has("channel_flag_maxfamilyclients_unlimited")) { if(cmd["channel_flag_maxfamilyclients_unlimited"].as()) cmd["channel_flag_maxfamilyclients_inherited"] = false; else cmd["channel_flag_maxfamilyclients_inherited"] = true; keys.push_back(property::info(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED)); } else if(cmd[0].has("channel_flag_maxfamilyclients_inherited")) { if(cmd["channel_flag_maxfamilyclients_inherited"].as()) cmd["channel_flag_maxfamilyclients_unlimited"] = false; else cmd["channel_flag_maxfamilyclients_unlimited"] = true; keys.push_back(property::info(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED)); } else /* not really possible */ return {findError("parameter_missing"), "channel_maxfamilyclients"}; /* family max clients must be */ cmd["channel_maxfamilyclients"] = -1; keys.push_back(property::info(property::CHANNEL_MAXFAMILYCLIENTS)); } //keep this order because this command: "channeledit cid= channel_maxfamilyclients=-1" should set max family clients mode to inherited if(!cmd[0].has("channel_flag_maxfamilyclients_inherited")) { auto flag_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as(); if(flag_unlimited) cmd["channel_flag_maxfamilyclients_inherited"] = false; else cmd["channel_flag_maxfamilyclients_inherited"] = cmd["channel_maxfamilyclients"].as() < 0; } if(!cmd[0].has("channel_flag_maxfamilyclients_unlimited")) { auto flag_inherited = cmd[0].has("channel_flag_maxfamilyclients_inherited") && cmd["channel_flag_maxfamilyclients_inherited"].as(); if(flag_inherited) cmd["channel_flag_maxfamilyclients_unlimited"] = false; else cmd["channel_flag_maxfamilyclients_unlimited"] = cmd["channel_maxfamilyclients"].as() < 0; } if(cmd["channel_flag_maxfamilyclients_inherited"].as() && cmd["channel_flag_maxfamilyclients_unlimited"].as()) return {findError("channel_invalid_flags")}; /* both at the same time are not possible */ if(cmd["channel_flag_maxfamilyclients_inherited"].as() && cmd["channel_maxfamilyclients"].as() != -1) return {findError("channel_invalid_flags")}; /* flag inherited required max users to be -1 */ if(cmd["channel_flag_maxfamilyclients_unlimited"].as() && cmd["channel_maxfamilyclients"].as() != -1) return {findError("channel_invalid_flags")}; /* flag unlimited required max users to be -1 */ if(cmd["channel_maxfamilyclients"].as() != -1 && target_channel_type == ChannelType::temporary) return {findError("channel_invalid_flags")}; /* temporary channels cant have a limited user count */ } /* test the channel name */ if(update_name) { auto named_channel = channel_tree->findChannel(cmd["channel_name"].string(), channel->parent()); if (named_channel) return {findError("channel_name_inuse"), to_string(named_channel->channelId())}; } shared_ptr old_default_channel; deque> child_channel_updated; for(const std::shared_ptr& key : keys) { if(*key == property::CHANNEL_ORDER) { /* TODO: May move that up because if it fails may some other props have already be applied */ if (!channel_tree->change_order(channel, cmd[key->name])) return {findError("channel_invalid_order"), "Can't change order id"}; if(this->server) { auto parent = channel->hasParent() ? channel_tree->findLinkedChannel(channel->parent()->channelId()) : nullptr; auto previous = channel_tree->findLinkedChannel(channel->previousChannelId()); this->server->forEachClient([&](const shared_ptr& cl) { unique_lock client_channel_lock(cl->channel_lock); auto actions = cl->channels->change_order(l_channel, parent, previous); std::deque deletions; for(const auto& action : actions) { switch (action.first) { case ClientChannelView::NOTHING: continue; case ClientChannelView::ENTER_VIEW: cl->notifyChannelShow(action.second->channel(), action.second->previous_channel); break; case ClientChannelView::DELETE_VIEW: deletions.push_back(action.second->channelId()); break; case ClientChannelView::MOVE: cl->notifyChannelMoved(action.second->channel(), action.second->previous_channel, this->ref()); break; case ClientChannelView::REORDER: cl->notifyChannelEdited(action.second->channel(), {"channel_order"}, this); break; } } if(!deletions.empty()) { cl->notifyChannelHide(deletions, false); return; //Channel got deleted so we dont have to send the updates } }); } } else if(*key == property::CHANNEL_FLAG_DEFAULT) { old_default_channel = channel_tree->getDefaultChannel(); if(old_default_channel == channel) { old_default_channel = nullptr; continue; } if(!cmd[key->name].as()) { old_default_channel = nullptr; continue; } channel_tree->setDefaultChannel(channel); deque> updated_channels; shared_ptr current_channel = channel; do { if(current_channel->channelType() == ChannelType::permanent) break; current_channel->setChannelType(ChannelType::permanent); if(current_channel != channel) updated_channels.push_back(channel); } while ((current_channel = current_channel->parent())); if(this->server && !updated_channels.empty()) { std::reverse(updated_channels.begin(), updated_channels.end()); this->server->forEachClient([&](const shared_ptr& client) { unique_lock client_channel_lock(client->channel_lock); for(const auto& channel : updated_channels) { client->notifyChannelEdited(channel, {"channel_flag_permanent", "channel_flag_semi_permanent"}, this); } }); } } 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); } } } } } } channel->properties()[key] = cmd[key->name].string(); } if(this->server) { vector key_vector; key_vector.reserve(keys.size()); for(const auto& key : keys) key_vector.push_back(key->name); this->server->forEachClient([&](const shared_ptr& client) { unique_lock client_channel_lock(client->channel_lock); for(const auto& channel : child_channel_updated) client->notifyChannelEdited(channel, {"channel_flag_permanent", "channel_flag_semi_permanent"}, this); client->notifyChannelEdited(channel, key_vector, this); if(old_default_channel) /* clients need to have one or more defualt channels... */ client->notifyChannelEdited(old_default_channel, {"channel_flag_default"}, this); }); } if(server_channel_w_lock.owns_lock()) server_channel_w_lock.unlock(); if(!channel_tree_read_lock.owns_lock()) channel_tree_read_lock.lock(); if(update_clients_in_channel && this->server) { for(const auto& client : this->server->getClientsByChannel(channel)) client->updateChannelClientProperties(true, true); //TODO: May only update the talk power and not all? } return CommandResult::Success; } CommandResult 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 {findError("channel_is_deleted"), "target channel has been 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 {findError("channel_invalid_id"), "Cant resolve order channel"}; } 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 {findError("channel_is_deleted"), "parent channel order previous channel has been deleted"}; if(channel->parent() == parent && channel->channelOrder() == (order ? order->channelId() : 0)) return CommandResult::Success; if (channel->parent() != parent) PERM_CHECK_CHANNELR(permission::b_channel_modify_parent, 1, channel, true); if ((order ? order->channelId() : 0) != channel->channelOrder()) PERM_CHECK_CHANNELR(permission::b_channel_modify_sortorder, 1, channel, true); { auto min_channel_deep = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_min_depth, nullptr, nullptr); auto max_channel_deep = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_channel_max_depth, nullptr, nullptr); if(min_channel_deep >= 0 || max_channel_deep >= 0) { auto channel_deep = 0; auto local_parent = l_parent; while(local_parent) { channel_deep++; local_parent = local_parent->parent.lock(); } if(min_channel_deep >= 0 && channel_deep < min_channel_deep) return CommandResultPermissionError{permission::i_channel_min_depth}; if(max_channel_deep >= 0 && channel_deep > max_channel_deep) return CommandResultPermissionError{permission::i_channel_max_depth}; } } { auto name = channel_tree->findChannel(channel->name(), parent); if(name && name != channel) return {findError("channel_invalid_name"), "Channel with this name already exists"}; } 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 {findError("channel_invalid_order"), "Cant change order id"}; deque> channel_type_updates; { auto flag_default = channel->defaultChannel(); auto current_channel = channel; do { if(flag_default) { if(current_channel->channelType() != ChannelType::permanent) { current_channel->setChannelType(ChannelType::permanent); channel_type_updates.push_front(current_channel); } } else if(current_channel->hasParent()) { if(current_channel->channelType() < current_channel->parent()->channelType()) { current_channel->setChannelType(current_channel->parent()->channelType()); channel_type_updates.push_front(current_channel); } } } while ((current_channel = dynamic_pointer_cast(current_channel->parent()))); } if(this->server) { 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, {"channel_flag_permanent", "channel_flag_semi_permanent"}, this); auto actions = client->channels->change_order(l_channel, l_parent, l_order); std::deque deletions; for(const auto& action : actions) { switch (action.first) { case ClientChannelView::NOTHING: continue; case ClientChannelView::ENTER_VIEW: client->notifyChannelShow(action.second->channel(), action.second->previous_channel); break; case ClientChannelView::DELETE_VIEW: deletions.push_back(action.second->channelId()); break; case ClientChannelView::MOVE: client->notifyChannelMoved(action.second->channel(), action.second->previous_channel, _this.lock()); break; case ClientChannelView::REORDER: client->notifyChannelEdited(action.second->channel(), vector{"channel_order"}, _this.lock().get()); break; } } if(!deletions.empty()) client->notifyChannelHide(deletions, false); }); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientPoke(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto client = this->server->findClient(cmd["clid"].as()); if (!client) return {findError("client_invalid_id"), "invalid client id"}; if (client->getType() == CLIENT_MUSIC) return {findError("client_invalid_type"), "You cant poke a music bot!"}; CACHED_PERM_CHECK(permission::i_client_poke_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_poke_power, client->currentChannel), false); client->notifyClientPoke(_this.lock(), cmd["msg"]); return CommandResult::Success; } CommandResult 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); CHANNEL_PERM_TEST_INIT; CHANNEL_PERM_TEST(permission::b_virtualserver_channel_permission_list, 1, true); 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_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::resolvePermissionData(std::get<0>(permission_data))->name; } 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::resolvePermissionData(std::get<0>(permission_data))->grant_name; } 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 {ErrorType::DBEmpty}; this->sendCommand(result); return CommandResult::Success; } // //channel_icon_id=18446744073297259750 //channel_name //channel_topic //Desctiption has no extra parm CommandResult 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); CHANNEL_PERMISSION_TEST(permission::i_channel_permission_modify_power, permission::i_channel_needed_permission_modify_power, channel, true); auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, channel); bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, channel); auto updateClients = false, update_view = false, update_channel_properties = false; CommandResult command_result = CommandResult::Success; bool conOnError = cmd[0].has("continueonerror"); auto permission_manager = channel->permissions(); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); auto val = cmd[index]["permvalue"].as(); if(permission_require_granted_value(permType) && val > maxValue) { command_result = CommandResultPermissionError{permission::i_permission_modify_power}; break; } if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, channel)) { command_result = CommandResultPermissionError{permission::i_permission_modify_power}; break; } if (grant) { permission_manager->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); } else { permission_manager->set_permission( permType, {cmd[index]["permvalue"], 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing, cmd[index]["permskip"].as() ? 1 : 0, cmd[index]["permnegated"].as() ? 1 : 0 ); updateClients |= permission_is_client_property(permType); update_view |= permType == permission::i_channel_needed_view_power; update_channel_properties |= channel->permission_require_property_update(permType); if (permType == permission::i_icon_id) { if(this->server) this->server->forEachClient([&](std::shared_ptr cl) { shared_lock client_channel_lock(cl->channel_lock); cl->notifyChannelEdited(channel, {"channel_icon_id"}, this); }); continue; } } } /* broadcast the updated channel properties */ if(update_channel_properties) { auto updates = channel->update_properties_from_permissions(); if(!updates.empty() && this->server){ vector keys; keys.reserve(updates.size()); for(auto& property : updates) keys.push_back(property::info(property)->name); this->server->forEachClient([&](std::shared_ptr cl) { shared_lock client_channel_lock(cl->channel_lock); cl->notifyChannelEdited(channel, keys, this); }); } } if(updateClients && this->server) for(const auto& client : this->server->getClientsByChannel(channel)) { /* let them lock the server channel tree as well (read lock so does not matter) */ client->updateChannelClientProperties(true, true); } if(update_view && this->server) this->server->forEachClient([&](const shared_ptr& cl) { /* server tree read lock still active */ auto l_source = cl->server->channelTree->findLinkedChannel(channel->channelId()); auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId()); sassert(l_source); if(cl->currentChannel) sassert(l_target); { unique_lock client_channel_lock(cl->channel_lock); deque deleted; for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) { if(update_entry.first) cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); else deleted.push_back(update_entry.second->channelId()); } if(!deleted.empty()) cl->notifyChannelHide(deleted, false); } }); return command_result; } CommandResult ConnectedClient::handleCommandChannelDelPerm(Command &cmd) { CMD_RESET_IDLE; RESOLVE_CHANNEL_R(cmd["cid"], true) auto channel = dynamic_pointer_cast(l_channel->entry); assert(channel); CHANNEL_PERMISSION_TEST(permission::i_channel_permission_modify_power, permission::i_channel_needed_permission_modify_power, channel, true); bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, channel); bool conOnError = cmd[0].has("continueonerror"); auto updateClients = false, update_view = false, update_channel_properties = false; auto permission_manager = channel->permissions(); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, channel)) return CommandResultPermissionError{permission::i_permission_modify_power}; if (grant) { permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); } else { permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing); updateClients |= permission_is_client_property(permType); update_view |= permType == permission::i_channel_needed_view_power; update_channel_properties |= channel->permission_require_property_update(permType); } } /* broadcast the updated channel properties */ if(update_channel_properties) { auto updates = channel->update_properties_from_permissions(); if(!updates.empty() && this->server){ vector keys; keys.reserve(updates.size()); for(auto& property : updates) keys.push_back(property::info(property)->name); this->server->forEachClient([&](std::shared_ptr cl) { shared_lock client_channel_lock(cl->channel_lock); cl->notifyChannelEdited(channel, keys, this); }); } } if(updateClients && this->server) this->server->forEachClient([&](std::shared_ptr cl) { if(cl->currentChannel == channel) cl->updateChannelClientProperties(true, true); }); if(update_view && this->server) { this->server->forEachClient([&](std::shared_ptr cl) { /* server tree read lock still active */ auto l_source = cl->server->channelTree->findLinkedChannel(channel->channelId()); auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId()); sassert(l_source); if(cl->currentChannel) sassert(l_target); { unique_lock client_channel_lock(cl->channel_lock); deque deleted; for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) { if(update_entry.first) cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel); else deleted.push_back(update_entry.second->channelId()); } if(!deleted.empty()) cl->notifyChannelHide(deleted, false); } }); } return CommandResult::Success; } //servergroupadd name=TestGroup type=1 CommandResult ConnectedClient::handleCommandServerGroupAdd(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_servergroup_create, 1, true); if(cmd["name"].string().empty()) return {findError("parameter_invalid"), "invalid group name"}; if(cmd["type"].as() == GroupType::GROUP_TYPE_QUERY) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_querygroup), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_querygroup}; } else if(cmd["type"].as() == GroupType::GROUP_TYPE_TEMPLATE) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_templates), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_templates}; } else if(!this->server) return {findError("parameter_invalid"), "you cant create normal groups on the template server!"}; auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); for(const auto& gr : group_manager->availableServerGroups(true)) if(gr->name() == cmd["name"].string() && gr->target() == GroupTarget::GROUPTARGET_SERVER) return {findError("parameter_invalid"), "Group already exists"}; auto group = group_manager->createGroup(GroupTarget::GROUPTARGET_SERVER, cmd["type"].as(), cmd["name"].string()); if (group) { group->permissions()->set_permission(permission::b_group_is_permanent, {1,0}, permission::v2::set_value, permission::v2::do_nothing); if(this->server) this->server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); } else return {ErrorType::VSError, "Could not create group"}; return CommandResult::Success; } //name=Server\sAdmin\s(Copy) ssgid=1 tsgid=0 type=1 CommandResult ConnectedClient::handleCommandServerGroupCopy(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_servergroup_create, 1, true); auto group_manager = this->server ? this->server->groups : serverInstance->getGroupManager().get(); bool global = false; auto src = group_manager->findGroup(cmd["ssgid"].as()); if (!src || src->target() != GROUPTARGET_SERVER) return {findError("parameter_invalid"), "invalid server group id"}; if(cmd[0].has("tsgid") && cmd["tsgid"].as() != 0) { auto target = group_manager->findGroup(cmd["tsgid"].as()); if (!target || target->target() != GROUPTARGET_SERVER) return {findError("parameter_invalid"), "invalid target server group id"}; auto result = group_manager->copyGroupPermissions(src, target); if(!result) return {findError("vs_critical"), "could not copy group!"}; global = !this->server || group_manager->isLocalGroup(target); } else { //GroupType auto type = cmd["type"].as(); if(type == GroupType::GROUP_TYPE_NORMAL && !this->server) return {findError("parameter_invalid"), "You cant create normal groups on the template server!"}; if(type == GroupType::GROUP_TYPE_QUERY) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_querygroup), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_querygroup}; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_templates), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_templates}; } auto result = group_manager->copyGroup(src, type, cmd["name"], type != GroupType::GROUP_TYPE_NORMAL ? 0 : this->getServerId()); //TODO maybe check by name? No duplicated groups? if (!result) return {findError("vs_critical"), "could not copy group!"}; global = !this->server || type != GroupType::GROUP_TYPE_NORMAL; if(this->getType() == ClientType::CLIENT_QUERY) { Command notify(""); notify["sgid"] = group_manager->availableServerGroups(false).back()->groupId(); this->sendCommand(notify); } } for(const auto& server : (global ? serverInstance->getVoiceServerManager()->serverInstances() : deque>{this->server})) if(server) server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); return CommandResult::Success; } //servergrouprename sgid=2 name=Operators CommandResult ConnectedClient::handleCommandServerGroupRename(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); auto serverGroup = group_manager->findGroup(cmd["sgid"].as()); if (!serverGroup || serverGroup->target() != GROUPTARGET_SERVER) return {findError("parameter_invalid"), "invalid server group id"}; GROUP_PERMISSION_TEST(permission::i_server_group_modify_power, permission::i_server_group_needed_modify_power, serverGroup, true); auto type = serverGroup->type(); if(type == GroupType::GROUP_TYPE_QUERY) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_querygroup), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_querygroup}; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_templates), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_templates}; } group_manager->renameGroup(serverGroup, cmd["name"].string()); if(this->server) this->server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); return CommandResult::Success; } //servergroupdel sgid=2 force=0 CommandResult ConnectedClient::handleCommandServerGroupDel(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_servergroup_delete, 1, true); auto group_manager = this->server ? this->server->getGroupManager() : serverInstance->getGroupManager().get(); auto serverGroup = group_manager->findGroup(cmd["sgid"].as()); if (!serverGroup || serverGroup->target() != GROUPTARGET_SERVER) return {findError("parameter_invalid"), "invalid server group id"}; if(this->server && this->server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] == serverGroup->groupId()) return {findError("parameter_invalid"), "Could not delete default server group!"}; if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP] == serverGroup->groupId()) return {findError("parameter_invalid"), "Could not delete instance default server admin group!"}; if(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP] == serverGroup->groupId()) return {findError("parameter_invalid"), "Could not delete instance default server group!"}; if(serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] == serverGroup->groupId()) return {findError("parameter_invalid"), "Could not delete instance default guest server query group!"}; auto type = serverGroup->type(); if(type == GroupType::GROUP_TYPE_QUERY) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_querygroup), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_querygroup}; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_templates), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_templates}; } if (!cmd["force"].as()) if (!group_manager->listGroupMembers(serverGroup, false).empty()) return {findError("database_empty_result"), "group not empty!"}; if (group_manager->deleteGroup(serverGroup)) { if(this->server) this->server->forEachClient([&](shared_ptr cl) { if(this->server->notifyClientPropertyUpdates(cl, this->server->groups->update_server_group_property(cl, true))) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ } cl->notifyServerGroupList(); }); } return CommandResult::Success; } //servergroupclientlist sgid=2 //notifyservergroupclientlist sgid=6 cldbid=2 client_nickname=WolverinDEV client_unique_identifier=xxjnc14LmvTk+Lyrm8OOeo4tOqw= CommandResult ConnectedClient::handleCommandServerGroupClientList(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_servergroup_client_list, 1, true); auto server = cmd[0].has("sid") && cmd["sid"] == 0 ? nullptr : this->server; auto groupManager = server ? this->server->groups : serverInstance->getGroupManager().get(); auto serverGroup = groupManager->findGroup(cmd["sgid"].as()); if (!serverGroup || serverGroup->target() != GROUPTARGET_SERVER) return {findError("parameter_invalid"), "invalid server group id"}; Command notify(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyservergroupclientlist" : ""); notify["sgid"] = cmd["sgid"].as(); int index = 0; for (const auto &clientEntry : groupManager->listGroupMembers(serverGroup)) { notify[index]["cldbid"] = clientEntry->cldbId; notify[index]["client_nickname"] = clientEntry->displayName; notify[index]["client_unique_identifier"] = clientEntry->uid; index++; } this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandServerGroupAddClient(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto server = cmd[0].has("sid") && cmd["sid"] == 0 ? nullptr : this->server; auto groupManager = server ? this->server->groups : serverInstance->getGroupManager().get(); auto serverGroup = groupManager->findGroup(cmd["sgid"].as()); if (!serverGroup) return {findError("parameter_invalid"), "invalid server group id"}; if((server != this->server || !this->server) && serverGroup->target() != GroupTarget::GROUPTARGET_SERVER && serverGroup->type() != GroupType::GROUP_TYPE_QUERY) return {findError("parameter_invalid"), "invalid group type"}; auto target_cldbid = cmd["cldbid"].as(); { if(!serverGroup->permission_granted(permission::i_server_group_needed_member_add_power, this->calculate_permission_value(permission::i_server_group_member_add_power, -1), true)) { if(target_cldbid != this->getClientDatabaseId()) return CommandResultPermissionError{permission::i_server_group_member_add_power}; if(!serverGroup->permission_granted(permission::i_server_group_needed_member_add_power, this->calculate_permission_value(permission::i_server_group_self_add_power, -1), true)) return CommandResultPermissionError{permission::i_server_group_self_add_power}; } auto needed_client_permission = this->server->calculatePermission(permission::PERMTEST_ORDERED, target_cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK,nullptr); if(needed_client_permission != permNotGranted) { if(!this->permission_granted(this->permissionValue(permission::i_client_permission_modify_power), needed_client_permission)) return CommandResultPermissionError{permission::i_client_needed_permission_modify_power}; } } if (!serverInstance->databaseHelper()->validClientDatabaseId(server, cmd["cldbid"])) return {findError("client_invalid_id"), "invalid cldbid"}; if(groupManager->hasServerGroupAssigned(cmd["cldbid"], serverGroup)) return {findError("parameter_invalid"), "Client is already member of this group"}; groupManager->addServerGroup(target_cldbid, serverGroup); for(const auto& _server : server ? std::deque>{server} : serverInstance->getVoiceServerManager()->serverInstances()) { for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) { if (_server->notifyClientPropertyUpdates(targetClient, _server->groups->update_server_group_property(targetClient,true))) { for (const auto &client : _server->getClients()) { if(client->isClientVisible(targetClient, true) || client == targetClient) client->notifyServerGroupClientAdd(_this.lock(), targetClient, serverGroup); } if(targetClient->update_cached_permissions()) /* update cached calculated permissions */ targetClient->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ targetClient->updateChannelClientProperties(true, true); } } } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandServerGroupDelClient(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto server = cmd[0].has("sid") && cmd["sid"] == 0 ? nullptr : this->server; auto groupManager = server ? this->server->groups : serverInstance->getGroupManager().get(); auto serverGroup = groupManager->findGroup(cmd["sgid"].as()); if (!serverGroup) return {findError("parameter_invalid"), "invalid server group id"}; if((server != this->server || !this->server) && serverGroup->target() != GroupTarget::GROUPTARGET_SERVER && serverGroup->type() != GroupType::GROUP_TYPE_QUERY) return {findError("parameter_invalid"), "invalid group type"}; auto target_cldbid = cmd["cldbid"].as(); { if(!serverGroup->permission_granted(permission::i_server_group_needed_member_remove_power, this->calculate_permission_value(permission::i_server_group_member_remove_power, -1), true)) { if(target_cldbid != this->getClientDatabaseId()) return CommandResultPermissionError{permission::i_server_group_member_remove_power}; if(!serverGroup->permission_granted(permission::i_server_group_needed_member_remove_power, this->calculate_permission_value(permission::i_server_group_self_remove_power, -1), true)) return CommandResultPermissionError{permission::i_server_group_self_remove_power}; } auto needed_client_permission = this->server->calculatePermission(permission::PERMTEST_ORDERED, target_cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK,nullptr); if(needed_client_permission != permNotGranted) { if(!this->permission_granted(this->permissionValue(permission::i_client_permission_modify_power), needed_client_permission)) return CommandResultPermissionError{permission::i_client_needed_permission_modify_power}; } } if (!serverInstance->databaseHelper()->validClientDatabaseId(server, cmd["cldbid"])) return {findError("client_invalid_id"), "invalid cldbid"}; if(!groupManager->hasServerGroupAssigned(cmd["cldbid"], serverGroup)) return {findError("parameter_invalid"), "Client isn't a member of this group"}; for(const auto& assignment : groupManager->listGroupAssignments(cmd["cldbid"])) if(assignment->group == serverGroup && assignment->server != this->getServerId()) return {findError("parameter_invalid"), "Group wasn't assigned over this server (Assigned server: " + to_string(assignment->server) + ")"}; groupManager->removeServerGroup(target_cldbid, serverGroup); for(const auto& _server : server ? std::deque>{server} : serverInstance->getVoiceServerManager()->serverInstances()) { for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) { if (_server->notifyClientPropertyUpdates(targetClient, _server->groups->update_server_group_property(targetClient, true))) { for (const auto &client : _server->getClients()) { if (client->isClientVisible(targetClient, true) || client == targetClient) client->notifyServerGroupClientRemove(_this.lock(), targetClient, serverGroup); } if(targetClient->update_cached_permissions()) /* update cached calculated permissions */ targetClient->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ targetClient->updateChannelClientProperties(true, true); } } } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandServerGroupPermList(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_servergroup_permission_list, 1, true); auto serverGroup = (this->server ? this->server->groups : serverInstance->getGroupManager().get())->findGroup(cmd["sgid"].as()); if (!serverGroup) return {findError("parameter_invalid"), "invalid server group id"}; if(this->getType() == ClientType::CLIENT_TEAMSPEAK && this->command_times.last_notify + this->command_times.notify_timeout < system_clock::now()) { this->sendTSPermEditorWarning(); } if (!this->notifyGroupPermList(serverGroup, cmd.hasParm("permsid"))) return {findError("database_empty_result"), "empty"}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto serverGroup = (this->server ? this->server->groups : serverInstance->getGroupManager().get())->findGroup(cmd["sgid"].as()); if (!serverGroup) return {findError("parameter_invalid"), "invalid server group id"}; if (serverGroup->target() != GROUPTARGET_SERVER) return {findError("parameter_invalid"), "invalid group type"}; GROUP_PERMISSION_TEST(permission::i_server_group_modify_power, permission::i_server_group_needed_modify_power, serverGroup, true); auto type = serverGroup->type(); if(type == GroupType::GROUP_TYPE_QUERY) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_querygroup), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_querygroup}; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_templates), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_templates}; } auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); bool ignoreGrant = this->permission_granted(this->cached_permission_value(permission::b_permission_modify_power_ignore), 1); bool conOnError = cmd[0].has("continueonerror"); bool checkTp = false; bool sgroupUpdate = false; auto permissions = serverGroup->permissions(); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); //permvalue='1' permnegated='0' permskip='0' auto val = cmd[index]["permvalue"].as(); if(permission_require_granted_value(permType) && val > maxValue) { if(conOnError) continue; return CommandResultPermissionError{permission::i_permission_modify_power}; } if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) { if(conOnError) continue; return CommandResultPermissionError{permission::i_permission_modify_power}; } if (grant) { permissions->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); } else { permissions->set_permission( permType, {cmd[index]["permvalue"], 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing, cmd[index]["permskip"].as() ? 1 : 0, cmd[index]["permnegated"].as() ? 1 : 0 ); sgroupUpdate |= permission_is_group_property(permType); checkTp |= permission_is_client_property(permType); } } if(sgroupUpdate) serverGroup->apply_properties_from_permissions(); //TODO may update for every server? if(this->server) { auto lock = this->_this.lock(); auto server = this->server; threads::Thread([checkTp, sgroupUpdate, serverGroup, lock, server]() { if(sgroupUpdate) server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); server->forEachClient([serverGroup, checkTp](shared_ptr cl) { if (cl->serverGroupAssigned(serverGroup)) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if (checkTp) cl->updateChannelClientProperties(true, true); } }); }).detach(); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto serverGroup = (this->server ? this->server->groups : serverInstance->getGroupManager().get())->findGroup(cmd["sgid"].as()); if (!serverGroup) return {findError("parameter_invalid"), "invalid server group id"}; if (serverGroup->target() != GROUPTARGET_SERVER) return {findError("parameter_invalid"), "invalid group type"}; GROUP_PERMISSION_TEST(permission::i_server_group_modify_power, permission::i_server_group_needed_modify_power, serverGroup, true); auto type = serverGroup->type(); if(type == GroupType::GROUP_TYPE_QUERY) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_querygroup), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_querygroup}; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_templates), 1, true)) return CommandResultPermissionError{permission::b_serverinstance_modify_templates}; } bool ignoreGrant = this->permission_granted(this->cached_permission_value(permission::b_permission_modify_power_ignore), 1); bool conOnError = cmd[0].has("continueonerror"); bool checkTp = false; auto sgroupUpdate = false; for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) { if(conOnError) continue; return CommandResultPermissionError{permission::i_permission_modify_power}; } if (grant) { serverGroup->permissions()->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); } else { serverGroup->permissions()->set_permission( permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing ); sgroupUpdate |= permission_is_group_property(permType); checkTp |= permission_is_client_property(permType); } } if(sgroupUpdate) serverGroup->apply_properties_from_permissions(); if(this->server) { auto lock = this->_this.lock(); auto server = this->server; threads::Thread([checkTp, sgroupUpdate, serverGroup, lock, server]() { if(sgroupUpdate) server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); server->forEachClient([serverGroup, checkTp](shared_ptr cl) { if (cl->serverGroupAssigned(serverGroup)) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if (checkTp) cl->updateChannelClientProperties(true, true); } }); }).detach(); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); deque> groups; for(const auto& group : this->server->groups->availableGroups(false)) { if(group->updateType() == cmd["sgtype"].as() && group->target() == GROUPTARGET_SERVER) { if(group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission_value(permission::i_server_group_modify_power, 0), true)) { auto type = group->type(); if(type == GroupType::GROUP_TYPE_QUERY) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_querygroup), 1, true)) continue; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_templates), 1, true)) continue; } groups.push_back(group);//sgtype } } } if(groups.empty()) return CommandResult::Success; auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); bool ignoreGrant = this->permission_granted(this->cached_permission_value(permission::b_permission_modify_power_ignore), 1); bool conOnError = cmd[0].has("continueonerror"); bool checkTp = false; bool sgroupUpdate = false; for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); //permvalue='1' permnegated='0' permskip='0'end auto val = cmd[index]["permvalue"].as(); if(permission_require_granted_value(permType) && val > maxValue) { if(conOnError) continue; return CommandResultPermissionError{permission::i_permission_modify_power}; } if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) { if(conOnError) continue; return CommandResultPermissionError{permission::i_permission_modify_power}; } for(const auto& serverGroup : groups) { if (grant) { serverGroup->permissions()->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value); } else { serverGroup->permissions()->set_permission( permType, {cmd[index]["permvalue"], 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing, cmd[index]["permskip"].as() ? 1 : 0, cmd[index]["permnegated"].as() ? 1 : 0 ); } } sgroupUpdate |= permission_is_group_property(permType); checkTp |= permission_is_client_property(permType); } if(sgroupUpdate) for(auto& group : groups) group->apply_properties_from_permissions(); auto lock = this->_this.lock(); auto server = this->server; threads::Thread([checkTp, sgroupUpdate, groups, lock, server]() { if(sgroupUpdate) server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); server->forEachClient([groups, checkTp](shared_ptr cl) { for(const auto& serverGroup : groups) { if (cl->serverGroupAssigned(serverGroup)) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if (checkTp) cl->updateChannelClientProperties(true, true); break; } } }); }).detach(); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); deque> groups; for(const auto& group : this->server->groups->availableGroups(false)) { if(group->updateType() == cmd["sgtype"].as() && group->target() == GROUPTARGET_SERVER) { if(group->permission_granted(permission::i_server_group_needed_modify_power, this->calculate_permission_value(permission::i_server_group_modify_power, 0), true)) { auto type = group->type(); if(type == GroupType::GROUP_TYPE_QUERY) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_querygroup), 1, true)) continue; } else if(type == GroupType::GROUP_TYPE_TEMPLATE) { if(!this->permission_granted(this->cached_permission_value(permission::b_serverinstance_modify_templates), 1, true)) continue; } groups.push_back(group);//sgtype } } } if(groups.empty()) return CommandResult::Success; bool ignoreGrant = this->permission_granted(this->cached_permission_value(permission::b_permission_modify_power_ignore), 1); bool conOnError = cmd[0].has("continueonerror"); bool checkTp = false; auto sgroupUpdate = false; for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) { if(conOnError) continue; return CommandResultPermissionError{permission::i_permission_modify_power}; } for(const auto& serverGroup : groups) { if (grant) { serverGroup->permissions()->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value); } else { serverGroup->permissions()->set_permission( permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing ); sgroupUpdate |= permission_is_group_property(permType); } } checkTp |= permission_is_client_property(permType); } if(sgroupUpdate) for(auto& group : groups) group->apply_properties_from_permissions(); auto lock = this->_this.lock(); auto server = this->server; threads::Thread([checkTp, sgroupUpdate, groups, lock, server]() { if(sgroupUpdate) server->forEachClient([](shared_ptr cl) { cl->notifyServerGroupList(); }); server->forEachClient([groups, checkTp](shared_ptr cl) { for(const auto& serverGroup : groups) { if (cl->serverGroupAssigned(serverGroup)) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if (checkTp) cl->updateChannelClientProperties(true, true); break; } } }); }).detach(); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto serverGroup = this->server->groups->findGroup(cmd["cgid"].as()); if (!serverGroup && cmd["gcid"].as() == 0) serverGroup = this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL); if (!serverGroup || serverGroup->target() != GROUPTARGET_CHANNEL) return {findError("parameter_invalid"), "invalid channel group id"}; shared_lock server_channel_lock(this->server->channel_tree_lock); /* ensure we dont get moved or somebody could move us */ std::shared_ptr channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; auto target_cldbid = cmd["cldbid"].as(); { if(!serverGroup->permission_granted(permission::i_channel_group_member_add_power, this->calculate_permission_value(permission::i_channel_group_member_add_power, -1), true)) { if(target_cldbid != this->getClientDatabaseId()) return CommandResultPermissionError{permission::i_channel_group_member_add_power}; if(!serverGroup->permission_granted(permission::i_channel_group_member_add_power, this->calculate_permission_value(permission::i_channel_group_self_add_power, -1), true)) return CommandResultPermissionError{permission::i_channel_group_self_add_power}; } auto needed_client_permission = this->server->calculatePermission(permission::PERMTEST_ORDERED, target_cldbid, permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK,nullptr); if(needed_client_permission != permNotGranted) { if(!this->permission_granted(this->permissionValue(permission::i_client_permission_modify_power), needed_client_permission)) return CommandResultPermissionError{permission::i_client_needed_permission_modify_power}; } } auto oldGroup = this->server->groups->getChannelGroupExact(target_cldbid, channel, false); if(oldGroup) { if(!serverGroup->permission_granted(permission::i_channel_group_member_remove_power, this->calculate_permission_value(permission::i_channel_group_member_remove_power, -1), true)) { if(target_cldbid != this->getClientDatabaseId()) return CommandResultPermissionError{permission::i_channel_group_member_remove_power}; if(!serverGroup->permission_granted(permission::i_channel_group_member_remove_power, this->calculate_permission_value(permission::i_channel_group_self_remove_power, -1), true)) return CommandResultPermissionError{permission::i_channel_group_self_remove_power}; } } this->server->groups->setChannelGroup(target_cldbid, serverGroup, channel); for (const auto &targetClient : this->server->findClientsByCldbId(target_cldbid)) { unique_lock client_channel_lock_w(targetClient->channel_lock); auto updates = this->server->groups->update_server_group_property(targetClient, false); /* needs a write lock */ client_channel_lock_w.unlock(); shared_lock client_channel_lock_r(targetClient->channel_lock); auto result = this->server->notifyClientPropertyUpdates(targetClient, updates); if (result) { if(targetClient->update_cached_permissions()) /* update cached calculated permissions */ targetClient->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if(targetClient->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] == channel->channelId()) { //Only if group assigned over the channel for (const auto &viewer : this->server->getClients()) { /* if in view will be tested within that method */ shared_lock viewer_channel_lock(viewer->channel_lock, defer_lock); if(viewer != targetClient) viewer_channel_lock.lock(); viewer->notifyClientChannelGroupChanged(_this.lock(), targetClient, targetClient->getChannel(), channel, serverGroup, false); } } } targetClient->updateChannelClientProperties(false, true); } return CommandResult::Success; } //sendtextmessage targetmode=1 <1 = direct | 2 = channel | 3 = server> msg=asd target=1 CommandResult ConnectedClient::handleCommandSendTextMessage(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); if (cmd["targetmode"].as() == ChatMessageMode::TEXTMODE_PRIVATE) { auto target = this->server->findClient(cmd["target"].as()); if (!target) return {findError("client_invalid_id"), "invalid target clid"}; bool chat_open = false; { shared_lock channel_lock(this->channel_lock); this->openChats.erase(remove_if(this->openChats.begin(), this->openChats.end(), [](const weak_ptr& weak) { return !weak.lock(); }), this->openChats.end()); for(const auto& entry : this->openChats) { if(entry.lock() == target) { chat_open = true; break; } } } if(!chat_open) { if (target == this) { PERM_CHECK_CHANNELR(permission::b_client_even_textmessage_send, 1, this->currentChannel, true); } PERM_CHECK_CHANNELR(permission::i_client_private_textmessage_power, target->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_private_textmessage_power, target->currentChannel), this->currentChannel, false); { unique_lock channel_lock(target->channel_lock); target->openChats.push_back(_this); } { unique_lock channel_lock(this->channel_lock); this->openChats.push_back(target); } } if(this->handleTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, cmd["msg"], target)) return CommandResult::Success; target->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), cmd["msg"].string()); this->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), cmd["msg"].string()); } else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_CHANNEL) { CMD_REQ_CHANNEL; CACHED_PERM_CHECK(permission::b_client_channel_textmessage_send, 1, false); if(this->handleTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, cmd["msg"], nullptr)) return CommandResult::Success; for (auto &cl : this->server->getClientsByChannel(this->currentChannel)) cl->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, _this.lock(), this->getClientId(), cmd["msg"].string()); } else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_SERVER) { CACHED_PERM_CHECK(permission::b_client_server_textmessage_send, 1); if(this->handleTextMessage(ChatMessageMode::TEXTMODE_SERVER, cmd["msg"], nullptr)) return CommandResult::Success; this->server->forEachClient([&](shared_ptr client) { client->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, _this.lock(), this->getClientId(), cmd["msg"].string()); }); } else return {findError("parameter_invalid"), "invalid target mode"}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientChatComposing(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(0); auto client = this->server->findClient(cmd["clid"].as()); if (!client) return {findError("client_invalid_id"), "invalid client id"}; client->notifyClientChatComposing(_this.lock()); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientChatClosed(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto client = this->server->findClient(cmd["clid"].as()); if (!client) return {findError("client_invalid_id"), "invalid client id"}; { unique_lock channel_lock(this->channel_lock); this->openChats.erase(remove_if(this->openChats.begin(), this->openChats.end(), [client](const weak_ptr& weak) { return weak.lock() == client; }), this->openChats.end()); } { unique_lock channel_lock(client->channel_lock); client->openChats.erase(remove_if(client->openChats.begin(), client->openChats.end(), [&](const weak_ptr& weak) { return weak.lock().get() == this; }), client->openChats.end()); } client->notifyClientChatClosed(_this.lock()); return CommandResult::Success; } //ftgetfilelist cid=1 cpw path=\/ return_code=1:x //Answer: //1 .. n // notifyfilelist cid=1 path=\/ return_code=1:x name=testFile size=35256 datetime=1509459767 type=1|name=testDir size=0 datetime=1509459741 type=0|name=testDir_2 size=0 datetime=1509459763 type=0 //notifyfilelistfinished cid=1 path=\/ inline void appendFileList(Command &fileList, vector> files) { int index = 0; for (const auto& fileEntry : files) { debugMessage(lstream << "Having file " << fileEntry->path << "/" << fileEntry->name << " (Name: " << fileEntry->name << ")" << endl); fileList[index]["name"] = fileEntry->name; fileList[index]["datetime"] = std::chrono::duration_cast(fileEntry->lastChanged.time_since_epoch()).count(); fileList[index]["type"] = fileEntry->type; if (fileEntry->type == file::FileType::FILE) fileList[index]["size"] = static_pointer_cast(fileEntry)->fileSize; else fileList[index]["size"] = 0; index++; } } #define CMD_REQ_FSERVER if(!serverInstance->getFileServer()) return {findError("vs_critical"), "file server not started yet!"} CommandResult ConnectedClient::handleCommandFTGetFileList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_REQ_FSERVER; std::string code = cmd["return_code"].size() > 0 ? cmd["return_code"].string() : ""; Command fileList(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyfilelist" : ""); Command fileListFinished("notifyfilelistfinished"); fileList["path"] = cmd["path"].as(); fileList["return_code"] = code; fileListFinished["path"] = cmd["path"].as(); fileList["cid"] = cmd["cid"].as(); fileListFinished["cid"] = cmd["cid"].as(); if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? CommandResultPermissionError{permission::b_ft_ignore_password} : CommandResult{findError("channel_invalid_password")}; CHANNEL_PERMISSION_TEST(permission::i_ft_file_browse_power, permission::i_ft_needed_file_browse_power, channel, true); appendFileList(fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->resolveDirectory(this->server, channel, cmd["path"].as()))); } else { if (cmd["path"].as() == "/icons" || cmd["path"].as() == "/icons/") { appendFileList(fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->iconDirectory(this->server))); PERM_CHECKR(permission::b_icon_manage, 1, true); } else if (cmd["path"].as() == "/") { appendFileList(fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->avatarDirectory(this->server))); PERM_CHECKR(permission::b_icon_manage, 1, true); } else { cerr << "Unknown requested directory: '" << cmd["path"].as() << "'" << endl; return CommandResult::NotImplemented; } } if (fileList[0].has("name")) { if(dynamic_cast(this)) { dynamic_cast(this)->sendCommand0(fileList, false, true); /* We need to process this directly else the order could get shuffeled up! */ this->sendCommand(fileListFinished); } else { this->sendCommand(fileList); } return CommandResult::Success; } else { return {findError("database_empty_result"), "empty"}; } } //ftcreatedir cid=4 cpw dirname=\/TestDir return_code=1:17 CommandResult ConnectedClient::handleCommandFTCreateDir(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); CMD_REQ_FSERVER; auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? CommandResultPermissionError{permission::b_ft_ignore_password} : CommandResult{findError("channel_invalid_password")}; CHANNEL_PERMISSION_TEST(permission::i_ft_directory_create_power, permission::i_ft_needed_directory_create_power, channel, true); auto dir = serverInstance->getFileServer()->createDirectory(cmd["dirname"], serverInstance->getFileServer()->resolveDirectory(this->server, channel, cmd["path"].as())); if (!dir) return {findError("file_invalid_path"), "could not create dir"}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandFTDeleteFile(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); CMD_REQ_FSERVER; std::vector> files; if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? CommandResultPermissionError{permission::b_ft_ignore_password} : CommandResult{findError("channel_invalid_password")}; CHANNEL_PERMISSION_TEST(permission::i_ft_file_delete_power, permission::i_ft_needed_file_delete_power, channel, true); for (int index = 0; index < cmd.bulkCount(); index++) files.push_back(serverInstance->getFileServer()->findFile(cmd[index]["name"].as(), serverInstance->getFileServer()->resolveDirectory(this->server, channel))); } else { if (cmd["name"].string().find("/icon_") == 0 && cmd["path"].string().empty()) { PERM_CHECKR(permission::b_icon_manage, 1, true); files.push_back(serverInstance->getFileServer()->findFile(cmd["name"].string(), serverInstance->getFileServer()->iconDirectory(this->server))); } else if (cmd["name"].string().find("/avatar_") == 0 && cmd["path"].string().empty()) { if (cmd["name"].string() != "/avatar_") { PERM_CHECKR(permission::b_client_avatar_delete_other, 1, true); auto uid = cmd["name"].string().substr(strlen("/avatar_")); auto avId = hex::hex(base64::decode(uid), 'a', 'q'); auto cls = this->server->findClientsByUid(uid); for (const auto &cl : cls) { cl->properties()[property::CLIENT_FLAG_AVATAR] = ""; this->server->notifyClientPropertyUpdates(cl, deque{property::CLIENT_FLAG_AVATAR}); } cmd["name"] = "/avatar_" + avId; } else { cmd["name"] = "/avatar_" + this->getAvatarId(); this->properties()[property::CLIENT_FLAG_AVATAR] = ""; this->server->notifyClientPropertyUpdates(_this.lock(), deque{property::CLIENT_FLAG_AVATAR}); } files.push_back(serverInstance->getFileServer()->findFile(cmd["name"].string(), serverInstance->getFileServer()->avatarDirectory(this->server))); } else { cerr << "Unknown requested file to delete: " << cmd["path"].as() << endl; return CommandResult::NotImplemented; } } for (const auto &file : files) { if (!file) continue; if (!serverInstance->getFileServer()->deleteFile(file)) { logCritical(this->getServerId(), lstream << "Cound not delete file " << file->path << "/" << file->name); } } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandFTInitUpload(Command &cmd) { CMD_REQ_SERVER; CMD_REQ_FSERVER; std::shared_ptr directory = nullptr; if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? CommandResultPermissionError{permission::b_ft_ignore_password} : CommandResult{findError("channel_invalid_password")}; CHANNEL_PERMISSION_TEST(permission::i_ft_file_upload_power, permission::i_ft_needed_file_upload_power, channel, true); directory = serverInstance->getFileServer()->resolveDirectory(this->server, channel); } else { if (cmd["path"].as().empty() && cmd["name"].as().find("/icon_") == 0) { auto max_size = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_max_icon_filesize, this->currentChannel); if(max_size != -1 && max_size < (ssize_t) cmd["size"].as()) return CommandResultPermissionError{permission::i_max_icon_filesize}; directory = serverInstance->getFileServer()->iconDirectory(this->server); } else if (cmd["path"].as().empty() && cmd["name"].string() == "/avatar") { auto max_size = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_max_avatar_filesize, this->currentChannel); if(max_size != -1 && max_size < (ssize_t) cmd["size"].as()) return CommandResultPermissionError{permission::i_client_max_avatar_filesize}; directory = serverInstance->getFileServer()->avatarDirectory(this->server); cmd["name"] = "/avatar_" + this->getAvatarId(); } else { cerr << "Unknown requested directory: " << cmd["path"].as() << endl; return CommandResult::NotImplemented; } } if (!directory || directory->type != file::FileType::DIRECTORY) { //Should not happen cerr << "Invalid upload file path!" << endl; return {findError("file_invalid_path"), "could not resolve directory"}; } { auto server_quota = this->server->properties()[property::VIRTUALSERVER_UPLOAD_QUOTA].as(); auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as(); server_used_quota += cmd["size"].as(); for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers()) server_used_quota += trans->size; for(const auto& trans : serverInstance->getFileServer()->running_file_transfers()) server_used_quota += trans->remaining_bytes(); if(server_quota >= 0 && server_quota * 1024 * 1024 < (int64_t) server_used_quota) return {findError("file_transfer_server_quota_exceeded")}; auto client_quota = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_ft_quota_mb_upload_per_client, this->currentChannel); auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].as(); client_used_quota += cmd["size"].as(); for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers(_this.lock())) client_used_quota += trans->size; for(const auto& trans : serverInstance->getFileServer()->running_file_transfers(_this.lock())) client_used_quota += trans->remaining_bytes(); if(client_quota >= 0 && client_quota * 1024 * 1024 < (int64_t) client_used_quota) return {findError("file_transfer_client_quota_exceeded")}; } if (cmd["overwrite"].as() && cmd["resume"].as()) return {findError("file_overwrite_excludes_resume"), "funny"}; if (serverInstance->getFileServer()->findFile(cmd["name"].as(), directory) && !(cmd["overwrite"].as() || cmd["resume"].as())) return {findError("file_already_exists"), "file already exists"}; //Request: clientftfid=1 serverftfid=6 ftkey=itRNdsIOvcBiBg\/Xj4Ge51ZSrsShHuid port=30033 seekpos=0 //Reqpose: notifystartupload clientftfid=4079 serverftfid=1 ftkey=aX9CFQbfaddHpOYxhQiSLu\/BumfVtPyP port=30033 seekpos=0 proto=1 string error = "success"; auto key = serverInstance->getFileServer()->generateUploadTransferKey(error, directory->path + "/" + directory->name + cmd["name"].string(), cmd["size"].as(), 0, _this.lock()); //TODO implement resume! if (!key) return {findError(""), "cant generate key"}; //TODO insert error! Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifystartupload" : ""); result["clientftfid"] = cmd["clientftfid"].as(); result["ftkey"] = key->key; result["port"] = ntohs(serverInstance->getFileServer()->boundedAddress()->sin_port); if(serverInstance->getFileServer()->boundedAddress()->sin_addr.s_addr != 0) result["ip"] = inet_ntoa(serverInstance->getFileServer()->boundedAddress()->sin_addr) + string(","); result["seekpos"] = 0; result["proto"] = 1; result["serverftfid"] = key->key_id; //TODO generate! this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandFTInitDownload(Command &cmd) { CMD_REQ_SERVER; CMD_REQ_FSERVER; std::shared_ptr directory = nullptr; if(!cmd[0].has("path")) cmd["path"] = ""; if (cmd[0].has("cid") && cmd["cid"] != (ChannelId) 0) { //Channel auto channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; CHANNEL_PERMISSION_TEST(permission::i_ft_file_download_power, permission::i_ft_needed_file_download_power, channel, true); if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? CommandResultPermissionError{permission::b_ft_ignore_password} : CommandResult{findError("channel_invalid_password")}; directory = serverInstance->getFileServer()->resolveDirectory(this->server, channel); } else { if (cmd["path"].as().empty() && cmd["name"].as().find("/icon_") == 0) { directory = serverInstance->getFileServer()->iconDirectory(this->server); } else if (cmd["path"].as().empty() && cmd["name"].as().find("/avatar_") == 0) { //TODO fix avatar download not working directory = serverInstance->getFileServer()->avatarDirectory(this->server); } else { cerr << "Unknown requested directory: " << cmd["path"].as() << endl; return CommandResult::NotImplemented; } } if (!directory || directory->type != file::FileType::DIRECTORY) { //Should not happen cerr << "Invalid download file path!" << endl; return {findError("file_invalid_path"), "could not resolve directory"}; } if (!serverInstance->getFileServer()->findFile(cmd["name"].as(), directory)) return {findError("file_not_found"), "file not exists"}; string error = "success"; auto key = serverInstance->getFileServer()->generateDownloadTransferKey(error, directory->path + "/" + directory->name + cmd["name"].as(), 0, _this.lock()); //TODO implement resume! if (!key) return {findError("vs_critical"), "cant generate key (" + error + ")"}; { auto server_quota = this->server->properties()[property::VIRTUALSERVER_DOWNLOAD_QUOTA].as(); auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as(); server_used_quota += key->size; for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers()) server_used_quota += trans->size; for(const auto& trans : serverInstance->getFileServer()->running_file_transfers()) server_used_quota += trans->remaining_bytes(); if(server_quota >= 0 && server_quota * 1024 * 1024 < (int64_t) server_used_quota) return {findError("file_transfer_server_quota_exceeded")}; auto client_quota = this->permissionValue(permission::PERMTEST_ORDERED, permission::i_ft_quota_mb_download_per_client, this->currentChannel); auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); client_used_quota += key->size; for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers(_this.lock())) client_used_quota += trans->size; for(const auto& trans : serverInstance->getFileServer()->running_file_transfers(_this.lock())) client_used_quota += trans->remaining_bytes(); if(client_quota >= 0 && client_quota * 1024 * 1024 < (int64_t) client_used_quota) return {findError("file_transfer_client_quota_exceeded")}; } Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifystartdownload" : ""); result["clientftfid"] = cmd["clientftfid"].as(); result["proto"] = 1; result["serverftfid"] = key->key_id; result["ftkey"] = key->key; result["port"] = ntohs(serverInstance->getFileServer()->boundedAddress()->sin_port); if(serverInstance->getFileServer()->boundedAddress()->sin_addr.s_addr != 0) result["ip"] = inet_ntoa(serverInstance->getFileServer()->boundedAddress()->sin_addr) + string(","); result["size"] = key->size; this->sendCommand(result); return CommandResult::Success; } /* * Usage: ftgetfileinfo cid={channelID} cpw={channelPassword} name={filePath}... Permissions: i_ft_file_browse_power i_ft_needed_file_browse_power Description: Displays detailed information about one or more specified files stored in a channels file repository. Example: ftgetfileinfo cid=2 cpw= path=\/Pic1.PNG|cid=2 cpw= path=\/Pic2.PNG cid=2 path=\/ name=Stuff size=0 datetime=1259415210 type=0|name=Pic1.PNG size=563783 datetime=1259425462 type=1|name=Pic2.PNG ... error id=0 msg=ok */ CommandResult ConnectedClient::handleCommandFTGetFileInfo(ts::Command &cmd) { CMD_REQ_SERVER; CMD_REQ_FSERVER; CMD_RESET_IDLE; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyfileinfo" : ""); int result_index = 0; for(int index = 0; index < cmd.bulkCount(); index++) { auto& request = cmd[index]; std::shared_ptr file; if (request.has("cid") && request["cid"].as() != 0) { //Channel auto channel = this->server->channelTree->findChannel(request["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; if (!channel->passwordMatch(cmd["cpw"]) && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_ft_ignore_password, 1, channel, true)) return cmd["cpw"].string().empty() ? CommandResultPermissionError{permission::b_ft_ignore_password} : CommandResult{findError("channel_invalid_password"), ""}; CHANNEL_PERMISSION_TEST(permission::i_ft_file_browse_power, permission::i_ft_needed_file_browse_power, channel, true); file = serverInstance->getFileServer()->findFile(request["name"],serverInstance->getFileServer()->resolveDirectory(this->server, channel, request["path"])); } else { std::shared_ptr directory; if (!request.has("path") || request["path"].as() == "/") { directory = serverInstance->getFileServer()->avatarDirectory(this->server); } else if (request["path"].as() == "/icons" || request["path"].as() == "/icons/") { directory = serverInstance->getFileServer()->iconDirectory(this->server); } else { cerr << "Unknown requested directory: '" << request["path"].as() << "'" << endl; return CommandResult::NotImplemented; } if(!directory) continue; file = serverInstance->getFileServer()->findFile(cmd["name"].as(), directory); } if(!file) continue; result[result_index]["cid"] = request["cid"].as(); result[result_index]["name"] = request["name"]; result[result_index]["path"] = request["path"]; result[result_index]["type"] = file->type; result[result_index]["datetime"] = duration_cast(file->lastChanged.time_since_epoch()).count(); if (file->type == file::FileType::FILE) result[result_index]["size"] = static_pointer_cast(file)->fileSize; else result[result_index]["size"] = 0; result_index++; } if(result_index == 0) return {findError("database_empty_result")}; this->sendCommand(result); return CommandResult::Success; } //notifybanlist banid=3 ip name uid=zbex8X3bFRTIKLI7mzeyJGZsh64= lastnickname=Wolf\sC++\sXXXX created=1510357269 duration=3600 invokername=WolverinDEV invokercldbid=5 invokeruid=xxjnc14LmvTk+Lyrm8OOeo4tOqw= reason=Prefix\sFake\s\p\sName enforcements=3 CommandResult ConnectedClient::handleCommandBanList(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); ServerId sid = this->getServerId(); if (cmd[0].has("sid")) sid = cmd["sid"]; if (sid == 0) { if(!this->permission_granted(this->cached_permission_value(permission::b_client_ban_list_global), 1)) return CommandResultPermissionError{permission::b_client_ban_list_global}; } else { auto server = serverInstance->getVoiceServerManager()->findServerById(sid); if (!server) return {findError("parameter_invalid"), ""}; if (server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_client_ban_list, this->getType(), nullptr) != 1) return CommandResultPermissionError{permission::b_client_ban_list}; } //When empty: return {findError("database_empty_result"), "empty"}; auto banList = serverInstance->banManager()->listBans(sid); if (banList.empty()) return {findError("database_empty_result")}; auto allow_ip = this->permission_granted(this->cached_permission_value(permission::b_client_remoteaddress_view), 1); Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifybanlist" : ""); int index = 0; for (const auto &elm : banList) { notify[index]["sid"] = elm->serverId; notify[index]["banid"] = elm->banId; if(allow_ip) notify[index]["ip"] = elm->ip; else notify[index]["ip"] = "hidden"; notify[index]["name"] = elm->name; notify[index]["uid"] = elm->uid; notify[index]["hwid"] = elm->hwid; notify[index]["lastnickname"] = elm->name; //Maybe update? notify[index]["created"] = chrono::duration_cast(elm->created.time_since_epoch()).count(); if (elm->until.time_since_epoch().count() != 0) notify[index]["duration"] = chrono::duration_cast(elm->until - elm->created).count(); else notify[index]["duration"] = 0; notify[index]["reason"] = elm->reason; notify[index]["enforcements"] = elm->triggered; notify[index]["invokername"] = elm->invokerName; notify[index]["invokercldbid"] = elm->invokerDbId; notify[index]["invokeruid"] = elm->invokerUid; index++; } this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandBanAdd(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); string ip = cmd[0].has("ip") ? cmd["ip"].string() : ""; string name = cmd[0].has("name") ? cmd["name"].string() : ""; string uid = cmd[0].has("uid") ? cmd["uid"].string() : ""; string hwid = cmd[0].has("hwid") ? cmd["hwid"].string() : ""; string banreason = cmd[0].has("banreason") ? cmd["banreason"].string() : "No reason set"; auto time = cmd[0].has("time") ? cmd["time"].as() : 0L; ServerId sid = this->getServerId(); if (cmd[0].has("sid")) sid = cmd["sid"]; if (sid == 0) { if(!this->permission_granted(this->cached_permission_value(permission::b_client_ban_create_global), 1)) return CommandResultPermissionError{permission::b_client_ban_create_global}; } else { auto server = serverInstance->getVoiceServerManager()->findServerById(sid); if (!server) return {findError("parameter_invalid"), ""}; if (server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_client_ban_create, this->getType(), nullptr) != 1) return CommandResultPermissionError{permission::b_client_ban_create_global}; } auto max_ban_time = this->cached_permission_value(permission::i_client_ban_max_bantime); if (max_ban_time != permNotGranted) { if (max_ban_time != -1 && max_ban_time < time) return CommandResultPermissionError(permission::i_client_ban_max_bantime); } chrono::time_point until = time > 0 ? chrono::system_clock::now() + chrono::seconds(time) : chrono::time_point(); auto existing = serverInstance->banManager()->findBanExact(sid, banreason, uid, ip, name, hwid); bool banned = false; if(existing) { if(existing->invokerDbId == this->getClientDatabaseId()) { if(existing->until == until) return {findError("database_duplicate_entry")}; else { existing->until = until; serverInstance->banManager()->updateBan(existing); banned = true; } } else if(!banned) { serverInstance->banManager()->unban(existing); } } if(!banned) serverInstance->banManager()->registerBan(sid, this->getClientDatabaseId(), banreason, uid, ip, name, hwid, until); for(auto server : (this->server ? std::deque>{this->server} : serverInstance->getVoiceServerManager()->serverInstances())) server->testBanStateChange(_this.lock()); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandBanEdit(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); ServerId sid = this->getServerId(); if (cmd[0].has("sid")) sid = cmd["sid"]; auto ban = serverInstance->banManager()->findBanById(sid, cmd["banid"].as()); if (!ban) return {findError("database_empty_result"), "empty"}; if (sid == 0) { if(!this->permission_granted(this->cached_permission_value(permission::b_client_ban_edit_global), 1)) return CommandResultPermissionError{permission::b_client_ban_edit_global}; } else { auto server = serverInstance->getVoiceServerManager()->findServerById(sid); if (!server) return {findError("parameter_invalid"), ""}; if (server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), permission::b_client_ban_edit, this->getType(), nullptr) != 1) return CommandResultPermissionError{permission::b_client_ban_edit}; } /* ip name uid reason time hwid */ bool changed = false; if (cmd[0].has("ip")) { ban->ip = cmd["ip"].as(); changed |= true; } if (cmd[0].has("name")) { ban->name = cmd["name"].as(); changed |= true; } if (cmd[0].has("uid")) { ban->uid = cmd["uid"].as(); changed |= true; } if (cmd[0].has("reason")) { ban->reason = cmd["reason"].as(); changed |= true; } if (cmd[0].has("banreason")) { ban->reason = cmd["banreason"].as(); changed |= true; } if (cmd[0].has("time")) { ban->until = ban->created + seconds(cmd["time"].as()); changed |= true; } if (cmd[0].has("hwid")) { ban->hwid = cmd["hwid"].as(); changed |= true; } if (changed) serverInstance->banManager()->updateBan(ban); else return {findError("parameter_invalid"), ""}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandBanClient(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); PERM_CHECKR(permission::i_client_ban_power, 1, true); //Require a little ban power string uid; string reason = cmd[0].has("banreason") ? cmd["banreason"].string() : ""; auto time = cmd[0].has("time") ? cmd["time"].as() : 0UL; chrono::time_point until = time > 0 ? chrono::system_clock::now() + chrono::seconds(time) : chrono::time_point(); const auto no_nickname = cmd.hasParm("no-nickname"); const auto no_hwid = cmd.hasParm("no-hardware-id"); const auto no_ip = cmd.hasParm("no-ip"); deque> target_clients; if (cmd[0].has("uid")) { target_clients = this->server->findClientsByUid(uid = cmd["uid"].string()); for(const auto& client : target_clients) if(client->getType() == ClientType::CLIENT_MUSIC) return {findError("client_invalid_id"), "You cant ban a music bot!"}; } else { target_clients = {this->server->findClient(cmd["clid"].as())}; if(!target_clients[0]) { return {findError("client_invalid_id"), "Could not find target client"}; } if(target_clients[0]->getType() == ClientType::CLIENT_MUSIC) { return {findError("client_invalid_id"), "You cant ban a music bot!"}; } uid = target_clients[0]->getUid(); } ClientDbId target_dbid = 0; if (!target_clients.empty()) target_dbid = target_clients[0]->getClientDatabaseId(); else { auto info = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->getServer(), {uid}); if (!info.empty()) target_dbid = info[0]->cldbid; else return {findError("client_unknown")}; } if (target_dbid != 0) { if (this->server->calculatePermission(permission::PERMTEST_ORDERED, target_dbid, permission::i_client_needed_ban_power, ClientType::CLIENT_TEAMSPEAK, nullptr) > this->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_ban_power)) return CommandResultPermissionError(permission::i_client_ban_power); if (this->server->calculatePermission(permission::PERMTEST_ORDERED, target_dbid, permission::b_client_ignore_bans, ClientType::CLIENT_TEAMSPEAK, nullptr) >= 1) return CommandResultPermissionError(permission::b_client_ignore_bans); } deque ban_ids; auto _id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, uid, "", "", "", until); ban_ids.push_back(_id); auto b_ban_name = this->cached_permission_value(permission::b_client_ban_name) == permNotGranted || this->permission_granted(this->cached_permission_value(permission::b_client_ban_name), 1, false); auto b_ban_ip = this->cached_permission_value(permission::b_client_ban_ip) == permNotGranted || this->permission_granted(this->cached_permission_value(permission::b_client_ban_ip), 1, false); auto b_ban_hwid = this->cached_permission_value(permission::b_client_ban_hwid) == permNotGranted || this->permission_granted(this->cached_permission_value(permission::b_client_ban_hwid), 1, false); auto max_value = this->cached_permission_value(permission::i_client_ban_max_bantime); if(max_value != permNotGranted && max_value != -1) { if(time > max_value) return CommandResultPermissionError{permission::i_client_ban_max_bantime}; if(time == 0) return CommandResultPermissionError{permission::i_client_ban_max_bantime}; } for (const auto &client : target_clients) { if (client->getType() != CLIENT_TEAMSPEAK && client->getType() != CLIENT_QUERY) continue; //Remember if you add new type you have to change stuff here this->server->notify_client_ban(client, this->ref(), reason, time); client->closeConnection(system_clock::now() + seconds(2)); string entry_name, entry_ip, entry_hardware_id; if(b_ban_name && !no_nickname) { entry_name = client->getDisplayName(); } if(b_ban_ip && !no_ip && !config::server::disable_ip_saving) { entry_ip = client->getPeerIp(); } if(b_ban_hwid && !no_hwid) { entry_hardware_id = client->getHardwareId(); } auto exact = serverInstance->banManager()->findBanExact(this->getServer()->getServerId(), reason, "", entry_ip, entry_name, entry_hardware_id); if(exact) { exact->until = until; exact->invokerDbId = this->getClientDatabaseId(); serverInstance->banManager()->updateBan(exact); ban_ids.push_back(exact->banId); } else { auto id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, "", entry_ip, entry_name, entry_hardware_id, until); ban_ids.push_back(id); } } this->server->testBanStateChange(_this.lock()); if (this->getType() == CLIENT_QUERY) { Command notify(""); int index = 0; for(const auto& ban_id : ban_ids) notify[index++]["banid"] = ban_id; this->sendCommand(notify); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandBanDel(Command &cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); ServerId sid = this->getServerId(); if (cmd[0].has("sid")) sid = cmd["sid"]; auto ban = serverInstance->banManager()->findBanById(sid, cmd["banid"].as()); if (!ban) return {findError("database_empty_result"), "empty"}; if (sid == 0) { const auto permission = ban->invokerDbId == this->getClientDatabaseId() ? permission::b_client_ban_delete_own_global : permission::b_client_ban_delete_global; if(!this->permission_granted(this->cached_permission_value(permission), 1)) return CommandResultPermissionError{permission}; } else { auto server = serverInstance->getVoiceServerManager()->findServerById(sid); if (!server) return {findError("parameter_invalid"), ""}; auto perm = ban->invokerDbId == this->getClientDatabaseId() ? permission::b_client_ban_delete_own : permission::b_client_ban_delete; if (server->calculatePermission(permission::PERMTEST_ORDERED, this->getClientDatabaseId(), perm, this->getType(), nullptr) != 1) return CommandResultPermissionError{perm}; } serverInstance->banManager()->unban(ban); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandBanDelAll(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); CACHED_PERM_CHECK(permission::b_client_ban_delete, 1, true); serverInstance->banManager()->deleteAllBans(server->getServerId()); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandBanTriggerList(ts::Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); CACHED_PERM_CHECK(permission::b_client_ban_trigger_list, 1, true); CMD_REQ_PARM("banid"); auto record = serverInstance->banManager()->findBanById(this->getServerId(), cmd["banid"]); if(!record) return {findError("parameter_invalid"), "Invalid ban id"}; Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifybantriggerlist" : ""); notify["banid"] = record->banId; notify["serverid"] = record->serverId; auto allow_ip = this->permission_granted(this->cached_permission_value(permission::b_client_remoteaddress_view), 1); int index = 0; for (auto &entry : serverInstance->banManager()->trigger_list(record, this->getServerId(), cmd[0].has("offset") ? cmd["offset"].as() : 0, cmd[0].has("limit") ? cmd["limit"].as() : -1)) { notify[index]["client_unique_identifier"] = entry->unique_id; notify[index]["client_hardware_identifier"] = entry->hardware_id; notify[index]["client_nickname"] = entry->name; if(allow_ip) notify[index]["connection_client_ip"] = entry->ip; else notify[index]["connection_client_ip"] = "hidden"; notify[index]["timestamp"] = duration_cast(entry->timestamp.time_since_epoch()).count(); index++; } if (index == 0) return {findError("database_empty_result")}; this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandTokenList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_token_list, 1, true); Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifytokenlist" : ""); int index = 0; for (auto &token : this->server->tokenManager->avariableTokes()) { notify[index]["token"] = token->token; notify[index]["token_type"] = token->type; notify[index]["token_id1"] = token->groupId; notify[index]["token_id2"] = token->channelId; notify[index]["token_created"] = chrono::duration_cast(token->created.time_since_epoch()).count(); notify[index]["token_description"] = token->description; index++; } if (index == 0) return {findError("database_empty_result"), "empty!"}; this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandTokenAdd(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_token_add, 1, true); TokenType ttype = static_cast(cmd["tokentype"].as()); auto gId = cmd["tokenid1"].as(); auto cId = cmd["tokenid2"].as(); string description = cmd["tokendescription"].string(); { auto group = this->server->groups->findGroup(gId); if(!group) return {findError("parameter_invalid"), "invalid server group id"}; if(group->type() == GroupType::GROUP_TYPE_TEMPLATE) return {findError("parameter_invalid"), "invalid server group type"}; if(group->target() == GroupTarget::GROUPTARGET_SERVER) GROUP_PERMISSION_TEST(permission::i_server_group_member_add_power, permission::i_server_group_needed_member_add_power, group, true); else GROUP_PERMISSION_TEST(permission::i_channel_group_member_add_power, permission::i_channel_group_needed_member_add_power, group, true); } if (ttype == TokenType::TOKEN_CHANNEL) { if (!this->server->channelTree->findChannel(cId)) return {findError("channel_invalid_id"), "Cant resolve channel"}; } else if (ttype == TokenType::TOKEN_SERVER); else return {findError("parameter_invalid"), "invalid token target type"}; auto result = this->server->tokenManager->createToken(ttype, gId, description, cId, cmd["token"]); if (!result) return {ErrorType::Success, "internal error"}; Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifytokenadd" : ""); notify["token"] = result->token; this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandTokenUse(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_token_use, 1, true); auto strToken = cmd["token"].string(); auto token = this->server->tokenManager->findToken(strToken); if (!token) return {findError("token_invalid_id"), "Invalid token. (Token not registered)"}; this->server->tokenManager->deleteToke(token->token); auto serverGroup = this->server->groups->findGroup(token->groupId); if (!serverGroup) return {findError("token_invalid_id"), "Token invalid groupId"}; this->server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; //TODO test if its the default token std::shared_ptr channel = nullptr; if (token->channelId != 0) { channel = this->server->channelTree->findChannel(token->channelId); if (!channel) return {findError("token_invalid_id"), "Token invalid channelId"}; this->server->groups->setChannelGroup(this->getClientDatabaseId(), serverGroup, channel); } else { if(!this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), serverGroup)) this->server->groups->addServerGroup(this->getClientDatabaseId(), serverGroup); else { return CommandResult::Success; } } if (this->server->notifyClientPropertyUpdates(_this.lock(), this->server->groups->update_server_group_property(_this.lock(), true))) { if(this->update_cached_permissions()) /* update cached calculated permissions */ this->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ { for (auto &viewer : this->server->getClients()) { if(viewer->isClientVisible(_this.lock(), true)) viewer->notifyServerGroupClientAdd(this->server->serverRoot, _this.lock(), serverGroup); } } this->notifyServerGroupClientAdd(this->server->serverRoot, _this.lock(), serverGroup); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandTokenDelete(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); CACHED_PERM_CHECK(permission::b_virtualserver_token_delete, 1, true); auto strToken = cmd["token"].string(); auto token = this->server->tokenManager->findToken(strToken); if (!token) return {findError("token_invalid_id"), "Invalid token. (Token not registered)"}; this->server->tokenManager->deleteToke(token->token); if(token->token == this->server->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY].as()) { this->server->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = ""; this->server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; logMessage(this->getServerId(), "{} Deleting the default server token. Don't ask anymore for this a token!", CLIENT_STR_LOG_PREFIX); } return CommandResult::Success; } //start=0 duration=10 //pattern=%asd% struct ClientDbArgs { shared_ptr server; int index = 0; int offset = 0; int resultIndex = 0; bool showIp = false; bool largeInfo = false; Command *result = nullptr; }; CommandResult ConnectedClient::handleCommandClientDbList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); CACHED_PERM_CHECK(permission::b_virtualserver_client_dblist, 1, true); Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdblist" : ""); if (!cmd[0].has("start")) cmd["start"] = 0; if (!cmd[0].has("duration")) cmd["duration"] = 20; if (cmd[0]["duration"].as() > 2000) cmd["duration"] = 2000; if (cmd[0]["duration"].as() < 1) cmd["duration"] = 1; auto maxIndex = cmd["start"].as() + cmd["duration"].as(); ClientDbArgs args; args.server = this->server; args.offset = cmd["start"].as(); args.result = ¬ify; args.resultIndex = 0; args.index = 0; args.showIp = this->permission_granted(this->cached_permission_value(permission::b_client_remoteaddress_view), 1); args.largeInfo = cmd.hasParm("details"); (LOG_SQL_CMD)(sql::command(this->server->getSql(), "SELECT * FROM `clients` WHERE `serverId` = :sid ORDER BY `cldbid` ASC" + (maxIndex > 0 ? " LIMIT " + to_string(maxIndex) : ""), variable{":sid", this->server->getServerId()}).query( [](ClientDbArgs *pArgs, int length, char **values, char **column) { pArgs->index++; if (pArgs->offset < pArgs->index) { ClientDbId id = 0; string uid, name, ip; string created = "0", lastConnected = "0", connections = "0"; for (int index = 0; index < length; index++) { string key = column[index]; if (key == "cldbid") id = stoll(values[index]); else if (key == "clientUid") uid = values[index]; else if (key == "firstConnect") created = values[index]; else if (key == "lastConnect") lastConnected = values[index]; else if (key == "connections") connections = values[index]; else if (key == "lastName") name = values[index]; } pArgs->result->operator[](pArgs->resultIndex)["cldbid"] = id; pArgs->result->operator[](pArgs->resultIndex)["client_unique_identifier"] = uid; pArgs->result->operator[](pArgs->resultIndex)["client_nickname"] = name; pArgs->result->operator[](pArgs->resultIndex)["client_created"] = created; pArgs->result->operator[](pArgs->resultIndex)["client_lastconnected"] = lastConnected; pArgs->result->operator[](pArgs->resultIndex)["client_totalconnections"] = connections; pArgs->result->operator[](pArgs->resultIndex)["client_description"] = ""; auto props = serverInstance->databaseHelper()->loadClientProperties(pArgs->server, id, ClientType::CLIENT_TEAMSPEAK); if (props) { pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as(); pArgs->result->operator[](pArgs->resultIndex)["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as(); if (pArgs->largeInfo) { pArgs->result->operator[](pArgs->resultIndex)["client_badges"] = (*props)[property::CLIENT_BADGES].as(); pArgs->result->operator[](pArgs->resultIndex)["client_version"] = (*props)[property::CLIENT_VERSION].as(); pArgs->result->operator[](pArgs->resultIndex)["client_platform"] = (*props)[property::CLIENT_PLATFORM].as(); pArgs->result->operator[](pArgs->resultIndex)["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as(); } } if (!pArgs->showIp) pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = "hidden"; pArgs->resultIndex++; } return 0; }, &args)); if (args.resultIndex == 0) return {findError("database_empty_result"), "empty!"}; if (cmd.hasParm("count")) { size_t result = 0; sql::command(this->server->getSql(), "SELECT COUNT(*) AS `count` FROM `clients` WHERE `serverId` = :sid", variable{":sid", this->server->getServerId()}).query([](size_t *ptr, int, char **v, char **) { *ptr = static_cast(stoll(v[0])); return 0; }, &result); notify[0]["count"] = result; } this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientDBEdit(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); PERM_CHECKR(permission::b_client_modify_dbproperties, 1, true); if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return {findError("database_empty_result"), "invalid cldbid"}; auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK); for (auto &elm : cmd[0].keys()) { if (elm == "cldbid") continue; auto info = property::info(elm); if(*info == property::CLIENT_UNDEFINED) { logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change someone's db entry, but the entry in unknown: " + elm); continue; } if(!info->validate_input(cmd[elm].as())) { logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[elm].as() + "', Property: '" + info->name + "')"); continue; } (*props)[info] = cmd[elm].string(); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPluginCmd(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto mode = cmd["targetmode"].as(); if (mode == PluginTargetMode::PLUGINCMD_CURRENT_CHANNEL) { CMD_REQ_CHANNEL; for (auto &cl : this->server->getClientsByChannel(this->currentChannel)) cl->notifyPluginCmd(cmd["name"], cmd["data"], _this.lock()); } else if (mode == PluginTargetMode::PLUGINCMD_SUBSCRIBED_CLIENTS) { for (auto &cl : this->server->getClients()) if (cl->isClientVisible(_this.lock(), true)) cl->notifyPluginCmd(cmd["name"], cmd["data"], _this.lock()); } else if (mode == PluginTargetMode::PLUGINCMD_SERVER) { for (auto &cl : this->server->getClients()) cl->notifyPluginCmd(cmd["name"], cmd["data"], _this.lock()); } else if (mode == PluginTargetMode::PLUGINCMD_CLIENT) { for (int index = 0; index < cmd.bulkCount(); index++) { auto target = cmd[index]["target"].as(); auto cl = this->server->findClient(target); if (!cl) return {findError("client_invalid_id"), "invalid target id"}; cl->notifyPluginCmd(cmd["name"], cmd["data"], _this.lock()); } } /* else if(mode == PluginTargetMode::PLUGINCMD_SEND_COMMAND) { auto target = _this.lock(); if(cmd[0].has("target")) target = this->server->findClient(cmd["target"].as()); if(!target) return {findError("client_invalid_id"), "invalid target id"}; target->sendCommand(Command(cmd["command"].string()), cmd[0].has("low") ? cmd["low"] : false); } */ else return CommandResult::NotImplemented; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientEdit(ts::Command &cmd) { CMD_REQ_SERVER; auto client = this->server->findClient(cmd["clid"].as()); if (!client) return {findError("client_invalid_id"), "invalid client id"}; return this->handleCommandClientEdit(cmd, client); } CommandResult ConnectedClient::handleCommandClientEdit(Command &cmd, const std::shared_ptr& client) { assert(client); auto self = client == this; CMD_CHK_AND_INC_FLOOD_POINTS(self ? 15 : 25); CMD_RESET_IDLE; bool update_talk_rights = false; unique_ptr> nickname_lock; deque> keys; for(const auto& key : cmd[0].keys()) { if(key == "return_code") continue; if(key == "clid") continue; const auto &info = property::info(key); if(*info == property::CLIENT_UNDEFINED) { logError(this->getServerId(), R"([{}] Tried to change a not existing client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string()); continue; } if((info->flags & property::FLAG_USER_EDITABLE) == 0) { logError(this->getServerId(), R"([{}] Tried to change a not user editable client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string()); continue; } if(!info->validate_input(cmd[key].as())) { logError(this->getServerId(), R"([{}] Tried to change a client property to an invalid value for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string()); continue; } if(client->properties()[info].as() == cmd[key].as()) continue; if (*info == property::CLIENT_DESCRIPTION) { if (self) PERM_CHECKR(permission::b_client_modify_own_description, 1, true); else if(client->getType() == ClientType::CLIENT_MUSIC) { if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); } } else { PERM_CHECKR(permission::b_client_modify_description, 1, true); } string value = cmd["client_description"].string(); if (count_characters(value) > 200) return {findError("parameter_invalid"), "Invalid description length. A maximum of 200 characters is allowed!"}; } else if (*info == property::CLIENT_IS_TALKER) { PERM_CHECK_CHANNELR(permission::b_client_set_flag_talker, 1, client->getChannel(), true); cmd["client_is_talker"] = cmd["client_is_talker"].as(); cmd["client_talk_request"] = 0; update_talk_rights = true; keys.emplace_back(property::CLIENT_IS_TALKER, "client_is_talker"); keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request"); continue; } else if(*info == property::CLIENT_NICKNAME) { if(!self) { if(client->getType() != ClientType::CLIENT_MUSIC) return {findError("client_invalid_type")}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::i_client_music_rename_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_rename_power, client->currentChannel), true); } } string name = cmd["client_nickname"].string(); if (count_characters(name) < 3) return {findError("parameter_invalid"), "Invalid name length. A minimum of 3 characters is required!"}; if (count_characters(name) > 30) return {findError("parameter_invalid"), "Invalid name length. A maximum of 30 characters is allowed!"}; auto banIgnore = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_ignore_bans, 1, this->currentChannel); if (!banIgnore) { auto banRecord = serverInstance->banManager()->findBanByName(this->getServerId(), name); if (banRecord) return {findError("client_nickname_inuse"), string() + "This nickname is " + (banRecord->serverId == 0 ? "globally " : "") + "banned for the reason: " + banRecord->reason}; } if (this->server) { nickname_lock = std::make_unique>(this->server->client_nickname_lock); bool self = false; for (const auto &cl : this->server->getClients()) { if (cl->getDisplayName() == cmd["client_nickname"].string()) { if(cl == this) self = true; else return {findError("client_nickname_inuse"), "This nickname is already in use"}; } } if(self) { nickname_lock.reset(); continue; } } } else if(*info == property::CLIENT_PLAYER_VOLUME) { if(client->getType() != ClientType::CLIENT_MUSIC) return {findError("client_invalid_type")}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); } auto bot = dynamic_pointer_cast(client); assert(bot); auto volume = cmd["player_volume"].as(); auto max_volume = this->cached_permission_value(permission::i_client_music_create_modify_max_volume); if(max_volume != permNotGranted && !this->permission_granted(max_volume, volume * 100)) return CommandResultPermissionError{permission::i_client_music_create_modify_max_volume}; bot->volume_modifier(cmd["player_volume"]); } else if(*info == property::CLIENT_IS_CHANNEL_COMMANDER) { if(!self) { if(client->getType() != ClientType::CLIENT_MUSIC) return {findError("client_invalid_type")}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); } } if(cmd["client_is_channel_commander"].as()) { CACHED_PERM_CHECK(permission::b_client_use_channel_commander, 1); } } else if(*info == property::CLIENT_IS_PRIORITY_SPEAKER) { //FIXME allow other to remove this thing if(!self) { if(client->getType() != ClientType::CLIENT_MUSIC) return {findError("client_invalid_type")}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); } } if(cmd["client_is_priority_speaker"].as()) { PERM_CHECK_CHANNELR(permission::b_client_use_priority_speaker, 1, this->currentChannel, true); } } else if (self && key == "client_talk_request") { CMD_CHK_AND_INC_FLOOD_POINTS(20); CACHED_PERM_CHECK(permission::b_client_request_talker, 1, true); if (cmd["client_talk_request"].as()) cmd["client_talk_request"] = duration_cast(system_clock::now().time_since_epoch()).count(); else cmd["client_talk_request"] = 0; keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request"); continue; } else if (self && key == "client_badges") { std::string str = cmd[key]; size_t index = 0; int badgesTags = 0; do { index = str.find("badges", index); if (index < str.length()) badgesTags++; index++; } while (index < str.length() && index != 0); if (badgesTags >= 2) { if (!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_allow_invalid_badges, 1, this->currentChannel)) ((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_badges, this->server ? this->server->serverAdmin : dynamic_pointer_cast(serverInstance->getInitalServerAdmin()), true); return {findError("parameter_invalid"), "Invalid badges"}; } //FIXME stuff here } else if(!self && key == "client_version") { if(client->getType() != ClientType::CLIENT_MUSIC) return {findError("client_invalid_type")}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); } } else if(!self && key == "client_platform") { if(client->getType() != ClientType::CLIENT_MUSIC) return {findError("client_invalid_type")}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); } } else if(!self && key == "client_country") { if(client->getType() != ClientType::CLIENT_MUSIC) return {findError("client_invalid_type")}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); } } else if(!self && (*info == property::CLIENT_FLAG_NOTIFY_SONG_CHANGE/* || *info == property::CLIENT_NOTIFY_SONG_MESSAGE*/)) { if(client->getType() != ClientType::CLIENT_MUSIC) return {findError("client_invalid_type")}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); } } else if(!self && key == "client_uptime_mode") { if(client->getType() != ClientType::CLIENT_MUSIC) return {findError("client_invalid_type")}; if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::i_client_music_modify_power, client->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_modify_power, client->currentChannel), true); } if(cmd[key].as() == MusicClient::UptimeMode::TIME_SINCE_SERVER_START) { cmd["client_lastconnected"] = duration_cast(this->server->startTimestamp.time_since_epoch()).count(); } else { string value = client->properties()[property::CLIENT_CREATED]; if(value.empty()) value = "0"; cmd["client_lastconnected"] = value; } keys.emplace_back(property::CLIENT_LASTCONNECTED, "client_lastconnected"); } else if(!self && *info == property::CLIENT_BOT_TYPE) { auto type = cmd["client_bot_type"].as(); if(type == MusicClient::Type::TEMPORARY) { CACHED_PERM_CHECK(permission::b_client_music_modify_temporary, 1, true); } else if(type == MusicClient::Type::SEMI_PERMANENT) { CACHED_PERM_CHECK(permission::b_client_music_modify_semi_permanent, 1, true); } else if(type == MusicClient::Type::PERMANENT) { CACHED_PERM_CHECK(permission::b_client_music_modify_permanent, 1, true); } else return {findError("parameter_invalid")}; } else if(!self) { /* dont edit random properties of other clients. For us self its allowed to edit the rest without permissions */ continue; } keys.emplace_back((property::ClientProperties) info->property_index, key); } deque updates; for(const auto& key : keys) { if(key.first == property::CLIENT_IS_PRIORITY_SPEAKER) { client->clientPermissions->set_permission(permission::b_client_is_priority_speaker, {1, 0}, cmd["client_is_priority_speaker"].as() ? permission::v2::PermissionUpdateType::set_value : permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing); } client->properties()[key.first] = cmd[0][key.second].value(); updates.push_back(key.first); } if(update_talk_rights) client->updateTalkRights(client->properties()[property::CLIENT_TALK_POWER]); if(this->server) this->server->notifyClientPropertyUpdates(client, updates); nickname_lock.reset(); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientUpdate(Command &cmd) { return this->handleCommandClientEdit(cmd, _this.lock()); } CommandResult ConnectedClient::handleCommandClientMute(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; auto client = this->server->findClient(cmd["clid"].as()); if (!client || client->getClientId() == this->getClientId()) return {findError("client_invalid_id"), "invalid client id"}; { unique_lock channel_lock(this->channel_lock); for(const auto& weak : this->mutedClients) if(weak.lock() == client) return CommandResult::Success; this->mutedClients.push_back(client); } if (config::voice::notifyMuted) client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), config::messages::mute_notify_message); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientUnmute(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; auto client = this->server->findClient(cmd["clid"].as()); if (!client || client->getClientId() == this->getClientId()) return {findError("client_invalid_id"), "invalid client id"}; { unique_lock channel_lock(this->channel_lock); this->mutedClients.erase(std::remove_if(this->mutedClients.begin(), this->mutedClients.end(), [client](const weak_ptr& weak) { auto c = weak.lock(); return !c || c == client; }), this->mutedClients.end()); } if (config::voice::notifyMuted) client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), config::messages::unmute_notify_message); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; bool allow_ip = false; if (cmd.hasParm("ip")) allow_ip = this->permission_granted(this->cached_permission_value(permission::b_client_remoteaddress_view), 1); Command result(""); int index = 0; this->server->forEachClient([&](shared_ptr client) { if (client->getType() == ClientType::CLIENT_INTERNAL) return; result[index]["clid"] = client->getClientId(); if (client->getChannel()) result[index]["cid"] = client->getChannel()->channelId(); else result[index]["cid"] = 0; result[index]["client_database_id"] = client->getClientDatabaseId(); result[index]["client_nickname"] = client->getDisplayName(); result[index]["client_type"] = client->getType(); if (cmd.hasParm("uid")) result[index]["client_unique_identifier"] = client->getUid(); if (cmd.hasParm("away")) { result[index]["client_away"] = client->properties()[property::CLIENT_AWAY].as(); result[index]["client_away_message"] = client->properties()[property::CLIENT_AWAY_MESSAGE].as(); } if (cmd.hasParm("groups")) { result[index]["client_channel_group_id"] = client->properties()[property::CLIENT_CHANNEL_GROUP_ID].as(); result[index]["client_servergroups"] = client->properties()[property::CLIENT_SERVERGROUPS].as(); result[index]["client_channel_group_inherited_channel_id"] = client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID].as(); } if (cmd.hasParm("times")) { result[index]["client_idle_time"] = duration_cast(system_clock::now() - client->idleTimestamp).count(); result[index]["client_total_online_time"] = client->properties()[property::CLIENT_TOTAL_ONLINE_TIME].as() + duration_cast(system_clock::now() - client->lastOnlineTimestamp).count(); result[index]["client_month_online_time"] = client->properties()[property::CLIENT_MONTH_ONLINE_TIME].as() + duration_cast(system_clock::now() - client->lastOnlineTimestamp).count(); result[index]["client_idle_time"] = duration_cast(system_clock::now() - client->idleTimestamp).count(); result[index]["client_created"] = client->properties()[property::CLIENT_CREATED].as(); result[index]["client_lastconnected"] = client->properties()[property::CLIENT_LASTCONNECTED].as(); } if (cmd.hasParm("info")) { result[index]["client_version"] = client->properties()[property::CLIENT_VERSION].as(); result[index]["client_platform"] = client->properties()[property::CLIENT_PLATFORM].as(); } if (cmd.hasParm("badges")) result[index]["client_badges"] = client->properties()[property::CLIENT_BADGES].as(); if (cmd.hasParm("country")) result[index]["client_country"] = client->properties()[property::CLIENT_COUNTRY].as(); if (cmd.hasParm("ip")) result[index]["connection_client_ip"] = allow_ip ? client->properties()[property::CONNECTION_CLIENT_IP].as() : "hidden"; if (cmd.hasParm("icon")) result[index]["client_icon_id"] = client->properties()[property::CLIENT_ICON_ID].as(); if (cmd.hasParm("voice")) { result[index]["client_talk_power"] = client->properties()[property::CLIENT_TALK_POWER].as(); result[index]["client_flag_talking"] = client->properties()[property::CLIENT_FLAG_TALKING].as(); result[index]["client_input_muted"] = client->properties()[property::CLIENT_INPUT_MUTED].as(); result[index]["client_output_muted"] = client->properties()[property::CLIENT_OUTPUT_MUTED].as(); result[index]["client_input_hardware"] = client->properties()[property::CLIENT_INPUT_HARDWARE].as(); result[index]["client_output_hardware"] = client->properties()[property::CLIENT_OUTPUT_HARDWARE].as(); result[index]["client_is_talker"] = client->properties()[property::CLIENT_IS_TALKER].as(); result[index]["client_is_priority_speaker"] = client->properties()[property::CLIENT_IS_PRIORITY_SPEAKER].as(); result[index]["client_is_recording"] = client->properties()[property::CLIENT_IS_RECORDING].as(); result[index]["client_is_channel_commander"] = client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as(); } index++; }); this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandWhoAmI(Command &cmd) { CMD_RESET_IDLE; Command result(""); if (this->server) { result["virtualserver_status"] = ServerState::string(this->getServer()->state); result["virtualserver_id"] = this->server->getServerId(); result["virtualserver_unique_identifier"] = this->server->properties()[property::VIRTUALSERVER_UNIQUE_IDENTIFIER].as(); result["virtualserver_port"] = 0; if (this->server->udpVoiceServer) { result["virtualserver_port"] = this->server->properties()[property::VIRTUALSERVER_PORT].as_save(); } } else { result["virtualserver_status"] = "template"; result["virtualserver_id"] = "0"; result["virtualserver_unique_identifier"] = ""; //TODO generate uid result["virtualserver_port"] = "0"; } result["client_id"] = this->getClientId(); result["client_channel_id"] = this->currentChannel ? this->currentChannel->channelId() : 0; result["client_nickname"] = this->getDisplayName(); result["client_database_id"] = this->getClientDatabaseId(); result["client_login_name"] = this->properties()[property::CLIENT_LOGIN_NAME].as(); result["client_unique_identifier"] = this->getUid(); { auto query = dynamic_cast(this); if(query) { auto account = query->getQueryAccount(); result["client_origin_server_id"] = account ? account->bound_server : 0; } else result["client_origin_server_id"] = 0; } this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandServerGroupsByClientId(Command &cmd) { CMD_RESET_IDLE; ClientDbId cldbid = cmd["cldbid"]; if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid)) return {findError("client_invalid_id"), "invalid client id"}; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyservergroupsbyclientid" : ""); int index = 0; if (this->server) { for (const auto &group : this->server->groups->getAssignedServerGroups(cldbid)) { result[index]["name"] = group->group->name(); result[index]["sgid"] = group->group->groupId(); result[index]["cldbid"] = cldbid; index++; } } else { for (const auto &group : serverInstance->getGroupManager()->getAssignedServerGroups(cldbid)) { result[index]["name"] = group->group->name(); result[index]["sgid"] = group->group->groupId(); result[index]["cldbid"] = cldbid; index++; } } if (index == 0) return {findError("database_empty_result"), "empty!"}; this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientGetDBIDfromUID(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; deque unique_ids; for(int index = 0; index < cmd.bulkCount(); index++) unique_ids.push_back(cmd[index]["cluid"].as()); auto res = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->server, unique_ids); if (res.empty()) return {findError("database_empty_result"), "empty!"}; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdbidfromuid" : ""); int result_index = 0; for(auto& info : res) { result[result_index]["cluid"] = info->uniqueId; result[result_index]["cldbid"] = info->cldbid; result_index++; } this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientGetNameFromDBID(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; deque dbids; for(int index = 0; index < cmd.bulkCount(); index++) dbids.push_back(cmd[index]["cldbid"].as()); auto res = serverInstance->databaseHelper()->queryDatabaseInfo(this->server, dbids); if (res.empty()) return {findError("database_empty_result"), "empty!"}; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetnamefromdbid" : ""); int result_index = 0; for(auto& info : res) { result[result_index]["cluid"] = info->uniqueId; result[result_index]["cldbid"] = info->cldbid; result[result_index]["name"] = info->lastName; result[result_index]["clname"] = info->lastName; result_index++; } this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientGetNameFromUid(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; deque unique_ids; for(int index = 0; index < cmd.bulkCount(); index++) unique_ids.push_back(cmd[index]["cluid"].as()); auto res = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->server, unique_ids); if (res.empty()) return {findError("database_empty_result"), "empty!"}; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientnamefromuid" : ""); int result_index = 0; for(auto& info : res) { result[result_index]["cluid"] = info->uniqueId; result[result_index]["cldbid"] = info->cldbid; result[result_index]["name"] = info->lastName; result[result_index]["clname"] = info->lastName; result_index++; } this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientGetUidFromClid(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; bool error = false; bool found = false; auto client_list = this->server->getClients(); Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetuidfromclid" : ""); int result_index = 0; for(int index = 0; index < cmd.bulkCount(); index++) { auto client_id = cmd[index]["clid"].as(); for(const auto& entry : client_list) { if(entry->getClientId() == client_id) { notify[result_index]["clname"] = entry->getDisplayName(); notify[result_index]["clid"] = entry->getClientId(); notify[result_index]["cluid"] = entry->getUid(); notify[result_index]["cldbid"] = entry->getClientDatabaseId(); result_index++; found = true; } } if(found) found = false; else error = false; } if(result_index > 0) this->sendCommand(notify); if(error) return {findError("database_empty_result"), "empty!"}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientAddPerm(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return {findError("client_invalid_id"), "invalid client id"}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]); PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, this->currentChannel); bool conOnError = cmd[0].has("continueonerror"); auto update_channels = false; for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); auto val = cmd[index]["permvalue"].as(); if(permission_require_granted_value(permType) && val > maxValue) return CommandResultPermissionError{permission::i_permission_modify_power}; if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) return CommandResultPermissionError{permission::i_permission_modify_power}; if (grant) { mgr->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); } else { mgr->set_permission(permType, {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0); update_channels |= permission_is_client_property(permType); } } auto onlineClients = this->server->findClientsByCldbId(cmd["cldbid"]); if (!onlineClients.empty()) for (const auto &elm : onlineClients) { if(elm->update_cached_permissions()) /* update cached calculated permissions */ elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if(update_channels) elm->updateChannelClientProperties(true, true); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientDelPerm(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return {findError("client_invalid_id"), "invalid client id"}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]); PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, this->currentChannel); bool conOnError = cmd[0].has("continueonerror"); auto onlineClients = this->server->findClientsByCldbId(cmd["cldbid"]); auto update_channel = false; for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd) if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) return CommandResultPermissionError{permission::i_permission_modify_power}; if (grant) { mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value); } else { mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing); update_channel |= permission_is_client_property(permType); } } if (!onlineClients.empty()) for (const auto &elm : onlineClients) { if(elm->update_cached_permissions()) /* update cached calculated permissions */ elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if(update_channel) elm->updateChannelClientProperties(true, true); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientPermList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); PERM_CHECKR(permission::b_virtualserver_client_permission_list, 1, true); if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return {findError("client_invalid_id"), "invalid client id"}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]); if (!this->notifyClientPermList(cmd["cldbid"], mgr, cmd.hasParm("permsid"))) return {findError("database_empty_result"), "empty!"}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandChannelClientPermList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); PERM_CHECKR(permission::b_virtualserver_channelclient_permission_list, 1, true); std::shared_ptr channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return {findError("client_invalid_id"), "invalid client id"}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"].as()); Command res(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifychannelclientpermlist" : ""); auto permissions = mgr->channel_permissions(channel->channelId()); if(permissions.empty()) return {ErrorType::DBEmpty}; int index = 0; res[index]["cid"] = channel->channelId(); res[index]["cldbid"] = cmd["cldbid"].as(); auto sids = cmd.hasParm("permsid"); for (const auto &permission_data : permissions) { auto& permission = std::get<1>(permission_data); if(permission.flags.value_set) { if (sids) res[index]["permsid"] = permission::resolvePermissionData(get<0>(permission_data))->name; 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::resolvePermissionData(get<0>(permission_data))->grant_name; 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 CommandResult::Success; } //TODO: Update this specific channel visibility? CommandResult ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return {findError("parameter_invalid"), "Invalid manager db id"}; std::shared_ptr channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]); PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, this->currentChannel); bool conOnError = cmd[0].has("continueonerror"); auto cll = this->server->findClientsByCldbId(cmd["cldbid"]); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) return CommandResultPermissionError{permission::i_permission_modify_power}; if (grant) { mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value); } else { mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing); } } if (!cll.empty()) { for (const auto &cl : cll) { if(cl->update_cached_permissions()) /* update cached calculated permissions */ cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ } for (const auto &elm : cll) if (elm->currentChannel == channel) { elm->updateChannelClientProperties(true, true); } } return CommandResult::Success; } //TODO: Update this specific channel visibility? CommandResult ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return {findError("parameter_invalid"), "Invalid manager db id"}; auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]); PERM_CHECKR(permission::i_client_permission_modify_power, this->server->calculatePermission(permission::PERMTEST_ORDERED, cmd["cldbid"], permission::i_client_needed_permission_modify_power, ClientType::CLIENT_TEAMSPEAK, nullptr), true); std::shared_ptr channel = this->server->channelTree->findChannel(cmd["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; auto maxValue = this->getPermissionGrantValue(permission::PERMTEST_ORDERED, permission::i_permission_modify_power, this->currentChannel); bool ignoreGrant = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_permission_modify_power_ignore, 1, this->currentChannel); bool conOnError = cmd[0].has("continueonerror"); auto onlineClientInstances = this->server->findClientsByCldbId(cmd["cldbid"]); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); auto val = cmd[index]["permvalue"].as(); if(permission_require_granted_value(permType) && val > maxValue) return CommandResultPermissionError{permission::i_permission_modify_power}; if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) return CommandResultPermissionError{permission::i_permission_modify_power}; if (grant) { mgr->set_channel_permission(permType, channel->channelId(), {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value); } else { mgr->set_channel_permission(permType, channel->channelId(), {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0); } } if (!onlineClientInstances.empty()) for (const auto &elm : onlineClientInstances) { if (elm->update_cached_permissions()) /* update cached calculated permissions */ elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */ if (elm->currentChannel == channel) { elm->updateChannelClientProperties(true, true); } } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientDbInfo(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); PERM_CHECKR(permission::b_virtualserver_client_dbinfo, 1, true); deque cldbids; for(int index = 0; index < cmd.bulkCount(); index++) cldbids.push_back(cmd[index]["cldbid"]); auto basic = serverInstance->databaseHelper()->queryDatabaseInfo(this->server, cldbids); if (basic.empty()) return {findError("database_empty_result"), "empty!"}; auto allow_ip = this->permission_granted(this->cached_permission_value(permission::b_client_remoteaddress_view), 1); Command res(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyclientdbinfo" : ""); size_t index = 0; for(const auto& info : basic) { res[index]["client_base64HashClientUID"] = hex::hex(base64::validate(info->uniqueId) ? base64::decode(info->uniqueId) : info->uniqueId, 'a', 'q'); res[index]["client_unique_identifier"] = info->uniqueId; res[index]["client_nickname"] = info->lastName; res[index]["client_database_id"] = info->cldbid; res[index]["client_created"] = chrono::duration_cast(info->created.time_since_epoch()).count(); res[index]["client_lastconnected"] = chrono::duration_cast(info->lastjoin.time_since_epoch()).count(); res[index]["client_totalconnections"] = info->connections; res[index]["client_database_id"] = info->cldbid; auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, info->cldbid, ClientType::CLIENT_TEAMSPEAK); if (allow_ip) res[index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as(); else res[index]["client_lastip"] = "hidden"; res[index]["client_icon_id"] = (*props)[property::CLIENT_ICON_ID].as(); res[index]["client_badges"] = (*props)[property::CLIENT_BADGES].as(); res[index]["client_version"] = (*props)[property::CLIENT_VERSION].as(); res[index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as(); res[index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as(); res[index]["client_total_bytes_downloaded"] = (*props)[property::CLIENT_TOTAL_BYTES_DOWNLOADED].as(); res[index]["client_total_bytes_uploaded"] = (*props)[property::CLIENT_TOTAL_BYTES_UPLOADED].as(); res[index]["client_month_bytes_downloaded"] = (*props)[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); res[index]["client_month_bytes_uploaded"] = (*props)[property::CLIENT_MONTH_BYTES_DOWNLOADED].as(); res[index]["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as(); res[index]["client_flag_avatar"] = (*props)[property::CLIENT_FLAG_AVATAR].as(); res[index]["client_month_online_time"] = (*props)[property::CLIENT_MONTH_ONLINE_TIME].as(); res[index]["client_total_online_time"] = (*props)[property::CLIENT_TOTAL_ONLINE_TIME].as(); index++; } this->sendCommand(res); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientDBDelete(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); PERM_CHECKR(permission::b_client_delete_dbproperties, 1, true); ClientDbId id = cmd["cldbid"]; if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, id)) return {findError("database_empty_result"), ""}; serverInstance->databaseHelper()->deleteClient(this->server, id); return CommandResult::Success; } struct DBFindArgs { int index = 0; bool full = false; bool ip = false; Command cmd{""}; }; CommandResult ConnectedClient::handleCommandClientDBFind(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); PERM_CHECKR(permission::b_virtualserver_client_dbsearch, 1, true); bool uid = cmd.hasParm("uid"); string pattern = cmd["pattern"]; DBFindArgs args{}; args.cmd = Command(this->getType() == CLIENT_QUERY ? "" : "notifyclientdbfind"); args.full = cmd.hasParm("details"); args.ip = this->permission_granted(this->cached_permission_value(permission::b_client_remoteaddress_view), 1); auto res = sql::command(this->sql, string() + "SELECT * FROM `clients` WHERE `serverId` = :sid AND `" + (uid ? "clientUid" : "lastName") + "` LIKE '" + pattern + "' LIMIT 50", variable{":sid", this->server->getServerId()}).query( [&](DBFindArgs *ptr, int len, char **values, char **names) { for (int index = 0; index < len; index++) if (strcmp(names[index], "cldbid") == 0) ptr->cmd[ptr->index]["cldbid"] = values[index]; else if (strcmp(names[index], "clientUid") == 0 && ptr->full) ptr->cmd[ptr->index]["client_unique_identifier"] = values[index]; else if (strcmp(names[index], "lastConnect") == 0 && ptr->full) ptr->cmd[ptr->index]["client_lastconnected"] = values[index]; else if (strcmp(names[index], "connections") == 0 && ptr->full) ptr->cmd[ptr->index]["client_totalconnections"] = values[index]; else if (strcmp(names[index], "lastName") == 0 && ptr->full) ptr->cmd[ptr->index]["client_nickname"] = values[index]; if (ptr->full) { auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, ptr->cmd[ptr->index]["cldbid"], ClientType::CLIENT_TEAMSPEAK); if (props) { if (ptr->ip) { ptr->cmd[ptr->index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as(); } else { ptr->cmd[ptr->index]["client_lastip"] = "hidden"; } ptr->cmd[ptr->index]["client_badges"] = (*props)[property::CLIENT_BADGES].as(); ptr->cmd[ptr->index]["client_version"] = (*props)[property::CLIENT_VERSION].as(); ptr->cmd[ptr->index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as(); ptr->cmd[ptr->index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as(); } } ptr->index++; return 0; }, &args); auto pf = LOG_SQL_CMD; pf(res); if (args.index == 0) return {findError("database_empty_result"), ""}; this->sendCommand(args.cmd); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientInfo(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; Command res(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyclientinfo" : ""); bool trigger_error = false; bool view_remote = this->permission_granted(this->cached_permission_value(permission::b_client_remoteaddress_view), 1); int result_index = 0; for(int index = 0; index < cmd.bulkCount(); index++) { auto client_id = cmd[index]["clid"].as(); if(client_id == 0) continue; auto client = this->server->findClient(client_id); if(!client) { trigger_error = true; continue; } for (const auto &key : client->properties()->list_properties(property::FLAG_CLIENT_VIEW | property::FLAG_CLIENT_VARIABLE | property::FLAG_CLIENT_INFO, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) res[result_index][key.type().name] = key.value(); if(view_remote) res[result_index]["connection_client_ip"] = client->properties()[property::CONNECTION_CLIENT_IP].as(); else res[result_index]["connection_client_ip"] = "hidden"; res[result_index]["client_idle_time"] = duration_cast(system_clock::now() - client->idleTimestamp).count(); res[result_index]["connection_connected_time"] = duration_cast(system_clock::now() - client->connectTimestamp).count(); { auto channel = client->currentChannel; if(channel) res[result_index]["cid"] = channel->channelId(); else res[result_index]["cid"] = 0; } result_index++; } if(result_index > 0) { this->sendCommand(res); } if(trigger_error || result_index == 0) return {findError("client_invalid_id"), "invalid client id"}; else return CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientFind(Command &cmd) { CMD_REQ_SERVER; CMD_CHK_AND_INC_FLOOD_POINTS(5); string pattern = cmd["pattern"]; std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); Command res(""); int index = 0; for (const auto &cl : this->server->getClients()) { string name = cl->getDisplayName(); std::transform(name.begin(), name.end(), name.begin(), ::tolower); if (name.find(pattern) != std::string::npos) { res[index]["clid"] = cl->getClientId(); res[index]["client_nickname"] = cl->getDisplayName(); index++; } } if (index == 0) return {findError("database_empty_result"), ""}; this->sendCommand(res); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandVersion(Command &) { CMD_RESET_IDLE; Command res(""); res["version"] = build::version()->string(false); res["build_count"] = build::buildCount(); res["build"] = duration_cast(build::version()->timestamp.time_since_epoch()).count(); #ifdef WINDOWS res["platform"] = "Windows"; #else res["platform"] = "Linux"; #endif this->sendCommand(res); return CommandResult::Success; } //cid=%d password=%s CommandResult ConnectedClient::handleCommandVerifyChannelPassword(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); std::shared_ptr channel = (this->server ? this->server->channelTree : serverInstance->getChannelTree().get())->findChannel(cmd["cid"].as()); if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"}; std::string password = cmd["password"]; if (!channel->passwordMatch(password, false)) return {findError("server_invalid_password"), ""}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandVerifyServerPassword(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); std::string password = cmd["password"]; if (!this->server->verifyServerPassword(password, false)) return {findError("server_invalid_password"), ""}; return CommandResult::Success; } //msgid=2 cluid=IkBXingb46\/z1Q3hhMvJEweb3lw= subject=The\sSubject timestamp=1512224138 flag_read=0 //notifymessagelist msgid=2 cluid=IkBXingb46\/z1Q3hhMvJEweb3lw= subject=The\sSubject timestamp=1512224138 flag_read=0 CommandResult ConnectedClient::handleCommandMessageList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto msgList = this->server->letters->avariableLetters(this->getUid()); if (msgList.empty()) return {findError("database_empty_result"), "no letters avaraible"}; Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifymessagelist" : ""); int index = 0; for (const auto &elm : msgList) { notify[index]["msgid"] = elm->id; notify[index]["cluid"] = elm->sender; notify[index]["subject"] = elm->subject; notify[index]["timestamp"] = duration_cast(elm->created.time_since_epoch()).count(); notify[index]["flag_read"] = elm->read; index++; } this->sendCommand(notify); return CommandResult::Success; } //messageadd cluid=ePHuXhcai9nk\/4Fd\/xkxrokvnNk= subject=Test message=Message CommandResult ConnectedClient::handleCommandMessageAdd(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); CACHED_PERM_CHECK(permission::b_client_offline_textmessage_send, 1, true); this->server->letters->createLetter(this->getUid(), cmd["cluid"], cmd["subject"], cmd["message"]); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandMessageGet(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(10); auto letter = this->server->letters->getFullLetter(cmd["msgid"]); //msgid=2 cluid=IkBXingb46\/z1Q3hhMvJEweb3lw= subject=The\sSubject message=The\sbody timestamp=1512224138 Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifymessage" : ""); notify["msgid"] = cmd["msgid"]; notify["cluid"] = letter->sender; notify["subject"] = letter->subject; notify["message"] = letter->message; notify["timestamp"] = duration_cast(letter->created.time_since_epoch()).count(); this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandMessageUpdateFlag(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); this->server->letters->updateReadFlag(cmd["msgid"], cmd["flag"]); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandMessageDel(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); this->server->letters->deleteLetter(cmd["msgid"]); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPermGet(Command &cmd) { CMD_RESET_IDLE; CACHED_PERM_CHECK(permission::b_client_permissionoverview_own, 1, true); Command res(""); deque requrested; for (int index = 0; index < cmd.bulkCount(); index++) { permission::PermissionType permType = permission::unknown; if (cmd[index].has("permid")) permType = cmd[index]["permid"].as(); else if (cmd[index].has("permsid")) permType = permission::resolvePermissionData(cmd[index]["permsid"].as())->type; if (permission::resolvePermissionData(permType)->type == permission::PermissionType::unknown) return {findError("parameter_invalid"), "could not resolve permission"}; requrested.push_back(permType); } int index = 0; for(const auto& entry : this->permissionValues(permission::PERMTEST_ORDERED, requrested, this->currentChannel)) { res[index]["permsid"] = permission::resolvePermissionData(entry.first)->name; res[index]["permid"] = entry.first; res[index++]["permvalue"] = entry.second; } this->sendCommand(res); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPermIdGetByName(Command &cmd) { auto found = permission::resolvePermissionData(cmd["permsid"].as()); Command res(""); res["permid"] = found->type; this->sendCommand(res); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPermFind(Command &cmd) { struct PermissionEntry { permission::PermissionType permission_type; permission::PermissionValue permission_value; permission::PermissionSqlType type; GroupId group_id; ChannelId channel_id; ClientDbId client_id; bool negate; bool skip; }; CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); PERM_CHECKR(permission::b_virtualserver_permission_find, 1, true); deque, bool>> permissions; std::shared_ptr permission; for(size_t index = 0; index < cmd.bulkCount(); index++) { bool granted = false; if (cmd[index].has("permid")) { permission = permission::resolvePermissionData((permission::PermissionType) (cmd[index]["permid"].as() & (~PERM_ID_GRANT))); granted = (cmd[index]["permid"].as() & PERM_ID_GRANT) > 0; if(permission->type == permission::PermissionType::unknown) return {findError("parameter_invalid"), "could not resolve permission (id=" + cmd[index]["permid"].string() + ")"}; } else if (cmd[index].has("permsid")) { permission = permission::resolvePermissionData(cmd[index]["permsid"].as()); granted = permission->grant_name == cmd[index]["permsid"].as(); if(permission->type == permission::PermissionType::unknown) return {findError("parameter_invalid"), "could not resolve permission (id=" + cmd[index]["permid"].string() + ")"}; } else { continue; } permissions.emplace_back(pair, bool>{{permission->name, permission->type}, granted}); } if(permissions.empty()) return {findError("database_empty_result")}; map flags; map quick_mapping; string query_string; for(const auto& entry : permissions) { if(flags[entry.first.first] == 0) { quick_mapping[entry.first.first] = entry.first.second; query_string += string(query_string.empty() ? "" : " OR ") + "`permId` = '" + entry.first.first + "'"; } flags[entry.first.first] |= entry.second ? 2 : 1; } deque> entries; //`serverId` INT NOT NULL, `type` INT, `id` INT, `channelId` INT, `permId` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` INT, `grant` INT sql::command(this->sql, "SELECT `permId`, `type`, `id`, `channelId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :sid AND (" + query_string + ") AND `type` != :playlist", variable{":sid", this->server->getServerId()}, variable{":playlist", permission::SQL_PERM_PLAYLIST} ).query([&](int length, string* values, string* columns) { permission::PermissionSqlType type = permission::SQL_PERM_GROUP; uint64_t id = 0; ChannelId channel_id = 0; permission::PermissionValue value = 0, granted_value = 0; string permission_name; bool negate = false, skip = false; for (int index = 0; index < length; index++) { try { if(columns[index] == "type") type = static_cast(stoll(values[index])); else if(columns[index] == "permId") permission_name = values[index]; else if(columns[index] == "id") id = static_cast(stoll(values[index])); else if(columns[index] == "channelId") channel_id = static_cast(stoll(values[index])); else if(columns[index] == "value") value = static_cast(stoll(values[index])); else if(columns[index] == "grant") granted_value = static_cast(stoll(values[index])); else if(columns[index] == "flag_negate") negate = !values[index].empty() && stol(values[index]) == 1; else if(columns[index] == "flag_skip") skip = !values[index].empty() && stol(values[index]) == 1; } catch(std::exception& ex) { debugMessage(this->getServerId(), "[{}] 'permfind' iterates over invalid permission entry. Key: {}, Value: {}, Error: {}", CLIENT_STR_LOG_PREFIX, columns[index], values[index], ex.what()); return 0; } } /* value */ if((flags[permission_name] & 0x1) > 0 && value > 0) { auto result = make_unique(); result->permission_type = quick_mapping[permission_name]; result->permission_value = value; result->type = type; result->channel_id = channel_id; result->negate = negate; result->skip = skip; if (type == permission::SQL_PERM_GROUP) { auto gr = this->server->groups->findGroup(id); if (!gr) return 0; result->group_id = id; if(gr->target() == GROUPTARGET_CHANNEL) result->channel_id = 1; } else if(type == permission::SQL_PERM_USER) { result->client_id = id; } if(result) entries.push_back(std::move(result)); } /* granted */ if((flags[permission_name] & 0x2) > 0 && granted_value > 0) { auto result = make_unique(); result->permission_type = (permission::PermissionType) (quick_mapping[permission_name] | PERM_ID_GRANT); result->permission_value = granted_value; result->type = type; result->channel_id = channel_id; result->negate = negate; result->skip = skip; if (type == permission::SQL_PERM_GROUP) { auto gr = this->server->groups->findGroup(id); if (!gr) return 0; result->group_id = id; if(gr->target() == GROUPTARGET_CHANNEL) result->channel_id = 1; } else if(type == permission::SQL_PERM_USER) { result->client_id = id; } if(result) entries.push_back(std::move(result)); } return 0; }); struct CommandPerm { permission::PermissionType p; permission::PermissionValue v; int64_t id1; int64_t id2; uint8_t t; }; std::deque perms; perms.resize(entries.size()); size_t index = 0; for(const auto& entry : entries) { auto& perm = perms[index++]; perm.p = entry->permission_type; perm.v = entry->permission_value; if(entry->type == permission::SQL_PERM_USER) { if(entry->channel_id > 0) { perm.id1 = entry->client_id; perm.id2 = entry->channel_id; perm.t = 4; /* client channel */ } else { perm.id1 = 0; perm.id2 = entry->client_id; perm.t = 1; /* client server */ } } else if(entry->type == permission::SQL_PERM_CHANNEL) { perm.id1 = 0; perm.id2 = entry->channel_id; perm.t = 2; /* channel permission */ } else if(entry->type == permission::SQL_PERM_GROUP) { if(entry->channel_id > 0) { perm.id1 = entry->group_id; perm.id2 = 0; perm.t = 3; /* channel group */ } else { perm.id1 = entry->group_id; perm.id2 = 0; perm.t = 0; /* server group */ } } } sort(perms.begin(), perms.end(), [](const CommandPerm& a, const CommandPerm& b) { if(a.t < b.t) return true; else if(b.t < a.t) return false; if(a.id1 < b.id1) return true; else if(b.id1 < a.id1) return false; if(a.id2 < b.id2) return true; else if(b.id2 < a.id2) return false; if(a.p < b.p) return true; else if(b.p < a.p) return false; return &a > &b; }); Command result(""); index = 0; // http://yat.qa/ressourcen/server-query-kommentare/#permfind for(const auto& e : perms) { result[index]["p"] = e.p; result[index]["v"] = e.v; result[index]["id1"] = e.id1; result[index]["id2"] = e.id2; result[index]["t"] = e.t; index++; } if(index == 0) return {findError("database_empty_result")}; this->sendCommand(result); return CommandResult::Success; } /* * - Alle rechte der aktuellen server gruppen vom client * - Alle client rechte | channel cleint rechte * - Alle rechte des channels */ CommandResult ConnectedClient::handleCommandPermOverview(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto client_dbid = cmd["cldbid"].as(); if(!serverInstance->databaseHelper()->validClientDatabaseId(this->getServer(), client_dbid)) return {findError("client_invalid_id")}; if(client_dbid == this->getClientDatabaseId()) { CACHED_PERM_CHECK(permission::b_client_permissionoverview_own, 1, true); } else { CACHED_PERM_CHECK(permission::b_client_permissionoverview_view, 1, true); } string channel_query, perm_query; auto channel = this->server ? this->server->channelTree->findChannel(cmd["cid"]) : serverInstance->getChannelTree()->findChannel(cmd["cid"]); if(!channel) return {findError("channel_invalid_id")}; auto server_groups = this->server->getGroupManager()->getServerGroups(client_dbid, ClientType::CLIENT_TEAMSPEAK); auto channel_group = this->server->getGroupManager()->getChannelGroup(client_dbid, channel, true); auto permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServer(), client_dbid); Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifypermoverview" : ""); size_t index = 0; result["cldbid"] = client_dbid; result["cid"] = channel->channelId(); if(cmd["return_code"].size() > 0) result["return_code"] = cmd["return_code"].string(); for(const auto& server_group : server_groups) { auto permission_manager = server_group->group->permissions(); for(const auto& permission_data : permission_manager->permissions()) { auto& permission = std::get<1>(permission_data); if(permission.flags.value_set) { result[index]["t"] = 0; /* server group */ result[index]["id1"] = server_group->group->groupId(); result[index]["id2"] = 0; result[index]["p"] = std::get<0>(permission_data); result[index]["v"] = permission.values.value; result[index]["n"] = permission.flags.negate; result[index]["s"] = permission.flags.skip; index++; } if(permission.flags.grant_set) { result[index]["t"] = 0; /* server group */ result[index]["id1"] = server_group->group->groupId(); result[index]["id2"] = 0; result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); result[index]["v"] = permission.values.grant; result[index]["n"] = false; result[index]["s"] = false; index++; } } } { for(const auto& permission_data : permission_manager->permissions()) { auto& permission = std::get<1>(permission_data); if(permission.flags.value_set) { result[index]["t"] = 1; /* client */ result[index]["id1"] = client_dbid; result[index]["id2"] = 0; result[index]["p"] = std::get<0>(permission_data); result[index]["v"] = permission.values.value; result[index]["n"] = permission.flags.negate; result[index]["s"] = permission.flags.skip; index++; } if(permission.flags.grant_set) { result[index]["t"] = 1; /* client */ result[index]["id1"] = client_dbid; result[index]["id2"] = 0; result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); result[index]["v"] = permission.values.grant; result[index]["n"] = false; result[index]["s"] = false; index++; } } } { auto permission_manager = channel->permissions(); for(const auto& permission_data : permission_manager->permissions()) { auto& permission = std::get<1>(permission_data); if(permission.flags.value_set) { result[index]["t"] = 2; /* server channel */ result[index]["id1"] = channel->channelId(); result[index]["id2"] = 0; result[index]["p"] = std::get<0>(permission_data); result[index]["v"] = permission.values.value; result[index]["n"] = permission.flags.negate; result[index]["s"] = permission.flags.skip; index++; } if(permission.flags.grant_set) { result[index]["t"] = 2; /* server channel */ result[index]["id1"] = channel->channelId(); result[index]["id2"] = 0; result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); result[index]["v"] = permission.values.grant; result[index]["n"] = false; result[index]["s"] = false; index++; } } } { auto permission_manager = channel_group->group->permissions(); for(const auto& permission_data : permission_manager->permissions()) { auto& permission = std::get<1>(permission_data); if(permission.flags.value_set) { result[index]["t"] = 3; /* channel group */ result[index]["id1"] = channel_group->channelId; result[index]["id2"] = channel_group->group->groupId(); result[index]["p"] = std::get<0>(permission_data); result[index]["v"] = permission.values.value; result[index]["n"] = permission.flags.negate; result[index]["s"] = permission.flags.skip; index++; } if(permission.flags.grant_set) { result[index]["t"] = 3; /* channel group */ result[index]["id1"] = channel_group->channelId; result[index]["id2"] = channel_group->group->groupId(); result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); result[index]["v"] = permission.values.grant; result[index]["n"] = false; result[index]["s"] = false; index++; } } } { for(const auto& permission_data : permission_manager->channel_permissions()) { auto& permission = std::get<2>(permission_data); if(permission.flags.value_set) { result[index]["t"] = 4; /* client channel */ result[index]["id1"] = std::get<1>(permission_data); result[index]["id2"] = client_dbid; result[index]["p"] = std::get<0>(permission_data); result[index]["v"] = permission.values.value; result[index]["n"] = permission.flags.negate; result[index]["s"] = permission.flags.skip; index++; } if(permission.flags.grant_set) { result[index]["t"] = 1; /* client */ result[index]["id1"] = std::get<1>(permission_data); result[index]["id2"] = client_dbid; result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT); result[index]["v"] = permission.values.grant; result[index]["n"] = false; result[index]["s"] = false; index++; } } } if (index == 0) return {findError("database_empty_result"), ""}; this->sendCommand(result); return CommandResult::Success; } CommandResult 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 {findError("database_empty_result"), ""}; this->sendCommand(res); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandChannelInfo(Command &cmd) { std::shared_ptr channel = (this->server ? this->server->channelTree : serverInstance->getChannelTree().get())->findChannel(cmd["cid"].as()); if (!channel) return {findError("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 CommandResult::Success; } CommandResult ConnectedClient::handleCommandClientSetServerQueryLogin(Command &cmd) { PERM_CHECKR(permission::b_client_create_modify_serverquery_login, 1, true); if(!cmd[0].has("client_login_password")) cmd["client_login_password"] = ""; std::string password = cmd["client_login_password"]; if(password.empty()) password = rnd_string(QUERY_PASSWORD_LENGTH); auto old = serverInstance->getQueryServer()->find_query_account_by_name(cmd["client_login_name"]); if (old) { if(old->unique_id == this->getUid()) { serverInstance->getQueryServer()->change_query_password(old, password); } else { return {findError("client_not_logged_in")}; } } else { serverInstance->getQueryServer()->create_query_account(cmd["client_login_name"], this->getServerId(), this->getUid(), password); } Command res(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientserverqueryloginpassword" : ""); res["client_login_password"] = password; this->sendCommand(res); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandComplainAdd(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); ClientDbId target = cmd["tcldbid"]; std::string msg = cmd["message"]; auto cl = this->server->findClientsByCldbId(target); if (cl.empty()) return {findError("client_invalid_id"), "invalid client id"}; PERM_CHECKR(permission::i_client_complain_power, cl[0]->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_needed_complain_power), true); /* if(!serverInstance->databaseHelper()->validClientDatabaseId(target)) return {findError("client_invalid_id"), "invalid database id"}; */ for (const auto &elm : this->server->complains->findComplainsFromTarget(target)) if (elm->invoker == this->getClientDatabaseId()) return {findError("database_duplicate_entry"), "you already send a complain"}; if (!this->server->complains->createComplain(target, this->getClientDatabaseId(), msg)) return {findError("vs_critical"), "could not create complains"}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandComplainList(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); CACHED_PERM_CHECK(permission::b_client_complain_list, 1, true); ClientDbId id = cmd[0].has("tcldbid") ? cmd["tcldbid"].as() : 0; auto list = id == 0 ? this->server->complains->complains() : this->server->complains->findComplainsFromTarget(id); if (list.empty()) return {findError("database_empty_result"), "empty!"}; deque nameQuery; for (const auto &elm : list) { if (std::find(nameQuery.begin(), nameQuery.end(), elm->invoker) == nameQuery.end()) nameQuery.push_back(elm->invoker); if (std::find(nameQuery.begin(), nameQuery.end(), elm->target) == nameQuery.end()) nameQuery.push_back(elm->target); } auto dbInfo = serverInstance->databaseHelper()->queryDatabaseInfo(this->server, nameQuery); Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifycomplainlist" : ""); int index = 0; for (const auto &elm : list) { result[index]["tcldbid"] = elm->target; result[index]["tname"] = "unknown"; result[index]["fcldbid"] = elm->invoker; result[index]["fname"] = "unknown"; result[index]["message"] = elm->reason; result[index]["timestamp"] = chrono::duration_cast(elm->created.time_since_epoch()).count(); for (const auto &e : dbInfo) { if (e->cldbid == elm->target) result[index]["tname"] = e->lastName; if (e->cldbid == elm->invoker) result[index]["fname"] = e->lastName; } index++; } this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandComplainDel(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); ClientDbId tid = cmd["tcldbid"]; ClientDbId fid = cmd["fcldbid"]; shared_ptr entry; for (const auto &elm : this->server->complains->findComplainsFromTarget(tid)) if (elm->invoker == fid) { entry = elm; break; } if (!entry) return {findError("database_empty_result"), "empty!"}; if (entry->invoker == this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::b_client_complain_delete_own, 1, true); else CACHED_PERM_CHECK(permission::b_client_complain_delete, 1, true); this->server->complains->deleteComplain(entry); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandComplainDelAll(Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); CACHED_PERM_CHECK(permission::b_client_complain_delete, 1, true); ClientDbId tid = cmd["tcldbid"]; if (!this->server->complains->deleteComplainsFromTarget(tid)) return {findError("database_empty_result"), "empty!"}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandMusicBotCreate(Command& cmd) { if(!config::music::enabled) return {findError("music_disabled")}; CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); if(this->server->musicManager->max_bots() != -1 && this->server->musicManager->max_bots() <= this->server->musicManager->current_bot_count()){ if(config::license->isPremium()) return {findError("music_limit_reached"), ""}; else return {findError("music_limit_reached"), "You reached the server music bot limit. You could increase this limit by extend your server with a premium license."}; } auto permissions_list = this->permissionValues(permission::PERMTEST_ORDERED, { permission::i_client_music_limit, permission::b_client_music_create_permanent, permission::b_client_music_create_semi_permanent, permission::b_client_music_create_temporary, permission::i_channel_join_power, permission::i_client_music_delete_power, permission::i_client_music_create_modify_max_volume }, this->currentChannel); auto permissions = map(permissions_list.begin(), permissions_list.end()); auto max_bots = permissions[permission::i_client_music_limit]; if(max_bots >= 0) { auto ownBots = this->server->musicManager->listBots(this->getClientDatabaseId()); if(ownBots.size() > max_bots) return {findError("music_client_limit_reached"), ""}; } MusicClient::Type::value create_type; if(cmd[0].has("type")) { create_type = cmd["type"].as(); switch(create_type) { case MusicClient::Type::PERMANENT: if(permissions[permission::b_client_music_create_permanent] != 1) return CommandResultPermissionError{permission::b_client_music_create_permanent}; break; case MusicClient::Type::SEMI_PERMANENT: if(permissions[permission::b_client_music_create_semi_permanent] != 1) return CommandResultPermissionError{permission::b_client_music_create_semi_permanent}; break; case MusicClient::Type::TEMPORARY: if(permissions[permission::b_client_music_create_temporary] != 1) return CommandResultPermissionError{permission::b_client_music_create_temporary}; break; default: return {ErrorType::VSError}; } } else { if(permissions[permission::b_client_music_create_permanent] == 1) create_type = MusicClient::Type::PERMANENT; else if(permissions[permission::b_client_music_create_semi_permanent] == 1) create_type = MusicClient::Type::SEMI_PERMANENT; else if(permissions[permission::b_client_music_create_temporary] == 1) create_type = MusicClient::Type::TEMPORARY; else return CommandResultPermissionError{permission::b_client_music_create_temporary}; } shared_lock server_channel_lock(this->server->channel_tree_lock); auto channel = cmd[0].has("cid") ? this->server->channelTree->findChannel(cmd["cid"]) : this->currentChannel; if(!channel) { if(cmd[0].has("cid")) return {findError("client_invalid_id")}; } else { CHANNEL_PERMISSION_TEST(permission::i_channel_description_view_power, permission::i_channel_needed_description_view_power, channel, false); auto permission_granted = this->calculate_permission_value(permission::i_channel_join_power, channel->channelId()); if(!channel->permission_granted(permission::i_channel_needed_join_power, permission_granted, false)) channel = nullptr; } if(!channel) { channel = this->server->channelTree->getDefaultChannel(); } auto bot = this->server->musicManager->createBot(this->getClientDatabaseId()); if(!bot) return {ErrorType::VSError, ""}; bot->set_bot_type(create_type); { if(permissions[permission::i_client_music_create_modify_max_volume] > 0) { auto max_volume = min(100, max(0, permissions[permission::i_client_music_create_modify_max_volume])); if(max_volume >= 0) bot->volume_modifier(max_volume / 100.f); } } this->selectedBot = bot; { server_channel_lock.unlock(); unique_lock server_channel_w_lock(this->server->channel_tree_lock); this->server->client_move( bot, channel, nullptr, "music bot created", ViewReasonId::VREASON_USER_ACTION, false, server_channel_w_lock ); } if(permissions[permission::i_client_music_delete_power] > 0) { bot->clientPermissions->set_permission(permission::i_client_music_needed_delete_power, {permissions[permission::i_client_music_delete_power],0}, permission::v2::set_value, permission::v2::do_nothing); } Command notify(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifymusiccreated" : ""); notify["bot_id"] = bot->getClientDatabaseId(); this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandMusicBotDelete(Command& cmd) { if(!config::music::enabled) return {findError("music_disabled")}; CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return {findError("music_invalid_id")}; bool permPower = this->permissionGranted(permission::PERMTEST_ORDERED, permission::i_client_music_delete_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_delete_power)); if(bot->getOwner() != this->getClientDatabaseId()) { if(!permPower) return CommandResultPermissionError{permission::i_client_music_delete_power}; } this->server->musicManager->deleteBot(bot); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandMusicBotSetSubscription(ts::Command &cmd) { if(!config::music::enabled) return {findError("music_disabled")}; auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); if(!bot && cmd["bot_id"].as() != 0) return {findError("music_invalid_id")}; { auto old_bot = this->subscribed_bot.lock(); if(old_bot) old_bot->remove_subscriber(_this.lock()); } if(bot) { bot->add_subscriber(_this.lock()); this->subscribed_bot = bot; } return CommandResult::Success; } void apply_song(Command& command, const std::shared_ptr& element, int index = 0) { if(!element) return; command[index]["song_id"] = element ? element->getSongId() : 0; command[index]["song_url"] = element ? element->getUrl() : ""; command[index]["song_invoker"] = element ? element->getInvoker() : 0; command[index]["song_loaded"] = false; auto entry = dynamic_pointer_cast(element); if(entry) { auto data = entry->song_loaded_data(); command[index]["song_loaded"] = entry->song_loaded() && data; if(entry->song_loaded() && data) { command[index]["song_title"] = data->title; command[index]["song_description"] = data->description; command[index]["song_thumbnail"] = data->thumbnail; command[index]["song_length"] = data->length.count(); } } } CommandResult ConnectedClient::handleCommandMusicBotPlayerInfo(Command& cmd) { if(!config::music::enabled) return {findError("music_disabled")}; CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return {findError("music_invalid_id")}; Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifymusicplayerinfo" : ""); result["bot_id"] = bot->getClientDatabaseId(); result["player_state"] =(int) bot->player_state(); if(bot->current_player()) { result["player_buffered_index"] = bot->current_player()->bufferedUntil().count(); result["player_replay_index"] = bot->current_player()->currentIndex().count(); result["player_max_index"] = bot->current_player()->length().count(); result["player_seekable"] = bot->current_player()->seek_supported(); result["player_title"] = bot->current_player()->songTitle(); result["player_description"] = bot->current_player()->songDescription(); } else { result["player_buffered_index"] = 0; result["player_replay_index"] = 0; result["player_max_index"] = 0; result["player_seekable"] = 0; result["player_title"] = ""; result["player_description"] = ""; } apply_song(result, bot->current_song()); this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandMusicBotPlayerAction(Command& cmd) { if(!config::music::enabled) return {findError("music_disabled")}; CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return {findError("music_invalid_id")}; PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); if(cmd["action"] == 0) { bot->stopMusic(); } else if(cmd["action"] == 1) { bot->playMusic(); } else if(cmd["action"] == 2) { bot->player_pause(); } else if(cmd["action"] == 3) { bot->forwardSong(); } else if(cmd["action"] == 4) { bot->rewindSong(); } else if(cmd["action"] == 5) { if(!bot->current_player()) return {findError("music_no_player")}; bot->current_player()->forward(::music::PlayerUnits(cmd["units"].as())); } else if(cmd["action"] == 6) { if(!bot->current_player()) return {findError("music_no_player")}; bot->current_player()->rewind(::music::PlayerUnits(cmd["units"].as())); } else return {findError("music_invalid_action")}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistList(ts::Command &cmd) { CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto self_dbid = this->getClientDatabaseId(); auto playlist_view_power = this->cached_permission_value(permission::i_playlist_view_power); auto playlists = this->server->musicManager->playlists(); playlists.erase(find_if(playlists.begin(), playlists.end(), [&](const shared_ptr& playlist) { if(playlist->properties()[property::PLAYLIST_OWNER_DBID] == self_dbid) return false; auto needed_view_power = playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power); return !this->permission_granted(playlist_view_power, needed_view_power, false); }), playlists.end()); if(playlists.empty()) return {ErrorType::DBEmpty}; Command notify(this->notify_response_command("notifyplaylistlist")); size_t index = 0; for(const auto& entry : playlists) { notify[index]["playlist_id"] = entry->playlist_id(); auto bot = entry->current_bot(); notify[index]["playlist_bot_id"] = bot ? bot->getClientDatabaseId() : 0; notify[index]["playlist_title"] = entry->properties()[property::PLAYLIST_TITLE].value(); notify[index]["playlist_type"] = entry->properties()[property::PLAYLIST_TYPE].value(); notify[index]["playlist_owner_dbid"] = entry->properties()[property::PLAYLIST_OWNER_DBID].value(); notify[index]["playlist_owner_name"] = entry->properties()[property::PLAYLIST_OWNER_NAME].value(); notify[index]["needed_power_modify"] = entry->permissions()->getPermissionValue(permission::i_playlist_needed_modify_power); notify[index]["needed_power_permission_modify"] = entry->permissions()->getPermissionValue(permission::i_playlist_needed_permission_modify_power); notify[index]["needed_power_delete"] = entry->permissions()->getPermissionValue(permission::i_playlist_needed_delete_power); notify[index]["needed_power_song_add"] = entry->permissions()->getPermissionValue(permission::i_playlist_song_needed_add_power); notify[index]["needed_power_song_move"] = entry->permissions()->getPermissionValue(permission::i_playlist_song_needed_move_power); notify[index]["needed_power_song_remove"] = entry->permissions()->getPermissionValue(permission::i_playlist_song_needed_remove_power); index++; } this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistCreate(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); CACHED_PERM_CHECK(permission::b_playlist_create, 1, true); { auto max_playlists = this->cached_permission_value(permission::i_max_playlists); if(max_playlists != permNotGranted) { auto playlists = ref_server->musicManager->find_playlists_by_client(this->getClientDatabaseId()); if(!this->permission_granted(this->cached_permission_value(permission::i_max_playlists), playlists.size(), false)) return CommandResultPermissionError{permission::i_max_playlists}; } } auto playlist = ref_server->musicManager->create_playlist(this->getClientDatabaseId(), this->getDisplayName()); if(!playlist) return {ErrorType::VSError}; playlist->properties()[property::PLAYLIST_TYPE] = music::Playlist::Type::GENERAL; { auto max_songs = this->cached_permission_value(permission::i_max_playlist_size); if(max_songs != permNotGranted) playlist->properties()[property::PLAYLIST_MAX_SONGS] = max_songs; } playlist->permissions()->setPermission(permission::i_playlist_song_needed_remove_power, this->cached_permission_value(permission::i_playlist_song_remove_power), nullptr); playlist->permissions()->setPermission(permission::i_playlist_needed_delete_power, this->cached_permission_value(permission::i_playlist_delete_power), nullptr); playlist->permissions()->setPermission(permission::i_playlist_needed_modify_power, this->cached_permission_value(permission::i_playlist_modify_power), nullptr); playlist->permissions()->setPermission(permission::i_playlist_needed_permission_modify_power, this->cached_permission_value(permission::i_playlist_permission_modify_power), nullptr); Command notify(this->notify_response_command("notifyplaylistcreated")); notify["playlist_id"] = playlist->playlist_id(); this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistDelete(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return {findError("playlist_invalid_id")}; if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::i_playlist_delete_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_delete_power)); string error; if(!ref_server->musicManager->delete_playlist(playlist->playlist_id(), error)) { logError(this->getServerId(), "Failed to delete playlist with id {}. Error: {}", playlist->playlist_id(), error); return {ErrorType::VSError, error}; } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistInfo(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return {findError("playlist_invalid_id")}; if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::i_playlist_view_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power)); Command notify(this->notify_response_command("notifyplaylistinfo")); for(const auto& property : playlist->properties().list_properties(property::FLAG_PLAYLIST_VARIABLE)) { notify[property.type().name] = property.value(); } this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistEdit(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return {findError("playlist_invalid_id")}; if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::i_playlist_modify_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_modify_power)); deque, string>> properties; for(const auto& key : cmd[0].keys()) { if(key == "playlist_id") continue; if(key == "return_code") continue; auto property = property::info(key); if(*property == property::PLAYLIST_UNDEFINED) { logError(this->getServerId(), R"([{}] Tried to edit a not existing playlist property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); continue; } if((property->flags & property::FLAG_USER_EDITABLE) == 0) { logError(this->getServerId(), "[{}] Tried to change a playlist property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); continue; } if(!property->validate_input(cmd[key].as())) { logError(this->getServerId(), "[{}] Tried to change a playlist property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string()); continue; } if(*property == property::PLAYLIST_CURRENT_SONG_ID) { auto song_id = cmd[key].as(); auto song = song_id > 0 ? playlist->find_song(song_id) : nullptr; if(song_id != 0 && !song) return {findError("playlist_invalid_song_id")}; } else if(*property == property::PLAYLIST_MAX_SONGS) { auto value = cmd[key].as(); auto max_value = this->cached_permission_value(permission::i_max_playlist_size); if(!this->permission_granted(max_value, value, false)) return CommandResultPermissionError{permission::i_max_playlist_size}; } properties.emplace_back(property, key); } for(const auto& property : properties) { if(*property.first == property::PLAYLIST_CURRENT_SONG_ID) { playlist->set_current_song(cmd[property.second]); continue; } playlist->properties()[property.first] = cmd[property.second].string(); } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistPermList(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return {findError("playlist_invalid_id")}; if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::b_virtualserver_playlist_permission_list, 1, true); auto permissions = playlist->permissions()->listPermissions(PERM_FLAG_PUBLIC); if(permissions.empty()) return {ErrorType::VSError}; Command result(this->notify_response_command("notifyplaylistpermlist")); int index = 0; result["playlist_id"] = playlist->playlist_id(); for (const auto &elm : permissions) { if(elm->hasValue()) { result[index]["permid"] = elm->type->type; result[index]["permvalue"] = elm->value; result[index]["permnegated"] = elm->flag_negate; result[index]["permskip"] = elm->flag_skip; index++; } if(elm->hasGrant()) { result[index]["permid"] = (uint16_t) (elm->type->type | PERM_ID_GRANT); result[index]["permvalue"] = elm->granted; result[index]["permnegated"] = 0; result[index]["permskip"] = 0; index++; } } this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return {findError("playlist_invalid_id")}; if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::i_playlist_permission_modify_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_permission_modify_power), true); auto maxValue = this->getPermissionGrantValue(permission::i_permission_modify_power, this->currentChannel); bool ignoreGrant = this->permission_granted(this->cached_permission_value(permission::b_permission_modify_power_ignore), 1, true); bool conOnError = cmd[0].has("continueonerror"); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); auto val = cmd[index]["permvalue"].as(); if(permission_require_granted_value(permType) && val > maxValue) return CommandResultPermissionError{permission::i_permission_modify_power}; if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) return CommandResultPermissionError{permission::i_permission_modify_power}; if (grant) { playlist->permissions()->setPermissionGranted(permType, cmd[index]["permvalue"], nullptr); } else { playlist->permissions()->setPermission(permType, cmd[index]["permvalue"], nullptr, cmd[index]["permskip"], cmd[index]["permnegated"]); } } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return {findError("playlist_invalid_id")}; if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::i_playlist_permission_modify_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_permission_modify_power), true); bool ignoreGrant = this->permission_granted(this->cached_permission_value(permission::b_permission_modify_power_ignore), 1, true); bool conOnError = cmd[0].has("continueonerror"); for (int index = 0; index < cmd.bulkCount(); index++) { PARSE_PERMISSION(cmd); if(!ignoreGrant && !this->permissionGrantGranted(permission::PERMTEST_ORDERED, permType, 1, this->currentChannel)) return CommandResultPermissionError{permission::i_permission_modify_power}; if (grant) { playlist->permissions()->setPermissionGranted(permType, permNotGranted, nullptr); } else { playlist->permissions()->deletePermission(permType, nullptr); } } return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return {findError("playlist_invalid_id")}; if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::i_playlist_view_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power)); auto songs = playlist->list_songs(); if(songs.empty()) return {ErrorType::DBEmpty}; Command notify(this->notify_response_command("notifyplaylistsonglist")); notify["playlist_id"] = playlist->playlist_id(); size_t index = 0; for(const auto& song : songs) { notify[index]["song_id"] = song->id; notify[index]["song_invoker"] = song->invoker; notify[index]["song_previous_song_id"] = song->previous_song_id; notify[index]["song_url"] = song->url; notify[index]["song_url_loader"] = song->url_loader; notify[index]["song_loaded"] = song->loaded; notify[index]["song_metadata"] = song->metadata; index++; } this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistSongAdd(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return {findError("playlist_invalid_id")}; if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::i_playlist_song_add_power, playlist->permissions()->getPermissionValue(permission::i_playlist_song_needed_add_power)); if(!cmd[0].has("invoker")) cmd["invoker"] = ""; if(!cmd[0].has("previous")) { auto songs = playlist->list_songs(); if(songs.empty()) cmd["previous"] = "0"; else cmd["previous"] = songs.back()->id; } auto song = playlist->add_song(_this.lock(), cmd["url"], cmd["invoker"], cmd["previous"]); if(!song) return {ErrorType::VSError}; Command notify(this->notify_response_command("notifyplaylistsongadd")); notify["song_id"] = song->id; this->sendCommand(notify); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistSongReorder(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return {findError("playlist_invalid_id")}; if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::i_playlist_song_move_power, playlist->permissions()->getPermissionValue(permission::i_playlist_song_needed_move_power)); SongId song_id = cmd["song_id"]; SongId previous_id = cmd["song_previous_song_id"]; auto song = playlist->find_song(song_id); if(!song) return {findError("playlist_invalid_song_id")}; if(!playlist->reorder_song(song_id, previous_id)) return {ErrorType::VSError}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPlaylistSongRemove(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist) return {findError("playlist_invalid_id")}; if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::i_playlist_song_remove_power, playlist->permissions()->getPermissionValue(permission::i_playlist_song_needed_remove_power)); SongId song_id = cmd["song_id"]; auto song = playlist->find_song(song_id); if(!song) return {findError("playlist_invalid_song_id")}; if(!playlist->delete_song(song_id)) return {ErrorType::VSError}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandMusicBotQueueList(Command& cmd) { return CommandResult::NotImplemented; //FIXME /* CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return {findError("music_invalid_id")}; PERM_CHECK_CHANNELR(permission::i_client_music_info, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_info, bot->currentChannel), this->currentChannel, true); bool bulked = cmd.hasParm("bulk") || cmd.hasParm("balk") || cmd.hasParm("pipe") || cmd.hasParm("bar") || cmd.hasParm("paypal"); int command_index = 0; Command notify(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : ""); { auto history = bot->queue()->history(); for(int index = history.size(); index > 0; index--) { if(!bulked) notify = Command(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : ""); apply_song(notify, history[index - 1], command_index); notify[command_index]["queue_index"] = -index; if(!bulked) this->sendCommand(notify); else command_index++; } } { if(!bulked) notify = Command(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : ""); auto song = bot->queue()->currentSong(); apply_song(notify, song, command_index); notify[command_index]["queue_index"] = 0; if(!bulked) this->sendCommand(notify); else if(song) command_index++; } { auto queue = bot->queue()->queueEntries(); for(int index = 0; index < queue.size(); index++) { if(!bulked) notify = Command(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : ""); apply_song(notify, queue[index], command_index); notify[command_index]["queue_index"] = index + 1; if(!bulked) this->sendCommand(notify); else command_index++; } } debugMessage(this->getServerId(),"Send: {}",notify.build()); if(bulked) { if(command_index > 0) { this->sendCommand(notify); } else return { ErrorType::DBEmpty }; } if(this->getExternalType() == CLIENT_VOICE) { Command notify("notifymusicqueuefinish"); notify["bot_id"] = bot->getClientDatabaseId(); this->sendCommand(notify); } return CommandResult::Success; */ } CommandResult ConnectedClient::handleCommandMusicBotQueueAdd(Command& cmd) { return CommandResult::NotImplemented; //FIXME /* CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return {findError("music_invalid_id")}; PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); MusicClient::loader_t loader; auto& type = cmd[0]["type"]; if((type.castable() && type.as() == 0) || type.as() == "yt") { loader = bot->ytLoader(this->getServer()); } else if((type.castable() && type.as() == 1) || type.as() == "ffmpeg") { loader = bot->ffmpegLoader(this->getServer()); } else if((type.castable() && type.as() == 2) || type.as() == "channel") { loader = bot->channelLoader(this->getServer()); } else if((type.castable() && type.as() == -1) || type.as() == "any") { loader = bot->providerLoader(this->getServer(), ""); } if(!loader) return {findError("music_invalid_action")}; auto entry = bot->queue()->insertEntry(cmd["url"], _this.lock(), loader); if(!entry) return {ErrorType::VSError}; this->server->forEachClient([&](shared_ptr client) { client->notifyMusicQueueAdd(bot, entry, bot->queue()->queueEntries().size() - 1, _this.lock()); }); return CommandResult::Success; */ } CommandResult ConnectedClient::handleCommandMusicBotQueueRemove(Command& cmd) { return CommandResult::NotImplemented; //FIXME /* CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return {findError("music_invalid_id")}; PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); std::deque> songs; for(int index = 0; index < cmd.bulkCount(); index++) { auto entry = bot->queue()->find_queue(cmd["song_id"]); if(!entry) { if(cmd.hasParm("skip_error")) continue; return {ErrorType::DBEmpty}; } songs.push_back(move(entry)); } for(const auto& entry : songs) bot->queue()->deleteEntry(dynamic_pointer_cast(entry)); this->server->forEachClient([&](shared_ptr client) { client->notifyMusicQueueRemove(bot, songs, _this.lock()); }); return CommandResult::Success; */ } CommandResult ConnectedClient::handleCommandMusicBotQueueReorder(Command& cmd) { return CommandResult::NotImplemented; //FIXME /* CMD_REQ_SERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto bot = this->server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return {findError("music_invalid_id")}; PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); auto entry = bot->queue()->find_queue(cmd["song_id"]); if(!entry) return {ErrorType::DBEmpty}; auto order = bot->queue()->changeOrder(entry, cmd["index"]); if(order < 0) return {ErrorType::VSError}; this->server->forEachClient([&](shared_ptr client) { client->notifyMusicQueueOrderChange(bot, entry, order, _this.lock()); }); return CommandResult::Success; */ } CommandResult ConnectedClient::handleCommandMusicBotPlaylistAssign(ts::Command &cmd) { CMD_REF_SERVER(ref_server); CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(25); auto bot = ref_server->musicManager->findBotById(cmd["bot_id"]); if(!bot) return {findError("music_invalid_id")}; if(bot->getOwner() != this->getClientDatabaseId()) PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::PERMTEST_ORDERED, permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true); auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]); if(!playlist && cmd["playlist_id"] != 0) return {findError("playlist_invalid_id")}; if(ref_server->musicManager->find_bot_by_playlist(playlist)) return {findError("playlist_already_in_use")}; if(playlist && playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId()) CACHED_PERM_CHECK(permission::i_playlist_view_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power)); if(!ref_server->musicManager->assign_playlist(bot, playlist)) return {ErrorType::VSError}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandHelp(Command& cmd) { CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(5); PERM_CHECKR(permission::b_serverinstance_help_view, 1, false); string command = cmd[0].has("command") ? cmd["command"].as() : ""; if(command.empty()) for(const auto& key : cmd[0].keys()) { command = key; break; } if(command.empty()) command = "help"; std::transform(command.begin(), command.end(), command.begin(), ::tolower); auto file = fs::u8path("commanddocs/" + command + ".txt"); if(!fs::exists(file)) return {findError("file_not_found"), "Could not resolve file " + file.string()}; string line; ifstream stream(file); if(!stream) return {findError("file_io_error"), "Could not read documentation file " + file.string()}; while(getline(stream, line)) this->sendCommand(Command(line)); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandPermReset(ts::Command& cmd) { CMD_REQ_FSERVER; CMD_RESET_IDLE; CMD_CHK_AND_INC_FLOOD_POINTS(50); PERM_CHECKR(permission::b_virtualserver_permission_reset, 1, true); string token; if(!this->server->resetPermissions(token)) return {ErrorType::VSError, "Could not reset permissions!"}; Command result(""); result["token"] = token; this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandLogView(ts::Command& cmd) { CMD_CHK_AND_INC_FLOOD_POINTS(50); auto lagacy = this->getType() == CLIENT_TEAMSPEAK || cmd.hasParm("lagacy") || cmd.hasParm("legacy"); string log_path; ServerId target_server = cmd[0].has("instance") && cmd["instance"].as() ? (ServerId) 0 : this->getServerId(); string server_identifier; if(target_server > 0) server_identifier = to_string(target_server); else server_identifier = "[A-Z]{0,7}"; if(target_server == 0) PERM_CHECKR(permission::b_serverinstance_log_view, 1, true); else PERM_CHECKR(permission::b_virtualserver_log_view, 1, true); for(const auto& sink : logger::logger(target_server)->sinks()) { if(dynamic_pointer_cast(sink)) { log_path = dynamic_pointer_cast(sink)->_file_helper.filename(); } } if(log_path.empty()) return {findError("file_not_found"), "Cant find log file (May log disabled?)"}; { //Replace " within the log path size_t index = 0; while((index = log_path.find('"', index)) != string::npos) { log_path.replace(index, 1, "\\\""); index += 2; } } string command = "cat \"" + log_path + "\""; command += " | grep -E "; command += "\"\\] \\[.*\\]( ){0,6}?" + server_identifier + " \\|\""; size_t beginpos = cmd[0].has("begin_pos") ? cmd["begin_pos"].as() : 0ULL; //TODO test it? size_t file_index = 0; size_t max_lines = cmd[0].has("lines") ? cmd["lines"].as() : 100ULL; //TODO bounds? deque> lines; { debugMessage(target_server, "Logview command: \"{}\"", command); array buffer{}; string line_buffer; std::shared_ptr pipe(popen(command.c_str(), "r"), pclose); if (!pipe) return {findError("file_io_error"), "Could not execute command"}; while (!feof(pipe.get())) { auto read = fread(buffer.data(), 1, buffer.size(), pipe.get()); if(read > 0) { if(beginpos == 0 || file_index < beginpos) { if(beginpos != 0 && file_index + read > beginpos) { //We're done we just want to get the size later line_buffer += string(buffer.data(), beginpos - file_index); lines.push_back({file_index, line_buffer}); if(lines.size() > max_lines) lines.pop_front(); //debugMessage(LOG_GENERAL, "Final line {}", line_buffer); line_buffer = ""; } else { line_buffer += string(buffer.data(), read); size_t index; size_t length; size_t cut_offset = 0; while((index = line_buffer.find("\n")) != string::npos || (index = line_buffer.find("\r")) != string::npos) { length = 0; if(index > 0) { if(line_buffer[index - 1] == '\r' || line_buffer[index - 1] == '\n') { length = 2; index--; } } if(length == 0) { if(index + 1 < line_buffer.length()) { if(line_buffer[index + 1] == '\r' || line_buffer[index + 1] == '\n') { length = 2; } } } if(length == 0) length = 1; //debugMessage(LOG_GENERAL, "Got line {}", line_buffer.substr(0, index)); lines.push_back({file_index + cut_offset, line_buffer.substr(0, index)}); if(lines.size() > max_lines) lines.pop_front(); cut_offset += index + length; line_buffer = line_buffer.substr(index + length); } } } file_index += read; } else if(read < 0) return {findError("file_io_error"), "fread(...) returned " + to_string(read) + " (" + to_string(errno) + ")"}; } if(!line_buffer.empty()) { lines.push_back({file_index - line_buffer.length(), line_buffer}); if(lines.size() > max_lines) lines.pop_front(); } } //last_pos=1558 file_size=1764 l if(lines.empty()) return {ErrorType::DBEmpty}; Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyserverlog" : ""); result["last_pos"] = lines.front().first; result["file_size"] = file_index; if(!(cmd.hasParm("reverse") && cmd["revers"].as())) std::reverse(lines.begin(), lines.end()); int index = 0; for(const auto& index_line : lines) { auto line = index_line.second; //2018-07-15 21:01:46.488639 //TeamSpeak format: //YYYY-MM-DD hh:mm:ss.millis|{:<8}|{:<14}|{:<3}|.... //2018-07-15 21:01:47.066367|INFO |VirtualServer |1 |listening on 0.0.0.0:9989, [::]:9989 //TeaSpeak: //[2018-07-15 23:21:47] [ERROR] Timer sql_test tick needs more than 9437 microseconds. Max allowed was 5000 microseconds. if(lagacy) { string ts = line.substr(1, 19) + ".000000|"; { string type = "unknown"; auto idx = line.find_first_of('[', 2); if(idx != string::npos) { type = line.substr(idx + 1, line.find(']', idx + 1) - idx - 1); } ts += type + " | | |" + line.substr(line.find('|') + 1); } result[index++]["l"] = ts; } else { result[index++]["l"] = line; } } this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandUpdateMyTsId(ts::Command &) { if(config::voice::suppress_myts_warnings) return CommandResult::Success; return CommandResult::NotImplemented; } CommandResult ConnectedClient::handleCommandUpdateMyTsData(ts::Command &) { if(config::voice::suppress_myts_warnings) return CommandResult::Success; return CommandResult::NotImplemented; } CommandResult ConnectedClient::handleCommandQueryList(ts::Command &cmd) { OptionalServerId server_id = EmptyServerId; if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) server_id = this->getServerId(); if(cmd[0].has("server_id")) server_id = cmd["server_id"]; if(cmd[0].has("sid")) server_id = cmd["sid"]; auto server = server_id == EmptyServerId ? nullptr : serverInstance->getVoiceServerManager()->findServerById(server_id); if(!server && server_id != EmptyServerId && server_id != 0) return {findError("server_invalid_id")}; auto global_list = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_list, 1, nullptr, true, nullptr, server, true); auto own_list = global_list || this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_list_own, 1, nullptr, true, nullptr, server, true); if(!own_list && !global_list) return CommandResultPermissionError{permission::b_client_query_list}; auto accounts = serverInstance->getQueryServer()->list_query_accounts(server_id); if(!global_list) { accounts.erase(remove_if(accounts.begin(), accounts.end(), [&](const std::shared_ptr& account) { return account->unique_id != this->getUid(); }), accounts.end()); } if(accounts.empty()) return {ErrorType::DBEmpty}; Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyquerylist" : ""); result["server_id"] = server_id; result["flag_own"] = own_list; result["flag_all"] = global_list; size_t index = 0; for(const auto& account : accounts) { result[index]["client_unique_identifier"] = account->unique_id; result[index]["client_login_name"] = account->username; result[index]["client_bound_server"] = account->bound_server; index++; } this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) { OptionalServerId server_id = this->getServerId(); if(cmd[0].has("server_id")) server_id = cmd["server_id"]; if(cmd[0].has("sid")) server_id = cmd["sid"]; auto server = server_id == EmptyServerId ? nullptr : serverInstance->getVoiceServerManager()->findServerById(server_id); if(!server && server_id != EmptyServerId && server_id != 0) return {findError("server_invalid_id")}; if(!this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_create, 1, nullptr, true, nullptr, server, true)) return CommandResultPermissionError{permission::b_client_query_create}; auto username = cmd["client_login_name"].as(); auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as() : ""; if(password.empty()) password = rnd_string(QUERY_PASSWORD_LENGTH); auto account = serverInstance->getQueryServer()->find_query_account_by_name(username); if(account) return {findError("query_already_exists")}; account = serverInstance->getQueryServer()->create_query_account(username, server_id, this->getUid(), password); if(!account) return {ErrorType::VSError}; Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyquerycreated" : ""); result["client_unique_identifier"] = account->unique_id; result["client_login_name"] = account->username; result["client_login_password"] = password; result["client_bound_server"] = account->bound_server; this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandQueryDelete(ts::Command &cmd) { auto username = cmd["client_login_name"].as(); auto account = serverInstance->getQueryServer()->find_query_account_by_name(username); if(!account) return {findError("query_not_exists")}; auto server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server); /* If the server is not existing anymore, we're asking for global permissions if(!server && account->bounded_server != 0) return {findError("server_invalid_id")}; */ auto delete_all = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_delete, 1, nullptr, true, nullptr, server, true); auto delete_own = delete_all || this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_delete_own, 1, nullptr, true, nullptr, server, true); if(account->unique_id == this->getUid()) { if(!delete_own) return CommandResultPermissionError{permission::b_client_query_delete_own}; } else { if(!delete_all) return CommandResultPermissionError{permission::b_client_query_delete}; } if(account->unique_id == "serveradmin" && account->username == "serveradmin") return ErrorType::VSError; serverInstance->getQueryServer()->delete_query_account(account); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandQueryRename(ts::Command &cmd) { auto username = cmd["client_login_name"].as(); auto new_username = cmd["client_new_login_name"].as(); auto account = serverInstance->getQueryServer()->find_query_account_by_name(username); if(!account) return {findError("query_not_exists")}; auto server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server); if(!server && account->bound_server != 0) return {findError("server_invalid_id")}; auto rename_all = this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_rename, 1, nullptr, true, nullptr, server, true); auto rename_own = rename_all || this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_rename_own, 1, nullptr, true, nullptr, server, true); if(account->unique_id == this->getUid()) { if(!rename_own) return CommandResultPermissionError{permission::b_client_query_rename_own}; } else { if(!rename_all) return CommandResultPermissionError{permission::b_client_query_rename}; } auto target_account = serverInstance->getQueryServer()->find_query_account_by_name(new_username); if(target_account) return {findError("query_already_exists")}; if(account->unique_id == "serveradmin" && account->username == "serveradmin") return ErrorType::VSError; if(!serverInstance->getQueryServer()->rename_query_account(account, new_username)) return {ErrorType::VSError}; return CommandResult::Success; } CommandResult ConnectedClient::handleCommandQueryChangePassword(ts::Command &cmd) { auto username = cmd["client_login_name"].as(); auto account = serverInstance->getQueryServer()->find_query_account_by_name(username); if(!account) return {findError("query_not_exists")}; auto server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server); if(!server && account->bound_server != 0) return {findError("server_invalid_id")}; auto change_all = this->permissionGranted(permission::PERMTEST_ORDERED, server ? permission::b_client_query_change_password : permission::b_client_query_change_password_global, 1, nullptr, true, nullptr, server, true); auto change_own = change_all || this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_query_change_own_password, 1, nullptr, true, nullptr, server, true); auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as() : ""; if(password.empty()) password = rnd_string(QUERY_PASSWORD_LENGTH); if(account->unique_id == this->getUid()) { if(!change_own) return CommandResultPermissionError{permission::b_client_query_change_own_password}; } else { if(!change_all) return CommandResultPermissionError{server ? permission::b_client_query_change_password : permission::b_client_query_change_password_global}; } if(!serverInstance->getQueryServer()->change_query_password(account, password)) return {ErrorType::VSError}; Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyquerypasswordchanges" : ""); result["client_login_name"] = account->username; result["client_login_password"] = password; this->sendCommand(result); return CommandResult::Success; } CommandResult ConnectedClient::handleCommandDummy_IpChange(ts::Command &cmd) { CMD_REF_SERVER(server); logMessage(this->getServerId(), "[{}] Address changed from {} to {}", CLIENT_STR_LOG_PREFIX, cmd["old_ip"].string(), cmd["new_ip"].string()); if(geoloc::provider_vpn && !this->permissionGranted(permission::PERMTEST_ORDERED, permission::b_client_ignore_vpn, 1)) { auto provider = this->isAddressV4() ? geoloc::provider_vpn->resolveInfoV4(this->getPeerIp(), true) : geoloc::provider_vpn->resolveInfoV6(this->getPeerIp(), true); if(provider) { this->disconnect(strvar::transform(ts::config::messages::kick_vpn, strvar::StringValue{"provider.name", provider->name}, strvar::StringValue{"provider.website", provider->side})); return CommandResult::Success; } } string new_country = config::geo::countryFlag; if(geoloc::provider) { auto loc = this->isAddressV4() ? geoloc::provider->resolveInfoV4(this->getPeerIp(), false) : geoloc::provider->resolveInfoV6(this->getPeerIp(), false); if(loc) { logError(this->getServerId(), "[{}] Received new ip location. IP {} traced to {} ({}).", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp(), loc->name, loc->identifier); this->properties()[property::CLIENT_COUNTRY] = loc->identifier; server->notifyClientPropertyUpdates(_this.lock(), deque{property::CLIENT_COUNTRY}); new_country = loc->identifier; } else { logError(this->getServerId(), "[{}] Failed to resolve ip location for IP {}.", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp()); } } return CommandResult::Success; }