Using new permissions system consequently

This commit is contained in:
WolverinDEV
2020-01-26 14:21:34 +01:00
parent bb2e7699dc
commit c7b6c0a3ba
37 changed files with 8982 additions and 657 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+486
View File
@@ -0,0 +1,486 @@
//
// Created by wolverindev on 26.01.20.
//
#include <memory>
#include <spdlog/sinks/rotating_file_sink.h>
#include <iostream>
#include <bitset>
#include <algorithm>
#include <openssl/sha.h>
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
#include "../music/MusicClient.h"
#include "../query/QueryClient.h"
#include "../../weblist/WebListManager.h"
#include "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include <experimental/filesystem>
#include <cstdint>
#include <StringVariable.h>
#include "helpers.h"
#include <Properties.h>
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <misc/base64.h>
#include <misc/hex.h>
#include <misc/digest.h>
#include <misc/rnd.h>
#include <misc/timer.h>
#include <misc/strobf.h>
#include <misc/scope_guard.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
//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!"}
command_result ConnectedClient::handleCommandFTGetFileList(Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_REQ_FSERVER;
std::string code = cmd["return_code"].size() > 0 ? cmd["return_code"].string() : "";
Command fileList(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyfilelist" : "");
Command fileListFinished("notifyfilelistfinished");
fileList["path"] = cmd["path"].as<std::string>();
if(!code.empty()) fileList["return_code"] = code;
fileListFinished["path"] = cmd["path"].as<std::string>();
fileList["cid"] = cmd["cid"].as<size_t>();
fileListFinished["cid"] = cmd["cid"].as<ChannelId>();
if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"};
if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId())))
return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password};
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_browse_power, permission::i_ft_file_browse_power, true);
cmd_filelist_append_files(
this->getServerId(),
fileList,
serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->resolveDirectory(this->server, channel, cmd["path"].as<std::string>()))
);
} else {
if (cmd["path"].as<string>() == "/icons" || cmd["path"].as<string>() == "/icons/") {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_icon_manage, 1);
cmd_filelist_append_files(this->getServerId(), fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->iconDirectory(this->server)));
} else if (cmd["path"].as<string>() == "/") {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_icon_manage, 1);
cmd_filelist_append_files(this->getServerId(), fileList, serverInstance->getFileServer()->listFiles(serverInstance->getFileServer()->avatarDirectory(this->server)));
} else {
logMessage(this->getServerId(), "{} Requested file list for unknown path/name: path: {} name: {}", cmd["path"].string(), cmd["name"].string());
return command_result{error::not_implemented};
}
}
if (fileList[0].has("name")) {
if(dynamic_cast<VoiceClient*>(this)) {
dynamic_cast<VoiceClient*>(this)->sendCommand0(fileList.build(), false, true); /* We need to process this directly else the order could get shuffled up! */
this->sendCommand(fileListFinished);
} else {
this->sendCommand(fileList);
if(this->getType() != CLIENT_QUERY)
this->sendCommand(fileListFinished);
}
return command_result{error::ok};
} else {
return command_result{error::database_empty_result};
}
}
//ftcreatedir cid=4 cpw dirname=\/TestDir return_code=1:17
command_result ConnectedClient::handleCommandFTCreateDir(Command &cmd) {
CMD_REQ_SERVER;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
CMD_REQ_FSERVER;
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"};
if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId())))
return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password};
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_directory_create_power, permission::i_ft_directory_create_power, true);
auto dir = serverInstance->getFileServer()->createDirectory(cmd["dirname"], serverInstance->getFileServer()->resolveDirectory(this->server, channel, cmd["path"].as<std::string>()));
if (!dir) return command_result{error::file_invalid_path, "could not create dir"};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandFTDeleteFile(Command &cmd) {
CMD_REQ_SERVER;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
CMD_REQ_FSERVER;
std::vector<std::shared_ptr<file::FileEntry>> files;
if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"};
if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId())))
return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password};
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_delete_power, permission::i_ft_file_delete_power, true);
for (int index = 0; index < cmd.bulkCount(); index++)
files.push_back(serverInstance->getFileServer()->findFile(cmd[index]["name"].as<std::string>(), serverInstance->getFileServer()->resolveDirectory(this->server, channel)));
} else {
if (cmd["name"].string().find("/icon_") == 0 && cmd["path"].string().empty()) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_icon_manage, 1);
files.push_back(serverInstance->getFileServer()->findFile(cmd["name"].string(), serverInstance->getFileServer()->iconDirectory(this->server)));
} else if (cmd["name"].string().find("/avatar_") == 0 && cmd["path"].string().empty()) {
if (cmd["name"].string() != "/avatar_") {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_client_avatar_delete_other, 1);
auto uid = cmd["name"].string().substr(strlen("/avatar_"));
auto avId = hex::hex(base64::decode(uid), 'a', 'q');
auto cls = this->server->findClientsByUid(uid);
for (const auto &cl : cls) {
cl->properties()[property::CLIENT_FLAG_AVATAR] = "";
this->server->notifyClientPropertyUpdates(cl, deque<property::ClientProperties>{property::CLIENT_FLAG_AVATAR});
}
cmd["name"] = "/avatar_" + avId;
} else {
cmd["name"] = "/avatar_" + this->getAvatarId();
this->properties()[property::CLIENT_FLAG_AVATAR] = "";
this->server->notifyClientPropertyUpdates(_this.lock(), deque<property::ClientProperties>{property::CLIENT_FLAG_AVATAR});
}
files.push_back(serverInstance->getFileServer()->findFile(cmd["name"].string(), serverInstance->getFileServer()->avatarDirectory(this->server)));
} else {
logError(this->getServerId(), "Unknown requested file to delete: {}", cmd["path"].as<std::string>());
return command_result{error::not_implemented};
}
}
for (const auto &file : files) {
if (!file) continue;
if (!serverInstance->getFileServer()->deleteFile(file)) {
logCritical(this->getServerId(), "Could not delete file {}/{}", file->path, file->name);
}
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandFTInitUpload(Command &cmd) {
CMD_REQ_SERVER;
CMD_REQ_FSERVER;
std::shared_ptr<file::Directory> directory = nullptr;
if (cmd[0].has("cid") && cmd["cid"] != 0) { //Channel
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"};
if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId())))
return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password};
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_upload_power, permission::i_ft_file_upload_power, true);
directory = serverInstance->getFileServer()->resolveDirectory(this->server, channel);
} else {
if (cmd["path"].as<std::string>().empty() && cmd["name"].as<std::string>().find("/icon_") == 0) {
auto max_size = this->calculate_permission(permission::i_max_icon_filesize, 0);
if(max_size.has_value && !max_size.has_infinite_power() && (max_size.value < 0 || max_size.value < cmd["size"].as<size_t>()))
return command_result{permission::i_max_icon_filesize};
directory = serverInstance->getFileServer()->iconDirectory(this->server);
} else if (cmd["path"].as<std::string>().empty() && cmd["name"].string() == "/avatar") {
auto max_size = this->calculate_permission(permission::i_client_max_avatar_filesize, 0);
if(max_size.has_value && !max_size.has_infinite_power() && (max_size.value < 0 || max_size.value < cmd["size"].as<size_t>()))
return command_result{permission::i_client_max_avatar_filesize};
directory = serverInstance->getFileServer()->avatarDirectory(this->server);
cmd["name"] = "/avatar_" + this->getAvatarId();
} else {
cerr << "Unknown requested directory: " << cmd["path"].as<std::string>() << endl;
return command_result{error::not_implemented};
}
}
if (!directory || directory->type != file::FileType::DIRECTORY) { //Should not happen
cerr << "Invalid upload file path!" << endl;
return command_result{error::file_invalid_path, "could not resolve directory"};
}
{
auto server_quota = this->server->properties()[property::VIRTUALSERVER_UPLOAD_QUOTA].as<ssize_t>();
auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as<size_t>();
server_used_quota += cmd["size"].as<uint64_t>();
for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers())
server_used_quota += trans->size;
for(const auto& trans : serverInstance->getFileServer()->running_file_transfers())
server_used_quota += trans->remaining_bytes();
if(server_quota >= 0 && server_quota * 1024 * 1024 < (int64_t) server_used_quota) return command_result{error::file_transfer_server_quota_exceeded};
auto client_quota = this->calculate_permission(permission::i_ft_quota_mb_upload_per_client, 0);
auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].as<size_t>();
client_used_quota += cmd["size"].as<uint64_t>();
for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers(_this.lock()))
client_used_quota += trans->size;
for(const auto& trans : serverInstance->getFileServer()->running_file_transfers(_this.lock()))
client_used_quota += trans->remaining_bytes();
if(client_quota.has_value && !client_quota.has_infinite_power() && (client_quota.value < 0 || client_quota.value * 1024 * 1024 < (int64_t) client_used_quota))
return command_result{error::file_transfer_client_quota_exceeded};
}
if (cmd["overwrite"].as<bool>() && cmd["resume"].as<bool>()) return command_result{error::file_overwrite_excludes_resume};
if (serverInstance->getFileServer()->findFile(cmd["name"].as<std::string>(), directory) && !(cmd["overwrite"].as<bool>() || cmd["resume"].as<bool>()))
return command_result{error::file_already_exists, "file already exists"};
//Request: clientftfid=1 serverftfid=6 ftkey=itRNdsIOvcBiBg\/Xj4Ge51ZSrsShHuid port=30033 seekpos=0
//Reqpose: notifystartupload clientftfid=4079 serverftfid=1 ftkey=aX9CFQbfaddHpOYxhQiSLu\/BumfVtPyP port=30033 seekpos=0 proto=1
string error = "success";
auto key = serverInstance->getFileServer()->generateUploadTransferKey(error, directory->path + "/" + directory->name + cmd["name"].string(), cmd["size"].as<uint64_t>(), 0, _this.lock()); //TODO implement resume!
if (!key) return command_result{error::vs_critical};
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifystartupload" : "");
result["clientftfid"] = cmd["clientftfid"].as<uint64_t>();
result["ftkey"] = key->key;
auto bindings = serverInstance->getFileServer()->list_bindings();
if(!bindings.empty()) {
result["port"] = net::port(bindings[0]->address);
string ip = "";
for(auto& entry : bindings) {
if(net::is_anybind(entry->address)) {
ip = "";
break;
}
ip += net::to_string(entry->address, false) + ",";
}
if(!ip.empty())
result["ip"] = ip;
} else {
return command_result{error::server_is_not_running, "file server is not bound to any address"};
}
result["seekpos"] = 0;
result["proto"] = 1;
result["serverftfid"] = key->key_id; //TODO generate!
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandFTInitDownload(Command &cmd) {
CMD_REQ_SERVER;
CMD_REQ_FSERVER;
std::shared_ptr<file::Directory> directory = nullptr;
if(!cmd[0].has("path")) cmd["path"] = "";
if (cmd[0].has("cid") && cmd["cid"] != (ChannelId) 0) { //Channel
auto channel = this->server->channelTree->findChannel(cmd["cid"].as<ChannelId>());
if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"};
if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId())))
return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password};
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_download_power, permission::i_ft_file_download_power, true);
directory = serverInstance->getFileServer()->resolveDirectory(this->server, channel);
} else {
if (cmd["path"].as<std::string>().empty() && cmd["name"].as<std::string>().find("/icon_") == 0) {
directory = serverInstance->getFileServer()->iconDirectory(this->server);
} else if (cmd["path"].as<std::string>().empty() && cmd["name"].as<std::string>().find("/avatar_") == 0) { //TODO fix avatar download not working
directory = serverInstance->getFileServer()->avatarDirectory(this->server);
} else {
cerr << "Unknown requested directory: " << cmd["path"].as<std::string>() << endl;
return command_result{error::not_implemented};
}
}
if (!directory || directory->type != file::FileType::DIRECTORY) { //Should not happen
cerr << "Invalid download file path!" << endl;
return command_result{error::file_invalid_path, "could not resolve directory"};
}
if (!serverInstance->getFileServer()->findFile(cmd["name"].as<std::string>(), directory))
return command_result{error::file_not_found, "file not exists"};
string error = "success";
auto key = serverInstance->getFileServer()->generateDownloadTransferKey(error, directory->path + "/" + directory->name + cmd["name"].as<std::string>(), 0, _this.lock()); //TODO implement resume!
if (!key) return command_result{error::vs_critical, "cant generate key (" + error + ")"};
{
auto server_quota = this->server->properties()[property::VIRTUALSERVER_DOWNLOAD_QUOTA].as<ssize_t>();
auto server_used_quota = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as<size_t>();
server_used_quota += key->size;
for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers())
server_used_quota += trans->size;
for(const auto& trans : serverInstance->getFileServer()->running_file_transfers())
server_used_quota += trans->remaining_bytes();
if(server_quota >= 0 && server_quota * 1024 * 1024 < (int64_t) server_used_quota) return command_result{error::file_transfer_server_quota_exceeded};
auto client_quota = this->calculate_permission(permission::i_ft_quota_mb_download_per_client, 0);
auto client_used_quota = this->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].as<size_t>();
client_used_quota += key->size;
for(const auto& trans : serverInstance->getFileServer()->pending_file_transfers(_this.lock()))
client_used_quota += trans->size;
for(const auto& trans : serverInstance->getFileServer()->running_file_transfers(_this.lock()))
client_used_quota += trans->remaining_bytes();
if(client_quota.has_value && !client_quota.has_infinite_power() && (client_quota.value < 0 || client_quota.value * 1024 * 1024 < (int64_t) client_used_quota))
return command_result{error::file_transfer_client_quota_exceeded};
}
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifystartdownload" : "");
result["clientftfid"] = cmd["clientftfid"].as<uint64_t>();
result["proto"] = 1;
result["serverftfid"] = key->key_id;
result["ftkey"] = key->key;
auto bindings = serverInstance->getFileServer()->list_bindings();
if(!bindings.empty()) {
result["port"] = net::port(bindings[0]->address);
string ip = "";
for(auto& entry : bindings) {
if(net::is_anybind(entry->address)) {
ip = "";
break;
}
ip += net::to_string(entry->address, false) + ",";
}
if(!ip.empty())
result["ip"] = ip;
} else {
return command_result{error::server_is_not_running, "file server is not bound to any address"};
}
result["size"] = key->size;
this->sendCommand(result);
return command_result{error::ok};
}
/*
* Usage: ftgetfileinfo cid={channelID} cpw={channelPassword} name={filePath}...
Permissions:
i_ft_file_browse_power
i_ft_needed_file_browse_power
Description:
Displays detailed information about one or more specified files stored in a
channels file repository.
Example:
ftgetfileinfo cid=2 cpw= path=\/Pic1.PNG|cid=2 cpw= path=\/Pic2.PNG
cid=2 path=\/ name=Stuff size=0 datetime=1259415210 type=0|name=Pic1.PNG size=563783 datetime=1259425462 type=1|name=Pic2.PNG ...
error id=0 msg=ok
*/
command_result ConnectedClient::handleCommandFTGetFileInfo(ts::Command &cmd) {
CMD_REQ_SERVER;
CMD_REQ_FSERVER;
CMD_RESET_IDLE;
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyfileinfo" : "");
int result_index = 0;
for(int index = 0; index < cmd.bulkCount(); index++) {
auto& request = cmd[index];
std::shared_ptr<file::FileEntry> file;
if (request.has("cid") && request["cid"].as<ChannelId>() != 0) { //Channel
auto channel = this->server->channelTree->findChannel(request["cid"].as<ChannelId>());
if (!channel) return command_result{error::channel_invalid_id, "Cant resolve channel"};
if (!channel->passwordMatch(cmd["cpw"]) && !permission::v2::permission_granted(1, this->calculate_permission(permission::b_ft_ignore_password, channel->channelId())))
return cmd["cpw"].string().empty() ? command_result{permission::b_ft_ignore_password} : command_result{error::channel_invalid_password};
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_ft_needed_file_browse_power, permission::i_ft_file_browse_power, true);
file = serverInstance->getFileServer()->findFile(request["name"],serverInstance->getFileServer()->resolveDirectory(this->server, channel, request["path"]));
} else {
std::shared_ptr<file::Directory> directory;
if (!request.has("path") || request["path"].as<string>() == "/") {
directory = serverInstance->getFileServer()->avatarDirectory(this->server);
} else if (request["path"].as<string>() == "/icons" || request["path"].as<string>() == "/icons/") {
directory = serverInstance->getFileServer()->iconDirectory(this->server);
} else {
cerr << "Unknown requested directory: '" << request["path"].as<std::string>() << "'" << endl;
return command_result{error::not_implemented};
}
if(!directory) continue;
file = serverInstance->getFileServer()->findFile(cmd["name"].as<std::string>(), directory);
}
if(!file) continue;
result[result_index]["cid"] = request["cid"].as<ChannelId>();
result[result_index]["name"] = request["name"];
result[result_index]["path"] = request["path"];
result[result_index]["type"] = file->type;
result[result_index]["datetime"] = duration_cast<seconds>(file->lastChanged.time_since_epoch()).count();
if (file->type == file::FileType::FILE)
result[result_index]["size"] = static_pointer_cast<file::File>(file)->fileSize;
else
result[result_index]["size"] = 0;
result_index++;
}
if(result_index == 0)
return command_result{error::database_empty_result};
this->sendCommand(result);
return command_result{error::ok};
}
+193
View File
@@ -0,0 +1,193 @@
#pragma once
/** Permission tests against required values **/
/* use this for any action which will do something with the server */
#define ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(required_permission_type, required_value, cache) \
do { \
if(!permission::v2::permission_granted(required_value, this->calculate_permission(required_permission_type, 0, false, cache))) \
return command_result{required_permission_type}; \
} while(0)
#define ACTION_REQUIRES_GLOBAL_PERMISSION(required_permission_type, required_value) \
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(required_permission_type, required_value, nullptr)
//TODO: Fixme: Really check for instance permissions!
#define ACTION_REQUIRES_INSTANCE_PERMISSION(required_permission_type, required_value) \
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(required_permission_type, required_value, nullptr)
/* use this for anything which will do something local in relation to the target channel */
#define ACTION_REQUIRES_PERMISSION(required_permission_type, required_value, channel_id) \
do { \
if(!permission::v2::permission_granted(required_value, this->calculate_permission(required_permission_type, channel_id))) \
return command_result{required_permission_type}; \
} while(0)
/** Permission tests against groups **/
/* use this when testing a permission against a group */
#define ACTION_REQUIRES_GROUP_PERMISSION(group, required_permission_type, own_permission_type, is_required) \
do { \
auto _permission_granted = this->calculate_permission(own_permission_type, 0); \
if(!(group)->permission_granted(required_permission_type, _permission_granted, is_required)) \
return command_result{own_permission_type}; \
} while(0)
/** Permission tests against channels **/
/* use this when testing a permission against a group */
#define ACTION_REQUIRES_CHANNEL_PERMISSION(channel, required_permission_type, own_permission_type, is_required) \
do { \
auto _permission_granted = this->calculate_permission(own_permission_type, channel ? channel->channelId() : 0); \
if(!(channel)->permission_granted(required_permission_type, _permission_granted, is_required)) \
return command_result{own_permission_type}; \
} while(0)
/* Helper methods for channel resolve */
#define RESOLVE_CHANNEL_R(command, force) \
auto channel_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get();\
shared_lock channel_tree_read_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock());\
auto channel_id = command.as<ChannelId>(); \
auto l_channel = channel_id ? channel_tree->findLinkedChannel(command.as<ChannelId>()) : nullptr; \
if (!l_channel && (channel_id != 0 || force)) return command_result{error::channel_invalid_id, "Cant resolve channel"}; \
#define RESOLVE_CHANNEL_W(command, force) \
auto channel_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get();\
unique_lock channel_tree_write_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock());\
auto channel_id = command.as<ChannelId>(); \
auto l_channel = channel_id ? channel_tree->findLinkedChannel(command.as<ChannelId>()) : nullptr; \
if (!l_channel && (channel_id != 0 || force)) return command_result{error::channel_invalid_id, "Cant resolve channel"}; \
//TODO: Map permsid!
#define PARSE_PERMISSION(cmd) \
permission::PermissionType permType = permission::PermissionType::unknown; \
bool grant = false; \
if (cmd[index].has("permid")) { \
permType = cmd[index]["permid"].as<permission::PermissionType>(); \
if ((permType & PERM_ID_GRANT) != 0) { \
grant = true; \
permType &= ~PERM_ID_GRANT; \
} \
} else if (cmd[index].has("permsid")) { \
auto resv = permission::resolvePermissionData(cmd[index]["permsid"].as<string>()); \
permType = resv->type; \
if (resv->grantName() == cmd[index]["permsid"].as<string>()) grant = true; \
} \
if (permission::resolvePermissionData(permType)->type == permission::PermissionType::unknown) { \
if (conOnError) continue; \
return ts::command_result{error::parameter_invalid}; \
}
inline bool permission_require_granted_value(ts::permission::PermissionType type) {
using namespace ts;
switch (type) {
case permission::i_permission_modify_power:
case permission::i_channel_group_member_add_power:
case permission::i_channel_group_member_remove_power:
case permission::i_channel_group_modify_power:
case permission::i_channel_group_needed_member_add_power:
case permission::i_channel_group_needed_member_remove_power:
case permission::i_channel_group_needed_modify_power:
case permission::i_server_group_member_add_power:
case permission::i_server_group_member_remove_power:
case permission::i_server_group_modify_power:
case permission::i_server_group_needed_member_add_power:
case permission::i_server_group_needed_member_remove_power:
case permission::i_server_group_needed_modify_power:
case permission::i_displayed_group_member_add_power:
case permission::i_displayed_group_member_remove_power:
case permission::i_displayed_group_modify_power:
case permission::i_displayed_group_needed_member_add_power:
case permission::i_displayed_group_needed_member_remove_power:
case permission::i_displayed_group_needed_modify_power:
case permission::i_channel_permission_modify_power:
case permission::i_channel_needed_permission_modify_power:
case permission::i_client_permission_modify_power:
case permission::i_client_needed_permission_modify_power:
case permission::i_client_needed_kick_from_server_power:
case permission::i_client_needed_kick_from_channel_power:
case permission::i_client_kick_from_channel_power:
case permission::i_client_kick_from_server_power:
return true;
default:
return false;
}
}
inline bool permission_is_group_property(ts::permission::PermissionType type) {
using namespace ts;
switch (type) {
case permission::i_icon_id:
case permission::i_group_show_name_in_tree:
case permission::i_group_sort_id:
case permission::b_group_is_permanent:
case permission::i_displayed_group_needed_modify_power:
case permission::i_displayed_group_needed_member_add_power:
case permission::i_displayed_group_needed_member_remove_power:
return true;
default:
return false;
}
}
inline bool permission_is_client_property(ts::permission::PermissionType type) {
using namespace ts;
switch (type) {
case permission::i_icon_id:
case permission::i_client_talk_power:
case permission::i_client_max_idletime:
case permission::i_group_sort_id:
case permission::i_channel_view_power:
case permission::b_channel_ignore_view_power:
case permission::b_client_is_priority_speaker:
return true;
default:
return false;
}
}
inline ssize_t count_characters(const std::string& in) {
size_t index = 0;
size_t count = 0;
while(index < in.length()){
count++;
auto current = (uint8_t) in[index];
if(current >= 128) { //UTF8 check
if(current >= 192 && (current <= 193 || current >= 245)) {
return -1;
} else if(current >= 194 && current <= 223) {
if(in.length() - index <= 1) return -1;
else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191) index += 1; //Valid
else return -1;
} else if(current >= 224 && current <= 239) {
if(in.length() - index <= 2) return -1;
else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 &&
(uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191) index += 2; //Valid
else return -1;
} else if(current >= 240 && current <= 244) {
if(in.length() - index <= 3) return -1;
else if((uint8_t) in[index + 1] >= 128 && (uint8_t) in[index + 1] <= 191 &&
(uint8_t) in[index + 2] >= 128 && (uint8_t) in[index + 2] <= 191 &&
(uint8_t) in[index + 3] >= 128 && (uint8_t) in[index + 3] <= 191) index += 3; //Valid
else return -1;
} else {
return -1;
}
}
index++;
}
return count;
}
File diff suppressed because it is too large Load Diff
+924
View File
@@ -0,0 +1,924 @@
//
// Created by wolverindev on 26.01.20.
//
#include <memory>
#include <spdlog/sinks/rotating_file_sink.h>
#include <iostream>
#include <bitset>
#include <algorithm>
#include <openssl/sha.h>
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
#include "../music/MusicClient.h"
#include "../query/QueryClient.h"
#include "../../weblist/WebListManager.h"
#include "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include <experimental/filesystem>
#include <cstdint>
#include <StringVariable.h>
#include "helpers.h"
#include <Properties.h>
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <misc/base64.h>
#include <misc/hex.h>
#include <misc/digest.h>
#include <misc/rnd.h>
#include <misc/timer.h>
#include <misc/strobf.h>
#include <misc/scope_guard.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;
command_result ConnectedClient::handleCommandMusicBotCreate(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
if(this->server->musicManager->max_bots() != -1 && this->server->musicManager->max_bots() <= this->server->musicManager->current_bot_count()){
if(config::license->isPremium())
return command_result{error::music_limit_reached};
else
return command_result{error::music_limit_reached, strobf("You reached the server music bot limit. You could increase this limit by extend your server with a premium license.").string()};
}
auto permissions_list = this->calculate_permissions({
permission::i_client_music_limit,
permission::b_client_music_create_permanent,
permission::b_client_music_create_semi_permanent,
permission::b_client_music_create_temporary,
permission::i_channel_join_power,
permission::i_client_music_delete_power,
permission::i_client_music_create_modify_max_volume
}, this->getChannelId());
auto permissions = map<permission::PermissionType, permission::v2::PermissionFlaggedValue>(permissions_list.begin(), permissions_list.end());
auto max_bots = permissions[permission::i_client_music_limit];
if(max_bots.has_value) {
auto ownBots = this->server->musicManager->listBots(this->getClientDatabaseId());
if(!permission::v2::permission_granted(ownBots.size() + 1, max_bots))
return command_result{error::music_client_limit_reached};
}
MusicClient::Type::value create_type;
if(cmd[0].has("type")) {
create_type = cmd["type"].as<MusicClient::Type::value>();
switch(create_type) {
case MusicClient::Type::PERMANENT:
if(!permission::v2::permission_granted(1, permissions[permission::b_client_music_create_permanent]))
return command_result{permission::b_client_music_create_permanent};
break;
case MusicClient::Type::SEMI_PERMANENT:
if(!permission::v2::permission_granted(1, permissions[permission::b_client_music_create_semi_permanent]))
return command_result{permission::b_client_music_create_semi_permanent};
break;
case MusicClient::Type::TEMPORARY:
if(!permission::v2::permission_granted(1, permissions[permission::b_client_music_create_temporary]))
return command_result{permission::b_client_music_create_temporary};
break;
default:
return command_result{error::vs_critical};
}
} else {
if(permission::v2::permission_granted(1, permissions[permission::b_client_music_create_permanent]))
create_type = MusicClient::Type::PERMANENT;
else if(permission::v2::permission_granted(1, permissions[permission::b_client_music_create_semi_permanent]))
create_type = MusicClient::Type::SEMI_PERMANENT;
else if(permission::v2::permission_granted(1, permissions[permission::b_client_music_create_temporary]))
create_type = MusicClient::Type::TEMPORARY;
else
return command_result{permission::b_client_music_create_temporary};
}
shared_lock server_channel_lock(this->server->channel_tree_lock);
auto channel = cmd[0].has("cid") ? this->server->channelTree->findChannel(cmd["cid"]) : this->currentChannel;
if(!channel) {
if(cmd[0].has("cid")) return command_result{error::channel_invalid_id};
} else {
if(!this->calculate_and_get_join_state(channel))
channel = nullptr;
}
if(!channel)
channel = this->server->channelTree->getDefaultChannel();
auto bot = this->server->musicManager->createBot(this->getClientDatabaseId());
if(!bot) return command_result{error::vs_critical};
bot->set_bot_type(create_type);
if(permissions[permission::i_client_music_create_modify_max_volume].has_value) {
auto max_volume = min(100, max(0, permissions[permission::i_client_music_create_modify_max_volume].value));
if(max_volume >= 0)
bot->volume_modifier(max_volume / 100.f);
}
this->selectedBot = bot;
{
server_channel_lock.unlock();
unique_lock server_channel_w_lock(this->server->channel_tree_lock);
this->server->client_move(
bot,
channel,
nullptr,
"music bot created",
ViewReasonId::VREASON_USER_ACTION,
false,
server_channel_w_lock
);
}
bot->properties()[property::CLIENT_LAST_CHANNEL] = channel ? channel->channelId() : 0;
bot->properties()[property::CLIENT_COUNTRY] = config::geo::countryFlag;
if(permissions[permission::i_client_music_delete_power].has_value && permissions[permission::i_client_music_delete_power].value >= 0) {
bot->clientPermissions->set_permission(permission::i_client_music_needed_delete_power, {permissions[permission::i_client_music_delete_power].value,0}, permission::v2::set_value, permission::v2::do_nothing);
}
Command notify(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifymusiccreated" : "");
notify["bot_id"] = bot->getClientDatabaseId();
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandMusicBotDelete(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
if(bot->getOwner() != this->getClientDatabaseId()) {
ACTION_REQUIRES_PERMISSION(permission::i_client_music_delete_power, bot->calculate_permission(permission::i_client_music_needed_delete_power, bot->getChannelId()), this->getChannelId());
}
this->server->musicManager->deleteBot(bot);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandMusicBotSetSubscription(ts::Command &cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot && cmd["bot_id"].as<ClientDbId>() != 0) return command_result{error::music_invalid_id};
{
auto old_bot = this->subscribed_bot.lock();
if(old_bot)
old_bot->remove_subscriber(_this.lock());
}
if(bot) {
bot->add_subscriber(_this.lock());
this->subscribed_bot = bot;
}
return command_result{error::ok};
}
void apply_song(Command& command, const std::shared_ptr<ts::music::SongInfo>& element, int index = 0) {
if(!element) return;
command[index]["song_id"] = element ? element->getSongId() : 0;
command[index]["song_url"] = element ? element->getUrl() : "";
command[index]["song_invoker"] = element ? element->getInvoker() : 0;
command[index]["song_loaded"] = false;
auto entry = dynamic_pointer_cast<ts::music::PlayableSong>(element);
if(entry) {
auto data = entry->song_loaded_data();
command[index]["song_loaded"] = entry->song_loaded() && data;
if(entry->song_loaded() && data) {
command[index]["song_title"] = data->title;
command[index]["song_description"] = data->description;
command[index]["song_thumbnail"] = data->thumbnail;
command[index]["song_length"] = data->length.count();
}
}
}
command_result ConnectedClient::handleCommandMusicBotPlayerInfo(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
Command result(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifymusicplayerinfo" : "");
result["bot_id"] = bot->getClientDatabaseId();
result["player_state"] =(int) bot->player_state();
if(bot->current_player()) {
result["player_buffered_index"] = bot->current_player()->bufferedUntil().count();
result["player_replay_index"] = bot->current_player()->currentIndex().count();
result["player_max_index"] = bot->current_player()->length().count();
result["player_seekable"] = bot->current_player()->seek_supported();
result["player_title"] = bot->current_player()->songTitle();
result["player_description"] = bot->current_player()->songDescription();
} else {
result["player_buffered_index"] = 0;
result["player_replay_index"] = 0;
result["player_max_index"] = 0;
result["player_seekable"] = 0;
result["player_title"] = "";
result["player_description"] = "";
}
apply_song(result, bot->current_song());
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandMusicBotPlayerAction(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
ACTION_REQUIRES_PERMISSION(permission::i_client_music_play_power, bot->calculate_permission(permission::i_client_music_needed_play_power, bot->getChannelId()), this->getChannelId());
if(cmd["action"] == 0) {
bot->stopMusic();
} else if(cmd["action"] == 1) {
bot->playMusic();
} else if(cmd["action"] == 2) {
bot->player_pause();
} else if(cmd["action"] == 3) {
bot->forwardSong();
} else if(cmd["action"] == 4) {
bot->rewindSong();
} else if(cmd["action"] == 5) {
if(!bot->current_player()) return command_result{error::music_no_player};
bot->current_player()->forward(::music::PlayerUnits(cmd["units"].as<int64_t>()));
} else if(cmd["action"] == 6) {
if(!bot->current_player()) return command_result{error::music_no_player};
bot->current_player()->rewind(::music::PlayerUnits(cmd["units"].as<int64_t>()));
} else return command_result{error::music_invalid_action};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistList(ts::Command &cmd) {
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto self_dbid = this->getClientDatabaseId();
auto playlist_view_power = this->calculate_permission(permission::i_playlist_view_power, 0);
auto playlists = this->server->musicManager->playlists();
playlists.erase(find_if(playlists.begin(), playlists.end(), [&](const shared_ptr<music::PlayablePlaylist>& playlist) {
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] == self_dbid)
return false;
auto needed_view_power = playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power);
return !permission::v2::permission_granted(needed_view_power, playlist_view_power, false);;
}), playlists.end());
if(playlists.empty())
return command_result{error::database_empty_result};
Command notify(this->notify_response_command("notifyplaylistlist"));
size_t index = 0;
for(const auto& entry : playlists) {
notify[index]["playlist_id"] = entry->playlist_id();
auto bot = entry->current_bot();
notify[index]["playlist_bot_id"] = bot ? bot->getClientDatabaseId() : 0;
notify[index]["playlist_title"] = entry->properties()[property::PLAYLIST_TITLE].value();
notify[index]["playlist_type"] = entry->properties()[property::PLAYLIST_TYPE].value();
notify[index]["playlist_owner_dbid"] = entry->properties()[property::PLAYLIST_OWNER_DBID].value();
notify[index]["playlist_owner_name"] = entry->properties()[property::PLAYLIST_OWNER_NAME].value();
notify[index]["needed_power_modify"] = entry->permissions()->getPermissionValue(permission::i_playlist_needed_modify_power);
notify[index]["needed_power_permission_modify"] = entry->permissions()->getPermissionValue(permission::i_playlist_needed_permission_modify_power);
notify[index]["needed_power_delete"] = entry->permissions()->getPermissionValue(permission::i_playlist_needed_delete_power);
notify[index]["needed_power_song_add"] = entry->permissions()->getPermissionValue(permission::i_playlist_song_needed_add_power);
notify[index]["needed_power_song_move"] = entry->permissions()->getPermissionValue(permission::i_playlist_song_needed_move_power);
notify[index]["needed_power_song_remove"] = entry->permissions()->getPermissionValue(permission::i_playlist_song_needed_remove_power);
index++;
}
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistCreate(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_playlist_create, 1);
{
auto max_playlists = this->calculate_permission(permission::i_max_playlists, 0);
if(max_playlists.has_value) {
auto playlists = ref_server->musicManager->find_playlists_by_client(this->getClientDatabaseId());
if(!permission::v2::permission_granted(playlists.size(), max_playlists))
return command_result{permission::i_max_playlists};
}
}
auto playlist = ref_server->musicManager->create_playlist(this->getClientDatabaseId(), this->getDisplayName());
if(!playlist) return command_result{error::vs_critical};
playlist->properties()[property::PLAYLIST_TYPE] = music::Playlist::Type::GENERAL;
{
auto max_songs = this->calculate_permission(permission::i_max_playlist_size, 0);
if(max_songs.has_value && max_songs.value >= 0)
playlist->properties()[property::PLAYLIST_MAX_SONGS] = max_songs.value;
}
auto power = this->calculate_permission(permission::i_playlist_song_remove_power, 0);
if(power.has_value && power.value >= 0)
playlist->permissions()->setPermission(permission::i_playlist_song_needed_remove_power, power.value, nullptr);
power = this->calculate_permission(permission::i_playlist_delete_power, 0);
if(power.has_value && power.value >= 0)
playlist->permissions()->setPermission(permission::i_playlist_needed_delete_power, power.value, nullptr);
power = this->calculate_permission(permission::i_playlist_modify_power, 0);
if(power.has_value && power.value >= 0)
playlist->permissions()->setPermission(permission::i_playlist_needed_modify_power, power.value, nullptr);
power = this->calculate_permission(permission::i_playlist_permission_modify_power, 0);
if(power.has_value && power.value >= 0)
playlist->permissions()->setPermission(permission::i_playlist_needed_permission_modify_power, power.value, nullptr);
Command notify(this->notify_response_command("notifyplaylistcreated"));
notify["playlist_id"] = playlist->playlist_id();
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistDelete(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_delete_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_delete_power));
string error;
if(!ref_server->musicManager->delete_playlist(playlist->playlist_id(), error)) {
logError(this->getServerId(), "Failed to delete playlist with id {}. Error: {}", playlist->playlist_id(), error);
return command_result{error::vs_critical};
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistInfo(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_view_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power));
Command notify(this->notify_response_command("notifyplaylistinfo"));
for(const auto& property : playlist->properties().list_properties(property::FLAG_PLAYLIST_VARIABLE)) {
notify[property.type().name] = property.value();
}
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistEdit(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_modify_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_modify_power));
deque<pair<shared_ptr<property::PropertyDescription>, string>> properties;
for(const auto& key : cmd[0].keys()) {
if(key == "playlist_id") continue;
if(key == "return_code") continue;
auto property = property::info<property::PlaylistProperties>(key);
if(*property == property::PLAYLIST_UNDEFINED) {
logError(this->getServerId(), R"([{}] Tried to edit a not existing playlist property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if((property->flags & property::FLAG_USER_EDITABLE) == 0) {
logError(this->getServerId(), "[{}] Tried to change a playlist property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if(!property->validate_input(cmd[key].as<string>())) {
logError(this->getServerId(), "[{}] Tried to change a playlist property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if(*property == property::PLAYLIST_CURRENT_SONG_ID) {
auto song_id = cmd[key].as<SongId>();
auto song = song_id > 0 ? playlist->find_song(song_id) : nullptr;
if(song_id != 0 && !song)
return command_result{error::playlist_invalid_song_id};
} else if(*property == property::PLAYLIST_MAX_SONGS) {
auto value = cmd[key].as<int32_t>();
auto max_value = this->calculate_permission(permission::i_max_playlist_size, this->getChannelId());
if(max_value.has_value && !permission::v2::permission_granted(value, max_value))
return command_result{permission::i_max_playlist_size};
}
properties.emplace_back(property, key);
}
for(const auto& property : properties) {
if(*property.first == property::PLAYLIST_CURRENT_SONG_ID) {
playlist->set_current_song(cmd[property.second]);
continue;
}
playlist->properties()[property.first] = cmd[property.second].string();
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistPermList(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_playlist_permission_list, 1);
auto permissions = playlist->permissions()->listPermissions(PERM_FLAG_PUBLIC);
if(permissions.empty())
return command_result{error::vs_critical};
Command result(this->notify_response_command("notifyplaylistpermlist"));
int index = 0;
result["playlist_id"] = playlist->playlist_id();
for (const auto &elm : permissions) {
if(elm->hasValue()) {
result[index]["permid"] = elm->type->type;
result[index]["permvalue"] = elm->value;
result[index]["permnegated"] = elm->flag_negate;
result[index]["permskip"] = elm->flag_skip;
index++;
}
if(elm->hasGrant()) {
result[index]["permid"] = (uint16_t) (elm->type->type | PERM_ID_GRANT);
result[index]["permvalue"] = elm->granted;
result[index]["permnegated"] = 0;
result[index]["permskip"] = 0;
index++;
}
}
this->sendCommand(result);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_permission_modify_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_permission_modify_power));
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");
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(val, this->calculate_permission(permType, 0, true))) {
if(conOnError) continue;
return command_result{permission::i_permission_modify_power};
}
if (grant) {
playlist->permissions()->setPermissionGranted(permType, cmd[index]["permvalue"], nullptr);
} else {
playlist->permissions()->setPermission(permType, cmd[index]["permvalue"], nullptr, cmd[index]["permskip"], cmd[index]["permnegated"]);
}
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(5);
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_permission_modify_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_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");
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) {
playlist->permissions()->setPermissionGranted(permType, permNotGranted, nullptr);
} else {
playlist->permissions()->deletePermission(permType, nullptr);
}
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_view_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power));
auto songs = playlist->list_songs();
if(songs.empty())
return command_result{error::database_empty_result};
Command notify(this->notify_response_command("notifyplaylistsonglist"));
notify["playlist_id"] = playlist->playlist_id();
size_t index = 0;
for(const auto& song : songs) {
notify[index]["song_id"] = song->id;
notify[index]["song_invoker"] = song->invoker;
notify[index]["song_previous_song_id"] = song->previous_song_id;
notify[index]["song_url"] = song->url;
notify[index]["song_url_loader"] = song->url_loader;
notify[index]["song_loaded"] = song->loaded;
notify[index]["song_metadata"] = song->metadata;
index++;
}
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistSongAdd(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_song_add_power, playlist->permissions()->getPermissionValue(permission::i_playlist_song_needed_add_power));
if(!cmd[0].has("invoker"))
cmd["invoker"] = "";
if(!cmd[0].has("previous")) {
auto songs = playlist->list_songs();
if(songs.empty())
cmd["previous"] = "0";
else
cmd["previous"] = songs.back()->id;
}
auto song = playlist->add_song(_this.lock(), cmd["url"], cmd["invoker"], cmd["previous"]);
if(!song) return command_result{error::vs_critical};
Command notify(this->notify_response_command("notifyplaylistsongadd"));
notify["song_id"] = song->id;
this->sendCommand(notify);
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistSongReorder(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_song_move_power, playlist->permissions()->getPermissionValue(permission::i_playlist_song_needed_move_power));
SongId song_id = cmd["song_id"];
SongId previous_id = cmd["song_previous_song_id"];
auto song = playlist->find_song(song_id);
if(!song) return command_result{error::playlist_invalid_song_id};
if(!playlist->reorder_song(song_id, previous_id))
return command_result{error::vs_critical};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistSongRemove(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist) return command_result{error::playlist_invalid_id};
if(playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_song_remove_power, playlist->permissions()->getPermissionValue(permission::i_playlist_song_needed_remove_power));
SongId song_id = cmd["song_id"];
auto song = playlist->find_song(song_id);
if(!song) return command_result{error::playlist_invalid_song_id};
if(!playlist->delete_song(song_id))
return command_result{error::vs_critical};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandMusicBotQueueList(Command& cmd) {
return command_result{error::not_implemented}; //FIXME
/*
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
PERM_CHECK_CHANNELR(permission::i_client_music_info, bot->permissionValue(permission::i_client_music_needed_info, bot->currentChannel), this->currentChannel, true);
bool bulked = cmd.hasParm("bulk") || cmd.hasParm("balk") || cmd.hasParm("pipe") || cmd.hasParm("bar") || cmd.hasParm("paypal");
int command_index = 0;
Command notify(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : "");
{
auto history = bot->queue()->history();
for(int index = history.size(); index > 0; index--) {
if(!bulked)
notify = Command(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : "");
apply_song(notify, history[index - 1], command_index);
notify[command_index]["queue_index"] = -index;
if(!bulked)
this->sendCommand(notify);
else
command_index++;
}
}
{
if(!bulked)
notify = Command(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : "");
auto song = bot->queue()->currentSong();
apply_song(notify, song, command_index);
notify[command_index]["queue_index"] = 0;
if(!bulked)
this->sendCommand(notify);
else if(song)
command_index++;
}
{
auto queue = bot->queue()->queueEntries();
for(int index = 0; index < queue.size(); index++) {
if(!bulked)
notify = Command(this->getExternalType() == CLIENT_VOICE ? "notifymusicqueueentry" : "");
apply_song(notify, queue[index], command_index);
notify[command_index]["queue_index"] = index + 1;
if(!bulked)
this->sendCommand(notify);
else
command_index++;
}
}
debugMessage(this->getServerId(),"Send: {}",notify.build());
if(bulked) {
if(command_index > 0) {
this->sendCommand(notify);
} else return { ErrorType::DBEmpty };
}
if(this->getExternalType() == CLIENT_VOICE) {
Command notify("notifymusicqueuefinish");
notify["bot_id"] = bot->getClientDatabaseId();
this->sendCommand(notify);
}
return command_result{error::ok};
*/
}
command_result ConnectedClient::handleCommandMusicBotQueueAdd(Command& cmd) {
return command_result{error::not_implemented}; //FIXME
/*
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true);
MusicClient::loader_t loader;
auto& type = cmd[0]["type"];
if((type.castable<int>() && type.as<int>() == 0) || type.as<string>() == "yt") {
loader = bot->ytLoader(this->getServer());
} else if((type.castable<int>() && type.as<int>() == 1) || type.as<string>() == "ffmpeg") {
loader = bot->ffmpegLoader(this->getServer());
} else if((type.castable<int>() && type.as<int>() == 2) || type.as<string>() == "channel") {
loader = bot->channelLoader(this->getServer());
} else if((type.castable<int>() && type.as<int>() == -1) || type.as<string>() == "any") {
loader = bot->providerLoader(this->getServer(), "");
}
if(!loader) return command_result{error::music_invalid_action};
auto entry = bot->queue()->insertEntry(cmd["url"], _this.lock(), loader);
if(!entry) return command_result{error::vs_critical};
this->server->forEachClient([&](shared_ptr<ConnectedClient> client) {
client->notifyMusicQueueAdd(bot, entry, bot->queue()->queueEntries().size() - 1, _this.lock());
});
return command_result{error::ok};
*/
}
command_result ConnectedClient::handleCommandMusicBotQueueRemove(Command& cmd) {
return command_result{error::not_implemented}; //FIXME
/*
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true);
std::deque<std::shared_ptr<music::SongInfo>> songs;
for(int index = 0; index < cmd.bulkCount(); index++) {
auto entry = bot->queue()->find_queue(cmd["song_id"]);
if(!entry) {
if(cmd.hasParm("skip_error")) continue;
return command_result{error::database_empty_result};
}
songs.push_back(move(entry));
}
for(const auto& entry : songs)
bot->queue()->deleteEntry(dynamic_pointer_cast<music::PlayableSong>(entry));
this->server->forEachClient([&](shared_ptr<ConnectedClient> client) {
client->notifyMusicQueueRemove(bot, songs, _this.lock());
});
return command_result{error::ok};
*/
}
command_result ConnectedClient::handleCommandMusicBotQueueReorder(Command& cmd) {
return command_result{error::not_implemented}; //FIXME
/*
CMD_REQ_SERVER;
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = this->server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
PERM_CHECK_CHANNELR(permission::i_client_music_play_power, bot->permissionValue(permission::i_client_music_needed_play_power, bot->currentChannel), this->currentChannel, true);
auto entry = bot->queue()->find_queue(cmd["song_id"]);
if(!entry) return command_result{error::database_empty_result};
auto order = bot->queue()->changeOrder(entry, cmd["index"]);
if(order < 0) return command_result{error::vs_critical};
this->server->forEachClient([&](shared_ptr<ConnectedClient> client) {
client->notifyMusicQueueOrderChange(bot, entry, order, _this.lock());
});
return command_result{error::ok};
*/
}
command_result ConnectedClient::handleCommandMusicBotPlaylistAssign(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
auto bot = ref_server->musicManager->findBotById(cmd["bot_id"]);
if(!bot) return command_result{error::music_invalid_id};
if(bot->getOwner() != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_client_music_play_power, bot->calculate_permission(permission::i_client_music_needed_play_power, 0));
auto playlist = ref_server->musicManager->find_playlist(cmd["playlist_id"]);
if(!playlist && cmd["playlist_id"] != 0) return command_result{error::playlist_invalid_id};
if(ref_server->musicManager->find_bot_by_playlist(playlist))
return command_result{error::playlist_already_in_use};
if(playlist && playlist->properties()[property::PLAYLIST_OWNER_DBID] != this->getClientDatabaseId())
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::i_playlist_view_power, playlist->permissions()->getPermissionValue(permission::i_playlist_needed_view_power));
if(!ref_server->musicManager->assign_playlist(bot, playlist))
return command_result{error::vs_critical};
return command_result{error::ok};
}
File diff suppressed because it is too large Load Diff