1205 lines
60 KiB
C++
1205 lines
60 KiB
C++
#include <memory>
|
|
|
|
#include <bitset>
|
|
#include <algorithm>
|
|
#include "../../build.h"
|
|
#include "../ConnectedClient.h"
|
|
#include "../InternalClient.h"
|
|
#include "../../server/file/FileServer.h"
|
|
#include "../voice/VoiceClient.h"
|
|
#include "PermissionManager.h"
|
|
#include "../../InstanceHandler.h"
|
|
#include "../../server/QueryServer.h"
|
|
#include "../file/FileClient.h"
|
|
#include "../music/MusicClient.h"
|
|
#include "../query/QueryClient.h"
|
|
#include "../../weblist/WebListManager.h"
|
|
#include "../../manager/ConversationManager.h"
|
|
#include "../../manager/PermissionNameMapper.h"
|
|
#include <cstdint>
|
|
|
|
#include "helpers.h"
|
|
|
|
#include <Properties.h>
|
|
#include <log/LogUtils.h>
|
|
#include <misc/sassert.h>
|
|
#include <misc/base64.h>
|
|
#include <misc/hex.h>
|
|
#include <misc/rnd.h>
|
|
#include <bbcode/bbcodes.h>
|
|
|
|
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::handleCommandClientGetVariables(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
|
|
shared_lock tree_lock(this->channel_lock);
|
|
|
|
if (!client || (client.client != this && !this->isClientVisible(client.client, false)))
|
|
return command_result{error::client_invalid_id, ""};
|
|
|
|
deque<shared_ptr<property::PropertyDescription>> props;
|
|
for (auto &prop : client->properties()->list_properties(property::FLAG_CLIENT_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
|
|
props.push_back(property::info((property::ClientProperties) prop.type().property_index));
|
|
}
|
|
|
|
this->notifyClientUpdated(client.client, props, false);
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientKick(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(25);
|
|
|
|
ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
|
|
if (!client) return command_result{error::client_invalid_id};
|
|
if (client->getType() == CLIENT_MUSIC) return command_result{error::client_invalid_type, "You cant kick a music bot!"};
|
|
std::shared_ptr<BasicChannel> targetChannel = nullptr;
|
|
auto type = cmd["reasonid"].as<ViewReasonId>();
|
|
if (type == ViewReasonId::VREASON_CHANNEL_KICK) {
|
|
auto channel = client->getChannel();
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_kick_from_channel_power, client->calculate_permission(permission::i_client_needed_kick_from_channel_power, client->getChannelId()), client->getChannelId());
|
|
targetChannel = this->server->channelTree->getDefaultChannel();
|
|
} else if (type == ViewReasonId::VREASON_SERVER_KICK) {
|
|
auto channel = client->getChannel();
|
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_kick_from_server_power, client->calculate_permission(permission::i_client_needed_kick_from_server_power, client->getChannelId()));
|
|
targetChannel = nullptr;
|
|
} else return command_result{error::not_implemented};
|
|
|
|
if (targetChannel) {
|
|
this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as<std::string>(), targetChannel);
|
|
} else {
|
|
this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as<std::string>(), nullptr);
|
|
client->close_connection(system_clock::now() + seconds(1));
|
|
}
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientGetIds(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
|
|
bool error = false;
|
|
bool found = false;
|
|
auto client_list = this->server->getClients();
|
|
|
|
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientids" : "");
|
|
int result_index = 0;
|
|
|
|
for(int index = 0; index < cmd.bulkCount(); index++) {
|
|
auto unique_id = cmd[index]["cluid"].as<string>();
|
|
for(const auto& entry : client_list) {
|
|
if(entry->getUid() == unique_id) {
|
|
if(!config::server::show_invisible_clients_as_online && !this->channels->channel_visible(entry->currentChannel, nullptr))
|
|
continue;
|
|
|
|
notify[result_index]["name"] = entry->getDisplayName();
|
|
notify[result_index]["clid"] = entry->getClientId();
|
|
notify[result_index]["cluid"] = entry->getUid();
|
|
result_index++;
|
|
found = true;
|
|
}
|
|
}
|
|
if(found) found = false;
|
|
else error = false;
|
|
}
|
|
string uid = cmd["cluid"];
|
|
|
|
if(result_index > 0) {
|
|
this->sendCommand(notify);
|
|
}
|
|
if(error) {
|
|
return command_result{error::database_empty_result};
|
|
}
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(10);
|
|
|
|
shared_lock server_channel_r_lock(this->server->channel_tree_lock);
|
|
auto target_client_id = cmd["clid"].as<ClientId>();
|
|
ConnectedLockedClient target_client{target_client_id == 0 ? this->ref() : this->server->find_client_by_id(target_client_id)};
|
|
if(!target_client) {
|
|
return command_result{error::client_invalid_id, "Invalid target clid"};
|
|
}
|
|
|
|
if(!target_client->getChannel()) {
|
|
if(target_client.client != this)
|
|
return command_result{error::client_invalid_id, "Invalid target clid"};
|
|
}
|
|
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
|
|
if (!channel) {
|
|
return command_result{error::channel_invalid_id};
|
|
}
|
|
|
|
auto permission_cache = make_shared<CalculateCache>();
|
|
if(!cmd[0].has("cpw"))
|
|
cmd["cpw"] = "";
|
|
if (!channel->passwordMatch(cmd["cpw"], true))
|
|
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId())))
|
|
return command_result{error::channel_invalid_password};
|
|
|
|
auto permission_error = this->calculate_and_get_join_state(channel);
|
|
if(permission_error != permission::unknown) return command_result{permission_error};
|
|
|
|
if (!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as<bool>() || !channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as<bool>()) {
|
|
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_ignore_maxclients, channel->channelId()))) {
|
|
if(!channel->properties()[property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED].as<bool>()) {
|
|
auto maxClients = channel->properties()[property::CHANNEL_MAXCLIENTS].as<int32_t>();
|
|
if (maxClients >= 0 && maxClients <= this->server->getClientsByChannel(channel).size())
|
|
return command_result{error::channel_maxclients_reached};
|
|
}
|
|
if(!channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED].as<bool>()) {
|
|
shared_ptr<BasicChannel> family_root;
|
|
|
|
if(channel->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as<bool>()) {
|
|
family_root = channel;
|
|
while(family_root && family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED].as<bool>()) family_root = family_root->parent();
|
|
}
|
|
if(family_root && !family_root->properties()[property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED]) { //Could not be CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED
|
|
auto maxClients = family_root->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as<int32_t>();
|
|
auto clients = 0;
|
|
for(const auto& entry : this->server->getClientsByChannelRoot(channel, false)) if(entry.get() != this) clients++; //Dont count the client itself
|
|
if (maxClients >= 0 && maxClients <= clients)
|
|
return command_result{error::channel_maxfamily_reached};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (target_client.client != this)
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_move_power, target_client->calculate_permission(permission::i_client_needed_move_power, target_client->getChannelId()), target_client->getChannelId());
|
|
|
|
server_channel_r_lock.unlock();
|
|
unique_lock server_channel_w_lock(this->server->channel_tree_lock);
|
|
auto oldChannel = target_client->getChannel();
|
|
this->server->client_move(
|
|
target_client.client,
|
|
channel,
|
|
target_client.client == this ? nullptr : _this.lock(),
|
|
"",
|
|
target_client.client == this ? ViewReasonId::VREASON_USER_ACTION : ViewReasonId::VREASON_MOVED,
|
|
true,
|
|
server_channel_w_lock
|
|
);
|
|
|
|
if(oldChannel) {
|
|
if(!server_channel_w_lock.owns_lock())
|
|
server_channel_w_lock.lock();
|
|
if(oldChannel->channelType() == ChannelType::temporary && oldChannel->properties()[property::CHANNEL_DELETE_DELAY].as<int64_t>() == 0)
|
|
if(this->server->getClientsByChannelRoot(oldChannel, false).empty())
|
|
this->server->delete_channel(dynamic_pointer_cast<ServerChannel>(oldChannel), this->ref(), "temporary auto delete", server_channel_w_lock);
|
|
if(server_channel_w_lock.owns_lock())
|
|
server_channel_w_lock.unlock();
|
|
}
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientPoke(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(25);
|
|
|
|
ConnectedLockedClient client{ this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
|
|
if (!client) return command_result{error::client_invalid_id};
|
|
if (client->getType() == CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_poke_power, client->calculate_permission(permission::i_client_needed_poke_power, client->getChannelId()), client->getChannelId());
|
|
|
|
client->notifyClientPoke(_this.lock(), cmd["msg"]);
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
|
|
command_result ConnectedClient::handleCommandClientChatComposing(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(0);
|
|
|
|
ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
|
|
if (!client) return command_result{error::client_invalid_id};
|
|
|
|
client->notifyClientChatComposing(_this.lock());
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientChatClosed(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
|
|
|
ConnectedLockedClient<ConnectedClient> client{this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
|
|
if (!client) return command_result{error::client_invalid_id};
|
|
{
|
|
unique_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() == client;
|
|
}), this->openChats.end());
|
|
}
|
|
{
|
|
unique_lock channel_lock(client->get_channel_lock());
|
|
client->openChats.erase(remove_if(client->openChats.begin(), client->openChats.end(), [&](const weak_ptr<ConnectedClient>& weak) {
|
|
return weak.lock().get() == this;
|
|
}), client->openChats.end());
|
|
}
|
|
client->notifyClientChatClosed(_this.lock());
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
//ftgetfilelist cid=1 cpw path=\/ return_code=1:x
|
|
//Answer:
|
|
//1 .. n
|
|
// notifyfilelist cid=1 path=\/ return_code=1:x name=testFile size=35256 datetime=1509459767 type=1|name=testDir size=0 datetime=1509459741 type=0|name=testDir_2 size=0 datetime=1509459763 type=0
|
|
//notifyfilelistfinished cid=1 path=\/
|
|
inline void cmd_filelist_append_files(ServerId sid, Command &command, vector<std::shared_ptr<file::FileEntry>> files) {
|
|
int index = 0;
|
|
|
|
logTrace(sid, "Sending file list for path {}", command["path"].string());
|
|
for (const auto& fileEntry : files) {
|
|
logTrace(sid, " - {} ({})", fileEntry->name, fileEntry->type == file::FileType::FILE ? "file" : "directory");
|
|
|
|
command[index]["name"] = fileEntry->name;
|
|
command[index]["datetime"] = std::chrono::duration_cast<std::chrono::seconds>(fileEntry->lastChanged.time_since_epoch()).count();
|
|
command[index]["type"] = fileEntry->type;
|
|
if (fileEntry->type == file::FileType::FILE)
|
|
command[index]["size"] = static_pointer_cast<file::File>(fileEntry)->fileSize;
|
|
else
|
|
command[index]["size"] = 0;
|
|
index++;
|
|
}
|
|
}
|
|
|
|
#define CMD_REQ_FSERVER if(!serverInstance->getFileServer()) return command_result{error::vs_critical, "file server not started yet!"}
|
|
|
|
//start=0 duration=10
|
|
//pattern=%asd%
|
|
|
|
struct ClientDbArgs {
|
|
shared_ptr<VirtualServer> server;
|
|
int index = 0;
|
|
int offset = 0;
|
|
int resultIndex = 0;
|
|
bool showIp = false;
|
|
bool largeInfo = false;
|
|
Command *result = nullptr;
|
|
};
|
|
|
|
command_result ConnectedClient::handleCommandClientDbList(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(25);
|
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dblist, 1);
|
|
|
|
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdblist" : "");
|
|
|
|
if (!cmd[0].has("start"))
|
|
cmd["start"] = 0;
|
|
if (!cmd[0].has("duration"))
|
|
cmd["duration"] = 20;
|
|
if (cmd[0]["duration"].as<int>() > 2000) cmd["duration"] = 2000;
|
|
if (cmd[0]["duration"].as<int>() < 1) cmd["duration"] = 1;
|
|
|
|
auto maxIndex = cmd["start"].as<uint32_t>() + cmd["duration"].as<uint32_t>();
|
|
ClientDbArgs args;
|
|
args.server = this->server;
|
|
args.offset = cmd["start"].as<uint32_t>();
|
|
args.result = ¬ify;
|
|
args.resultIndex = 0;
|
|
args.index = 0;
|
|
args.showIp = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
|
args.largeInfo = cmd.hasParm("details");
|
|
|
|
(LOG_SQL_CMD)(sql::command(this->server->getSql(), "SELECT * FROM `clients` WHERE `serverId` = :sid ORDER BY `cldbid` ASC" + (maxIndex > 0 ? " LIMIT " + to_string(maxIndex) : ""), variable{":sid", this->server->getServerId()}).query(
|
|
[](ClientDbArgs *pArgs, int length, char **values, char **column) {
|
|
pArgs->index++;
|
|
if (pArgs->offset < pArgs->index) {
|
|
ClientDbId id = 0;
|
|
string uid, name, ip;
|
|
string created = "0", lastConnected = "0", connections = "0";
|
|
for (int index = 0; index < length; index++) {
|
|
string key = column[index];
|
|
if (key == "cldbid")
|
|
id = stoll(values[index]);
|
|
else if (key == "clientUid")
|
|
uid = values[index];
|
|
else if (key == "firstConnect")
|
|
created = values[index];
|
|
else if (key == "lastConnect")
|
|
lastConnected = values[index];
|
|
else if (key == "connections")
|
|
connections = values[index];
|
|
else if (key == "lastName")
|
|
name = values[index];
|
|
}
|
|
|
|
pArgs->result->operator[](pArgs->resultIndex)["cldbid"] = id;
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_unique_identifier"] = uid;
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_nickname"] = name;
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_created"] = created;
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_lastconnected"] = lastConnected;
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_totalconnections"] = connections;
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_description"] = "";
|
|
|
|
auto props = serverInstance->databaseHelper()->loadClientProperties(pArgs->server, id, ClientType::CLIENT_TEAMSPEAK);
|
|
if (props) {
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as<string>();
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as<string>();
|
|
|
|
if (pArgs->largeInfo) {
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_badges"] = (*props)[property::CLIENT_BADGES].as<string>();
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_version"] = (*props)[property::CLIENT_VERSION].as<string>();
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_platform"] = (*props)[property::CLIENT_PLATFORM].as<string>();
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as<string>();
|
|
}
|
|
}
|
|
if (!pArgs->showIp)
|
|
pArgs->result->operator[](pArgs->resultIndex)["client_lastip"] = "hidden";
|
|
pArgs->resultIndex++;
|
|
}
|
|
return 0;
|
|
}, &args));
|
|
|
|
if (args.resultIndex == 0) return command_result{error::database_empty_result};
|
|
if (cmd.hasParm("count")) {
|
|
size_t result = 0;
|
|
sql::command(this->server->getSql(), "SELECT COUNT(*) AS `count` FROM `clients` WHERE `serverId` = :sid", variable{":sid", this->server->getServerId()}).query([](size_t *ptr, int, char **v, char **) {
|
|
*ptr = static_cast<size_t>(stoll(v[0]));
|
|
return 0;
|
|
}, &result);
|
|
notify[0]["count"] = result;
|
|
}
|
|
this->sendCommand(notify);
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientDBEdit(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_modify_dbproperties, 1);
|
|
|
|
if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return command_result{error::database_empty_result, "invalid cldbid"};
|
|
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK);
|
|
|
|
for (auto &elm : cmd[0].keys()) {
|
|
if (elm == "cldbid") continue;
|
|
|
|
auto info = property::info<property::ClientProperties>(elm);
|
|
if(*info == property::CLIENT_UNDEFINED) {
|
|
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change someone's db entry, but the entry in unknown: " + elm);
|
|
continue;
|
|
}
|
|
if(!info->validate_input(cmd[elm].as<string>())) {
|
|
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[elm].as<string>() + "', Property: '" + info->name + "')");
|
|
continue;
|
|
}
|
|
(*props)[info] = cmd[elm].string();
|
|
}
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientEdit(ts::Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
|
|
ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
|
|
if (!client) return command_result{error::client_invalid_id};
|
|
return this->handleCommandClientEdit(cmd, client.client);
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std::shared_ptr<ConnectedClient>& client) {
|
|
assert(client);
|
|
auto self = client == this;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(self ? 15 : 25);
|
|
CMD_RESET_IDLE;
|
|
|
|
|
|
bool update_talk_rights = false;
|
|
unique_ptr<lock_guard<std::recursive_mutex>> nickname_lock;
|
|
deque<pair<property::ClientProperties, string>> keys;
|
|
for(const auto& key : cmd[0].keys()) {
|
|
if(key == "return_code") continue;
|
|
if(key == "clid") continue;
|
|
|
|
const auto &info = property::info<property::ClientProperties>(key);
|
|
if(*info == property::CLIENT_UNDEFINED) {
|
|
logError(this->getServerId(), R"([{}] Tried to change a not existing client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string());
|
|
continue;
|
|
}
|
|
|
|
if((info->flags & property::FLAG_USER_EDITABLE) == 0) {
|
|
logError(this->getServerId(), R"([{}] Tried to change a not user editable client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string());
|
|
continue;
|
|
}
|
|
|
|
if(!info->validate_input(cmd[key].as<string>())) {
|
|
logError(this->getServerId(), R"([{}] Tried to change a client property to an invalid value for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string());
|
|
continue;
|
|
}
|
|
if(client->properties()[info].as<string>() == cmd[key].as<string>()) continue;
|
|
|
|
if (*info == property::CLIENT_DESCRIPTION) {
|
|
if (self) {
|
|
ACTION_REQUIRES_PERMISSION(permission::b_client_modify_own_description, 1, client->getChannelId());
|
|
} else if(client->getType() == ClientType::CLIENT_MUSIC) {
|
|
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
|
}
|
|
} else {
|
|
ACTION_REQUIRES_PERMISSION(permission::b_client_modify_description, 1, client->getChannelId());
|
|
}
|
|
|
|
string value = cmd["client_description"].string();
|
|
if (count_characters(value) > 200) return command_result{error::parameter_invalid, "Invalid description length. A maximum of 200 characters is allowed!"};
|
|
} else if (*info == property::CLIENT_IS_TALKER) {
|
|
ACTION_REQUIRES_PERMISSION(permission::b_client_set_flag_talker, 1, client->getChannelId());
|
|
cmd["client_is_talker"] = cmd["client_is_talker"].as<bool>();
|
|
cmd["client_talk_request"] = 0;
|
|
update_talk_rights = true;
|
|
|
|
keys.emplace_back(property::CLIENT_IS_TALKER, "client_is_talker");
|
|
keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request");
|
|
continue;
|
|
} else if(*info == property::CLIENT_NICKNAME) {
|
|
if(!self) {
|
|
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
|
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_rename_power, client->calculate_permission(permission::i_client_music_needed_rename_power, client->getChannelId()), client->getChannelId());
|
|
}
|
|
}
|
|
|
|
string name = cmd["client_nickname"].string();
|
|
if (count_characters(name) < 3) return command_result{error::parameter_invalid, "Invalid name length. A minimum of 3 characters is required!"};
|
|
if (count_characters(name) > 30) return command_result{error::parameter_invalid, "Invalid name length. A maximum of 30 characters is allowed!"};
|
|
|
|
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, client->getClientId()))) {
|
|
auto banRecord = serverInstance->banManager()->findBanByName(this->getServerId(), name);
|
|
if (banRecord)
|
|
return command_result{error::client_nickname_inuse, string() + "This nickname is " + (banRecord->serverId == 0 ? "globally " : "") + "banned for the reason: " + banRecord->reason};
|
|
}
|
|
if (this->server) {
|
|
nickname_lock = std::make_unique<lock_guard<recursive_mutex>>(this->server->client_nickname_lock);
|
|
bool self = false;
|
|
for (const auto &cl : this->server->getClients()) {
|
|
if (cl->getDisplayName() == cmd["client_nickname"].string()) {
|
|
if(cl == this)
|
|
self = true;
|
|
else
|
|
return command_result{error::client_nickname_inuse, "This nickname is already in use"};
|
|
}
|
|
}
|
|
if(self) {
|
|
nickname_lock.reset();
|
|
continue;
|
|
}
|
|
}
|
|
} else if(*info == property::CLIENT_PLAYER_VOLUME) {
|
|
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
|
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
|
}
|
|
auto bot = dynamic_pointer_cast<MusicClient>(client);
|
|
assert(bot);
|
|
|
|
auto volume = cmd["player_volume"].as<float>();
|
|
|
|
auto max_volume = this->calculate_permission(permission::i_client_music_create_modify_max_volume, client->getClientId());
|
|
if(max_volume.has_value && !permission::v2::permission_granted(volume * 100, max_volume))
|
|
return command_result{permission::i_client_music_create_modify_max_volume};
|
|
|
|
bot->volume_modifier(cmd["player_volume"]);
|
|
} else if(*info == property::CLIENT_IS_CHANNEL_COMMANDER) {
|
|
if(!self) {
|
|
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
|
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
|
}
|
|
}
|
|
|
|
if(cmd["client_is_channel_commander"].as<bool>())
|
|
ACTION_REQUIRES_PERMISSION(permission::b_client_use_channel_commander, 1, client->getChannelId());
|
|
} else if(*info == property::CLIENT_IS_PRIORITY_SPEAKER) {
|
|
//FIXME allow other to remove this thing
|
|
if(!self) {
|
|
if(client->getType() != ClientType::CLIENT_MUSIC)
|
|
return command_result{error::client_invalid_type};
|
|
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId())
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getClientId()), client->getClientId());
|
|
}
|
|
|
|
if(cmd["client_is_priority_speaker"].as<bool>())
|
|
ACTION_REQUIRES_PERMISSION(permission::b_client_use_priority_speaker, 1, client->getChannelId());
|
|
} else if (self && key == "client_talk_request") {
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(20);
|
|
ACTION_REQUIRES_PERMISSION(permission::b_client_request_talker, 1, client->getChannelId());
|
|
|
|
if (cmd["client_talk_request"].as<bool>())
|
|
cmd["client_talk_request"] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
|
else
|
|
cmd["client_talk_request"] = 0;
|
|
keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request");
|
|
continue;
|
|
} else if (self && key == "client_badges") {
|
|
std::string str = cmd[key];
|
|
size_t index = 0;
|
|
int badgesTags = 0;
|
|
do {
|
|
index = str.find("badges", index);
|
|
if (index < str.length()) badgesTags++;
|
|
index++;
|
|
} while (index < str.length() && index != 0);
|
|
if (badgesTags >= 2) {
|
|
if (!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_allow_invalid_badges, client->getClientId())))
|
|
((VoiceClient *) this)->disconnect(VREASON_SERVER_KICK, config::messages::kick_invalid_badges, this->server ? this->server->serverAdmin : dynamic_pointer_cast<ConnectedClient>(serverInstance->getInitialServerAdmin()), true);
|
|
return command_result{error::parameter_invalid, "Invalid badges"};
|
|
}
|
|
//FIXME stuff here
|
|
} else if(!self && key == "client_version") {
|
|
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
|
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
|
}
|
|
} else if(!self && key == "client_platform") {
|
|
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
|
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
|
}
|
|
} else if(!self && key == "client_country") {
|
|
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
|
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
|
}
|
|
} else if(!self && (*info == property::CLIENT_FLAG_NOTIFY_SONG_CHANGE/* || *info == property::CLIENT_NOTIFY_SONG_MESSAGE*/)) {
|
|
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
|
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
|
}
|
|
} else if(!self && key == "client_uptime_mode") {
|
|
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
|
|
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
|
}
|
|
|
|
if(cmd[key].as<MusicClient::UptimeMode::value>() == MusicClient::UptimeMode::TIME_SINCE_SERVER_START) {
|
|
cmd["client_lastconnected"] = duration_cast<seconds>(this->server->startTimestamp.time_since_epoch()).count();
|
|
} else {
|
|
string value = client->properties()[property::CLIENT_CREATED];
|
|
if(value.empty())
|
|
value = "0";
|
|
cmd["client_lastconnected"] = value;
|
|
}
|
|
|
|
keys.emplace_back(property::CLIENT_LASTCONNECTED, "client_lastconnected");
|
|
} else if(!self && *info == property::CLIENT_BOT_TYPE) {
|
|
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
|
|
auto type = cmd["client_bot_type"].as<MusicClient::Type::value>();
|
|
if(type == MusicClient::Type::TEMPORARY) {
|
|
ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_temporary, 1, client->getChannelId());
|
|
} else if(type == MusicClient::Type::SEMI_PERMANENT) {
|
|
ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_semi_permanent, 1, client->getChannelId());
|
|
} else if(type == MusicClient::Type::PERMANENT) {
|
|
ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_permanent, 1, client->getChannelId());
|
|
} else
|
|
return command_result{error::parameter_invalid};
|
|
} else if(*info == property::CLIENT_AWAY_MESSAGE) {
|
|
if(!self) continue;
|
|
|
|
if(cmd["client_away_message"].string().length() > 256)
|
|
return command_result{error::parameter_invalid};
|
|
} else if(!self) { /* dont edit random properties of other clients. For us self its allowed to edit the rest without permissions */
|
|
continue;
|
|
}
|
|
|
|
keys.emplace_back((property::ClientProperties) info->property_index, key);
|
|
}
|
|
|
|
deque<property::ClientProperties> updates;
|
|
for(const auto& key : keys) {
|
|
if(key.first == property::CLIENT_IS_PRIORITY_SPEAKER) {
|
|
client->clientPermissions->set_permission(permission::b_client_is_priority_speaker, {1, 0}, cmd["client_is_priority_speaker"].as<bool>() ? permission::v2::PermissionUpdateType::set_value : permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing);
|
|
}
|
|
client->properties()[key.first] = cmd[0][key.second].value();
|
|
updates.push_back(key.first);
|
|
}
|
|
if(update_talk_rights)
|
|
client->updateTalkRights(client->properties()[property::CLIENT_TALK_POWER]);
|
|
|
|
if(this->server)
|
|
this->server->notifyClientPropertyUpdates(client, updates);
|
|
nickname_lock.reset();
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientUpdate(Command &cmd) {
|
|
return this->handleCommandClientEdit(cmd, _this.lock());
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientMute(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
|
|
ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
|
|
if (!client || client->getClientId() == this->getClientId()) return command_result{error::client_invalid_id};
|
|
|
|
{
|
|
unique_lock channel_lock(this->channel_lock);
|
|
for(const auto& weak : this->mutedClients)
|
|
if(weak.lock() == client) return command_result{error::ok};
|
|
this->mutedClients.push_back(client.client);
|
|
}
|
|
|
|
if (config::voice::notifyMuted)
|
|
client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), 0, system_clock::now(), config::messages::mute_notify_message);
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientUnmute(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
|
|
ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
|
|
if (!client || client->getClientId() == this->getClientId()) return command_result{error::client_invalid_id};
|
|
|
|
{
|
|
unique_lock channel_lock(this->channel_lock);
|
|
this->mutedClients.erase(std::remove_if(this->mutedClients.begin(), this->mutedClients.end(), [&](const weak_ptr<ConnectedClient>& weak) {
|
|
auto c = weak.lock();
|
|
return !c || c == client;
|
|
}), this->mutedClients.end());
|
|
}
|
|
|
|
if (config::voice::notifyMuted)
|
|
client->notifyTextMessage(ChatMessageMode::TEXTMODE_PRIVATE, _this.lock(), client->getClientId(), 0, system_clock::now(), config::messages::unmute_notify_message);
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientList(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
|
|
bool allow_ip = false;
|
|
if (cmd.hasParm("ip"))
|
|
allow_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
|
Command result("");
|
|
|
|
int index = 0;
|
|
this->server->forEachClient([&](shared_ptr<ConnectedClient> client) {
|
|
if (client->getType() == ClientType::CLIENT_INTERNAL) return;
|
|
|
|
result[index]["clid"] = client->getClientId();
|
|
if (client->getChannel())
|
|
result[index]["cid"] = client->getChannel()->channelId();
|
|
else result[index]["cid"] = 0;
|
|
result[index]["client_database_id"] = client->getClientDatabaseId();
|
|
result[index]["client_nickname"] = client->getDisplayName();
|
|
result[index]["client_type"] = client->getType();
|
|
|
|
if (cmd.hasParm("uid"))
|
|
result[index]["client_unique_identifier"] = client->getUid();
|
|
if (cmd.hasParm("away")) {
|
|
result[index]["client_away"] = client->properties()[property::CLIENT_AWAY].as<string>();
|
|
result[index]["client_away_message"] = client->properties()[property::CLIENT_AWAY_MESSAGE].as<string>();
|
|
}
|
|
if (cmd.hasParm("groups")) {
|
|
result[index]["client_channel_group_id"] = client->properties()[property::CLIENT_CHANNEL_GROUP_ID].as<string>();
|
|
result[index]["client_servergroups"] = client->properties()[property::CLIENT_SERVERGROUPS].as<string>();
|
|
result[index]["client_channel_group_inherited_channel_id"] = client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID].as<string>();
|
|
}
|
|
if (cmd.hasParm("times")) {
|
|
result[index]["client_idle_time"] = duration_cast<milliseconds>(system_clock::now() - client->idleTimestamp).count();
|
|
result[index]["client_total_online_time"] = client->properties()[property::CLIENT_TOTAL_ONLINE_TIME].as<int64_t>() + duration_cast<seconds>(system_clock::now() - client->lastOnlineTimestamp).count();
|
|
result[index]["client_month_online_time"] = client->properties()[property::CLIENT_MONTH_ONLINE_TIME].as<int64_t>() + duration_cast<seconds>(system_clock::now() - client->lastOnlineTimestamp).count();
|
|
result[index]["client_idle_time"] = duration_cast<milliseconds>(system_clock::now() - client->idleTimestamp).count();
|
|
result[index]["client_created"] = client->properties()[property::CLIENT_CREATED].as<string>();
|
|
result[index]["client_lastconnected"] = client->properties()[property::CLIENT_LASTCONNECTED].as<string>();
|
|
}
|
|
if (cmd.hasParm("info")) {
|
|
result[index]["client_version"] = client->properties()[property::CLIENT_VERSION].as<string>();
|
|
result[index]["client_platform"] = client->properties()[property::CLIENT_PLATFORM].as<string>();
|
|
}
|
|
|
|
if (cmd.hasParm("badges"))
|
|
result[index]["client_badges"] = client->properties()[property::CLIENT_BADGES].as<string>();
|
|
if (cmd.hasParm("country"))
|
|
result[index]["client_country"] = client->properties()[property::CLIENT_COUNTRY].as<string>();
|
|
if (cmd.hasParm("ip"))
|
|
result[index]["connection_client_ip"] = allow_ip ? client->properties()[property::CONNECTION_CLIENT_IP].as<string>() : "hidden";
|
|
if (cmd.hasParm("icon"))
|
|
result[index]["client_icon_id"] = client->properties()[property::CLIENT_ICON_ID].as<string>();
|
|
|
|
if (cmd.hasParm("voice")) {
|
|
result[index]["client_talk_power"] = client->properties()[property::CLIENT_TALK_POWER].as<string>();
|
|
result[index]["client_flag_talking"] = client->properties()[property::CLIENT_FLAG_TALKING].as<string>();
|
|
result[index]["client_input_muted"] = client->properties()[property::CLIENT_INPUT_MUTED].as<string>();
|
|
result[index]["client_output_muted"] = client->properties()[property::CLIENT_OUTPUT_MUTED].as<string>();
|
|
result[index]["client_input_hardware"] = client->properties()[property::CLIENT_INPUT_HARDWARE].as<string>();
|
|
result[index]["client_output_hardware"] = client->properties()[property::CLIENT_OUTPUT_HARDWARE].as<string>();
|
|
result[index]["client_is_talker"] = client->properties()[property::CLIENT_IS_TALKER].as<string>();
|
|
result[index]["client_is_priority_speaker"] = client->properties()[property::CLIENT_IS_PRIORITY_SPEAKER].as<string>();
|
|
result[index]["client_is_recording"] = client->properties()[property::CLIENT_IS_RECORDING].as<string>();
|
|
result[index]["client_is_channel_commander"] = client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as<string>();
|
|
|
|
}
|
|
index++;
|
|
});
|
|
this->sendCommand(result);
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientGetDBIDfromUID(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
|
|
deque<string> unique_ids;
|
|
for(int index = 0; index < cmd.bulkCount(); index++)
|
|
unique_ids.push_back(cmd[index]["cluid"].as<string>());
|
|
|
|
auto res = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->server, unique_ids);
|
|
if (res.empty()) return command_result{error::database_empty_result};
|
|
|
|
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientdbidfromuid" : "");
|
|
int result_index = 0;
|
|
for(auto& info : res) {
|
|
result[result_index]["cluid"] = info->uniqueId;
|
|
result[result_index]["cldbid"] = info->cldbid;
|
|
result_index++;
|
|
}
|
|
this->sendCommand(result);
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientGetNameFromDBID(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
|
|
deque<ClientDbId> dbids;
|
|
for(int index = 0; index < cmd.bulkCount(); index++)
|
|
dbids.push_back(cmd[index]["cldbid"].as<ClientDbId>());
|
|
|
|
auto res = serverInstance->databaseHelper()->queryDatabaseInfo(this->server, dbids);
|
|
if (res.empty()) return command_result{error::database_empty_result};
|
|
|
|
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetnamefromdbid" : "");
|
|
int result_index = 0;
|
|
for(auto& info : res) {
|
|
result[result_index]["cluid"] = info->uniqueId;
|
|
result[result_index]["cldbid"] = info->cldbid;
|
|
result[result_index]["name"] = info->lastName;
|
|
result[result_index]["clname"] = info->lastName;
|
|
result_index++;
|
|
}
|
|
this->sendCommand(result);
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientGetNameFromUid(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
|
|
deque<string> unique_ids;
|
|
for(int index = 0; index < cmd.bulkCount(); index++)
|
|
unique_ids.push_back(cmd[index]["cluid"].as<string>());
|
|
|
|
auto res = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->server, unique_ids);
|
|
if (res.empty()) return command_result{error::database_empty_result};
|
|
|
|
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientnamefromuid" : "");
|
|
int result_index = 0;
|
|
for(auto& info : res) {
|
|
result[result_index]["cluid"] = info->uniqueId;
|
|
result[result_index]["cldbid"] = info->cldbid;
|
|
result[result_index]["name"] = info->lastName;
|
|
result[result_index]["clname"] = info->lastName;
|
|
result_index++;
|
|
}
|
|
this->sendCommand(result);
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientGetUidFromClid(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
|
|
bool error = false;
|
|
bool found = false;
|
|
auto client_list = this->server->getClients();
|
|
|
|
Command notify(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientgetuidfromclid" : "");
|
|
int result_index = 0;
|
|
|
|
for(int index = 0; index < cmd.bulkCount(); index++) {
|
|
auto client_id = cmd[index]["clid"].as<ClientId>();
|
|
for(const auto& entry : client_list) {
|
|
if(entry->getClientId() == client_id) {
|
|
notify[result_index]["clname"] = entry->getDisplayName();
|
|
notify[result_index]["clid"] = entry->getClientId();
|
|
notify[result_index]["cluid"] = entry->getUid();
|
|
notify[result_index]["cldbid"] = entry->getClientDatabaseId();
|
|
result_index++;
|
|
found = true;
|
|
}
|
|
}
|
|
if(found) found = false;
|
|
else error = false;
|
|
}
|
|
|
|
if(result_index > 0)
|
|
this->sendCommand(notify);
|
|
if(error)
|
|
return command_result{error::database_empty_result};
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
|
|
|
auto cldbid = cmd["cldbid"].as<ClientDbId>();
|
|
if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid))
|
|
return command_result{error::client_invalid_id};
|
|
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
|
|
|
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0));
|
|
auto max_value = this->calculate_permission(permission::i_permission_modify_power, 0, true);
|
|
if(!max_value.has_value) return command_result{permission::i_permission_modify_power};
|
|
|
|
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
|
bool conOnError = cmd[0].has("continueonerror");
|
|
auto update_channels = false;
|
|
for (int index = 0; index < cmd.bulkCount(); index++) {
|
|
PARSE_PERMISSION(cmd);
|
|
|
|
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
|
|
if(permission_require_granted_value(permType) && !permission::v2::permission_granted(val, max_value)) {
|
|
if(conOnError) continue;
|
|
return command_result{permission::i_permission_modify_power};
|
|
}
|
|
|
|
if(!ignore_granted_values && !permission::v2::permission_granted(1, this->calculate_permission(permType, 0, true))) {
|
|
if(conOnError) continue;
|
|
return command_result{permission::i_permission_modify_power};
|
|
}
|
|
|
|
if (grant) {
|
|
mgr->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value);
|
|
} else {
|
|
mgr->set_permission(permType, {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0);
|
|
update_channels |= permission_is_client_property(permType);
|
|
}
|
|
}
|
|
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
|
|
auto onlineClients = this->server->findClientsByCldbId(cldbid);
|
|
if (!onlineClients.empty())
|
|
for (const auto &elm : onlineClients) {
|
|
if(elm->update_cached_permissions()) /* update cached calculated permissions */
|
|
elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
|
if(update_channels)
|
|
elm->updateChannelClientProperties(true, true);
|
|
elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
|
|
}
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
|
|
|
auto cldbid = cmd["cldbid"].as<ClientDbId>();
|
|
if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cldbid))
|
|
return command_result{error::client_invalid_id};
|
|
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
|
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_permission_modify_power, this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cldbid, ClientType::CLIENT_TEAMSPEAK, 0));
|
|
|
|
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
|
|
bool conOnError = cmd[0].has("continueonerror");
|
|
auto onlineClients = this->server->findClientsByCldbId(cmd["cldbid"]);
|
|
auto update_channel = false;
|
|
for (int index = 0; index < cmd.bulkCount(); index++) {
|
|
PARSE_PERMISSION(cmd)
|
|
|
|
if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, 0, true))) {
|
|
if(conOnError) continue;
|
|
return command_result{permission::i_permission_modify_power};
|
|
}
|
|
|
|
|
|
if (grant) {
|
|
mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value);
|
|
} else {
|
|
mgr->set_permission(permType, permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing);
|
|
update_channel |= permission_is_client_property(permType);
|
|
}
|
|
}
|
|
|
|
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
|
|
if (!onlineClients.empty())
|
|
for (const auto &elm : onlineClients) {
|
|
if(elm->update_cached_permissions()) /* update cached calculated permissions */
|
|
elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
|
|
if(update_channel)
|
|
elm->updateChannelClientProperties(true, true);
|
|
elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
|
|
}
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientPermList(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_permission_list, 1);
|
|
|
|
if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return command_result{error::client_invalid_id};
|
|
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"]);
|
|
if (!this->notifyClientPermList(cmd["cldbid"], mgr, cmd.hasParm("permsid"))) return command_result{error::database_empty_result};
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientDbInfo(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dbinfo, 1);
|
|
|
|
deque<ClientDbId> cldbids;
|
|
for(int index = 0; index < cmd.bulkCount(); index++)
|
|
cldbids.push_back(cmd[index]["cldbid"]);
|
|
|
|
auto basic = serverInstance->databaseHelper()->queryDatabaseInfo(this->server, cldbids);
|
|
if (basic.empty()) return command_result{error::database_empty_result};
|
|
|
|
auto allow_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
|
Command res(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyclientdbinfo" : "");
|
|
|
|
size_t index = 0;
|
|
for(const auto& info : basic) {
|
|
res[index]["client_base64HashClientUID"] = hex::hex(base64::validate(info->uniqueId) ? base64::decode(info->uniqueId) : info->uniqueId, 'a', 'q');
|
|
res[index]["client_unique_identifier"] = info->uniqueId;
|
|
res[index]["client_nickname"] = info->lastName;
|
|
res[index]["client_database_id"] = info->cldbid;
|
|
res[index]["client_created"] = chrono::duration_cast<chrono::seconds>(info->created.time_since_epoch()).count();
|
|
res[index]["client_lastconnected"] = chrono::duration_cast<chrono::seconds>(info->lastjoin.time_since_epoch()).count();
|
|
res[index]["client_totalconnections"] = info->connections;
|
|
res[index]["client_database_id"] = info->cldbid;
|
|
|
|
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, info->cldbid, ClientType::CLIENT_TEAMSPEAK);
|
|
if (allow_ip)
|
|
res[index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as<string>();
|
|
else
|
|
res[index]["client_lastip"] = "hidden";
|
|
|
|
res[index]["client_icon_id"] = (*props)[property::CLIENT_ICON_ID].as<string>();
|
|
res[index]["client_badges"] = (*props)[property::CLIENT_BADGES].as<string>();
|
|
res[index]["client_version"] = (*props)[property::CLIENT_VERSION].as<string>();
|
|
res[index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as<string>();
|
|
res[index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as<string>();
|
|
res[index]["client_total_bytes_downloaded"] = (*props)[property::CLIENT_TOTAL_BYTES_DOWNLOADED].as<string>();
|
|
res[index]["client_total_bytes_uploaded"] = (*props)[property::CLIENT_TOTAL_BYTES_UPLOADED].as<string>();
|
|
res[index]["client_month_bytes_downloaded"] = (*props)[property::CLIENT_MONTH_BYTES_DOWNLOADED].as<string>();
|
|
res[index]["client_month_bytes_uploaded"] = (*props)[property::CLIENT_MONTH_BYTES_DOWNLOADED].as<string>();
|
|
res[index]["client_description"] = (*props)[property::CLIENT_DESCRIPTION].as<string>();
|
|
res[index]["client_flag_avatar"] = (*props)[property::CLIENT_FLAG_AVATAR].as<string>();
|
|
|
|
res[index]["client_month_online_time"] = (*props)[property::CLIENT_MONTH_ONLINE_TIME].as<string>();
|
|
res[index]["client_total_online_time"] = (*props)[property::CLIENT_TOTAL_ONLINE_TIME].as<string>();
|
|
index++;
|
|
}
|
|
|
|
this->sendCommand(res);
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientDBDelete(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_delete_dbproperties, 1);
|
|
|
|
ClientDbId id = cmd["cldbid"];
|
|
if (!serverInstance->databaseHelper()->validClientDatabaseId(this->server, id)) return command_result{error::database_empty_result};
|
|
serverInstance->databaseHelper()->deleteClient(this->server, id);
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
struct DBFindArgs {
|
|
int index = 0;
|
|
bool full = false;
|
|
bool ip = false;
|
|
Command cmd{""};
|
|
};
|
|
|
|
command_result ConnectedClient::handleCommandClientDBFind(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_client_dbsearch, 1);
|
|
|
|
bool uid = cmd.hasParm("uid");
|
|
string pattern = cmd["pattern"];
|
|
|
|
DBFindArgs args{};
|
|
args.cmd = Command(this->getType() == CLIENT_QUERY ? "" : "notifyclientdbfind");
|
|
args.full = cmd.hasParm("details");
|
|
args.ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
|
auto res = sql::command(this->sql, "SELECT * FROM `clients` WHERE `serverId` = :sid AND `" + std::string{uid ? "clientUid" : "lastName"} + "` LIKE :pattern LIMIT 50",
|
|
variable{":sid", this->server->getServerId()},
|
|
variable{":pattern", pattern}).query(
|
|
[&](DBFindArgs *ptr, int len, char **values, char **names) {
|
|
for (int index = 0; index < len; index++)
|
|
if (strcmp(names[index], "cldbid") == 0)
|
|
ptr->cmd[ptr->index]["cldbid"] = values[index];
|
|
else if (strcmp(names[index], "clientUid") == 0 && ptr->full)
|
|
ptr->cmd[ptr->index]["client_unique_identifier"] = values[index];
|
|
else if (strcmp(names[index], "lastConnect") == 0 && ptr->full)
|
|
ptr->cmd[ptr->index]["client_lastconnected"] = values[index];
|
|
else if (strcmp(names[index], "connections") == 0 && ptr->full)
|
|
ptr->cmd[ptr->index]["client_totalconnections"] = values[index];
|
|
else if (strcmp(names[index], "lastName") == 0 && ptr->full)
|
|
ptr->cmd[ptr->index]["client_nickname"] = values[index];
|
|
if (ptr->full) {
|
|
auto props = serverInstance->databaseHelper()->loadClientProperties(this->server, ptr->cmd[ptr->index]["cldbid"], ClientType::CLIENT_TEAMSPEAK);
|
|
if (props) {
|
|
if (ptr->ip) {
|
|
ptr->cmd[ptr->index]["client_lastip"] = (*props)[property::CONNECTION_CLIENT_IP].as<string>();
|
|
} else {
|
|
ptr->cmd[ptr->index]["client_lastip"] = "hidden";
|
|
}
|
|
ptr->cmd[ptr->index]["client_badges"] = (*props)[property::CLIENT_BADGES].as<string>();
|
|
ptr->cmd[ptr->index]["client_version"] = (*props)[property::CLIENT_VERSION].as<string>();
|
|
ptr->cmd[ptr->index]["client_platform"] = (*props)[property::CLIENT_PLATFORM].as<string>();
|
|
ptr->cmd[ptr->index]["client_hwid"] = (*props)[property::CLIENT_HARDWARE_ID].as<string>();
|
|
}
|
|
}
|
|
ptr->index++;
|
|
return 0;
|
|
}, &args);
|
|
auto pf = LOG_SQL_CMD;
|
|
pf(res);
|
|
if (args.index == 0) return command_result{error::database_empty_result};
|
|
|
|
this->sendCommand(args.cmd);
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientInfo(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_RESET_IDLE;
|
|
|
|
Command res(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifyclientinfo" : "");
|
|
bool trigger_error = false;
|
|
bool view_remote = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, 0));
|
|
|
|
int result_index = 0;
|
|
for(int index = 0; index < cmd.bulkCount(); index++) {
|
|
auto client_id = cmd[index]["clid"].as<ClientId>();
|
|
if(client_id == 0) continue;
|
|
|
|
ConnectedLockedClient client{this->server->find_client_by_id(client_id)};
|
|
if(!client) {
|
|
trigger_error = true;
|
|
continue;
|
|
}
|
|
|
|
for (const auto &key : client->properties()->list_properties(property::FLAG_CLIENT_VIEW | property::FLAG_CLIENT_VARIABLE | property::FLAG_CLIENT_INFO, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
|
|
res[result_index][key.type().name] = key.value();
|
|
if(view_remote)
|
|
res[result_index]["connection_client_ip"] = client->properties()[property::CONNECTION_CLIENT_IP].as<string>();
|
|
else
|
|
res[result_index]["connection_client_ip"] = "hidden";
|
|
res[result_index]["client_idle_time"] = duration_cast<milliseconds>(system_clock::now() - client->idleTimestamp).count();
|
|
res[result_index]["connection_connected_time"] = duration_cast<milliseconds>(system_clock::now() - client->connectTimestamp).count();
|
|
{
|
|
auto channel = client->currentChannel;
|
|
if(channel)
|
|
res[result_index]["cid"] = channel->channelId();
|
|
else
|
|
res[result_index]["cid"] = 0;
|
|
}
|
|
|
|
result_index++;
|
|
}
|
|
|
|
|
|
if(result_index > 0) {
|
|
this->sendCommand(res);
|
|
}
|
|
|
|
if(trigger_error || result_index == 0)
|
|
return command_result{error::client_invalid_id};
|
|
else
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientFind(Command &cmd) {
|
|
CMD_REQ_SERVER;
|
|
CMD_CHK_AND_INC_FLOOD_POINTS(5);
|
|
|
|
string pattern = cmd["pattern"];
|
|
std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower);
|
|
|
|
Command res("");
|
|
int index = 0;
|
|
for (const auto &cl : this->server->getClients()) {
|
|
string name = cl->getDisplayName();
|
|
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
|
if (name.find(pattern) != std::string::npos) {
|
|
res[index]["clid"] = cl->getClientId();
|
|
res[index]["client_nickname"] = cl->getDisplayName();
|
|
index++;
|
|
}
|
|
}
|
|
if (index == 0) return command_result{error::database_empty_result};
|
|
this->sendCommand(res);
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
command_result ConnectedClient::handleCommandClientSetServerQueryLogin(Command &cmd) {
|
|
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_create_modify_serverquery_login, 1);
|
|
|
|
if(!cmd[0].has("client_login_password")) cmd["client_login_password"] = "";
|
|
|
|
std::string password = cmd["client_login_password"];
|
|
if(password.empty())
|
|
password = rnd_string(QUERY_PASSWORD_LENGTH);
|
|
|
|
auto old = serverInstance->getQueryServer()->find_query_account_by_name(cmd["client_login_name"]);
|
|
if (old) {
|
|
if(old->unique_id == this->getUid()) {
|
|
serverInstance->getQueryServer()->change_query_password(old, password);
|
|
} else {
|
|
return command_result{error::client_not_logged_in};
|
|
}
|
|
} else {
|
|
serverInstance->getQueryServer()->create_query_account(cmd["client_login_name"], this->getServerId(), this->getUid(), password);
|
|
}
|
|
|
|
Command res(this->notify_response_command("notifyclientserverqueryloginpassword"));
|
|
res["client_login_password"] = password;
|
|
this->sendCommand(res);
|
|
|
|
return command_result{error::ok};
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|