7233 lines
333 KiB
C++
7233 lines
333 KiB
C++
#include <memory>
|
|
|
|
#include <iostream>
|
|
#include <bitset>
|
|
#include <algorithm>
|
|
#include <openssl/sha.h>
|
|
#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 <experimental/filesystem>
|
|
#include <cstdint>
|
|
#include <StringVariable.h>
|
|
|
|
#include <Properties.h>
|
|
#include <log/LogUtils.h>
|
|
#include <misc/sassert.h>
|
|
#include <misc/base64.h>
|
|
#include <misc/hex.h>
|
|
#include <misc/digest.h>
|
|
#include <misc/rnd.h>
|
|
#include <misc/timer.h>
|
|
#include <bbcode/bbcodes.h>
|
|
|
|
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<permission::PermissionType>(); \
|
|
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<string>()); \
|
|
permType = resv->type; \
|
|
if (resv->grantName() == cmd[index]["permsid"].as<string>()) 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<string>() : cmd[index]["permsid"].as<string>())}; \
|
|
}
|
|
|
|
#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<ChannelId>(); \
|
|
auto l_channel = channel_id ? channel_tree->findLinkedChannel(command.as<ChannelId>()) : 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<ChannelId>(); \
|
|
auto l_channel = channel_id ? channel_tree->findLinkedChannel(command.as<ChannelId>()) : 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<ConnectedClient>(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<std::string>(); \
|
|
if(!cmd[0][key].castable<type>()) 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<std::string>(); \
|
|
if(!cmd[0][key].castable<type>()) 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<std::string>(); \
|
|
if(!cmd[0][key].castable<type_a>() && !!cmd[0][key].castable<type_b>()) 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<ServerId>())
|
|
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<ServerId>() != 0) return {findError("server_invalid_id")};
|
|
}
|
|
|
|
auto cache = make_shared<CalculateCache>();
|
|
map<string, string> 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<size_t>() > 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<GroupId>());
|
|
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<GroupId>());
|
|
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<GroupId>());
|
|
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<GroupId>());
|
|
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<bool>())
|
|
serverInstance->getWebList()->enable_report(target_server);
|
|
else
|
|
serverInstance->getWebList()->disable_report(target_server);
|
|
debugMessage(string() + "Changed weblist state to -> " + (cmd["virtualserver_weblist_enabled"].as<bool>() ? "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<bool>() && !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<uint64_t>());
|
|
else
|
|
toApplay["virtualserver_max_download_total_bandwidth"] = to_string(cmd["virtualserver_max_download_total_bandwidth"].as<int64_t>());
|
|
}
|
|
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<uint64_t>());
|
|
else
|
|
toApplay["virtualserver_max_upload_total_bandwidth"] = to_string(cmd["virtualserver_max_upload_total_bandwidth"].as<int64_t>());
|
|
}
|
|
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<uint64_t>());
|
|
else
|
|
toApplay["virtualserver_download_quota"] = to_string(cmd["virtualserver_download_quota"].as<int64_t>());
|
|
}
|
|
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<uint64_t>());
|
|
else
|
|
toApplay["virtualserver_upload_quota"] = to_string(cmd["virtualserver_upload_quota"].as<int64_t>());
|
|
}
|
|
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<std::string> keys;
|
|
bool group_update = false;
|
|
for (const auto& elm : toApplay) {
|
|
auto info = property::impl::info<property::VirtualServerProperties>(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<ConnectedClient>& 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<ClientId>());
|
|
shared_lock tree_lock(this->channel_lock);
|
|
|
|
if (!client || (client != this && !this->isClientVisible(client, false)))
|
|
return {findError("client_invalid_id"), ""};
|
|
|
|
deque<shared_ptr<property::PropertyDescription>> 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<ClientId>());
|
|
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<BasicChannel> targetChannel = nullptr;
|
|
auto type = cmd["reasonid"].as<ViewReasonId>();
|
|
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<std::string>(), targetChannel);
|
|
} else {
|
|
this->server->notify_client_kick(client, this->ref(), cmd["reasonmsg"].as<std::string>(), 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<BasicChannel>(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<ClientId>());
|
|
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<ConnectionInfoData>();
|
|
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<shared_ptr<ConnectedClient>> 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<string>();
|
|
notify[0]["connection_filetransfer_bytes_received_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
|
|
|
|
notify[0]["connection_packets_sent_total"] = (*statistics)[property::CONNECTION_PACKETS_SENT_TOTAL].as<string>();
|
|
notify[0]["connection_bytes_sent_total"] = (*statistics)[property::CONNECTION_BYTES_SENT_TOTAL].as<string>();
|
|
notify[0]["connection_packets_received_total"] = (*statistics)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].as<string>();
|
|
notify[0]["connection_bytes_received_total"] = (*statistics)[property::CONNECTION_BYTES_RECEIVED_TOTAL].as<string>();
|
|
|
|
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<string>();
|
|
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<std::string>();
|
|
this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL] = cmd["connection_server2client_packetloss_control"].as<std::string>();
|
|
this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH] = cmd["connection_server2client_packetloss_speech"].as<std::string>();
|
|
this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL] = cmd["connection_server2client_packetloss_total"].as<std::string>();
|
|
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<string>();
|
|
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<shared_ptr<BasicChannel>> 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<ChannelId>());
|
|
if(!local_channel)
|
|
return {findError("channel_invalid_id"), "Cant resolve channel"};
|
|
|
|
auto channel = this->server->channelTree->findChannel(cmd[index]["cid"].as<ChannelId>());
|
|
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<shared_ptr<BasicChannel>> channels;
|
|
for(int index = 0; index < cmd.bulkCount(); index++) {
|
|
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
|
|
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<permission::PermissionTypeEntry> &a, const std::shared_ptr<permission::PermissionTypeEntry> &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<ptype>()) { \
|
|
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>() == 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<GroupType>(), 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<ConnectedClient> 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<GroupId>());
|
|
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<GroupId>() != 0) {
|
|
auto target = group_manager->findGroup(cmd["tcgid"].as<GroupId>());
|
|
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<GroupType>();
|
|
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<shared_ptr<TSServer>>{this->server}))
|
|
if(server)
|
|
server->forEachClient([](shared_ptr<ConnectedClient> 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<GroupId>());
|
|
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<ConnectedClient> 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<GroupId>());
|
|
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<bool>())
|
|
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<ConnectedClient> 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<variable> 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<GroupId>() > 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<GroupId>()});
|
|
} 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<ClientDbId>() > 0) {
|
|
query += " AND `cldbid` = :cldbid";
|
|
variables.push_back({":cldbid", cmd["cldbid"].as<ClientDbId>()});
|
|
}
|
|
if(cmd[0].has("cid") && cmd["cid"].as<ChannelId>() > 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<ChannelId>()});
|
|
}
|
|
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<GroupId>());
|
|
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<GroupId>());
|
|
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<permission::PermissionValue>();
|
|
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<bool>() ? 1 : 0,
|
|
cmd[index]["permnegated"].as<bool>() ? 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<ConnectedClient> cl) {
|
|
cl->notifyChannelGroupList();
|
|
});
|
|
this->server->forEachClient([channelGroup](shared_ptr<ConnectedClient> 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<GroupId>());
|
|
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<ConnectedClient> cl) {
|
|
cl->notifyChannelGroupList();
|
|
});
|
|
this->server->forEachClient([channelGroup](shared_ptr<ConnectedClient> 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<ClientId>();
|
|
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<ChannelId>());
|
|
if (!channel) {
|
|
return {findError("channel_invalid_id")};
|
|
}
|
|
|
|
auto permission_cache = make_shared<CalculateCache>();
|
|
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<bool>() || !channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as<bool>()) {
|
|
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<bool>()) {
|
|
auto maxClients = channel->properties()[property::CHANNEL_MAXCLIENTS].as<int32_t>();
|
|
if (maxClients >= 0 && maxClients <= this->server->getClientsByChannel(channel).size())
|
|
return {findError("channel_maxclients_reached"), ""};
|
|
}
|
|
if(!channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as<bool>()) {
|
|
shared_ptr<BasicChannel> family_root;
|
|
|
|
if(channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as<bool>()) {
|
|
family_root = channel;
|
|
while(family_root && family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as<bool>()) 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<int32_t>();
|
|
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<CalculateCache>();
|
|
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<int64_t>() == 0)
|
|
if(this->server->getClientsByChannelRoot(oldChannel, false).empty())
|
|
this->server->delete_channel(dynamic_pointer_cast<ServerChannel>(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<CalculateCache>();
|
|
if (cmd[0].has("cpid") && cmd["cpid"].as<uint64_t>() != 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<bool>()) CACHED_PERM_CHECK(permission::b_channel_create_permanent, 1);
|
|
else if (cmd[0]["channel_flag_semi_permanent"].as<bool>()) 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<bool>() && !this->server) return {findError("parameter_invalid"), "You can only create a permanent channel"};
|
|
|
|
if (cmd[0]["channel_flag_default"].as<bool>()) CACHED_PERM_CHECK(permission::b_channel_create_with_default, 1);
|
|
if (cmd[0]["channel_flag_password"].as<bool>()) 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<bool>())) {
|
|
CACHED_PERM_CHECK(permission::b_channel_create_with_maxclients, 1);
|
|
if(!cmd[0]["channel_flag_permanent"].as<bool>() && !cmd[0]["channel_flag_semi_permanent"].as<bool>()) {
|
|
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<permission::PermissionValue>() : 0UL;
|
|
if(delete_delay == 0) {
|
|
if(this->server)
|
|
cmd["channel_delete_delay"] = this->server->properties()[property::VIRTUALSERVER_CHANNEL_TEMP_DELETE_DELAY_DEFAULT].as<uint32_t>();
|
|
else
|
|
cmd["channel_delete_delay"] = 0;
|
|
} else {
|
|
CACHED_PERM_CHECK(permission::i_channel_create_modify_with_temp_delete_delay, cmd["channel_delete_delay"].as<permission::PermissionValue>());
|
|
}
|
|
}
|
|
|
|
{
|
|
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<bool>())
|
|
created_perm++;
|
|
else if(channel->properties()[property::CHANNEL_FLAG_SEMI_PERMANENT].as<bool>())
|
|
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<bool>()) {
|
|
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<bool>()) {
|
|
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<TreeView::LinkedTreeEntry> parent = nullptr;
|
|
std::shared_ptr<BasicChannel> created_channel = nullptr, old_default_channel;
|
|
|
|
//bool enforce_permanent_parent = cmd[0]["channel_flag_default"].as<bool>(); //TODO check parents here
|
|
{ //Checkout the parent(s)
|
|
if (cmd[0].has("cpid") && cmd["cpid"].as<ChannelId>() != 0 && cmd["cpid"].as<int>() != -1) {
|
|
parent = target_tree->findLinkedChannel(cmd["cpid"].as<ChannelId>());
|
|
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<ServerChannel>(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<std::string>(), parent ? dynamic_pointer_cast<BasicChannel>(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>() : (ChannelId) 0, cmd["channel_name"].as<std::string>());
|
|
}
|
|
|
|
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<bool>()) {
|
|
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<property::ChannelProperties>(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<string>())) {
|
|
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as<string>() + "', Property: '" + property->name + "')");
|
|
continue;
|
|
}
|
|
created_channel->properties()[property] = cmd[prop].as<std::string>();
|
|
}
|
|
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<ConnectedClient>& 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<ServerChannel>(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<ServerChannel>(l_channel->entry);
|
|
assert(channel);
|
|
if(channel->deleted) {
|
|
/* channel gets already removed */
|
|
return CommandResult::Success;
|
|
}
|
|
|
|
std::deque<std::shared_ptr<property::PropertyDescription>> 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<property::ChannelProperties>(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<string>())) {
|
|
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<string>() == cmd[key].as<string>())
|
|
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<bool>())
|
|
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<bool>())
|
|
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<bool>()) {
|
|
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<bool>()) {
|
|
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<permission::PermissionValue>(), 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<bool>())
|
|
return {findError("parameter_missing"), ""};
|
|
else
|
|
cmd["channel_password"] = ""; /* no password set */
|
|
keys.push_back(property::info<property::ChannelProperties>(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::ChannelProperties>(property::CHANNEL_FLAG_PASSWORD));
|
|
}
|
|
|
|
if(cmd["channel_flag_password"].as<bool>()) {
|
|
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<bool>()) || channel->properties()[property::CHANNEL_FLAG_PASSWORD]) {
|
|
cmd["channel_flag_password"] = false;
|
|
cmd["channel_password"] = "";
|
|
keys.push_back(property::info<property::ChannelProperties>(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::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
|
|
keys.push_back(property::info<property::ChannelProperties>(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::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
|
|
keys.push_back(property::info<property::ChannelProperties>(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<int>() != -1) {
|
|
cmd["channel_maxclients"] = -1;
|
|
cmd["channel_flag_maxclients_unlimited"] = true;
|
|
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
|
|
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
|
|
update_max_clients = true;
|
|
}
|
|
if(channel->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as<int>() != -1) {
|
|
cmd["channel_maxfamilyclients"] = -1;
|
|
cmd["channel_flag_maxfamilyclients_inherited"] = true;
|
|
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
|
|
keys.push_back(property::info<property::ChannelProperties>(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<bool>())
|
|
cmd["channel_maxclients"] = -1;
|
|
else
|
|
return {findError("parameter_missing"), "channel_maxclients"}; /* max clients must be specified */
|
|
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
|
|
}
|
|
|
|
if(!cmd[0].has("channel_flag_maxclients_unlimited")) {
|
|
cmd["channel_flag_maxclients_unlimited"] = cmd["channel_maxclients"].as<int>() < 0;
|
|
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
|
|
}
|
|
|
|
if(cmd["channel_flag_maxclients_unlimited"].as<bool>() && cmd["channel_maxclients"].as<int>() != -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<bool>() && 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<bool>())
|
|
cmd["channel_flag_maxfamilyclients_inherited"] = false;
|
|
else
|
|
cmd["channel_flag_maxfamilyclients_inherited"] = true;
|
|
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
|
|
} else if(cmd[0].has("channel_flag_maxfamilyclients_inherited")) {
|
|
if(cmd["channel_flag_maxfamilyclients_inherited"].as<bool>())
|
|
cmd["channel_flag_maxfamilyclients_unlimited"] = false;
|
|
else
|
|
cmd["channel_flag_maxfamilyclients_unlimited"] = true;
|
|
keys.push_back(property::info<property::ChannelProperties>(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::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
|
|
}
|
|
//keep this order because this command: "channeledit cid=<x> 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<bool>();
|
|
if(flag_unlimited)
|
|
cmd["channel_flag_maxfamilyclients_inherited"] = false;
|
|
else
|
|
cmd["channel_flag_maxfamilyclients_inherited"] = cmd["channel_maxfamilyclients"].as<int>() < 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<bool>();
|
|
if(flag_inherited)
|
|
cmd["channel_flag_maxfamilyclients_unlimited"] = false;
|
|
else
|
|
cmd["channel_flag_maxfamilyclients_unlimited"] = cmd["channel_maxfamilyclients"].as<int>() < 0;
|
|
}
|
|
|
|
if(cmd["channel_flag_maxfamilyclients_inherited"].as<bool>() && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>())
|
|
return {findError("channel_invalid_flags")}; /* both at the same time are not possible */
|
|
|
|
if(cmd["channel_flag_maxfamilyclients_inherited"].as<bool>() && cmd["channel_maxfamilyclients"].as<int>() != -1)
|
|
return {findError("channel_invalid_flags")}; /* flag inherited required max users to be -1 */
|
|
|
|
if(cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>() && cmd["channel_maxfamilyclients"].as<int>() != -1)
|
|
return {findError("channel_invalid_flags")}; /* flag unlimited required max users to be -1 */
|
|
|
|
if(cmd["channel_maxfamilyclients"].as<int>() != -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<BasicChannel> old_default_channel;
|
|
deque<shared_ptr<BasicChannel>> child_channel_updated;
|
|
for(const std::shared_ptr<property::PropertyDescription>& key : keys) {
|
|
if(*key == property::CHANNEL_ORDER) {
|
|
/* TODO: May move that up because if it fails may some other props have already be applied */
|
|
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<ConnectedClient>& cl) {
|
|
unique_lock client_channel_lock(cl->channel_lock);
|
|
|
|
auto actions = cl->channels->change_order(l_channel, parent, previous);
|
|
std::deque<ChannelId> 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<bool>()) {
|
|
old_default_channel = nullptr;
|
|
continue;
|
|
}
|
|
|
|
channel_tree->setDefaultChannel(channel);
|
|
|
|
deque<shared_ptr<BasicChannel>> updated_channels;
|
|
shared_ptr<BasicChannel> 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<ConnectedClient>& 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<shared_ptr<BasicChannel>> 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<string> key_vector;
|
|
key_vector.reserve(keys.size());
|
|
for(const auto& key : keys)
|
|
key_vector.push_back(key->name);
|
|
|
|
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& 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<ServerChannel>(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<TreeView::LinkedTreeEntry> l_order;
|
|
if(cmd[0].has("order")) {
|
|
l_order = channel_tree->findLinkedChannel(cmd["order"]);
|
|
if (!l_order && cmd["order"].as<ChannelId>() != 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<ServerChannel>(l_parent->entry) : nullptr;
|
|
auto order = l_order ? dynamic_pointer_cast<ServerChannel>(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<shared_ptr<BasicChannel>> 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<ServerChannel>(current_channel->parent())));
|
|
}
|
|
|
|
if(this->server) {
|
|
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& 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<ChannelId> 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<string>{"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<ClientId>());
|
|
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<BasicChannel>(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<BasicChannel>(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<permission::PermissionValue>();
|
|
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<bool>() ? 1 : 0,
|
|
cmd[index]["permnegated"].as<bool>() ? 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<ConnectedClient> 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<string> keys;
|
|
keys.reserve(updates.size());
|
|
|
|
for(auto& property : updates)
|
|
keys.push_back(property::info(property)->name);
|
|
|
|
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> 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<ConnectedClient>& 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<ChannelId> 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<BasicChannel>(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<string> keys;
|
|
keys.reserve(updates.size());
|
|
|
|
for(auto& property : updates)
|
|
keys.push_back(property::info(property)->name);
|
|
|
|
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
|
|
shared_lock client_channel_lock(cl->channel_lock);
|
|
cl->notifyChannelEdited(channel, keys, this);
|
|
});
|
|
}
|
|
}
|
|
|
|
if(updateClients && this->server)
|
|
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
|
|
if(cl->currentChannel == channel)
|
|
cl->updateChannelClientProperties(true, true);
|
|
});
|
|
if(update_view && this->server) {
|
|
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> 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<ChannelId> 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>() == 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>() == 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<GroupType>(), 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<ConnectedClient> 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<GroupId>());
|
|
if (!src || src->target() != GROUPTARGET_SERVER) return {findError("parameter_invalid"), "invalid server group id"};
|
|
if(cmd[0].has("tsgid") && cmd["tsgid"].as<GroupId>() != 0) {
|
|
auto target = group_manager->findGroup(cmd["tsgid"].as<GroupId>());
|
|
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<GroupType>();
|
|
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<shared_ptr<TSServer>>{this->server}))
|
|
if(server)
|
|
server->forEachClient([](shared_ptr<ConnectedClient> 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<GroupId>());
|
|
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<ConnectedClient> 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<GroupId>());
|
|
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<bool>())
|
|
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<ConnectedClient> 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<GroupId>());
|
|
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<GroupId>();
|
|
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<GroupId>());
|
|
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<ClientDbId>();
|
|
{
|
|
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<shared_ptr<TSServer>>{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<GroupId>());
|
|
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<ClientDbId>();
|
|
{
|
|
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<shared_ptr<TSServer>>{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<GroupId>());
|
|
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<GroupId>());
|
|
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<permission::PermissionValue>();
|
|
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<bool>() ? 1 : 0,
|
|
cmd[index]["permnegated"].as<bool>() ? 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<ConnectedClient> cl) {
|
|
cl->notifyServerGroupList();
|
|
});
|
|
server->forEachClient([serverGroup, checkTp](shared_ptr<ConnectedClient> 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<GroupId>());
|
|
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<ConnectedClient> cl) {
|
|
cl->notifyServerGroupList();
|
|
});
|
|
server->forEachClient([serverGroup, checkTp](shared_ptr<ConnectedClient> 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<shared_ptr<Group>> groups;
|
|
for(const auto& group : this->server->groups->availableGroups(false)) {
|
|
if(group->updateType() == cmd["sgtype"].as<permission::PermissionValue>() && 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<permission::PermissionValue>();
|
|
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<bool>() ? 1 : 0,
|
|
cmd[index]["permnegated"].as<bool>() ? 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<ConnectedClient> cl) {
|
|
cl->notifyServerGroupList();
|
|
});
|
|
server->forEachClient([groups, checkTp](shared_ptr<ConnectedClient> 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<shared_ptr<Group>> groups;
|
|
for(const auto& group : this->server->groups->availableGroups(false)) {
|
|
if(group->updateType() == cmd["sgtype"].as<permission::PermissionValue>() && 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<ConnectedClient> cl) {
|
|
cl->notifyServerGroupList();
|
|
});
|
|
server->forEachClient([groups, checkTp](shared_ptr<ConnectedClient> 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<GroupId>());
|
|
if (!serverGroup && cmd["gcid"].as<GroupId>() == 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<BasicChannel> channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
|
|
if (!channel) return {findError("channel_invalid_id"), "Cant resolve channel"};
|
|
|
|
auto target_cldbid = cmd["cldbid"].as<ClientDbId>();
|
|
{
|
|
|
|
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 <clid>
|
|
CommandResult ConnectedClient::handleCommandSendTextMessage(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
|
|
|
if (cmd["targetmode"].as<ChatMessageMode>() == ChatMessageMode::TEXTMODE_PRIVATE) {
|
|
auto target = this->server->findClient(cmd["target"].as<ClientId>());
|
|
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<ConnectedClient>& 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<ConnectedClient> 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<ClientId>());
|
|
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<ClientId>());
|
|
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<ConnectedClient>& 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<ConnectedClient>& 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<std::shared_ptr<file::FileEntry>> 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<std::chrono::seconds>(fileEntry->lastChanged.time_since_epoch()).count();
|
|
fileList[index]["type"] = fileEntry->type;
|
|
if (fileEntry->type == file::FileType::FILE)
|
|
fileList[index]["size"] = static_pointer_cast<file::File>(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<std::string>();
|
|
fileList["return_code"] = code;
|
|
fileListFinished["path"] = cmd["path"].as<std::string>();
|
|
fileList["cid"] = cmd["cid"].as<size_t>();
|
|
fileListFinished["cid"] = cmd["cid"].as<size_t>();
|
|
|
|
if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel
|
|
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
|
|
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<std::string>())));
|
|
} else {
|
|
if (cmd["path"].as<string>() == "/icons" || cmd["path"].as<string>() == "/icons/") {
|
|
appendFileList(fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->iconDirectory(this->server)));
|
|
PERM_CHECKR(permission::b_icon_manage, 1, true);
|
|
} else if (cmd["path"].as<string>() == "/") {
|
|
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<std::string>() << "'" << endl;
|
|
return CommandResult::NotImplemented;
|
|
}
|
|
}
|
|
|
|
if (fileList[0].has("name")) {
|
|
if(dynamic_cast<VoiceClient*>(this)) {
|
|
dynamic_cast<VoiceClient*>(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<ChannelId>());
|
|
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<std::string>()));
|
|
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<std::shared_ptr<file::FileEntry>> files;
|
|
|
|
if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel
|
|
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
|
|
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<std::string>(), 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::ClientProperties>{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::ClientProperties>{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<std::string>() << 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<file::Directory> directory = nullptr;
|
|
|
|
if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel
|
|
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
|
|
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<std::string>().empty() && cmd["name"].as<std::string>().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<size_t>()) return CommandResultPermissionError{permission::i_max_icon_filesize};
|
|
directory = serverInstance->getFileServer()->iconDirectory(this->server);
|
|
} else if (cmd["path"].as<std::string>().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<size_t>()) 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<std::string>() << 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<ssize_t>();
|
|
auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as<size_t>();
|
|
server_used_quota += cmd["size"].as<uint64_t>();
|
|
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<size_t>();
|
|
client_used_quota += cmd["size"].as<uint64_t>();
|
|
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<bool>() && cmd["resume"].as<bool>()) return {findError("file_overwrite_excludes_resume"), "funny"};
|
|
if (serverInstance->getFileServer()->findFile(cmd["name"].as<std::string>(), directory) && !(cmd["overwrite"].as<bool>() || cmd["resume"].as<bool>()))
|
|
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<uint64_t>(), 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<uint64_t>();
|
|
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<file::Directory> 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<ChannelId>());
|
|
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<std::string>().empty() && cmd["name"].as<std::string>().find("/icon_") == 0) {
|
|
directory = serverInstance->getFileServer()->iconDirectory(this->server);
|
|
} else if (cmd["path"].as<std::string>().empty() && cmd["name"].as<std::string>().find("/avatar_") == 0) { //TODO fix avatar download not working
|
|
directory = serverInstance->getFileServer()->avatarDirectory(this->server);
|
|
} else {
|
|
cerr << "Unknown requested directory: " << cmd["path"].as<std::string>() << 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<std::string>(), 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<std::string>(), 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<ssize_t>();
|
|
auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as<size_t>();
|
|
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<size_t>();
|
|
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<uint64_t>();
|
|
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::FileEntry> file;
|
|
if (request.has("cid") && request["cid"].as<ChannelId>() != 0) { //Channel
|
|
auto channel = this->server->channelTree->findChannel(request["cid"].as<ChannelId>());
|
|
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<file::Directory> directory;
|
|
if (!request.has("path") || request["path"].as<string>() == "/") {
|
|
directory = serverInstance->getFileServer()->avatarDirectory(this->server);
|
|
} else if (request["path"].as<string>() == "/icons" || request["path"].as<string>() == "/icons/") {
|
|
directory = serverInstance->getFileServer()->iconDirectory(this->server);
|
|
} else {
|
|
cerr << "Unknown requested directory: '" << request["path"].as<std::string>() << "'" << endl;
|
|
return CommandResult::NotImplemented;
|
|
}
|
|
|
|
if(!directory) continue;
|
|
file = serverInstance->getFileServer()->findFile(cmd["name"].as<std::string>(), directory);
|
|
}
|
|
if(!file) continue;
|
|
|
|
result[result_index]["cid"] = request["cid"].as<ChannelId>();
|
|
result[result_index]["name"] = request["name"];
|
|
result[result_index]["path"] = request["path"];
|
|
result[result_index]["type"] = file->type;
|
|
result[result_index]["datetime"] = duration_cast<seconds>(file->lastChanged.time_since_epoch()).count();
|
|
if (file->type == file::FileType::FILE)
|
|
result[result_index]["size"] = static_pointer_cast<file::File>(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<chrono::seconds>(elm->created.time_since_epoch()).count();
|
|
if (elm->until.time_since_epoch().count() != 0)
|
|
notify[index]["duration"] = chrono::duration_cast<chrono::seconds>(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<int64_t>() : 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<chrono::system_clock> until = time > 0 ? chrono::system_clock::now() + chrono::seconds(time) : chrono::time_point<chrono::system_clock>();
|
|
|
|
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<shared_ptr<TSServer>>{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<uint64_t>());
|
|
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<string>();
|
|
changed |= true;
|
|
}
|
|
|
|
if (cmd[0].has("name")) {
|
|
ban->name = cmd["name"].as<string>();
|
|
changed |= true;
|
|
}
|
|
|
|
if (cmd[0].has("uid")) {
|
|
ban->uid = cmd["uid"].as<string>();
|
|
changed |= true;
|
|
}
|
|
|
|
if (cmd[0].has("reason")) {
|
|
ban->reason = cmd["reason"].as<string>();
|
|
changed |= true;
|
|
}
|
|
if (cmd[0].has("banreason")) {
|
|
ban->reason = cmd["banreason"].as<string>();
|
|
changed |= true;
|
|
}
|
|
|
|
if (cmd[0].has("time")) {
|
|
ban->until = ban->created + seconds(cmd["time"].as<size_t>());
|
|
changed |= true;
|
|
}
|
|
|
|
if (cmd[0].has("hwid")) {
|
|
ban->hwid = cmd["hwid"].as<string>();
|
|
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<uint64_t>() : 0UL;
|
|
chrono::time_point<chrono::system_clock> until = time > 0 ? chrono::system_clock::now() + chrono::seconds(time) : chrono::time_point<chrono::system_clock>();
|
|
|
|
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<shared_ptr<ConnectedClient>> 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<ClientId>())};
|
|
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<BanId> 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<uint64_t>());
|
|
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<int64_t>() : 0, cmd[0].has("limit") ? cmd["limit"].as<int64_t>() : -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<milliseconds>(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<chrono::seconds>(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<TokenType>(cmd["tokentype"].as<uint32_t>());
|
|
auto gId = cmd["tokenid1"].as<GroupId>();
|
|
auto cId = cmd["tokenid2"].as<ChannelId>();
|
|
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<BasicChannel> 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<string>()) {
|
|
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<TSServer> 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<int>() > 2000) cmd["duration"] = 2000;
|
|
if (cmd[0]["duration"].as<int>() < 1) cmd["duration"] = 1;
|
|
|
|
auto maxIndex = cmd["start"].as<uint32_t>() + cmd["duration"].as<uint32_t>();
|
|
ClientDbArgs args;
|
|
args.server = this->server;
|
|
args.offset = cmd["start"].as<uint32_t>();
|
|
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<string>();
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as<string>();
|
|
|
|
if (pArgs->largeInfo) {
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_badges"] = (*props)[property::CLIENT_BADGES].as<string>();
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_version"] = (*props)[property::CLIENT_VERSION].as<string>();
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_platform"] = (*props)[property::CLIENT_PLATFORM].as<string>();
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as<string>();
|
|
}
|
|
}
|
|
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<size_t>(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<property::ClientProperties>(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<string>())) {
|
|
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[elm].as<string>() + "', 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<PluginTargetMode>();
|
|
|
|
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<ClientId>();
|
|
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<ClientId>());
|
|
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<ClientId>());
|
|
if (!client) return {findError("client_invalid_id"), "invalid client id"};
|
|
return this->handleCommandClientEdit(cmd, client);
|
|
}
|
|
|
|
CommandResult ConnectedClient::handleCommandClientEdit(Command &cmd, const std::shared_ptr<ConnectedClient>& 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<lock_guard<std::recursive_mutex>> nickname_lock;
|
|
deque<pair<property::ClientProperties, string>> keys;
|
|
for(const auto& key : cmd[0].keys()) {
|
|
if(key == "return_code") continue;
|
|
if(key == "clid") continue;
|
|
|
|
const auto &info = property::info<property::ClientProperties>(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<string>())) {
|
|
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<string>() == cmd[key].as<string>()) 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<bool>();
|
|
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<lock_guard<recursive_mutex>>(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<MusicClient>(client);
|
|
assert(bot);
|
|
|
|
auto volume = cmd["player_volume"].as<float>();
|
|
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<bool>()) {
|
|
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<bool>()) {
|
|
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<bool>())
|
|
cmd["client_talk_request"] = duration_cast<seconds>(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<ConnectedClient>(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::value>() == MusicClient::UptimeMode::TIME_SINCE_SERVER_START) {
|
|
cmd["client_lastconnected"] = duration_cast<seconds>(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<MusicClient::Type::value>();
|
|
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<property::ClientProperties> 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<bool>() ? 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<ClientId>());
|
|
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<ClientId>());
|
|
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<ConnectedClient>& 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<ConnectedClient> 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<string>();
|
|
result[index]["client_away_message"] = client->properties()[property::CLIENT_AWAY_MESSAGE].as<string>();
|
|
}
|
|
if (cmd.hasParm("groups")) {
|
|
result[index]["client_channel_group_id"] = client->properties()[property::CLIENT_CHANNEL_GROUP_ID].as<string>();
|
|
result[index]["client_servergroups"] = client->properties()[property::CLIENT_SERVERGROUPS].as<string>();
|
|
result[index]["client_channel_group_inherited_channel_id"] = client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID].as<string>();
|
|
}
|
|
if (cmd.hasParm("times")) {
|
|
result[index]["client_idle_time"] = duration_cast<milliseconds>(system_clock::now() - client->idleTimestamp).count();
|
|
result[index]["client_total_online_time"] = client->properties()[property::CLIENT_TOTAL_ONLINE_TIME].as<int64_t>() + duration_cast<seconds>(system_clock::now() - client->lastOnlineTimestamp).count();
|
|
result[index]["client_month_online_time"] = client->properties()[property::CLIENT_MONTH_ONLINE_TIME].as<int64_t>() + duration_cast<seconds>(system_clock::now() - client->lastOnlineTimestamp).count();
|
|
result[index]["client_idle_time"] = duration_cast<milliseconds>(system_clock::now() - client->idleTimestamp).count();
|
|
result[index]["client_created"] = client->properties()[property::CLIENT_CREATED].as<string>();
|
|
result[index]["client_lastconnected"] = client->properties()[property::CLIENT_LASTCONNECTED].as<string>();
|
|
}
|
|
if (cmd.hasParm("info")) {
|
|
result[index]["client_version"] = client->properties()[property::CLIENT_VERSION].as<string>();
|
|
result[index]["client_platform"] = client->properties()[property::CLIENT_PLATFORM].as<string>();
|
|
}
|
|
|
|
if (cmd.hasParm("badges"))
|
|
result[index]["client_badges"] = client->properties()[property::CLIENT_BADGES].as<string>();
|
|
if (cmd.hasParm("country"))
|
|
result[index]["client_country"] = client->properties()[property::CLIENT_COUNTRY].as<string>();
|
|
if (cmd.hasParm("ip"))
|
|
result[index]["connection_client_ip"] = allow_ip ? client->properties()[property::CONNECTION_CLIENT_IP].as<string>() : "hidden";
|
|
if (cmd.hasParm("icon"))
|
|
result[index]["client_icon_id"] = client->properties()[property::CLIENT_ICON_ID].as<string>();
|
|
|
|
if (cmd.hasParm("voice")) {
|
|
result[index]["client_talk_power"] = client->properties()[property::CLIENT_TALK_POWER].as<string>();
|
|
result[index]["client_flag_talking"] = client->properties()[property::CLIENT_FLAG_TALKING].as<string>();
|
|
result[index]["client_input_muted"] = client->properties()[property::CLIENT_INPUT_MUTED].as<string>();
|
|
result[index]["client_output_muted"] = client->properties()[property::CLIENT_OUTPUT_MUTED].as<string>();
|
|
result[index]["client_input_hardware"] = client->properties()[property::CLIENT_INPUT_HARDWARE].as<string>();
|
|
result[index]["client_output_hardware"] = client->properties()[property::CLIENT_OUTPUT_HARDWARE].as<string>();
|
|
result[index]["client_is_talker"] = client->properties()[property::CLIENT_IS_TALKER].as<string>();
|
|
result[index]["client_is_priority_speaker"] = client->properties()[property::CLIENT_IS_PRIORITY_SPEAKER].as<string>();
|
|
result[index]["client_is_recording"] = client->properties()[property::CLIENT_IS_RECORDING].as<string>();
|
|
result[index]["client_is_channel_commander"] = client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as<string>();
|
|
|
|
}
|
|
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<string>();
|
|
result["virtualserver_port"] = 0;
|
|
if (this->server->udpVoiceServer) {
|
|
result["virtualserver_port"] = this->server->properties()[property::VIRTUALSERVER_PORT].as_save<uint16_t>();
|
|
}
|
|
} 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<string>();
|
|
result["client_unique_identifier"] = this->getUid();
|
|
|
|
{
|
|
auto query = dynamic_cast<QueryClient*>(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<string> unique_ids;
|
|
for(int index = 0; index < cmd.bulkCount(); index++)
|
|
unique_ids.push_back(cmd[index]["cluid"].as<string>());
|
|
|
|
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<ClientDbId> dbids;
|
|
for(int index = 0; index < cmd.bulkCount(); index++)
|
|
dbids.push_back(cmd[index]["cldbid"].as<ClientDbId>());
|
|
|
|
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<string> unique_ids;
|
|
for(int index = 0; index < cmd.bulkCount(); index++)
|
|
unique_ids.push_back(cmd[index]["cluid"].as<string>());
|
|
|
|
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<ClientId>();
|
|
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<permission::PermissionValue>();
|
|
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<BasicChannel> channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
|
|
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<ClientDbId>());
|
|
|
|
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<ClientDbId>();
|
|
|
|
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<BasicChannel> channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
|
|
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<BasicChannel> channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
|
|
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<permission::PermissionValue>();
|
|
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<ClientDbId> 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<chrono::seconds>(info->created.time_since_epoch()).count();
|
|
res[index]["client_lastconnected"] = chrono::duration_cast<chrono::seconds>(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<string>();
|
|
else
|
|
res[index]["client_lastip"] = "hidden";
|
|
|
|
res[index]["client_icon_id"] = (*props)[property::CLIENT_ICON_ID].as<string>();
|
|
res[index]["client_badges"] = (*props)[property::CLIENT_BADGES].as<string>();
|
|
res[index]["client_version"] = (*props)[property::CLIENT_VERSION].as<string>();
|
|
res[index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as<string>();
|
|
res[index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as<string>();
|
|
res[index]["client_total_bytes_downloaded"] = (*props)[property::CLIENT_TOTAL_BYTES_DOWNLOADED].as<string>();
|
|
res[index]["client_total_bytes_uploaded"] = (*props)[property::CLIENT_TOTAL_BYTES_UPLOADED].as<string>();
|
|
res[index]["client_month_bytes_downloaded"] = (*props)[property::CLIENT_MONTH_BYTES_DOWNLOADED].as<string>();
|
|
res[index]["client_month_bytes_uploaded"] = (*props)[property::CLIENT_MONTH_BYTES_DOWNLOADED].as<string>();
|
|
res[index]["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as<string>();
|
|
res[index]["client_flag_avatar"] = (*props)[property::CLIENT_FLAG_AVATAR].as<string>();
|
|
|
|
res[index]["client_month_online_time"] = (*props)[property::CLIENT_MONTH_ONLINE_TIME].as<string>();
|
|
res[index]["client_total_online_time"] = (*props)[property::CLIENT_TOTAL_ONLINE_TIME].as<string>();
|
|
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<string>();
|
|
} else {
|
|
ptr->cmd[ptr->index]["client_lastip"] = "hidden";
|
|
}
|
|
ptr->cmd[ptr->index]["client_badges"] = (*props)[property::CLIENT_BADGES].as<string>();
|
|
ptr->cmd[ptr->index]["client_version"] = (*props)[property::CLIENT_VERSION].as<string>();
|
|
ptr->cmd[ptr->index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as<string>();
|
|
ptr->cmd[ptr->index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as<string>();
|
|
}
|
|
}
|
|
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<ClientId>();
|
|
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<string>();
|
|
else
|
|
res[result_index]["connection_client_ip"] = "hidden";
|
|
res[result_index]["client_idle_time"] = duration_cast<milliseconds>(system_clock::now() - client->idleTimestamp).count();
|
|
res[result_index]["connection_connected_time"] = duration_cast<milliseconds>(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<seconds>(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<BasicChannel> channel = (this->server ? this->server->channelTree : serverInstance->getChannelTree().get())->findChannel(cmd["cid"].as<ChannelId>());
|
|
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<seconds>(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<seconds>(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<permission::PermissionType> requrested;
|
|
for (int index = 0; index < cmd.bulkCount(); index++) {
|
|
permission::PermissionType permType = permission::unknown;
|
|
if (cmd[index].has("permid"))
|
|
permType = cmd[index]["permid"].as<permission::PermissionType>();
|
|
else if (cmd[index].has("permsid"))
|
|
permType = permission::resolvePermissionData(cmd[index]["permsid"].as<string>())->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<string>());
|
|
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<pair<pair<string, permission::PermissionType>, bool>> permissions;
|
|
std::shared_ptr<permission::PermissionTypeEntry> 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<permission::PermissionType>() & (~PERM_ID_GRANT)));
|
|
granted = (cmd[index]["permid"].as<permission::PermissionType>() & 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<string>());
|
|
granted = permission->grant_name == cmd[index]["permsid"].as<string>();
|
|
|
|
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<pair<string, permission::PermissionType>, bool>{{permission->name, permission->type}, granted});
|
|
}
|
|
|
|
if(permissions.empty())
|
|
return {findError("database_empty_result")};
|
|
|
|
map<string, uint8_t> flags;
|
|
map<string, permission::PermissionType> 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<unique_ptr<PermissionEntry>> 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<permission::PermissionSqlType>(stoll(values[index]));
|
|
else if(columns[index] == "permId")
|
|
permission_name = values[index];
|
|
else if(columns[index] == "id")
|
|
id = static_cast<uint64_t>(stoll(values[index]));
|
|
else if(columns[index] == "channelId")
|
|
channel_id = static_cast<ChannelId>(stoll(values[index]));
|
|
else if(columns[index] == "value")
|
|
value = static_cast<permission::PermissionValue>(stoll(values[index]));
|
|
else if(columns[index] == "grant")
|
|
granted_value = static_cast<permission::PermissionValue>(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<PermissionEntry>();
|
|
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<PermissionEntry>();
|
|
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<CommandPerm> 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<ClientDbId>();
|
|
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<BasicChannel> channel = (this->server ? this->server->channelTree : serverInstance->getChannelTree().get())->findChannel(cmd["cid"].as<ChannelId>());
|
|
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<ClientDbId>() : 0;
|
|
auto list = id == 0 ? this->server->complains->complains() : this->server->complains->findComplainsFromTarget(id);
|
|
if (list.empty()) return {findError("database_empty_result"), "empty!"};
|
|
|
|
deque<ClientDbId> 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<chrono::seconds>(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<ComplainEntry> 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<permission::PermissionType, permission::PermissionValue>(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<MusicClient::Type::value>();
|
|
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<ClientDbId>() != 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<ts::music::SongInfo>& 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<ts::music::PlayableSong>(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<int64_t>()));
|
|
} else if(cmd["action"] == 6) {
|
|
if(!bot->current_player()) return {findError("music_no_player")};
|
|
bot->current_player()->rewind(::music::PlayerUnits(cmd["units"].as<int64_t>()));
|
|
} 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<music::PlayablePlaylist>& 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<pair<shared_ptr<property::PropertyDescription>, string>> properties;
|
|
|
|
for(const auto& key : cmd[0].keys()) {
|
|
if(key == "playlist_id") continue;
|
|
if(key == "return_code") continue;
|
|
|
|
auto property = property::info<property::PlaylistProperties>(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<string>())) {
|
|
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<SongId>();
|
|
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<int32_t>();
|
|
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<permission::PermissionValue>();
|
|
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<int>() && type.as<int>() == 0) || type.as<string>() == "yt") {
|
|
loader = bot->ytLoader(this->getServer());
|
|
} else if((type.castable<int>() && type.as<int>() == 1) || type.as<string>() == "ffmpeg") {
|
|
loader = bot->ffmpegLoader(this->getServer());
|
|
} else if((type.castable<int>() && type.as<int>() == 2) || type.as<string>() == "channel") {
|
|
loader = bot->channelLoader(this->getServer());
|
|
} else if((type.castable<int>() && type.as<int>() == -1) || type.as<string>() == "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<ConnectedClient> 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<std::shared_ptr<music::SongInfo>> 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<music::PlayableSong>(entry));
|
|
this->server->forEachClient([&](shared_ptr<ConnectedClient> 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<ConnectedClient> 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<string>() : "";
|
|
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<bool>() ? (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<logger::ColoredFileSink>(sink)) {
|
|
log_path = dynamic_pointer_cast<logger::ColoredFileSink>(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<size_t>() : 0ULL; //TODO test it?
|
|
size_t file_index = 0;
|
|
size_t max_lines = cmd[0].has("lines") ? cmd["lines"].as<size_t>() : 100ULL; //TODO bounds?
|
|
deque<pair<uintptr_t, string>> lines;
|
|
{
|
|
debugMessage(target_server, "Logview command: \"{}\"", command);
|
|
|
|
array<char, 1024> buffer{};
|
|
string line_buffer;
|
|
|
|
std::shared_ptr<FILE> 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<bool>()))
|
|
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<QueryAccount>& 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<string>();
|
|
auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as<string>() : "";
|
|
|
|
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<string>();
|
|
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<string>();
|
|
auto new_username = cmd["client_new_login_name"].as<string>();
|
|
|
|
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<string>();
|
|
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<string>() : "";
|
|
|
|
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::ClientProperties>{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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|