Teaspeak-Server/server/src/client/command_handler/misc.cpp

2882 lines
129 KiB
C++

//
// Created by wolverindev on 26.01.20.
//
#include <memory>
#include <iostream>
#include <bitset>
#include <algorithm>
#include <openssl/sha.h>
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../music/MusicClient.h"
#include "../query/QueryClient.h"
#include "../../weblist/WebListManager.h"
#include "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include "../../manager/ActionLogger.h"
#include <experimental/filesystem>
#include <cstdint>
#include <StringVariable.h>
#include "helpers.h"
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <misc/rnd.h>
#include <misc/strobf.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;
#define QUERY_PASSWORD_LENGTH 12
command_result ConnectedClient::handleCommand(Command &cmd) {
threads::MutexLock l2(this->command_lock);
auto command = cmd.command();
if (command == "servergetvariables") return this->handleCommandServerGetVariables(cmd);
else if (command == "serverrequestconnectioninfo") return this->handleCommandServerRequestConnectionInfo(cmd);
else if (command == "getconnectioninfo") return this->handleCommandGetConnectionInfo(cmd);
else if (command == "setconnectioninfo") return this->handleCommandSetConnectionInfo(cmd);
else if (command == "clientgetvariables") return this->handleCommandClientGetVariables(cmd);
else if (command == "serveredit") return this->handleCommandServerEdit(cmd);
else if (command == "clientedit") return this->handleCommandClientEdit(cmd);
else if (command == "channelgetdescription") return this->handleCommandChannelGetDescription(cmd);
else if (command == "connectioninfoautoupdate") return this->handleCommandConnectionInfoAutoUpdate(cmd);
else if (command == "permissionlist") return this->handleCommandPermissionList(cmd);
else if (command == "propertylist") return this->handleCommandPropertyList(cmd);
//Server group
else if (command == "servergrouplist") return this->handleCommandServerGroupList(cmd);
else if (command == "servergroupadd") return this->handleCommandServerGroupAdd(cmd);
else if (command == "servergroupcopy") return this->handleCommandServerGroupCopy(cmd);
else if (command == "servergroupdel") return this->handleCommandServerGroupDel(cmd);
else if (command == "servergrouprename") return this->handleCommandServerGroupRename(cmd);
else if (command == "servergroupclientlist") return this->handleCommandServerGroupClientList(cmd);
else if (command == "servergroupaddclient" || command == "clientaddservergroup") return this->handleCommandServerGroupAddClient(cmd);
else if (command == "servergroupdelclient" || command == "clientdelservergroup") return this->handleCommandServerGroupDelClient(cmd);
else if (command == "servergrouppermlist") return this->handleCommandServerGroupPermList(cmd);
else if (command == "servergroupaddperm") return this->handleCommandServerGroupAddPerm(cmd);
else if (command == "servergroupdelperm") return this->handleCommandServerGroupDelPerm(cmd);
else if (command == "setclientchannelgroup") return this->handleCommandSetClientChannelGroup(cmd);
//Channel basic actions
else if (command == "channelcreate") return this->handleCommandChannelCreate(cmd);
else if (command == "channelmove") return this->handleCommandChannelMove(cmd);
else if (command == "channeledit") return this->handleCommandChannelEdit(cmd);
else if (command == "channeldelete") return this->handleCommandChannelDelete(cmd);
//Find a channel and get informations
else if (command == "channelfind") return this->handleCommandChannelFind(cmd);
else if (command == "channelinfo") return this->handleCommandChannelInfo(cmd);
//Channel perm actions
else if (command == "channelpermlist") return this->handleCommandChannelPermList(cmd);
else if (command == "channeladdperm") return this->handleCommandChannelAddPerm(cmd);
else if (command == "channeldelperm") return this->handleCommandChannelDelPerm(cmd);
//Channel group actions
else if (command == "channelgroupadd") return this->handleCommandChannelGroupAdd(cmd);
else if (command == "channelgroupcopy") return this->handleCommandChannelGroupCopy(cmd);
else if (command == "channelgrouprename") return this->handleCommandChannelGroupRename(cmd);
else if (command == "channelgroupdel") return this->handleCommandChannelGroupDel(cmd);
else if (command == "channelgrouplist") return this->handleCommandChannelGroupList(cmd);
else if (command == "channelgroupclientlist") return this->handleCommandChannelGroupClientList(cmd);
else if (command == "channelgrouppermlist") return this->handleCommandChannelGroupPermList(cmd);
else if (command == "channelgroupaddperm") return this->handleCommandChannelGroupAddPerm(cmd);
else if (command == "channelgroupdelperm") return this->handleCommandChannelGroupDelPerm(cmd);
//Channel sub/unsubscribe
else if (command == "channelsubscribe") return this->handleCommandChannelSubscribe(cmd);
else if (command == "channelsubscribeall") return this->handleCommandChannelSubscribeAll(cmd);
else if (command == "channelunsubscribe") return this->handleCommandChannelUnsubscribe(cmd);
else if (command == "channelunsubscribeall") return this->handleCommandChannelUnsubscribeAll(cmd);
//manager channel permissions
else if (command == "channelclientpermlist") return this->handleCommandChannelClientPermList(cmd);
else if (command == "channelclientaddperm") return this->handleCommandChannelClientAddPerm(cmd);
else if (command == "channelclientdelperm") return this->handleCommandChannelClientDelPerm(cmd);
//Client actions
else if (command == "clientupdate") return this->handleCommandClientUpdate(cmd);
else if (command == "clientmove") return this->handleCommandClientMove(cmd);
else if (command == "clientgetids") return this->handleCommandClientGetIds(cmd);
else if (command == "clientkick") return this->handleCommandClientKick(cmd);
else if (command == "clientpoke") return this->handleCommandClientPoke(cmd);
else if (command == "sendtextmessage") return this->handleCommandSendTextMessage(cmd);
else if (command == "clientchatcomposing") return this->handleCommandClientChatComposing(cmd);
else if (command == "clientchatclosed") return this->handleCommandClientChatClosed(cmd);
else if (command == "clientfind") return this->handleCommandClientFind(cmd);
else if (command == "clientinfo") return this->handleCommandClientInfo(cmd);
else if (command == "clientaddperm") return this->handleCommandClientAddPerm(cmd);
else if (command == "clientdelperm") return this->handleCommandClientDelPerm(cmd);
else if (command == "clientpermlist") return this->handleCommandClientPermList(cmd);
//File transfare
else if (command == "ftgetfilelist") return this->handleCommandFTGetFileList(cmd);
else if (command == "ftcreatedir") return this->handleCommandFTCreateDir(cmd);
else if (command == "ftdeletefile") return this->handleCommandFTDeleteFile(cmd);
else if (command == "ftinitupload") {
auto result = this->handleCommandFTInitUpload(cmd);
if(result.has_error() && this->getType() == ClientType::CLIENT_TEAMSPEAK) {
ts::command_builder notify{"notifystatusfiletransfer"};
notify.put_unchecked(0, "clientftfid", cmd["clientftfid"].string());
notify.put(0, "size", 0);
this->writeCommandResult(notify, result, "status");
this->sendCommand(notify);
result.release_data();
return command_result{error::ok};
}
return result;
}
else if (command == "ftinitdownload") {
auto result = this->handleCommandFTInitDownload(cmd);
if(result.has_error() && this->getType() == ClientType::CLIENT_TEAMSPEAK) {
ts::command_builder notify{"notifystatusfiletransfer"};
notify.put_unchecked(0, "clientftfid", cmd["clientftfid"].string());
notify.put(0, "size", 0);
this->writeCommandResult(notify, result, "status");
this->sendCommand(notify);
result.release_data();
return command_result{error::ok};
}
return result;
}
else if (command == "ftgetfileinfo") return this->handleCommandFTGetFileInfo(cmd);
else if (command == "ftrenamefile") return this->handleCommandFTRenameFile(cmd);
else if (command == "ftlist") return this->handleCommandFTList(cmd);
else if (command == "ftstop") return this->handleCommandFTStop(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 == "logquery") return this->handleCommandLogQuery(cmd);
else if (command == "logadd") return this->handleCommandLogAdd(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 == "playlistsetsubscription") return this->handleCommandPlaylistSetSubscription(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 == "playlistclientlist") return this->handleCommandPlaylistClientList(cmd);
else if (command == "playlistclientpermlist") return this->handleCommandPlaylistClientPermList(cmd);
else if (command == "playlistclientaddperm") return this->handleCommandPlaylistClientAddPerm(cmd);
else if (command == "playlistclientdelperm") return this->handleCommandPlaylistClientDelPerm(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 == "playlistsongsetcurrent") return this->handleCommandPlaylistSongSetCurrent(cmd);
else if (command == "playlistsongadd") return this->handleCommandPlaylistSongAdd(cmd);
else if (command == "playlistsongreorder" || command == "playlistsongmove") return this->handleCommandPlaylistSongReorder(cmd);
else if (command == "playlistsongremove") return this->handleCommandPlaylistSongRemove(cmd);
else if (command == "dummy_ipchange") return this->handleCommandDummy_IpChange(cmd);
else if (command == "conversationhistory") return this->handleCommandConversationHistory(cmd);
else if (command == "conversationfetch") return this->handleCommandConversationFetch(cmd);
else if (command == "conversationmessagedelete") return this->handleCommandConversationMessageDelete(cmd);
else if (command == "listfeaturesupport") return this->handleCommandListFeatureSupport(cmd);
if (this->getType() == ClientType::CLIENT_QUERY)
return command_result{error::command_not_found}; //Dont log query invalid commands
if (this->getType() == ClientType::CLIENT_TEAMSPEAK)
if (command.empty() || command.find_first_not_of(' ') == -1) {
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_allow_invalid_packet, this->getChannelId())))
((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_command, this->server ? this->server->serverAdmin : static_pointer_cast<ConnectedClient>(serverInstance->getInitialServerAdmin()), true);
}
logError(this->getServerId(), "Missing command '{}'", command);
return command_result{error::command_not_found};
};
command_result ConnectedClient::handleCommandGetConnectionInfo(Command &cmd) {
CMD_REQ_SERVER;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
if (!client) return command_result{error::client_invalid_id};
bool send_temp{false};
auto info = client->request_connection_info(_this.lock(), send_temp);
if (info || send_temp) {
this->notifyConnectionInfo(client.client, info);
} else if(this->getType() != ClientType::CLIENT_TEAMSPEAK)
return command_result{error::no_cached_connection_info};
return command_result{error::ok};
}
command_result 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 command_result{error::ok};
}
//connectioninfoautoupdate connection_server2client_packetloss_speech=0.0000 connection_server2client_packetloss_keepalive=0.0010 connection_server2client_packetloss_control=0.0000 connection_server2client_packetloss_total=0.0009
command_result ConnectedClient::handleCommandConnectionInfoAutoUpdate(Command &cmd) {
this->properties()[property::CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE] = cmd["connection_server2client_packetloss_keepalive"].as<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 command_result{error::ok};
}
command_result ConnectedClient::handleCommandPermissionList(Command &cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(5);
static std::string permission_list_string[ClientType::MAX];
static std::mutex permission_list_string_lock;
static auto build_permission_list = [](const std::string& command, const ClientType& type) {
Command list_builder(command);
int index = 0;
for (auto group : permission::availableGroups)
list_builder[index++]["group_id_end"] = group;
auto avPerms = permission::availablePermissions;
std::sort(avPerms.begin(), avPerms.end(), [](const std::shared_ptr<permission::PermissionTypeEntry> &a, const std::shared_ptr<permission::PermissionTypeEntry> &b) {
return a->type < b->type;
});
auto mapper = serverInstance->getPermissionMapper();
for (const auto& permission : avPerms) {
if (!permission->clientSupported) continue;
auto &blk = list_builder[index++];
blk["permname"] = permission->name;
blk["permname"] = mapper->permission_name(type, permission->type);
blk["permdesc"] = permission->description;
blk["permid"] = permission->type;
}
return list_builder;
};
auto type = this->getType();
if(type == CLIENT_TEASPEAK || type == CLIENT_TEAMSPEAK || type == CLIENT_QUERY) {
Command response(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifypermissionlist" : "");
{
lock_guard lock(permission_list_string_lock);
if(permission_list_string[type].empty())
permission_list_string[type] = build_permission_list("", type).build();
response[0][""] = permission_list_string[type];
}
this->sendCommand(response);
} else {
this->sendCommand(build_permission_list(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifypermissionlist" : "", type));
}
return command_result{error::ok};
}
#define M(ptype) \
do { \
for(const auto& prop : property::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]; \
response[index]["value"] = prop->default_value; \
response[index]["value_type"] = property::ValueType_Names[(int) prop->type_value]; \
index++; \
} \
} while(0)
command_result ConnectedClient::handleCommandPropertyList(ts::Command& cmd) {
Command response(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifypropertylist" : "");
{
string pattern;
for (auto flag_name : property::flag_names) {
if(flag_name) {
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);
if(cmd.hasParm("all") || cmd.hasParm("playlist"))
M(property::PlaylistProperties);
this->sendCommand(response);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto serverGroup = this->server->groups->findGroup(cmd["cgid"].as<GroupId>());
if (!serverGroup && cmd["cgid"].as<GroupId>() == 0)
serverGroup = this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
if (!serverGroup || serverGroup->target() != GROUPTARGET_CHANNEL)
return command_result{error::group_invalid_id};
shared_lock server_channel_lock(this->server->channel_tree_lock); /* ensure we dont get moved or somebody could move us */
auto channel_id = cmd["cid"].as<ChannelId>();
auto channel = this->server->channelTree->findChannel(channel_id);
if (!channel) return command_result{error::channel_invalid_id};
auto target_cldbid = cmd["cldbid"].as<ClientDbId>();
{
auto channel_group_member_add_power = this->calculate_permission(permission::i_channel_group_member_add_power, channel_id);
if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_add_power, channel_group_member_add_power, true)) {
if(target_cldbid != this->getClientDatabaseId())
return command_result{permission::i_channel_group_member_add_power};
auto channel_group_self_add_power = this->calculate_permission(permission::i_channel_group_self_add_power, channel_id);
if(!serverGroup->permission_granted(permission::i_channel_group_needed_member_add_power, channel_group_self_add_power, true))
return command_result{permission::i_channel_group_self_add_power};
}
auto client_permission_modify_power = this->calculate_permission(permission::i_client_permission_modify_power, channel_id);
auto client_needed_permission_modify_power = this->server->calculate_permission(
permission::i_client_needed_permission_modify_power, target_cldbid, ClientType::CLIENT_TEAMSPEAK, channel_id);
if(client_needed_permission_modify_power.has_value) {
if(!permission::v2::permission_granted(client_needed_permission_modify_power, client_permission_modify_power))
return command_result{permission::i_client_permission_modify_power};
}
}
std::shared_ptr<GroupAssignment> old_group;
{
old_group = this->server->groups->getChannelGroupExact(target_cldbid, channel, false);
if(old_group) {
auto channel_group_member_remove_power = this->calculate_permission(permission::i_channel_group_member_remove_power, channel_id);
if(!old_group->group->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_member_remove_power, true)) {
if(target_cldbid != this->getClientDatabaseId())
return command_result{permission::i_channel_group_member_remove_power};
auto channel_group_self_remove_power = this->calculate_permission(permission::i_channel_group_self_remove_power, channel_id);
if(!old_group->group->permission_granted(permission::i_channel_group_needed_member_remove_power, channel_group_self_remove_power, true))
return command_result{permission::i_channel_group_self_remove_power};
}
}
}
this->server->groups->setChannelGroup(target_cldbid, serverGroup, channel);
std::shared_ptr<ConnectedClient> connected_client{};
for (const auto &targetClient : this->server->findClientsByCldbId(target_cldbid)) {
connected_client = targetClient;
unique_lock client_channel_lock_w(targetClient->channel_lock);
auto updates = this->server->groups->update_server_group_property(targetClient, false, targetClient->getChannel()); /* needs a write lock */
client_channel_lock_w.unlock();
shared_lock client_channel_lock_r(targetClient->channel_lock);
auto result = this->server->notifyClientPropertyUpdates(targetClient, updates);
if (result) {
if(targetClient->update_cached_permissions()) /* update cached calculated permissions */
targetClient->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
if(targetClient->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] == channel->channelId()) { //Only if group assigned over the channel
for (const auto &viewer : this->server->getClients()) {
/* if in view will be tested within that method */
shared_lock viewer_channel_lock(viewer->channel_lock, defer_lock);
if(viewer != targetClient)
viewer_channel_lock.lock();
viewer->notifyClientChannelGroupChanged(_this.lock(), targetClient, targetClient->getChannel(), channel, serverGroup, false);
}
}
}
targetClient->updateChannelClientProperties(false, true);
}
if(old_group) {
serverInstance->action_logger()->group_assignment_logger.log_group_assignment_remove(this->getServerId(),
this->ref(), log::GroupTarget::CHANNEL,
old_group->group->groupId(), old_group->group->name(),
target_cldbid, connected_client ? connected_client->getDisplayName() : ""
);
}
if(serverGroup != this->server->groups->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL)) {
serverInstance->action_logger()->group_assignment_logger.log_group_assignment_add(this->getServerId(),
this->ref(), log::GroupTarget::CHANNEL,
serverGroup->groupId(), serverGroup->name(),
target_cldbid, connected_client ? connected_client->getDisplayName() : ""
);
}
return command_result{error::ok};
}
//sendtextmessage targetmode=1 <1 = direct | 2 = channel | 3 = server> msg=asd target=1 <clid>
command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto timestamp = system_clock::now();
if (cmd["targetmode"].as<ChatMessageMode>() == ChatMessageMode::TEXTMODE_PRIVATE) {
ConnectedLockedClient target{this->server->find_client_by_id(cmd["target"].as<ClientId>())};
if (!target) return command_result{error::client_invalid_id};
bool chat_open = false;
{
shared_lock channel_lock(this->channel_lock);
this->openChats.erase(remove_if(this->openChats.begin(), this->openChats.end(), [](const weak_ptr<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.client == this)
ACTION_REQUIRES_PERMISSION(permission::b_client_even_textmessage_send, 1, this->getChannelId());
if(!permission::v2::permission_granted(target->calculate_permission(permission::i_client_needed_private_textmessage_power, target->getClientId()), this->calculate_permission(permission::i_client_private_textmessage_power, this->getClientId()), false))
return command_result{permission::i_client_private_textmessage_power};
{
unique_lock channel_lock(target->get_channel_lock());
target->openChats.push_back(_this);
}
{
unique_lock channel_lock(this->channel_lock);
this->openChats.push_back(target.client);
}
}
if(this->handleTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, cmd["msg"], target.client)) return command_result{error::ok};
target->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), 0, timestamp, cmd["msg"].string());
this->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), target->getClientId(), 0, timestamp, cmd["msg"].string());
} else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_CHANNEL) {
if(!cmd[0].has("cid")) {
cmd["cid"] = 0;
}
RESOLVE_CHANNEL_R(cmd["cid"], false);
auto channel = l_channel ? dynamic_pointer_cast<BasicChannel>(l_channel->entry) : nullptr;
if(!channel) {
CMD_REQ_CHANNEL;
channel = this->currentChannel;
channel_id = this->currentChannel->channelId();
}
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_channel_textmessage_send, channel_id), false)) \
return command_result{permission::b_client_channel_textmessage_send};
if(channel == this->currentChannel) {
channel_tree_read_lock.unlock(); //Method may creates a music bot which modifies the channel tree
if(this->handleTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, cmd["msg"], nullptr)) {
return command_result{error::ok};
}
channel_tree_read_lock.lock();
}
auto conversation_mode = channel->properties()[property::CHANNEL_CONVERSATION_MODE].as<ChannelConversationMode>();
if(conversation_mode == ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE) {
return command_result{error::conversation_not_exists};
} else if(channel != this->currentChannel) {
if(conversation_mode == ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE) {
return command_result{error::conversation_is_private};
}
if(auto fail_perm{this->calculate_and_get_join_state(channel)}; fail_perm != permission::ok) {
return command_result{fail_perm}; /* You're not allowed to send messages :) */
}
}
this->server->send_text_message(channel, this->ref(), cmd["msg"].string());
} else if (cmd["targetmode"] == ChatMessageMode::TEXTMODE_SERVER) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_server_textmessage_send, 1);
if(this->handleTextMessage(ChatMessageMode::TEXTMODE_SERVER, cmd["msg"], nullptr)) return command_result{error::ok};
for(const auto& client : this->server->getClients()) {
if (client->connectionState() != ConnectionState::CONNECTED)
continue;
auto type = client->getType();
if (type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC)
continue;
client->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, _this.lock(), this->getClientId(), 0, timestamp, cmd["msg"].string());
}
{
auto conversations = this->server->conversation_manager();
auto conversation = conversations->get_or_create(0);
conversation->register_message(this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), timestamp, cmd["msg"].string());
}
} else return command_result{error::parameter_invalid, "invalid target mode"};
return command_result{error::ok};
}
//notifybanlist banid=3 ip name uid=zbex8X3bFRTIKLI7mzeyJGZsh64= lastnickname=Wolf\sC++\sXXXX created=1510357269 duration=3600 invokername=WolverinDEV invokercldbid=5 invokeruid=xxjnc14LmvTk+Lyrm8OOeo4tOqw= reason=Prefix\sFake\s\p\sName enforcements=3
command_result ConnectedClient::handleCommandBanList(Command &cmd) {
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
ServerId sid = this->getServerId();
if (cmd[0].has("sid"))
sid = cmd["sid"];
if (sid == 0) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_ban_list_global, 1);
} else {
auto server = serverInstance->getVoiceServerManager()->findServerById(sid);
if (!server) return command_result{error::parameter_invalid};
if (!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_ban_list, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_ban_list};
}
//When empty: return command_result{error::database_empty_result};
auto banList = serverInstance->banManager()->listBans(sid);
if (banList.empty()) return command_result{error::database_empty_result};
auto allow_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifybanlist" : "");
int index = 0;
for (const auto &elm : banList) {
notify[index]["sid"] = elm->serverId;
notify[index]["banid"] = elm->banId;
if(allow_ip)
notify[index]["ip"] = elm->ip;
else
notify[index]["ip"] = "hidden";
notify[index]["name"] = elm->name;
notify[index]["uid"] = elm->uid;
notify[index]["hwid"] = elm->hwid;
notify[index]["lastnickname"] = elm->name; //Maybe update?
notify[index]["created"] = chrono::duration_cast<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 command_result{error::ok};
}
command_result ConnectedClient::handleCommandBanAdd(Command &cmd) {
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
string ip = cmd[0].has("ip") ? cmd["ip"].string() : "";
string name = cmd[0].has("name") ? cmd["name"].string() : "";
string uid = cmd[0].has("uid") ? cmd["uid"].string() : "";
string hwid = cmd[0].has("hwid") ? cmd["hwid"].string() : "";
string banreason = cmd[0].has("banreason") ? cmd["banreason"].string() : "No reason set";
auto time = cmd[0].has("time") ? cmd["time"].as<int64_t>() : 0L;
ServerId sid = this->getServerId();
if (cmd[0].has("sid"))
sid = cmd["sid"];
if (sid == 0) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_ban_create_global, 1);
} else {
auto server = serverInstance->getVoiceServerManager()->findServerById(sid);
if (!server) return command_result{error::parameter_invalid};
if (!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_ban_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_ban_create};
}
auto max_ban_time = this->calculate_permission(permission::i_client_ban_max_bantime, this->getClientDatabaseId());
if(!max_ban_time.has_value)
return command_result{permission::i_client_ban_max_bantime};
if (!max_ban_time.has_infinite_power()) {
if (max_ban_time.value < time)
return command_result{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 command_result{error::database_duplicate_entry};
} else {
existing->until = until;
serverInstance->banManager()->updateBan(existing);
banned = true;
}
} else if(!banned) {
serverInstance->banManager()->unban(existing);
}
}
if(!banned) {
serverInstance->banManager()->registerBan(sid, this->getClientDatabaseId(), banreason, uid, ip, name, hwid, until);
}
for(auto server : (this->server ? std::deque<shared_ptr<VirtualServer>>{this->server} : serverInstance->getVoiceServerManager()->serverInstances()))
server->testBanStateChange(_this.lock());
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandBanEdit(Command &cmd) {
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
ServerId sid = this->getServerId();
if (cmd[0].has("sid"))
sid = cmd["sid"];
auto ban = serverInstance->banManager()->findBanById(sid, cmd["banid"].as<uint64_t>());
if (!ban) return command_result{error::database_empty_result};
if (sid == 0) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_ban_edit_global, 1);
} else {
auto server = serverInstance->getVoiceServerManager()->findServerById(sid);
if (!server) return command_result{error::parameter_invalid};
if (!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_ban_edit, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_ban_edit};
}
/* ip name uid reason time hwid */
bool changed = false;
if (cmd[0].has("ip")) {
ban->ip = cmd["ip"].as<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 command_result{error::parameter_invalid};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandBanClient(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
std::string target_unique_id{};
ClientDbId target_database_id{0};
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");
std::deque<std::shared_ptr<ConnectedClient>> target_clients;
if (cmd[0].has("uid")) {
target_clients = this->server->findClientsByUid(target_unique_id = cmd["uid"].string());
} else if(cmd[0].has("cldbid")) {
target_clients = this->server->findClientsByCldbId(target_database_id = cmd["cldbid"].as<ClientDbId>());
} else {
target_clients = {this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
if(!target_clients[0]) {
return command_result{error::client_invalid_id, "Could not find target client"};
}
}
for(const auto& client : target_clients)
if(client->getType() == ClientType::CLIENT_MUSIC)
return command_result{error::client_invalid_id, "You cant ban a music bot!"};
if(!target_clients.empty()) {
if(target_unique_id.empty())
target_unique_id = target_clients.back()->getUid();
if(!target_database_id)
target_database_id = target_clients.back()->getClientDatabaseId();
}
if(!permission::v2::permission_granted(this->server->calculate_permission(permission::i_client_needed_ban_power, target_database_id, ClientType::CLIENT_TEAMSPEAK, 0), this->calculate_permission(permission::i_client_ban_power, 0)))
return command_result{permission::i_client_ban_power};
if (permission::v2::permission_granted(1, this->server->calculate_permission(permission::b_client_ignore_bans, target_database_id, ClientType::CLIENT_TEAMSPEAK, 0)))
return command_result{permission::b_client_ignore_bans};
deque<BanId> ban_ids;
if(!target_unique_id.empty()) {
auto _id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, target_unique_id, "", "", "", until);
ban_ids.push_back(_id);
}
auto b_ban_name = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_name, 0), false);
auto b_ban_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_ip, 0), false);
auto b_ban_hwid = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_hwid, 0), false);
auto max_ban_time = this->calculate_permission(permission::i_client_ban_max_bantime, 0);
if(!max_ban_time.has_value) return command_result{permission::i_client_ban_max_bantime};
if (!max_ban_time.has_infinite_power()) {
if (max_ban_time.value < time)
return command_result{permission::i_client_ban_max_bantime};
}
for (const auto &client : target_clients) {
if (client->getType() != CLIENT_TEAMSPEAK && client->getType() != CLIENT_QUERY) continue; //Remember if you add new type you have to change stuff here
this->server->notify_client_ban(client, this->ref(), reason, time);
client->close_connection(system_clock::now() + seconds(2));
string entry_name, entry_ip, entry_hardware_id;
if(b_ban_name && !no_nickname) {
entry_name = client->getDisplayName();
}
if(b_ban_ip && !no_ip && !config::server::disable_ip_saving) {
entry_ip = client->getPeerIp();
}
if(b_ban_hwid && !no_hwid) {
entry_hardware_id = client->getHardwareId();
}
auto exact = serverInstance->banManager()->findBanExact(this->getServer()->getServerId(), reason, "", entry_ip, entry_name, entry_hardware_id);
if(exact) {
exact->until = until;
exact->invokerDbId = this->getClientDatabaseId();
serverInstance->banManager()->updateBan(exact);
ban_ids.push_back(exact->banId);
} else {
auto id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, "", entry_ip, entry_name, entry_hardware_id, until);
ban_ids.push_back(id);
}
}
this->server->testBanStateChange(_this.lock());
if (this->getType() == CLIENT_QUERY) {
Command notify("");
int index = 0;
for(const auto& ban_id : ban_ids)
notify[index++]["banid"] = ban_id;
this->sendCommand(notify);
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandBanDel(Command &cmd) {
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ServerId sid = this->getServerId();
if (cmd[0].has("sid"))
sid = cmd["sid"];
auto ban = serverInstance->banManager()->findBanById(sid, cmd["banid"].as<uint64_t>());
if (!ban) return command_result{error::database_empty_result};
if (sid == 0) {
const auto permission = ban->invokerDbId == this->getClientDatabaseId() ? permission::b_client_ban_delete_own_global : permission::b_client_ban_delete_global;
ACTION_REQUIRES_GLOBAL_PERMISSION(permission, 1);
} else {
auto server = serverInstance->getVoiceServerManager()->findServerById(sid);
if (!server) return command_result{error::parameter_invalid};
auto perm = ban->invokerDbId == this->getClientDatabaseId() ? permission::b_client_ban_delete_own : permission::b_client_ban_delete;
if (!permission::v2::permission_granted(1, server->calculate_permission(perm, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{perm};
}
serverInstance->banManager()->unban(ban);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandBanDelAll(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_ban_delete, 1);
serverInstance->banManager()->deleteAllBans(server->getServerId());
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandBanTriggerList(ts::Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_ban_trigger_list, 1);
CMD_REQ_PARM("banid");
auto record = serverInstance->banManager()->findBanById(this->getServerId(), cmd["banid"]);
if(!record) return command_result{error::parameter_invalid, "Invalid ban id"};
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifybantriggerlist" : "");
notify["banid"] = record->banId;
notify["serverid"] = record->serverId;
auto allow_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
int index = 0;
for (auto &entry : serverInstance->banManager()->trigger_list(record, this->getServerId(), cmd[0].has("offset") ? cmd["offset"].as<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 command_result{error::database_empty_result};
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandTokenList(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_list, 1);
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifytokenlist" : "");
int index = 0;
for (auto &token : this->server->tokenManager->avariableTokes()) {
notify[index]["token"] = token->token;
notify[index]["token_type"] = token->type;
notify[index]["token_id1"] = token->groupId;
notify[index]["token_id2"] = token->channelId;
notify[index]["token_created"] = chrono::duration_cast<chrono::seconds>(token->created.time_since_epoch()).count();
notify[index]["token_description"] = token->description;
index++;
}
if (index == 0) return command_result{error::database_empty_result};
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandTokenAdd(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_add, 1);
TokenType ttype = static_cast<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 command_result{error::group_invalid_id};
if(group->type() == GroupType::GROUP_TYPE_TEMPLATE) return command_result{error::parameter_invalid, "invalid server group type"};
if(group->target() == GroupTarget::GROUPTARGET_SERVER)
ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_server_group_needed_member_add_power, permission::i_server_group_member_add_power, true);
else
ACTION_REQUIRES_GROUP_PERMISSION(group, permission::i_channel_group_needed_member_add_power, permission::i_channel_group_member_add_power, true);
}
if (ttype == TokenType::TOKEN_CHANNEL) {
if (!this->server->channelTree->findChannel(cId)) return command_result{error::channel_invalid_id, "Cant resolve channel"};
} else if (ttype == TokenType::TOKEN_SERVER);
else return command_result{error::parameter_invalid, "invalid token target type"};
auto result = this->server->tokenManager->createToken(ttype, gId, description, cId, cmd["token"]);
if (!result) return command_result{error::vs_critical};
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifytokenadd" : "");
notify["token"] = result->token;
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandTokenUse(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_use, 1);
auto strToken = cmd["token"].string();
auto token = this->server->tokenManager->findToken(strToken);
if (!token) return command_result{error::token_invalid_id, "Invalid token. (Token not registered)"};
this->server->tokenManager->deleteToke(token->token);
auto serverGroup = this->server->groups->findGroup(token->groupId);
if (!serverGroup) return command_result{error::token_invalid_id, "Token invalid groupId"};
this->server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false; //TODO test if its the default token
std::shared_ptr<BasicChannel> channel = nullptr;
if (token->channelId != 0) {
channel = this->server->channelTree->findChannel(token->channelId);
if (!channel) return command_result{error::token_invalid_id, "Token invalid channelId"};
this->server->groups->setChannelGroup(this->getClientDatabaseId(), serverGroup, channel);
} else {
if(!this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), serverGroup))
this->server->groups->addServerGroup(this->getClientDatabaseId(), serverGroup);
else {
return command_result{error::ok};
}
}
if (this->server->notifyClientPropertyUpdates(_this.lock(), this->server->groups->update_server_group_property(_this.lock(), true, this->getChannel()))) {
if(this->update_cached_permissions()) /* update cached calculated permissions */
this->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
{
for (auto &viewer : this->server->getClients()) {
if(viewer->isClientVisible(_this.lock(), true))
viewer->notifyServerGroupClientAdd(this->server->serverRoot, _this.lock(), serverGroup);
}
}
this->notifyServerGroupClientAdd(this->server->serverRoot, _this.lock(), serverGroup);
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandTokenDelete(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_token_delete, 1);
auto strToken = cmd["token"].string();
auto token = this->server->tokenManager->findToken(strToken);
if (!token) return command_result{error::token_invalid_id, "Invalid token. (Token not registered)"};
this->server->tokenManager->deleteToke(token->token);
if(token->token == this->server->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY].as<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 command_result{error::ok};
}
//start=0 duration=10
//pattern=%asd%
command_result ConnectedClient::handleCommandPluginCmd(Command &cmd) {
CMD_REQ_SERVER;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto mode = cmd["targetmode"].as<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>();
ConnectedLockedClient cl{this->server->find_client_by_id(target)};
if (!cl) return command_result{error::client_invalid_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 command_result{error::client_invalid_id, "invalid target id"};
target->sendCommand(Command(cmd["command"].string()), cmd[0].has("low") ? cmd["low"] : false);
}
*/
else return command_result{error::not_implemented};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandWhoAmI(Command &cmd) {
CMD_RESET_IDLE;
Command result("");
if (this->server) {
result["virtualserver_status"] = ServerState::string(this->getServer()->state);
result["virtualserver_id"] = this->server->getServerId();
result["virtualserver_unique_identifier"] = this->server->properties()[property::VIRTUALSERVER_UNIQUE_IDENTIFIER].as<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 command_result{error::ok};
}
struct DBFindArgs {
int index = 0;
bool full = false;
bool ip = false;
Command cmd{""};
};
command_result ConnectedClient::handleCommandVersion(Command &) {
CMD_RESET_IDLE;
Command res("");
res["version"] = build::version()->string(false);
res["build_count"] = build::buildCount();
res["build"] = duration_cast<seconds>(build::version()->timestamp.time_since_epoch()).count();
#ifdef WIN32
res["platform"] = "Windows";
#else
res["platform"] = "Linux";
#endif
this->sendCommand(res);
return command_result{error::ok};
}
//cid=%d password=%s
command_result ConnectedClient::handleCommandVerifyChannelPassword(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
std::shared_ptr<BasicChannel> channel = (this->server ? this->server->channelTree : serverInstance->getChannelTree().get())->findChannel(cmd["cid"].as<ChannelId>());
if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"};
std::string password = cmd["password"];
if (!channel->passwordMatch(password, false)) return command_result{error::server_invalid_password};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandVerifyServerPassword(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
std::string password = cmd["password"];
if (!this->server->verifyServerPassword(password, false)) return command_result{error::server_invalid_password};
return command_result{error::ok};
}
//msgid=2 cluid=IkBXingb46\/z1Q3hhMvJEweb3lw= subject=The\sSubject timestamp=1512224138 flag_read=0
//notifymessagelist msgid=2 cluid=IkBXingb46\/z1Q3hhMvJEweb3lw= subject=The\sSubject timestamp=1512224138 flag_read=0
command_result ConnectedClient::handleCommandMessageList(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto msgList = this->server->letters->avariableLetters(this->getUid());
if (msgList.empty()) return command_result{error::database_empty_result, "no letters available"};
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifymessagelist" : "");
int index = 0;
for (const auto &elm : msgList) {
notify[index]["msgid"] = elm->id;
notify[index]["cluid"] = elm->sender;
notify[index]["subject"] = elm->subject;
notify[index]["timestamp"] = duration_cast<seconds>(elm->created.time_since_epoch()).count();
notify[index]["flag_read"] = elm->read;
index++;
}
this->sendCommand(notify);
return command_result{error::ok};
}
//messageadd cluid=ePHuXhcai9nk\/4Fd\/xkxrokvnNk= subject=Test message=Message
command_result ConnectedClient::handleCommandMessageAdd(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_offline_textmessage_send, 1);
this->server->letters->createLetter(this->getUid(), cmd["cluid"], cmd["subject"], cmd["message"]);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandMessageGet(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(10);
auto letter = this->server->letters->getFullLetter(cmd["msgid"]);
//msgid=2 cluid=IkBXingb46\/z1Q3hhMvJEweb3lw= subject=The\sSubject message=The\sbody timestamp=1512224138
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifymessage" : "");
notify["msgid"] = cmd["msgid"];
notify["cluid"] = letter->sender;
notify["subject"] = letter->subject;
notify["message"] = letter->message;
notify["timestamp"] = duration_cast<seconds>(letter->created.time_since_epoch()).count();
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandMessageUpdateFlag(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
this->server->letters->updateReadFlag(cmd["msgid"], cmd["flag"]);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandMessageDel(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
this->server->letters->deleteLetter(cmd["msgid"]);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPermGet(Command &cmd) {
CMD_RESET_IDLE;
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_permissionoverview_own, 1);
Command res("");
deque<permission::PermissionType> requrested;
auto permission_mapper = serverInstance->getPermissionMapper();
auto type = this->getType();
for (int index = 0; index < cmd.bulkCount(); index++) {
permission::PermissionType permType = permission::unknown;
if (cmd[index].has("permid"))
permType = cmd[index]["permid"].as<permission::PermissionType>();
else if (cmd[index].has("permsid"))
permType = permission::resolvePermissionData(cmd[index]["permsid"].as<string>())->type; //TODO: Map the other way around!
if (permission::resolvePermissionData(permType)->type == permission::PermissionType::unknown) return command_result{error::parameter_invalid, "could not resolve permission"};
requrested.push_back(permType);
}
int index = 0;
for(const auto& entry : this->calculate_permissions(requrested, this->getChannelId())) {
if(!entry.second.has_value) continue;
res[index]["permsid"] = permission_mapper->permission_name(type, entry.first);;
res[index]["permid"] = entry.first;
res[index++]["permvalue"] = entry.second.value;
}
if(index == 0)
return command_result{error::database_empty_result};
this->sendCommand(res);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPermIdGetByName(Command &cmd) {
auto found = permission::resolvePermissionData(cmd["permsid"].as<string>()); //TODO: Map the other way around
Command res("");
res["permid"] = found->type;
this->sendCommand(res);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
struct PermissionEntry {
permission::PermissionType permission_type;
permission::PermissionValue permission_value;
permission::PermissionSqlType type;
GroupId group_id;
ChannelId channel_id;
ClientDbId client_id;
bool negate;
bool skip;
};
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_permission_find, 1);
std::vector<std::tuple<std::string, permission::PermissionType, bool>> requested_permissions{};
requested_permissions.reserve(cmd.bulkCount());
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 command_result{error::parameter_invalid, "could not resolve permission (id=" + cmd[index]["permid"].string() + ")"};
} else if (cmd[index].has("permsid")) {
permission = permission::resolvePermissionData(cmd[index]["permsid"].as<string>()); //TODO: Map the other way around
granted = permission->grant_name == cmd[index]["permsid"].as<string>();
if(permission->type == permission::PermissionType::unknown)
return command_result{error::parameter_invalid, "could not resolve permission (id=" + cmd[index]["permid"].string() + ")"};
} else {
continue;
}
requested_permissions.emplace_back(permission->name, permission->type, granted);
}
if(requested_permissions.empty())
return command_result{error::database_empty_result};
std::map<std::string, std::tuple<permission::PermissionType, uint8_t>> db_lookup_mapping{};
std::string query_string{};
for(const auto& [name, id, as_granted] : requested_permissions) {
auto& mapping = db_lookup_mapping[name];
if(std::get<0>(mapping) == 0) {
std::get<0>(mapping) = id;
query_string += std::string{query_string.empty() ? "" : " OR "} + "`permId` = '" + name + "'";
}
std::get<1>(mapping) |= (1U << as_granted);
}
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;
#if 0
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;
}
}
#else
assert(length == 8);
try {
type = static_cast<permission::PermissionSqlType>(stoll(values[1]));
permission_name = values[0];
id = static_cast<uint64_t>(stoll(values[2]));
channel_id = static_cast<ChannelId>(stoll(values[3]));
value = static_cast<permission::PermissionValue>(stoll(values[4]));
granted_value = static_cast<permission::PermissionValue>(stoll(values[5]));
negate = values[7] == "1";
skip = values[6] == "1";
} catch(std::exception& ex) {
return 0;
}
#endif
auto request = db_lookup_mapping.find(permission_name);
if(request == db_lookup_mapping.end()) return 0; /* shall not happen */
auto flags = std::get<1>(request->second);
/* value */
if((flags & 0x1U) > 0 && value > 0) {
auto result = make_unique<PermissionEntry>();
result->permission_type = std::get<0>(request->second);
result->permission_value = value;
result->type = type;
result->channel_id = channel_id;
result->negate = negate;
result->skip = skip;
if (type == permission::SQL_PERM_GROUP) {
result->group_id = id;
} else if(type == permission::SQL_PERM_USER) {
result->client_id = id;
}
entries.push_back(std::move(result));
}
/* granted */
if((flags & 0x2U) > 0 && granted_value > 0) {
auto result = make_unique<PermissionEntry>();
result->permission_type = (permission::PermissionType) (std::get<0>(request->second) | 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) {
result->group_id = id;
} else if(type == permission::SQL_PERM_USER) {
result->client_id = id;
}
entries.push_back(std::move(result));
}
return 0;
});
if(entries.empty())
return command_result{error::database_empty_result};
struct CommandPerm {
permission::PermissionType p;
permission::PermissionValue v;
int64_t id1;
int64_t id2;
uint8_t t;
};
std::vector<CommandPerm> perms;
perms.resize(entries.size());
size_t index{0};
auto all_groups = this->server->groups->availableGroups(true);
for(const auto& entry : entries) {
auto& perm = perms[index++];
#if 0 /* TS3 switched the oder and YatQa as well, to keep compatibility we do it as well */
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 */
}
}
#else
if(entry->type == permission::SQL_PERM_USER) {
if(entry->channel_id > 0) {
perm.id1 = entry->channel_id;
perm.id2 = entry->client_id;
perm.t = 4; /* client channel */
} else {
perm.id1 = entry->client_id;
perm.id2 = 0;
perm.t = 1; /* client server */
}
} else if(entry->type == permission::SQL_PERM_CHANNEL) {
perm.id1 = entry->channel_id;
perm.id2 = 0;
perm.t = 2; /* channel permission */
} else if(entry->type == permission::SQL_PERM_GROUP) {
auto group = std::find_if(all_groups.begin(), all_groups.end(), [&](const auto& group) { return group->groupId() == entry->group_id; });
if(group == all_groups.end()) {
index--; /* unknown group */
continue;
}
if((*group)->target() == GroupTarget::GROUPTARGET_CHANNEL) {
perm.id1 = 0;
perm.id2 = entry->group_id;
perm.t = 3; /* channel group */
} else {
perm.id1 = entry->group_id;
perm.id2 = 0;
perm.t = 0; /* server group */
}
}
#endif
perm.p = entry->permission_type;
perm.v = entry->permission_value;
}
perms.erase(perms.begin() + index, perms.end());
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;
});
#if 0
Command result(this->notify_response_command("notifypermfind"));
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++;
}
this->sendCommand(result);
#else
command_builder result{this->notify_response_command("notifypermfind"), 64, perms.size()};
index = 0;
for(const auto& e : perms) {
auto bulk = result.bulk(index++);
bulk.put("t", e.t);
bulk.put("p", (uint16_t) e.p);
bulk.put("v", e.v);
bulk.put("id1", e.id1);
bulk.put("id2", e.id2);
}
this->sendCommand(result);
#endif
return command_result{error::ok};
}
/*
* - Alle rechte der aktuellen server gruppen vom client
* - Alle client rechte | channel cleint rechte
* - Alle rechte des channels
*/
command_result ConnectedClient::handleCommandPermOverview(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto client_dbid = cmd["cldbid"].as<ClientDbId>();
if(!serverInstance->databaseHelper()->validClientDatabaseId(this->getServer(), client_dbid)) return command_result{error::client_invalid_id};
if(client_dbid == this->getClientDatabaseId()) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_permissionoverview_own, 1);
} else {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_permissionoverview_view, 1);
}
string channel_query, perm_query;
auto channel = this->server ? this->server->channelTree->findChannel(cmd["cid"]) : serverInstance->getChannelTree()->findChannel(cmd["cid"]);
if(!channel) return command_result{error::channel_invalid_id};
auto server_groups = this->server->getGroupManager()->getServerGroups(client_dbid, ClientType::CLIENT_TEAMSPEAK);
auto channel_group = this->server->getGroupManager()->getChannelGroup(client_dbid, channel, true);
auto permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServer(), client_dbid);
Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifypermoverview" : "");
size_t index = 0;
result["cldbid"] = client_dbid;
result["cid"] = channel->channelId();
if(cmd["return_code"].size() > 0)
result["return_code"] = cmd["return_code"].string();
for(const auto& server_group : server_groups) {
auto permission_manager = server_group->group->permissions();
for(const auto& permission_data : permission_manager->permissions()) {
auto& permission = std::get<1>(permission_data);
if(permission.flags.value_set) {
result[index]["t"] = 0; /* server group */
result[index]["id1"] = server_group->group->groupId();
result[index]["id2"] = 0;
result[index]["p"] = std::get<0>(permission_data);
result[index]["v"] = permission.values.value;
result[index]["n"] = permission.flags.negate;
result[index]["s"] = permission.flags.skip;
index++;
}
if(permission.flags.grant_set) {
result[index]["t"] = 0; /* server group */
result[index]["id1"] = server_group->group->groupId();
result[index]["id2"] = 0;
result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT);
result[index]["v"] = permission.values.grant;
result[index]["n"] = false;
result[index]["s"] = false;
index++;
}
}
}
{
for(const auto& permission_data : permission_manager->permissions()) {
auto& permission = std::get<1>(permission_data);
if(permission.flags.value_set) {
result[index]["t"] = 1; /* client */
result[index]["id1"] = client_dbid;
result[index]["id2"] = 0;
result[index]["p"] = std::get<0>(permission_data);
result[index]["v"] = permission.values.value;
result[index]["n"] = permission.flags.negate;
result[index]["s"] = permission.flags.skip;
index++;
}
if(permission.flags.grant_set) {
result[index]["t"] = 1; /* client */
result[index]["id1"] = client_dbid;
result[index]["id2"] = 0;
result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT);
result[index]["v"] = permission.values.grant;
result[index]["n"] = false;
result[index]["s"] = false;
index++;
}
}
}
{
auto permission_manager = channel->permissions();
for(const auto& permission_data : permission_manager->permissions()) {
auto& permission = std::get<1>(permission_data);
if(permission.flags.value_set) {
result[index]["t"] = 2; /* server channel */
result[index]["id1"] = channel->channelId();
result[index]["id2"] = 0;
result[index]["p"] = std::get<0>(permission_data);
result[index]["v"] = permission.values.value;
result[index]["n"] = permission.flags.negate;
result[index]["s"] = permission.flags.skip;
index++;
}
if(permission.flags.grant_set) {
result[index]["t"] = 2; /* server channel */
result[index]["id1"] = channel->channelId();
result[index]["id2"] = 0;
result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT);
result[index]["v"] = permission.values.grant;
result[index]["n"] = false;
result[index]["s"] = false;
index++;
}
}
}
{
auto permission_manager = channel_group->group->permissions();
for(const auto& permission_data : permission_manager->permissions()) {
auto& permission = std::get<1>(permission_data);
if(permission.flags.value_set) {
result[index]["t"] = 3; /* channel group */
result[index]["id1"] = channel_group->channelId;
result[index]["id2"] = channel_group->group->groupId();
result[index]["p"] = std::get<0>(permission_data);
result[index]["v"] = permission.values.value;
result[index]["n"] = permission.flags.negate;
result[index]["s"] = permission.flags.skip;
index++;
}
if(permission.flags.grant_set) {
result[index]["t"] = 3; /* channel group */
result[index]["id1"] = channel_group->channelId;
result[index]["id2"] = channel_group->group->groupId();
result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT);
result[index]["v"] = permission.values.grant;
result[index]["n"] = false;
result[index]["s"] = false;
index++;
}
}
}
{
for(const auto& permission_data : permission_manager->channel_permissions()) {
auto& permission = std::get<2>(permission_data);
if(permission.flags.value_set) {
result[index]["t"] = 4; /* client channel */
result[index]["id1"] = std::get<1>(permission_data);
result[index]["id2"] = client_dbid;
result[index]["p"] = std::get<0>(permission_data);
result[index]["v"] = permission.values.value;
result[index]["n"] = permission.flags.negate;
result[index]["s"] = permission.flags.skip;
index++;
}
if(permission.flags.grant_set) {
result[index]["t"] = 1; /* client */
result[index]["id1"] = std::get<1>(permission_data);
result[index]["id2"] = client_dbid;
result[index]["p"] = (std::get<0>(permission_data) | PERM_ID_GRANT);
result[index]["v"] = permission.values.grant;
result[index]["n"] = false;
result[index]["s"] = false;
index++;
}
}
}
if (index == 0) return command_result{error::database_empty_result};
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandComplainAdd(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
ClientDbId target = cmd["tcldbid"];
std::string msg = cmd["message"];
auto cl = this->server->findClientsByCldbId(target);
if (cl.empty()) return command_result{error::client_invalid_id};
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_complain_power, cl[0]->calculate_permission(permission::i_client_needed_complain_power, 0));
/*
if(!serverInstance->databaseHelper()->validClientDatabaseId(target))
return command_result{error::client_invalid_id, "invalid database id"};
*/
for (const auto &elm : this->server->complains->findComplainsFromTarget(target))
if (elm->invoker == this->getClientDatabaseId())
return command_result{error::database_duplicate_entry, "you already send a complain"};
if (!this->server->complains->createComplain(target, this->getClientDatabaseId(), msg)) return command_result{error::vs_critical, "could not create complains"};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandComplainList(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_complain_list, 1);
ClientDbId id = cmd[0].has("tcldbid") ? cmd["tcldbid"].as<ClientDbId>() : 0;
auto list = id == 0 ? this->server->complains->complains() : this->server->complains->findComplainsFromTarget(id);
if (list.empty()) return command_result{error::database_empty_result};
deque<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->client_database_id == elm->target)
result[index]["tname"] = e->client_nickname;
if (e->client_database_id == elm->invoker)
result[index]["fname"] = e->client_nickname;
}
index++;
}
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandComplainDel(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ClientDbId tid = cmd["tcldbid"];
ClientDbId fid = cmd["fcldbid"];
shared_ptr<ComplainEntry> entry;
for (const auto &elm : this->server->complains->findComplainsFromTarget(tid))
if (elm->invoker == fid) {
entry = elm;
break;
}
if (!entry) return command_result{error::database_empty_result};
if (entry->invoker == this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_complain_delete_own, 1);
else
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_complain_delete, 1);
this->server->complains->deleteComplain(entry);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandComplainDelAll(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_complain_delete, 1);
ClientDbId tid = cmd["tcldbid"];
if (!this->server->complains->deleteComplainsFromTarget(tid)) return command_result{error::database_empty_result};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandHelp(Command& cmd) {
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_help_view, 1);
string command = cmd[0].has("command") ? cmd["command"].as<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 command_result{error::file_not_found};
string line;
ifstream stream(file);
if(!stream) return command_result{error::file_io_error};
while(getline(stream, line))
this->sendCommand(Command{line});
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPermReset(ts::Command& cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(50);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_permission_reset, 1);
string token;
if(!this->server->resetPermissions(token))
return command_result{error::vs_critical};
Command result("");
result["token"] = token;
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(50);
ServerId target_server = cmd[0].has("instance") && cmd["instance"].as<bool>() ? (ServerId) 0 : this->getServerId();
if(target_server == 0)
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_view, 1);
else
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_view, 1);
auto lagacy = this->getType() == CLIENT_TEAMSPEAK || cmd.hasParm("lagacy") || cmd.hasParm("legacy");
#if 0
string log_path;
string server_identifier;
if(target_server > 0)
server_identifier = to_string(target_server);
else
server_identifier = "[A-Z]{0,7}";
for(const auto& sink : logger::logger(target_server)->sinks()) {
if(dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_mt>(sink)) {
log_path = dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_mt>(sink)->filename();
break;
} else if(dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_st>(sink)) {
log_path = dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_st>(sink)->filename();
break;
}
}
if(log_path.empty())
return command_result{error::file_not_found, "Cant find log file (May log disabled?)"};
{ //Replace " within the log path
size_t index = 0;
while((index = log_path.find('"', index)) != string::npos) {
log_path.replace(index, 1, "\\\"");
index += 2;
}
}
string command = "cat \"" + log_path + "\"";
command += " | grep -E ";
command += R"("\] \[.*\]( ){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 command_result{error::file_io_error, "Could not execute command"};
while (!feof(pipe.get())) {
auto read = fread(buffer.data(), 1, buffer.size(), pipe.get());
if(read > 0) {
if(beginpos == 0 || file_index < beginpos) {
if(beginpos != 0 && file_index + read > beginpos) { //We're done we just want to get the size later
line_buffer += string(buffer.data(), beginpos - file_index);
lines.emplace_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.emplace_back(file_index + cut_offset, line_buffer.substr(0, index));
if(lines.size() > max_lines) lines.pop_front();
cut_offset += index + length;
line_buffer = line_buffer.substr(index + length);
}
}
}
file_index += read;
} else if(read < 0) return command_result{error::file_io_error, "fread(...) returned " + to_string(read) + " (" + to_string(errno) + ")"};
}
if(!line_buffer.empty()) {
lines.emplace_back(file_index - line_buffer.length(), line_buffer);
if(lines.size() > max_lines) lines.pop_front();
}
}
//last_pos=1558 file_size=1764 l
if(lines.empty()) return command_result{error::database_empty_result};
Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyserverlog" : "");
result["last_pos"] = lines.front().first;
result["file_size"] = file_index;
if(!(cmd.hasParm("reverse") && cmd["revers"].as<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);
}
if(ts.length() > 1024)
ts = ts.substr(0, 1024) + "...";
result[index++]["l"] = ts;
} else {
if(line.length() > 1024)
line = line.substr(0, 1024) + "...";
result[index++]["l"] = line;
}
}
this->sendCommand(result);
#else
constexpr static std::array<std::string_view, 5> log_output{
"located at your TeaSpeak installation folder. All logs could be found there.",
"If you need to lookup the TeaSpeak - Server logs, please visit the 'logs/' folder,",
"",
"In order to lookup the server actions use 'logquery'.",
"The command 'logview' is not supported anymore."
};
command_builder result{this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyserverlog" : ""};
result.put_unchecked(0, "last_pos", 0);
result.put_unchecked(0, "file_size", 0);
size_t index{0};
if(lagacy) {
for(const auto& message : log_output) {
std::string line{"2020-06-27 00:00.000" + std::to_string(index) + "|CRITICAL|Server Instance | |"};
line += message;
result.put_unchecked(index++, "l", line);
}
} else {
for(const auto& message : log_output) {
std::string line{"[2020-06-27 00:00:0" + std::to_string(index) + "][ERROR] "};
line += message;
result.put_unchecked(index++, "l", line);
}
}
this->sendCommand(result);
#endif
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandLogQuery(ts::Command &cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(50);
uint64_t target_server = (cmd[0].has("instance") && cmd["instance"].as<bool>()) || cmd.hasParm("instance") ? (ServerId) 0 : this->getServerId();
if(target_server == 0) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_view, 1);
} else {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_view, 1);
}
std::chrono::system_clock::time_point timestamp_begin{}, timestamp_end{};
if(cmd[0].has("begin"))
timestamp_begin += std::chrono::milliseconds{cmd["begin"].as<uint64_t>()};
if(cmd[0].has("end"))
timestamp_end += std::chrono::milliseconds{cmd["end"].as<uint64_t>()};
if(timestamp_begin <= timestamp_end && timestamp_begin.time_since_epoch().count() != 0)
return command_result{error::parameter_constraint_violation, "begin > end"};
size_t limit{100};
if(cmd[0].has("limit"))
limit = std::min((size_t) 2000, cmd["limit"].as<size_t>());
std::vector<log::LoggerGroup> groups{};
if(cmd[0].has("groups")) {
auto groups_string = cmd["groups"].string();
size_t offset{0}, findex;
do {
findex = groups_string.find(',', offset);
auto group_name = groups_string.substr(offset, findex - offset);
offset = findex + 1;
size_t index{0};
for(; index < (size_t) log::LoggerGroup::MAX; index++) {
auto group = static_cast<log::LoggerGroup>(index);
if(log::kLoggerGroupName[(int) group] == group_name) {
if(std::find(groups.begin(), groups.end(), group) != groups.end())
return command_result{error::parameter_invalid, "groups"};
groups.push_back(group);
}
}
if(index == (size_t) log::LoggerGroup::MAX)
return command_result{error::parameter_invalid, "groups"};
} while(offset != 0);
}
auto result = serverInstance->action_logger()->query(groups, target_server, timestamp_begin, timestamp_end, limit);
if(result.empty())
return command_result{error::database_empty_result};
command_builder notify{this->notify_response_command("notifylogquery")};
size_t index{0};
size_t threshold = this->getType() == ClientType::CLIENT_QUERY ? (size_t) -1 : 4096; /* limit each command to 4096 bytes */
for(log::LogEntryInfo& entry : result) {
notify.set_bulk(index, std::move(entry.info));
if(index == 0)
notify.put_unchecked(0, "sid", this->getServerId());
if(notify.current_size() > threshold) {
this->sendCommand(notify);
notify.reset();
index = 0;
}
index++;
}
if(index> 0)
this->sendCommand(notify);
if(this->getType() != ClientType::CLIENT_QUERY)
this->sendCommand(command_builder{"notifylogqueryfinished"});
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandLogAdd(ts::Command& cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(50);
uint64_t target_server = cmd[0].has("instance") && cmd["instance"].as<bool>() ? (ServerId) 0 : this->getServerId();
if(target_server == 0) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_log_add, 1);
} else {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_log_add, 1);
}
serverInstance->action_logger()->custom_logger.add_log_message(target_server, this->ref(), cmd["msg"]);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandUpdateMyTsId(ts::Command &) {
if(config::voice::suppress_myts_warnings) return command_result{error::ok};
return command_result{error::not_implemented};
}
command_result ConnectedClient::handleCommandUpdateMyTsData(ts::Command &) {
if(config::voice::suppress_myts_warnings) return command_result{error::ok};
return command_result{error::not_implemented};
}
command_result ConnectedClient::handleCommandQueryList(ts::Command &cmd) {
OptionalServerId server_id = EmptyServerId;
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK)
server_id = this->getServerId();
if(cmd[0].has("server_id"))
server_id = cmd["server_id"];
if(cmd[0].has("sid"))
server_id = cmd["sid"];
auto server = server_id == EmptyServerId ? nullptr : serverInstance->getVoiceServerManager()->findServerById(server_id);
if(!server && server_id != EmptyServerId && server_id != 0)
return command_result{error::server_invalid_id};
auto global_list = permission::v2::permission_granted(1,
server ? server->calculate_permission(permission::b_client_query_list, this->getClientDatabaseId(), this->getType(), 0) :
serverInstance->calculate_permission(permission::b_client_query_list, this->getClientDatabaseId(), this->getType(), 0)
);
auto own_list = global_list || permission::v2::permission_granted(1,
server ? server->calculate_permission(permission::b_client_query_list_own, this->getClientDatabaseId(), this->getType(), 0) :
serverInstance->calculate_permission(permission::b_client_query_list_own, this->getClientDatabaseId(), this->getType(), 0)
);
if(!own_list && !global_list)
return command_result{permission::b_client_query_list};
auto accounts = serverInstance->getQueryServer()->list_query_accounts(server_id);
if(!global_list) {
accounts.erase(remove_if(accounts.begin(), accounts.end(), [&](const std::shared_ptr<QueryAccount>& account) {
return account->unique_id != this->getUid();
}), accounts.end());
}
if(accounts.empty())
return command_result{error::database_empty_result};
Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyquerylist" : "");
result["server_id"] = server_id;
result["flag_own"] = own_list;
result["flag_all"] = global_list;
size_t index = 0;
for(const auto& account : accounts) {
result[index]["client_unique_identifier"] = account->unique_id;
result[index]["client_login_name"] = account->username;
result[index]["client_bound_server"] = account->bound_server;
index++;
}
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) {
OptionalServerId server_id = this->getServerId();
if(cmd[0].has("server_id"))
server_id = cmd["server_id"];
if(cmd[0].has("sid"))
server_id = cmd["sid"];
auto server = server_id == EmptyServerId ? nullptr : serverInstance->getVoiceServerManager()->findServerById(server_id);
if(!server && server_id != EmptyServerId && server_id != 0)
return command_result{error::server_invalid_id};
auto username = cmd["client_login_name"].as<string>();
auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as<string>() : "";
auto account = serverInstance->getQueryServer()->find_query_account_by_name(username);
if(account) return command_result{error::query_already_exists};
std::string uid = this->getUid();
if(cmd[0].has("cldbid")){
if(!serverInstance->databaseHelper()->validClientDatabaseId(server, cmd["cldbid"].as<ClientDbId>()))
return command_result{error::database_empty_result};
if(server) {
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create};
} else {
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create};
}
auto info = serverInstance->databaseHelper()->queryDatabaseInfo(server, {cmd["cldbid"].as<ClientDbId>()});
if(info.empty())
return command_result{error::database_empty_result};
uid = info[0]->client_unique_id;
} else {
if(server) {
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create_own, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create_own};
} else {
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create_own, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create_own};
}
}
if(password.empty())
password = rnd_string(QUERY_PASSWORD_LENGTH);
account = serverInstance->getQueryServer()->create_query_account(username, server_id, uid, password);
if(!account)
return command_result{error::vs_critical};
Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyquerycreated" : "");
result["client_unique_identifier"] = account->unique_id;
result["client_login_name"] = account->username;
result["client_login_password"] = password;
result["client_bound_server"] = account->bound_server;
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandQueryDelete(ts::Command &cmd) {
auto username = cmd["client_login_name"].as<string>();
auto account = serverInstance->getQueryServer()->find_query_account_by_name(username);
if(!account)
return command_result{error::query_not_exists};
auto server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server);
/* If the server is not existing anymore, we're asking for global permissions
if(!server && account->bounded_server != 0)
return command_result{error::server_invalid_id};
*/
auto delete_all = permission::v2::permission_granted(1,
server ? server->calculate_permission(permission::b_client_query_delete, this->getClientDatabaseId(), this->getType(), 0) :
serverInstance->calculate_permission(permission::b_client_query_delete, this->getClientDatabaseId(), this->getType(), 0)
);
auto delete_own = delete_all || permission::v2::permission_granted(1,
server ? server->calculate_permission(permission::b_client_query_delete_own, this->getClientDatabaseId(), this->getType(), 0) :
serverInstance->calculate_permission(permission::b_client_query_delete_own, this->getClientDatabaseId(), this->getType(), 0)
);
if(account->unique_id == this->getUid()) {
if(!delete_own)
return command_result{permission::b_client_query_delete_own};
} else {
if(!delete_all)
return command_result{permission::b_client_query_delete};
}
if(account->unique_id == "serveradmin" && account->username == "serveradmin")
return command_result{error::vs_critical};
serverInstance->getQueryServer()->delete_query_account(account);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandQueryRename(ts::Command &cmd) {
auto username = cmd["client_login_name"].as<string>();
auto new_username = cmd["client_new_login_name"].as<string>();
auto account = serverInstance->getQueryServer()->find_query_account_by_name(username);
if(!account)
return command_result{error::query_not_exists};
auto server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server);
if(!server && account->bound_server != 0)
return command_result{error::server_invalid_id};
auto rename_all = permission::v2::permission_granted(1,
server ? server->calculate_permission(permission::b_client_query_rename, this->getClientDatabaseId(), this->getType(), 0) :
serverInstance->calculate_permission(permission::b_client_query_rename, this->getClientDatabaseId(), this->getType(), 0)
);
auto rename_own = rename_all || permission::v2::permission_granted(1,
server ? server->calculate_permission(permission::b_client_query_rename_own, this->getClientDatabaseId(), this->getType(), 0) :
serverInstance->calculate_permission(permission::b_client_query_rename_own, this->getClientDatabaseId(), this->getType(), 0)
);
if(account->unique_id == this->getUid()) {
if(!rename_own)
return command_result{permission::b_client_query_rename_own};
} else {
if(!rename_all)
return command_result{permission::b_client_query_rename};
}
auto target_account = serverInstance->getQueryServer()->find_query_account_by_name(new_username);
if(target_account) return command_result{error::query_already_exists};
if(account->unique_id == "serveradmin" && account->username == "serveradmin")
return command_result{error::parameter_invalid};
if(!serverInstance->getQueryServer()->rename_query_account(account, new_username))
return command_result{error::vs_critical};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandQueryChangePassword(ts::Command &cmd) {
auto username = cmd["client_login_name"].as<string>();
auto account = serverInstance->getQueryServer()->find_query_account_by_name(username);
if(!account)
return command_result{error::query_not_exists};
auto server = serverInstance->getVoiceServerManager()->findServerById(account->bound_server);
if(!server && account->bound_server != 0)
return command_result{error::server_invalid_id};
auto change_all = permission::v2::permission_granted(1,
server ? server->calculate_permission(permission::b_client_query_change_password, this->getClientDatabaseId(), this->getType(), 0) :
serverInstance->calculate_permission(permission::b_client_query_change_password_global, this->getClientDatabaseId(), this->getType(), 0)
);
auto change_own = change_all || permission::v2::permission_granted(1,
server ? server->calculate_permission(permission::b_client_query_change_own_password, this->getClientDatabaseId(), this->getType(), 0) :
serverInstance->calculate_permission(permission::b_client_query_change_own_password, this->getClientDatabaseId(), this->getType(), 0)
);
auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as<string>() : "";
if(password.empty())
password = rnd_string(QUERY_PASSWORD_LENGTH);
if(account->unique_id == this->getUid()) {
if(!change_own)
return command_result{permission::b_client_query_change_own_password};
} else {
if(!change_all)
return command_result{server ? permission::b_client_query_change_password : permission::b_client_query_change_password_global};
}
if(!serverInstance->getQueryServer()->change_query_password(account, password))
return command_result{error::vs_critical};
Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyquerypasswordchanges" : "");
result["client_login_name"] = account->username;
result["client_login_password"] = password;
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandDummy_IpChange(ts::Command &cmd) {
CMD_REF_SERVER(server);
logMessage(this->getServerId(), "[{}] Address changed from {} to {}", CLIENT_STR_LOG_PREFIX, cmd["old_ip"].string(), cmd["new_ip"].string());
if(geoloc::provider_vpn && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_vpn, 0))) {
auto provider = this->isAddressV4() ? geoloc::provider_vpn->resolveInfoV4(this->getPeerIp(), true) : geoloc::provider_vpn->resolveInfoV6(this->getPeerIp(), true);
if(provider) {
this->disconnect(strvar::transform(ts::config::messages::kick_vpn, strvar::StringValue{"provider.name", provider->name}, strvar::StringValue{"provider.website", provider->side}));
return command_result{error::ok};
}
}
string new_country = config::geo::countryFlag;
if(geoloc::provider) {
auto loc = this->isAddressV4() ? geoloc::provider->resolveInfoV4(this->getPeerIp(), false) : geoloc::provider->resolveInfoV6(this->getPeerIp(), false);
if(loc) {
logMessage(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());
}
}
this->properties()[property::CONNECTION_CLIENT_IP] = this->getLoggingPeerIp();
return command_result{error::ok};
}
//conversationhistory cid=1 [cpw=xxx] [timestamp_begin] [timestamp_end (0 := no end)] [message_count (default 25| max 100)] [-merge]
command_result ConnectedClient::handleCommandConversationHistory(ts::Command &command) {
CMD_REF_SERVER(ref_server);
CMD_CHK_AND_INC_FLOOD_POINTS(25);
if(!command[0].has("cid") || !command[0]["cid"].castable<ChannelId>())
return command_result{error::conversation_invalid_id};
auto conversation_id = command[0]["cid"].as<ChannelId>();
/* test if we have access to the conversation */
if(conversation_id > 0) {
/* test if we're able to see the channel */
{
shared_lock channel_view_lock(this->channel_lock);
auto channel = this->channel_view()->find_channel(conversation_id);
if(!channel)
return command_result{error::conversation_invalid_id};
auto conversation_mode = channel->channel()->properties()[property::CHANNEL_CONVERSATION_MODE].as<ChannelConversationMode>();
switch (conversation_mode) {
case ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE:
return command_result{error::conversation_is_private};
case ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE:
return command_result{error::conversation_not_exists};
case ChannelConversationMode::CHANNELCONVERSATIONMODE_PUBLIC:
break;
}
}
/* test if there is a channel password or join power which denies that we see the conversation */
{
shared_lock channel_view_lock(ref_server->channel_tree_lock);
auto channel = ref_server->getChannelTree()->findChannel(conversation_id);
if(!channel) /* should never happen! */
return command_result{error::conversation_invalid_id};
if(!command[0].has("cpw"))
command[0]["cpw"] = "";
if (!channel->passwordMatch(command["cpw"], true))
ACTION_REQUIRES_PERMISSION(permission::b_channel_join_ignore_password, 1, channel->channelId());
auto error = this->calculate_and_get_join_state(channel);
if(error) return command_result{error};
}
}
auto conversation_manager = ref_server->conversation_manager();
auto conversation = conversation_manager->get(conversation_id);
if(!conversation)
return command_result{error::database_empty_result};
system_clock::time_point timestamp_begin = system_clock::now();
system_clock::time_point timestamp_end;
size_t message_count = 25;
if(command[0].has("timestamp_begin"))
timestamp_begin = system_clock::time_point{} + milliseconds(command[0]["timestamp_begin"].as<uint64_t>());
if(command[0].has("timestamp_end"))
timestamp_end = system_clock::time_point{} + milliseconds(command[0]["timestamp_end"].as<uint64_t>());
if(command[0].has("message_count"))
message_count = command[0]["message_count"].as<uint64_t>();
if(timestamp_begin < timestamp_end)
return command_result{error::parameter_invalid};
if(message_count > 100)
message_count = 100;
auto messages = conversation->message_history(timestamp_begin, message_count + 1, timestamp_end); /* query one more to test for more data */
if(messages.empty())
return command_result{error::database_empty_result};
bool more_data = messages.size() > message_count;
if(more_data)
messages.pop_back();
Command notify(this->notify_response_command("notifyconversationhistory"));
size_t index = 0;
size_t length = 0;
bool merge = command.hasParm("merge");
for(auto it = messages.rbegin(); it != messages.rend(); it++) {
if(index == 0) {
notify[index]["cid"] = conversation_id;
notify[index]["flag_volatile"] = conversation->volatile_only();
}
auto& message = *it;
notify[index]["timestamp"] = duration_cast<milliseconds>(message->message_timestamp.time_since_epoch()).count();
notify[index]["sender_database_id"] = message->sender_database_id;
notify[index]["sender_unique_id"] = message->sender_unique_id;
notify[index]["sender_name"] = message->sender_name;
notify[index]["msg"] = message->message;
length += message->message.size();
length += message->sender_name.size();
length += message->sender_unique_id.size();
if(length > 1024 * 8 || !merge) {
index = 0;
this->sendCommand(notify);
notify = Command{this->notify_response_command("notifyconversationhistory")};
} else
index++;
}
if(index > 0)
this->sendCommand(notify);
if(more_data)
return command_result{error::conversation_more_data};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandConversationFetch(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_CHK_AND_INC_FLOOD_POINTS(25);
Command result(this->notify_response_command("notifyconversationindex"));
size_t result_index = 0;
auto conversation_manager = ref_server->conversation_manager();
for(size_t index = 0; index < cmd.bulkCount(); index++) {
auto& bulk = cmd[index];
if(!bulk.has("cid") || !bulk["cid"].castable<ChannelId>())
continue;
auto conversation_id = bulk["cid"].as<ChannelId>();
auto& result_bulk = result[result_index++];
result_bulk["cid"] = conversation_id;
/* test if we have access to the conversation */
if(conversation_id > 0) {
/* test if we're able to see the channel */
{
shared_lock channel_view_lock(this->channel_lock);
auto channel = this->channel_view()->find_channel(conversation_id);
if(!channel) {
auto error = findError("conversation_invalid_id");
result_bulk["error_id"] = error.errorId;
result_bulk["error_msg"] = error.message;
continue;
}
auto conversation_mode = channel->channel()->properties()[property::CHANNEL_CONVERSATION_MODE].as<ChannelConversationMode>();
switch (conversation_mode) {
case ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE: {
auto error = findError("conversation_is_private");
result_bulk["error_id"] = error.errorId;
result_bulk["error_msg"] = error.message;
continue;
}
case ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE: {
auto error = findError("conversation_not_exists");
result_bulk["error_id"] = error.errorId;
result_bulk["error_msg"] = error.message;
continue;
}
case ChannelConversationMode::CHANNELCONVERSATIONMODE_PUBLIC:
break;
}
}
/* test if there is a channel password or join power which denies that we see the conversation */
{
shared_lock channel_view_lock(ref_server->channel_tree_lock);
auto channel = ref_server->getChannelTree()->findChannel(conversation_id);
if(!channel) { /* should never happen! */
auto error = findError("conversation_invalid_id");
result_bulk["error_id"] = error.errorId;
result_bulk["error_msg"] = error.message;
continue;
}
if(!bulk.has("cpw"))
bulk["cpw"] = "";
if (!channel->passwordMatch(bulk["cpw"], true))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, 1, channel->channelId()))) {
auto error = findError("channel_invalid_password");
result_bulk["error_id"] = error.errorId;
result_bulk["error_msg"] = error.message;
continue;
}
if(auto error_perm = this->calculate_and_get_join_state(channel); error_perm) {
auto error = findError("server_insufficeient_permissions");
result_bulk["error_id"] = error.errorId;
result_bulk["error_msg"] = error.message;
result_bulk["failed_permid"] = (int) error_perm;
continue;
}
}
}
auto conversation = conversation_manager->get(conversation_id);
if(conversation) {
result_bulk["timestamp"] = duration_cast<milliseconds>(conversation->last_message().time_since_epoch()).count();
result_bulk["flag_volatile"] = conversation->volatile_only();
} else {
result_bulk["timestamp"] = 0;
result_bulk["flag_volatile"] = false;
}
}
if(result_index == 0)
return command_result{error::database_empty_result};
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandConversationMessageDelete(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto conversation_manager = ref_server->conversation_manager();
std::shared_ptr<conversation::Conversation> current_conversation;
ChannelId current_conversation_id = 0;
for(size_t index = 0; index < cmd.bulkCount(); index++) {
auto &bulk = cmd[index];
if(!bulk.has("cid") || !bulk["cid"].castable<ChannelId>())
continue;
/* test if we have access to the conversation */
if(current_conversation_id != bulk["cid"].as<ChannelId>()) {
current_conversation_id = bulk["cid"].as<ChannelId>();
/* test if we're able to see the channel */
{
shared_lock channel_view_lock(this->channel_lock);
auto channel = this->channel_view()->find_channel(current_conversation_id);
if(!channel)
return command_result{error::conversation_invalid_id};
}
/* test if there is a channel password or join power which denies that we see the conversation */
{
shared_lock channel_view_lock(ref_server->channel_tree_lock);
auto channel = ref_server->getChannelTree()->findChannel(current_conversation_id);
if(!channel)
return command_result{error::conversation_invalid_id};
if(!bulk.has("cpw"))
bulk["cpw"] = "";
if (!channel->passwordMatch(bulk["cpw"], true))
ACTION_REQUIRES_PERMISSION(permission::b_channel_join_ignore_password, 1, channel->channelId());
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_conversation_message_delete, channel->channelId())))
return command_result{permission::b_channel_conversation_message_delete};
if(auto error_perm = this->calculate_and_get_join_state(channel); error_perm != permission::ok && error_perm != permission::b_client_is_sticky)
return command_result{error_perm};
}
}
current_conversation = conversation_manager->get(current_conversation_id);
if(!current_conversation) continue;
auto timestamp_begin = system_clock::time_point{} + milliseconds{bulk["timestamp_begin"]};
auto timestamp_end = system_clock::time_point{} + milliseconds{bulk.has("timestamp_end") ? bulk["timestamp_end"].as<uint64_t>() : 0};
auto limit = bulk.has("limit") ? bulk["limit"].as<uint64_t>() : 1;
if(limit > 100)
limit = 100;
auto delete_count = current_conversation->delete_messages(timestamp_end, limit, timestamp_begin, bulk["cldbid"]);
if(delete_count > 0) {
for(const auto& client : ref_server->getClients()) {
if(client->connectionState() != ConnectionState::CONNECTED)
continue;
auto type = client->getType();
if(type == ClientType::CLIENT_INTERNAL || type == ClientType::CLIENT_MUSIC)
continue;
client->notifyConversationMessageDelete(current_conversation_id, timestamp_begin, timestamp_end, bulk["cldbid"], delete_count);
}
}
}
return command_result{error::ok};
}
enum struct FeatureSupportMode {
NONE,
FULL,
EXPERIMENTAL,
DEPRECATED
};
#define REGISTER_FEATURE(name, support, version) \
notify.put_unchecked(index, "name", name); \
notify.put_unchecked(index, "support", (int) support); \
notify.put_unchecked(index, "version", version); \
index++
command_result ConnectedClient::handleCommandListFeatureSupport(ts::Command &cmd) {
ts::command_builder notify{this->notify_response_command("notifyfeaturesupport")};
int index{0};
REGISTER_FEATURE("error-bulks", FeatureSupportMode::FULL, 1);
REGISTER_FEATURE("advanced-channel-chat", FeatureSupportMode::FULL, 1);
REGISTER_FEATURE("log-query", FeatureSupportMode::FULL, 1);
REGISTER_FEATURE("whisper-echo", FeatureSupportMode::FULL, 1);
REGISTER_FEATURE("video", FeatureSupportMode::EXPERIMENTAL, 1);
this->sendCommand(notify);
return command_result{error::ok};
}