10 Commits

Author SHA1 Message Date
root d2ba1b4eee Totally fucked up... 2020-03-17 12:08:33 +01:00
WolverinDEV d6f483a019 totally fucked up 2020-03-17 12:08:07 +01:00
WolverinDEV 58666b8906 Some changes 2020-03-11 10:19:08 +01:00
WolverinDEV c002be7307 Merge branch '1.4.10-openssl' into 1.5.0 2020-03-10 17:04:45 +01:00
WolverinDEV 60a1c34dc9 Some more updates 2020-03-10 17:03:38 +01:00
WolverinDEV 799df15ace Some updates 2020-03-09 18:29:28 +01:00
WolverinDEV 7926a26091 Changed some stuff 2020-03-09 18:28:49 +01:00
WolverinDEV 6172628247 Some more changes 2020-03-05 14:10:22 +01:00
WolverinDEV 3a5c152b3c Added new files 2020-03-04 16:26:43 +01:00
WolverinDEV be4a99dcd7 Begining with the new VS structure 2020-03-04 16:24:36 +01:00
194 changed files with 16055 additions and 10917 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
branch = master
[submodule "shared"]
path = shared
url = https://git.did.science/TeaSpeak/TeaSpeakLibrary.git
url = https://git.did.science/WolverinDEV/TeaSpeak-SharedLib.git
[submodule "music"]
path = music
url = https://github.com/TeaSpeak/TeaMusic-Providers.git
+1 -2
View File
@@ -103,5 +103,4 @@ add_definitions(-DINET -DINET6)
add_subdirectory(shared/)
add_subdirectory(server/)
add_subdirectory(license/)
add_subdirectory(MusicBot/)
add_subdirectory(file/)
add_subdirectory(MusicBot/)
+4 -12
View File
@@ -30,12 +30,9 @@ void AbstractMusicPlayer::unregisterEventHandler(const std::string& string) {
}
void AbstractMusicPlayer::fireEvent(MusicEvent event) {
decltype(this->eventHandlers) handlers{};
{
std::lock_guard lock(this->eventLock);
handlers = this->eventHandlers; //Copy for remove while fire
}
for(const auto& entry : handlers)
std::lock_guard lock(this->eventLock);
auto listCopy = this->eventHandlers; //Copy for remove while fire
for(const auto& entry : listCopy)
entry.second(event);
}
@@ -78,16 +75,11 @@ void manager::loadProviders(const std::string& path) {
}
deque<fs::path> paths;
error_code error_code{};
for(const auto& entry : fs::directory_iterator(dir, error_code)){
for(const auto& entry : fs::directory_iterator(dir)){
if(!entry.path().has_extension()) continue;
if(entry.path().extension().string() == ".so")
paths.push_back(entry.path());
}
if(error_code) {
log::log(log::err, "Failed to scan the target directory (" + dir.string() + "): " + error_code.message());
return;
}
std::sort(paths.begin(), paths.end(), [](const fs::path& a, const fs::path& b){ return a.filename().string() < b.filename().string(); });
int index = 0;
-35
View File
@@ -1,35 +0,0 @@
cmake_minimum_required(VERSION 3.6)
project(TeaSpeak-Files)
#set(CMAKE_CXX_STANDARD 17)
#set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(TeaSpeak-FileServer STATIC
local_server/LocalFileProvider.cpp
local_server/LocalFileSystem.cpp
local_server/LocalFileTransfer.cpp
local_server/LocalFileTransferClientWorker.cpp
local_server/LocalFileTransferDisk.cpp
local_server/LocalFileTransferNetwork.cpp
local_server/clnpath.cpp
)
target_link_libraries(TeaSpeak-FileServer PUBLIC TeaSpeak ${StringVariable_LIBRARIES_STATIC} stdc++fs
libevent::core libevent::pthreads
DataPipes::core::static
openssl::ssl::shared
openssl::crypto::shared
)
target_include_directories(TeaSpeak-FileServer PUBLIC include/)
add_executable(TeaSpeak-FileServerTest test/main.cpp)
target_link_libraries(TeaSpeak-FileServerTest PUBLIC TeaSpeak-FileServer
TeaMusic #Static (Must be in here, so we link against TeaMusic which uses C++11. That forbids GCC to use the newer glibc version)
CXXTerminal::static #Static
stdc++fs
)
target_compile_options(TeaSpeak-FileServerTest PUBLIC -static-libgcc -static-libstdc++)
add_executable(FileServer-CLNText local_server/clnpath.cpp)
target_compile_definitions(FileServer-CLNText PUBLIC -DCLN_EXEC)
-307
View File
@@ -1,307 +0,0 @@
#pragma once
#include <string>
#include <chrono>
#include <Definitions.h>
#include <condition_variable>
#include <variant>
#include <deque>
#include <functional>
#define TRANSFER_KEY_LENGTH (32)
namespace ts::server::file {
enum struct ExecuteStatus {
UNKNOWN,
WAITING,
SUCCESS,
ERROR
};
template<typename VariantType, typename T, std::size_t index = 0>
constexpr std::size_t variant_index() {
if constexpr (index == std::variant_size_v<VariantType>) {
return index;
} else if constexpr (std::is_same_v<std::variant_alternative_t<index, VariantType>, T>) {
return index;
} else {
return variant_index<VariantType, T, index + 1>();
}
}
struct EmptyExecuteResponse { };
template <class error_t, class response_t = EmptyExecuteResponse>
class ExecuteResponse {
typedef std::variant<EmptyExecuteResponse, error_t, response_t> variant_t;
public:
ExecuteStatus status{ExecuteStatus::WAITING};
[[nodiscard]] inline const auto& response() const { return std::get<response_t>(this->response_); }
template <typename = std::enable_if_t<!std::is_void<error_t>::value>>
[[nodiscard]] inline const error_t& error() const { return std::get<error_t>(this->response_); }
inline void wait() const {
std::unique_lock nlock{this->notify_mutex};
this->notify_cv.wait(nlock, [&]{ return this->status != ExecuteStatus::WAITING; });
}
template<typename _Rep, typename _Period>
[[nodiscard]] inline bool wait_for(const std::chrono::duration<_Rep, _Period>& time) const {
std::unique_lock nlock{this->notify_mutex};
return this->notify_cv.wait_for(nlock, time, [&]{ return this->status != ExecuteStatus::WAITING; });
}
template <typename... Args>
inline void emplace_success(Args&&... args) {
constexpr auto success_index = variant_index<variant_t, response_t>();
std::lock_guard rlock{this->notify_mutex};
this->response_.template emplace<success_index, Args...>(std::forward<Args>(args)...);
this->status = ExecuteStatus::SUCCESS;
this->notify_cv.notify_all();
}
template <typename... Args>
inline void emplace_fail(Args&&... args) {
constexpr auto error_index = variant_index<variant_t, error_t>();
std::lock_guard rlock{this->notify_mutex};
this->response_.template emplace<error_index, Args...>(std::forward<Args>(args)...);
this->status = ExecuteStatus::ERROR;
this->notify_cv.notify_all();
}
[[nodiscard]] inline bool succeeded() const {
return this->status == ExecuteStatus::SUCCESS;
}
ExecuteResponse(std::mutex& notify_mutex, std::condition_variable& notify_cv)
: notify_mutex{notify_mutex}, notify_cv{notify_cv} {}
private:
variant_t response_{}; /* void* as default value so we don't initialize error_t or response_t */
std::mutex& notify_mutex;
std::condition_variable& notify_cv;
};
namespace filesystem {
template <typename ErrorCodes>
struct DetailedError {
ErrorCodes error_type{ErrorCodes::UNKNOWN};
std::string error_message{};
DetailedError(ErrorCodes type, std::string extraMessage) : error_type{type}, error_message{std::move(extraMessage)} {}
};
enum struct DirectoryQueryErrorType {
UNKNOWN,
PATH_EXCEEDS_ROOT_PATH,
PATH_IS_A_FILE,
PATH_DOES_NOT_EXISTS,
FAILED_TO_LIST_FILES,
MAX
};
constexpr std::array<std::string_view, (int) DirectoryQueryErrorType::MAX> directory_query_error_messages = {
"unknown error",
"path exceeds base path",
"path is a file",
"path does not exists",
"failed to list files"
};
typedef DetailedError<DirectoryQueryErrorType> DirectoryQueryError;
struct DirectoryEntry {
enum Type {
UNKNOWN,
DIRECTORY,
FILE
};
Type type{Type::UNKNOWN};
std::string name{};
std::chrono::system_clock::time_point modified_at{};
size_t size{0};
};
enum struct DirectoryModifyErrorType {
UNKNOWN,
PATH_EXCEEDS_ROOT_PATH,
PATH_ALREADY_EXISTS,
FAILED_TO_CREATE_DIRECTORIES
};
typedef DetailedError<DirectoryModifyErrorType> DirectoryModifyError;
enum struct FileModifyErrorType {
UNKNOWN,
PATH_EXCEEDS_ROOT_PATH,
TARGET_PATH_EXCEEDS_ROOT_PATH,
PATH_DOES_NOT_EXISTS,
TARGET_PATH_ALREADY_EXISTS,
FAILED_TO_DELETE_FILES,
FAILED_TO_RENAME_FILE,
SOME_FILES_ARE_LOCKED
};
typedef DetailedError<FileModifyErrorType> FileModifyError;
enum struct ServerCommandErrorType {
UNKNOWN,
FAILED_TO_CREATE_DIRECTORIES,
FAILED_TO_DELETE_DIRECTORIES
};
typedef DetailedError<ServerCommandErrorType> ServerCommandError;
class AbstractProvider {
public:
typedef ExecuteResponse<DirectoryQueryError, std::deque<DirectoryEntry>> directory_query_response_t;
/* server */
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(ServerId /* server */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(ServerId /* server */) = 0;
/* channels */
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_channel_directory(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<DirectoryModifyError>> create_channel_directory(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> delete_channel_file(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> rename_channel_file(ServerId /* server */, ChannelId /* channel */, const std::string& /* path */, const std::string& /* target */) = 0;
/* icons */
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_icon_directory(ServerId /* server */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> delete_icon(ServerId /* server */, const std::string& /* name */) = 0;
/* avatars */
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_avatar_directory(ServerId /* server */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> delete_avatar(ServerId /* server */, const std::string& /* name */) = 0;
private:
};
}
namespace transfer {
typedef uint32_t transfer_id;
struct Transfer {
transfer_id server_transfer_id{0};
transfer_id client_transfer_id{0};
ServerId server_id{0};
ClientId client_id{0};
ChannelId channel_id{0};
char transfer_key[TRANSFER_KEY_LENGTH]{};
std::chrono::system_clock::time_point initialized_timestamp{};
enum Direction {
DIRECTION_UNKNOWN,
DIRECTION_UPLOAD,
DIRECTION_DOWNLOAD
} direction{DIRECTION_UNKNOWN};
struct Address {
std::string hostname{};
uint16_t port{0};
};
std::vector<Address> server_addresses{};
enum TargetType {
TARGET_TYPE_UNKNOWN,
TARGET_TYPE_CHANNEL_FILE,
TARGET_TYPE_ICON,
TARGET_TYPE_AVATAR
} target_type{TARGET_TYPE_UNKNOWN};
std::string target_file_path{};
int64_t max_bandwidth{-1};
size_t expected_file_size{0}; /* incl. the offset! */
size_t file_offset{0};
bool override_exiting{false};
};
struct TransferStatistics {
uint64_t network_bytes_send{0};
uint64_t network_bytes_received{0};
uint64_t delta_network_bytes_send{0};
uint64_t delta_network_bytes_received{0};
uint64_t file_bytes_transferred{0};
uint64_t delta_file_bytes_transferred{0};
size_t file_start_offset{0};
size_t file_current_offset{0};
size_t file_total_size{0};
};
struct TransferInitError {
enum Type {
UNKNOWN
} error_type{UNKNOWN};
std::string error_message{};
};
struct TransferActionError {
enum Type {
UNKNOWN,
UNKNOWN_TRANSFER
} error_type{UNKNOWN};
std::string error_message{};
};
struct TransferError {
enum Type {
UNKNOWN,
TRANSFER_TIMEOUT,
DISK_IO_ERROR,
DISK_TIMEOUT,
DISK_INITIALIZE_ERROR,
NETWORK_IO_ERROR,
UNEXPECTED_CLIENT_DISCONNECT,
UNEXPECTED_DISK_EOF
} error_type{UNKNOWN};
std::string error_message{};
};
class AbstractProvider {
public:
struct TransferInfo {
std::string file_path{};
bool override_exiting{false}; /* only for upload valid */
size_t file_offset{0};
size_t expected_file_size{0};
int64_t max_bandwidth{-1};
};
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> initialize_channel_transfer(Transfer::Direction /* direction */, ServerId /* server */, ChannelId /* channel */, const TransferInfo& /* info */) = 0;
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> initialize_icon_transfer(Transfer::Direction /* direction */, ServerId /* server */, const TransferInfo& /* info */) = 0;
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> initialize_avatar_transfer(Transfer::Direction /* direction */, ServerId /* server */, const TransferInfo& /* info */) = 0;
virtual std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(transfer_id /* id */, bool /* flush */) = 0;
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_registered{}; /* transfer has been registered */
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_started{}; /* transfer has been started */
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_finished{}; /* transfer has been finished */
std::function<void(const std::shared_ptr<Transfer>&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transferring the data */
std::function<void(const std::shared_ptr<Transfer>&, const TransferStatistics&)> callback_transfer_statistics{};
};
}
class AbstractFileServer {
public:
[[nodiscard]] virtual filesystem::AbstractProvider& file_system() = 0;
[[nodiscard]] virtual transfer::AbstractProvider& file_transfer() = 0;
private:
};
extern bool initialize(std::string& /* error */);
extern void finalize();
extern std::shared_ptr<AbstractFileServer> server();
}
-121
View File
@@ -1,121 +0,0 @@
//
// Created by WolverinDEV on 28/04/2020.
//
#include <netinet/in.h>
#include "LocalFileProvider.h"
using namespace ts::server;
using LocalFileServer = file::LocalFileProvider;
EVP_PKEY* ssl_generate_key() {
auto key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
auto rsa = RSA_new();
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(BN_new(), ::BN_free);
BN_set_word(e.get(), RSA_F4);
if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return nullptr;
EVP_PKEY_assign_RSA(key.get(), rsa);
return key.release();
}
X509* ssl_generate_certificate(EVP_PKEY* key) {
auto cert = X509_new();
X509_set_pubkey(cert, key);
ASN1_INTEGER_set(X509_get_serialNumber(cert), 3);
X509_gmtime_adj(X509_get_notBefore(cert), 0);
X509_gmtime_adj(X509_get_notAfter(cert), 31536000L);
X509_NAME* name = nullptr;
name = X509_get_subject_name(cert);
//for(const auto& subject : this->subjects)
// X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
X509_set_subject_name(cert, name);
name = X509_get_issuer_name(cert);
//for(const auto& subject : this->issues)
// X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
X509_set_issuer_name(cert, name);
X509_sign(cert, key, EVP_sha512());
return cert;
}
std::shared_ptr<LocalFileServer> server_instance{};
bool file::initialize(std::string &error) {
server_instance = std::make_shared<LocalFileProvider>();
auto options = std::make_shared<pipes::SSL::Options>();
options->verbose_io = true;
options->context_method = SSLv23_method();
options->free_unused_keypairs = false;
{
std::shared_ptr<EVP_PKEY> pkey{ssl_generate_key(), ::EVP_PKEY_free};
std::shared_ptr<X509> cert{ssl_generate_certificate(&*pkey), ::X509_free};
options->default_keypair({pkey, cert});
}
if(!server_instance->initialize(error, options)) {
server_instance = nullptr;
return false;
}
return true;
}
void file::finalize() {
auto server = std::exchange(server_instance, nullptr);
if(!server) return;
server->finalize();
}
std::shared_ptr<file::AbstractFileServer> file::server() {
return server_instance;
}
LocalFileServer::LocalFileProvider() : file_system_{}, file_transfer_{this->file_system_} {}
LocalFileServer::~LocalFileProvider() {}
bool LocalFileServer::initialize(std::string &error, const std::shared_ptr<pipes::SSL::Options>& ssl_options) {
if(!this->file_system_.initialize(error, "file-root/"))
return false;
std::deque<std::shared_ptr<transfer::NetworkBinding>> bindings{};
{
auto binding = std::make_shared<transfer::NetworkBinding>();
binding->hostname = "localhost";
auto& iaddr = *(sockaddr_in*) &binding->address;
iaddr.sin_family = AF_INET;
iaddr.sin_port = htons(1112);
iaddr.sin_addr.s_addr = INADDR_ANY;
bindings.push_back(std::move(binding));
}
if(!this->file_transfer_.start(bindings, ssl_options)) {
error = "transfer server startup failed";
this->file_system_.finalize();
return false;
}
return true;
}
void LocalFileServer::finalize() {
this->file_transfer_.stop();
this->file_system_.finalize();
}
file::filesystem::AbstractProvider &LocalFileServer::file_system() {
return this->file_system_;
}
file::transfer::AbstractProvider & LocalFileServer::file_transfer() {
return this->file_transfer_;
}
-547
View File
@@ -1,547 +0,0 @@
#pragma once
#include <files/FileServer.h>
#include <deque>
#include <utility>
#include <thread>
#include <shared_mutex>
#include <sys/socket.h>
#include <pipes/ws.h>
#include <pipes/ssl.h>
#include <misc/net.h>
#include <random>
#define TRANSFER_MAX_CACHED_BYTES (1024 * 1024 * 1) // Buffer up to 1mb
namespace ts::server::file {
namespace filesystem {
#ifdef FS_INCLUDED
namespace fs = std::experimental::filesystem;
#endif
class LocalFileSystem : public filesystem::AbstractProvider {
using FileModifyError = filesystem::FileModifyError;
using DirectoryModifyError = filesystem::DirectoryModifyError;
public:
enum struct FileCategory {
ICON,
AVATAR,
CHANNEL
};
virtual ~LocalFileSystem();
bool initialize(std::string & /* error */, const std::string & /* root path */);
void finalize();
void lock_file(const std::string& /* absolute path */);
void unlock_file(const std::string& /* absolute path */);
[[nodiscard]] inline const auto &root_path() const { return this->root_path_; }
[[nodiscard]] std::string absolute_avatar_path(ServerId, const std::string&);
[[nodiscard]] std::string absolute_icon_path(ServerId, const std::string&);
[[nodiscard]] std::string absolute_channel_path(ServerId, ChannelId, const std::string&);
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(ServerId /* server */) override;
std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(ServerId /* server */) override;
std::shared_ptr<directory_query_response_t>
query_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override;
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
create_channel_directory(ServerId id, ChannelId channelId, const std::string &string) override;
std::shared_ptr<ExecuteResponse<FileModifyError>>
delete_channel_file(ServerId id, ChannelId channelId, const std::string &string) override;
std::shared_ptr<ExecuteResponse<FileModifyError>>
rename_channel_file(ServerId id, ChannelId channelId, const std::string &, const std::string &) override;
std::shared_ptr<directory_query_response_t> query_icon_directory(ServerId id) override;
std::shared_ptr<ExecuteResponse<FileModifyError>>
delete_icon(ServerId id, const std::string &string) override;
std::shared_ptr<directory_query_response_t> query_avatar_directory(ServerId id) override;
std::shared_ptr<ExecuteResponse<FileModifyError>>
delete_avatar(ServerId id, const std::string &string) override;
private:
#ifdef FS_INCLUDED
[[nodiscard]] fs::path server_path(ServerId);
[[nodiscard]] fs::path server_channel_path(ServerId, ChannelId);
[[nodiscard]] static bool exceeds_base_path(const fs::path& /* base */, const fs::path& /* target */);
[[nodiscard]] bool is_any_file_locked(const fs::path& /* base */, const std::string& /* path */, std::string& /* file (relative to the base) */);
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileModifyError>>
delete_file(const fs::path& /* base */, const std::string &string);
[[nodiscard]] std::shared_ptr<directory_query_response_t>
query_directory(const fs::path& /* base */, const std::string &string, bool);
#endif
template <typename error_t, typename result_t = EmptyExecuteResponse>
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
}
std::string target_file_path(FileCategory type, ts::ServerId sid, ts::ChannelId cid, const std::string &path);
std::mutex result_notify_mutex{};
std::condition_variable result_notify_cv{};
std::string root_path_{};
std::mutex locked_files_mutex{};
std::deque<std::string> locked_files_{};
};
}
namespace transfer {
class LocalFileTransfer;
struct Buffer {
Buffer* next{nullptr};
size_t capacity{0};
size_t length{0};
size_t offset{0};
char data[1]{};
};
[[nodiscard]] extern Buffer* allocate_buffer(size_t);
extern void free_buffer(Buffer*);
struct NetworkThrottle {
constexpr static auto kThrottleTimespanMs{250};
typedef uint8_t span_t;
ssize_t max_bytes{0};
span_t current_index{0};
size_t bytes_send{0};
inline bool increase_bytes(size_t bytes) {
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
if(this->current_index != current_span) {
this->current_index = current_span;
this->bytes_send = bytes;
} else {
this->bytes_send += bytes;
}
return this->max_bytes > 0 && this->bytes_send >= this->max_bytes;
}
inline void set_max_bandwidth(ssize_t bytes_per_second) {
if(bytes_per_second <= 0)
this->max_bytes = -1;
else
this->max_bytes = bytes_per_second * kThrottleTimespanMs / 1000;
}
[[nodiscard]] inline bool should_throttle(timeval& next_timestamp) {
if(this->max_bytes <= 0) return false;
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
if(this->current_index != current_span) return false;
if(this->bytes_send < this->max_bytes) return false;
next_timestamp.tv_usec = (kThrottleTimespanMs - current_ms % kThrottleTimespanMs) * 1000;
next_timestamp.tv_sec = next_timestamp.tv_usec / 1000000;
next_timestamp.tv_usec -= next_timestamp.tv_sec * 1000000;
return true;
}
[[nodiscard]] inline size_t bytes_left() const {
if(this->max_bytes <= 0) return (size_t) -1;
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
if(this->current_index != current_span) return this->max_bytes;
if(this->bytes_send < this->max_bytes) return this->max_bytes - this->bytes_send;
return 0;
}
[[nodiscard]] inline std::chrono::milliseconds expected_writing_time(size_t bytes) const {
if(this->max_bytes <= 0) return std::chrono::milliseconds{0};
return std::chrono::seconds{bytes / (this->max_bytes * (1000 / kThrottleTimespanMs))};
}
};
/* all variables are locked via the state_mutex */
struct FileClient : std::enable_shared_from_this<FileClient> {
LocalFileTransfer* handle;
std::shared_ptr<Transfer> transfer{nullptr};
std::shared_mutex state_mutex{};
enum {
STATE_AWAITING_KEY, /* includes SSL/HTTP init */
STATE_TRANSFERRING,
STATE_DISCONNECTING,
STATE_DISCONNECTED
} state{STATE_AWAITING_KEY};
enum NetworkingProtocol {
PROTOCOL_UNKNOWN,
PROTOCOL_HTTPS,
PROTOCOL_TS_V1
};
enum HTTPUploadState {
HTTP_AWAITING_HEADER,
HTTP_STATE_AWAITING_BOUNDARY,
HTTP_STATE_UPLOADING,
HTTP_STATE_DOWNLOADING
};
struct {
bool file_locked{false};
std::string absolute_path{};
int file_descriptor{0};
bool currently_processing{false};
FileClient* next_client{nullptr};
} file{};
struct {
size_t provided_bytes{0};
char key[TRANSFER_KEY_LENGTH]{0};
} transfer_key{};
/* will be used for both directions */
struct {
std::mutex mutex{};
size_t bytes{0};
bool buffering_stopped{false};
Buffer* buffer_head{nullptr};
Buffer** buffer_tail{&buffer_head};
} buffer{};
struct {
sockaddr_storage address{};
int file_descriptor{0};
NetworkingProtocol protocol{PROTOCOL_UNKNOWN};
struct event* event_read{nullptr};
struct event* event_write{nullptr};
struct event* event_throttle{nullptr};
bool add_event_write{false}, add_event_read{false};
std::chrono::system_clock::time_point disconnect_timeout{};
NetworkThrottle throttle;
pipes::SSL pipe_ssl{};
bool pipe_ssl_init{false};
std::unique_ptr<Buffer, decltype(free_buffer)*> http_header_buffer{nullptr, free_buffer};
HTTPUploadState http_state{HTTPUploadState::HTTP_AWAITING_HEADER};
/* Only read the transfer key length at the beginning. We than have the actual limit which will be set via throttle */
size_t max_read_buffer_size{TRANSFER_KEY_LENGTH};
} networking{};
struct {
size_t network_bytes_send{0};
size_t network_bytes_received{0};
size_t file_bytes_transferred{0};
/* used for delta statistics */
size_t last_network_bytes_send{0};
size_t last_network_bytes_received{0};
/* used for delta statistics */
size_t last_file_bytes_transferred{0};
size_t disk_bytes_read{0};
size_t disk_bytes_write{0};
} statistics{};
struct {
std::chrono::system_clock::time_point last_write{};
std::chrono::system_clock::time_point last_read{};
std::chrono::system_clock::time_point connected{};
std::chrono::system_clock::time_point key_received{};
std::chrono::system_clock::time_point disconnecting{};
} timings;
explicit FileClient(LocalFileTransfer* handle) : handle{handle} {}
~FileClient();
void add_network_write_event(bool /* ignore bandwidth limits */);
void add_network_write_event_nolock(bool /* ignore bandwidth limits */);
/* will check if we've enough space in out read buffer again */
void add_network_read_event(bool /* ignore bandwidth limits */);
bool send_file_bytes(const void* /* buffer */, size_t /* length */);
bool enqueue_buffer_bytes(const void* /* buffer */, size_t /* length */);
[[nodiscard]] inline std::string log_prefix() const { return "[" + net::to_string(this->networking.address) + "]"; }
};
enum struct DiskIOStartResult {
SUCCESS,
OUT_OF_MEMORY
};
enum struct NetworkingStartResult {
SUCCESS,
OUT_OF_MEMORY,
NO_BINDINGS
};
enum struct ClientWorkerStartResult {
SUCCESS
};
enum struct NetworkInitializeResult {
SUCCESS,
OUT_OF_MEMORY
};
enum struct FileInitializeResult {
SUCCESS,
INVALID_TRANSFER_DIRECTION,
OUT_OF_MEMORY,
PROCESS_FILE_LIMIT_REACHED,
SYSTEM_FILE_LIMIT_REACHED,
FILE_IS_BUSY,
FILE_DOES_NOT_EXISTS,
FILE_SYSTEM_ERROR,
FILE_IS_A_DIRECTORY,
FILE_TOO_LARGE,
DISK_IS_READ_ONLY,
FILE_SIZE_MISMATCH,
FILE_SEEK_FAILED,
FILE_IS_NOT_ACCESSIBLE,
MAX
};
constexpr static std::array<std::string_view, (size_t) FileInitializeResult::MAX> kFileInitializeResultMessages{
/* SUCCESS */ "success",
/* INVALID_TRANSFER_DIRECTION */ "invalid file transfer direction",
/* OUT_OF_MEMORY */ "out of memory",
/* PROCESS_FILE_LIMIT_REACHED */ "process file limit reached",
/* SYSTEM_FILE_LIMIT_REACHED */ "system file limit reached",
/* FILE_IS_BUSY */ "target file is busy",
/* FILE_DOES_NOT_EXISTS */ "target file does not exists",
/* FILE_SYSTEM_ERROR */ "internal file system error",
/* FILE_IS_A_DIRECTORY */ "target file is a directory",
/* FILE_TOO_LARGE */ "file is too large",
/* DISK_IS_READ_ONLY */ "disk is in read only mode",
/* FILE_SIZE_MISMATCH */ "file size mismatch",
/* FILE_SEEK_FAILED */ "failed to seek to target file offset",
/* FILE_IS_NOT_ACCESSIBLE */ "file is not accessible"
};
enum struct TransferKeyApplyResult {
SUCCESS,
FILE_ERROR,
UNKNOWN_KEY,
INTERNAL_ERROR
};
enum struct TransferUploadRawResult {
MORE_DATA_TO_RECEIVE,
FINISH,
/* UNKNOWN ERROR */
};
enum struct TransferUploadHTTPResult {
MORE_DATA_TO_RECEIVE,
FINISH,
BOUNDARY_MISSING,
BOUNDARY_TOKEN_INVALID,
BOUNDARY_INVALID,
MISSING_CONTENT_TYPE,
INVALID_CONTENT_TYPE
/* UNKNOWN ERROR */
};
struct NetworkBinding : std::enable_shared_from_this<NetworkBinding> {
std::string hostname{};
sockaddr_storage address{};
int file_descriptor{-1};
struct event* accept_event{nullptr};
LocalFileTransfer* handle{nullptr};
};
class LocalFileTransfer : public AbstractProvider {
public:
explicit LocalFileTransfer(filesystem::LocalFileSystem&);
~LocalFileTransfer();
[[nodiscard]] bool start(const std::deque<std::shared_ptr<NetworkBinding>>& /* bindings */, const std::shared_ptr<pipes::SSL::Options>& /* ssl options */);
void stop();
[[nodiscard]] inline const auto& ssl_options() const {
return this->ssl_options_;
}
inline void set_ssl_options(const std::shared_ptr<pipes::SSL::Options>& options) {
this->ssl_options_ = options;
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_channel_transfer(Transfer::Direction direction, ServerId id, ChannelId channelId,
const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_icon_transfer(Transfer::Direction direction, ServerId id, const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_avatar_transfer(Transfer::Direction direction, ServerId id, const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(transfer_id id, bool) override;
private:
enum struct DiskIOLoopState {
STOPPED,
RUNNING,
STOPPING,
FORCE_STOPPING
};
filesystem::LocalFileSystem& file_system_;
std::atomic<transfer_id> current_transfer_id{0};
std::mt19937 transfer_random_token_generator{std::random_device{}()};
std::mutex result_notify_mutex{};
std::condition_variable result_notify_cv{};
std::mutex transfers_mutex{};
std::deque<std::shared_ptr<FileClient>> transfers_{};
std::deque<std::shared_ptr<Transfer>> pending_transfers{};
std::shared_ptr<pipes::SSL::Options> ssl_options_{};
enum ServerState {
STOPPED,
RUNNING
} state{ServerState::STOPPED};
struct {
bool active{false};
std::thread dispatch_thread{};
std::mutex mutex{};
std::condition_variable notify_cv{};
} disconnect;
struct {
bool active{false};
std::thread dispatch_thread{};
struct event_base* event_base{nullptr};
std::deque<std::shared_ptr<NetworkBinding>> bindings{};
} network{};
struct {
DiskIOLoopState state{DiskIOLoopState::STOPPED};
std::thread dispatch_thread{};
std::mutex queue_lock{};
std::condition_variable notify_work_awaiting{};
std::condition_variable notify_client_processed{};
FileClient* queue_head{nullptr};
FileClient** queue_tail{&queue_head};
} disk_io{};
template <typename error_t, typename result_t = EmptyExecuteResponse>
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_transfer(Transfer::Direction, ServerId, ChannelId, Transfer::TargetType, const TransferInfo &info);
[[nodiscard]] NetworkingStartResult start_networking();
[[nodiscard]] DiskIOStartResult start_disk_io();
[[nodiscard]] ClientWorkerStartResult start_client_worker();
void shutdown_networking();
void shutdown_disk_io();
void shutdown_client_worker();
void disconnect_client(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */, bool /* flush */);
[[nodiscard]] NetworkInitializeResult initialize_networking(const std::shared_ptr<FileClient>& /* client */, int /* file descriptor */);
/* might block 'till all IO operations have been succeeded */
void finalize_networking(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
[[nodiscard]] FileInitializeResult initialize_file_io(const std::shared_ptr<FileClient>& /* client */);
void finalize_file_io(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
[[nodiscard]] bool initialize_client_ssl(const std::shared_ptr<FileClient>& /* client */);
void finalize_client_ssl(const std::shared_ptr<FileClient>& /* client */);
void enqueue_disk_io(const std::shared_ptr<FileClient>& /* client */);
void execute_disk_io(const std::shared_ptr<FileClient>& /* client */);
void report_transfer_statistics(const std::shared_ptr<FileClient>& /* client */);
[[nodiscard]] TransferUploadRawResult handle_transfer_upload_raw(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */);
[[nodiscard]] TransferUploadHTTPResult handle_transfer_upload_http(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */);
void send_http_response(const std::shared_ptr<FileClient>& /* client */, http::HttpResponse& /* response */);
static void callback_transfer_network_write(int, short, void*);
static void callback_transfer_network_read(int, short, void*);
static void callback_transfer_network_throttle(int, short, void*);
static void callback_transfer_network_accept(int, short, void*);
static void dispatch_loop_client_worker(void*);
static void dispatch_loop_network(void*);
static void dispatch_loop_disk_io(void*);
size_t handle_transfer_read(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
size_t handle_transfer_read_raw(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
[[nodiscard]] TransferKeyApplyResult handle_transfer_key_provided(const std::shared_ptr<FileClient>& /* client */, std::string& /* error */);
};
}
class LocalFileProvider : public AbstractFileServer {
public:
LocalFileProvider();
virtual ~LocalFileProvider();
[[nodiscard]] bool initialize(std::string& /* error */, const std::shared_ptr<pipes::SSL::Options>& /* ssl options */);
void finalize();
filesystem::AbstractProvider &file_system() override;
transfer::AbstractProvider &file_transfer() override;
private:
filesystem::LocalFileSystem file_system_;
transfer::LocalFileTransfer file_transfer_;
};
}
-350
View File
@@ -1,350 +0,0 @@
//
// Created by WolverinDEV on 29/04/2020.
//
#include <experimental/filesystem>
#define FS_INCLUDED
#include <log/LogUtils.h>
#include "LocalFileProvider.h"
#include "clnpath.h"
using namespace ts::server::file;
using namespace ts::server::file::filesystem;
namespace fs = std::experimental::filesystem;
using directory_query_response_t = AbstractProvider::directory_query_response_t;
LocalFileSystem::~LocalFileSystem() = default;
bool LocalFileSystem::initialize(std::string &error_message, const std::string &root_path_string) {
auto root_path = fs::u8path(root_path_string);
std::error_code error{};
if(!fs::exists(root_path, error)) {
if(error)
logWarning(LOG_FT, "Failed to check root path existence. Assuming it does not exist. ({}/{})", error.value(), error.message());
if(!fs::create_directories(root_path, error) || error) {
error_message = "Failed to create root file system at " + root_path_string + ": " + std::to_string(error.value()) + "/" + error.message();
return false;
}
}
auto croot = clnpath(fs::absolute(root_path).string());
logMessage(LOG_FT, "Started file system root at {}", croot);
this->root_path_ = croot;
return true;
}
void LocalFileSystem::finalize() {}
fs::path LocalFileSystem::server_path(ts::ServerId id) {
return fs::u8path(this->root_path_) / fs::u8path("server_" + std::to_string(id));
}
fs::path LocalFileSystem::server_channel_path(ts::ServerId sid, ts::ChannelId cid) {
return this->server_path(sid) / fs::u8path("channel_" + std::to_string(cid));
}
bool LocalFileSystem::exceeds_base_path(const fs::path &base, const fs::path &target) {
auto rel_target = clnpath(target.string());
if(rel_target.starts_with("..")) return true;
auto base_string = clnpath(fs::absolute(base).string());
auto target_string = clnpath(fs::absolute(target).string());
return !target_string.starts_with(base_string);
}
bool LocalFileSystem::is_any_file_locked(const fs::path &base, const std::string &path, std::string &locked_file) {
auto c_path = clnpath(fs::absolute(base / fs::u8path(path)).string());
std::lock_guard lock{this->locked_files_mutex};
for(const auto& lfile : this->locked_files_) {
if(lfile.starts_with(c_path)) {
locked_file = lfile.substr(base.string().length());
return true;
}
}
return false;
}
std::string LocalFileSystem::target_file_path(FileCategory type, ts::ServerId sid, ts::ChannelId cid, const std::string &path) {
fs::path target_path{};
switch (type) {
case FileCategory::CHANNEL:
target_path = this->server_channel_path(sid, cid) / path;
break;
case FileCategory::ICON:
target_path = this->server_path(sid) / "icons" / path;
break;
case FileCategory::AVATAR:
target_path = this->server_path(sid) / "avatars" / path;
break;
}
return clnpath(fs::absolute(target_path).string());
}
std::string LocalFileSystem::absolute_avatar_path(ServerId sid, const std::string &path) {
return this->target_file_path(FileCategory::AVATAR, sid, 0, path);
}
std::string LocalFileSystem::absolute_icon_path(ServerId sid, const std::string &path) {
return this->target_file_path(FileCategory::ICON, sid, 0, path);
}
std::string LocalFileSystem::absolute_channel_path(ServerId sid, ChannelId cid, const std::string &path) {
return this->target_file_path(FileCategory::CHANNEL, sid, cid, path);
}
void LocalFileSystem::lock_file(const std::string &c_path) {
std::lock_guard lock{this->locked_files_mutex};
this->locked_files_.push_back(c_path);
}
void LocalFileSystem::unlock_file(const std::string &c_path) {
std::lock_guard lock{this->locked_files_mutex};
this->locked_files_.erase(std::remove_if(this->locked_files_.begin(), this->locked_files_.end(), [&](const auto& p) { return p == c_path; }), this->locked_files_.end());
}
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::initialize_server(ServerId id) {
auto path = this->server_path(id);
std::error_code error{};
auto response = this->create_execute_response<ServerCommandError>();
if(!fs::exists(path, error)) {
if(!fs::create_directories(path, error) || error) {
response->emplace_fail(ServerCommandErrorType::FAILED_TO_CREATE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
return response;
}
}
//TODO: Copy the default icon
response->emplace_success();
return response;
}
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::delete_server(ServerId id) {
auto path = this->server_path(id);
std::error_code error{};
auto response = this->create_execute_response<ServerCommandError>();
//TODO: Stop all running file transfers!
if(fs::exists(path, error)) {
if(!fs::remove_all(path, error) || error) {
response->emplace_fail(ServerCommandErrorType::FAILED_TO_DELETE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
return response;
}
}
response->emplace_success();
return response;
}
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(const fs::path &base,
const std::string &path,
bool allow_non_existance) {
std::error_code error{};
auto response = this->create_execute_response<DirectoryQueryError, std::deque<DirectoryEntry>>();
auto target_path = base / fs::u8path(path);
if(this->exceeds_base_path(base, target_path)) {
response->emplace_fail(DirectoryQueryErrorType::PATH_EXCEEDS_ROOT_PATH, "");
return response;
}
if(!fs::exists(target_path, error)) {
if(allow_non_existance)
response->emplace_success();
else
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
return response;
} else if(error) {
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
return response;
}
if(!fs::is_directory(target_path, error)) {
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
return response;
} else if(error) {
logWarning(LOG_FT, "Failed to check for directory at {}: {}. Assuming its not a directory.", target_path.string(), error.value(), error.message());
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
return response;
}
std::deque<DirectoryEntry> entries{};
for(auto& entry : fs::directory_iterator(target_path, error)) {
auto status = entry.status(error);
if(error) {
logWarning(LOG_FT, "Failed to query file status for {} ({}/{}). Skipping entry for directory query.", entry.path().string(), error.value(), error.message());
continue;
}
if(status.type() == fs::file_type::directory) {
auto& dentry = entries.emplace_back();
dentry.type = DirectoryEntry::DIRECTORY;
dentry.name = entry.path().filename();
dentry.modified_at = fs::last_write_time(entry.path(), error);
if(error)
logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", entry.path().string(), error.value(), error.message());
dentry.size = 0;
} else if(status.type() == fs::file_type::regular) {
auto& dentry = entries.emplace_back();
dentry.type = DirectoryEntry::FILE;
dentry.name = entry.path().filename();
dentry.modified_at = fs::last_write_time(entry.path(), error);
if(error)
logWarning(LOG_FT, "Failed to query last write time for file {} ({}/{}).", entry.path().string(), error.value(), error.message());
dentry.size = fs::file_size(entry.path(), error);
if(error)
logWarning(LOG_FT, "Failed to query size for file {} ({}/{}).", entry.path().string(), error.value(), error.message());
} else {
logWarning(LOG_FT, "Directory query listed an unknown file type for file {} ({}).", entry.path().string(), (int) status.type());
}
}
if(error && entries.empty()) {
response->emplace_fail(DirectoryQueryErrorType::FAILED_TO_LIST_FILES, std::to_string(error.value()) + "/" + error.message());
return response;
}
response->emplace_success(std::forward<decltype(entries)>(entries));
return response;
}
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_icon_directory(ServerId id) {
return this->query_directory(this->server_path(id) / fs::u8path("icons"), "/", true);
}
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_avatar_directory(ServerId id) {
return this->query_directory(this->server_path(id) / fs::u8path("avatars"), "/", true);
}
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_channel_directory(ServerId id, ChannelId channelId, const std::string &path) {
return this->query_directory(this->server_channel_path(id, channelId), path, false);
}
std::shared_ptr<ExecuteResponse<DirectoryModifyError>> LocalFileSystem::create_channel_directory(ServerId id, ChannelId channelId, const std::string &path) {
auto channel_path_root = this->server_channel_path(id, channelId);
std::error_code error{};
auto response = this->create_execute_response<DirectoryModifyError>();
auto target_path = channel_path_root / fs::u8path(path);
if(this->exceeds_base_path(channel_path_root, target_path)) {
response->emplace_fail(DirectoryModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
return response;
}
if(fs::exists(target_path, error)) {
response->emplace_fail(DirectoryModifyErrorType::PATH_ALREADY_EXISTS, "");
return response;
} else if(error) {
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
}
if(!fs::create_directories(target_path, error) || error) {
response->emplace_fail(DirectoryModifyErrorType::FAILED_TO_CREATE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
return response;
}
response->emplace_success();
return response;
}
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channel_file(ServerId id, ChannelId channelId, const std::string &current_path_string, const std::string &new_path_string) {
auto channel_path_root = this->server_channel_path(id, channelId);
std::error_code error{};
std::string locked_file{};
auto response = this->create_execute_response<FileModifyError>();
auto current_path = channel_path_root / fs::u8path(current_path_string);
auto target_path = channel_path_root / fs::u8path(new_path_string);
if(this->exceeds_base_path(channel_path_root, current_path)) {
response->emplace_fail(FileModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
return response;
}
if(this->exceeds_base_path(channel_path_root, target_path)) {
response->emplace_fail(FileModifyErrorType::TARGET_PATH_EXCEEDS_ROOT_PATH, "");
return response;
}
if(!fs::exists(current_path, error)) {
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
return response;
} else if(error) {
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", current_path.string(), error.value(), error.message());
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
return response;
}
if(fs::exists(target_path, error)) {
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
return response;
} else if(error) {
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", current_path.string(), error.value(), error.message());
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
return response;
}
if(this->is_any_file_locked(channel_path_root, current_path, locked_file)) {
response->emplace_fail(FileModifyErrorType::SOME_FILES_ARE_LOCKED, locked_file);
return response;
}
fs::rename(current_path, target_path, error);
if(error) {
response->emplace_fail(FileModifyErrorType::FAILED_TO_RENAME_FILE, std::to_string(error.value()) + "/" + error.message());
return response;
}
response->emplace_success();
return response;
}
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::delete_file(const fs::path &base,
const std::string &path) {
std::error_code error{};
std::string locked_file{};
auto response = this->create_execute_response<FileModifyError>();
auto target_path = base / fs::u8path(path);
if(fs::exists(target_path, error)) {
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
return response;
} else if(error) {
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", target_path.string(), error.value(), error.message());
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
return response;
}
if(this->is_any_file_locked(base, path, locked_file)) {
response->emplace_fail(FileModifyErrorType::SOME_FILES_ARE_LOCKED, locked_file);
return response;
}
if(!fs::remove(target_path, error) || error) {
response->emplace_fail(FileModifyErrorType::FAILED_TO_DELETE_FILES, std::to_string(error.value()) + "/" + error.message());
return response;
}
response->emplace_success();
return response;
}
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::delete_channel_file(ServerId id, ChannelId channelId, const std::string &path) {
return this->delete_file(this->server_channel_path(id, channelId), path);
}
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::delete_icon(ServerId id, const std::string &icon) {
return this->delete_file(this->server_path(id) / fs::u8path("icons"), icon);
}
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::delete_avatar(ServerId id, const std::string &avatar) {
return this->delete_file(this->server_path(id) / fs::u8path("avatars"), avatar);
}
-212
View File
@@ -1,212 +0,0 @@
//
// Created by WolverinDEV on 04/05/2020.
//
#include <cassert>
#include <event2/event.h>
#include <log/LogUtils.h>
#include <random>
#include "./LocalFileProvider.h"
#include "LocalFileProvider.h"
using namespace ts::server::file;
using namespace ts::server::file::transfer;
Buffer* transfer::allocate_buffer(size_t size) {
auto total_size = sizeof(Buffer) + size;
auto buffer = (Buffer*) malloc(total_size);
new (buffer) Buffer{};
buffer->capacity = size;
return buffer;
}
void transfer::free_buffer(Buffer* buffer) {
buffer->~Buffer();
free(buffer);
}
FileClient::~FileClient() {
auto head = this->buffer.buffer_head;
while (head) {
auto next = head->next;
free_buffer(head);
head = next;
}
assert(!this->file.file_descriptor);
assert(!this->file.currently_processing);
assert(!this->file.next_client);
assert(!this->networking.event_read);
assert(!this->networking.event_write);
assert(this->state == STATE_DISCONNECTED);
}
LocalFileTransfer::LocalFileTransfer(filesystem::LocalFileSystem &fs) : file_system_{fs} {}
LocalFileTransfer::~LocalFileTransfer() = default;
bool LocalFileTransfer::start(const std::deque<std::shared_ptr<NetworkBinding>>& bindings, const std::shared_ptr<pipes::SSL::Options>& ssl_options) {
assert(ssl_options);
this->ssl_options_ = ssl_options;
(void) this->start_client_worker();
{
auto start_result = this->start_disk_io();
switch (start_result) {
case DiskIOStartResult::SUCCESS:
break;
case DiskIOStartResult::OUT_OF_MEMORY:
logError(LOG_FT, "Failed to start disk worker (Out of memory)");
goto error_exit_disk;
default:
logError(LOG_FT, "Failed to start disk worker ({})", (int) start_result);
goto error_exit_disk;
}
}
{
this->network.bindings = bindings;
auto start_result = this->start_networking();
switch (start_result) {
case NetworkingStartResult::SUCCESS:
break;
case NetworkingStartResult::OUT_OF_MEMORY:
logError(LOG_FT, "Failed to start networking (Out of memory)");
goto error_exit_network;
case NetworkingStartResult::NO_BINDINGS:
logError(LOG_FT, "Failed to start networking (No address could be bound)");
goto error_exit_network;
default:
logError(LOG_FT, "Failed to start networking ({})", (int) start_result);
goto error_exit_network;
}
}
return true;
error_exit_network:
this->shutdown_networking();
error_exit_disk:
this->shutdown_disk_io();
this->shutdown_client_worker();
return false;
}
void LocalFileTransfer::stop() {
this->shutdown_networking();
this->shutdown_disk_io();
this->shutdown_client_worker();
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_icon_transfer(Transfer::Direction direction, ServerId sid, const TransferInfo &info) {
return this->initialize_transfer(direction, sid, 0, Transfer::TARGET_TYPE_ICON, info);
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_avatar_transfer(Transfer::Direction direction, ServerId sid, const TransferInfo &info) {
return this->initialize_transfer(direction, sid, 0, Transfer::TARGET_TYPE_AVATAR, info);
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_channel_transfer(Transfer::Direction direction, ServerId sid, ChannelId cid, const TransferInfo &info) {
return this->initialize_transfer(direction, sid, cid, Transfer::TARGET_TYPE_CHANNEL_FILE, info);
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_transfer(
Transfer::Direction direction, ServerId sid, ChannelId cid,
Transfer::TargetType ttype,
const TransferInfo &info) {
auto response = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
/* TODO: test for a transfer limit */
auto transfer = std::make_shared<Transfer>();
transfer->server_transfer_id = ++this->current_transfer_id;
transfer->server_id = sid;
transfer->channel_id = cid;
transfer->target_type = ttype;
transfer->direction = direction;
transfer->client_id = 0; /* must be provided externally */
transfer->client_transfer_id = 0; /* must be provided externally */
transfer->server_addresses.reserve(this->network.bindings.size());
for(auto& binding : this->network.bindings) {
if(!binding->file_descriptor) continue;
transfer->server_addresses.emplace_back(Transfer::Address{binding->hostname, net::port(binding->address)});
}
transfer->target_file_path = info.file_path;
transfer->file_offset = info.file_offset;
transfer->expected_file_size = info.expected_file_size;
transfer->max_bandwidth = info.max_bandwidth;
constexpr static std::string_view kTokenCharacters{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"};
for(auto& c : transfer->transfer_key)
c = kTokenCharacters[transfer_random_token_generator() % kTokenCharacters.length()];
transfer->transfer_key[0] = (char) 'r'; /* (114) */ /* a non valid SSL header type to indicate that we're using a file transfer key and not doing a SSL handshake */
transfer->transfer_key[1] = (char) 'a'; /* ( 97) */
transfer->transfer_key[2] = (char) 'w'; /* (119) */
transfer->initialized_timestamp = std::chrono::system_clock::now();
{
std::lock_guard tlock{this->transfers_mutex};
this->pending_transfers.push_back(transfer);
}
if(auto callback{this->callback_transfer_registered}; callback)
callback(transfer);
response->emplace_success(std::move(transfer));
return response;
}
std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_transfer(transfer_id id, bool flush) {
auto response = this->create_execute_response<TransferActionError>();
std::shared_ptr<Transfer> transfer{};
std::shared_ptr<FileClient> connected_transfer{};
{
std::lock_guard tlock{this->transfers_mutex};
auto ct_it = std::find_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& t) {
return t->transfer && t->transfer->server_transfer_id == id;
});
if(ct_it != this->transfers_.end())
connected_transfer = *ct_it;
else {
auto t_it = std::find_if(this->pending_transfers.begin(), this->pending_transfers.end(), [&](const std::shared_ptr<Transfer>& t) {
return t->server_transfer_id == id;
});
if(t_it != this->pending_transfers.end()) {
transfer = *t_it;
this->pending_transfers.erase(t_it);
}
}
}
if(!transfer) {
if(connected_transfer)
transfer = connected_transfer->transfer;
else {
response->emplace_fail(TransferActionError{TransferActionError::UNKNOWN_TRANSFER, ""});
return response;
}
}
if(connected_transfer) {
logMessage(LOG_FT, "{} Stopping transfer due to an user request.", connected_transfer->log_prefix());
std::unique_lock slock{connected_transfer->state_mutex};
this->disconnect_client(connected_transfer, slock, flush);
} else {
logMessage(LOG_FT, "Removing pending file transfer for id {}", id);
}
response->emplace_success();
return response;
}
@@ -1,222 +0,0 @@
//
// Created by WolverinDEV on 04/05/2020.
//
#include <cassert>
#include <event2/event.h>
#include <log/LogUtils.h>
#include "./LocalFileProvider.h"
#include "LocalFileProvider.h"
using namespace ts::server::file;
using namespace ts::server::file::transfer;
ClientWorkerStartResult LocalFileTransfer::start_client_worker() {
assert(!this->disconnect.active);
this->disconnect.active = true;
this->disconnect.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_client_worker, this);
return ClientWorkerStartResult::SUCCESS;
}
void LocalFileTransfer::shutdown_client_worker() {
if(!this->disconnect.active) return;
this->disconnect.active = false;
this->disconnect.notify_cv.notify_all();
if(this->disconnect.dispatch_thread.joinable())
this->disconnect.dispatch_thread.join();
{
std::unique_lock tlock{this->transfers_mutex};
if(!this->transfers_.empty())
logWarning(LOG_FT, "Shutting down disconnect worker even thou we still have some active clients. This could cause memory leaks.");
}
}
void LocalFileTransfer::disconnect_client(const std::shared_ptr<FileClient> &client, std::unique_lock<std::shared_mutex>& state_lock, bool flush) {
assert(state_lock.owns_lock());
if(client->state == FileClient::STATE_DISCONNECTED || (client->state == FileClient::STATE_DISCONNECTING && flush)) {
return; /* shall NOT happen */
}
#define del_ev_noblock(event) if(event) event_del_noblock(event)
client->state = flush ? FileClient::STATE_DISCONNECTING : FileClient::STATE_DISCONNECTED;
client->timings.disconnecting = std::chrono::system_clock::now();
if(flush) {
const auto network_flush_time = client->networking.throttle.expected_writing_time(client->buffer.bytes) + std::chrono::seconds{10};
if(!client->transfer) {
del_ev_noblock(client->networking.event_read);
del_ev_noblock(client->networking.event_throttle);
client->add_network_write_event_nolock(false);
/* max flush 10 seconds */
client->networking.disconnect_timeout = std::chrono::system_clock::now() + network_flush_time;
debugMessage(LOG_FT, "{} Disconnecting client. Flushing pending bytes (max {} seconds)", client->log_prefix(), std::chrono::floor<std::chrono::seconds>(network_flush_time).count());
} else if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
del_ev_noblock(client->networking.event_read);
del_ev_noblock(client->networking.event_write);
del_ev_noblock(client->networking.event_throttle);
/* no direct timeout needed here, we're just flushing the disk */
this->enqueue_disk_io(client);
} else if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
del_ev_noblock(client->networking.event_read);
client->add_network_write_event_nolock(false);
/* max flush 10 seconds */
client->networking.disconnect_timeout = std::chrono::system_clock::now() + network_flush_time;
debugMessage(LOG_FT, "{} Disconnecting client. Flushing pending bytes (max {} seconds)", client->log_prefix(), std::chrono::floor<std::chrono::seconds>(network_flush_time).count());
}
} else {
del_ev_noblock(client->networking.event_read);
del_ev_noblock(client->networking.event_write);
del_ev_noblock(client->networking.event_throttle);
this->disconnect.notify_cv.notify_one();
}
#undef del_ev_noblock
}
void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
auto provider = reinterpret_cast<LocalFileTransfer*>(ptr_transfer);
while(provider->disconnect.active) {
{
std::unique_lock dlock{provider->disconnect.mutex};
provider->disconnect.notify_cv.wait_for(dlock, std::chrono::seconds{1});
}
/* run the disconnect worker at least once before exiting */
/* transfer statistics */
{
std::unique_lock tlock{provider->transfers_mutex};
auto transfers = provider->transfers_;
tlock.unlock();
for(const auto& transfer : transfers) {
switch(transfer->state) {
case FileClient::STATE_TRANSFERRING:
break;
case FileClient::STATE_DISCONNECTING:
if(transfer->transfer && transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD)
break; /* we're still transferring (sending data) */
default:
continue;
}
provider->report_transfer_statistics(transfer->shared_from_this());
}
}
{
std::deque<std::shared_ptr<Transfer>> timeouted_transfers{};
{
std::unique_lock tlock{provider->transfers_mutex};
auto now = std::chrono::system_clock::now();
std::copy_if(provider->pending_transfers.begin(), provider->pending_transfers.end(), std::back_inserter(timeouted_transfers), [&](const std::shared_ptr<Transfer>& t) {
return t->initialized_timestamp + std::chrono::seconds{100} < now; //FIXME: Decrease to 10 again!
});
provider->pending_transfers.erase(std::remove_if(provider->pending_transfers.begin(), provider->pending_transfers.end(), [&](const auto& t) {
return std::find(timeouted_transfers.begin(), timeouted_transfers.end(), t) != timeouted_transfers.end();
}), provider->pending_transfers.end());
}
for(const auto& pt : timeouted_transfers) {
if(auto callback{provider->callback_transfer_aborted}; callback)
callback(pt, { TransferError::TRANSFER_TIMEOUT, "" });
}
if(!timeouted_transfers.empty())
logMessage(LOG_FT, "Removed {} pending transfers because no request has been made for them.", timeouted_transfers.size());
}
{
std::deque<std::shared_ptr<FileClient>> disconnected_clients{};
{
std::unique_lock tlock{provider->transfers_mutex};
auto now = std::chrono::system_clock::now();
std::copy_if(provider->transfers_.begin(), provider->transfers_.end(), std::back_inserter(disconnected_clients), [&](const std::shared_ptr<FileClient>& t) {
std::shared_lock slock{t->state_mutex};
if(t->state == FileClient::STATE_DISCONNECTED) {
return true;
} else if(t->state == FileClient::STATE_AWAITING_KEY) {
return t->timings.connected + std::chrono::seconds{10} < now;
} else if(t->state == FileClient::STATE_TRANSFERRING) {
assert(t->transfer);
if(t->transfer->direction == Transfer::DIRECTION_UPLOAD) {
return false; //FIXME: Due to debugging reasons
return t->timings.last_read + std::chrono::seconds{5} < now;
} else if(t->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
return t->timings.last_write + std::chrono::seconds{5} < now;
}
} else if(t->state == FileClient::STATE_DISCONNECTING) {
if(t->networking.disconnect_timeout.time_since_epoch().count() > 0)
return t->networking.disconnect_timeout + std::chrono::seconds{5} < now;
return t->timings.disconnecting + std::chrono::seconds{30} < now;
}
return false;
});
provider->transfers_.erase(std::remove_if(provider->transfers_.begin(), provider->transfers_.end(), [&](const auto& t) {
return std::find(disconnected_clients.begin(), disconnected_clients.end(), t) != disconnected_clients.end();
}), provider->transfers_.end());
}
for(auto& client : disconnected_clients) {
switch(client->state) {
case FileClient::STATE_AWAITING_KEY:
logMessage(LOG_FT, "{} Received no key. Dropping client.", client->log_prefix());
break;
case FileClient::STATE_TRANSFERRING:
logMessage(LOG_FT, "{} Networking timeout. Dropping client", client->log_prefix());
if(auto callback{provider->callback_transfer_aborted}; callback)
callback(client->transfer, { TransferError::TRANSFER_TIMEOUT, "" });
break;
case FileClient::STATE_DISCONNECTING:
logMessage(LOG_FT, "{} Failed to flush connection. Dropping client", client->log_prefix());
break;
default:
break;
}
{
std::unique_lock slock{client->state_mutex};
client->state = FileClient::STATE_DISCONNECTED;
provider->finalize_file_io(client, slock);
provider->finalize_client_ssl(client);
provider->finalize_networking(client, slock);
}
debugMessage(LOG_FT, "{} Destroying transfer.", client->log_prefix());
}
}
}
}
void LocalFileTransfer::report_transfer_statistics(const std::shared_ptr<FileClient> &client) {
auto callback{this->callback_transfer_statistics};
if(!callback) return;
TransferStatistics stats{};
stats.network_bytes_send = client->statistics.network_bytes_send;
stats.network_bytes_received = client->statistics.network_bytes_received;
stats.file_bytes_transferred = client->statistics.file_bytes_transferred;
stats.delta_network_bytes_received = stats.network_bytes_received - std::exchange(client->statistics.last_network_bytes_received, stats.network_bytes_received);
stats.delta_network_bytes_send = stats.network_bytes_send - std::exchange(client->statistics.last_network_bytes_send, stats.network_bytes_send);
stats.delta_file_bytes_transferred = stats.file_bytes_transferred - std::exchange(client->statistics.last_file_bytes_transferred, stats.file_bytes_transferred);
stats.file_start_offset = client->transfer->file_offset;
stats.file_current_offset = client->statistics.file_bytes_transferred + client->transfer->file_offset;
stats.file_total_size = client->transfer->expected_file_size;
callback(client->transfer, stats);
}
-475
View File
@@ -1,475 +0,0 @@
//
// Created by WolverinDEV on 04/05/2020.
//
#include <cassert>
#include <event.h>
#include <experimental/filesystem>
#include <log/LogUtils.h>
#include "./LocalFileProvider.h"
#include "./duration_utils.h"
#include "LocalFileProvider.h"
using namespace ts::server::file;
using namespace ts::server::file::transfer;
namespace fs = std::experimental::filesystem;
DiskIOStartResult LocalFileTransfer::start_disk_io() {
assert(this->disk_io.state == DiskIOLoopState::STOPPED);
this->disk_io.state = DiskIOLoopState::RUNNING;
this->disk_io.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_disk_io, this);
return DiskIOStartResult::SUCCESS;
}
void LocalFileTransfer::shutdown_disk_io() {
if(this->disk_io.state == DiskIOLoopState::STOPPED) return;
this->disk_io.state = DiskIOLoopState::STOPPING;
{
std::unique_lock qlock{this->disk_io.queue_lock};
this->disk_io.notify_work_awaiting.notify_all();
while(this->disk_io.queue_head)
this->disk_io.notify_client_processed.wait_for(qlock, std::chrono::seconds{10});
if(this->disk_io.queue_head) {
logWarning(0, "Failed to flush disk IO. Force aborting.");
this->disk_io.state = DiskIOLoopState::FORCE_STOPPING;
this->disk_io.notify_work_awaiting.notify_all();
this->disk_io.notify_client_processed.wait(qlock);
}
}
if(this->disk_io.dispatch_thread.joinable())
this->disk_io.dispatch_thread.join();
this->disk_io.state = DiskIOLoopState::STOPPED;
}
void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) {
auto provider = reinterpret_cast<LocalFileTransfer*>(provider_ptr);
std::shared_ptr<FileClient> client{};
while(true) {
{
std::unique_lock qlock{provider->disk_io.queue_lock};
if(client) {
client->file.currently_processing = false;
provider->disk_io.notify_client_processed.notify_all();
client = nullptr;
}
provider->disk_io.notify_work_awaiting.wait(qlock, [&]{ return provider->disk_io.state != DiskIOLoopState::RUNNING || provider->disk_io.queue_head != nullptr; });
if(provider->disk_io.queue_head) {
client = provider->disk_io.queue_head->shared_from_this();
provider->disk_io.queue_head = provider->disk_io.queue_head->file.next_client;
if(!provider->disk_io.queue_head)
provider->disk_io.queue_tail = &provider->disk_io.queue_head;
}
if(provider->disk_io.state != DiskIOLoopState::RUNNING) {
if(provider->disk_io.state == DiskIOLoopState::STOPPING) {
if(!client)
break;
/* break only if all clients have been flushed */
} else {
/* force stopping without any flushing */
auto fclient = &*client;
while(fclient)
fclient = std::exchange(fclient->file.next_client, nullptr);
provider->disk_io.queue_head = nullptr;
provider->disk_io.queue_tail = &provider->disk_io.queue_head;
break;
}
}
if(!client)
continue;
client->file.currently_processing = true;
client->file.next_client = nullptr;
}
provider->execute_disk_io(client);
}
provider->disk_io.notify_client_processed.notify_all();
}
FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr<FileClient> &transfer) {
FileInitializeResult result{FileInitializeResult::SUCCESS};
assert(transfer->transfer);
std::shared_lock slock{transfer->state_mutex};
auto& file_data = transfer->file;
assert(!file_data.file_descriptor);
assert(!file_data.next_client);
{
unsigned int open_flags{0};
if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
open_flags = O_RDONLY;
std::error_code fs_error{};
if(file_data.absolute_path.empty() || !fs::exists(file_data.absolute_path, fs_error)) {
result = FileInitializeResult::FILE_DOES_NOT_EXISTS;
goto error_exit;
} else if(fs_error) {
logWarning(LOG_FT, "{} Failed to check for file existence of {}: {}/{}", transfer->log_prefix(), file_data.absolute_path, fs_error.value(), fs_error.message());
result = FileInitializeResult::FILE_SYSTEM_ERROR;
goto error_exit;
}
} else if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
open_flags = (unsigned) O_WRONLY | (unsigned) O_CREAT;
if(transfer->transfer->override_exiting)
open_flags |= (unsigned) O_TRUNC;
} else {
return FileInitializeResult::INVALID_TRANSFER_DIRECTION;
}
file_data.file_descriptor = open(file_data.absolute_path.c_str(), (int) open_flags, 0644);
if(file_data.file_descriptor <= 0) {
const auto errno_ = errno;
switch (errno_) {
case EACCES:
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
break;
case EDQUOT:
logWarning(LOG_FT, "{} Disk inode limit has been reached. Failed to start file transfer for file {}", transfer->log_prefix(), file_data.absolute_path);
result = FileInitializeResult::FILE_SYSTEM_ERROR;
break;
case EISDIR:
result = FileInitializeResult::FILE_IS_A_DIRECTORY;
break;
case EMFILE:
result = FileInitializeResult::PROCESS_FILE_LIMIT_REACHED;
break;
case ENFILE:
result = FileInitializeResult::SYSTEM_FILE_LIMIT_REACHED;
break;
case ETXTBSY:
result = FileInitializeResult::FILE_IS_BUSY;
break;
case EROFS:
result = FileInitializeResult::DISK_IS_READ_ONLY;
break;
default:
logWarning(LOG_FT, "{} Failed to start file transfer for file {}: {}/{}", transfer->log_prefix(), file_data.absolute_path, errno_, strerror(errno_));
result = FileInitializeResult::FILE_SYSTEM_ERROR;
break;
}
goto error_exit;
}
}
this->file_system_.lock_file(transfer->file.absolute_path);
transfer->file.file_locked = true;
if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
if(ftruncate(file_data.file_descriptor, transfer->transfer->expected_file_size) != 0) {
const auto errno_ = errno;
switch (errno_) {
case EACCES:
logWarning(LOG_FT, "{} File {} got inaccessible on truncating, but not on opening.", transfer->log_prefix(), file_data.absolute_path);
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
goto error_exit;
case EFBIG:
result = FileInitializeResult::FILE_TOO_LARGE;
goto error_exit;
case EIO:
logWarning(LOG_FT, "{} A disk IO error occurred while resizing file {}.", transfer->log_prefix(), file_data.absolute_path);
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
goto error_exit;
case EROFS:
logWarning(LOG_FT, "{} Failed to resize file {} because disk is in read only mode.", transfer->log_prefix(), file_data.absolute_path);
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
goto error_exit;
default:
debugMessage(LOG_FT, "{} Failed to truncate file {}: {}/{}. Trying to upload file anyways.", transfer->log_prefix(), file_data.absolute_path, errno_, strerror(errno_));
}
}
} else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
auto file_size = lseek(file_data.file_descriptor, 0, SEEK_END);
if(file_size != transfer->transfer->expected_file_size) {
logWarning(LOG_FT, "{} Expected target file to be of size {}, but file is actually of size {}", transfer->log_prefix(), transfer->transfer->expected_file_size, file_size);
result = FileInitializeResult::FILE_SIZE_MISMATCH;
goto error_exit;
}
}
{
auto new_pos = lseek(file_data.file_descriptor, transfer->transfer->file_offset, SEEK_SET);
if(new_pos < 0) {
logWarning(LOG_FT, "{} Failed to seek to target file offset ({}): {}/{}", transfer->log_prefix(), transfer->transfer->file_offset, errno, strerror(errno));
result = FileInitializeResult::FILE_SEEK_FAILED;
goto error_exit;
} else if(new_pos != transfer->transfer->file_offset) {
logWarning(LOG_FT, "{} File rw offset mismatch after seek. Expected {} but received {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
result = FileInitializeResult::FILE_SEEK_FAILED;
goto error_exit;
}
debugMessage(LOG_FT, "{} Seek to file offset {}. New actual offset is {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
}
return FileInitializeResult::SUCCESS;
error_exit:
if(std::exchange(transfer->file.file_locked, false))
this->file_system_.unlock_file(transfer->file.absolute_path);
if(file_data.file_descriptor > 0)
::close(file_data.file_descriptor);
file_data.file_descriptor = 0;
return result;
}
void LocalFileTransfer::finalize_file_io(const std::shared_ptr<FileClient> &transfer,
std::unique_lock<std::shared_mutex> &state_lock) {
assert(state_lock.owns_lock());
auto& file_data = transfer->file;
state_lock.unlock();
{
std::unique_lock dlock{this->disk_io.queue_lock};
while(true) {
if(file_data.currently_processing) {
this->disk_io.notify_client_processed.wait(dlock);
continue;
}
if(file_data.next_client) {
if(this->disk_io.queue_head == &*transfer) {
this->disk_io.queue_head = file_data.next_client;
if(!this->disk_io.queue_head)
this->disk_io.queue_tail = &this->disk_io.queue_head;
} else {
FileClient* head{this->disk_io.queue_head};
while(head->file.next_client != &*transfer) {
assert(head->file.next_client);
head = head->file.next_client;
}
head->file.next_client = file_data.next_client;
if(!file_data.next_client)
this->disk_io.queue_tail = &head->file.next_client;
}
file_data.next_client = nullptr;
}
break;
}
}
state_lock.lock();
if(std::exchange(file_data.file_locked, false))
this->file_system_.unlock_file(file_data.absolute_path);
if(file_data.file_descriptor > 0)
::close(file_data.file_descriptor);
file_data.file_descriptor = 0;
}
void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr<FileClient> &client) {
if(!client->file.file_descriptor)
return;
if(!client->transfer)
return;
if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
if(client->state != FileClient::STATE_TRANSFERRING)
return;
if(client->buffer.bytes > TRANSFER_MAX_CACHED_BYTES)
return;
} else if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
/* we don't do this check because this might be a flush instruction, where the buffer is actually zero bytes filled */
/*
if(client->buffer.bytes == 0)
return;
*/
}
std::lock_guard dlock{this->disk_io.queue_lock};
if(client->file.next_client || this->disk_io.queue_tail == &client->file.next_client)
return;
*this->disk_io.queue_tail = &*client;
this->disk_io.queue_tail = &client->file.next_client;
this->disk_io.notify_work_awaiting.notify_all();
}
void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &client) {
if(!client->transfer) return;
if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
Buffer* buffer{nullptr};
size_t buffer_left_size{0};
while(true) {
{
std::lock_guard block{client->buffer.mutex};
buffer = client->buffer.buffer_head;
buffer_left_size = client->buffer.bytes;
}
if(!buffer) {
assert(buffer_left_size == 0);
break;
}
assert(buffer->offset < buffer->length);
auto written = ::write(client->file.file_descriptor, buffer->data + buffer->offset, buffer->length - buffer->offset);
if(written <= 0) {
if(written == 0) {
/* EOF, how the hell is this event possible?! */
auto offset_written = client->statistics.disk_bytes_write + client->transfer->file_offset;
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
logError(LOG_FT, "{} Received unexpected file write EOF. EOF received at {} but expected {}. Actual file offset: {}. Closing transfer.",
client->log_prefix(), offset_written, client->transfer->expected_file_size, aoffset);
this->report_transfer_statistics(client);
if(auto callback{client->handle->callback_transfer_aborted}; callback)
callback(client->transfer, { TransferError::UNEXPECTED_DISK_EOF, strerror(errno) });
{
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client, slock, true);
}
} else {
if(errno == EAGAIN) {
//TODO: Timeout?
this->enqueue_disk_io(client);
break;
}
auto offset_written = client->statistics.disk_bytes_write + client->transfer->file_offset;
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
logError(LOG_FT, "{} Received write to disk IO error. Write pointer is at {} of {}. Actual file offset: {}. Closing transfer.",
client->log_prefix(), offset_written, client->transfer->expected_file_size, aoffset);
this->report_transfer_statistics(client);
if(auto callback{client->handle->callback_transfer_aborted}; callback)
callback(client->transfer, { TransferError::DISK_IO_ERROR, strerror(errno) });
{
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client, slock, true);
}
}
return;
} else {
buffer->offset += written;
assert(buffer->offset <= buffer->length);
if(buffer->length == buffer->offset) {
{
std::lock_guard block{client->buffer.mutex};
client->buffer.buffer_head = buffer->next;
if(!buffer->next)
client->buffer.buffer_tail = &client->buffer.buffer_head;
assert(client->buffer.bytes >= written);
client->buffer.bytes -= written;
buffer_left_size = client->buffer.bytes;
(void) buffer_left_size; /* trick my IDE here a bit */
}
free_buffer(buffer);
} else {
std::lock_guard block{client->buffer.mutex};
assert(client->buffer.bytes >= written);
client->buffer.bytes -= written;
buffer_left_size = client->buffer.bytes;
(void) buffer_left_size; /* trick my IDE here a bit */
}
client->statistics.disk_bytes_write += written;
}
}
if(buffer_left_size > 0)
this->enqueue_disk_io(client);
else if(client->state == FileClient::STATE_DISCONNECTING) {
debugMessage(LOG_FT, "{} Disk IO has been flushed.", client->log_prefix());
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client->shared_from_this(), slock, false);
}
if(client->state == FileClient::STATE_TRANSFERRING && buffer_left_size < TRANSFER_MAX_CACHED_BYTES / 2) {
if(client->buffer.buffering_stopped)
logMessage(LOG_FT, "{} Starting network read, buffer is capable for reading again.", client->log_prefix());
client->add_network_read_event(false);
}
} else if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
if(client->state == FileClient::STATE_DISCONNECTING) return;
while(true) {
constexpr auto buffer_capacity{4096};
char buffer[buffer_capacity];
auto read = ::read(client->file.file_descriptor, buffer, buffer_capacity);
if(read <= 0) {
if(read == 0) {
/* EOF */
auto offset_send = client->statistics.disk_bytes_read + client->transfer->file_offset;
if(client->transfer->expected_file_size == offset_send) {
debugMessage(LOG_FT, "{} Finished file reading. Flushing and disconnecting transfer. Reading took {} seconds.",
client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received));
} else {
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
logError(LOG_FT, "{} Received unexpected read EOF. EOF received at {} but expected {}. Actual file offset: {}. Disconnecting client.",
client->log_prefix(), offset_send, client->transfer->expected_file_size, aoffset);
this->report_transfer_statistics(client);
if(auto callback{client->handle->callback_transfer_aborted}; callback)
callback(client->transfer, { TransferError::UNEXPECTED_DISK_EOF, strerror(errno) });
}
{
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client, slock, true);
}
} else {
if(errno == EAGAIN) {
this->enqueue_disk_io(client);
return;
}
logWarning(LOG_FT, "{} Failed to read from file {} ({}/{}). Aborting transfer.", client->log_prefix(), client->file.absolute_path, errno, strerror(errno));
this->report_transfer_statistics(client);
if(auto callback{client->handle->callback_transfer_aborted}; callback)
callback(client->transfer, { TransferError::DISK_IO_ERROR, strerror(errno) });
{
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client, slock, true);
}
}
return;
} else {
auto buffer_full = client->send_file_bytes(buffer, read);
client->statistics.disk_bytes_read += read;
client->statistics.file_bytes_transferred += read;
std::shared_lock slock{client->state_mutex};
if(buffer_full) {
logMessage(LOG_FT, "{} Stopping buffering from disk. Buffer full ({}bytes)", client->log_prefix(), client->buffer.bytes);
break;
}
/* we've stuff to write again, yeahr */
client->add_network_write_event(false);
}
}
} else {
logError(LOG_FT, "{} Disk IO scheduled, but transfer direction is unknown.", client->log_prefix());
}
}
File diff suppressed because it is too large Load Diff
-283
View File
@@ -1,283 +0,0 @@
//
// Created by WolverinDEV on 29/04/2020.
//
#include "clnpath.h"
#include <cstring>
#define MAX_PATH_ELEMENTS 128 /* Number of levels of directory */
void ts_clnpath(char *path)
{
char *src;
char *dst;
char c;
int slash = 0;
/* Convert multiple adjacent slashes to single slash */
src = dst = path;
while ((c = *dst++ = *src++) != '\0')
{
if (c == '/')
{
slash = 1;
while (*src == '/')
src++;
}
}
if (slash == 0)
return;
/* Remove "./" from "./xxx" but leave "./" alone. */
/* Remove "/." from "xxx/." but reduce "/." to "/". */
/* Reduce "xxx/./yyy" to "xxx/yyy" */
src = dst = (*path == '/') ? path + 1 : path;
while (src[0] == '.' && src[1] == '/' && src[2] != '\0')
src += 2;
while ((c = *dst++ = *src++) != '\0')
{
if (c == '/' && src[0] == '.' && (src[1] == '\0' || src[1] == '/'))
{
src++;
dst--;
}
}
if (path[0] == '/' && path[1] == '.' &&
(path[2] == '\0' || (path[2] == '/' && path[3] == '\0')))
path[1] = '\0';
/* Remove trailing slash, if any. There is at most one! */
/* dst is pointing one beyond terminating null */
if ((dst -= 2) > path && *dst == '/')
*dst++ = '\0';
}
bool ts_strequal(const char* a, const char* b) {
return strcmp(a, b) == 0;
}
int ts_tokenise(char* ostring, const char* del, char** result, int max_tokens) {
int num_tokens{0};
char* token, *string, *tofree;
tofree = string = strdup(ostring);
while ((token = strsep(&string, del)) != nullptr) {
result[num_tokens++] = strdup(token);
if(num_tokens > max_tokens)
break;
}
free(tofree);
return num_tokens;
}
/*
** clnpath2() is not part of the basic clnpath() function because it can
** change the meaning of a path name if there are symbolic links on the
** system. For example, suppose /usr/tmp is a symbolic link to /var/tmp.
** If the user supplies /usr/tmp/../abcdef as the directory name, clnpath
** would transform that to /usr/abcdef, not to /var/abcdef which is what
** the kernel would interpret it as.
*/
void ts_clnpath2(char *path)
{
char *token[MAX_PATH_ELEMENTS], *otoken[MAX_PATH_ELEMENTS];
int ntok, ontok;
ts_clnpath(path);
/* Reduce "<name>/.." to "/" */
ntok = ontok = ts_tokenise(path, "/", otoken, MAX_PATH_ELEMENTS);
memcpy(token, otoken, sizeof(char*) * ntok);
if (ntok > 1) {
for (int i = 0; i < ntok - 1; i++)
{
if (!ts_strequal(token[i], "..") && ts_strequal(token[i + 1], ".."))
{
if (*token[i] == '\0')
continue;
while (i < ntok - 1)
{
token[i] = token[i + 2];
i++;
}
ntok -= 2;
i = -1; /* Restart enclosing for loop */
}
}
}
/* Reassemble string */
char *dst = path;
if (ntok == 0)
{
*dst++ = '.';
*dst = '\0';
}
else
{
if (token[0][0] == '\0')
{
int i;
for (i = 1; i < ntok && ts_strequal(token[i], ".."); i++)
;
if (i > 1)
{
int j;
for (j = 1; i < ntok; i++)
token[j++] = token[i];
ntok = j;
}
}
if (ntok == 1 && token[0][0] == '\0')
{
*dst++ = '/';
*dst = '\0';
}
else
{
for (int i = 0; i < ntok; i++)
{
char *src = token[i];
while ((*dst++ = *src++) != '\0')
;
*(dst - 1) = '/';
}
*(dst - 1) = '\0';
}
}
for(int i{0}; i < ontok; i++)
::free(otoken[i]);
}
std::string clnpath(const std::string_view& data) {
std::string result{data};
ts_clnpath2(result.data());
auto index = result.find((char) 0);
if(index != std::string::npos)
result.resize(index);
return result;
}
#ifdef CLN_EXEC
typedef struct p1_test_case
{
const char *input;
const char *output;
} p1_test_case;
/* This stress tests the cleaning, concentrating on the boundaries. */
static const p1_test_case p1_tests[] =
{
{ "/", "/", },
{ "//", "/", },
{ "///", "/", },
{ "/.", "/", },
{ "/./", "/", },
{ "/./.", "/", },
{ "/././.profile", "/.profile", },
{ "./", ".", },
{ "./.", ".", },
{ "././", ".", },
{ "./././.profile", ".profile", },
{ "abc/.", "abc", },
{ "abc/./def", "abc/def", },
{ "./abc", "abc", },
{ "//abcd///./abcd////", "/abcd/abcd", },
{ "//abcd///././../defg///ddd//.", "/abcd/../defg/ddd", },
{ "/abcd/./../././defg/./././ddd", "/abcd/../defg/ddd", },
{ "//abcd//././../defg///ddd//.///", "/abcd/../defg/ddd", },
/* Most of these are minimal interest in phase 1 */
{ "/usr/tmp/clnpath.c", "/usr/tmp/clnpath.c", },
{ "/usr/tmp/", "/usr/tmp", },
{ "/bin/..", "/bin/..", },
{ "bin/..", "bin/..", },
{ "/bin/.", "/bin", },
{ "sub/directory", "sub/directory", },
{ "sub/directory/file", "sub/directory/file", },
{ "/part1/part2/../.././../", "/part1/part2/../../..", },
{ "/.././../usr//.//bin/./cc", "/../../usr/bin/cc", },
};
static void p1_tester(const void *data)
{
const p1_test_case *test = (const p1_test_case *)data;
char buffer[256];
strcpy(buffer, test->input);
ts_clnpath(buffer);
if (strcmp(buffer, test->output) == 0)
printf("<<%s>> cleans to <<%s>>\n", test->input, buffer);
else
{
fprintf(stderr, "<<%s>> - unexpected output from clnpath()\n", test->input);
fprintf(stderr, "Wanted <<%s>>\n", test->output);
fprintf(stderr, "Actual <<%s>>\n", buffer);
}
}
typedef struct p2_test_case
{
const char *input;
const char *output;
} p2_test_case;
static const p2_test_case p2_tests[] =
{
{ "/abcd/../defg/ddd", "/defg/ddd" },
{ "/bin/..", "/" },
{ "bin/..", "." },
{ "/usr/bin/..", "/usr" },
{ "/usr/bin/../..", "/" },
{ "usr/bin/../..", "." },
{ "../part/of/../the/way", "../part/the/way" },
{ "/../part/of/../the/way", "/part/the/way" },
{ "part1/part2/../../part3", "part3" },
{ "part1/part2/../../../part3", "../part3" },
{ "/part1/part2/../../../part3", "/part3" },
{ "/part1/part2/../../../", "/" },
{ "/../../usr/bin/cc", "/usr/bin/cc" },
{ "../../usr/bin/cc", "../../usr/bin/cc" },
{ "part1/./part2/../../part3", "part3" },
{ "./part1/part2/../../../part3", "../part3" },
{ "/part1/part2/.././../../part3", "/part3" },
{ "/part1/part2/../.././../", "/" },
{ "/.././..//./usr///bin/cc/", "/usr/bin/cc" },
{nullptr, nullptr}
};
static void p2_tester(const void *data)
{
auto test = (const p2_test_case *)data;
char buffer[256];
strcpy(buffer, test->input);
ts_clnpath2(buffer);
if (strcmp(buffer, test->output) == 0)
printf("<<%s>> cleans to <<%s>>\n", test->input, buffer);
else
{
fprintf(stderr, "<<%s>> - unexpected output from clnpath2()\n", test->input);
fprintf(stderr, "Wanted <<%s>>\n", test->output);
fprintf(stderr, "Actual <<%s>>\n", buffer);
}
}
int main() {
for(const auto& test : p1_tests) {
if(!test.input) break;
p1_tester(&test);
}
printf("------------------------------\n");
for(const auto& test : p2_tests) {
if(!test.input) break;
p2_tester(&test);
}
}
#endif
-6
View File
@@ -1,6 +0,0 @@
#pragma once
#include <string>
#include <string_view>
extern std::string clnpath(const std::string_view&);
-35
View File
@@ -1,35 +0,0 @@
#pragma once
template <typename Rep, typename Per>
inline std::string duration_to_string(std::chrono::duration<Rep, Per> ms) {
std::string result{};
{
auto hours = std::chrono::duration_cast<std::chrono::hours>(ms);
if(hours.count())
result += std::to_string(hours.count()) + " hours ";
ms -= hours;
}
{
auto minutes = std::chrono::duration_cast<std::chrono::minutes>(ms);
if(minutes.count())
result += std::to_string(minutes.count()) + " minutes ";
ms -= minutes;
}
{
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(ms);
if(seconds.count())
result += std::to_string(seconds.count()) + " seconds ";
ms -= seconds;
}
if(result.empty()) {
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(ms);
if(milliseconds.count())
result = std::to_string(milliseconds.count()) + " milliseconds ";
}
if(result.empty()) {
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(ms);
result = std::to_string(microseconds.count()) + " microseconds ";
}
return result.substr(0, result.length() - 1);
}
-181
View File
@@ -1,181 +0,0 @@
//
// Created by WolverinDEV on 29/04/2020.
//
#include <files/FileServer.h>
#include <log/LogUtils.h>
#include <experimental/filesystem>
#include <local_server/clnpath.h>
#include <event2/thread.h>
namespace fs = std::experimental::filesystem;
using namespace ts::server;
struct Nothing {};
template <typename ErrorType, typename ResponseType>
inline void print_fs_response(const std::string& message, const std::shared_ptr<file::ExecuteResponse<file::filesystem::DetailedError<ErrorType>, ResponseType>>& response) {
if(response->status == file::ExecuteStatus::ERROR)
logError(LOG_FT, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message);
else if(response->status == file::ExecuteStatus::SUCCESS)
logMessage(LOG_FT, "{}: success", message);
else
logWarning(LOG_FT, "Unknown response state ({})!", (int) response->status);
}
template <typename ErrorType, typename ResponseType>
inline void print_ft_response(const std::string& message, const std::shared_ptr<file::ExecuteResponse<ErrorType, ResponseType>>& response) {
if(response->status == file::ExecuteStatus::ERROR)
logError(LOG_FT, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message);
else if(response->status == file::ExecuteStatus::SUCCESS)
logMessage(LOG_FT, "{}: success", message);
else
logWarning(LOG_FT, "Unknown response state ({})!", (int) response->status);
}
inline void print_query(const std::string& message, const file::filesystem::AbstractProvider::directory_query_response_t& response) {
if(response.status == file::ExecuteStatus::ERROR)
logError(LOG_FT, "{}: {} => {}", message, (int) response.error().error_type, response.error().error_message);
else if(response.status == file::ExecuteStatus::SUCCESS) {
const auto& entries = response.response();
logMessage(LOG_FT, "{}: Found {} entries", message, entries.size());
for(auto& entry : entries) {
if(entry.type == file::filesystem::DirectoryEntry::FILE)
logMessage(LOG_FT, " - File {}", entry.name);
else if(entry.type == file::filesystem::DirectoryEntry::DIRECTORY)
logMessage(LOG_FT, " - Directory {}", entry.name);
else
logMessage(LOG_FT, " - Unknown {}", entry.name);
logMessage(LOG_FT, " Write timestamp: {}", std::chrono::floor<std::chrono::seconds>(entry.modified_at.time_since_epoch()).count());
logMessage(LOG_FT, " Size: {}", entry.size);
}
} else
logWarning(LOG_FT, "{}: Unknown response state ({})!", message, (int) response.status);
}
int main() {
evthread_use_pthreads();
auto log_config = std::make_shared<logger::LoggerConfig>();
log_config->terminalLevel = spdlog::level::trace;
logger::setup(log_config);
std::string error{};
if(!file::initialize(error)) {
logError(LOG_FT, "Failed to initialize file server: {}", error);
return 0;
}
logMessage(LOG_FT, "File server started");
auto instance = file::server();
#if 0
auto& fs = instance->file_system();
{
auto response = fs.initialize_server(0);
response->wait();
print_fs_response("Server init result", response);
if(response->status != file::ExecuteStatus::SUCCESS)
return 0;
}
{
auto response = fs.create_channel_directory(0, 2, "/");
response->wait();
print_fs_response("Channel dir create A", response);
}
{
auto response = fs.create_channel_directory(0, 2, "/test-folder/");
response->wait();
print_fs_response("Channel dir create B", response);
}
{
auto response = fs.create_channel_directory(0, 2, "../test-folder/");
response->wait();
print_fs_response("Channel dir create C", response);
}
{
auto response = fs.create_channel_directory(0, 2, "./test-folder/../test-folder-2");
response->wait();
print_fs_response("Channel dir create D", response);
}
{
auto response = fs.query_channel_directory(0, 2, "/");
response->wait();
print_query("Channel query", *response);
}
{
auto response = fs.query_icon_directory(0);
response->wait();
print_query("Icons", *response);
}
{
auto response = fs.query_avatar_directory(0);
response->wait();
print_query("Avatars", *response);
}
{
auto response = fs.rename_channel_file(0, 2, "./test-folder/../test-folder-2", "./test-folder/../test-folder-3");
response->wait();
print_fs_response("Folder rename A", response);
}
{
auto response = fs.rename_channel_file(0, 2, "./test-folder/../test-folder-3", "./test-folder/../test-folder-2");
response->wait();
print_fs_response("Folder rename B", response);
}
#endif
#if 1
auto& ft = instance->file_transfer();
ft.callback_transfer_finished = [](const std::shared_ptr<file::transfer::Transfer>& transfer) {
logMessage(0, "Transfer finished");
};
ft.callback_transfer_started = [](const std::shared_ptr<file::transfer::Transfer>& transfer) {
logMessage(0, "Transfer started");
};
ft.callback_transfer_aborted = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const file::transfer::TransferError& error) {
logMessage(0, "Transfer aborted ({}/{})", (int) error.error_type, error.error_message);
};
ft.callback_transfer_statistics = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const file::transfer::TransferStatistics& stats) {
logMessage(0, "Transfer stats. New file bytes: {}, delta bytes send {}", stats.delta_file_bytes_transferred, stats.delta_network_bytes_send);
};
{
auto response = ft.initialize_channel_transfer(file::transfer::Transfer::DIRECTION_UPLOAD, 0, 2, {
"test2.txt",
false,
4,
120,
32
});
response->wait();
print_ft_response("Download test.txt", response);
if(response->succeeded())
logMessage(LOG_FT, "Download key: {}", std::string{response->response()->transfer_key, TRANSFER_KEY_LENGTH});
}
#endif
std::this_thread::sleep_for(std::chrono::seconds{120});
//TODO: Test file locking
file::finalize();
return 0;
}
-2
View File
@@ -1,2 +0,0 @@
Hello World
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+1 -1
View File
@@ -533,7 +533,7 @@ std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> DatabaseHa
bool DatabaseHandler::register_license_upgrade(license_key_id_t old_key_id, license_key_id_t new_key_id,
const std::chrono::system_clock::time_point &begin_timestamp, const std::chrono::system_clock::time_point &end_timestamp, const std::string &license_key) {
auto upgrade_id = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
auto upgrade_id = std::chrono::system_clock::now().time_since_epoch().count();
auto sql_result = sql::command(this->sql(), "INSERT INTO `license_upgrades` (`upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license`) VALUES"
"(:upgrade_id, :old_key_id, :new_key_id, :timestamp_begin, :timestamp_end, 1, 0, :license)",
variable{":upgrade_id", upgrade_id},
+9 -30
View File
@@ -134,19 +134,16 @@ void LicenseServer::handleEventWrite(int fd, short, void* ptrServer) {
if(!client) return;
buffer::RawBuffer* write_buffer{nullptr};
std::unique_lock write_lock(client->network.write_queue_lock);
while(true) { //TODO: May add some kind of timeout?
std::lock_guard lock(client->network.write_queue_lock);
write_buffer = TAILQ_FIRST(&client->network.write_queue);
if(!write_buffer) return;
auto writtenBytes = send(fd, &write_buffer->buffer[write_buffer->index], write_buffer->length - write_buffer->index, 0);
if(writtenBytes <= 0) {
write_lock.unlock();
if(writtenBytes == -1 && errno == EAGAIN)
return;
logError(LOG_LICENSE_CONTROLL, "Invalid write. Disconnecting remote client. Message: {}/{}", errno, strerror(errno));
server->unregisterClient(client);
return;
} else {
write_buffer->index += writtenBytes;
}
@@ -190,7 +187,7 @@ void ConnectedClient::init() {
TAILQ_INIT(&network.write_queue);
}
void ConnectedClient::uninit(bool blocking) {
void ConnectedClient::uninit() {
{
lock_guard queue_lock{this->network.write_queue_lock};
ts::buffer::RawBuffer* buffer;
@@ -204,21 +201,11 @@ void ConnectedClient::uninit(bool blocking) {
close(this->network.fileDescriptor);
network.fileDescriptor = 0;
}
if(this->network.readEvent) event_del(this->network.readEvent);
this->network.readEvent = nullptr;
std::unique_lock elock{this->network.event_mutex};
auto read_event = std::exchange(this->network.readEvent, nullptr);
auto write_event = std::exchange(this->network.writeEvent, nullptr);
elock.unlock();
if(blocking) {
if(read_event) event_del_block(read_event);
if(write_event) event_del_block(write_event);
} else {
if(read_event) event_del_noblock(read_event);
if(write_event) event_del_noblock(write_event);
}
if(read_event) event_free(read_event);
if(write_event) event_free(write_event);
if(this->network.writeEvent) event_del(this->network.writeEvent);
this->network.writeEvent = nullptr;
}
void LicenseServer::handleEventRead(int fd, short, void* ptrServer) {
@@ -234,20 +221,12 @@ void LicenseServer::handleEventRead(int fd, short, void* ptrServer) {
if(read < 0){
if(errno == EWOULDBLOCK) return;
logError(LOG_LICENSE_CONTROLL, "Invalid read. Disconnecting remote client. Message: {}/{}", errno, strerror(errno));
{
std::lock_guard elock{client->network.event_mutex};
if(client->network.readEvent)
event_del_noblock(client->network.readEvent);
}
event_del_noblock(client->network.readEvent);
server->closeConnection(client);
return;
} else if(read == 0) {
logMessage(LOG_LICENSE_CONTROLL, "[CLIENT][" + client->address() + "] Received EOF for client. Removing client.");
{
std::lock_guard elock{client->network.event_mutex};
if(client->network.readEvent)
event_del_noblock(client->network.readEvent);
}
event_del_noblock(client->network.readEvent);
server->closeConnection(client);
return;
}
@@ -333,7 +312,7 @@ void LicenseServer::unregisterClient(const std::shared_ptr<ConnectedClient> &cli
std::lock_guard state_lock{client->protocol.state_lock};
client->protocol.state = protocol::UNCONNECTED;
}
client->uninit(this_thread::get_id() != this->event_base_dispatch.get_id());
client->uninit();
}
void LicenseServer::cleanup_clients() {
+1 -3
View File
@@ -31,8 +31,6 @@ namespace license {
struct {
sockaddr_in remoteAddr;
int fileDescriptor = 0;
std::mutex event_mutex{};
event* readEvent = nullptr; /* protected via state_lock (check state and the use these variables) */
event* writeEvent = nullptr;
@@ -59,7 +57,7 @@ namespace license {
bool invalid_license = false;
void init();
void uninit(bool /* blocking */);
void uninit();
void sendPacket(const protocol::packet&);
inline std::string address() { return inet_ntoa(network.remoteAddr.sin_addr); }
+2 -8
View File
@@ -188,7 +188,7 @@ bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client,
logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license has been deleted! Shutting down server!", client->address());
} else {
fill_info(response.mutable_license_info(), info, remote_license->data.licenceKey);
auto is_invalid = !info->isNotExpired();
auto is_invalid = !info->isValid();
if(is_invalid) {
response.set_invalid_reason("license is invalid");
response.set_valid(false);
@@ -198,15 +198,9 @@ bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client,
}
}
this->manager->logRequest(remote_license->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid());
} else {
/* shall never happen, by default each server has the default license */
} else {
response.set_valid(true);
}
if(pkt.has_memory_valid() && !pkt.memory_valid()) {
response.set_invalid_reason("server memory seems to be invalid");
response.set_valid(false);
logError(LOG_GENERAL, "Server {} has patched license memory!", client->address());
}
if(client->protocol.version == 2) {
if(response.valid())
+10 -18
View File
@@ -297,21 +297,17 @@ void WebStatistics::handleEventWrite(int file_descriptor, short, void* ptr_serve
}
std::unique_lock elock(client->execute_lock);
std::lock_guard<std::recursive_mutex> lock(client->execute_lock);
if(client->buffer_write.empty()) return;
auto& buffer = client->buffer_write.front();
auto written = send(file_descriptor, buffer.data(), buffer.length(), MSG_DONTWAIT | MSG_NOSIGNAL);
if(written < 0){
elock.unlock();
if(errno == EWOULDBLOCK) return;
logError(LOG_LICENSE_WEB, "[{}] Invalid write: {}/{}. Closing connection.", client->client_prefix(), errno, strerror(errno));
server->close_connection(client);
return;
} else if(written == 0) {
elock.unlock();
logError(LOG_LICENSE_WEB, "[{}] Invalid write (eof). Closing connection", client->client_prefix());
server->close_connection(client);
return;
@@ -338,20 +334,17 @@ void WebStatistics::close_connection(const std::shared_ptr<license::web::WebStat
else; //TODO Error handling?
}
std::unique_lock elock(client->execute_lock);
auto event_read = std::exchange(client->event_read, nullptr);
auto event_write = std::exchange(client->event_write, nullptr);
elock.unlock();
if(event_read) {
event_del(event_read);
event_free(event_read);
std::lock_guard<std::recursive_mutex> lock(client->execute_lock);
if(client->event_read) {
event_del(client->event_read);
event_free(client->event_read);
client->event_read = nullptr;
}
if(event_write) {
event_del(event_write);
event_free(event_write);
if(client->event_write) {
event_del(client->event_write);
event_free(client->event_write);
client->event_write = nullptr;
}
elock.lock();
if(client->file_descriptor > 0) {
if(shutdown(client->file_descriptor, SHUT_RDWR) < 0); //TODO error handling
if(close(client->file_descriptor) < 0); //TODO error handling
@@ -360,7 +353,6 @@ void WebStatistics::close_connection(const std::shared_ptr<license::web::WebStat
if(client->pipe_websocket)
client->pipe_websocket = nullptr;
if(client->pipe_ssl) {
client->pipe_ssl->finalize();
client->pipe_ssl = nullptr;
+1
View File
@@ -92,6 +92,7 @@ namespace license {
std::deque<std::shared_ptr<Client>> clients;
threads::ThreadPool scheduler{1, "WebStatistics #"};
protected:
static void handleEventAccept(int, short, void*);
static void handleEventRead(int, short, void*);
+1 -1
View File
@@ -8,7 +8,7 @@
#include "license.h"
namespace license::client {
class LicenseServerClient : public std::enable_shared_from_this<LicenseServerClient> {
class LicenseServerClient {
public:
enum ConnectionState {
CONNECTING,
+1 -1
View File
@@ -348,7 +348,7 @@ namespace license {
bool deleted{false};
uint32_t upgrade_id{0};
inline bool isNotExpired() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); }
inline bool isValid() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); }
};
extern std::shared_ptr<License> readLocalLicence(const std::string &, std::string &);
@@ -41,7 +41,6 @@ message ServerValidation {
required bool license_info = 2;
optional bytes license = 3;
optional ServerInfo info = 4; //Change somewhere to required but its currently for legacy support
optional bool memory_valid = 5;
}
message LicenseResponse {
+9 -27
View File
@@ -7,7 +7,6 @@
#include <event.h>
#include <ThreadPool/ThreadHelper.h>
#include <misc/endianness.h>
#include <shared/include/license/client.h>
#include "shared/include/license/client.h"
#include "crypt.h"
@@ -43,16 +42,11 @@ LicenseServerClient::LicenseServerClient(const sockaddr_in &address, int pversio
}
LicenseServerClient::~LicenseServerClient() {
const auto is_event_loop = this->network.event_dispatch.get_id() == std::this_thread::get_id();
{
std::unique_lock slock{this->connection_lock};
this->connection_state = ConnectionState::UNCONNECTED;
}
this->cleanup_network_resources(); /* force cleanup ignoring the previous state */
if(is_event_loop) this->network.event_dispatch.detach();
this->close_connection();
if(this->buffers.read)
Buffer::free(this->buffers.read);
threads::save_join(this->network.event_dispatch, false);
}
bool LicenseServerClient::start_connection(std::string &error) {
@@ -94,25 +88,14 @@ bool LicenseServerClient::start_connection(std::string &error) {
this->network.event_base = event_base_new();
this->network.event_read = event_new(this->network.event_base, this->network.file_descriptor, EV_READ | EV_PERSIST, [](int, short e, void* _this) {
auto client = reinterpret_cast<LicenseServerClient*>(_this);
auto client_ref = client->weak_from_this().lock();
if(!client_ref) {
logCritical(0, "Network callback for expired client (E011)!");
return;
}
client->callback_read(e);
client_ref.reset();
}, this);
this->network.event_write = event_new(this->network.event_base, this->network.file_descriptor, EV_WRITE, [](int, short e, void* _this) {
auto client = reinterpret_cast<LicenseServerClient*>(_this);
auto client_ref = client->weak_from_this().lock();
if(!client_ref) {
logCritical(0, "Network callback for expired client (E012)!");
return;
}
client->callback_write(e);
client_ref.reset();
}, this);
event_dispatch_spawned = true;
this->network.event_dispatch = std::thread([&] {
signal(SIGPIPE, SIG_IGN);
@@ -131,6 +114,10 @@ bool LicenseServerClient::start_connection(std::string &error) {
return true;
error_cleanup:
this->cleanup_network_resources();
if(!event_dispatch_spawned) {
event_base_free(this->network.event_base);
this->network.event_base = nullptr;
}
this->connection_state = ConnectionState::UNCONNECTED;
return false;
}
@@ -177,7 +164,7 @@ void LicenseServerClient::cleanup_network_resources() {
auto buffer = TAILQ_FIRST(&this->buffers.write);
while(buffer) {
auto next = TAILQ_NEXT(buffer, tail);
Buffer::free(buffer);
Buffer::free(next);
buffer = next;
}
TAILQ_INIT(&this->buffers.write);
@@ -256,11 +243,8 @@ void LicenseServerClient::callback_write(short events) {
}
if(events & EV_WRITE) {
if(this->connection_state == ConnectionState::CONNECTING) {
if(this->connection_state == ConnectionState::CONNECTING)
this->callback_socket_connected();
if(this->connection_state == ConnectionState::UNCONNECTED) /* state may change in the callback */
return;
}
ssize_t written_bytes{0};
@@ -381,8 +365,6 @@ void LicenseServerClient::handle_data(void *recv_buffer, size_t length) {
if(buffer_length < header->length + sizeof(protocol::packet_header)) return;
this->handle_raw_packet(header->packetId, buffer_ptr + buffer_offset + sizeof(protocol::packet_header), header->length);
if(this->connection_state == ConnectionState::UNCONNECTED) return; /* state may change while we're handing the packet */
buffer_offset += header->length + sizeof(protocol::packet_header);
buffer_length -= header->length + sizeof(protocol::packet_header);
}
+1 -1
Submodule music updated: 8e1ce32ae0...ad24c38923
+37 -48
View File
@@ -4,6 +4,7 @@ project(TeaSpeak-Server)
set(CMAKE_VERBOSE_MAKEFILE ON)
#--allow-multiple-definition
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive -Wall -Wno-reorder -Wno-sign-compare -static-libgcc -static-libstdc++ -g -Wl,--no-whole-archive -pthread ${MEMORY_DEBUG_FLAGS} -Werror=return-type")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,--unresolved-symbols=ignore-all")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3")
@@ -32,8 +33,8 @@ add_definitions(-DUSE_BORINGSSL)
#3 = PRIVATE
option(BUILD_TYPE "Sets the build type" OFF)
option(BUILD_TYPE_NAME "Sets the build type name" OFF)
option(COMPILE_WEB_CLIENT "Enable/Disable the web cleint future" OFF)
#set(COMPILE_WEB_CLIENT "ON")
option(COMPILE_WEB_CLIENT "Enable/Disable the web client future" OFF)
set(COMPILE_WEB_CLIENT "OFF") #FIXME!
set(CMAKE_VERBOSE_MAKEFILE ON)
set(SERVER_SOURCE_FILES
@@ -43,13 +44,11 @@ set(SERVER_SOURCE_FILES
# MySQLLibSSLFix.c
src/client/ConnectedClient.cpp
src/client/voice/PrecomputedPuzzles.cpp
src/server/udp-server/PrecomputedPuzzles.cpp
src/client/voice/VoiceClient.cpp
src/client/voice/VoiceClientHandschake.cpp
src/client/voice/VoiceClientCommandHandler.cpp
src/client/voice/VoiceClientPacketHandler.cpp
src/client/voice/VoiceClientView.cpp
src/client/voice/PacketStatistics.cpp
src/TS3ServerClientManager.cpp
src/VirtualServer.cpp
src/TS3ServerHeartbeat.cpp
@@ -62,11 +61,10 @@ set(SERVER_SOURCE_FILES
src/client/command_handler/client.cpp
src/client/command_handler/server.cpp
src/client/command_handler/misc.cpp
src/client/command_handler/bulk_parsers.cpp
src/client/ConnectedClientNotifyHandler.cpp
src/VirtualServerManager.cpp
src/server/file/LocalFileServer.cpp
src/server/file/FileServer.cpp
src/channel/ServerChannel.cpp
src/channel/ClientChannelView.cpp
src/client/file/FileClient.cpp
@@ -83,6 +81,7 @@ set(SERVER_SOURCE_FILES
src/client/query/QueryClientCommands.cpp
src/client/query/QueryClientNotify.cpp
src/manager/IpListManager.cpp
src/ConnectionStatistics.cpp
@@ -137,16 +136,32 @@ set(SERVER_SOURCE_FILES
src/weblist/WebListManager.cpp
src/weblist/TeamSpeakWebClient.cpp
src/snapshots/permission.cpp
src/snapshots/client.cpp
src/snapshots/channel.cpp
src/snapshots/server.cpp
src/snapshots/groups.cpp
src/snapshots/deploy.cpp
src/manager/ConversationManager.cpp
src/client/SpeakingClientHandshake.cpp
src/client/command_handler/music.cpp src/client/command_handler/file.cpp)
src/client/command_handler/music.cpp
src/client/command_handler/file.cpp
src/vserver/VirtualServerBase.cpp
src/vserver/VirtualServer.cpp
src/vserver/DefaultServer.cpp
src/services/PermissionsService.cpp
src/services/ClientChannelService.cpp
src/music/PlaylistPermissions.cpp
src/lincense/LicenseService.cpp
src/groups/GroupManager.cpp
src/groups/GroupAssignmentManager.cpp
src/groups/Group.cpp
src/services/VirtualServerInformation.cpp
src/vserver/VirtualServerManager.cpp
src/services/VirtualServerBroadcastService.cpp
src/server/udp-server/UDPServer.cpp
src/client/voice/PacketEncoder.cpp
src/client/voice/PacketDecoder.cpp
src/client/voice/PingHandler.cpp
src/client/query/QueryClientConnection.cpp
)
if (COMPILE_WEB_CLIENT)
add_definitions(-DCOMPILE_WEB_CLIENT)
@@ -159,7 +174,7 @@ if (COMPILE_WEB_CLIENT)
src/client/web/WSWebClient.cpp
src/client/web/SampleHandler.cpp
src/client/web/VoiceBridge.cpp
src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h src/lincense/LicenseService.cpp src/lincense/LicenseService.h)
)
endif ()
add_executable(PermHelper helpers/permgen.cpp)
@@ -243,7 +258,7 @@ target_link_libraries(PermMapHelper
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
SET(CPACK_PACKAGE_VERSION_MINOR "4")
SET(CPACK_PACKAGE_VERSION_PATCH "14")
SET(CPACK_PACKAGE_VERSION_PATCH "10")
if (BUILD_TYPE_NAME EQUAL OFF)
SET(CPACK_PACKAGE_VERSION_DATA "beta")
elseif (BUILD_TYPE_NAME STREQUAL "")
@@ -275,7 +290,6 @@ target_link_libraries(TeaSpeakServer
#Require a so
sqlite3
DataPipes::rtc::shared
breakpad::static
protobuf::libprotobuf
@@ -289,14 +303,10 @@ target_link_libraries(TeaSpeakServer
)
if (COMPILE_WEB_CLIENT)
file(GLOB GLIB20_ARCHS ${glib20_DIR}/lib/*)
list(LENGTH GLIB20_ARCHS GLIB20_ARCHS_LENGTH)
if (NOT ${GLIB20_ARCHS_LENGTH} EQUAL 1)
message(FATAL_ERROR "Missing arch specific folder for glib2.0 in ${glib20_DIR}. Found ${GLIB20_ARCHS_LENGTH} directories, expected 1.")
endif ()
list(GET GLIB20_ARCHS 0 GLIB20_ARCH_DIR)
target_link_libraries(TeaSpeakServer ${GLIB20_ARCH_DIR}/libffi.so.7 ${nice_DIR}/lib/libnice.so.10)
endif ()
target_link_libraries(TeaSpeakServer DataPipes::rtc::shared ${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10)
else()
target_link_libraries(TeaSpeakServer DataPipes::core::shared)
endif()
# include_directories(${LIBRARY_PATH}/boringssl/include/)
target_link_libraries(TeaSpeakServer
@@ -321,25 +331,4 @@ if (NOT DISABLE_JEMALLOC)
jemalloc
)
add_definitions(-DHAVE_JEMALLOC)
endif ()
add_executable(Snapshots-Permissions-Test src/snapshots/permission.cpp tests/snapshots/permission.cpp)
target_link_libraries(Snapshots-Permissions-Test PUBLIC
TeaSpeak
CXXTerminal::static #Static
${StringVariable_LIBRARIES_STATIC}
${YAML_CPP_LIBRARIES}
pthread
stdc++fs
libevent::core libevent::pthreads
#Require a so
sqlite3
DataPipes::rtc::shared
tomcrypt::static
tommath::static
${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10
)
target_include_directories(Snapshots-Permissions-Test PUBLIC ${CMAKE_SOURCE_DIR}/server/src/)
endif ()
+1
View File
@@ -0,0 +1 @@
../repro/env/geoloc/
+1
View File
@@ -0,0 +1 @@
../../music/bin/providers/
+1
View File
@@ -0,0 +1 @@
../repro/env/resources/
+1 -1
View File
@@ -6,7 +6,7 @@
#include <functional> /* required from permission manager */
#include "log/LogUtils.h"
#include "Definitions.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
using namespace std;
using namespace ts;
+1 -1
View File
@@ -6,7 +6,7 @@
#include <functional> /* required from permission manager */
#include "log/LogUtils.h"
#include "Definitions.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
using namespace std;
using namespace ts;
+68 -40
View File
@@ -1,58 +1,86 @@
CommandResult handleCommandClientUpdate(Command&);
CommandResult handleCommandClientEdit(Command&);
CommandResult handleCommandClientEdit(Command&, const std::shared_ptr<ConnectedClient>& /* target */);
CommandResult handleCommandClientMove(Command&);
CommandResult handleCommandClientGetVariables(Command&);
CommandResult handleCommandClientKick(Command&);
CommandResult handleCommandClientPoke(Command&);
General lock order:
- Client execute lock
- Server state lock
- Server channel tree
- Client channel tree
CommandResult handleCommandChannelSubscribe(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelSubscribeAll(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelUnsubscribe(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelUnsubscribeAll(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelCreate(Command&); write lock server channel tree => iterate clients lock (write)
CommandResult handleCommandChannelDelete(Command&); write lock server channel tree => iterate clients lock (write)
CommandResult handleCommandChannelEdit(Command&); write lock server channel tree
CommandResult handleCommandChannelGetDescription(Command&); read lock: server channel tree
CommandResult handleCommandChannelMove(Command&); write lock server channel tree
When executing a command:
Lock order:
- Client execute lock
- Client state lock
- Server state lock (Server should not try to change state while a client is executing something)
Notes:
The server might be null or the default server.
This must be checked via the given macro!
CommandResult handleCommandChannelAddPerm(Command&); read lock: server channel tree => all clients write lock
CommandResult handleCommandChannelDelPerm(Command&); read lock: server channel tree => all clients write lock
Disconnect/unregister a client:
- Lock client execute lock
- Lock server state
- Unregister client from server and set server variable to nullptr
- Underlying server (UDP, Web, etc. disconnect)
CommandResult handleCommandChannelGroupDel(Command&); read lock: server channel tree => all clients in the group should not be allowed to switch a channel
CommandResult handleCommandSetClientChannelGroup(Command&); read lock: server channel tree => client should not be allowed to switch channel
Client channel tree connect/mode/disconnect:
- Lock client execute lock
- Lock server channel tree in write mode
- Lock client channel tree in write mode
- Move client & notify (notify required all clients to have their channel tree locked in shared mode)
CommandResult handleCommandPluginCmd(Command&);
Command lock overview:
CommandResult handleCommandClientUpdate(Command&);
CommandResult handleCommandClientEdit(Command&);
CommandResult handleCommandClientEdit(Command&, const std::shared_ptr<ConnectedClient>& /* target */);
CommandResult handleCommandClientMove(Command&);
CommandResult handleCommandClientGetVariables(Command&);
CommandResult handleCommandClientKick(Command&);
CommandResult handleCommandClientPoke(Command&);
CommandResult handleCommandClientMute(Command&);
CommandResult handleCommandClientUnmute(Command&);
CommandResult handleCommandChannelSubscribe(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelSubscribeAll(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelUnsubscribe(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelUnsubscribeAll(Command&); read lock: server channel tree => client lock write
CommandResult handleCommandChannelCreate(Command&); write lock server channel tree => iterate clients lock (write)
CommandResult handleCommandChannelDelete(Command&); write lock server channel tree => iterate clients lock (write)
CommandResult handleCommandChannelEdit(Command&); write lock server channel tree
CommandResult handleCommandChannelGetDescription(Command&); read lock: server channel tree
CommandResult handleCommandChannelMove(Command&); write lock server channel tree
//Original from query but still reachable for all
CommandResult handleCommandClientList(Command&);
CommandResult handleCommandChannelAddPerm(Command&); read lock: server channel tree => all clients write lock
CommandResult handleCommandChannelDelPerm(Command&); read lock: server channel tree => all clients write lock
CommandResult handleCommandClientFind(Command&);
CommandResult handleCommandClientInfo(Command&);
CommandResult handleCommandChannelGroupDel(Command&); read lock: server channel tree => all clients in the group should not be allowed to switch a channel
CommandResult handleCommandSetClientChannelGroup(Command&); read lock: server channel tree => client should not be allowed to switch channel
CommandResult handleCommandVerifyChannelPassword(Command&); read lock: server channel tree
CommandResult handleCommandPluginCmd(Command&);
handleCommandChannelFind read lock: server channel tree
handleCommandChannelInfo read lock: server channel tree
CommandResult handleCommandClientMute(Command&);
CommandResult handleCommandClientUnmute(Command&);
General command handling: client_command_lock
Ensure that only one command at time will be handeled
//Original from query but still reachable for all
CommandResult handleCommandClientList(Command&);
CommandResult handleCommandClientFind(Command&);
CommandResult handleCommandClientInfo(Command&);
CommandResult handleCommandVerifyChannelPassword(Command&); read lock: server channel tree
handleCommandChannelFind read lock: server channel tree
handleCommandChannelInfo read lock: server channel tree
General command handling: client_command_lock
Ensure that only one command at time will be handeled
Read access server channel tree: read lock channel_tree_lock
Write access server channel tree: lock channel_tree_lock
Write access client channel tree: read lock channel_tree_lock => lock client channel tree
if we write to the server channel tree no client should have their channel tree updated
Read access client channel tree: no lock required
Note: the server channel tree should not be accessed!
Read access server channel tree: read lock channel_tree_lock
Write access server channel tree: lock channel_tree_lock
Write access client channel tree: read lock channel_tree_lock => lock client channel tree
if we write to the server channel tree no client should have their channel tree updated
Read access client channel tree: no lock required
Note: the server channel tree should not be accessed!
Move client acts like access server channel tree: write lock channel_tree_lock => for each client write lock their tree
Move client acts like access server channel tree: write lock channel_tree_lock => for each client write lock their tree
TODO: Some kind of perm channel lock
TODO: Fix handleCommandChannelEdit
Test: Channel hide & show with clients! Multible clients as well!
Test: Channel hide & show with clients! Multiple clients as well!
+14 -33
View File
@@ -10,7 +10,7 @@
#include "src/VirtualServer.h"
#include "src/InstanceHandler.h"
#include "src/server/QueryServer.h"
#include "src/server/file/LocalFileServer.h"
#include "src/server/file/FileServer.h"
#include "src/terminal/CommandHandler.h"
#include "src/client/InternalClient.h"
#include "src/SignalHandler.h"
@@ -44,10 +44,11 @@ extern void testTomMath();
#define DB_NAME "TeaData.sqlite"
#endif
#include <regex>
#include <codecvt>
#include "src/client/music/internal_provider/channel_replay/ChannelProvider.h"
class CLIParser {
class CLIParser{
public:
CLIParser (int &argc, char **argv){
for (int i = 1; i < argc; i++)
@@ -128,6 +129,7 @@ int main(int argc, char** argv) {
terminal::install();
if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
}
assert(ts::property::impl::validateUnique());
if(arguments.cmdOptionExists("--help") || arguments.cmdOptionExists("-h")) {
#define HELP_FMT " {} {} | {}"
@@ -263,7 +265,7 @@ int main(int argc, char** argv) {
if(!cfgErrors.empty()){
logErrorFmt(true, LOG_GENERAL, "Could not load configuration. Errors: (" + to_string(cfgErrors.size()) + ")");
for(const auto& entry : cfgErrors)
logErrorFmt(true, LOG_GENERAL, " - {}", entry);
logError(true, LOG_GENERAL, " - {}", entry);
logErrorFmt(true, LOG_GENERAL, "Stopping server...");
goto stopApp;
}
@@ -307,32 +309,6 @@ int main(int argc, char** argv) {
logMessageFmt(true, LOG_GENERAL, strobf("[]---------------------------------------------------------[]").string());
}
{
rlimit rlimit{0, 0};
//forum.teaspeak.de/index.php?threads/2570/
constexpr auto seek_help_message = "Fore more help visit the forum and read this thread (https://forum.teaspeak.de/index.php?threads/2570/).";
if(getrlimit(RLIMIT_NOFILE, &rlimit) != 0) {
//prlimit -n4096 -p pid_of_process
logWarningFmt(true, LOG_INSTANCE, "Failed to get open file rlimit ({}). Please ensure its over 16384.", strerror(errno));
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
} else {
const auto original = rlimit.rlim_cur;
rlimit.rlim_cur = std::max(rlimit.rlim_cur, std::min(rlimit.rlim_max, (rlim_t) 16384));
if(original != rlimit.rlim_cur) {
if(setrlimit(RLIMIT_NOFILE, &rlimit) != 0) {
logErrorFmt(true, LOG_INSTANCE, "Failed to set open file rlimit to {} ({}). Please ensure its over 16384.", rlimit.rlim_cur, strerror(errno));
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
goto rlimit_updates;
}
}
if(rlimit.rlim_cur < 16384) {
logWarningFmt(true, LOG_INSTANCE, "Open file rlimit is bellow 16384 ({}). Please increase the system file descriptor limits.", rlimit.rlim_cur);
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
}
}
rlimit_updates:;
}
logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true));
logMessage(LOG_GENERAL, "Starting music providers");
@@ -395,13 +371,18 @@ int main(int argc, char** argv) {
auto password = arguments.cmdOptionExists("-q") ? arguments.get_option("-q") : arguments.get_option("--set_query_password");
if(!password.empty()) {
logMessageFmt(true, LOG_GENERAL, "Updating server admin query password to \"{}\"", password);
auto account = serverInstance->getQueryServer()->find_query_account_by_name("serveradmin");
if(!account) {
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!");
} else {
auto accounts = serverInstance->getQueryServer()->find_query_accounts_by_unique_id(serverInstance->getInitialServerAdmin()->getUid());
bool found = false;
for(const auto& account : accounts) {
if(account->bound_server != 0) continue;
if(!serverInstance->getQueryServer()->change_query_password(account, password)) {
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! (Internal error)");
}
found = true;
break;
}
if(!found) {
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!");
}
}
}
+9
View File
@@ -0,0 +1,9 @@
The general namespace prefix is ts::
TeaSpeak - Server: ts::server
Basic: ts::server
Sub-Server:
Query: ts::server::server::query
Voice: ts::server::server::udp
File: ts::server::server::file
Web: ts::server::server::web
+1 -34
View File
@@ -6,39 +6,6 @@ if [[ -z "${BUILD_PATH}" ]]; then
exit 1
fi
rm buildVersion.txt
cp env/buildVersion.txt .
rm -r env
mkdir env
cd env
[[ $? -ne 0 ]] && {
echo "Failed to create the env"
exit 1
}
cp -rf ../../../git-teaspeak/default_files/{certs,commanddocs,geoloc,resources,*.sh} .
[[ $? -ne 0 ]] && {
echo "Failed to copy env"
exit 1
}
cp -rf ../../../music/bin/providers .
[[ $? -ne 0 ]] && {
echo "Failed to copy providers"
exit 1
}
#
cp ../../environment/TeaSpeakServer .
[[ $? -ne 0 ]] && {
echo "Failed to copy server"
exit 1
}
cd ..
mv buildVersion.txt env/buildVersion.txt
[[ $? -ne 0 ]] && {
echo "Failed to move the build version back"
exit 1
}
./generate_version.sh "${BUILD_PATH}" || {
echo "Failed to generate version! ($?)"
exit 1
@@ -57,4 +24,4 @@ mv buildVersion.txt env/buildVersion.txt
./deploy_build.sh "${BUILD_PATH}" || {
echo "Failed to deploy package! ($?)"
exit 1
}
}
+1
View File
@@ -0,0 +1 @@
../../environment/TeaSpeakServer
Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../../../git-teaspeak/default_files/certs/
+1
View File
@@ -0,0 +1 @@
../../../git-teaspeak/default_files/commanddocs/
Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../../../git-teaspeak/default_files/geoloc/
+1
View File
@@ -0,0 +1 @@
../../../git-teaspeak/default_files/install_music.sh
+1
View File
@@ -0,0 +1 @@
../../../music/bin/providers/
+1
View File
@@ -0,0 +1 @@
../../../git-teaspeak/default_files/resources/
+1
View File
@@ -0,0 +1 @@
../../../git-teaspeak/default_files/tealoop.sh
+1
View File
@@ -0,0 +1 @@
../../../git-teaspeak/default_files/teastart.sh
+1
View File
@@ -0,0 +1 @@
../../../git-teaspeak/default_files/teastart_autorestart.sh
+1
View File
@@ -0,0 +1 @@
../../../git-teaspeak/default_files/teastart_minimal.sh
+3 -3
View File
@@ -3,7 +3,7 @@
#Required libraries:
# "libssl.so"
# "libcrypto.so"
# "libDataPipes-Rtc-Shared.so"
# "libDataPipes.so"
# "libjemalloc.so.2"
# "libsqlite3.so.0"
# "libTeaMusic.so"
@@ -50,8 +50,8 @@ query_system_link "libcrypto.so.1.1"
cp "${library_path}" . || { echo "failed to copy libcrypto.so.1.1"; exit 1; }
# Setting up DataPipes
library_path=$(realpath "${library_base}/DataPipes/${build_path}/lib/libDataPipes-Rtc-Shared.so")
cp "$library_path" . || { echo "failed to copy libDataPipes-Rtc-Shared.so"; exit 1; }
library_path=$(realpath "${library_base}/DataPipes/${build_path}/lib/libDataPipes-RTC.so")
cp "$library_path" . || { echo "failed to copy libDataPipes-RTC.so"; exit 1; }
_dp_path="$library_path"
# Setting up Sqlite3
+33 -116
View File
@@ -52,18 +52,8 @@ bool config::server::badges::allow_overwolf;
bool config::server::authentication::name;
bool config::server::clients::teamspeak;
std::string config::server::clients::extra_welcome_message_teamspeak;
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teamspeak;
bool config::server::clients::teaweb;
std::string config::server::clients::extra_welcome_message_teaweb;
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaweb;
bool config::server::clients::teaspeak;
std::string config::server::clients::extra_welcome_message_teaspeak;
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaspeak;
bool config::server::clients::ignore_max_clone_permissions;
uint16_t config::voice::default_voice_port;
size_t config::voice::DefaultPuzzlePrecomputeSize;
@@ -125,12 +115,8 @@ bool config::web::activated;
std::deque<std::tuple<std::string, std::string, std::string>> config::web::ssl::certificates;
uint16_t config::web::webrtc_port_max;
uint16_t config::web::webrtc_port_min;
bool config::web::stun_enabled;
std::string config::web::stun_host;
uint16_t config::web::stun_port;
deque<string> config::web::ice_servers;
bool config::web::enable_upnp;
bool config::web::udp_enabled;
bool config::web::tcp_enabled;
size_t config::log::vs_size;
std::string config::log::path;
@@ -482,6 +468,7 @@ vector<string> config::parseConfig(const std::string& path) {
goto license_parsing;
}
/*
if(!config::license->isValid()) {
if(config::license->data.type == license::LicenseType::INVALID) {
errors.emplace_back(strobf("Give license isn't valid!").string());
@@ -491,18 +478,38 @@ vector<string> config::parseConfig(const std::string& path) {
logErrorFmt(true, LOG_GENERAL, strobf("The given license isn't valid!").string());
logErrorFmt(true, LOG_GENERAL, strobf("Falling back to the default license.").string());
teaspeak_license = "none";
goto license_parsing;
}
*/
}
{
auto bindings = create_bindings();
read_bindings(config, bindings);
build_comments(comments, bindings);
for(const auto& entry : config::web::ice_servers) {
auto dp = entry.find(':');
if(dp == string::npos) {
errors.emplace_back("Invalid ice server entry! Missing port");
continue;
}
auto host = entry.substr(0, dp);
auto port = entry.substr(dp + 1);
if(port.find_last_not_of("0123456789") != string::npos) {
errors.push_back("Invalid ice server entry! Invalid port (" + port + ")");
continue;
}
try {
stoi(port);
} catch(std::exception& ex) {
errors.push_back("Invalid ice server entry! Invalid port (" + port + ")");
}
}
}
auto currentVersion = config::server::default_version();
auto currentVersion = strobf("TeaSpeak ").string() + build::version()->string(true);
if(currentVersion != config::server::DefaultServerVersion) {
auto ref = config::server::DefaultServerVersion;
try {
@@ -1100,18 +1107,17 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
BIND_GROUP(query);
{
CREATE_BINDING("nl_char", 0);
BIND_STRING(config::query::newlineCharacter, "\n");
BIND_STRING(config::query::newlineCharacter, "\r\n");
ADD_DESCRIPTION("Change the query newline character");
ADD_NOTE("NOTE: TS3 - Compatible bots may require \"\n\r\"");
}
{
CREATE_BINDING("motd", 0);
BIND_STRING(config::query::motd, "TeaSpeak\nWelcome on the TeaSpeak ServerQuery interface.\n");
BIND_STRING(config::query::motd, "TeaSpeak\r\nWelcome on the TeaSpeak ServerQuery interface.\r\n");
ADD_DESCRIPTION("The query welcome message");
ADD_NOTE("If not like TeamSpeak then some applications may not recognize the Query");
ADD_NOTE("Default TeamSpeak 3 MOTD:");
ADD_NOTE("TS3\n\rWelcome to the TeamSpeak 3 ServerQuery interface, type \"help\" for a list of commands and \"help <command>\" for information on a specific command.\n\r");
ADD_NOTE("TS3\r\nWelcome to the TeamSpeak 3 ServerQuery interface, type \"help\" for a list of commands and \"help <command>\" for information on a specific command.\r\n");
ADD_NOTE("NOTE: Sometimes you have to append one \r\n more!");
}
{
@@ -1311,60 +1317,13 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
}
}
{
using WelcomeMessageType = config::server::clients::WelcomeMessageType;
BIND_GROUP(clients);
/* TeamSpeak */
{
CREATE_BINDING("teamspeak", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teamspeak, true);
ADD_DESCRIPTION("Allow/disallow the TeamSpeak 3 client to join the server.");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teamspeak_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::extra_welcome_message_teamspeak, "");
ADD_DESCRIPTION("Add an extra welcome message for TeamSpeak client users");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teamspeak_message_type", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teamspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
ADD_DESCRIPTION("The welcome message type modes");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
ADD_NOTE_RELOADABLE();
}
/* TeaSpeak */
/*
{
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teaspeak, true);
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
ADD_NOTE_RELOADABLE();
}
*/
config::server::clients::teaspeak = true;
{
CREATE_BINDING("teaspeak_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::extra_welcome_message_teaspeak, "");
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Client users");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaspeak_message_type", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
ADD_DESCRIPTION("The welcome message type modes");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
ADD_NOTE_RELOADABLE();
}
/* TeaWeb */
{
CREATE_BINDING("teaweb", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teaweb, true);
@@ -1372,26 +1331,9 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaweb_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::extra_welcome_message_teaweb, "");
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Web client users");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaweb_message_type", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaweb, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
ADD_DESCRIPTION("The welcome message type modes");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("ignore_max_clone_permissions", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::ignore_max_clone_permissions, false);
ADD_DESCRIPTION("Allows you to disable the permission checks for i_client_max_clones_uid, i_client_max_clones_ip and i_client_max_clones_hwid");
ADD_SENSITIVE();
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teaspeak, true);
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
ADD_NOTE_RELOADABLE();
}
}
@@ -1484,34 +1426,9 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)");
}
{
CREATE_BINDING("webrtc.stun.enabled", 0);
BIND_INTEGRAL(config::web::stun_enabled, false, false, true);
ADD_DESCRIPTION("Whatever to use a STUN server");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.stun.host", 0);
BIND_STRING(config::web::stun_host, "stun.l.google.com");
ADD_DESCRIPTION("Stun hostname");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.stun.port", 0);
BIND_INTEGRAL(config::web::stun_port, 19302, 1, 0xFFFF);
ADD_DESCRIPTION("Port of the stun server");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.udp", 0);
BIND_INTEGRAL(config::web::udp_enabled, true, false, true);
ADD_DESCRIPTION("Enable UDP for theweb client");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.tcp", 0);
BIND_INTEGRAL(config::web::tcp_enabled, true, false, true);
ADD_DESCRIPTION("Enable TCP for theweb client");
ADD_NOTE_RELOADABLE();
CREATE_BINDING("webrtc.ice", 0);
BIND_VECTOR(config::web::ice_servers, {"stun.l.google.com:19302"});
ADD_DESCRIPTION("A list of possible offered ice servers");
}
}
{
+1 -33
View File
@@ -7,10 +7,8 @@
#undef byte
#endif
#include <spdlog/common.h>
#include <misc/strobf.h>
#include "geo/GeoLocation.h"
#include "../../license/shared/include/license/license.h"
#include "build.h"
namespace YAML {
class Node;
@@ -88,35 +86,12 @@ namespace ts::config {
}
namespace clients {
enum WelcomeMessageType {
WELCOME_MESSAGE_TYPE_MIN,
WELCOME_MESSAGE_TYPE_NONE = WELCOME_MESSAGE_TYPE_MIN,
WELCOME_MESSAGE_TYPE_CHAT,
WELCOME_MESSAGE_TYPE_POKE,
WELCOME_MESSAGE_TYPE_MAX
};
extern bool teamspeak;
extern std::string extra_welcome_message_teamspeak;
extern WelcomeMessageType extra_welcome_message_type_teamspeak;
extern bool teaspeak;
extern std::string extra_welcome_message_teaspeak;
extern WelcomeMessageType extra_welcome_message_type_teaspeak;
extern bool teaweb;
extern std::string extra_welcome_message_teaweb;
extern WelcomeMessageType extra_welcome_message_type_teaweb;
extern bool ignore_max_clone_permissions;
}
extern ssize_t max_virtual_server;
__attribute__((always_inline)) inline std::string default_version() { return strobf("TeaSpeak ").string() + build::version()->string(true); }
__attribute__((always_inline)) inline bool check_server_version_with_license() {
return default_version() == DefaultServerVersion || (license->isPremium() && license->isValid());
}
}
namespace voice {
@@ -210,15 +185,8 @@ namespace ts::config {
extern uint16_t webrtc_port_min;
extern uint16_t webrtc_port_max;
extern std::deque<std::string> ice_servers;
extern bool enable_upnp;
extern bool stun_enabled;
extern std::string stun_host;
extern uint16_t stun_port;
extern bool tcp_enabled;
extern bool udp_enabled;
}
namespace threads {
+268 -46
View File
@@ -3,9 +3,8 @@
//
#include <misc/memtracker.h>
#include <utility>
#include "ConnectionStatistics.h"
#include "VirtualServer.h"
using namespace std;
using namespace std::chrono;
@@ -14,98 +13,321 @@ using namespace ts::server;
using namespace ts::stats;
using namespace ts::protocol;
ConnectionStatistics::ConnectionStatistics(shared_ptr<ConnectionStatistics> handle) : handle(std::move(handle)) {
ConnectionStatistics::ConnectionStatistics(const shared_ptr<ConnectionStatistics>& handle, bool properties) : handle(handle) {
memtrack::allocated<ConnectionStatistics>(this);
if(properties) {
this->properties = make_shared<Properties>(); //TODO load etc?
this->properties->register_property_type<property::ConnectionProperties>();
}
/*
this->properties->registerProperty("connection_packets_sent_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_sent_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_sent_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_sent_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_sent_last_second_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_sent_last_minute_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_received_last_second_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_received_last_minute_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bandwidth_sent", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bandwidth_received", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bytes_sent_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bytes_received_total", 0, PROP_STATISTIC);
*/
}
ConnectionStatistics::~ConnectionStatistics() {
memtrack::freed<ConnectionStatistics>(this);
{
lock_guard lock(this->history_lock_incoming);
for(auto entry : this->history_incoming)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
for(auto entry : this->history_file_incoming)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_incoming.clear();
this->history_file_incoming.clear();
}
{
lock_guard lock(this->history_lock_outgoing);
for(auto entry : this->history_outgoing)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
for(auto entry : this->history_file_outgoing)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_outgoing.clear();
this->history_file_outgoing.clear();
}
}
std::shared_ptr<Properties> ConnectionStatistics::statistics() {
return this->properties;
}
void ConnectionStatistics::logIncomingPacket(const category::value &category, size_t size) {
assert(category >= 0 && category <= 2);
this->statistics_second_current.connection_bytes_received[category] += size;
this->statistics_second_current.connection_packets_received[category] += 1;
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = uint16_t(size);
this->_log_incoming_packet(info_entry, category);
}
void ConnectionStatistics::_log_incoming_packet(ts::stats::StatisticEntry *info_entry, int8_t index) {
if(index >= 0 && index <= 3) {
this->connection_packets_received[index] ++;
this->connection_bytes_received[index] += info_entry->size;
}
this->connection_packets_received[0] ++;
this->connection_bytes_received[0] += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_incoming);
this->history_incoming.push_back(info_entry);
}
if(this->handle)
this->handle->logIncomingPacket(category, size);
this->handle->_log_incoming_packet(info_entry, index);
}
void ConnectionStatistics::logOutgoingPacket(const category::value &category, size_t size) {
assert(category >= 0 && category <= 2);
this->statistics_second_current.connection_bytes_sent[category] += size;
this->statistics_second_current.connection_packets_sent[category] += 1;
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = uint16_t(size);
this->_log_outgoing_packet(info_entry, category);
}
void ConnectionStatistics::_log_outgoing_packet(ts::stats::StatisticEntry *info_entry, int8_t index) {
if(index >= 0 && index <= 3) {
this->connection_packets_sent[index] ++;
this->connection_bytes_sent[index] += info_entry->size;
}
this->connection_packets_sent[0] ++;
this->connection_bytes_sent[0] += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_outgoing);
this->history_outgoing.push_back(info_entry);
}
if(this->handle)
this->handle->logOutgoingPacket(category, size);
this->handle->_log_outgoing_packet(info_entry, index);
}
/* file transfer */
void ConnectionStatistics::logFileTransferIn(uint32_t bytes) {
this->statistics_second_current.file_bytes_received += bytes;
this->file_bytes_received += bytes;
void ConnectionStatistics::logFileTransferIn(uint64_t bytes) {
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = bytes;
if(this->handle)
this->handle->logFileTransferIn(bytes);
this->_log_incoming_file_packet(info_entry);
}
void ConnectionStatistics::logFileTransferOut(uint32_t bytes) {
this->statistics_second_current.file_bytes_sent += bytes;
this->file_bytes_sent += bytes;
void ConnectionStatistics::_log_incoming_file_packet(ts::stats::StatisticEntry *info_entry) {
this->file_bytes_received += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_incoming);
this->history_file_incoming.push_back(info_entry);
}
if(this->handle)
this->handle->logFileTransferOut(bytes);
this->handle->_log_incoming_file_packet(info_entry);
}
void ConnectionStatistics::logFileTransferOut(uint64_t bytes) {
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = bytes;
this->_log_outgoing_file_packet(info_entry);
}
void ConnectionStatistics::_log_outgoing_file_packet(ts::stats::StatisticEntry *info_entry) {
this->file_bytes_sent += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_outgoing);
this->history_file_outgoing.push_back(info_entry);
}
if(this->handle)
this->handle->_log_outgoing_file_packet(info_entry);
}
void ConnectionStatistics::tick() {
auto now = std::chrono::system_clock::now();
auto time_difference = this->last_second_tick.time_since_epoch().count() > 0 ? now - this->last_second_tick : std::chrono::seconds{1};
if(time_difference >= std::chrono::seconds{1}) {
BandwidthEntry<uint32_t> current{};
current.atomic_exchange(this->statistics_second_current);
StatisticEntry* entry;
{
auto timeout_min = system_clock::now() - minutes(1);
auto period_ms = std::chrono::floor<std::chrono::milliseconds>(time_difference).count();
auto current_normalized = current.mul<long double>(1000.0 / period_ms);
lock_guard lock(this->history_lock_incoming);
this->statistics_second = this->statistics_second.mul<long double>(.2) + current_normalized.mul<long double>(.8);
this->total_statistics += current;
while(!this->history_incoming.empty() && (entry = this->history_incoming[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
auto current_second = std::chrono::floor<std::chrono::seconds>(now.time_since_epoch()).count();
if(statistics_minute_offset == 0)
statistics_minute_offset = current_second;
this->history_incoming.pop_front();
}
/* fill all "lost" with the current bandwidth as well */
while(statistics_minute_offset <= current_second)
this->statistics_minute[statistics_minute_offset++ % this->statistics_minute.size()] = current_normalized;
this->last_second_tick = now;
while(!this->history_file_incoming.empty() && (entry = this->history_file_incoming[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_file_incoming.pop_front();
}
}
{
auto timeout_min = system_clock::now() - minutes(1);
lock_guard lock(this->history_lock_outgoing);
while(!this->history_outgoing.empty() && (entry = this->history_outgoing[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_outgoing.pop_front();
}
while(!this->history_file_outgoing.empty() && (entry = this->history_file_outgoing[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_file_outgoing.pop_front();
}
}
if(this->properties) {
auto& _properties = *this->properties;
#define M(type, index) \
_properties[property::CONNECTION_BYTES_SENT_ ##type] = (uint64_t) this->connection_bytes_sent[index]; \
_properties[property::CONNECTION_PACKETS_SENT_ ##type] = (uint64_t) this->connection_packets_sent[index]; \
_properties[property::CONNECTION_BYTES_RECEIVED_ ##type] = (uint64_t) this->connection_bytes_received[index]; \
_properties[property::CONNECTION_PACKETS_RECEIVED_ ##type] = (uint64_t) this->connection_packets_received[index]; \
M(TOTAL, 0);
M(CONTROL, 1);
M(KEEPALIVE, 2);
M(SPEECH, 3);
_properties[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL] = (uint64_t) this->file_bytes_received;
_properties[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL] = (uint64_t) this->file_bytes_sent;
_properties[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL] = (uint64_t) this->file_bytes_received;
_properties[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL] = (uint64_t) this->file_bytes_sent;
}
}
BandwidthEntry<uint32_t> ConnectionStatistics::minute_stats() const {
BandwidthEntry<uint32_t> result{};
for(const auto& second : this->statistics_minute)
result += second;
return result.mul<uint32_t>(1. / (double) this->statistics_minute.size());
DataSummery ConnectionStatistics::dataReport() {
DataSummery report{};
auto minTimeout = system_clock::now() - seconds(1);
{
lock_guard lock(this->history_lock_incoming);
for(const auto& elm : this->history_incoming){
if(elm->timestamp >= minTimeout) {
report.recv_second += elm->size;
}
report.recv_minute += elm->size;
}
for(const auto& elm : this->history_file_incoming) {
report.file_recv += elm->size;
}
}
{
lock_guard lock(this->history_lock_outgoing);
for(const auto& elm : this->history_outgoing){
if(elm->timestamp >= minTimeout) {
report.send_second += elm->size;
}
report.send_minute += elm->size;
}
for(const auto& elm : this->history_file_outgoing) {
report.file_send += elm->size;
}
}
report.recv_minute /= 60;
report.send_minute /= 60;
return report;
}
FileTransferStatistics ConnectionStatistics::file_stats() {
FileTransferStatistics result{};
FullReport ConnectionStatistics::full_report() {
FullReport report{};
result.bytes_received = this->file_bytes_received;
result.bytes_sent = this->file_bytes_sent;
for(size_t index = 0 ; index < 4; index++) {
report.connection_bytes_sent[index] = (uint64_t) this->connection_bytes_sent[index];
report.connection_packets_sent[index] = (uint64_t) this->connection_packets_sent[index];
report.connection_bytes_received[index] = (uint64_t) this->connection_bytes_received[index];
report.connection_packets_received[index] = (uint64_t) this->connection_packets_received[index];
}
return result;
report.file_bytes_sent = this->file_bytes_sent;
report.file_bytes_received = this->file_bytes_received;
return report;
}
std::pair<uint64_t, uint64_t> ConnectionStatistics::mark_file_bytes() {
std::pair<uint64_t, uint64_t> result;
{
lock_guard lock(this->history_lock_incoming);
if(this->mark_file_bytes_received < this->file_bytes_received)
result.second = this->file_bytes_received - this->mark_file_bytes_received;
this->mark_file_bytes_received = (uint64_t) this->file_bytes_received;
}
{
lock_guard lock(this->history_lock_outgoing);
if(this->mark_file_bytes_sent < this->file_bytes_sent)
result.first = this->file_bytes_sent - this->mark_file_bytes_sent;
this->mark_file_bytes_sent = (uint64_t) this->file_bytes_sent;
+66 -102
View File
@@ -11,97 +11,41 @@ namespace ts {
}
namespace stats {
template <typename value_t>
struct BandwidthEntry {
std::array<value_t, 3> connection_packets_sent{};
std::array<value_t, 3> connection_bytes_sent{};
std::array<value_t, 3> connection_packets_received{};
std::array<value_t, 3> connection_bytes_received{};
value_t file_bytes_sent{0};
value_t file_bytes_received{0};
template <typename other_type>
inline BandwidthEntry& operator=(const BandwidthEntry<other_type>& other) {
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
this->connection_packets_sent[index] = other.connection_packets_sent[index];
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
this->connection_bytes_sent[index] = other.connection_bytes_sent[index];
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
this->connection_packets_received[index] = other.connection_packets_received[index];
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
this->connection_bytes_received[index] = other.connection_bytes_received[index];
this->file_bytes_sent = other.file_bytes_sent;
this->file_bytes_received = other.file_bytes_received;
return *this;
}
template <typename target_t>
inline BandwidthEntry<target_t> mul(double factor) const {
BandwidthEntry<target_t> result{};
result = *this;
for(auto& val : result.connection_packets_sent) val *= factor;
for(auto& val : result.connection_bytes_sent) val *= factor;
for(auto& val : result.connection_packets_received) val *= factor;
for(auto& val : result.connection_bytes_received) val *= factor;
result.file_bytes_sent *= factor;
result.file_bytes_received *= factor;
return result;
}
template <typename other_type>
inline BandwidthEntry& operator+=(const BandwidthEntry<other_type>& other) {
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
this->connection_packets_sent[index] += other.connection_packets_sent[index];
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
this->connection_bytes_sent[index] += other.connection_bytes_sent[index];
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
this->connection_packets_received[index] += other.connection_packets_received[index];
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
this->connection_bytes_received[index] += other.connection_bytes_received[index];
this->file_bytes_sent += other.file_bytes_sent;
this->file_bytes_received += other.file_bytes_received;
return *this;
}
template <typename other_type>
inline BandwidthEntry operator+(const BandwidthEntry<other_type>& other) {
return BandwidthEntry{*this} += other;
}
template <typename atomic_t>
inline void atomic_exchange(BandwidthEntry<std::atomic<atomic_t>>& source) {
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
this->connection_packets_sent[index] = source.connection_packets_sent[index].exchange(0);
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
this->connection_bytes_sent[index] = source.connection_bytes_sent[index].exchange(0);
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
this->connection_packets_received[index] = source.connection_packets_received[index].exchange(0);
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
this->connection_bytes_received[index] = source.connection_bytes_received[index].exchange(0);
this->file_bytes_sent = source.file_bytes_sent.exchange(0);
this->file_bytes_received = source.file_bytes_received.exchange(0);
}
struct StatisticEntry {
std::atomic<int8_t> use_count{0};
std::chrono::time_point<std::chrono::system_clock> timestamp;
uint16_t size = 0;
};
struct FileTransferStatistics {
uint64_t bytes_received{0};
uint64_t bytes_sent{0};
struct DataSummery {
uint32_t send_minute;
uint32_t send_second;
uint32_t recv_minute;
uint32_t recv_second;
uint32_t file_recv;
uint32_t file_send;
};
struct FullReport {
uint64_t connection_packets_sent[4]{0, 0, 0, 0};
uint64_t connection_bytes_sent[4]{0, 0, 0, 0};
uint64_t connection_packets_received[4]{0, 0, 0, 0};
uint64_t connection_bytes_received[4]{0, 0, 0, 0};
uint64_t file_bytes_sent = 0;
uint64_t file_bytes_received = 0;
};
class ConnectionStatistics {
public:
struct category {
/* Only three categories. Map unknown to category 0 */
enum value {
COMMAND,
KEEP_ALIVE,
ACK,
VOICE,
UNKNOWN = COMMAND
UNKNOWN
};
constexpr static std::array<category::value, 16> lookup_table{
@@ -109,10 +53,10 @@ namespace ts {
VOICE, /* VoiceWhisper */
COMMAND, /* Command */
COMMAND, /* CommandLow */
KEEP_ALIVE, /* Ping */
KEEP_ALIVE, /* Pong */
COMMAND, /* Ack */
COMMAND, /* AckLow */
ACK, /* Ping */
ACK, /* Pong */
ACK, /* Ack */
ACK, /* AckLow */
COMMAND, /* */
UNKNOWN,
@@ -132,38 +76,58 @@ namespace ts {
return from_type(type.type());
}
};
explicit ConnectionStatistics(std::shared_ptr<ConnectionStatistics> /* root */);
explicit ConnectionStatistics(const std::shared_ptr<ConnectionStatistics>& /* root */, bool /* spawn properties */);
~ConnectionStatistics();
std::shared_ptr<Properties> statistics();
inline void logIncomingPacket(const protocol::ClientPacket& packet) { this->logIncomingPacket(category::from_type(packet.type()), packet.length()); }
void logIncomingPacket(const category::value& /* category */, size_t /* length */);
inline void logOutgoingPacket(const protocol::ServerPacket& packet) { this->logOutgoingPacket(category::from_type(packet.type()), packet.length()); }
void logOutgoingPacket(const category::value& /* category */, size_t /* length */);
void logFileTransferIn(uint32_t);
void logFileTransferOut(uint32_t);
void logFileTransferIn(uint64_t);
void logFileTransferOut(uint64_t);
void tick();
[[nodiscard]] inline const BandwidthEntry<uint64_t>& total_stats() const { return this->total_statistics; }
[[nodiscard]] inline BandwidthEntry<uint32_t> second_stats() const { return this->statistics_second; }
[[nodiscard]] BandwidthEntry<uint32_t> minute_stats() const;
DataSummery dataReport();
FullReport full_report();
FileTransferStatistics file_stats();
std::pair<uint64_t, uint64_t> mark_file_bytes();
inline bool measure_bandwidths() { return this->_measure_bandwidths; }
void measure_bandwidths(bool flag) { this->_measure_bandwidths = flag; }
inline bool has_properties() { return !!this->properties; }
private:
bool _measure_bandwidths = true;
std::shared_ptr<ConnectionStatistics> handle;
std::shared_ptr<Properties> properties;
BandwidthEntry<uint64_t> total_statistics{};
BandwidthEntry<std::atomic<uint64_t>> statistics_second_current{};
BandwidthEntry<uint32_t> statistics_second{}; /* will be updated every second by the stats from the "current_second" */
std::array<BandwidthEntry<uint32_t>, 60> statistics_minute{};
uint32_t statistics_minute_offset{0}; /* pointing to the upcoming minute */
std::chrono::system_clock::time_point last_second_tick{};
std::atomic<uint64_t> connection_packets_sent[4]{0, 0, 0, 0};
std::atomic<uint64_t> connection_bytes_sent[4]{0, 0, 0, 0};
std::atomic<uint64_t> connection_packets_received[4]{0, 0, 0, 0};
std::atomic<uint64_t> connection_bytes_received[4]{0, 0, 0, 0};
std::atomic<uint64_t> file_bytes_sent{0};
std::atomic<uint64_t> file_bytes_received{0};
std::atomic<uint64_t> file_bytes_sent = 0;
std::atomic<uint64_t> file_bytes_received = 0;
uint64_t mark_file_bytes_sent{0};
uint64_t mark_file_bytes_received{0};
std::atomic<uint64_t> mark_file_bytes_sent = 0;
std::atomic<uint64_t> mark_file_bytes_received = 0;
spin_lock history_lock_outgoing;
spin_lock history_lock_incoming;
std::deque<StatisticEntry*> history_file_incoming{};
std::deque<StatisticEntry*> history_file_outgoing{};
std::deque<StatisticEntry*> history_incoming{};
std::deque<StatisticEntry*> history_outgoing{};
void _log_incoming_packet(StatisticEntry */* statistics */, int8_t /* type index */);
void _log_outgoing_packet(StatisticEntry* /* statistics */, int8_t /* type index */);
void _log_incoming_file_packet(StatisticEntry */* statistics */);
void _log_outgoing_file_packet(StatisticEntry* /* statistics */);
};
}
}
+61 -74
View File
@@ -37,7 +37,7 @@ void DatabaseHelper::tick() {
{
threads::MutexLock l(this->propsLock);
auto pcpy = this->cachedProperties;
for(const auto& mgr : pcpy) {
for(const auto& mgr : pcpy){
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5))
mgr->ownLock.reset();
if(mgr->properties.expired()) {
@@ -159,15 +159,9 @@ void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server,
//TODO delete complains
}
inline sql::result load_permissions_v2(
const std::shared_ptr<VirtualServer>& server,
v2::PermissionManager* manager,
sql::command& command,
bool test_channel, /* only used for client permissions (client channel permissions) */
bool is_channel) {
inline sql::result load_permissions_v2(VirtualServerId server_id, v2::PermissionRegister* manager, sql::command& command, bool test_channel /* only used for client permissions (client channel permissions) */) {
auto start = system_clock::now();
auto server_id = server ? server->getServerId() : 0;
return command.query([&](int length, char** values, char** names){
permission::PermissionType key = permission::PermissionType::undefined;
permission::PermissionValue value = permNotGranted, granted = permNotGranted;
@@ -203,6 +197,8 @@ inline sql::result load_permissions_v2(
return 0;
}
if(channel_id > 0 && test_channel) {
//TODO: Test for invalid channels
/*
if(!server)
logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
else {
@@ -210,6 +206,7 @@ inline sql::result load_permissions_v2(
if(!channel)
logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
}
*/
}
@@ -218,7 +215,7 @@ inline sql::result load_permissions_v2(
return 0;
}
if(channel_id == 0 || is_channel)
if(channel_id == 0)
manager->load_permission(key, {value, granted}, skipped, negated, value != permNotGranted, granted != permNotGranted);
else
manager->load_permission(key, {value, granted}, channel_id, skipped, negated, value != permNotGranted, granted != permNotGranted);
@@ -234,13 +231,12 @@ inline sql::result load_permissions_v2(
#define INSERT_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"
#define DELETE_COMMAND "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
auto server_id = server ? server->getServerId() : 0;
std::shared_ptr<v2::PermissionRegister> DatabaseHelper::loadClientPermissionManager(VirtualServerId server_id, ClientDbId cldbid) {
#ifndef DISABLE_CACHING
{
lock_guard<threads::Mutex> lock(permManagerLock);
for(auto permMgr : this->cachedPermissionManagers)
if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)) {
if(permMgr->cldbid == cldbid && permMgr->sid == server_id) {
auto ptr = permMgr->manager.lock();
if(!ptr){
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
@@ -254,14 +250,14 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
#endif
logTrace(server_id, "[Permission] Loading client permission manager for client {}", cldbid);
auto permission_manager = std::make_shared<v2::PermissionManager>();
auto permission_manager = std::make_shared<v2::PermissionRegister>();
bool loaded = false;
if(this->use_startup_cache && server) {
if(this->use_startup_cache) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
if(entries->sid == server_id) {
entry = entries;
break;
}
@@ -270,10 +266,8 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
if(entry) {
for(const auto& perm : entry->permissions) {
if(perm->type == permission::SQL_PERM_USER && perm->id == cldbid) {
auto channel = perm->channelId > 0 ? server->getChannelTree()->findChannel(perm->channelId) : nullptr;
if(channel)
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, channel->channelId(), perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
if(perm->channelId > 0)
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->channelId, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
else
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
}
@@ -284,10 +278,10 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
if(!loaded) {
auto command = sql::command(this->sql, "SELECT `permId`, `value`, `channelId`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server_id},
variable{":type", permission::SQL_PERM_USER},
variable{":id", cldbid});
LOG_SQL_CMD(load_permissions_v2(server, permission_manager.get(), command, true, false));
LOG_SQL_CMD(load_permissions_v2(server_id, permission_manager.get(), command, true));
}
@@ -306,12 +300,11 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
}
void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::ClientDbId client_dbid, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
void DatabaseHelper::saveClientPermissions(VirtualServerId server_id, ts::ClientDbId client_dbid, const std::shared_ptr<ts::permission::v2::PermissionRegister> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
@@ -326,7 +319,7 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
query
);
sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server_id},
variable{":id", client_dbid},
variable{":chId", update.channel_id},
variable{":type", permission::SQL_PERM_USER},
@@ -341,14 +334,14 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
}
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPermissions(const std::shared_ptr<VirtualServer>& server, ts::GroupId group_id) {
auto result = std::make_shared<v2::PermissionManager>();
std::shared_ptr<permission::v2::PermissionRegister> DatabaseHelper::loadGroupPermissions(VirtualServerId server, ts::GroupId group_id, uint8_t type) {
auto result = std::make_shared<v2::PermissionRegister>();
if(this->use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
if(entries->sid == server) {
entry = entries;
break;
}
@@ -366,19 +359,18 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPerm
//7931
auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id});
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, false));
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
return result;
}
void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::GroupId group_id, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
void DatabaseHelper::saveGroupPermissions(VirtualServerId server_id, ts::GroupId group_id, const std::shared_ptr<ts::permission::v2::PermissionRegister> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
@@ -393,7 +385,7 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::Virt
query
);
sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server_id},
variable{":id", group_id},
variable{":chId", 0},
variable{":type", permission::SQL_PERM_GROUP},
@@ -407,8 +399,8 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::Virt
}
}
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadPlaylistPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::PlaylistId playlist_id) {
shared_ptr<permission::v2::PermissionManager> result;
std::shared_ptr<permission::v2::PermissionRegister> DatabaseHelper::loadPlaylistPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::PlaylistId playlist_id) {
shared_ptr<permission::v2::PermissionRegister> result;
if(this->use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
@@ -421,7 +413,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadPlaylistP
}
}
if(entry) {
result = std::make_shared<permission::v2::PermissionManager>();
result = std::make_shared<permission::v2::PermissionRegister>();
for(const auto& perm : entry->permissions) {
if(perm->type == permission::SQL_PERM_PLAYLIST && perm->id == playlist_id) {
@@ -436,16 +428,16 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadPlaylistP
return result;
}
result = std::make_shared<permission::v2::PermissionManager>();
result = std::make_shared<permission::v2::PermissionRegister>();
auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_PLAYLIST},
variable{":id", playlist_id});
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, false));
LOG_SQL_CMD(load_permissions_v2(server ? server->getServerId() : 0, result.get(), command, false));
return result;
}
void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer> &server, PlaylistId pid, const std::shared_ptr<permission::v2::PermissionManager> &permissions) {
void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer> &server, PlaylistId pid, const std::shared_ptr<permission::v2::PermissionRegister> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
@@ -479,8 +471,8 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
}
}
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadChannelPermissions(const std::shared_ptr<VirtualServer>& server, ts::ChannelId channel) {
auto result = std::make_shared<v2::PermissionManager>();
std::shared_ptr<permission::v2::PermissionRegister> DatabaseHelper::loadChannelPermissions(const std::shared_ptr<VirtualServer>& server, ts::ChannelId channel) {
auto result = std::make_shared<v2::PermissionRegister>();
if(this->use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
{
@@ -507,11 +499,11 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadChannelPe
variable{":chid", channel},
variable{":id", 0},
variable{":type", permission::SQL_PERM_CHANNEL});
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, true));
LOG_SQL_CMD(load_permissions_v2(server ? server->getServerId() : 0, result.get(), command, false));
return result;
}
void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::ChannelId channel_id, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::ChannelId channel_id, const std::shared_ptr<ts::permission::v2::PermissionRegister> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
@@ -560,44 +552,39 @@ std::shared_ptr<Properties> DatabaseHelper::default_properties_client(std::share
return properties;
}
std::mutex DatabaseHelper::database_id_mutex{};
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::shared_ptr<DataClient> cl) {
cl->loadDataForCurrentServer();
if(cl->getClientDatabaseId() == 0){ //Client does not exist
ClientDbId new_client_database_id{0};
ClientDbId cldbid = 0;
auto res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 AND `clientUid` = :cluid", variable{":cluid", cl->getUid()}).query([](ClientDbId* ptr, int length, char** values, char** names){
*ptr = static_cast<ClientDbId>(stoll(values[0]));
return 0;
}, &new_client_database_id);
}, &cldbid);
auto pf = LOG_SQL_CMD;
pf(res);
if(!res) return false;
auto insertTemplate = sql::model(sql, "INSERT INTO `clients` (`serverId`, `cldbId`, `clientUid`, `lastName`,`firstConnect`,`lastConnect`, `connections`) VALUES (:serverId, :cldbid, :cluid, :name, :fconnect, :lconnect, :connections)",
variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()},
variable{":fconnect", duration_cast<seconds>(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0},
variable{":connections", 0});
if(new_client_database_id == 0) { /* we've a completely new user */
std::lock_guard db_id_lock{DatabaseHelper::database_id_mutex};
res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([&](int length, std::string* values, std::string* names) {
assert(length == 1);
new_client_database_id = (ClientDbId) stoll(values[0]);
});
LOG_SQL_CMD(res);
variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()}, variable{":fconnect", duration_cast<seconds>(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0}, variable{":connections", 0});
if(cldbid == 0){ //Completly new user
res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([](ClientDbId* ptr, int length, char** values, char** names){
*ptr = static_cast<ClientDbId>(stoll(values[0]));
return 0;
}, &cldbid);
pf(res);
if(!res) return false;
new_client_database_id += 1;
res = insertTemplate.command().values(variable{":serverId", 0}, variable{":cldbid", new_client_database_id}).execute(); //Insert global
LOG_SQL_CMD(res);
cldbid += 1;
res = insertTemplate.command().values(variable{":serverId", 0}, variable{":cldbid", cldbid}).execute(); //Insert global
pf(res);
if(!res) return false;
debugMessage(LOG_INSTANCE, "Registered a new client. Unique id: {}, First server: {}, Database ID: {}", cl->getUid(), id, new_client_database_id);
debugMessage(id, "Having new instance user on server {}. (Database ID: {} Name: {})", id, cldbid, cl->getDisplayName());
} else {
debugMessage(id, "Having new client, which is already known on this instance. Unique id: {}, First server: {}, Database ID: {}", cl->getUid(), id, new_client_database_id);
debugMessage(id, "Having new server user on server {}. (Database ID: {} Name: {})", id, cldbid, cl->getDisplayName());
}
if(id != 0){ //Else already inserted
res = insertTemplate.command().values(variable{":serverId", id}, variable{":cldbid", new_client_database_id}).execute();
res = insertTemplate.command().values(variable{":serverId", id}, variable{":cldbid", cldbid}).execute();
pf(res);
if(!res) return false;
}
@@ -605,7 +592,7 @@ bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::sh
return assignDatabaseId(sql, id, cl);
}
logTrace(id, "Loaded client successfully from database. Database id: {} Unique id: {}", cl->getClientDatabaseId(), cl->getUid());
logTrace(id, "Loaded client from database. Database id: {} Unique id: {}", cl->getClientDatabaseId(), cl->getUid());
return true;
}
@@ -627,8 +614,8 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
}
}
const auto &info = property::find(type, key);
if(info.name == "undefined") {
const auto &info = property::impl::info_key(type, key);
if(info->name == "undefined") {
logError(sid, "Found unknown property in database! ({})", key);
return 0;
}
@@ -640,9 +627,9 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
prop.setDbReference(true);
*/
auto data = std::make_unique<FastPropertyEntry>();
data->type = &info;
auto data = make_unique<FastPropertyEntry>();
data->value = value;
data->type = info;
properties.push_back(move(data));
return 0;
});
@@ -715,7 +702,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shar
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
}
logTrace(serverId, "Updating server property: " + std::string{prop.type().name} + ". New value: " + prop.value() + ". Query: " + sql);
logTrace(serverId, "Updating server property: " + prop.type().name + ". New value: " + prop.value() + ". Query: " + sql);
sql::command(this->sql, sql,
variable{":sid", serverId},
variable{":type", property::PropertyType::PROP_TYPE_SERVER},
@@ -935,7 +922,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
if(!prop.isModified()) return;
if((prop.type().flags & property::FLAG_SAVE) == 0 && (type != ClientType::CLIENT_MUSIC || (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0)) {
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + std::string{prop.type().name} + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + prop.type().name + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
return;
}
if(!prop.get_handle()) return;
@@ -950,7 +937,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
}
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + std::string{prop.type().name} + " value: " + prop.value());
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + prop.type().name + " value: " + prop.value());
sql::command(this->sql, sql,
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", prop.type().type_property},
@@ -975,7 +962,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
else if(prop.type() == property::CLIENT_LASTCONNECTED)
query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
if(query.empty()) return;
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '" + std::string{prop.type().name} + "' for " + to_string(cldbid) + " (New value: " + prop.value() + ", SQL: " + query + ")");
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '" + prop.type().name + "' for " + to_string(cldbid) + " (New value: " + prop.value() + ", SQL: " + query + ")");
sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":cldbid", cldbid}, variable{":value", prop.value()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
});
@@ -1129,8 +1116,8 @@ void DatabaseHelper::loadStartupPropertyCache() {
}
}
const auto& info = property::find(type, key);
if(info.is_undefined()) {
auto info = property::impl::info_key(type, key);
if(info == property::PropertyDescription::unknown) {
logError(serverId, "Invalid property ({} | {})", key, type);
return 0;
}
@@ -1155,7 +1142,7 @@ void DatabaseHelper::loadStartupPropertyCache() {
}
auto entry = make_unique<StartupPropertyEntry>();
entry->info = &info;
entry->info = info;
entry->value = value;
entry->id = id;
entry->type = type;
+14 -15
View File
@@ -4,7 +4,7 @@
#include <map>
#include <vector>
#include <chrono>
#include <PermissionManager.h>
#include <PermissionRegister.h>
#include <Properties.h>
#include <cstdint>
@@ -26,8 +26,8 @@ namespace ts {
struct CachedPermissionManager {
ServerId sid;
ClientDbId cldbid;
std::weak_ptr<permission::v2::PermissionManager> manager;
std::shared_ptr<permission::v2::PermissionManager> ownLock;
std::weak_ptr<permission::v2::PermissionRegister> manager;
std::shared_ptr<permission::v2::PermissionRegister> ownLock;
std::chrono::time_point<std::chrono::system_clock> lastAccess;
};
@@ -57,8 +57,8 @@ namespace ts {
struct StartupPropertyEntry {
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
uint64_t id{0};
const property::PropertyDescription* info{&property::undefined_property_description};
uint64_t id = 0;
std::shared_ptr<property::PropertyDescription> info = property::PropertyDescription::unknown;
std::string value;
};
@@ -70,7 +70,7 @@ namespace ts {
};
struct FastPropertyEntry {
const property::PropertyDescription* type;
std::shared_ptr<property::PropertyDescription> type;
std::string value;
};
@@ -78,7 +78,6 @@ namespace ts {
public:
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
static bool assignDatabaseId(sql::SqlManager *, ServerId id, std::shared_ptr<DataClient>);
static std::mutex database_id_mutex;
explicit DatabaseHelper(sql::SqlManager*);
~DatabaseHelper();
@@ -92,17 +91,17 @@ namespace ts {
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &, std::deque<std::string>);
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const std::shared_ptr<VirtualServer>&, ClientDbId);
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionRegister> loadClientPermissionManager(VirtualServerId, ClientDbId);
void saveClientPermissions(VirtualServerId, ClientDbId , const std::shared_ptr<permission::v2::PermissionRegister>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionManager> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionRegister> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionRegister>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
void saveGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionRegister> loadGroupPermissions(VirtualServerId, GroupId, uint8_t /* group type */);
void saveGroupPermissions(VirtualServerId, GroupId, const std::shared_ptr<permission::v2::PermissionRegister>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionManager> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
std::shared_ptr<permission::v2::PermissionRegister> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionRegister>& /* permission manager */);
std::shared_ptr<Properties> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
std::shared_ptr<Properties> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
+24 -56
View File
@@ -6,7 +6,8 @@
#include "VirtualServer.h"
#include "src/client/ConnectedClient.h"
#include "InstanceHandler.h"
#include "src/server/file/LocalFileServer.h"
#include "src/server/file/FileServer.h"
#include "src/groups/Group.h"
using namespace std;
using namespace std::chrono;
@@ -21,7 +22,7 @@ Group::Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId g
this->_properties = new Properties();
this->_properties->register_property_type<property::GroupProperties>();
this->setPermissionManager(make_shared<permission::v2::PermissionManager>());
this->setPermissionManager(make_shared<permission::v2::PermissionRegister>());
this->properties()[property::GROUP_ID] = groupId;
this->properties()[property::GROUP_TYPE] = type;
@@ -59,7 +60,7 @@ Group::Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId g
});
}
void Group::setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager> &manager) {
void Group::setPermissionManager(const std::shared_ptr<permission::v2::PermissionRegister> &manager) {
this->_permissions = manager;
this->apply_properties_from_permissions();
}
@@ -93,7 +94,6 @@ GroupManager::GroupManager(const shared_ptr<VirtualServer> &server, sql::SqlMana
GroupManager::~GroupManager() {}
bool GroupManager::loadGroupFormDatabase(GroupId id) {
std::lock_guard glock{this->group_lock};
if(id == 0){
this->groups.clear();
@@ -117,13 +117,8 @@ bool GroupManager::loadGroupFormDatabase(GroupId id) {
std::vector<std::shared_ptr<Group>> GroupManager::availableGroups(bool root) {
std::vector<std::shared_ptr<Group>> response;
{
std::lock_guard glock{this->group_lock};
for(const auto& group : this->groups)
response.push_back(group);
}
for(const auto& group : this->groups)
response.push_back(group);
if(root && this->root){
auto elm = this->root->availableGroups();
for(const auto& e : elm)
@@ -134,14 +129,9 @@ std::vector<std::shared_ptr<Group>> GroupManager::availableGroups(bool root) {
std::vector<std::shared_ptr<Group>> GroupManager::availableServerGroups(bool root){
std::vector<std::shared_ptr<Group>> response;
{
std::lock_guard glock{this->group_lock};
for(const auto& group : this->groups)
if(group->target() == GroupTarget::GROUPTARGET_SERVER)
response.push_back(group);
}
for(const auto& group : this->groups)
if(group->target() == GroupTarget::GROUPTARGET_SERVER)
response.push_back(group);
if(root && this->root){
auto elm = this->root->availableServerGroups();
for(const auto& e : elm)
@@ -152,12 +142,9 @@ std::vector<std::shared_ptr<Group>> GroupManager::availableServerGroups(bool roo
std::vector<std::shared_ptr<Group>> GroupManager::availableChannelGroups(bool root) {
std::vector<std::shared_ptr<Group>> response;
{
std::lock_guard glock{this->group_lock};
for(const auto& group : this->groups)
if(group->target() == GroupTarget::GROUPTARGET_CHANNEL)
response.push_back(group);
}
for(const auto& group : this->groups)
if(group->target() == GroupTarget::GROUPTARGET_CHANNEL)
response.push_back(group);
if(root && this->root){
auto elm = this->root->availableChannelGroups(true);
for(const auto& e : elm)
@@ -181,7 +168,8 @@ int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
groupId = (GroupType) stoll(values[index]);
else if(strcmp(column[index], "displayName") == 0)
targetName = values[index];
//else cerr << "Invalid group table row " << column[index] << endl;
else if(strcmp(column[index], "serverId") == 0);
else cerr << "Invalid group table row " << column[index] << endl;
}
if((size_t) groupId == 0 || (size_t) target == 0xff || (size_t) type == 0xff || targetName.empty()) {
@@ -225,7 +213,7 @@ int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
group->properties()[property::GROUP_NAME] = targetName;
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock() ? this->server.lock()->getServerId() : 0, group->groupId(), (uint8_t) -1));
debugMessage(this->getServerId(), "Push back group -> " + to_string(group->groupId()) + " - " + group->name());
this->groups.push_back(group);
@@ -250,7 +238,6 @@ void GroupManager::handleChannelDeleted(const ChannelId& channel_id) {
}
bool GroupManager::isLocalGroup(std::shared_ptr<Group> gr) {
std::lock_guard glock{this->group_lock};
return std::find(this->groups.begin(), this->groups.end(), gr) != this->groups.end();
}
@@ -266,12 +253,9 @@ std::shared_ptr<Group> GroupManager::defaultGroup(GroupTarget type, bool enforce
auto group = this->findGroupLocal(id);
if(group || enforce_property) return group;
{
std::lock_guard glock{this->group_lock};
for(auto elm : this->groups)
if(elm->target() == type)
return elm;
}
for(auto elm : this->groups)
if(elm->target() == type)
return elm;
return nullptr; //Worst case!
}
@@ -283,7 +267,6 @@ std::shared_ptr<Group> GroupManager::findGroup(GroupId groupId) {
}
std::shared_ptr<Group> GroupManager::findGroupLocal(GroupId groupId) {
std::lock_guard glock{this->group_lock};
for(const auto& elm : this->groups)
if(elm->groupId() == groupId) return elm;
return nullptr;
@@ -291,11 +274,8 @@ std::shared_ptr<Group> GroupManager::findGroupLocal(GroupId groupId) {
std::vector<std::shared_ptr<Group>> GroupManager::findGroup(GroupTarget target, std::string name) {
vector<shared_ptr<Group>> res;
{
std::lock_guard glock{this->group_lock};
for(const auto &elm : this->groups)
if(elm->name() == name && elm->target() == target) res.push_back(elm);
}
for(const auto &elm : this->groups)
if(elm->name() == name && elm->target() == target) res.push_back(elm);
if(this->root) {
auto r = root->findGroup(target, name);
for(const auto &e : r) res.push_back(e);
@@ -325,9 +305,7 @@ std::shared_ptr<Group> GroupManager::createGroup(GroupTarget target, GroupType t
std::shared_ptr<Group> group = std::make_shared<Group>(this, target, type, groupId);
group->properties()[property::GROUP_NAME] = name;
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
std::lock_guard glock{this->group_lock};
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock() ? this->server.lock()->getServerId() : 0, group->groupId(), (uint8_t) -1));
this->groups.push_back(group);
return group;
}
@@ -367,7 +345,7 @@ bool GroupManager::copyGroupPermissions(const shared_ptr<Group> &source, const s
res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":ssid", sourceServer}, variable{":tsid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":source", source->groupId()}, variable{":target", target->groupId()}).execute();
target->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(target->handle->server.lock(), target->groupId()));
target->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(target->handle->server.lock() ? target->handle->server.lock()->getServerId() : 0, target->groupId(), (uint8_t) -1));
LOG_SQL_CMD(res);
return true;
}
@@ -378,7 +356,7 @@ bool GroupManager::reloadGroupPermissions(std::shared_ptr<Group> group) {
return false;
}
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock() ? this->server.lock()->getServerId() : 0, group->groupId(), (uint8_t) -1));
return true;
}
@@ -417,10 +395,7 @@ bool GroupManager::deleteGroup(std::shared_ptr<Group> group) {
return false;
}
{
std::lock_guard glock{this->group_lock};
this->groups.erase(std::find(this->groups.begin(), this->groups.end(), group));
}
this->groups.erase(std::find(this->groups.begin(), this->groups.end(), group));
/* erase the group out of our cache */
{
@@ -483,11 +458,6 @@ std::deque<property::ClientProperties> GroupManager::update_server_group_propert
unique_lock chan_lock(client->channel_lock, defer_lock);
if(channel_lock)
chan_lock.lock();
client->cached_server_groups.clear();
client->cached_server_groups.reserve(groups.size());
for(const auto& group : groups)
client->cached_server_groups.push_back(group->group->groupId());
}
}
@@ -506,8 +476,6 @@ std::deque<property::ClientProperties> GroupManager::update_server_group_propert
unique_lock chan_lock(client->channel_lock, defer_lock);
if(channel_lock)
chan_lock.lock();
client->cached_channel_group = group->group->groupId();
}
}
+8 -8
View File
@@ -4,7 +4,7 @@
#include <chrono>
#include <BasicChannel.h>
#include <ThreadPool/Mutex.h>
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "Properties.h"
#include "channel/ServerChannel.h"
#include "Definitions.h"
@@ -29,7 +29,9 @@ namespace ts {
enum GroupTarget {
GROUPTARGET_SERVER,
GROUPTARGET_CHANNEL
GROUPTARGET_CHANNEL,
GROUPTARGET_UNKNOWN
};
enum GroupNameMode {
@@ -82,7 +84,7 @@ namespace ts {
Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId groupId);
~Group();
std::shared_ptr<permission::v2::PermissionManager> permissions(){ return this->_permissions; }
std::shared_ptr<permission::v2::PermissionRegister> permissions(){ return this->_permissions; }
Properties& properties(){ return *this->_properties; }
GroupNameMode nameMode(){ return (GroupNameMode) (uint8_t) properties()[property::GROUP_NAMEMODE]; }
@@ -133,10 +135,10 @@ namespace ts {
return data.has_value ? data.value : 0;
}
private:
void setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager>& manager);
void setPermissionManager(const std::shared_ptr<permission::v2::PermissionRegister>& manager);
GroupManager* handle;
std::shared_ptr<permission::v2::PermissionManager> _permissions;
std::shared_ptr<permission::v2::PermissionRegister> _permissions;
Properties* _properties;
GroupTarget _target;
GroupType _type;
@@ -215,6 +217,7 @@ namespace ts {
bool isClientCached(const ClientDbId& /* client database id */);
void clearCache();
bool isLocalGroup(std::shared_ptr<Group>);
protected:
void handleChannelDeleted(const ChannelId& /* channel id */);
@@ -224,10 +227,7 @@ namespace ts {
ServerId getServerId();
sql::SqlManager* sql;
std::mutex group_lock{};
std::vector<std::shared_ptr<Group>> groups;
threads::Mutex cacheLock;
std::vector<std::shared_ptr<CachedClient>> cachedClients;
+29 -21
View File
@@ -9,7 +9,7 @@
#include "InstanceHandler.h"
#include "src/client/InternalClient.h"
#include "src/server/QueryServer.h"
#include "src/server/file/LocalFileServer.h"
#include "src/server/file/FileServer.h"
#include "SignalHandler.h"
#include "src/manager/PermissionNameMapper.h"
#include <ThreadPool/Timer.h>
@@ -23,6 +23,7 @@
#include <misc/strobf.h>
#include <jemalloc/jemalloc.h>
#include <protocol/buffers.h>
#include "src/server/udp-server/UDPServer.h"
#ifndef _POSIX_SOURCE
#define _POSIX_SOURCE
@@ -41,7 +42,8 @@ extern bool mainThreadActive;
InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
serverInstance = this;
this->tick_manager = make_shared<threads::Scheduler>(config::threads::ticking, "tick task ");
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr);
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr, true);
this->statistics->measure_bandwidths(true);
std::string error_message{};
this->license_service_ = std::make_shared<license::LicenseService>();
@@ -69,8 +71,8 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
}
}
const auto &info = property::find<property::InstanceProperties>(key);
if(info == property::SERVERINSTANCE_UNDEFINED) {
const auto &info = property::impl::info<property::InstanceProperties>(key);
if(*info == property::SERVERINSTANCE_UNDEFINED) {
logError(0, "Got an unknown instance property " + key);
return 0;
}
@@ -187,7 +189,7 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}
this->banMgr = new BanManager(this->getSql());
this->banMgr = new bans::BanManager(this->getSql());
this->banMgr->loadBans();
this->web_list = make_shared<weblist::WebListManager>();
@@ -281,19 +283,19 @@ bool InstanceHandler::startInstance() {
}
this->loadWebCertificate();
fileServer = new ts::server::LocalFileServer();
fileServer = new ts::server::FileServer();
{
auto bindings_string = this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].as<string>();
auto port = this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as<uint16_t>();
auto ft_bindings = net::resolve_bindings(bindings_string, port);
deque<shared_ptr<LocalFileServer::Binding>> bindings;
deque<shared_ptr<FileServer::Binding>> bindings;
for(auto& binding : ft_bindings) {
if(!get<2>(binding).empty()) {
logError(LOG_FT, "Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding));
continue;
}
auto entry = make_shared<LocalFileServer::Binding>();
auto entry = make_shared<FileServer::Binding>();
memcpy(&entry->address, &get<1>(binding), sizeof(sockaddr_storage));
entry->file_descriptor = -1;
@@ -388,6 +390,12 @@ FwIDAQAB
}
}
this->udpServer = new server::udp::Server{};
if(std::string error{}; !this->udpServer->initialize(error)) {
logCritical(LOG_INSTANCE, "Failed to allocate UDP server.");
return false;
}
this->voiceServerManager = new VirtualServerManager(this);
if (!this->voiceServerManager->initialize(true)) {
logCritical(LOG_INSTANCE, "Could not load servers!");
@@ -413,7 +421,7 @@ FwIDAQAB
startTimestamp = system_clock::now();
this->voiceServerManager->executeAutostart();
this->scheduler()->schedule(INSTANCE_TICK_NAME, bind(&InstanceHandler::tickInstance, this), milliseconds{500});
this->scheduler()->schedule(INSTANCE_TICK_NAME, bind(&InstanceHandler::tickInstance, this), milliseconds(100));
return true;
}
@@ -432,22 +440,22 @@ void InstanceHandler::stopInstance() {
debugMessage(LOG_INSTANCE, "Stopping all virtual servers");
if (this->voiceServerManager)
this->voiceServerManager->shutdownAll(ts::config::messages::applicationStopped);
delete this->voiceServerManager;
this->voiceServerManager = nullptr;
delete std::exchange(this->voiceServerManager, nullptr);
debugMessage(LOG_INSTANCE, "All virtual server stopped");
debugMessage(LOG_QUERY, "Stopping query server");
if (this->queryServer) this->queryServer->stop();
delete this->queryServer;
this->queryServer = nullptr;
delete std::exchange(this->queryServer, nullptr);
debugMessage(LOG_QUERY, "Query server stopped");
debugMessage(LOG_FT, "Stopping file server");
if (this->fileServer) this->fileServer->stop();
delete this->fileServer;
this->fileServer = nullptr;
delete std::exchange(this->fileServer, nullptr);
debugMessage(LOG_FT, "File server stopped");
if(this->udpServer) this->udpServer->finalize();
delete std::exchange(this->udpServer, nullptr);
this->save_channel_permissions();
this->save_group_permissions();
@@ -481,12 +489,12 @@ void InstanceHandler::tickInstance() {
ALARM_TIMER(t, "InstanceHandler::tickInstance -> flush", milliseconds(5));
//logger::flush();
}
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
this->statistics->tick();
}
if(statisticsUpdateTimestamp + seconds(1) < now) {
if(statisticsUpdateTimestamp + seconds(5) < now) {
statisticsUpdateTimestamp = now;
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
this->statistics->tick();
}
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick [monthly]", milliseconds(2));
@@ -568,7 +576,7 @@ void InstanceHandler::save_group_permissions() {
auto permissions = group->permissions();
if(permissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveGroupPermissions(nullptr, group->groupId(), permissions);
serverInstance->databaseHelper()->saveGroupPermissions(0, group->groupId(), permissions);
auto end = system_clock::now();
debugMessage(0, "Saved instance group permissions for group {} ({}) in {}ms", group->groupId(), group->name(), duration_cast<milliseconds>(end - begin).count());
}
+12 -4
View File
@@ -23,6 +23,10 @@ namespace ts {
class LicenseService;
}
namespace server::udp {
class Server;
}
class InstanceHandler {
public:
explicit InstanceHandler(SqlDataManager*);
@@ -42,13 +46,15 @@ namespace ts {
std::shared_mutex& getChannelTreeLock() { return this->default_tree_lock; }
VirtualServerManager* getVoiceServerManager(){ return this->voiceServerManager; }
LocalFileServer* getFileServer(){ return fileServer; }
FileServer* getFileServer(){ return fileServer; }
QueryServer* getQueryServer(){ return queryServer; }
DatabaseHelper* databaseHelper(){ return this->dbHelper; }
BanManager* banManager(){ return this->banMgr; }
bans::BanManager* banManager(){ return this->banMgr; }
ssl::SSLManager* sslManager(){ return this->sslMgr; }
sql::SqlManager* getSql(){ return sql->sql(); }
[[nodiscard]] inline auto udp_server() { return this->udpServer; }
std::chrono::time_point<std::chrono::system_clock> getStartTimestamp(){ return startTimestamp; }
void executeTick(VirtualServer*);
@@ -110,11 +116,13 @@ namespace ts {
std::chrono::system_clock::time_point memcleanTimestamp;
SqlDataManager* sql;
LocalFileServer* fileServer = nullptr;
FileServer* fileServer = nullptr;
QueryServer* queryServer = nullptr;
server::udp::Server* udpServer{nullptr};
VirtualServerManager* voiceServerManager = nullptr;
DatabaseHelper* dbHelper = nullptr;
BanManager* banMgr = nullptr;
bans::BanManager* banMgr = nullptr;
ssl::SSLManager* sslMgr = nullptr;
ts::Properties* _properties = nullptr;
+3 -3
View File
@@ -6,7 +6,7 @@
#include "InstanceHandler.h"
#include "src/client/InternalClient.h"
#include "src/server/QueryServer.h"
#include "src/server/file/LocalFileServer.h"
#include "src/server/file/FileServer.h"
using namespace std;
using namespace std::chrono;
@@ -134,8 +134,8 @@ bool InstanceHandler::setupDefaultGroups() {
}
for(const auto& property : info->properties) {
const auto& prop = property::find<property::InstanceProperties>(property);
if(prop.is_undefined()) {
const auto& prop = property::impl::info<property::InstanceProperties>(property);
if(*prop == property::SERVERINSTANCE_UNDEFINED) {
logCritical(LOG_INSTANCE, "Invalid template property name: " + property);
} else {
this->properties()[prop] = group->groupId();
+11 -11
View File
@@ -201,7 +201,7 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
auto snapshot_version = arguments[index].has("snapshot_version") ? arguments[index++]["snapshot_version"] : 0;
debugMessage(0, "Got server snapshot with version {}", snapshot_version);
while(true){
for(const auto &key : arguments[index].keys()){
for(auto &key : arguments[index].keys()){
if(key == "end_virtualserver") continue;
if(key == "begin_virtualserver") continue;
if(snapshot_version == 0) {
@@ -559,8 +559,8 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
if(key == "bot_owner_id") continue;
if(key == "bot_id") continue;
const auto& property = property::find<property::ClientProperties>(key);
if(property.is_undefined()) {
const auto& property = property::info<property::ClientProperties>(key);
if(property->property_index == property::CLIENT_UNDEFINED) {
debugMessage(log_server_id, PREFIX + "Failed to parse give music bot property {} for bot {} (old: {}). Value: {}", key, new_bot_id, bot_id, arguments[index][key].string());
continue;
}
@@ -597,8 +597,8 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
if(key == "begin_playlist") continue;
if(key == "playlist_id") continue;
const auto& property = property::find<property::ClientProperties>(key);
if(property.is_undefined()) {
const auto& property = property::info<property::ClientProperties>(key);
if(property->property_index == property::CLIENT_UNDEFINED) {
debugMessage(log_server_id, PREFIX + "Failed to parse given playlist property {} for playlist {} (old: {}). Value: {}", key, playlist_index, playlist_id, arguments[index][key].string());
continue;
}
@@ -707,7 +707,7 @@ struct PermissionCommandTuple {
ChannelId channel;
};
inline bool writePermissions(const shared_ptr<permission::v2::PermissionManager>& manager, Command& cmd, int& index, int version, permission::teamspeak::GroupType type, std::string& error) {
inline bool writePermissions(const shared_ptr<permission::v2::PermissionRegister>& manager, Command& cmd, int& index, int version, permission::teamspeak::GroupType type, std::string& error) {
for(const auto& permission_container : manager->permissions()) {
auto permission = get<1>(permission_container);
SnapshotPermissionEntry{
@@ -787,12 +787,12 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
case property::VIRTUALSERVER_UPLOAD_QUOTA:
case property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH:
case property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH:
cmd[index][std::string{serverProperty.type().name}] = (uint64_t) serverProperty.as_save<int64_t>();
cmd[index][serverProperty.type().name] = (uint64_t) serverProperty.as_save<int64_t>();
default:
break;
}
}
cmd[index][std::string{serverProperty.type().name}] = serverProperty.value();
cmd[index][serverProperty.type().name] = serverProperty.value();
}
cmd[index++]["end_virtualserver"] = "";
}
@@ -807,7 +807,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
else if(channelProperty.type() == property::CHANNEL_PID)
cmd[index]["channel_pid"] = channelProperty.as<string>();
else
cmd[index][std::string{channelProperty.type().name}] = channelProperty.as<string>();
cmd[index][channelProperty.type().name] = channelProperty.as<string>();
}
index++;
}
@@ -899,7 +899,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue;
if(property->value == property->type->default_value) continue;
cmd[index][std::string{property->type->name}] = property->value;
cmd[index][property->type->name] = property->value;
}
index++;
@@ -931,7 +931,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue;
if(property->value == property->type->default_value) continue;
cmd[index][std::string{property->type->name}] = property->value;
cmd[index][property->type->name] = property->value;
}
index++;
+6 -7
View File
@@ -31,7 +31,6 @@ void ts::server::shutdownInstance(const std::string& message) {
force_kill.detach();
exit(2);
//kill(0, SIGKILL);
});
threads::name(hangup_controller, "stop controller");
hangup_controller.detach();
@@ -43,8 +42,8 @@ void ts::server::shutdownInstance(const std::string& message) {
mainThreadActive = false;
}
std::shared_ptr<server::ShutdownData> currentShutdown = nullptr;
std::shared_ptr<server::ShutdownData> server::scheduledShutdown() { return currentShutdown; }
std::shared_ptr<ts::server::ShutdownData> currentShutdown = nullptr;
std::shared_ptr<ts::server::ShutdownData> ts::server::scheduledShutdown() { return currentShutdown; }
inline void broadcastMessage(const std::string& message) {
if(!serverInstance || !serverInstance->getVoiceServerManager())
@@ -58,8 +57,8 @@ inline void broadcastMessage(const std::string& message) {
}
void executeScheduledShutdown(const std::shared_ptr<ShutdownData>& data);
bool server::scheduleShutdown(const std::chrono::system_clock::time_point& time, const std::string& reason) {
server::cancelShutdown(false); //Cancel old shutdown
bool ts::server::scheduleShutdown(const std::chrono::system_clock::time_point& time, const std::string& reason) {
ts::server::cancelShutdown(false); //Cancel old shutdown
auto data = std::make_shared<ShutdownData>();
data->active = true;
@@ -74,13 +73,13 @@ bool server::scheduleShutdown(const std::chrono::system_clock::time_point& time,
return true;
}
void server::cancelShutdown(bool notify) {
void ts::server::cancelShutdown(bool notify) {
if(!currentShutdown) return;
if(notify && !config::messages::shutdown::canceled.empty()) {
broadcastMessage(config::messages::shutdown::canceled);
}
auto current = server::scheduledShutdown();
auto current = ts::server::scheduledShutdown();
current->active = false;
current->shutdownNotify.notify_all();
if(!threads::save_join(current->shutdown_thread)) {
+12 -8
View File
@@ -88,7 +88,7 @@ bool VirtualServer::registerClient(shared_ptr<ConnectedClient> client) {
bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> cl, std::string reason, std::unique_lock<std::shared_mutex>& chan_tree_lock) {
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK && cl->getType() == ClientType::CLIENT_WEB) {
sassert(cl->state == ConnectionState::DISCONNECTED);
sassert(cl->state == ClientState::DISCONNECTED);
}
auto client_id = cl->getClientId();
@@ -122,13 +122,13 @@ bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> cl, std::string
this->client_move(cl, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock);
}
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
serverInstance->databaseHelper()->saveClientPermissions(this->serverId, cl->getClientDatabaseId(), cl->clientPermissions);
cl->setClientId(0);
return true;
}
void VirtualServer::registerInternalClient(std::shared_ptr<ConnectedClient> client) {
client->state = ConnectionState::CONNECTED;
client->state = ClientState::CONNECTED;
{
lock_guard lock(this->clients.lock);
if(client->getClientId() > 0) {
@@ -152,7 +152,7 @@ void VirtualServer::registerInternalClient(std::shared_ptr<ConnectedClient> clie
}
void VirtualServer::unregisterInternalClient(std::shared_ptr<ConnectedClient> client) {
client->state = ConnectionState::DISCONNECTED;
client->state = ClientState::DISCONNECTED;
{
auto client_id = client->getClientId();
@@ -259,14 +259,16 @@ void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target,
lock_guard command_lock(target->command_lock);
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
ChannelId old_channel_id{0};
if(target->currentChannel) {
old_channel_id = target->currentChannel->channelId();
for(const auto& client : this->getClients()) {
if(!client || client == target)
continue;
unique_lock client_channel_lock(client->channel_lock);
if(client->isClientVisible(target, false))
client->notifyClientLeftViewBanned(target, reason, invoker, time, false);
client->notifyClientLeftViewBanned(target, old_channel_id, invoker, time, false, reason);
}
auto s_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
@@ -275,7 +277,7 @@ void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target,
/* now disconnect the target itself */
unique_lock client_channel_lock(target->channel_lock);
target->notifyClientLeftViewBanned(target, reason, invoker, time, false);
target->notifyClientLeftViewBanned(target, old_channel_id, invoker, time, false, reason);
target->currentChannel = nullptr;
}
@@ -294,14 +296,16 @@ void VirtualServer::notify_client_kick(
lock_guard command_lock(target->command_lock);
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
ChannelId old_channel_id{0};
if(target->currentChannel) {
old_channel_id = target->currentChannel->channelId();
for(const auto& client : this->getClients()) {
if(!client || client == target)
continue;
unique_lock client_channel_lock(client->channel_lock);
if(client->isClientVisible(target, false))
client->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
client->notifyClientLeftViewKicked(target, old_channel_id, reason, invoker, false, nullptr);
}
auto s_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
@@ -310,7 +314,7 @@ void VirtualServer::notify_client_kick(
/* now disconnect the target itself */
unique_lock client_channel_lock(target->channel_lock);
target->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
target->notifyClientLeftViewKicked(target, old_channel_id, reason, invoker, false, nullptr);
target->currentChannel = nullptr;
}
}
+4 -3
View File
@@ -87,7 +87,8 @@ void VirtualServer::executeServerTick() {
if(clientOnline + queryOnline == 0) //We don't need to tick, when server is empty!
return;
properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = this->channelTree->channel_count();
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->generate_network_report().average_ping;
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->averagePing();
END_TIMINGS(timing_update_states);
}
@@ -151,7 +152,7 @@ void VirtualServer::executeServerTick() {
if(cl->clientPermissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
serverInstance->databaseHelper()->saveClientPermissions(this->serverId, cl->getClientDatabaseId(), cl->clientPermissions);
auto end = system_clock::now();
debugMessage(this->serverId, "Saved client permissions for client {} ({}) in {}ms", cl->getClientDatabaseId(), cl->getDisplayName(), duration_cast<milliseconds>(end - begin).count());
}
@@ -249,7 +250,7 @@ void VirtualServer::executeServerTick() {
auto permissions = group->permissions();
if(permissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveGroupPermissions(this->ref(), group->groupId(), permissions);
serverInstance->databaseHelper()->saveGroupPermissions(this->serverId, group->groupId(), permissions);
auto end = system_clock::now();
debugMessage(this->serverId, "Saved group permissions for group {} ({}) in {}ms", group->groupId(), group->name(), duration_cast<milliseconds>(end - begin).count());
}
+102 -143
View File
@@ -18,12 +18,13 @@
#include "./client/query/QueryClient.h"
#include "music/MusicBotManager.h"
#include "server/VoiceServer.h"
#include "src/server/file/LocalFileServer.h"
#include "server/file/FileServer.h"
#include "server/QueryServer.h"
#include "InstanceHandler.h"
#include "Configuration.h"
#include "VirtualServer.h"
#include "src/manager/ConversationManager.h"
#include "src/server/udp-server/UDPServer.h"
#include <misc/sassert.h>
using namespace std;
@@ -57,7 +58,7 @@ bool VirtualServer::initialize(bool test_properties) {
this->_voice_encryption_mode = prop.as<int>();
return;
}
std::string sql{};
std::string sql;
if(prop.type() == property::VIRTUALSERVER_HOST)
sql = "UPDATE `servers` SET `host` = :value WHERE `serverId` = :sid";
else if(prop.type() == property::VIRTUALSERVER_PORT)
@@ -129,7 +130,6 @@ bool VirtualServer::initialize(bool test_properties) {
return false;
}
channelTree->deleteSemiPermanentChannels();
if(channelTree->channel_count() == 0){
logMessage(this->serverId, "Creating new channel tree (Copy from server 0)");
LOG_SQL_CMD(sql::command(this->getSql(), "INSERT INTO `channels` (`serverId`, `channelId`, `type`, `parentId`) SELECT :serverId AS `serverId`, `channelId`, `type`, `parentId` FROM `channels` WHERE `serverId` = 0", variable{":serverId", this->serverId}).execute());
@@ -142,7 +142,7 @@ bool VirtualServer::initialize(bool test_properties) {
channelTree->loadChannelsFromDatabase();
if(channelTree->channel_count() == 0){
logCritical(this->serverId, "Failed to setup channel tree!");
return false;
return 0;
}
if(!channelTree->getDefaultChannel()) {
logError(this->serverId, "Missing default channel! Using first one!");
@@ -155,7 +155,7 @@ bool VirtualServer::initialize(bool test_properties) {
if(default_channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>())
default_channel->properties()[property::CHANNEL_FLAG_PASSWORD] = false;
this->tokenManager = new token::TokenManager(this);
this->tokenManager = new ts::server::tokens::TokenManager(this);
this->tokenManager->loadTokens();
this->complains = new ComplainManager(this);
@@ -181,7 +181,7 @@ bool VirtualServer::initialize(bool test_properties) {
letters = new letter::LetterManager(this);
serverStatistics = make_shared<stats::ConnectionStatistics>(serverInstance->getStatistics());
serverStatistics = make_shared<stats::ConnectionStatistics>(serverInstance->getStatistics(), true);
this->serverRoot = std::make_shared<InternalClient>(this->sql, self.lock(), this->properties()[property::VIRTUALSERVER_NAME].as<string>(), false);
static_pointer_cast<InternalClient>(this->serverRoot)->setSharedLock(this->serverRoot);
@@ -217,13 +217,12 @@ bool VirtualServer::initialize(bool test_properties) {
property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH,
property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH,
}) {
const auto& info = property::describe(type);
auto info = property::impl::info(type);
auto prop = this->properties()[type];
if(prop.default_value() == prop.value()) continue;
if(!info.validate_input(this->properties()[type].value())) {
this->properties()[type] = info.default_value;
logMessage(this->getServerId(), "Server property " + std::string{info.name} + " contains an invalid value! Resetting it.");
if(!info->validate_input(this->properties()[type].value())) {
this->properties()[type] = info->default_value;
logMessage(this->getServerId(), "Server property " + info->name + " contains an invalid value! Resetting it.");
}
}
@@ -315,64 +314,65 @@ bool VirtualServer::start(std::string& error) {
}
}
auto host = this->properties()[property::VIRTUALSERVER_HOST].as<string>();
if(config::binding::enforce_default_voice_host)
host = config::binding::DefaultVoiceHost;
{
auto host = this->properties()[property::VIRTUALSERVER_HOST].as<string>();
if(config::binding::enforce_default_voice_host)
host = config::binding::DefaultVoiceHost;
if(host.empty()){
error = "invalid host (\"" + host + "\")";
this->stop("failed to start", true);
return false;
}
if(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>() <= 0){
error = "invalid port";
this->stop("failed to start", true);
return false;
}
deque<shared_ptr<VoiceServerBinding>> bindings;
for(const auto& address : split_hosts(host, ',')) {
auto entry = make_shared<VoiceServerBinding>();
if(net::is_ipv4(address)) {
sockaddr_in addr{};
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
if(!evaluateAddress4(address, addr.sin_addr)) {
logError(this->serverId, "Fail to resolve v4 address info for \"{}\"", address);
continue;
}
memcpy(&entry->address, &addr, sizeof(addr));
} else if(net::is_ipv6(address)) {
sockaddr_in6 addr{};
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
if(!evaluateAddress6(address, addr.sin6_addr)) {
logError(this->serverId, "Fail to resolve v6 address info for \"{}\"", address);
continue;
}
memcpy(&entry->address, &addr, sizeof(addr));
} else {
logError(this->serverId, "Failed to determinate address type for \"{}\"", address);
continue;
if(host.empty()){
error = "invalid host (\"" + host + "\")";
this->stop("failed to start", true);
return false;
}
if(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>() <= 0){
error = "invalid port";
this->stop("failed to start", true);
return false;
}
bindings.push_back(entry);
}
if(bindings.empty()) {
error = "failed to resole any host!";
this->stop("failed to start", false);
return false;
}
//Setup voice server
udpVoiceServer = make_shared<VoiceServer>(self.lock());
if(!udpVoiceServer->start(bindings, error)) {
error = "could not start voice server. Message: " + error;
this->stop("failed to start", false);
return false;
deque<shared_ptr<VoiceServerBinding>> bindings;
for(const auto& address : split_hosts(host, ',')) {
auto entry = make_shared<VoiceServerBinding>();
if(net::is_ipv4(address)) {
sockaddr_in addr{};
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
if(!evaluateAddress4(address, addr.sin_addr)) {
logError(this->serverId, "Fail to resolve v4 address info for \"{}\"", address);
continue;
}
memcpy(&entry->address, &addr, sizeof(addr));
} else if(net::is_ipv6(address)) {
sockaddr_in6 addr{};
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
if(!evaluateAddress6(address, addr.sin6_addr)) {
logError(this->serverId, "Fail to resolve v6 address info for \"{}\"", address);
continue;
}
memcpy(&entry->address, &addr, sizeof(addr));
} else {
logError(this->serverId, "Failed to determinate address type for \"{}\"", address);
continue;
}
bindings.push_back(entry);
}
if(bindings.empty()) {
error = "failed to resole any host!";
this->stop("failed to start", false);
return false;
}
auto result = serverInstance->udp_server()->register_virtual_server(this);
if(result != server::udp::ServerRegisterResult::SUCCESS) {
error = "failed to start udp voice server (" + std::to_string((int) result) + ")";
this->stop("failed to start", false);
return false;
}
}
if(ts::config::web::activated && serverInstance->sslManager()->web_ssl_options()) {
@@ -503,9 +503,7 @@ void VirtualServer::stop(const std::string& reason, bool disconnect_query) {
this->musicManager->disconnectBots();
serverInstance->cancelExecute(this);
if(this->udpVoiceServer) this->udpVoiceServer->stop();
this->udpVoiceServer = nullptr;
serverInstance->udp_server()->unregister_virtual_server(this);
#ifdef COMPILE_WEB_CLIENT
if(this->webControlServer) this->webControlServer->stop();
@@ -639,30 +637,26 @@ std::shared_ptr<ConnectedClient> VirtualServer::findClient(std::string name, boo
}
bool VirtualServer::forEachClient(std::function<void(std::shared_ptr<ConnectedClient>)> function) {
for(const auto& elm : this->getClients()) {
shared_lock close_lock(elm->finalDisconnectLock, try_to_lock_t{});
if(close_lock.owns_lock()) //If not locked than client is on the way to disconnect
if(elm->state == ConnectionState::CONNECTED && elm->getType() != ClientType::CLIENT_INTERNAL) {
function(elm);
}
}
for(const auto& elm : this->getClients())
if(elm->state == ClientState::CONNECTED && elm->getType() != ClientType::CLIENT_INTERNAL)
function(elm);
return true;
}
std::vector<std::shared_ptr<ConnectedClient>> VirtualServer::getClients() {
vector<shared_ptr<ConnectedClient>> clients;
vector<shared_ptr<ConnectedClient>> result{};
{
lock_guard lock(this->clients.lock);
clients.reserve(this->clients.count);
result.reserve(this->clients.count);
for(auto& client : this->clients.clients) {
if(!client) continue;
clients.push_back(client);
result.push_back(client);
}
}
return clients;
return result;
}
deque<shared_ptr<ConnectedClient>> VirtualServer::getClientsByChannel(std::shared_ptr<BasicChannel> channel) {
@@ -679,7 +673,7 @@ deque<shared_ptr<ConnectedClient>> VirtualServer::getClientsByChannel(std::share
for(const auto& weak_client : weak_clients) {
auto client = weak_client.lock();
if(!client) continue;
if(client->connectionState() != ConnectionState::CONNECTED) continue;
if(client->connectionState() != ClientState::CONNECTED) continue;
if(client->getChannel() != channel) continue; /* to be sure */
result.push_back(move(client));
@@ -713,8 +707,8 @@ bool VirtualServer::notifyServerEdited(std::shared_ptr<ConnectedClient> invoker,
cmd["invokeruid"] = invoker->getUid();
cmd["reasonid"] = ViewReasonId::VREASON_EDITED;
for(const auto& key : keys) {
const auto& info = property::find<property::VirtualServerProperties>(key);
if(info == property::VIRTUALSERVER_UNDEFINED) {
auto info = property::impl::info<property::VirtualServerProperties>(key);
if(*info == property::VIRTUALSERVER_UNDEFINED) {
logError(this->getServerId(), "Tried to broadcast a server update with an unknown info: " + key);
continue;
}
@@ -726,10 +720,10 @@ bool VirtualServer::notifyServerEdited(std::shared_ptr<ConnectedClient> invoker,
return true;
}
bool VirtualServer::notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient> client, const deque<const property::PropertyDescription*>& keys, bool selfNotify) {
if(keys.empty() || !client) return false;
bool VirtualServer::notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient> client, const deque<shared_ptr<property::PropertyDescription>>& keys, bool selfNotify) {
if(keys.empty()) return false;
this->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
shared_lock client_channel_lock(cl->channel_lock);
shared_lock client_channel_lock(client->channel_lock);
if(cl->isClientVisible(client, false) || (cl == client && selfNotify))
cl->notifyClientUpdated(client, keys, false);
});
@@ -794,7 +788,7 @@ vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlagge
}
if(!cache->client_permissions) {
cache->client_permissions = serverInstance->databaseHelper()->loadClientPermissionManager(self.lock(), client_dbid);
cache->client_permissions = serverInstance->databaseHelper()->loadClientPermissionManager(this->serverId, client_dbid);
}
bool have_skip_permission = false;
@@ -1019,33 +1013,35 @@ bool VirtualServer::verifyServerPassword(std::string password, bool hashed) {
return password == this->properties()[property::VIRTUALSERVER_PASSWORD].as<std::string>();
}
VirtualServer::NetworkReport VirtualServer::generate_network_report() {
double total_ping{0}, total_loss{0};
size_t pings_counted{0}, loss_counted{0};
float VirtualServer::averagePacketLoss() {
//TODO Average packet loss
return 0.f;
}
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& client) {
if(auto vc = dynamic_pointer_cast<VoiceClient>(client); vc) {
total_ping += vc->current_ping().count();
total_loss += vc->current_packet_loss();
pings_counted++;
loss_counted++;
float VirtualServer::averagePing() {
float count = 0;
float sum = 0;
this->forEachClient([&count, &sum](shared_ptr<ConnectedClient> client) {
auto type = client->getType();
if(type == ClientType::CLIENT_TEAMSPEAK || type == ClientType::CLIENT_TEASPEAK) {
count++;
sum += duration_cast<milliseconds>(dynamic_pointer_cast<VoiceClient>(client)->calculatePing()).count();
}
#ifdef COMPILE_WEB_CLIENT
else if(client->getType() == ClientType::CLIENT_WEB) {
pings_counted++;
total_ping += duration_cast<milliseconds>(dynamic_pointer_cast<WebClient>(client)->client_ping()).count();
else if(type == ClientType::CLIENT_WEB) {
count++;
sum += duration_cast<milliseconds>(dynamic_pointer_cast<WebClient>(client)->client_ping()).count();
}
#endif
});
VirtualServer::NetworkReport result{};
if(loss_counted) result.average_loss = total_loss / loss_counted;
if(pings_counted) result.average_ping = total_ping / pings_counted;
return result;
if(count == 0) return 0;
return sum / count;
}
bool VirtualServer::resetPermissions(std::string& token) {
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` != :channel_type", variable{":serverId", this->serverId}, variable{":channel_type", permission::SQL_PERM_CHANNEL}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
@@ -1108,7 +1104,7 @@ bool VirtualServer::resetPermissions(std::string& token) {
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_guest->name()).front()->groupId();
auto token_admin = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_admin->name()).front()->groupId();
auto created = this->tokenManager->createToken(token::TOKEN_SERVER, token_admin, "Default server token for the server admin.");
auto created = this->tokenManager->createToken(ts::server::tokens::TOKEN_SERVER, token_admin, "Default server token for the server admin.");
if(!created) {
logCritical(this->serverId, "Failed to generate default serveradmin token!");
} else {
@@ -1189,7 +1185,7 @@ void VirtualServer::send_text_message(const std::shared_ptr<BasicChannel> &chann
bool conversation_private = channel->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as<bool>();
auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>();
for(const auto& client : this->getClients()) {
if(client->connectionState() != ConnectionState::CONNECTED)
if(client->connectionState() != ClientState::CONNECTED)
continue;
auto type = client->getType();
@@ -1217,41 +1213,4 @@ void VirtualServer::send_text_message(const std::shared_ptr<BasicChannel> &chann
auto conversation = conversations->get_or_create(channel->channelId());
conversation->register_message(sender->getClientDatabaseId(), sender->getUid(), sender->getDisplayName(), now, message);
}
}
void VirtualServer::update_channel_from_permissions(const std::shared_ptr<BasicChannel> &channel, const std::shared_ptr<ConnectedClient>& issuer) {
bool require_view_update;
auto property_updates = channel->update_properties_from_permissions(require_view_update);
if(!property_updates.empty()) {
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& cl) {
shared_lock client_channel_lock(cl->channel_lock);
cl->notifyChannelEdited(channel, property_updates, issuer, false);
});
}
if(require_view_update) {
auto l_source = this->channelTree->findLinkedChannel(channel->channelId());
this->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
/* server tree read lock still active */
auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId());
sassert(l_source);
if(cl->currentChannel) sassert(l_target);
{
unique_lock client_channel_lock(cl->channel_lock);
deque<ChannelId> deleted;
for(const auto& [flag_visible, channel] : cl->channels->update_channel(l_source, l_target)) {
if(flag_visible) {
cl->notifyChannelShow(channel->channel(), channel->previous_channel);
} else {
deleted.push_back(channel->channelId());
}
}
if(!deleted.empty())
cl->notifyChannelHide(deleted, false);
}
});
}
}
+8 -17
View File
@@ -58,7 +58,7 @@ namespace ts {
class InstanceHandler;
class VoiceServer;
class QueryServer;
class LocalFileServer;
class FileServer;
class SpeakingClient;
class WebControlServer;
@@ -106,7 +106,7 @@ namespace ts {
bool global_skip = false;
bool global_skip_set = false;
std::shared_ptr<permission::v2::PermissionManager> client_permissions;
std::shared_ptr<permission::v2::PermissionRegister> client_permissions;
std::vector<std::shared_ptr<GroupAssignment>> assignment_server_groups;
bool assignment_server_groups_set = false;
@@ -136,11 +136,6 @@ namespace ts {
friend class InstanceHandler;
friend class VirtualServerManager;
public:
struct NetworkReport {
float average_ping{0};
float average_loss{0};
};
VirtualServer(ServerId serverId, sql::SqlManager*);
~VirtualServer();
@@ -187,11 +182,11 @@ namespace ts {
inline GroupManager* getGroupManager() { return this->groups; }
bool notifyServerEdited(std::shared_ptr<ConnectedClient>, std::deque<std::string> keys);
bool notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient>, const std::deque<const property::PropertyDescription*>& keys, bool selfNotify = true); /* execute only with at least channel tree read lock! */
bool notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient>, const std::deque<std::shared_ptr<property::PropertyDescription>>& keys, bool selfNotify = true); /* execute only with at least channel tree read lock! */
inline bool notifyClientPropertyUpdates(const std::shared_ptr<ConnectedClient>& client, const std::deque<property::ClientProperties>& keys, bool selfNotify = true) {
if(keys.empty()) return false;
std::deque<const property::PropertyDescription*> _keys{};
for(const auto& key : keys) _keys.push_back(&property::describe(key));
std::deque<std::shared_ptr<property::PropertyDescription>> _keys;
for(const auto& key : keys) _keys.push_back(property::impl::info<property::ClientProperties>(key));
return this->notifyClientPropertyUpdates(client, _keys, selfNotify);
};
@@ -209,7 +204,6 @@ namespace ts {
std::shared_ptr<stats::ConnectionStatistics> getServerStatistics(){ return serverStatistics; }
std::shared_ptr<VoiceServer> getVoiceServer(){ return this->udpVoiceServer; }
WebControlServer* getWebServer(){ return this->webControlServer; }
/* calculate permissions for an client in this server */
@@ -235,7 +229,8 @@ namespace ts {
void testBanStateChange(const std::shared_ptr<ConnectedClient>& invoker);
[[nodiscard]] NetworkReport generate_network_report();
float averagePing();
float averagePacketLoss();
bool resetPermissions(std::string&);
void ensureValidDefaultGroups();
@@ -278,9 +273,6 @@ namespace ts {
inline int voice_encryption_mode() { return this->_voice_encryption_mode; }
inline std::shared_ptr<conversation::ConversationManager> conversation_manager() { return this->_conversation_manager; }
void update_channel_from_permissions(const std::shared_ptr<BasicChannel>& /* channel */, const std::shared_ptr<ConnectedClient>& /* issuer */);
protected:
bool registerClient(std::shared_ptr<ConnectedClient>);
bool unregisterClient(std::shared_ptr<ConnectedClient>, std::string, std::unique_lock<std::shared_mutex>& channel_tree_lock);
@@ -295,9 +287,8 @@ namespace ts {
std::chrono::system_clock::time_point lastTick;
void executeServerTick();
std::shared_ptr<VoiceServer> udpVoiceServer = nullptr;
WebControlServer* webControlServer = nullptr;
token::TokenManager* tokenManager = nullptr;
ts::server::tokens::TokenManager* tokenManager = nullptr;
ComplainManager* complains = nullptr;
letter::LetterManager* letters = nullptr;
std::shared_ptr<music::MusicBotManager> musicManager;
+24 -65
View File
@@ -4,7 +4,7 @@
#include "src/server/VoiceServer.h"
#include "src/client/query/QueryClient.h"
#include "InstanceHandler.h"
#include "src/server/file/LocalFileServer.h"
#include "src/server/file/FileServer.h"
#include "src/client/ConnectedClient.h"
#include <ThreadPool/ThreadHelper.h>
@@ -13,7 +13,7 @@ using namespace std::chrono;
using namespace ts::server;
VirtualServerManager::VirtualServerManager(InstanceHandler* handle) : handle(handle) {
this->puzzles = new udp::PuzzleManager{};
this->puzzles = new server::udp::PuzzleManager();
this->handshakeTickers = new threads::Scheduler(1, "handshake ticker");
this->execute_loop = new event::EventExecutor("executor #");
//this->join_loop = new event::EventExecutor("joiner #");
@@ -67,8 +67,7 @@ bool VirtualServerManager::initialize(bool autostart) {
this->state = State::STARTING;
logMessage(LOG_INSTANCE, "Generating server puzzles...");
auto start = system_clock::now();
if(!this->puzzles->precompute_puzzles(config::voice::DefaultPuzzlePrecomputeSize))
logCritical(LOG_INSTANCE, "Failed to precompute RSA puzzles");
this->puzzles->precompute_puzzles(config::voice::DefaultPuzzlePrecomputeSize);
logMessage(LOG_INSTANCE, "Puzzles generated! Time required: " + to_string(duration_cast<milliseconds>(system_clock::now() - start).count()) + "ms");
size_t serverCount = 0;
@@ -201,11 +200,9 @@ shared_ptr<VirtualServer> VirtualServerManager::findServerByPort(uint16_t port)
return nullptr;
}
uint16_t VirtualServerManager::next_available_port(const std::string& host_string) {
uint16_t VirtualServerManager::next_available_port() {
auto instances = this->serverInstances();
std::vector<uint16_t> unallowed_ports{};
unallowed_ports.reserve(instances.size());
deque<uint16_t> unallowed_ports;
for(const auto& instance : instances) {
unallowed_ports.push_back(instance->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
@@ -216,39 +213,18 @@ uint16_t VirtualServerManager::next_available_port(const std::string& host_strin
}
}
}
auto bindings = net::resolve_bindings(host_string, 0);
uint16_t port = config::voice::default_voice_port;
while(true) {
if(port < 1024) goto next_port;
if(port < 1024) goto c;
for(auto& p : unallowed_ports) {
if(p == port)
goto next_port;
}
for(auto& binding : bindings) {
if(!std::get<2>(binding).empty()) continue; /* error on that */
auto& baddress = std::get<1>(binding);
auto& raw_port = baddress.ss_family == AF_INET ? ((sockaddr_in*) &baddress)->sin_port : ((sockaddr_in6*) &baddress)->sin6_port;
raw_port = htons(port);
switch (net::address_available(baddress, net::binding_type::TCP)) {
case net::binding_result::ADDRESS_USED:
goto next_port;
default:
break; /* if we've an internal error we ignore it */
}
switch (net::address_available(baddress, net::binding_type::UDP)) {
case net::binding_result::ADDRESS_USED:
goto next_port;
default:
break; /* if we've an internal error we ignore it */
}
goto c;
}
break;
next_port:
c:
port++;
}
return port;
@@ -340,9 +316,8 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
if(!sid_success)
return nullptr;
this->delete_server_in_db(serverId); /* just to ensure */
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", hosts}, variable{":port", port}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
//`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT
auto prop_copy = sql::command(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :target_sid AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `type` = :type AND `id` = 0 AND `serverId` = 0;",
variable{":target_sid", serverId},
variable{":type", property::PROP_TYPE_SERVER}).execute();
@@ -418,8 +393,21 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
}
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
this->delete_server_in_db(server->serverId);
sql::command(this->handle->getSql(), "DELETE FROM `tokens` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `properties` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `permissions` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `groups` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `clients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `channels` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `bannedClients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `servers` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `musicbots` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `bannedClients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `ban_trigger` WHERE `server_id` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
this->handle->getFileServer()->deleteServer(server);
return true;
}
@@ -456,35 +444,6 @@ void VirtualServerManager::tickHandshakeClients() {
for(const auto& server : this->serverInstances()) {
auto vserver = server->getVoiceServer();
if(vserver)
vserver->tickHandshakingClients();
vserver->tickClients();
}
}
void VirtualServerManager::delete_server_in_db(ts::ServerId server_id) {
#define execute_delete(statement) \
result = sql::command(this->handle->getSql(), statement, variable{":sid", server_id}).execute(); \
if(!result) { \
logWarning(LOG_INSTANCE, "Failed to execute SQL command {}: {}", statement, result.fmtStr()); \
result = sql::result{}; \
}
sql::result result{};
execute_delete("DELETE FROM `tokens` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `properties` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `permissions` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `clients` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `channels` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `bannedClients` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `ban_trigger` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `groups` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `assignedGroups` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `servers` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `musicbots` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `conversations` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `conversation_blocks` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `playlists` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `playlist_songs` WHERE `serverId` = :sid");
}
+73 -82
View File
@@ -2,107 +2,98 @@
#include <deque>
#include <EventLoop.h>
#include "client/voice/PrecomputedPuzzles.h"
#include "src/server/udp-server/PrecomputedPuzzles.h"
#include "server/VoiceIOManager.h"
#include "VirtualServer.h"
#include <query/command3.h>
#include "snapshots/snapshot.h"
namespace ts::server {
class InstanceHandler;
namespace ts {
namespace server {
class InstanceHandler;
struct ServerReport {
size_t avariable;
size_t online;
struct ServerReport {
size_t avariable;
size_t online;
size_t slots;
size_t onlineClients;
size_t onlineChannels;
};
class VirtualServerManager {
public:
enum State {
STOPPED,
STARTING,
STARTED,
STOPPING
};
size_t slots;
size_t onlineClients;
size_t onlineChannels;
};
class VirtualServerManager {
public:
enum State {
STOPPED,
STARTING,
STARTED,
STOPPING
};
explicit VirtualServerManager(InstanceHandler*);
~VirtualServerManager();
explicit VirtualServerManager(InstanceHandler*);
~VirtualServerManager();
bool initialize(bool execute_autostart = true);
bool initialize(bool execute_autostart = true);
std::shared_ptr<VirtualServer> create_server(std::string hosts, uint16_t port);
bool deleteServer(std::shared_ptr<VirtualServer>);
std::shared_ptr<VirtualServer> create_server(std::string hosts, uint16_t port);
bool deleteServer(std::shared_ptr<VirtualServer>);
std::shared_ptr<VirtualServer> findServerById(ServerId);
std::shared_ptr<VirtualServer> findServerByPort(uint16_t);
uint16_t next_available_port(const std::string& /* host string */);
ServerId next_available_server_id(bool& /* success */);
std::shared_ptr<VirtualServer> findServerById(ServerId);
std::shared_ptr<VirtualServer> findServerByPort(uint16_t);
uint16_t next_available_port();
ServerId next_available_server_id(bool& /* success */);
std::deque<std::shared_ptr<VirtualServer>> serverInstances(){
threads::MutexLock l(this->instanceLock);
return instances;
}
std::deque<std::shared_ptr<VirtualServer>> serverInstances(){
threads::MutexLock l(this->instanceLock);
return instances;
}
ServerReport report();
OnlineClientReport clientReport();
size_t runningServers();
size_t usedSlots();
ServerReport report();
OnlineClientReport clientReport();
size_t runningServers();
size_t usedSlots();
void executeAutostart();
void shutdownAll(const std::string&);
void executeAutostart();
void shutdownAll(const std::string&);
//Dotn use shared_ptr references to keep sure that they be hold in memory
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
//Dotn use shared_ptr references to keep sure that they be hold in memory
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
bool deploy_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
server::udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
event::EventExecutor* get_join_loop() { return this->join_loop; }
event::EventExecutor* get_executor_loop() { return this->execute_loop; }
event::EventExecutor* get_join_loop() { return this->join_loop; }
event::EventExecutor* get_executor_loop() { return this->execute_loop; }
inline void adjust_executor_threads() {
std::unique_lock instance_lock(this->instanceLock);
auto instance_count = this->instances.size();
instance_lock.unlock();
inline void adjust_executor_threads() {
std::unique_lock instance_lock(this->instanceLock);
auto instance_count = this->instances.size();
instance_lock.unlock();
auto threads = std::min(config::threads::voice::execute_per_server * instance_count, config::threads::voice::execute_limit);
this->execute_loop->threads(threads);
}
io::VoiceIOManager* ioManager(){ return this->_ioManager; }
auto threads = std::min(config::threads::voice::execute_per_server * instance_count, config::threads::voice::execute_limit);
this->execute_loop->threads(threads);
}
io::VoiceIOManager* ioManager(){ return this->_ioManager; }
threads::Mutex server_create_lock;
threads::Mutex server_create_lock;
State getState() { return this->state; }
private:
State state = State::STOPPED;
InstanceHandler* handle;
threads::Mutex instanceLock;
std::deque<std::shared_ptr<VirtualServer>> instances;
server::udp::PuzzleManager* puzzles = nullptr;
State getState() { return this->state; }
private:
State state = State::STOPPED;
InstanceHandler* handle;
threads::Mutex instanceLock;
std::deque<std::shared_ptr<VirtualServer>> instances;
udp::PuzzleManager* puzzles{nullptr};
event::EventExecutor* execute_loop = nullptr;
event::EventExecutor* join_loop = nullptr;
threads::Scheduler* handshakeTickers = nullptr;
io::VoiceIOManager* _ioManager = nullptr;
event::EventExecutor* execute_loop = nullptr;
event::EventExecutor* join_loop = nullptr;
threads::Scheduler* handshakeTickers = nullptr;
io::VoiceIOManager* _ioManager = nullptr;
struct {
std::thread executor{};
std::condition_variable condition;
std::mutex lock;
} acknowledge;
struct {
std::thread executor{};
std::condition_variable condition;
std::mutex lock;
} acknowledge;
void tickHandshakeClients();
void delete_server_in_db(ServerId /* server id */);
/* methods used to preprocess a snapshot */
bool deploy_ts3_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
bool deploy_teaspeak_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
/* actual deploy method */
bool deploy_raw_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */, const std::string& /* hash */, size_t /* offset */, snapshots::type /* type */, snapshots::version_t /* version */);
};
void tickHandshakeClients();
};
}
}
+6 -3
View File
@@ -5,7 +5,7 @@
#include "misc/rnd.h"
#include "src/VirtualServer.h"
#include "src/client/ConnectedClient.h"
#include "src/server/file/LocalFileServer.h"
#include "src/server/file/FileServer.h"
#include "src/InstanceHandler.h"
#include "../manager/ConversationManager.h"
@@ -102,7 +102,7 @@ std::shared_ptr<BasicChannel> ServerChannelTree::createChannel(ChannelId parentI
channel->properties()[property::CHANNEL_LAST_LEFT] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
auto result = sql::command(this->sql, "INSERT INTO `channels` (`serverId`, `channelId`, `parentId`) VALUES(:sid, :chid, :parent);", variable{":sid", this->getServerId()}, variable{":chid", channel->channelId()}, variable{":parent", channel->parent() ? channel->parent()->channelId() : 0}).execute();
auto result = sql::command(this->sql, "INSERT INTO `channels` (`serverId`, `channelId`, `type`, `parentId`) VALUES(:sid, :chid, :type, :parent);", variable{":sid", this->getServerId()}, variable{":chid", channel->channelId()}, variable{":type", channel->channelType()}, variable{":parent", channel->parent() ? channel->parent()->channelId() : 0}).execute();
auto pf = LOG_SQL_CMD;
pf(result);
@@ -493,7 +493,7 @@ bool ServerChannelTree::validateChannelIcons() {
}
void ServerChannelTree::loadChannelsFromDatabase() {
auto res = sql::command(this->sql, "SELECT `channelId`, `parentId` FROM `channels` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).query(&ServerChannelTree::loadChannelFromData, this);
auto res = sql::command(this->sql, "SELECT * FROM `channels` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).query(&ServerChannelTree::loadChannelFromData, this);
(LOG_SQL_CMD)(res);
if(!res){
logError(this->getServerId(), "Could not load channel tree from database");
@@ -512,6 +512,7 @@ void ServerChannelTree::loadChannelsFromDatabase() {
int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column) {
ChannelId channelId = 0;
ChannelId parentId = 0;
auto type = static_cast<ChannelType::ChannelType>(0xFF);
int index = 0;
@@ -519,6 +520,8 @@ int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column)
for(index = 0; index < argc; index++){
if(strcmp(column[index], "channelId") == 0) channelId = static_cast<ChannelId>(stoll(data[index]));
else if(strcmp(column[index], "parentId") == 0) parentId = static_cast<ChannelId>(stoll(data[index]));
else if(strcmp(column[index], "type") == 0) type = static_cast<ChannelType::ChannelType>(stoll(data[index]));
else if(strcmp(column[index], "serverId") == 0) {}
else logError(this->getServerId(), "ServerChannelTree::loadChannelFromData called with invalid column from sql \"{}\"", column[index]);
}
} catch (std::exception& ex) {
+3 -3
View File
@@ -1,11 +1,11 @@
#pragma once
#include <lock/rw_mutex.h>
#include <stdint.h>
#include <cstdlib>
#include "Properties.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "BasicChannel.h"
#include "../Group.h"
#include <memory>
#include <sql/SqlQuery.h>
@@ -45,7 +45,7 @@ namespace ts {
void deleteSemiPermanentChannels();
std::shared_ptr<LinkedTreeEntry> tree_head() { return this->head; }
[[nodiscard]] std::shared_ptr<LinkedTreeEntry> tree_head() { return this->head; }
protected:
virtual ChannelId generateChannelId() override;
+69 -96
View File
@@ -11,7 +11,7 @@
#include "src/VirtualServer.h"
#include "voice/VoiceClient.h"
#include "../server/VoiceServer.h"
#include "src/server/file/LocalFileServer.h"
#include "../server/file/FileServer.h"
#include "../InstanceHandler.h"
#include "ConnectedClient.h"
@@ -21,7 +21,7 @@ using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
extern ts::server::InstanceHandler* serverInstance;
@@ -29,7 +29,9 @@ ConnectedClient::ConnectedClient(sql::SqlManager* db, const std::shared_ptr<Virt
memtrack::allocated<ConnectedClient>(this);
memset(&this->remote_address, 0, sizeof(this->remote_address));
connectionStatistics = make_shared<stats::ConnectionStatistics>(server ? server->getServerStatistics() : nullptr);
connectionStatistics = make_shared<stats::ConnectionStatistics>(server ? server->getServerStatistics() : nullptr, false);
this->connectionStatistics->measure_bandwidths(false); /* done by the client and we trust this */
channels = make_shared<ClientChannelView>(this);
}
@@ -182,17 +184,14 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool
client_channel_lock.lock();
}
/* might have been changed since we locked the tree */
if(this->currentChannel) {
deque<ChannelId> deleted;
for(const auto& update_entry : this->channels->update_channel_path(server_ref->channelTree->tree_head(), server_ref->channelTree->find_linked_entry(this->currentChannel->channelId()))) {
if(update_entry.first)
this->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel);
else deleted.push_back(update_entry.second->channelId());
}
if(!deleted.empty())
this->notifyChannelHide(deleted, false); /* we've locked the tree before */
deque<ChannelId> deleted;
for(const auto& update_entry : this->channels->update_channel_path(server_ref->channelTree->tree_head(), server_ref->channelTree->find_linked_entry(this->currentChannel->channelId()))) {
if(update_entry.first)
this->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel);
else deleted.push_back(update_entry.second->channelId());
}
if(!deleted.empty())
this->notifyChannelHide(deleted, false); /* we've locked the tree before */
}
}
@@ -382,7 +381,7 @@ bool ConnectedClient::notifyClientLeftView(
std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree) {
assert(!lock_channel_tree); /* not supported yet! */
assert(client == this || (client && client->getClientId() != 0));
assert(client && client->getClientId() != 0);
assert(client->currentChannel || &*client == this);
if(client != this) {
@@ -452,11 +451,10 @@ bool ConnectedClient::notifyClientLeftView(
return true;
}
bool ConnectedClient::notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client,
const std::shared_ptr<BasicChannel> &target_channel,
const std::string& message,
std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree) {
bool ConnectedClient::notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
const std::string &message, std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree,
const std::shared_ptr<BasicChannel> &target_channel) {
assert(!lock_channel_tree); /* not supported yet! */
assert(client && client->getClientId() != 0);
assert(client->currentChannel || &*client == this);
@@ -480,7 +478,7 @@ bool ConnectedClient::notifyClientLeftViewKicked(const std::shared_ptr<Connected
Command cmd("notifyclientleftview");
cmd["clid"] = client->getClientId();
cmd["cfid"] = client->currentChannel ? client->currentChannel->channelId() : 0;
cmd["cfid"] = channel_from;
cmd["ctid"] = target_channel ? target_channel->channelId() : 0;
cmd["reasonid"] = (uint8_t) (target_channel ? ViewReasonId::VREASON_CHANNEL_KICK : ViewReasonId::VREASON_SERVER_KICK);
cmd["reasonmsg"] = message;
@@ -495,12 +493,10 @@ bool ConnectedClient::notifyClientLeftViewKicked(const std::shared_ptr<Connected
return true;
}
bool ConnectedClient::notifyClientLeftViewBanned(
const shared_ptr<ConnectedClient> &client,
const std::string& message,
std::shared_ptr<ConnectedClient> invoker,
size_t length,
bool lock_channel_tree) {
bool ConnectedClient::notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
std::shared_ptr<ConnectedClient> invoker, size_t length,
bool lock_channel_tree,
const std::string &message) {
assert(!lock_channel_tree); /* not supported yet! */
assert(client && client->getClientId() != 0);
@@ -509,7 +505,7 @@ bool ConnectedClient::notifyClientLeftViewBanned(
Command cmd("notifyclientleftview");
cmd["clid"] = client->getClientId();
cmd["cfid"] = client->currentChannel ? client->currentChannel->channelId() : 0;
cmd["cfid"] = channel_from;
cmd["ctid"] = 0;
cmd["reasonid"] = ViewReasonId::VREASON_BAN;
cmd["reasonmsg"] = message;
@@ -539,7 +535,7 @@ bool ConnectedClient::notifyClientLeftViewBanned(
}
bool ConnectedClient::sendNeededPermissions(bool enforce) {
if(!enforce && this->state != ConnectionState::CONNECTED) return false;
if(!enforce && this->state != ClientState::CONNECTED) return false;
if(!enforce && chrono::system_clock::now() - this->lastNeededNotify < chrono::seconds(5) && this->lastNeededPermissionNotifyChannel == this->currentChannel) { //Dont spam these (hang up ui)
this->requireNeededPermissionResend = true;
@@ -564,6 +560,10 @@ bool ConnectedClient::notifyClientNeededPermissions() {
cmd[index]["permid"] = value.first;
cmd[index++]["permvalue"] = value.second.has_value ? value.second.value : 0;
}
if(index == 0) {
cmd[index]["permid"] = permission::i_client_talk_power;
cmd[index++]["permvalue"] = 0;
}
if(index == 0) {
cmd[index]["permid"] = permission::i_client_talk_power;
@@ -574,66 +574,27 @@ bool ConnectedClient::notifyClientNeededPermissions() {
return true;
}
inline void write_command_result_error(ts::command_builder_bulk bulk, const command_result& result) {
bulk.put_unchecked("id", (uint32_t) result.error_code());
bulk.put_unchecked("msg", findError(result.error_code()).message);
if(result.is_permission_error())
bulk.put_unchecked("failed_permid", (uint32_t) result.permission_id());
}
inline void write_command_result_detailed(ts::command_builder_bulk bulk, const command_result& result) {
auto details = result.details();
bulk.put_unchecked("id", (uint32_t) details->error_id);
bulk.put_unchecked("msg", findError(details->error_id).message);
for(const auto& extra : details->extra_properties)
bulk.put(extra.first, extra.second);
}
bool ConnectedClient::notifyError(const command_result& result, const std::string& retCode) {
ts::command_builder command{"error"};
Command cmd("error");
switch(result.type()) {
case command_result_type::error:
write_command_result_error(command.bulk(0), result);
break;
case command_result_type::detailed:
write_command_result_detailed(command.bulk(0), result);
break;
if(result.is_detailed()) {
auto detailed = result.details();
cmd["id"] = (int) detailed->error_id;
cmd["msg"] = findError(detailed->error_id).message;
case command_result_type::bulked: {
auto bulks = result.bulks();
command.reserve_bulks(bulks->size());
for(size_t index{0}; index < bulks->size(); index++) {
auto& entry = bulks->at(index);
switch (entry.type()) {
case command_result_type::error:
write_command_result_error(command.bulk(index), entry);
break;
case command_result_type::detailed:
write_command_result_detailed(command.bulk(index), entry);
break;
case command_result_type::bulked:
assert(false);
break;
}
}
if(bulks->empty()) {
logWarning(this->getServerId(), "{} Trying to send empty error bulk.", CLIENT_STR_LOG_PREFIX_(this));
command.put_unchecked(0, "id", (uint32_t) error::ok);
command.put_unchecked(0, "msg", findError(error::ok).message);
}
break;
}
default:
assert(false);
break;
for(const auto& extra : detailed->extra_properties)
cmd[extra.first] = extra.second;
} else {
cmd["id"] = (int) result.error_code();
cmd["msg"] = findError(result.error_code()).message;
if(result.is_permission_error())
cmd["failed_permid"] = result.permission_id();
}
if(!retCode.empty())
command.put_unchecked(0, "return_code", retCode);
if(retCode.length() > 0)
cmd["return_code"] = retCode;
this->sendCommand(command);
this->sendCommand(cmd);
return true;
}
@@ -675,7 +636,12 @@ inline void send_channels(ConnectedClient* client, ChannelIT begin, const Channe
break;
}
client->sendCommand(builder);
if(dynamic_cast<VoiceClient*>(client)) {
auto vc = dynamic_cast<VoiceClient*>(client);
vc->sendCommand0(builder.build(), false, true); /* we need to process this command directly so it will be processed before the channellistfinished stuff */
} else {
client->sendCommand(builder);
}
if(begin != end)
send_channels(client, begin, end, override_orderid);
}
@@ -754,7 +720,7 @@ void ConnectedClient::sendChannelDescription(const std::shared_ptr<BasicChannel>
void ConnectedClient::tick(const std::chrono::system_clock::time_point &time) {
ALARM_TIMER(A1, "ConnectedClient::tick", milliseconds(2));
if(this->state == ConnectionState::CONNECTED) {
if(this->state == ClientState::CONNECTED) {
if(this->requireNeededPermissionResend)
this->sendNeededPermissions(false);
if(this->lastOnlineTimestamp.time_since_epoch().count() == 0) {
@@ -779,14 +745,17 @@ void ConnectedClient::tick(const std::chrono::system_clock::time_point &time) {
}
this->connectionStatistics->tick();
if(this->last_statistics_tick + seconds(5) < time) {
this->last_statistics_tick = time;
this->connectionStatistics->tick();
}
}
void ConnectedClient::sendServerInit() {
Command command("initserver");
for(const auto& prop : this->server->properties().list_properties(property::FLAG_SERVER_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
command[std::string{prop.type().name}] = prop.value();
command[prop.type().name] = prop.value();
}
command["virtualserver_maxclients"] = 32;
@@ -816,7 +785,11 @@ void ConnectedClient::sendServerInit() {
command["pv"] = 6; //Protocol version
command["acn"] = this->getDisplayName();
command["aclid"] = this->getClientId();
this->sendCommand(command);
if(dynamic_cast<VoiceClient*>(this)) {
dynamic_cast<VoiceClient*>(this)->sendCommand0(command.build(), false, true); /* process it directly so the order for the channellist entries is ensured. (First serverinit then everything else) */
} else {
this->sendCommand(command);
}
}
bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
@@ -828,14 +801,14 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
command_result result;
try {
result.reset(this->handleCommand(cmd));
result = this->handleCommand(cmd);
} catch(invalid_argument& ex){
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what());
if(disconnectOnFail) {
this->disconnect("Invalid argument (" + string(ex.what()) + ")");
return false;
} else {
result.reset(command_result{error::parameter_convert, ex.what()});
result = command_result{error::parameter_convert};
}
} catch (exception& ex) {
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received exception with message: {}", CLIENT_STR_LOG_PREFIX, ex.what());
@@ -843,7 +816,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
this->disconnect("Error while command handling (" + string(ex.what()) + ")!");
return false;
} else {
result.reset(command_result{error::vs_critical});
result = command_result{error::vs_critical};
}
} catch (...) {
this->disconnect("Error while command handling! (unknown)");
@@ -851,7 +824,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
}
bool generateReturnStatus = false;
if(result.has_error() || this->getType() == ClientType::CLIENT_QUERY){
if(result.error_code() != error::ok || this->getType() == ClientType::CLIENT_QUERY){
generateReturnStatus = true;
} else if(cmd["return_code"].size() > 0) {
generateReturnStatus = !cmd["return_code"].string().empty();
@@ -860,7 +833,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
if(generateReturnStatus)
this->notifyError(result, cmd["return_code"].size() > 0 ? cmd["return_code"].first().as<std::string>() : "");
if(result.has_error() && this->state == ConnectionState::INIT_HIGH)
if(result.error_code() != error::ok && this->state == ClientState::INITIALIZING)
this->close_connection(system_clock::now()); //Disconnect now
for (const auto& handler : postCommandHandler)
@@ -874,17 +847,17 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
else
logWarning(this->getServerId(), "Command handling of command {} needs {}ms.", cmd.command(), duration_cast<milliseconds>(end - start).count());
}
result.release_data();
result.release_details();
return true;
}
std::shared_ptr<BanRecord> ConnectedClient::resolveActiveBan(const std::string& ip_address) {
std::shared_ptr<bans::BanRecord> ConnectedClient::resolveActiveBan(const std::string& ip_address) {
if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_bans, 0))) return nullptr;
//Check if manager banned
auto banManager = serverInstance->banManager();
shared_ptr<BanRecord> banEntry = nullptr;
deque<shared_ptr<BanRecord>> entries;
shared_ptr<bans::BanRecord> banEntry = nullptr;
deque<shared_ptr<bans::BanRecord>> entries;
if (!banEntry) {
banEntry = banManager->findBanByName(this->server->getServerId(), this->getDisplayName());
+29 -51
View File
@@ -8,6 +8,7 @@
#include "../channel/ClientChannelView.h"
#include "DataClient.h"
#include "query/command3.h"
#include "src/manager/BanManager.h"
#define CLIENT_STR_LOG_PREFIX_(this) (std::string("[") + this->getLoggingPeerIp() + ":" + std::to_string(this->getPeerPort()) + "/" + this->getDisplayName() + " | " + std::to_string(this->getClientId()) + "]")
#define CLIENT_STR_LOG_PREFIX CLIENT_STR_LOG_PREFIX_(this)
@@ -67,14 +68,13 @@ namespace ts {
friend class QueryServer;
friend class DataClient;
friend class SpeakingClient;
friend class connection::VoiceClientConnection;
friend class ts::GroupManager;
friend class VirtualServerManager;
public:
explicit ConnectedClient(sql::SqlManager*, const std::shared_ptr<VirtualServer>& server);
~ConnectedClient() override;
ConnectionState connectionState(){ return this->state; }
ClientState connectionState(){ return this->state; }
std::string getLoggingPeerIp() { return config::server::disable_ip_saving || (this->server && this->server->disable_ip_saving()) ? "X.X.X.X" : this->getPeerIp(); }
std::string getPeerIp(){ return this->isAddressV4() ? net::to_string(this->getAddressV4()->sin_addr) : this->isAddressV6() ? net::to_string(this->getAddressV6()->sin6_addr) : "localhost"; }
uint16_t getPeerPort(){ return ntohs(this->isAddressV4() ? this->getAddressV4()->sin_port : this->isAddressV6() ? this->getAddressV6()->sin6_port : (uint16_t) 0); }
@@ -96,6 +96,8 @@ namespace ts {
virtual void setClientId(uint16_t clId) { properties()[property::CLIENT_ID] = clId; }
inline std::shared_ptr<BasicChannel> getChannel(){ return this->currentChannel; }
/* ATTENTION: Do this only with command_lock locked, and client_channel_tree lock lock in exclusive mode */
inline void setChannel(const std::shared_ptr<BasicChannel>& channel){ this->currentChannel = channel; }
inline ChannelId getChannelId(){ auto channel = this->currentChannel; return channel ? channel->channelId() : 0; }
inline std::shared_ptr<VirtualServer> getServer(){ return this->server; }
inline ServerId getServerId(){ return this->server ? this->server->getServerId() : (ServerId) 0; }
@@ -122,7 +124,7 @@ namespace ts {
virtual bool notifyClientNeededPermissions();
virtual bool notifyServerGroupList();
virtual bool notifyGroupPermList(const std::shared_ptr<Group>&, bool);
virtual bool notifyClientPermList(ClientDbId, const std::shared_ptr<permission::v2::PermissionManager>&, bool);
virtual bool notifyClientPermList(ClientDbId, const std::shared_ptr<permission::v2::PermissionRegister>&, bool);
virtual bool notifyChannelGroupList();
virtual bool notifyConnectionInfo(const std::shared_ptr<ConnectedClient> &target, const std::shared_ptr<ConnectionInfoData> &info);
virtual bool notifyChannelSubscribed(const std::deque<std::shared_ptr<BasicChannel>> &);
@@ -135,7 +137,7 @@ namespace ts {
virtual bool notifyClientPoke(std::shared_ptr<ConnectedClient> invoker, std::string msg);
virtual bool notifyClientUpdated(
const std::shared_ptr<ConnectedClient> &,
const std::deque<const property::PropertyDescription*> &,
const std::deque<std::shared_ptr<property::PropertyDescription>> &,
bool lock_channel_tree
); /* invalid client id causes error: invalid clientID */
@@ -210,20 +212,13 @@ namespace ts {
const ViewReasonServerLeftT& /* mode */
);
virtual bool notifyClientLeftViewKicked(
const std::shared_ptr<ConnectedClient> &client,
const std::shared_ptr<BasicChannel> &target_channel,
const std::string& message,
std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree
);
virtual bool notifyClientLeftViewBanned(
const std::shared_ptr<ConnectedClient> &client,
const std::string& message,
std::shared_ptr<ConnectedClient> invoker,
size_t length,
bool lock_channel_tree
);
virtual bool notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
const std::string &message, std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree, const std::shared_ptr<BasicChannel> &target_channel);
virtual bool notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
std::shared_ptr<ConnectedClient> invoker, size_t length,
bool lock_channel_tree,
const std::string &message);
virtual bool notifyMusicPlayerSongChange(const std::shared_ptr<MusicClient>& bot, const std::shared_ptr<music::SongInfo>& newEntry);
virtual bool notifyMusicQueueAdd(const std::shared_ptr<MusicClient>& bot, const std::shared_ptr<ts::music::SongInfo>& entry, int index, const std::shared_ptr<ConnectedClient>& invoker);
@@ -249,7 +244,7 @@ namespace ts {
virtual void updateChannelClientProperties(bool /* lock channel tree */, bool /* notify our self */);
void updateTalkRights(permission::PermissionValue talk_power);
virtual std::shared_ptr<BanRecord> resolveActiveBan(const std::string& ip_address);
virtual std::shared_ptr<bans::BanRecord> resolveActiveBan(const std::string& ip_address);
inline std::shared_ptr<stats::ConnectionStatistics> getConnectionStatistics() {
return this->connectionStatistics;
@@ -277,43 +272,27 @@ namespace ts {
*/
bool update_cached_permissions();
std::shared_lock<std::shared_mutex> require_connected_state(bool blocking = false) {
//try_to_lock_t
std::shared_lock<std::shared_mutex> disconnect_lock{};
if(blocking) [[unlikely]]
disconnect_lock = std::shared_lock{this->finalDisconnectLock};
else
disconnect_lock = std::shared_lock{this->finalDisconnectLock, std::try_to_lock};
if(!disconnect_lock) [[unlikely]]
return disconnect_lock;
{
std::lock_guard state_lock{this->state_lock};
if(this->state != ConnectionState::CONNECTED)
return {};
}
return disconnect_lock;
}
inline bool playlist_subscribed(const std::shared_ptr<ts::music::Playlist>& playlist) const {
return this->_subscribed_playlist.lock() == playlist;
}
[[nodiscard]] auto require_connected_state() {
return ts::rwshared_lock{this->state_lock};
}
template <typename T = std::lock_guard<threads::Mutex>>
[[nodiscard]] inline auto lock_command_handling() { return T{this->command_lock}; }
void increase_join_state() { this->join_state_id++; }
protected:
std::weak_ptr<ConnectedClient> _this;
sockaddr_storage remote_address;
//General states
std::mutex state_lock;
ConnectionState state{ConnectionState::UNKNWON};
ts::rw_mutex state_lock{};
ClientState state{ClientState::UNKNWON};
bool allowedToTalk = false;
std::shared_mutex finalDisconnectLock; /* locked before state lock! */
std::vector<GroupId> cached_server_groups{}; /* variable locked with channel_lock */
GroupId cached_channel_group = 0; /* variable locked with channel_lock */
std::deque<std::weak_ptr<ConnectedClient>> visibleClients{}; /* variable locked with channel_lock */
std::deque<std::weak_ptr<ConnectedClient>> mutedClients{}; /* variable locked with channel_lock */
std::deque<std::weak_ptr<ConnectedClient>> openChats{}; /* variable locked with channel_lock */
@@ -326,6 +305,7 @@ namespace ts {
std::chrono::system_clock::time_point lastOnlineTimestamp;
std::chrono::system_clock::time_point lastTransfareTimestamp;
std::chrono::system_clock::time_point idleTimestamp;
std::chrono::system_clock::time_point last_statistics_tick;
struct {
std::mutex lock;
@@ -633,25 +613,23 @@ namespace ts {
template <typename T>
struct ConnectedLockedClient {
ConnectedLockedClient() {}
explicit ConnectedLockedClient(std::shared_ptr<T> client) : client{std::move(client)} {
if(this->client)
this->connection_lock = this->client->require_connected_state();
}
explicit ConnectedLockedClient(ConnectedLockedClient&& client) : client{std::move(client.client)}, connection_lock{std::move(client.connection_lock)} { }
inline ConnectedLockedClient &operator=(const ConnectedLockedClient& other) {
inline ConnectedLockedClient<T> &operator=(const ConnectedLockedClient& other) {
this->client = other.client;
if(other)
this->connection_lock = std::shared_lock{*other.connection_lock.mutex()}; /* if the other is true (state locked & client) than we could easily acquire a shared lock */
}
inline ConnectedLockedClient &operator=(ConnectedLockedClient&& other) {
inline ConnectedLockedClient<T> &operator=(ConnectedLockedClient&& other) {
this->client = std::move(other.client);
this->connection_lock = std::move(other.connection_lock);
}
inline bool valid() const { return !!this->client && !!this->connection_lock; }
inline bool valid() const { return !!this->client && this->connection_lock.shared_locked() && client->connectionState() == ClientState::CONNECTED; }
inline operator bool() const { return this->valid(); }
@@ -665,7 +643,7 @@ namespace ts {
T &operator*() { return *this->client; }
std::shared_ptr<T> client;
std::shared_lock<std::shared_mutex> connection_lock{};
ts::rwshared_lock<ts::rw_mutex> connection_lock{};
};
}
}
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@
#include <algorithm>
#include "ConnectedClient.h"
#include "voice/VoiceClient.h"
#include "src/server/file/LocalFileServer.h"
#include "../server/file/FileServer.h"
#include "../server/VoiceServer.h"
#include "../InstanceHandler.h"
#include "../server/QueryServer.h"
@@ -19,7 +19,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
extern ts::server::InstanceHandler* serverInstance;
@@ -60,7 +60,7 @@ bool ConnectedClient::notifyServerGroupList() {
cmd[index]["sgid"] = group->groupId();
}
for (const auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
cmd[index][std::string{prop.type().name}] = prop.value();
cmd[index][prop.type().name] = prop.value();
auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power);
@@ -133,7 +133,7 @@ bool ConnectedClient::notifyGroupPermList(const std::shared_ptr<Group>& group, b
return true;
}
bool ConnectedClient::notifyClientPermList(ClientDbId cldbid, const std::shared_ptr<permission::v2::PermissionManager>& mgr, bool perm_sids) {
bool ConnectedClient::notifyClientPermList(ClientDbId cldbid, const std::shared_ptr<permission::v2::PermissionRegister>& mgr, bool perm_sids) {
Command res(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyclientpermlist" : "");
auto permissions = mgr->permissions();
@@ -186,7 +186,7 @@ bool ConnectedClient::notifyChannelGroupList() {
cmd[index]["sgid"] = group->groupId();
}
for (auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
cmd[index][std::string{prop.type().name}] = prop.value();
cmd[index][prop.type().name] = prop.value();
auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power);
@@ -271,116 +271,108 @@ bool ConnectedClient::notifyClientChannelGroupChanged(
}
bool ConnectedClient::notifyConnectionInfo(const shared_ptr<ConnectedClient> &target, const shared_ptr<ConnectionInfoData> &info) {
command_builder notify{"notifyconnectioninfo"};
auto bulk = notify.bulk(0);
bulk.put_unchecked("clid", target->getClientId());
Command notify("notifyconnectioninfo");
notify["clid"] = target->getClientId();
auto not_set = this->getType() == CLIENT_TEAMSPEAK ? 0 : -1;
/* we deliver data to the web client as well, because its a bit dump :D */
if(target->getClientId() != this->getClientId()) {
auto file_stats = target->connectionStatistics->file_stats();
auto report = target->connectionStatistics->full_report();
/* default values which normally sets the client */
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_SPEECH, not_set);
notify["connection_bandwidth_received_last_minute_control"] = not_set;
notify["connection_bandwidth_received_last_minute_keepalive"] = not_set;
notify["connection_bandwidth_received_last_minute_speech"] = not_set;
notify["connection_bandwidth_received_last_second_control"] = not_set;
notify["connection_bandwidth_received_last_second_keepalive"] = not_set;
notify["connection_bandwidth_received_last_second_speech"] = not_set;
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_SPEECH, not_set);
notify["connection_bandwidth_sent_last_minute_control"] = not_set;
notify["connection_bandwidth_sent_last_minute_keepalive"] = not_set;
notify["connection_bandwidth_sent_last_minute_speech"] = not_set;
notify["connection_bandwidth_sent_last_second_control"] = not_set;
notify["connection_bandwidth_sent_last_second_keepalive"] = not_set;
notify["connection_bandwidth_sent_last_second_speech"] = not_set;
/* its flipped here because the report is out of the clients view */
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_SPEECH, not_set);
notify["connection_bytes_received_control"] = not_set;
notify["connection_bytes_received_keepalive"] = not_set;
notify["connection_bytes_received_speech"] = not_set;
notify["connection_bytes_sent_control"] = not_set;
notify["connection_bytes_sent_keepalive"] = not_set;
notify["connection_bytes_sent_speech"] = not_set;
/* its flipped here because the report is out of the clients view */
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_SPEECH, not_set);
notify["connection_packets_received_control"] = not_set;
notify["connection_packets_received_keepalive"] = not_set;
notify["connection_packets_received_speech"] = not_set;
notify["connection_packets_sent_control"] = not_set;
notify["connection_packets_sent_keepalive"] = not_set;
notify["connection_packets_sent_speech"] = not_set;
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL, not_set);
notify["connection_server2client_packetloss_control"] = not_set;
notify["connection_server2client_packetloss_keepalive"] = not_set;
notify["connection_server2client_packetloss_speech"] = not_set;
notify["connection_server2client_packetloss_total"] = not_set;
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, not_set);
notify["connection_ping"] = 0;
notify["connection_ping_deviation"] = 0;
bulk.put_unchecked(property::CONNECTION_PING, 0);
bulk.put_unchecked(property::CONNECTION_PING_DEVIATION, 0);
bulk.put_unchecked(property::CONNECTION_CONNECTED_TIME, 0);
bulk.put_unchecked(property::CONNECTION_IDLE_TIME, 0);
notify["connection_connected_time"] = 0;
notify["connection_idle_time"] = 0;
/* its flipped here because the report is out of the clients view */
bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT, file_stats.bytes_received);
bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, file_stats.bytes_sent);
notify["connection_filetransfer_bandwidth_sent"] = report.file_bytes_received;
notify["connection_filetransfer_bandwidth_received"] = report.file_bytes_sent;
}
if(info) {
for(const auto& [key, value] : info->properties)
bulk.put(key, value);
} else {
//Fill in what we can, else we trust the client
if(target->getType() == ClientType::CLIENT_TEASPEAK || target->getType() == ClientType::CLIENT_TEAMSPEAK || target->getType() == ClientType::CLIENT_WEB) {
auto& stats = target->connectionStatistics->total_stats();
/* its flipped here because the report is out of the clients view */
bulk.put(property::CONNECTION_BYTES_RECEIVED_CONTROL, stats.connection_bytes_received[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_BYTES_RECEIVED_KEEPALIVE, stats.connection_bytes_received[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_BYTES_RECEIVED_SPEECH, stats.connection_bytes_received[stats::ConnectionStatistics::category::VOICE]);
bulk.put(property::CONNECTION_BYTES_SENT_CONTROL, stats.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_BYTES_SENT_KEEPALIVE, stats.connection_bytes_sent[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_BYTES_SENT_SPEECH, stats.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE]);
/* its flipped here because the report is out of the clients view */
bulk.put(property::CONNECTION_PACKETS_RECEIVED_CONTROL, stats.connection_packets_received[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE, stats.connection_packets_received[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_PACKETS_RECEIVED_SPEECH, stats.connection_packets_received[stats::ConnectionStatistics::category::VOICE]);
bulk.put(property::CONNECTION_PACKETS_SENT_CONTROL, stats.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_PACKETS_SENT_KEEPALIVE, stats.connection_packets_sent[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_PACKETS_SENT_SPEECH, stats.connection_packets_sent[stats::ConnectionStatistics::category::VOICE]);
for(const auto& elm : info->properties) {
notify[elm.first] = elm.second;
}
}
if(auto vc = dynamic_pointer_cast<VoiceClient>(target); vc) {
bulk.put(property::CONNECTION_PING, floor<milliseconds>(vc->current_ping()).count());
bulk.put(property::CONNECTION_PING_DEVIATION, vc->current_ping_deviation());
}
} else {
//Fill in some server stuff
if(dynamic_pointer_cast<VoiceClient>(target))
notify["connection_ping"] = floor<milliseconds>(dynamic_pointer_cast<VoiceClient>(target)->calculatePing()).count();
#ifdef COMPILE_WEB_CLIENT
else if(dynamic_pointer_cast<WebClient>(target))
bulk.put(property::CONNECTION_PING, floor<milliseconds>(dynamic_pointer_cast<WebClient>(target)->client_ping()).count());
else if(dynamic_pointer_cast<WebClient>(target))
notify["connection_ping"] = floor<milliseconds>(dynamic_pointer_cast<WebClient>(target)->client_ping()).count();
#endif
if(auto vc = dynamic_pointer_cast<VoiceClient>(target); vc){
auto& calculator = vc->connection->packet_statistics();
auto report = calculator.loss_report();
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, std::to_string(report.voice_loss()));
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, std::to_string(report.keep_alive_loss()));
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, std::to_string(report.control_loss()));
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, std::to_string(report.total_loss()));
if(target->getType() == ClientType::CLIENT_TEASPEAK || target->getType() == ClientType::CLIENT_TEAMSPEAK || target->getType() == ClientType::CLIENT_WEB) {
auto report = target->connectionStatistics->full_report();
/* its flipped here because the report is out of the clients view */
notify["connection_bytes_received_control"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_bytes_received_keepalive"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_bytes_received_speech"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
notify["connection_bytes_sent_control"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_bytes_sent_keepalive"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_bytes_sent_speech"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
/* its flipped here because the report is out of the clients view */
notify["connection_packets_received_control"] = report.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_packets_received_keepalive"] = report.connection_packets_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_packets_received_speech"] = report.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
notify["connection_packets_sent_control"] = report.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_packets_sent_keepalive"] = report.connection_packets_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_packets_sent_speech"] = report.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
}
}
if(target->getClientId() == this->getClientId() || permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, this->getChannelId()))) {
bulk.put(property::CONNECTION_CLIENT_IP, target->getLoggingPeerIp());
bulk.put(property::CONNECTION_CLIENT_PORT, target->getPeerPort());
notify["connection_client_ip"] = target->getLoggingPeerIp();
notify["connection_client_port"] = target->getPeerPort();
}
bulk.put(property::CONNECTION_CONNECTED_TIME, chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->connectTimestamp).count());
bulk.put(property::CONNECTION_IDLE_TIME, chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->idleTimestamp).count());
//Needs to be filled out
notify["connection_client2server_packetloss_speech"] = not_set;
notify["connection_client2server_packetloss_keepalive"] = not_set;
notify["connection_client2server_packetloss_control"] = not_set;
notify["connection_client2server_packetloss_total"] = not_set;
notify["connection_connected_time"] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->connectTimestamp).count();
notify["connection_idle_time"] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->idleTimestamp).count();
this->sendCommand(notify);
return true;
}
@@ -412,7 +404,7 @@ bool ConnectedClient::notifyClientMoved(const shared_ptr<ConnectedClient> &clien
return true;
}
bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &client, const deque<const property::PropertyDescription*> &props, bool lock) {
bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &client, const deque<shared_ptr<property::PropertyDescription>> &props, bool lock) {
shared_lock channel_lock(this->channel_lock, defer_lock);
if(lock)
channel_lock.lock();
@@ -428,7 +420,7 @@ bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient>
Command response("notifyclientupdated");
response["clid"] = client_id;
for (const auto &prop : props) {
if(lastOnlineTimestamp.time_since_epoch().count() > 0 && (*prop == property::CLIENT_TOTAL_ONLINE_TIME || *prop == property::CLIENT_MONTH_ONLINE_TIME))
if(lastOnlineTimestamp.time_since_epoch().count() > 0 && (prop->property_index == property::CLIENT_TOTAL_ONLINE_TIME || prop->property_index == property::CLIENT_MONTH_ONLINE_TIME))
response[prop->name] = client->properties()[prop].as<int64_t>() + duration_cast<seconds>(system_clock::now() - client->lastOnlineTimestamp).count();
else
response[prop->name] = client->properties()[prop].value();
@@ -658,7 +650,7 @@ bool ConnectedClient::notifyClientEnterView(const std::deque<std::shared_ptr<Con
this->visibleClients.push_back(client);
for (const auto &elm : client->properties()->list_properties(property::FLAG_CLIENT_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
cmd[index][std::string{elm.type().name}] = elm.value();
cmd[index][elm.type().name] = elm.value();
}
index++;
@@ -686,14 +678,14 @@ bool ConnectedClient::notifyChannelEdited(
Command notify("notifychanneledited");
for(auto prop : properties) {
const auto& prop_info = property::describe(prop);
const auto& prop_info = property::impl::info(prop);
if(prop == property::CHANNEL_ORDER)
notify[prop_info.name] = v_channel->previous_channel;
notify[prop_info->name] = v_channel->previous_channel;
else if(prop == property::CHANNEL_DESCRIPTION) {
send_description_change = true;
} else {
notify[prop_info.name] = channel->properties()[prop].as<string>();
notify[prop_info->name] = channel->properties()[prop].as<string>();
}
}
@@ -158,8 +158,8 @@ bool ConnectedClient::handle_text_command(
if (TARG(0, "create")) {
Command cmd("");
auto result = this->handleCommandMusicBotCreate(cmd);
if(result.has_error()) {
result.release_data();
if(result.error_code()) {
result.release_details();
HANDLE_CMD_ERROR("Failed to create music bot");
return true;
}
@@ -220,9 +220,9 @@ bool ConnectedClient::handle_text_command(
Command cmd("");
cmd["client_nickname"] = ss.str();
auto result = this->handleCommandClientEdit(cmd, bot);
if(result.has_error()) {
if(result.error_code()) {
HANDLE_CMD_ERROR("Failed to rename bot");
result.release_data();
result.release_details();
return true;
}
send_message(serverInstance->musicRoot(), "Name successfully changed!");
@@ -486,7 +486,7 @@ bool ConnectedClient::handle_text_command(
for(const auto& property : bot->properties()->list_properties(~0)) {
if(find(editable_properties.begin(), editable_properties.end(), property.type().name) == editable_properties.end()) continue;
send_message(bot, " - " + std::string{property.type().name} + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
send_message(bot, " - " + property.type().name + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
}
} else if(arguments.size() < 4) {
if(find(editable_properties.begin(), editable_properties.end(), arguments[2]) == editable_properties.end()) {
@@ -494,23 +494,23 @@ bool ConnectedClient::handle_text_command(
return true;
}
const auto &property_info = property::find<property::ClientProperties>(arguments[2]);
if(property_info.is_undefined()) {
const std::shared_ptr<property::PropertyDescription> &property_info = property::info<property::ClientProperties>(arguments[2]);
if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::CLIENT_UNDEFINED) {
send_message(bot, "Unknown property " + arguments[2] + ".");
return true;
}
auto prop = bot->properties()[(property::ClientProperties) property_info.property_index];
send_message(bot, "Bot property " + std::string{property_info.name} + " = " + prop.value() + " " + (property_info.default_value == prop.value() ? "(default)" : ""));
auto prop = bot->properties()[(property::ClientProperties) property_info->property_index];
send_message(bot, "Bot property " + property_info->name + " = " + prop.value() + " " + (property_info->default_value == prop.value() ? "(default)" : ""));
return true;
} else {
Command cmd("");
JOIN_ARGS(value, 3);
cmd[arguments[2]] = value;
auto result = this->handleCommandClientEdit(cmd, bot);
if(result.has_error()) {
if(result.error_code()) {
HANDLE_CMD_ERROR("Failed to change bot property");
result.release_data();
result.release_details();
return true;
}
send_message(serverInstance->musicRoot(), "Property successfully changed!");
@@ -527,36 +527,36 @@ bool ConnectedClient::handle_text_command(
if(arguments.size() < 3) {
send_message(bot, "Playlist properties:");
for(const auto& property : playlist->properties().list_properties(property::FLAG_PLAYLIST_VARIABLE)) {
send_message(bot, " - " + std::string{property.type().name} + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
send_message(bot, " - " + property.type().name + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
}
} else if(arguments.size() < 4) {
const auto &property_info = property::find<property::PlaylistProperties>(arguments[2]);
if(property_info.is_undefined()) {
const std::shared_ptr<property::PropertyDescription> &property_info = property::info<property::PlaylistProperties>(arguments[2]);
if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::PLAYLIST_UNDEFINED) {
send_message(bot, "Unknown property " + arguments[2] + ".");
return true;
}
auto prop = playlist->properties()[(property::PlaylistProperties) property_info.property_index];
send_message(bot, "Bot property " + std::string{property_info.name} + " = " + prop.value() + " " + (property_info.default_value == prop.value() ? "(default)" : ""));
auto prop = playlist->properties()[(property::PlaylistProperties) property_info->property_index];
send_message(bot, "Bot property " + property_info->name + " = " + prop.value() + " " + (property_info->default_value == prop.value() ? "(default)" : ""));
} else {
const auto &property_info = property::find<property::PlaylistProperties>(arguments[2]);
if(property_info.is_undefined()) {
const std::shared_ptr<property::PropertyDescription> &property_info = property::info<property::PlaylistProperties>(arguments[2]);
if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::PLAYLIST_UNDEFINED) {
send_message(bot, "Unknown property " + arguments[2] + ".");
return true;
}
JOIN_ARGS(value, 3);
if(!property_info.validate_input(value)) {
if(!property_info->validate_input(value)) {
send_message(bot, "Please enter a valid value!");
return true;
}
if((property_info.flags & property::FLAG_USER_EDITABLE) == 0) {
if((property_info->flags & property::FLAG_USER_EDITABLE) == 0) {
send_message(bot, "This property isnt changeable!");
return true;
}
playlist->properties()[(property::PlaylistProperties) property_info.property_index] = value;
playlist->properties()[(property::PlaylistProperties) property_info->property_index] = value;
send_message(bot, "Property successfully changed");
return true;
}
@@ -629,6 +629,7 @@ bool ConnectedClient::handle_text_command(
if(!vc) return false;
send_message(_this.lock(), "Packet generations:");
auto& id_generator = vc->getConnection()->packet_encoder().id_generator();
for(const auto& type : {
protocol::PacketTypeInfo::Command,
protocol::PacketTypeInfo::CommandLow,
@@ -639,44 +640,11 @@ bool ConnectedClient::handle_text_command(
protocol::PacketTypeInfo::Ping,
protocol::PacketTypeInfo::Pong}) {
auto id = vc->getConnection()->getPacketIdManager().currentPacketId(type);
auto gen = vc->getConnection()->getPacketIdManager().generationId(type);
auto& genestis = vc->getConnection()->get_incoming_generation_estimators();
auto id = id_generator.currentPacketId(type);
auto gen = id_generator.generationId(type);
send_message(_this.lock(), " OUT " + type.name() + " => generation: " + to_string(gen) + " id: " + to_string(id));
send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id()));
}
return true;
} else if(TARG(0, "ping")) {
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false;
auto& ack = vc->connection->getAcknowledgeManager();
send_message(_this.lock(), "Command retransmission values:");
send_message(_this.lock(), " RTO : " + std::to_string(ack.current_rto()));
send_message(_this.lock(), " RTTVAR: " + std::to_string(ack.current_rttvar()));
send_message(_this.lock(), " SRTT : " + std::to_string(ack.current_srtt()));
return true;
} else if(TARG(0, "sgeneration")) {
TLEN(4);
try {
auto type = stol(arguments[1]);
auto generation = stol(arguments[2]);
auto pid = stol(arguments[3]);
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false;
auto& genestis = vc->getConnection()->get_incoming_generation_estimators();
if(type >= genestis.size()) {
send_message(_this.lock(), "Invalid type");
return true;
}
genestis[type].set_last_state(pid, generation);
} catch(std::exception& ex) {
send_message(_this.lock(), "Failed to parse argument");
return true;
//auto& buffer = vc->getConnection()->packet_buffers()[type.type()];
//send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(buffer.generation(0)) + " id: " + to_string(buffer.current_index()));
}
return true;
} else if(TARG(0, "disconnect")) {
+2 -2
View File
@@ -4,7 +4,7 @@
#include <misc/hex.h>
#include "DataClient.h"
#include "ConnectedClient.h"
#include "src/server/file/LocalFileServer.h"
#include "src/server/file/FileServer.h"
#include "src/InstanceHandler.h"
#include "misc/base64.h"
@@ -112,7 +112,7 @@ bool DataClient::loadDataForCurrentServer() { //TODO for query
}
this->_properties->toggleSave(true);
this->clientPermissions = serverInstance->databaseHelper()->loadClientPermissionManager(ref_server, this->getClientDatabaseId());
this->clientPermissions = serverInstance->databaseHelper()->loadClientPermissionManager(ref_server ? ref_server->serverId : 0, this->getClientDatabaseId());
//Setup / fix stuff
if(!this->properties()[property::CLIENT_FLAG_AVATAR].as<string>().empty()){
+5 -9
View File
@@ -45,17 +45,10 @@ namespace ts {
return std::forward<F>(f)(*handle);
}
/*
template <typename T>
ts::PropertyWrapper operator[](T type) {
return (*handle)[type];
}
*/
template <typename T>
ts::PropertyWrapper operator[](const T& type) {
return (*handle)[type];
}
};
DataClient(sql::SqlManager*, const std::shared_ptr<VirtualServer>&);
@@ -105,14 +98,17 @@ namespace ts {
virtual bool loadDataForCurrentServer();
[[nodiscard]] inline std::shared_ptr<permission::v2::PermissionRegister> permissions() { return this->clientPermissions; }
protected:
sql::SqlManager* sql;
std::shared_ptr<VirtualServer> server;
std::shared_ptr<permission::v2::PermissionManager> clientPermissions = nullptr;
std::shared_ptr<permission::v2::PermissionRegister> clientPermissions = nullptr;
std::shared_ptr<Properties> _properties;
std::shared_ptr<BasicChannel> currentChannel = nullptr;
/* the current channel is save to access when the server channel tree has been locked (shared or exclusively) */
std::shared_ptr<BasicChannel> currentChannel{nullptr};
};
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ namespace ts {
namespace server {
class InternalClient : public ConnectedClient {
public:
InternalClient(sql::SqlManager*, const std::shared_ptr<server::VirtualServer>&, std::string, bool);
InternalClient(sql::SqlManager*, const std::shared_ptr<ts::server::VirtualServer>&, std::string, bool);
~InternalClient();
void setSharedLock(const std::shared_ptr<ConnectedClient>& _this){
+120 -177
View File
@@ -10,6 +10,7 @@
#include "SpeakingClient.h"
#include "src/InstanceHandler.h"
#include "StringVariable.h"
#include "src/music/MusicBotManager.h"
#include "misc/timer.h"
using namespace std::chrono;
@@ -51,14 +52,6 @@ void SpeakingClient::handlePacketVoice(const pipes::buffer_view& data, bool head
return;
}
#if 0
if(rand() % 10 == 0) {
logMessage(0, "Dropping audio packet");
return;
}
logMessage(0, "Received voice: Head: {} Fragmented: {}, length: {}", head, fragmented, data.length());
#endif
auto current_channel = this->currentChannel;
if(!current_channel) { return; }
if(!this->allowedToTalk) { return; }
@@ -72,6 +65,7 @@ void SpeakingClient::handlePacketVoice(const pipes::buffer_view& data, bool head
if(!speaking_client) return true;
return !speaking_client->shouldReceiveVoice(self);
}), target_clients.end());
if(target_clients.empty()) {
return;
@@ -106,10 +100,9 @@ void SpeakingClient::handlePacketVoice(const pipes::buffer_view& data, bool head
memcpy(&buffer[5], &data[3], data.length() - 3);
}
auto bview = pipes::buffer_view{buffer, data.length() + 2};
for (const auto& client : target_clients) {
auto speaking_client = static_pointer_cast<SpeakingClient>(client);
speaking_client->send_voice_packet(bview, flags);
speaking_client->send_voice_packet(pipes::buffer_view{buffer, data.length() + 2}, flags);
}
}
@@ -121,9 +114,7 @@ enum WhisperType {
SERVER_GROUP = 0,
CHANNEL_GROUP = 1,
CHANNEL_COMMANDER = 2,
ALL = 3,
ECHO_TEXT = 0x10,
ALL = 3
};
enum WhisperTarget {
@@ -183,93 +174,87 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
#endif
deque<shared_ptr<SpeakingClient>> available_clients;
if(type == WhisperType::ECHO_TEXT) {
available_clients.push_back(dynamic_pointer_cast<SpeakingClient>(this->ref()));
} else {
for(const auto& client : this->server->getClients()) {
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(client);
if(!speakingClient || client == this) continue;
if(!speakingClient->currentChannel) continue;
auto type_id_string = std::to_string(type_id);
for(const auto& client : this->server->getClients()) {
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(client);
if(!speakingClient || client == this) continue;
if(!speakingClient->currentChannel) continue;
if(type == WhisperType::ALL) {
available_clients.push_back(speakingClient);
} else if(type == WhisperType::SERVER_GROUP) {
if(type_id == 0)
available_clients.push_back(speakingClient);
else {
auto client_groups = client->properties()[property::CLIENT_SERVERGROUPS].as<std::string>();
auto index = client_groups.find(client_groups);
if(index == std::string::npos) continue;
if(index != client_groups.length() && client_groups[index] != ',') continue;
if(type == WhisperType::ALL) {
available_clients.push_back(speakingClient);
} else if(type == WhisperType::SERVER_GROUP) {
if(type_id == 0)
available_clients.push_back(speakingClient);
else {
shared_lock client_lock(this->channel_lock);
for(const auto& id : client->cached_server_groups) {
if(id == type_id) {
available_clients.push_back(speakingClient);
break;
}
}
}
} else if(type == WhisperType::CHANNEL_GROUP) {
if(client->cached_channel_group == type_id)
available_clients.push_back(speakingClient);
} else if(type == WhisperType::CHANNEL_COMMANDER) {
if(client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as<bool>())
available_clients.push_back(speakingClient);
}
} else if(type == WhisperType::CHANNEL_GROUP) {
if(client->properties()[property::CLIENT_CHANNEL_GROUP_ID].as_save<GroupId>() == type_id)
available_clients.push_back(speakingClient);
} else if(type == WhisperType::CHANNEL_COMMANDER) {
if(client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as<bool>())
available_clients.push_back(speakingClient);
}
}
if(target == WhisperTarget::CHANNEL_CURRENT) {
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
return target->currentChannel != this->currentChannel;
}), available_clients.end());
} else if(target == WhisperTarget::CHANNEL_PARENT) {
auto current_parent = this->currentChannel->parent();
if(!current_parent) return;
if(target == WhisperTarget::CHANNEL_CURRENT) {
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
return target->currentChannel != this->currentChannel;
}), available_clients.end());
} else if(target == WhisperTarget::CHANNEL_PARENT) {
auto current_parent = this->currentChannel->parent();
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
return target->currentChannel != current_parent;
}), available_clients.end());
} else if(target == WhisperTarget::CHANNEL_ALL_PARENT) {
shared_ptr<BasicChannel> current_parent;
{
current_parent = this->currentChannel->parent();
if(!current_parent) return;
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
return target->currentChannel != current_parent;
}), available_clients.end());
} else if(target == WhisperTarget::CHANNEL_ALL_PARENT) {
shared_ptr<BasicChannel> current_parent;
{
current_parent = this->currentChannel->parent();
if(!current_parent) return;
}
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
auto tmp_parent = current_parent;
while(tmp_parent && tmp_parent != target->currentChannel)
tmp_parent = tmp_parent->parent();
return target->currentChannel != tmp_parent;
}), available_clients.end());
} else if(target == WhisperTarget::CHANNEL_FAMILY) {
shared_ptr<BasicChannel> current = this->currentChannel;
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
auto tmp_current = target->currentChannel;
while(tmp_current && tmp_current != current)
tmp_current = tmp_current->parent();
return current != tmp_current;
}), available_clients.end());
} else if(target == WhisperTarget::CHANNEL_COMPLETE_FAMILY) {
shared_ptr<BasicChannel> current = this->currentChannel;
while(current && current->parent()) current = current->parent();
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
auto tmp_current = target->currentChannel;
while(tmp_current && tmp_current != current)
tmp_current = tmp_current->parent();
return current != tmp_current;
}), available_clients.end());
} else if(target == WhisperTarget::CHANNEL_SUBCHANNELS) {
shared_ptr<BasicChannel> current = this->currentChannel;
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
return target->currentChannel->parent() != current;
}), available_clients.end());
}
auto self_lock = this->_this.lock();
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
return !speakingClient->shouldReceiveVoiceWhisper(self_lock);
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
auto tmp_parent = current_parent;
while(tmp_parent && tmp_parent != target->currentChannel)
tmp_parent = tmp_parent->parent();
return target->currentChannel != tmp_parent;
}), available_clients.end());
} else if(target == WhisperTarget::CHANNEL_FAMILY) {
shared_ptr<BasicChannel> current = this->currentChannel;
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
auto tmp_current = target->currentChannel;
while(tmp_current && tmp_current != current)
tmp_current = tmp_current->parent();
return current != tmp_current;
}), available_clients.end());
} else if(target == WhisperTarget::CHANNEL_COMPLETE_FAMILY) {
shared_ptr<BasicChannel> current = this->currentChannel;
while(current && current->parent()) current = current->parent();
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
auto tmp_current = target->currentChannel;
while(tmp_current && tmp_current != current)
tmp_current = tmp_current->parent();
return current != tmp_current;
}), available_clients.end());
} else if(target == WhisperTarget::CHANNEL_SUBCHANNELS) {
shared_ptr<BasicChannel> current = this->currentChannel;
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const shared_ptr<SpeakingClient>& target) {
return target->currentChannel->parent() != current;
}), available_clients.end());
}
auto self_lock = this->_this.lock();
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
return !speakingClient->shouldReceiveVoiceWhisper(self_lock);
}), available_clients.end());
if(available_clients.empty()) {
if(update_whisper_error(this->speak_last_no_whisper_target)) {
command_result result{error::whisper_no_targets};
@@ -297,7 +282,7 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
VoicePacketFlags flags{};
auto data = pipes::buffer_view(packet_buffer, OUT_WHISPER_PKT_OFFSET + data_length);
for(const auto& cl : available_clients){
cl->send_voice_whisper_packet(data, flags);
cl->send_voice_whisper_packet(data, flags);
}
this->updateSpeak(false, system_clock::now());
@@ -393,7 +378,7 @@ auto regex_wildcard = std::regex(".*");
#define S(x) #x
#define HWID_REGEX(name, pattern) \
auto regex_hwid_ ##name = []() noexcept { \
auto regex_hwid_ ##name = [](){ \
try { \
return std::regex(pattern); \
} catch (std::exception& ex) { \
@@ -495,13 +480,13 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
}
}
const auto &info = property::find<property::ClientProperties>(key);
if(info.is_undefined()) {
const auto &info = property::info<property::ClientProperties>(key);
if(*info == property::CLIENT_UNDEFINED) {
logError(this->getServerId(), "{} Tried to pass a unknown value {}. Please report this, if you're sure that this key should be known!", CLIENT_STR_LOG_PREFIX, key);
continue;
//return {findError("parameter_invalid"), "Unknown property " + key};
}
if(!info.validate_input(cmd[key].as<string>()))
if(!info->validate_input(cmd[key].as<string>()))
return command_result{error::parameter_invalid};
this->properties()[info] = cmd[key].as<std::string>();
@@ -548,39 +533,37 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
if(!this->server->verifyServerPassword(cmd["client_server_password"].string(), true))
return command_result{error::server_invalid_password};
if(!config::server::clients::ignore_max_clone_permissions) {
size_t clones_uid = 0;
size_t clones_ip = 0;
size_t clones_hwid = 0;
size_t clones_uid = 0;
size_t clones_ip = 0;
size_t clones_hwid = 0;
auto _own_hwid = this->getHardwareId();
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
if(client->getExternalType() != CLIENT_TEAMSPEAK) return;
if(client->getUid() == this->getUid())
clones_uid++;
if(client->getPeerIp() == this->getPeerIp())
clones_ip++;
if(!_own_hwid.empty() && client->getHardwareId() == _own_hwid)
clones_hwid++;
});
auto _own_hwid = this->getHardwareId();
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
if(client->getExternalType() != CLIENT_TEAMSPEAK) return;
if(client->getUid() == this->getUid())
clones_uid++;
if(client->getPeerIp() == this->getPeerIp())
clones_ip++;
if(!_own_hwid.empty() && client->getHardwareId() == _own_hwid)
clones_hwid++;
});
if(clones_uid > 0 && permissions[permission::i_client_max_clones_uid].has_value && !permission::v2::permission_granted(clones_uid, permissions[permission::i_client_max_clones_uid])) {
logMessage(this->getServerId(), "{} Disconnecting because there are already {} uid clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_uid, permissions[permission::i_client_max_clones_uid]);
return command_result{error:: client_too_many_clones_connected, "too many clones connected (uid)"};
}
if(clones_ip > 0 && permissions[permission::i_client_max_clones_ip].has_value && !permission::v2::permission_granted(clones_ip, permissions[permission::i_client_max_clones_ip])) {
logMessage(this->getServerId(), "{} Disconnecting because there are already {} ip clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_ip, permissions[permission::i_client_max_clones_ip]);
return command_result{error:: client_too_many_clones_connected, "too many clones connected (ip)"};
}
if(clones_hwid > 0 && permissions[permission::i_client_max_clones_hwid].has_value && !permission::v2::permission_granted(clones_hwid, permissions[permission::i_client_max_clones_hwid])) {
logMessage(this->getServerId(), "{} Disconnecting because there are already {} hwid clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_hwid, permissions[permission::i_client_max_clones_hwid]);
return command_result{error:: client_too_many_clones_connected, "too many clones connected (hwid)"};
}
TIMING_STEP(timings, "max clones ");
if(clones_uid > 0 && permissions[permission::i_client_max_clones_uid].has_value && !permission::v2::permission_granted(clones_uid, permissions[permission::i_client_max_clones_uid])) {
logMessage(this->getServerId(), "{} Disconnecting because there are already {} uid clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_uid, permissions[permission::i_client_max_clones_uid]);
return command_result{error:: client_too_many_clones_connected, "too many clones connected (uid)"};
}
if(clones_ip > 0 && permissions[permission::i_client_max_clones_ip].has_value && !permission::v2::permission_granted(clones_ip, permissions[permission::i_client_max_clones_ip])) {
logMessage(this->getServerId(), "{} Disconnecting because there are already {} ip clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_ip, permissions[permission::i_client_max_clones_ip]);
return command_result{error:: client_too_many_clones_connected, "too many clones connected (ip)"};
}
if(clones_hwid > 0 && permissions[permission::i_client_max_clones_hwid].has_value && !permission::v2::permission_granted(clones_hwid, permissions[permission::i_client_max_clones_hwid])) {
logMessage(this->getServerId(), "{} Disconnecting because there are already {} hwid clones connected. (Allowed: {})", CLIENT_STR_LOG_PREFIX, clones_hwid, permissions[permission::i_client_max_clones_hwid]);
return command_result{error:: client_too_many_clones_connected, "too many clones connected (hwid)"};
}
TIMING_STEP(timings, "max clones ");
auto banEntry = this->resolveActiveBan(this->getPeerIp());
if(banEntry) {
logMessage(this->getServerId(), "{} Disconnecting while init because of ban record. Record id {} at server {}",
@@ -639,7 +622,7 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
{
for(const auto &cl : this->server->getClients())
if((cl->getType() == CLIENT_TEAMSPEAK || cl->getType() == CLIENT_WEB || cl->getType() == CLIENT_TEASPEAK || cl->getType() == CLIENT_MUSIC))
if(cl->connectionState() <= ConnectionState::CONNECTED && cl->connectionState() >= ConnectionState::INIT_HIGH)
if(cl->connectionState() <= ClientState::CONNECTED && cl->connectionState() >= ClientState::CONNECTED) //TODO: Get "real" state and do not ignore just initializing clients!
count++;
}
@@ -670,19 +653,8 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
}
}
this->postCommandHandler.emplace_back([&](){
auto self = dynamic_pointer_cast<SpeakingClient>(_this.lock());
std::thread([self](){
if(self->state != ConnectionState::INIT_HIGH) return;
try {
self->processJoin();
} catch (std::exception& ex) {
logError(self->getServerId(), "Failed to proceed client join for {}. Got exception with message {}", CLIENT_STR_LOG_PREFIX_(self), ex.what());
self->close_connection(chrono::system_clock::now() + chrono::seconds{5});
}
}).detach();
});
/* we're not doing this anymore from a different thread */
this->processJoin();
debugMessage(this->getServerId(), "{} Client init timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
return command_result{error::ok};
}
@@ -696,7 +668,7 @@ void SpeakingClient::processJoin() {
this->resetIdleTime();
threads::MutexLock lock(this->command_lock); //Don't process any commands!
if(this->state != ConnectionState::INIT_HIGH) {
if(this->state != ClientState::INITIALIZING) {
logError(this->getServerId(), "{} Invalid processJoin() connection state!", CLIENT_STR_LOG_PREFIX);
return;
}
@@ -739,14 +711,14 @@ void SpeakingClient::processJoin() {
if(!ref_server->assignDefaultChannel(this->ref(), false)) {
auto result = command_result{error::vs_critical, "Could not assign default channel!"};
this->notifyError(result);
result.release_data();
result.release_details();
this->close_connection(system_clock::now() + seconds(1));
return;
}
TIMING_STEP(timings, "assign chan");
this->sendChannelList(true);
this->state = ConnectionState::CONNECTED;
this->state = ClientState::CONNECTED;
TIMING_STEP(timings, "send chan t");
/* trick the join method */
@@ -760,7 +732,7 @@ void SpeakingClient::processJoin() {
unique_lock server_channel_lock(this->server->channel_tree_lock);
this->server->client_move(this->ref(), channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_lock);
if(this->getType() != ClientType::CLIENT_TEAMSPEAK) this->subscribeChannel({this->currentChannel}, false, true); /* su "improve" the TS3 clients join speed we send the channel clients a bit later, when the TS3 client gets his own client variables */
this->subscribeChannel({this->currentChannel}, false, true);
}
TIMING_STEP(timings, "join move ");
@@ -778,34 +750,13 @@ void SpeakingClient::processJoin() {
this->connectTimestamp = chrono::system_clock::now();
this->idleTimestamp = chrono::system_clock::now();
TIMING_STEP(timings, "welcome msg");
{
std::string message{};
config::server::clients::WelcomeMessageType type{config::server::clients::WELCOME_MESSAGE_TYPE_NONE};
if(this->getType() == ClientType::CLIENT_TEASPEAK) {
message = config::server::clients::extra_welcome_message_teaspeak;
type = config::server::clients::extra_welcome_message_type_teaspeak;
} else if(this->getType() == ClientType::CLIENT_TEAMSPEAK) {
message = config::server::clients::extra_welcome_message_teamspeak;
type = config::server::clients::extra_welcome_message_type_teamspeak;
} else if(this->getType() == ClientType::CLIENT_WEB) {
message = config::server::clients::extra_welcome_message_teaweb;
type = config::server::clients::extra_welcome_message_type_teaweb;
}
if(type == config::server::clients::WELCOME_MESSAGE_TYPE_POKE) {
this->notifyClientPoke(this->server->serverRoot, message);
} else if(type == config::server::clients::WELCOME_MESSAGE_TYPE_CHAT) {
this->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, this->server->serverRoot, 0, 0, std::chrono::system_clock::now(), message);
}
}
debugMessage(this->getServerId(), "{} Client join timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
}
void SpeakingClient::processLeave() {
auto ownLock = _this.lock();
auto server = this->getServer();
if(server){
if(server) {
logMessage(this->getServerId(), "Voice client {}/{} ({}) from {} left.", this->getClientDatabaseId(), this->getUid(), this->getDisplayName(), this->getLoggingPeerIp() + ":" + to_string(this->getPeerPort()));
{
unique_lock server_channel_lock(this->server->channel_tree_lock);
@@ -815,14 +766,6 @@ void SpeakingClient::processLeave() {
server->musicManager->cleanup_client_bots(this->getClientDatabaseId());
//ref_server = nullptr; Removed caused nullptr exceptions
}
{ //Delete own viewing clients
/*
* No need, are only weak references!
threads::MutexLock l(this->viewLock);
this->visibleClients.clear();
this->mutedClients.clear();
*/
}
}
void SpeakingClient::triggerVoiceEnd() {
@@ -853,7 +796,7 @@ void SpeakingClient::tick(const std::chrono::system_clock::time_point &time) {
ALARM_TIMER(A1, "SpeakingClient::tick", milliseconds(2));
this->updateSpeak(true, time);
if(this->state == ConnectionState::CONNECTED) {
if(this->state == ClientState::CONNECTED) {
if(this->max_idle_time.has_value) {
auto max_idle = this->max_idle_time.value;
if(max_idle > 0 && this->idleTimestamp.time_since_epoch().count() > 0 && duration_cast<seconds>(time - this->idleTimestamp).count() > max_idle) {
@@ -871,17 +814,17 @@ void SpeakingClient::updateChannelClientProperties(bool channel_lock, bool notif
}
command_result SpeakingClient::handleCommand(Command &command) {
if(this->connectionState() == ConnectionState::INIT_HIGH) {
if(this->connectionState() == ClientState::INITIALIZING) {
if(this->handshake.state == HandshakeState::BEGIN || this->handshake.state == HandshakeState::IDENTITY_PROOF) {
command_result result;
if(command.command() == "handshakebegin")
result.reset(this->handleCommandHandshakeBegin(command));
result = this->handleCommandHandshakeBegin(command);
else if(command.command() == "handshakeindentityproof")
result.reset(this->handleCommandHandshakeIdentityProof(command));
result = this->handleCommandHandshakeIdentityProof(command);
else
result.reset(command_result{error::client_not_logged_in});
result = command_result{error::client_not_logged_in};
if(result.has_error())
if(result.error_code())
this->postCommandHandler.push_back([&]{
this->close_connection(system_clock::now() + seconds(1));
});
@@ -1,5 +0,0 @@
//
// Created by WolverinDEV on 07/05/2020.
//
#include "bulk_parsers.h"
@@ -1,242 +0,0 @@
#pragma once
#include <vector>
#include <query/Command.h>
#include <Error.h>
#include <PermissionManager.h>
#include <src/client/ConnectedClient.h>
#include "./helpers.h"
namespace ts::command::bulk_parser {
template <bool kParseValue>
class PermissionBulkParser {
public:
explicit PermissionBulkParser(ts::ParameterBulk& bulk) {
if(bulk.has("permid")) {
auto type = bulk["permid"].as<permission::PermissionType>();
if ((type & PERM_ID_GRANT) != 0) {
type &= ~PERM_ID_GRANT;
}
this->permission_ = permission::resolvePermissionData(type);
} else if(bulk.has("permsid")) {
auto permission_name = bulk["permsid"].string();
this->permission_ = permission::resolvePermissionData(permission_name);
this->grant_ = this->permission_->grantName() == permission_name;;
} else {
this->error_.reset(ts::command_result{error::parameter_missing, "permid"});
return;
}
if(this->permission_->is_invalid()) {
this->error_.reset(ts::command_result{error::parameter_invalid});
return;
}
if(kParseValue) {
if(!bulk.has("permvalue")) {
this->error_.reset(ts::command_result{error::parameter_missing, "permvalue"});
return;
}
this->value_ = bulk["permvalue"].as<permission::PermissionValue>();
this->flag_skip_ = bulk.has("permskip") && bulk["permskip"].as<bool>();
this->flag_negated_ = bulk.has("permnegated") && bulk["permnegated"].as<bool>();
}
}
PermissionBulkParser(const PermissionBulkParser&) = delete;
PermissionBulkParser(PermissionBulkParser&&) = default;
~PermissionBulkParser() {
this->error_.release_data();
}
[[nodiscard]] inline bool has_error() const {
assert(!this->error_released_);
return this->error_.has_error();
}
[[nodiscard]] inline bool has_value() const { return this->value_ != permission::undefined; }
[[nodiscard]] inline bool is_grant_permission() const { return this->grant_; }
[[nodiscard]] inline const auto& permission() const { return this->permission_; }
[[nodiscard]] inline auto permission_type() const { return this->permission_->type; }
[[nodiscard]] inline auto value() const { return this->value_; }
[[nodiscard]] inline auto flag_skip() const { return this->flag_skip_; }
[[nodiscard]] inline auto flag_negated() const { return this->flag_negated_; }
[[nodiscard]] inline ts::command_result release_error() {
assert(!std::exchange(this->error_released_, true));
return std::move(this->error_);
}
inline void emplace_custom_error(ts::command_result&& result) {
assert(!this->error_released_);
this->error_.reset(std::forward<ts::command_result>(result));
}
inline void apply_to(const std::shared_ptr<permission::v2::PermissionManager>& manager, permission::v2::PermissionUpdateType mode) const {
if(this->is_grant_permission()) {
manager->set_permission(this->permission_type(), { 0, this->value() }, permission::v2::PermissionUpdateType::do_nothing, mode);
} else {
manager->set_permission(
this->permission_type(),
{ this->value(), 0 },
mode,
permission::v2::PermissionUpdateType::do_nothing,
this->flag_skip(),
this->flag_negated()
);
}
}
inline void apply_to_channel(const std::shared_ptr<permission::v2::PermissionManager>& manager, permission::v2::PermissionUpdateType mode, ChannelId channel_id) const {
if(this->is_grant_permission()) {
manager->set_channel_permission(this->permission_type(), channel_id, { this->value(), true }, permission::v2::PermissionUpdateType::do_nothing, mode);
} else {
manager->set_channel_permission(
this->permission_type(),
channel_id,
{ this->value(), true },
mode,
permission::v2::PermissionUpdateType::do_nothing
);
}
}
[[nodiscard]] inline bool is_group_property() const {
return permission_is_group_property(this->permission_type());
}
[[nodiscard]] inline bool is_client_view_property() const {
return permission_is_client_property(this->permission_type());
}
private:
std::shared_ptr<permission::PermissionTypeEntry> permission_{nullptr};
bool grant_{false};
bool flag_skip_{false};
bool flag_negated_{false};
permission::PermissionValue value_{0};
ts::command_result error_{error::ok};
#ifndef NDEBUG
bool error_released_{false};
#endif
};
template <bool kParseValue>
class PermissionBulksParser {
public:
PermissionBulksParser(const PermissionBulksParser&) = delete;
PermissionBulksParser(PermissionBulksParser&&) = default;
template <typename base_iterator>
struct FilteredPermissionIterator : public base_iterator {
public:
FilteredPermissionIterator() = default;
explicit FilteredPermissionIterator(base_iterator position, base_iterator end = {}) : base_iterator{position}, end_{end} {
if(*this != this->end_) {
const auto& entry = **this;
if(entry.has_error())
this->operator++();
}
}
FilteredPermissionIterator& operator++() {
while(true) {
base_iterator::operator++();
if(*this == this->end_) break;
const auto& entry = **this;
if(!entry.has_error()) break;
}
return *this;
}
[[nodiscard]] FilteredPermissionIterator operator++(int) const {
FilteredPermissionIterator copy = *this;
++*this;
return copy;
}
private:
base_iterator end_;
};
struct FilteredPermissionListIterable {
typedef typename std::vector<PermissionBulkParser<kParseValue>>::const_iterator const_iterator;
public:
FilteredPermissionListIterable(const_iterator begin, const_iterator end) noexcept : begin_{begin}, end_{end} {}
FilteredPermissionIterator<const_iterator> begin() const {
return FilteredPermissionIterator{this->begin_, this->end_};
}
FilteredPermissionIterator<const_iterator> end() const {
return FilteredPermissionIterator{this->end_, this->end_};
}
private:
const_iterator begin_;
const_iterator end_;
};
explicit PermissionBulksParser(ts::Command& command) {
this->permissions_.reserve(command.bulkCount());
for(size_t index{0}; index < command.bulkCount(); index++)
this->permissions_.emplace_back(command[index]);
}
[[nodiscard]] inline bool validate(const std::shared_ptr<server::ConnectedClient>& issuer, ChannelId channel_id) {
auto ignore_granted_values = permission::v2::permission_granted(1, issuer->calculate_permission(permission::b_permission_modify_power_ignore, channel_id));
if(!ignore_granted_values) {
auto max_value = issuer->calculate_permission(permission::i_permission_modify_power, channel_id, false);
if(!max_value.has_value) {
for(PermissionBulkParser<kParseValue>& permission : this->permissions_) {
if(permission.has_error()) continue;
permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power});
}
return false;
}
for(size_t index{0}; index < this->permissions_.size(); index++) {
PermissionBulkParser<kParseValue>& permission = this->permissions_[index];
if(permission.has_error()) continue;
if(kParseValue && permission_require_granted_value(permission.permission_type()) && !permission::v2::permission_granted(permission.value(), max_value)) {
permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power});
continue;
}
if(!permission::v2::permission_granted(1, issuer->calculate_permission(permission.permission_type(), channel_id, true))) {
permission.emplace_custom_error(ts::command_result{permission::i_permission_modify_power});
continue;
}
}
}
return true;
}
[[nodiscard]] inline FilteredPermissionListIterable iterate_valid_permissions() const {
return FilteredPermissionListIterable{this->permissions_.begin(), this->permissions_.end()};
}
[[nodiscard]] inline ts::command_result build_command_result() {
assert(!std::exchange(this->result_created_, true));
ts::command_result_bulk result{};
for(auto& permission : this->permissions_)
result.insert_result(std::forward<ts::command_result>(permission.release_error()));
return ts::command_result{std::move(result)};
}
private:
std::vector<PermissionBulkParser<kParseValue>> permissions_{};
#ifndef NDEBUG
bool result_created_{false};
#endif
};
}
+361 -193
View File
@@ -5,10 +5,10 @@
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "src/server/file/LocalFileServer.h"
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@@ -20,7 +20,6 @@
#include <cstdint>
#include "helpers.h"
#include "./bulk_parsers.h"
#include <Properties.h>
#include <log/LogUtils.h>
@@ -33,7 +32,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
//{findError("parameter_invalid"), "could not resolve permission " + (cmd[index].has("permid") ? cmd[index]["permid"].as<string>() : cmd[index]["permsid"].as<string>())}; \
//TODO: Log missing permissions?
@@ -408,16 +407,46 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) {
if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"};
ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true);
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
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};
bool updateList{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
updateList |= ppermission.is_group_property();
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
auto conOnError = cmd[0].has("continueonerror");
bool updateList = false;
auto permission_manager = channelGroup->permissions();
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
if(permission_require_granted_value(permType) && !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) {
permission_manager->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value);
} else {
permission_manager->set_permission(
permType,
{cmd[index]["permvalue"], 0},
permission::v2::PermissionUpdateType::set_value,
permission::v2::PermissionUpdateType::do_nothing,
cmd[index]["permskip"].as<bool>() ? 1 : 0,
cmd[index]["permnegated"].as<bool>() ? 1 : 0
);
updateList |= permission_is_group_property(permType);
}
}
if(updateList)
channelGroup->apply_properties_from_permissions();
@@ -436,8 +465,7 @@ command_result ConnectedClient::handleCommandChannelGroupAddPerm(Command &cmd) {
}
});
}
return pparser.build_command_result();
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) {
@@ -447,14 +475,31 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) {
if (!channelGroup || channelGroup->target() != GROUPTARGET_CHANNEL) return command_result{error::parameter_invalid, "invalid channel group id"};
ACTION_REQUIRES_GROUP_PERMISSION(channelGroup, permission::i_channel_group_needed_modify_power, permission::i_channel_group_modify_power, true);
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
bool updateList{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(channelGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
updateList |= ppermission.is_group_property();
bool updateList = false;
bool conOnError = cmd[0].has("continueonerror");
auto permission_manager = channelGroup->permissions();
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd)
if(!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) {
permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value);
} else {
permission_manager->set_permission(
permType,
permission::v2::empty_permission_values,
permission::v2::PermissionUpdateType::delete_value,
permission::v2::PermissionUpdateType::do_nothing
);
updateList |= permission_is_group_property(permType);
}
}
if(updateList)
@@ -475,8 +520,7 @@ command_result ConnectedClient::handleCommandChannelGroupDelPerm(Command &cmd) {
}
});
}
return pparser.build_command_result();
return command_result{error::ok};
}
//TODO: Test if parent or previous is deleted!
@@ -485,66 +529,51 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(25);
CMD_CHK_PARM_COUNT(1);
std::shared_ptr<TreeView::LinkedTreeEntry> parent = nullptr;
std::shared_ptr<BasicChannel> created_channel = nullptr, old_default_channel;
auto target_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get();
auto& tree_lock = this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock();
unique_lock tree_channel_lock(tree_lock);
if (cmd[0].has("cpid") && cmd["cpid"].as<ChannelId>() != 0 && cmd["cpid"].as<int>() != -1) {
parent = target_tree->findLinkedChannel(cmd["cpid"].as<ChannelId>());
if (!parent) return command_result{error::channel_invalid_id, "Cant resolve parent channel"};
}
ChannelId parent_channel_id = parent ? parent->entry->channelId() : 0;
#define test_permission(required, permission_type) \
do {\
if(!permission::v2::permission_granted(required, this->calculate_permission(permission_type, parent_channel_id, false, permission_cache))) \
return command_result{permission_type};\
} while(0)
//TODO: Use for this here the cache as well!
auto permission_cache = make_shared<CalculateCache>();
if(parent) test_permission(1, permission::b_channel_create_child);
if (cmd[0].has("channel_order")) test_permission(1, permission::b_channel_create_with_sortorder);
if (cmd[0].has("cpid") && cmd["cpid"].as<uint64_t>() != 0) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_child, 1, permission_cache);
if (cmd[0].has("channel_order")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_sortorder, 1, permission_cache);
if(!cmd[0].has("channel_flag_permanent")) cmd[0]["channel_flag_permanent"] = false;
if(!cmd[0].has("channel_flag_semi_permanent")) cmd[0]["channel_flag_semi_permanent"] = false;
if(!cmd[0].has("channel_flag_default")) cmd[0]["channel_flag_default"] = false;
if(!cmd[0].has("channel_flag_password")) cmd[0]["channel_flag_password"] = false;
if (cmd[0]["channel_flag_permanent"].as<bool>()) test_permission(1, permission::b_channel_create_permanent);
else if (cmd[0]["channel_flag_semi_permanent"].as<bool>()) test_permission(1, permission::b_channel_create_semi_permanent);
else test_permission(1, permission::b_channel_create_temporary);
if (cmd[0]["channel_flag_permanent"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_permanent, 1, permission_cache);
else if (cmd[0]["channel_flag_semi_permanent"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_semi_permanent, 1, permission_cache);
else ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_temporary, 1, permission_cache);
if (!cmd[0]["channel_flag_permanent"].as<bool>() && !this->server) return command_result{error::parameter_invalid, "You can only create a permanent channel"};
if (cmd[0]["channel_flag_default"].as<bool>()) test_permission(1, permission::b_channel_create_with_default);
if (cmd[0]["channel_flag_password"].as<bool>()) test_permission(1, permission::b_channel_create_with_password);
else if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_create_modify_with_force_password, parent_channel_id, false, permission_cache)))
if (cmd[0]["channel_flag_default"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_default, 1, permission_cache);
if (cmd[0]["channel_flag_password"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_password, 1, permission_cache);
else if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_create_modify_with_force_password, 0, false, permission_cache)))
return command_result{permission::b_channel_create_modify_with_force_password};
if(cmd[0].has("channel_password") && this->getType() == ClientType::CLIENT_QUERY)
cmd["channel_password"] = base64::decode(digest::sha1(cmd["channel_password"].string()));
if (cmd[0].has("channel_description")) test_permission(1, permission::b_channel_create_with_description);
if (cmd[0].has("channel_description")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_description, 1, permission_cache);
if (cmd[0].has("channel_maxclients") || (cmd[0].has("channel_flag_maxclients_unlimited") && !cmd["channel_flag_maxclients_unlimited"].as<bool>())) {
test_permission(1, permission::b_channel_create_with_maxclients);
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_maxclients, 1, permission_cache);
if(!cmd[0]["channel_flag_permanent"].as<bool>() && !cmd[0]["channel_flag_semi_permanent"].as<bool>()) {
cmd["channel_maxclients"] = -1;
cmd["channel_flag_maxclients_unlimited"] = 1;
}
}
if (cmd[0].has("channel_maxfamilyclients")) test_permission(1, permission::b_channel_create_with_maxfamilyclients);
if (cmd[0].has("channel_needed_talk_power")) test_permission(1,permission::b_channel_create_with_needed_talk_power);
if (cmd[0].has("channel_topic")) test_permission(1,permission::b_channel_create_with_topic);
if (cmd[0].has("channel_maxfamilyclients")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_maxfamilyclients, 1, permission_cache);
if (cmd[0].has("channel_needed_talk_power")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_needed_talk_power, 1, permission_cache);
if (cmd[0].has("channel_topic")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_topic, 1, permission_cache);
auto target_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get();
auto& tree_lock = this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock();
unique_lock tree_channel_lock(tree_lock);
if(cmd[0].has("channel_conversation_history_length")) {
auto value = cmd["channel_conversation_history_length"].as<int64_t>();
if(value == 0) {
test_permission(1, permission::b_channel_create_modify_conversation_history_unlimited);
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_modify_conversation_history_unlimited, 1, permission_cache);
} else {
test_permission(1, permission::i_channel_create_modify_conversation_history_length);
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::i_channel_create_modify_conversation_history_length, 1, permission_cache);
}
}
@@ -556,10 +585,9 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
else
cmd["channel_delete_delay"] = 0;
} else {
test_permission(cmd["channel_delete_delay"].as<permission::PermissionValue>(), permission::i_channel_create_modify_with_temp_delete_delay);
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::i_channel_create_modify_with_temp_delete_delay, cmd["channel_delete_delay"].as<permission::PermissionValue>(), permission_cache);
}
}
#undef test_permission
{
size_t created_total = 0, created_tmp = 0, created_semi = 0, created_perm = 0;
@@ -579,14 +607,14 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
if(this->server && created_total >= this->server->properties()[property::VIRTUALSERVER_MAX_CHANNELS].as<uint64_t>())
return command_result{error::channel_limit_reached};
auto max_channels = this->calculate_permission(permission::i_client_max_channels, parent_channel_id, false, permission_cache);
auto max_channels = this->calculate_permission(permission::i_client_max_channels, 0, false, permission_cache);
if(max_channels.has_value) {
if(!permission::v2::permission_granted(created_perm + created_semi + created_tmp + 1, max_channels))
return command_result{permission::i_client_max_channels};
}
if (cmd[0]["channel_flag_permanent"].as<bool>()) {
max_channels = this->calculate_permission(permission::i_client_max_permanent_channels, parent_channel_id, false, permission_cache);
max_channels = this->calculate_permission(permission::i_client_max_permanent_channels, 0, false, permission_cache);
if(max_channels.has_value) {
if(!permission::v2::permission_granted(created_perm + 1, max_channels))
@@ -594,7 +622,7 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
}
}
else if (cmd[0]["channel_flag_semi_permanent"].as<bool>()) {
max_channels = this->calculate_permission(permission::i_client_max_semi_channels, parent_channel_id, false, permission_cache);
max_channels = this->calculate_permission(permission::i_client_max_semi_channels, 0, false, permission_cache);
if(max_channels.has_value) {
if(!permission::v2::permission_granted(created_semi + 1, max_channels))
@@ -602,7 +630,7 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
}
}
else {
max_channels = this->calculate_permission(permission::i_client_max_temporary_channels, parent_channel_id, false, permission_cache);
max_channels = this->calculate_permission(permission::i_client_max_temporary_channels, 0, false, permission_cache);
if(max_channels.has_value) {
if(!permission::v2::permission_granted(created_tmp + 1, max_channels))
@@ -612,12 +640,20 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
}
//TODO check voice (opus etc)
std::shared_ptr<TreeView::LinkedTreeEntry> parent = nullptr;
std::shared_ptr<BasicChannel> created_channel = nullptr, old_default_channel;
//bool enforce_permanent_parent = cmd[0]["channel_flag_default"].as<bool>(); //TODO check parents here
{ //Checkout the parent(s)
if (cmd[0].has("cpid") && cmd["cpid"].as<ChannelId>() != 0 && cmd["cpid"].as<int>() != -1) {
parent = target_tree->findLinkedChannel(cmd["cpid"].as<ChannelId>());
if (!parent) return command_result{error::channel_invalid_id, "Cant resolve parent channel"};
}
{
auto min_channel_deep = this->calculate_permission(permission::i_channel_min_depth, parent_channel_id, false, permission_cache);
auto max_channel_deep = this->calculate_permission(permission::i_channel_max_depth, parent_channel_id, false, permission_cache);
auto min_channel_deep = this->calculate_permission(permission::i_channel_min_depth, 0, false, permission_cache);
auto max_channel_deep = this->calculate_permission(permission::i_channel_max_depth, 0, false, permission_cache);
if(min_channel_deep.has_value || max_channel_deep.has_value) {
auto channel_deep = 0;
@@ -668,8 +704,8 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
created_channel->properties()[property::CHANNEL_CREATED_BY] = this->getClientDatabaseId();
{
auto default_modify_power = this->calculate_permission(permission::i_channel_modify_power, parent_channel_id, false, permission_cache);
auto default_delete_power = this->calculate_permission(permission::i_channel_delete_power, parent_channel_id, false, permission_cache);
auto default_modify_power = this->calculate_permission(permission::i_channel_modify_power, 0, false, permission_cache);
auto default_delete_power = this->calculate_permission(permission::i_channel_delete_power, 0, false, permission_cache);
auto permission_manager = created_channel->permissions();
permission_manager->set_permission(
@@ -693,14 +729,14 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
if (prop == "cpid") continue;
if (prop == "cid") continue;
const auto &property = property::find<property::ChannelProperties>(prop);
if(property == property::CHANNEL_UNDEFINED) {
const auto &property = property::info<property::ChannelProperties>(prop);
if(*property == property::CHANNEL_UNDEFINED) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + prop);
continue;
}
if(!property.validate_input(cmd[prop].as<string>())) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as<string>() + "', Property: '" + std::string{property.name} + "')");
if(!property->validate_input(cmd[prop].as<string>())) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as<string>() + "', Property: '" + property->name + "')");
continue;
}
created_channel->properties()[property] = cmd[prop].as<std::string>();
@@ -803,7 +839,7 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
return command_result{error::ok};
}
std::deque<const property::PropertyDescription*> keys;
std::deque<std::shared_ptr<property::PropertyDescription>> keys;
bool require_write_lock = false;
bool update_max_clients = false;
bool update_max_family_clients = false;
@@ -822,23 +858,23 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
if(key == "return_code")
continue;
const auto &property = property::find<property::ChannelProperties>(key);
if(property == property::CHANNEL_UNDEFINED) {
const auto &property = property::info<property::ChannelProperties>(key);
if(*property == property::CHANNEL_UNDEFINED) {
logError(this->getServerId(), R"({} Tried to edit a not existing channel property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if((property.flags & property::FLAG_USER_EDITABLE) == 0) {
if((property->flags & property::FLAG_USER_EDITABLE) == 0) {
logError(this->getServerId(), "{} Tried to change a channel property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if(!property.validate_input(cmd[key].as<string>())) {
if(!property->validate_input(cmd[key].as<string>())) {
logError(this->getServerId(), "{} Tried to change a channel property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if(channel->properties()[property].as<string>() == cmd[key].as<string>())
if(channel->properties()[*property].as<string>() == cmd[key].as<string>())
continue; /* we dont need to update stuff which is the same */
if(key == "channel_icon_id") {
@@ -928,7 +964,7 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
);
continue;
}
keys.push_back(&property);
keys.push_back(property);
}
unique_lock server_channel_w_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock(), defer_lock);
@@ -948,11 +984,11 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
return command_result{error::parameter_missing};
else
cmd["channel_password"] = ""; /* no password set */
keys.push_back(&property::describe(property::CHANNEL_PASSWORD));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_PASSWORD));
}
if(!cmd[0].has("channel_flag_password")) {
cmd["channel_flag_password"] = !cmd["channel_password"].string().empty();
keys.push_back(&property::describe(property::CHANNEL_FLAG_PASSWORD));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_PASSWORD));
}
if(cmd["channel_flag_password"].as<bool>()) {
@@ -968,28 +1004,27 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
}
/* test the default channel update */
const auto target_will_be_default = cmd[0].has("channel_flag_default") ? cmd["channel_flag_default"].as<bool>() : channel->defaultChannel();
if(target_will_be_default) {
if(cmd[0].has("channel_flag_default") || channel->defaultChannel()) {
if(target_channel_type != ChannelType::permanent)
return command_result{error::channel_default_require_permanent}; /* default channel is not allowed to be non permanent */
if((cmd[0].has("channel_flag_password") && cmd["channel_flag_password"].as<bool>()) || channel->properties()[property::CHANNEL_FLAG_PASSWORD]) {
cmd["channel_flag_password"] = false;
cmd["channel_password"] = "";
keys.push_back(&property::describe(property::CHANNEL_FLAG_PASSWORD));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_PASSWORD));
}
if(target_will_be_default) {
if(cmd[0].has("channel_flag_default")) {
cmd["channel_maxclients"] = -1;
cmd["channel_flag_maxclients_unlimited"] = true;
keys.push_back(&property::describe(property::CHANNEL_MAXCLIENTS));
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
update_max_clients = true;
cmd["channel_maxfamilyclients"] = -1;
cmd["channel_flag_maxfamilyclients_inherited"] = false;
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
cmd["channel_flag_maxfamilyclients_inherited"] = true;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
update_max_family_clients = true;
}
}
@@ -1000,15 +1035,15 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
if(channel->properties()[property::CHANNEL_MAXCLIENTS].as<int>() != -1) {
cmd["channel_maxclients"] = -1;
cmd["channel_flag_maxclients_unlimited"] = true;
keys.push_back(&property::describe(property::CHANNEL_MAXCLIENTS));
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
update_max_clients = true;
}
if(channel->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as<int>() != -1) {
cmd["channel_maxfamilyclients"] = -1;
cmd["channel_flag_maxfamilyclients_inherited"] = true;
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
update_max_family_clients = true;
}
}
@@ -1031,12 +1066,12 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
cmd["channel_maxclients"] = -1;
else
return command_result{error::parameter_missing, "channel_maxclients"}; /* max clients must be specified */
keys.push_back(&property::describe(property::CHANNEL_MAXCLIENTS));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
}
if(!cmd[0].has("channel_flag_maxclients_unlimited")) {
cmd["channel_flag_maxclients_unlimited"] = cmd["channel_maxclients"].as<int>() < 0;
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
}
if(cmd["channel_flag_maxclients_unlimited"].as<bool>() && cmd["channel_maxclients"].as<int>() != -1)
@@ -1048,24 +1083,32 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
/* test the max family clients parameters */
if(update_max_family_clients) {
//auto channel_maxfamilyclients = cmd[0].has("channel_maxfamilyclients") ? std::optional<int>{cmd["channel_maxfamilyclients"].as<int>()} : std::nullopt;
//auto channel_flag_maxfamilyclients_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") ? std::optional<bool>{cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>()} : std::nullopt;
//auto channel_flag_maxfamilyclients_inherited = cmd[0].has("channel_flag_maxfamilyclients_inherited") ? std::optional<bool>{cmd["channel_flag_maxfamilyclients_inherited"].as<bool>()} : std::nullopt;
/* update actual count from flags */
if(!cmd[0].has("channel_maxfamilyclients")) {
if(cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>()) {
cmd["channel_maxfamilyclients"] = -1;
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
} else if(cmd[0].has("channel_flag_maxfamilyclients_inherited") && cmd["channel_flag_maxfamilyclients_inherited"].as<bool>()) {
cmd["channel_maxfamilyclients"] = -1;
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
} else {
return command_result{error::parameter_missing, "channel_maxfamilyclients"}; /* since its not unlimited or inherited, channel_maxfamilyclients must be specified */
}
if(cmd[0].has("channel_flag_maxfamilyclients_unlimited")) {
if(cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>())
cmd["channel_flag_maxfamilyclients_inherited"] = false;
else
cmd["channel_flag_maxfamilyclients_inherited"] = true;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
} else if(cmd[0].has("channel_flag_maxfamilyclients_inherited")) {
if(cmd["channel_flag_maxfamilyclients_inherited"].as<bool>())
cmd["channel_flag_maxfamilyclients_unlimited"] = false;
else
cmd["channel_flag_maxfamilyclients_unlimited"] = true;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED));
} else /* not really possible */
return command_result{error::parameter_missing, "channel_maxfamilyclients"}; /* family max clients must be */
cmd["channel_maxfamilyclients"] = -1;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
}
//keep this order because this command: "channeledit cid=<x> channel_maxfamilyclients=-1" should set max family clients mode to inherited
if(!cmd[0].has("channel_flag_maxfamilyclients_inherited")) {
auto flag_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>();
if(flag_unlimited)
cmd["channel_flag_maxfamilyclients_inherited"] = false;
else
cmd["channel_flag_maxfamilyclients_inherited"] = cmd["channel_maxfamilyclients"].as<int>() < 0;
}
//Update the flags from channel_maxfamilyclients if needed
if(!cmd[0].has("channel_flag_maxfamilyclients_unlimited")) {
auto flag_inherited = cmd[0].has("channel_flag_maxfamilyclients_inherited") && cmd["channel_flag_maxfamilyclients_inherited"].as<bool>();
if(flag_inherited)
@@ -1074,15 +1117,6 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
cmd["channel_flag_maxfamilyclients_unlimited"] = cmd["channel_maxfamilyclients"].as<int>() < 0;
}
if(!cmd[0].has("channel_flag_maxfamilyclients_inherited")) {
auto flag_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>();
if(flag_unlimited)
cmd["channel_flag_maxfamilyclients_inherited"] = false;
else
cmd["channel_flag_maxfamilyclients_inherited"] = cmd["channel_maxfamilyclients"].as<int>() < 0;
}
/* final checkup */
if(cmd["channel_flag_maxfamilyclients_inherited"].as<bool>() && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>())
return command_result{error::channel_invalid_flags}; /* both at the same time are not possible */
@@ -1106,10 +1140,10 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
auto self_ref = this->ref();
shared_ptr<BasicChannel> old_default_channel;
deque<shared_ptr<BasicChannel>> child_channel_updated;
for(const property::PropertyDescription* key : keys) {
for(const std::shared_ptr<property::PropertyDescription>& key : keys) {
if(*key == property::CHANNEL_ORDER) {
/* TODO: May move that up because if it fails may some other props have already be applied */
if (!channel_tree->change_order(channel, cmd[std::string{key->name}]))
if (!channel_tree->change_order(channel, cmd[key->name]))
return command_result{error::channel_invalid_order, "Can't change order id"};
if(this->server) {
@@ -1152,7 +1186,7 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
continue;
}
if(!cmd[std::string{key->name}].as<bool>()) {
if(!cmd[key->name].as<bool>()) {
old_default_channel = nullptr;
continue;
}
@@ -1211,13 +1245,13 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
if(conversation_manager) {
auto conversation = conversation_manager->get(channel->channelId());
if(conversation)
conversation->set_history_length(cmd[std::string{key->name}]);
conversation->set_history_length(cmd[key->name]);
}
} else if(*key == property::CHANNEL_NEEDED_TALK_POWER) {
channel->permissions()->set_permission(permission::i_client_needed_talk_power, {cmd[key->name].as<int>(), 0}, permission::v2::set_value, permission::v2::do_nothing);
}
channel->properties()[*key] = cmd[std::string{key->name}].string();
channel->properties()[key] = cmd[key->name].string();
}
if(this->server) {
vector<property::ChannelProperties> key_vector;
@@ -1437,33 +1471,101 @@ command_result ConnectedClient::handleCommandChannelAddPerm(Command &cmd) {
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true);
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
if(!pparser.validate(this->ref(), channel->channelId()))
return pparser.build_command_result();
auto max_value = this->calculate_permission(permission::i_permission_modify_power, channel_id, 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, channel_id));
auto updateClients = false, update_view = false, update_channel_properties = false;
bool conOnError = cmd[0].has("continueonerror");
auto permission_manager = channel->permissions();
auto updateClients = false, update_join_permissions = false, update_channel_properties = false;
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::set_value);
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
updateClients |= ppermission.is_client_view_property();
update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power;
update_channel_properties |= channel->permission_require_property_update(ppermission.permission_type());
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, channel_id, true))) {
if(conOnError) continue;
return command_result{permission::i_permission_modify_power};
}
if (grant) {
permission_manager->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value);
} else {
permission_manager->set_permission(
permType,
{cmd[index]["permvalue"], 0},
permission::v2::PermissionUpdateType::set_value,
permission::v2::PermissionUpdateType::do_nothing,
cmd[index]["permskip"].as<bool>() ? 1 : 0,
cmd[index]["permnegated"].as<bool>() ? 1 : 0
);
updateClients |= permission_is_client_property(permType);
update_view |= permType == permission::i_channel_needed_view_power;
update_channel_properties |= channel->permission_require_property_update(permType);
if (permType == permission::i_icon_id) {
if(this->server) {
auto self_ref = this->ref();
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
shared_lock client_channel_lock(cl->channel_lock);
cl->notifyChannelEdited(channel, {property::CHANNEL_ICON_ID}, self_ref, false);
});
}
continue;
}
}
}
if(update_channel_properties && this->server)
this->server->update_channel_from_permissions(channel, this->ref());
/* broadcast the updated channel properties */
if(update_channel_properties) {
auto updates = channel->update_properties_from_permissions();
if(!updates.empty() && this->server){
auto self_ref = this->ref();
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
shared_lock client_channel_lock(cl->channel_lock);
cl->notifyChannelEdited(channel, updates, self_ref, false);
});
}
}
if((updateClients || update_join_permissions) && this->server) {
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
if(updateClients && cl->currentChannel == channel)
cl->updateChannelClientProperties(true, true);
if(update_join_permissions)
cl->join_state_id++;
if(updateClients && this->server)
for(const auto& client : this->server->getClientsByChannel(channel)) {
/* let them lock the server channel tree as well (read lock so does not matter) */
client->updateChannelClientProperties(true, true);
client->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
}
if(update_view && this->server) {
auto l_source = this->server->channelTree->findLinkedChannel(channel->channelId());
this->server->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
/* server tree read lock still active */
auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId());
sassert(l_source);
if(cl->currentChannel) sassert(l_target);
{
unique_lock client_channel_lock(cl->channel_lock);
deque<ChannelId> deleted;
for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) {
if(update_entry.first)
cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel);
else deleted.push_back(update_entry.second->channelId());
}
if(!deleted.empty())
cl->notifyChannelHide(deleted, false);
}
});
}
return pparser.build_command_result();
return command_result{error::ok};;
}
command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) {
@@ -1475,33 +1577,71 @@ command_result ConnectedClient::handleCommandChannelDelPerm(Command &cmd) {
ACTION_REQUIRES_CHANNEL_PERMISSION(channel, permission::i_channel_needed_permission_modify_power, permission::i_channel_permission_modify_power, true);
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
if(!pparser.validate(this->ref(), channel->channelId()))
return pparser.build_command_result();
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, channel_id));
bool conOnError = cmd[0].has("continueonerror");
auto updateClients = false, update_view = false, update_channel_properties = false;
auto permission_manager = channel->permissions();
auto updateClients = false, update_join_permissions = false, update_channel_properties = false;
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(permission_manager, permission::v2::PermissionUpdateType::delete_value);
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
updateClients |= ppermission.is_client_view_property();
update_join_permissions = ppermission.permission_type() == permission::i_channel_needed_join_power;
update_channel_properties |= channel->permission_require_property_update(ppermission.permission_type());
if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, channel_id, true))) {
if(conOnError) continue;
return command_result{permission::i_permission_modify_power};
}
if (grant) {
permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value);
} else {
permission_manager->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing);
updateClients |= permission_is_client_property(permType);
update_view |= permType == permission::i_channel_needed_view_power;
update_channel_properties |= channel->permission_require_property_update(permType);
}
}
if(update_channel_properties && this->server)
this->server->update_channel_from_permissions(channel, this->ref());
/* broadcast the updated channel properties */
if(update_channel_properties) {
auto updates = channel->update_properties_from_permissions();
if(!updates.empty() && this->server){
auto self_ref = this->ref();
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
shared_lock client_channel_lock(cl->channel_lock);
cl->notifyChannelEdited(channel, updates, self_ref, false);
});
}
}
if((updateClients || update_join_permissions) && this->server) {
if(updateClients && this->server)
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
if(updateClients && cl->currentChannel == channel)
if(cl->currentChannel == channel) {
cl->updateChannelClientProperties(true, true);
if(update_join_permissions)
cl->join_state_id++;
cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
}
});
if(update_view && this->server) {
this->server->forEachClient([&](std::shared_ptr<ConnectedClient> cl) {
/* server tree read lock still active */
auto l_source = cl->server->channelTree->findLinkedChannel(channel->channelId());
auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId());
sassert(l_source);
if(cl->currentChannel) sassert(l_target);
{
unique_lock client_channel_lock(cl->channel_lock);
deque<ChannelId> deleted;
for(const auto& update_entry : cl->channels->update_channel(l_source, l_target)) {
if(update_entry.first)
cl->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel);
else deleted.push_back(update_entry.second->channelId());
}
if(!deleted.empty())
cl->notifyChannelHide(deleted, false);
}
});
}
return pparser.build_command_result();
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandChannelClientPermList(Command &cmd) {
@@ -1514,7 +1654,7 @@ command_result ConnectedClient::handleCommandChannelClientPermList(Command &cmd)
ACTION_REQUIRES_PERMISSION(permission::b_virtualserver_channelclient_permission_list, 1, channel_id);
if(!serverInstance->databaseHelper()->validClientDatabaseId(this->server, cmd["cldbid"])) return command_result{error::client_invalid_id};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cmd["cldbid"].as<ClientDbId>());
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->serverId : 0, cmd["cldbid"].as<ClientDbId>());
Command res(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifychannelclientpermlist" : "");
@@ -1574,27 +1714,35 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
auto channel = dynamic_pointer_cast<ServerChannel>(l_channel->entry);
if(!channel) return command_result{error::vs_critical};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->serverId : 0, cldbid);
{
auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id);
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
}
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
if(!pparser.validate(this->ref(), channel->channelId()))
return pparser.build_command_result();
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, channel_id));
bool update_view{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::delete_value, channel->channelId());
update_view |= ppermission.is_client_view_property();
bool conOnError = cmd[0].has("continueonerror"), update_view = false;
auto cll = this->server->findClientsByCldbId(cldbid);
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
if(!ignore_granted_values && !permission::v2::permission_granted(0, this->calculate_permission(permType, channel_id, true))) {
if(conOnError) continue;
return command_result{permission::i_permission_modify_power};
}
if (grant) {
mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::do_nothing, permission::v2::delete_value);
} else {
mgr->set_channel_permission(permType, channel->channelId(), permission::v2::empty_permission_values, permission::v2::delete_value, permission::v2::do_nothing);
update_view = permType == permission::b_channel_ignore_view_power || permType == permission::i_channel_view_power;
}
}
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
auto onlineClients = this->server->findClientsByCldbId(cldbid);
if (!onlineClients.empty()) {
for (const auto &elm : onlineClients) {
serverInstance->databaseHelper()->saveClientPermissions(this->server ? this->server->serverId : 0, cldbid, mgr);
if (!cll.empty()) {
for (const auto &elm : cll) {
if(elm->update_cached_permissions()) /* update cached calculated permissions */
elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
@@ -1620,7 +1768,7 @@ command_result ConnectedClient::handleCommandChannelClientDelPerm(Command &cmd)
}
}
return pparser.build_command_result();
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd) {
@@ -1636,26 +1784,46 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
auto channel = dynamic_pointer_cast<ServerChannel>(l_channel->entry);
if(!channel) return command_result{error::vs_critical};
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server, cldbid);
auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id);
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
if(!pparser.validate(this->ref(), channel->channelId()))
return pparser.build_command_result();
bool update_view{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to_channel(mgr, permission::v2::PermissionUpdateType::set_value, channel->channelId());
update_view |= ppermission.is_client_view_property();
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->serverId : 0, cldbid);
{
auto required_permissions = this->server->calculate_permission(permission::i_client_needed_permission_modify_power, cmd["cldbid"], ClientType::CLIENT_TEAMSPEAK, channel_id);
ACTION_REQUIRES_PERMISSION(permission::i_client_permission_modify_power, required_permissions, channel_id);
}
serverInstance->databaseHelper()->saveClientPermissions(this->server, cldbid, mgr);
auto max_value = this->calculate_permission(permission::i_permission_modify_power, channel_id, 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, channel_id));
auto update_view = false;
auto onlineClients = this->server->findClientsByCldbId(cldbid);
if (!onlineClients.empty())
for (const auto &elm : onlineClients) {
bool conOnError = cmd[0].has("continueonerror");
auto onlineClientInstances = this->server->findClientsByCldbId(cldbid);
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
if(permission_require_granted_value(permType) && !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, channel_id, true))) {
if(conOnError) continue;
return command_result{permission::i_permission_modify_power};
}
if (grant) {
mgr->set_channel_permission(permType, channel->channelId(), {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value);
} else {
mgr->set_channel_permission(permType, channel->channelId(), {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"] ? 1 : 0, cmd[index]["permnegated"] ? 1 : 0);
update_view = permType == permission::b_channel_ignore_view_power || permType == permission::i_channel_view_power;
}
}
serverInstance->databaseHelper()->saveClientPermissions(this->server ? this->server->serverId : 0, cldbid, mgr);
if (!onlineClientInstances.empty())
for (const auto &elm : onlineClientInstances) {
if (elm->update_cached_permissions()) /* update cached calculated permissions */
elm->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
@@ -1679,7 +1847,7 @@ command_result ConnectedClient::handleCommandChannelClientAddPerm(Command &cmd)
elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
}
return pparser.build_command_result();
return command_result{error::ok};
}
+142 -221
View File
@@ -1,14 +1,13 @@
#include <memory>
#include <vector>
#include <bitset>
#include <algorithm>
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "src/server/file/LocalFileServer.h"
#include "../../server/file/FileServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@@ -20,7 +19,6 @@
#include <cstdint>
#include "helpers.h"
#include "./bulk_parsers.h"
#include <Properties.h>
#include <log/LogUtils.h>
@@ -34,28 +32,24 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
#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);
shared_lock tree_lock(this->channel_lock);
if (!client || (client.client != this && !this->isClientVisible(client.client, false)))
return command_result{error::client_invalid_id, ""};
if (!client || (client.client != this && !this->isClientVisible(client.client, false)))
return command_result{error::client_invalid_id, ""};
deque<const 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(&prop.type());
}
this->notifyClientUpdated(client.client, props, false);
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));
}
if(client.client == this && this->getType() == ClientType::CLIENT_TEAMSPEAK)
this->subscribeChannel({this->currentChannel}, true, true); /* lets show the clients in the current channel because we've not done that while joining (speed improvement ;))*/
this->notifyClientUpdated(client.client, props, false);
return command_result{error::ok};
}
@@ -63,56 +57,29 @@ command_result ConnectedClient::handleCommandClientKick(Command &cmd) {
CMD_REQ_SERVER;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
command_result_bulk result{};
result.reserve(cmd.bulkCount());
std::vector<ConnectedLockedClient<ConnectedClient>> clients{};
clients.reserve(cmd.bulkCount());
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>();
auto target_channel = type == ViewReasonId::VREASON_CHANNEL_KICK ? this->server->channelTree->getDefaultChannel() : nullptr;
auto kick_power = type == ViewReasonId::VREASON_CHANNEL_KICK ?
this->calculate_permission(permission::i_client_kick_from_channel_power, target_channel->channelId()) :
this->calculate_permission(permission::i_client_kick_from_server_power, 0);
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};
for(size_t index = 0; index < cmd.bulkCount(); index++) {
ConnectedLockedClient<ConnectedClient> client{this->server->find_client_by_id(cmd[index]["clid"].as<ClientId>())};
if (!client) {
result.emplace_result(error::client_invalid_id);
continue;
}
if (client->getType() == CLIENT_MUSIC) {
result.emplace_result(error::client_invalid_type);
continue;
}
if(type == ViewReasonId::VREASON_CHANNEL_KICK) {
if(!permission::v2::permission_granted(client->calculate_permission(permission::i_client_needed_kick_from_channel_power, client->getChannelId()), kick_power)) {
result.emplace_result(permission::i_client_needed_kick_from_channel_power);
continue;
}
} else {
if(!permission::v2::permission_granted(client->calculate_permission(permission::i_client_needed_kick_from_server_power, client->getChannelId()), kick_power)) {
result.emplace_result(permission::i_client_needed_kick_from_server_power);
continue;
}
}
clients.emplace_back(std::move(client));
result.emplace_result(error::ok);
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));
}
for(auto& client : clients) {
if (target_channel) {
this->server->notify_client_kick(client.client, this->ref(), cmd["reasonmsg"].as<std::string>(), target_channel);
} 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{std::forward<command_result_bulk>(result)};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandClientGetIds(Command &cmd) {
@@ -159,7 +126,16 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
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};
@@ -168,7 +144,6 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
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};
@@ -176,49 +151,11 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
auto permission_error = this->calculate_and_get_join_state(channel);
if(permission_error != permission::unknown) return command_result{permission_error};
command_result_bulk result{};
result.reserve(cmd.bulkCount());
std::vector<ConnectedLockedClient<ConnectedClient>> clients{};
for(size_t index{0}; index < cmd.bulkCount(); index++) {
auto target_client_id = cmd[index]["clid"].as<ClientId>();
ConnectedLockedClient target_client{target_client_id == 0 ? this->ref() : this->server->find_client_by_id(target_client_id)};
if(!target_client) {
result.emplace_result(error::client_invalid_id);
continue;
}
if(!target_client->getChannel()) {
if(target_client.client != this) {
result.emplace_result(error::client_invalid_id);
continue;
}
}
if(target_client->getChannel() == channel) {
result.emplace_result(error::ok);
continue;
}
if(target_client.client != this) {
if(!permission::v2::permission_granted(target_client->calculate_permission(permission::i_client_needed_move_power, target_client->getChannelId()), this->calculate_permission(permission::i_client_move_power, target_client->getChannelId()))) {
result.emplace_result(permission::i_client_move_power);
continue;
}
if(!permission::v2::permission_granted(target_client->calculate_permission(permission::i_client_needed_move_power, channel->channelId()), this->calculate_permission(permission::i_client_move_power, channel->channelId()))) {
result.emplace_result(permission::i_client_move_power);
continue;
}
}
clients.emplace_back(std::move(target_client));
result.emplace_result(error::ok);
}
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() + clients.size())
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>()) {
@@ -226,45 +163,36 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
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();
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 client_count = 0;
for(const auto& entry : this->server->getClientsByChannelRoot(channel, false))
if(entry.get() != this) client_count++; //Dont count the client itself
if (maxClients >= 0 && maxClients < client_count + clients.size())
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);
std::vector<std::shared_ptr<BasicChannel>> channels{};
channels.reserve(clients.size());
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
);
for(auto& client : clients) {
auto oldChannel = client->getChannel();
if(!oldChannel) continue;
this->server->client_move(
client.client,
channel,
client.client == this ? nullptr : _this.lock(),
"",
client.client == this ? ViewReasonId::VREASON_USER_ACTION : ViewReasonId::VREASON_MOVED,
true,
server_channel_w_lock
);
if(std::find_if(channels.begin(), channels.end(), [&](const std::shared_ptr<BasicChannel>& channel) { return &*channel == &*oldChannel; }) == channels.end())
channels.push_back(oldChannel);
}
for(const auto& oldChannel : channels) {
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)
@@ -273,7 +201,7 @@ command_result ConnectedClient::handleCommandClientMove(Command &cmd) {
if(server_channel_w_lock.owns_lock())
server_channel_w_lock.unlock();
}
return command_result{std::forward<command_result_bulk>(result)};
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandClientPoke(Command &cmd) {
@@ -281,45 +209,13 @@ command_result ConnectedClient::handleCommandClientPoke(Command &cmd) {
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
command_result_bulk result{};
result.reserve(cmd.bulkCount());
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());
std::vector<ConnectedLockedClient<ConnectedClient>> clients{};
clients.reserve(cmd.bulkCount());
for(size_t index{0}; index < cmd.bulkCount(); index++) {
ConnectedLockedClient client{ this->server->find_client_by_id(cmd[index]["clid"].as<ClientId>())};
if (!client) {
result.emplace_result(error::client_invalid_id);
continue;
}
if (client->getType() == CLIENT_MUSIC) {
result.emplace_result(error::client_invalid_type);
continue;
}
auto own_permission = this->calculate_permission(permission::i_client_poke_power, client->getChannelId());
if(!permission::v2::permission_granted(client->calculate_permission(permission::i_client_needed_poke_power, client->getChannelId()), own_permission)) {
result.emplace_result(permission::i_client_poke_power);
continue;
}
clients.push_back(std::move(client));
result.emplace_result(error::ok);
}
/* clients might be empty ;) */
if(clients.size() > 1) {
auto max_clients = this->calculate_permission(permission::i_client_poke_max_clients, 0);
if(!permission::v2::permission_granted(clients.size(), max_clients))
return command_result{permission::i_client_poke_max_clients};
}
for(auto& client : clients)
client->notifyClientPoke(_this.lock(), cmd["msg"]);
return command_result{std::forward<command_result_bulk>(result)};
client->notifyClientPoke(_this.lock(), cmd["msg"]);
return command_result{error::ok};
}
@@ -495,13 +391,13 @@ command_result ConnectedClient::handleCommandClientDBEdit(Command &cmd) {
for (auto &elm : cmd[0].keys()) {
if (elm == "cldbid") continue;
const auto& info = property::find<property::ClientProperties>(elm);
if(info == property::CLIENT_UNDEFINED) {
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: '" + std::string{info.name} + "')");
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();
@@ -527,29 +423,29 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
bool update_talk_rights = false;
unique_ptr<lock_guard<std::recursive_mutex>> nickname_lock;
std::deque<std::pair<const property::PropertyDescription*, std::string>> keys;
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::find<property::ClientProperties>(key);
if(info == property::CLIENT_UNDEFINED) {
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) {
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>())) {
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(client->properties()[info].as<string>() == cmd[key].as<string>()) continue;
if (info == property::CLIENT_DESCRIPTION) {
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) {
@@ -562,16 +458,16 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
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) {
} 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::describe(property::CLIENT_IS_TALKER), "client_is_talker");
keys.emplace_back(&property::describe(property::CLIENT_TALK_REQUEST), "client_talk_request");
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) {
} 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()) {
@@ -604,7 +500,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
continue;
}
}
} else if(info == property::CLIENT_PLAYER_VOLUME) {
} 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());
@@ -619,7 +515,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
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) {
} 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()) {
@@ -629,7 +525,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
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) {
} else if(*info == property::CLIENT_IS_PRIORITY_SPEAKER) {
//FIXME allow other to remove this thing
if(!self) {
if(client->getType() != ClientType::CLIENT_MUSIC)
@@ -648,7 +544,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
cmd["client_talk_request"] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
else
cmd["client_talk_request"] = 0;
keys.emplace_back(&property::describe(property::CLIENT_TALK_REQUEST), "client_talk_request");
keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request");
continue;
} else if (self && key == "client_badges") {
std::string str = cmd[key];
@@ -680,7 +576,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
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*/)) {
} 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());
@@ -700,8 +596,8 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
cmd["client_lastconnected"] = value;
}
keys.emplace_back(&property::describe(property::CLIENT_LASTCONNECTED), "client_lastconnected");
} else if(!self && info == property::CLIENT_BOT_TYPE) {
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) {
@@ -712,7 +608,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
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) {
} else if(*info == property::CLIENT_AWAY_MESSAGE) {
if(!self) continue;
if(cmd["client_away_message"].string().length() > 256)
@@ -721,12 +617,12 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
continue;
}
keys.emplace_back(&info, key);
keys.emplace_back((property::ClientProperties) info->property_index, key);
}
deque<const property::PropertyDescription*> updates;
deque<property::ClientProperties> updates;
for(const auto& key : keys) {
if(*key.first == property::CLIENT_IS_PRIORITY_SPEAKER) {
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();
@@ -971,21 +867,37 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) {
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);
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->getServerId() : 0, 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};
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
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);
bool update_channels{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::set_value);
update_channels |= ppermission.is_client_view_property();
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);
serverInstance->databaseHelper()->saveClientPermissions(this->server ? this->server->getServerId() : 0, cldbid, mgr);
auto onlineClients = this->server->findClientsByCldbId(cldbid);
if (!onlineClients.empty())
for (const auto &elm : onlineClients) {
@@ -996,7 +908,7 @@ command_result ConnectedClient::handleCommandClientAddPerm(Command &cmd) {
elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
}
return pparser.build_command_result();
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) {
@@ -1007,31 +919,40 @@ command_result ConnectedClient::handleCommandClientDelPerm(Command &cmd) {
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);
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->serverId : 0, 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));
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
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)
bool update_channels{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(mgr, permission::v2::PermissionUpdateType::delete_value);
update_channels |= ppermission.is_client_view_property();
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);
auto onlineClients = this->server->findClientsByCldbId(cldbid);
serverInstance->databaseHelper()->saveClientPermissions(this->server ? this->server->serverId : 0, 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_channels)
if(update_channel)
elm->updateChannelClientProperties(true, true);
elm->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
}
return pparser.build_command_result();
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandClientPermList(Command &cmd) {
@@ -1041,7 +962,7 @@ command_result ConnectedClient::handleCommandClientPermList(Command &cmd) {
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"]);
auto mgr = serverInstance->databaseHelper()->loadClientPermissionManager(this->server ? this->server->getServerId() : 0, cmd["cldbid"]);
if (!this->notifyClientPermList(cmd["cldbid"], mgr, cmd.hasParm("permsid"))) return command_result{error::database_empty_result};
return command_result{error::ok};
}
@@ -1195,7 +1116,7 @@ command_result ConnectedClient::handleCommandClientInfo(Command &cmd) {
}
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][std::string{key.type().name}] = key.value();
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
+12 -5
View File
@@ -13,10 +13,10 @@
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "src/server/file/LocalFileServer.h"
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@@ -48,7 +48,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
#define QUERY_PASSWORD_LENGTH 12
@@ -118,9 +118,14 @@ command_result ConnectedClient::handleCommandFTGetFileList(Command &cmd) {
}
if (fileList[0].has("name")) {
this->sendCommand(fileList);
if(this->getType() != CLIENT_QUERY)
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};
@@ -229,11 +234,13 @@ command_result ConnectedClient::handleCommandFTInitUpload(Command &cmd) {
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"};
}
@@ -137,10 +137,6 @@ inline bool permission_require_granted_value(ts::permission::PermissionType type
case permission::i_client_max_permanent_channels:
case permission::i_client_max_semi_channels:
case permission::i_client_max_temporary_channels:
case permission::i_channel_create_modify_with_temp_delete_delay:
case permission::i_client_talk_power:
case permission::i_client_needed_talk_power:
case permission::b_channel_create_with_needed_talk_power:
case permission::i_channel_max_depth:
case permission::i_channel_min_depth:
@@ -150,8 +146,6 @@ inline bool permission_require_granted_value(ts::permission::PermissionType type
case permission::i_max_playlist_size:
case permission::i_max_playlists:
case permission::i_client_poke_max_clients:
case permission::i_client_ban_max_bantime:
case permission::i_client_max_idletime:
case permission::i_group_sort_id:
+87 -165
View File
@@ -13,10 +13,10 @@
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "src/server/file/LocalFileServer.h"
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@@ -48,7 +48,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
#define QUERY_PASSWORD_LENGTH 12
@@ -366,7 +366,7 @@ command_result ConnectedClient::handleCommandPermissionList(Command &cmd) {
#define M(ptype) \
do { \
for(const auto& prop : property::list<ptype>()) { \
for(const auto& prop : property::impl::list<ptype>()) { \
if((prop->flags & property::FLAG_INTERNAL) > 0) continue; \
response[index]["name"] = prop->name; \
response[index]["flags"] = prop->flags; \
@@ -460,6 +460,7 @@ command_result ConnectedClient::handleCommandSetClientChannelGroup(Command &cmd)
}
}
//TODO: When using new VS model, check for a temporary assignment!
this->server->groups->setChannelGroup(target_cldbid, serverGroup, channel);
for (const auto &targetClient : this->server->findClientsByCldbId(target_cldbid)) {
unique_lock client_channel_lock_w(targetClient->channel_lock);
@@ -568,7 +569,7 @@ command_result ConnectedClient::handleCommandSendTextMessage(Command &cmd) {
if(this->handleTextMessage(ChatMessageMode::TEXTMODE_SERVER, cmd["msg"], nullptr)) return command_result{error::ok};
for(const auto& client : this->server->getClients()) {
if (client->connectionState() != ConnectionState::CONNECTED)
if (client->connectionState() != ClientState::CONNECTED)
continue;
auto type = client->getType();
@@ -767,9 +768,7 @@ command_result ConnectedClient::handleCommandBanClient(Command &cmd) {
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
std::string target_unique_id{};
ClientDbId target_database_id{0};
string uid;
string reason = cmd[0].has("banreason") ? cmd["banreason"].string() : "";
auto time = cmd[0].has("time") ? cmd["time"].as<uint64_t>() : 0UL;
chrono::time_point<chrono::system_clock> until = time > 0 ? chrono::system_clock::now() + chrono::seconds(time) : chrono::time_point<chrono::system_clock>();
@@ -778,40 +777,43 @@ command_result ConnectedClient::handleCommandBanClient(Command &cmd) {
const auto no_hwid = cmd.hasParm("no-hardware-id");
const auto no_ip = cmd.hasParm("no-ip");
std::deque<std::shared_ptr<ConnectedClient>> target_clients;
deque<shared_ptr<ConnectedClient>> target_clients;
if (cmd[0].has("uid")) {
target_clients = this->server->findClientsByUid(target_unique_id = cmd["uid"].string());
} else if(cmd[0].has("cldbid")) {
target_clients = this->server->findClientsByCldbId(target_database_id = cmd["cldbid"].as<ClientDbId>());
target_clients = this->server->findClientsByUid(uid = cmd["uid"].string());
for(const auto& client : target_clients)
if(client->getType() == ClientType::CLIENT_MUSIC)
return command_result{error::client_invalid_id, "You cant ban a music bot!"};
} else {
target_clients = {this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
if(!target_clients[0]) {
return command_result{error::client_invalid_id, "Could not find target client"};
}
}
for(const auto& client : target_clients)
if(client->getType() == ClientType::CLIENT_MUSIC)
if(target_clients[0]->getType() == ClientType::CLIENT_MUSIC) {
return command_result{error::client_invalid_id, "You cant ban a music bot!"};
if(!target_clients.empty()) {
if(target_unique_id.empty())
target_unique_id = target_clients.back()->getUid();
if(!target_database_id)
target_database_id = target_clients.back()->getClientDatabaseId();
}
uid = target_clients[0]->getUid();
}
if(!permission::v2::permission_granted(this->server->calculate_permission(permission::i_client_needed_ban_power, target_database_id, ClientType::CLIENT_TEAMSPEAK, 0), this->calculate_permission(permission::i_client_ban_power, 0)))
ClientDbId target_dbid = 0;
if (!target_clients.empty()) {
target_dbid = target_clients[0]->getClientDatabaseId();
} else {
auto info = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->getServer(), {uid});
if (!info.empty())
target_dbid = info[0]->cldbid;
else
return command_result{error::client_unknown};
}
if(!permission::v2::permission_granted(this->server->calculate_permission(permission::i_client_needed_ban_power, target_dbid, ClientType::CLIENT_TEAMSPEAK, 0), this->calculate_permission(permission::i_client_ban_power, 0)))
return command_result{permission::i_client_ban_power};
if (permission::v2::permission_granted(1, this->server->calculate_permission(permission::b_client_ignore_bans, target_database_id, ClientType::CLIENT_TEAMSPEAK, 0)))
if (permission::v2::permission_granted(1, this->server->calculate_permission(permission::b_client_ignore_bans, target_dbid, ClientType::CLIENT_TEAMSPEAK, 0)))
return command_result{permission::b_client_ignore_bans};
deque<BanId> ban_ids;
if(!target_unique_id.empty()) {
auto _id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, target_unique_id, "", "", "", until);
ban_ids.push_back(_id);
}
auto _id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, uid, "", "", "", until);
ban_ids.push_back(_id);
auto b_ban_name = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_name, 0), false);
auto b_ban_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_ip, 0), false);
@@ -1328,9 +1330,7 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_permission_find, 1);
std::vector<std::tuple<std::string, permission::PermissionType, bool>> requested_permissions{};
requested_permissions.reserve(cmd.bulkCount());
deque<pair<pair<string, permission::PermissionType>, bool>> permissions;
std::shared_ptr<permission::PermissionTypeEntry> permission;
for(size_t index = 0; index < cmd.bulkCount(); index++) {
bool granted = false;
@@ -1350,21 +1350,22 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
continue;
}
requested_permissions.emplace_back(permission->name, permission->type, granted);
permissions.emplace_back(pair<pair<string, permission::PermissionType>, bool>{{permission->name, permission->type}, granted});
}
if(requested_permissions.empty())
if(permissions.empty())
return command_result{error::database_empty_result};
std::map<std::string, std::tuple<permission::PermissionType, uint8_t>> db_lookup_mapping{};
std::string query_string{};
for(const auto& [name, id, as_granted] : requested_permissions) {
auto& mapping = db_lookup_mapping[name];
if(std::get<0>(mapping) == 0) {
std::get<0>(mapping) = id;
query_string += std::string{query_string.empty() ? "" : " OR "} + "`permId` = '" + name + "'";
map<string, uint8_t> flags;
map<string, permission::PermissionType> quick_mapping;
string query_string;
for(const auto& entry : permissions) {
if(flags[entry.first.first] == 0) {
quick_mapping[entry.first.first] = entry.first.second;
query_string += string(query_string.empty() ? "" : " OR ") + "`permId` = '" + entry.first.first + "'";
}
std::get<1>(mapping) |= (1U << as_granted);
flags[entry.first.first] |= entry.second ? 2 : 1;
}
deque<unique_ptr<PermissionEntry>> entries;
@@ -1373,14 +1374,13 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
variable{":sid", this->server->getServerId()},
variable{":playlist", permission::SQL_PERM_PLAYLIST}
).query([&](int length, string* values, string* columns) {
permission::PermissionSqlType type{permission::SQL_PERM_GROUP};
uint64_t id{0};
ChannelId channel_id{0};
permission::PermissionValue value{0}, granted_value{0};
string permission_name{};
permission::PermissionSqlType type = permission::SQL_PERM_GROUP;
uint64_t id = 0;
ChannelId channel_id = 0;
permission::PermissionValue value = 0,
granted_value = 0;
string permission_name;
bool negate = false, skip = false;
#if 0
for (int index = 0; index < length; index++) {
try {
if(columns[index] == "type")
@@ -1404,48 +1404,35 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
return 0;
}
}
#else
assert(length == 8);
try {
type = static_cast<permission::PermissionSqlType>(stoll(values[1]));
permission_name = values[0];
id = static_cast<uint64_t>(stoll(values[2]));
channel_id = static_cast<ChannelId>(stoll(values[3]));
value = static_cast<permission::PermissionValue>(stoll(values[4]));
granted_value = static_cast<permission::PermissionValue>(stoll(values[5]));
negate = values[7] == "1";
skip = values[6] == "1";
} catch(std::exception& ex) {
return 0;
}
#endif
auto request = db_lookup_mapping.find(permission_name);
if(request == db_lookup_mapping.end()) return 0; /* shall not happen */
auto flags = std::get<1>(request->second);
/* value */
if((flags & 0x1U) > 0 && value > 0) {
if((flags[permission_name] & 0x1) > 0 && value > 0) {
auto result = make_unique<PermissionEntry>();
result->permission_type = std::get<0>(request->second);
result->permission_type = quick_mapping[permission_name];
result->permission_value = value;
result->type = type;
result->channel_id = channel_id;
result->negate = negate;
result->skip = skip;
if (type == permission::SQL_PERM_GROUP) {
auto gr = this->server->groups->findGroup(id);
if (!gr) return 0;
result->group_id = id;
if(gr->target() == GROUPTARGET_CHANNEL)
result->channel_id = 1;
} else if(type == permission::SQL_PERM_USER) {
result->client_id = id;
}
entries.push_back(std::move(result));
if(result)
entries.push_back(std::move(result));
}
/* granted */
if((flags & 0x2U) > 0 && granted_value > 0) {
if((flags[permission_name] & 0x2) > 0 && granted_value > 0) {
auto result = make_unique<PermissionEntry>();
result->permission_type = (permission::PermissionType) (std::get<0>(request->second) | PERM_ID_GRANT);
result->permission_type = (permission::PermissionType) (quick_mapping[permission_name] | PERM_ID_GRANT);
result->permission_value = granted_value;
result->type = type;
result->channel_id = channel_id;
@@ -1453,17 +1440,21 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
result->skip = skip;
if (type == permission::SQL_PERM_GROUP) {
auto gr = this->server->groups->findGroup(id);
if (!gr) return 0;
result->group_id = id;
if(gr->target() == GROUPTARGET_CHANNEL)
result->channel_id = 1;
} else if(type == permission::SQL_PERM_USER) {
result->client_id = id;
}
entries.push_back(std::move(result));
if(result)
entries.push_back(std::move(result));
}
return 0;
});
if(entries.empty())
return command_result{error::database_empty_result};
struct CommandPerm {
permission::PermissionType p;
@@ -1475,13 +1466,13 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
std::vector<CommandPerm> perms;
perms.resize(entries.size());
size_t index{0};
auto all_groups = this->server->groups->availableGroups(true);
size_t index = 0;
for(const auto& entry : entries) {
auto& perm = perms[index++];
#if 0 /* TS3 switched the oder and YatQa as well, to keep compatibility we do it as well */
perm.p = entry->permission_type;
perm.v = entry->permission_value;
if(entry->type == permission::SQL_PERM_USER) {
if(entry->channel_id > 0) {
perm.id1 = entry->client_id;
@@ -1507,42 +1498,7 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
perm.t = 0; /* server group */
}
}
#else
if(entry->type == permission::SQL_PERM_USER) {
if(entry->channel_id > 0) {
perm.id1 = entry->channel_id;
perm.id2 = entry->client_id;
perm.t = 4; /* client channel */
} else {
perm.id1 = entry->client_id;
perm.id2 = 0;
perm.t = 1; /* client server */
}
} else if(entry->type == permission::SQL_PERM_CHANNEL) {
perm.id1 = entry->channel_id;
perm.id2 = 0;
perm.t = 2; /* channel permission */
} else if(entry->type == permission::SQL_PERM_GROUP) {
auto group = std::find_if(all_groups.begin(), all_groups.end(), [&](const auto& group) { return group->groupId() == entry->group_id; });
if(group == all_groups.end()) {
index--; /* unknown group */
continue;
}
if((*group)->target() == GroupTarget::GROUPTARGET_CHANNEL) {
perm.id1 = 0;
perm.id2 = entry->group_id;
perm.t = 3; /* channel group */
} else {
perm.id1 = entry->group_id;
perm.id2 = 0;
perm.t = 0; /* server group */
}
}
#endif
perm.p = entry->permission_type;
perm.v = entry->permission_value;
}
perms.erase(perms.begin() + index, perms.end());
sort(perms.begin(), perms.end(), [](const CommandPerm& a, const CommandPerm& b) {
@@ -1561,7 +1517,6 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
return &a > &b;
});
#if 0
Command result(this->notify_response_command("notifypermfind"));
index = 0;
@@ -1574,22 +1529,9 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
result[index]["t"] = e.t;
index++;
}
this->sendCommand(result);
#else
command_builder result{this->notify_response_command("notifypermfind"), 64, perms.size()};
index = 0;
for(const auto& e : perms) {
auto bulk = result.bulk(index++);
bulk.put("t", e.t);
bulk.put("p", (uint16_t) e.p);
bulk.put("v", e.v);
bulk.put("id1", e.id1);
bulk.put("id2", e.id2);
}
if(index == 0) return command_result{error::database_empty_result};
this->sendCommand(result);
#endif
return command_result{error::ok};
}
@@ -1619,7 +1561,7 @@ command_result ConnectedClient::handleCommandPermOverview(Command &cmd) {
auto server_groups = this->server->getGroupManager()->getServerGroups(client_dbid, ClientType::CLIENT_TEAMSPEAK);
auto channel_group = this->server->getGroupManager()->getChannelGroup(client_dbid, channel, true);
auto permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServer(), client_dbid);
auto permission_manager = serverInstance->databaseHelper()->loadClientPermissionManager(this->getServerId(), client_dbid);
Command result(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK ? "notifypermoverview" : "");
size_t index = 0;
@@ -2147,7 +2089,6 @@ command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) {
OptionalServerId server_id = this->getServerId();
if(cmd[0].has("server_id"))
server_id = cmd["server_id"];
if(cmd[0].has("sid"))
server_id = cmd["sid"];
@@ -2155,43 +2096,24 @@ command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) {
if(!server && server_id != EmptyServerId && server_id != 0)
return command_result{error::server_invalid_id};
if(server) {
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create};
} else {
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create};
}
auto username = cmd["client_login_name"].as<string>();
auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as<string>() : "";
auto account = serverInstance->getQueryServer()->find_query_account_by_name(username);
if(account) return command_result{error::query_already_exists};
std::string uid = this->getUid();
if(cmd[0].has("cldbid")){
if(!serverInstance->databaseHelper()->validClientDatabaseId(server, cmd["cldbid"].as<ClientDbId>()))
return command_result{error::database_empty_result};
if(server) {
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create};
} else {
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create};
}
auto info = serverInstance->databaseHelper()->queryDatabaseInfo(server, {cmd["cldbid"].as<ClientDbId>()});
if(info.empty())
return command_result{error::database_empty_result};
uid = info[0]->uniqueId;
} else {
if(server) {
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create_own, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create_own};
} else {
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create_own, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create_own};
}
}
if(password.empty())
password = rnd_string(QUERY_PASSWORD_LENGTH);
account = serverInstance->getQueryServer()->create_query_account(username, server_id, uid, password);
auto account = serverInstance->getQueryServer()->find_query_account_by_name(username);
if(account) return command_result{error::query_already_exists};
account = serverInstance->getQueryServer()->create_query_account(username, server_id, this->getUid(), password);
if(!account)
return command_result{error::vs_critical};
@@ -2341,7 +2263,7 @@ command_result ConnectedClient::handleCommandDummy_IpChange(ts::Command &cmd) {
if(geoloc::provider) {
auto loc = this->isAddressV4() ? geoloc::provider->resolveInfoV4(this->getPeerIp(), false) : geoloc::provider->resolveInfoV6(this->getPeerIp(), false);
if(loc) {
logMessage(this->getServerId(), "[{}] Received new ip location. IP {} traced to {} ({}).", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp(), loc->name, loc->identifier);
logError(this->getServerId(), "[{}] Received new ip location. IP {} traced to {} ({}).", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp(), loc->name, loc->identifier);
this->properties()[property::CLIENT_COUNTRY] = loc->identifier;
server->notifyClientPropertyUpdates(_this.lock(), deque<property::ClientProperties>{property::CLIENT_COUNTRY});
new_country = loc->identifier;
@@ -2604,7 +2526,7 @@ command_result ConnectedClient::handleCommandConversationMessageDelete(ts::Comma
auto delete_count = current_conversation->delete_messages(timestamp_end, limit, timestamp_begin, bulk["cldbid"]);
if(delete_count > 0) {
for(const auto& client : ref_server->getClients()) {
if(client->connectionState() != ConnectionState::CONNECTED)
if(client->connectionState() != ClientState::CONNECTED)
continue;
auto type = client->getType();
+123 -91
View File
@@ -13,10 +13,10 @@
#include "../../build.h"
#include "../ConnectedClient.h"
#include "../InternalClient.h"
#include "src/server/file/LocalFileServer.h"
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionManager.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@@ -30,7 +30,6 @@
#include <StringVariable.h>
#include "helpers.h"
#include "./bulk_parsers.h"
#include <Properties.h>
#include <log/LogUtils.h>
@@ -50,7 +49,7 @@ using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
using namespace ts::server::tokens;
command_result ConnectedClient::handleCommandMusicBotCreate(Command& cmd) {
if(!config::music::enabled) return command_result{error::music_disabled};
@@ -448,41 +447,41 @@ command_result ConnectedClient::handleCommandPlaylistEdit(ts::Command &cmd) {
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_modify_power, permission::i_playlist_modify_power); perr)
return command_result{perr};
deque<pair<const property::PropertyDescription*, string>> properties;
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;
const auto& property = property::find<property::PlaylistProperties>(key);
if(property == property::PLAYLIST_UNDEFINED) {
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) {
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>())) {
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) {
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) {
} 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);
properties.emplace_back(property, key);
}
for(const auto& property : properties) {
if(*property.first == property::PLAYLIST_CURRENT_SONG_ID) {
@@ -559,14 +558,35 @@ command_result ConnectedClient::handleCommandPlaylistAddPerm(ts::Command &cmd) {
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
return command_result{perr};
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
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};
for(const auto& ppermission : pparser.iterate_valid_permissions())
ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value);
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
return pparser.build_command_result();
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(1, this->calculate_permission(permType, 0, true))) {
if(conOnError) continue;
return command_result{permission::i_permission_modify_power};
}
if (grant) {
playlist->permission_manager()->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value);
} else {
playlist->permission_manager()->set_permission(permType, {cmd[index]["permvalue"],0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"].as<bool>(), cmd[index]["permnegated"].as<bool>());
}
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) {
@@ -580,14 +600,26 @@ command_result ConnectedClient::handleCommandPlaylistDelPerm(ts::Command &cmd) {
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
return command_result{perr};
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
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(const auto& ppermission : pparser.iterate_valid_permissions())
ppermission.apply_to(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value);
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
return pparser.build_command_result();
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->permission_manager()->set_permission(permType, {0, 0}, permission::v2::do_nothing, permission::v2::delete_value);
} else {
playlist->permission_manager()->set_permission(permType, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing);
}
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistClientList(ts::Command &cmd) {
@@ -703,14 +735,34 @@ command_result ConnectedClient::handleCommandPlaylistClientAddPerm(ts::Command &
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
return command_result{perr};
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
return pparser.build_command_result();
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};
for(const auto& ppermission : pparser.iterate_valid_permissions())
ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::set_value, client_id);
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
return pparser.build_command_result();
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(1, this->calculate_permission(permType, 0, true))) {
if(conOnError) continue;
return command_result{permission::i_permission_modify_power};
}
if (grant) {
playlist->permission_manager()->set_channel_permission(permType, client_id, {0, cmd[index]["permvalue"]}, permission::v2::do_nothing, permission::v2::set_value);
} else {
playlist->permission_manager()->set_channel_permission(permType, client_id, {cmd[index]["permvalue"], 0}, permission::v2::set_value, permission::v2::do_nothing, cmd[index]["permskip"].as<bool>(), cmd[index]["permnegated"].as<bool>());
}
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &cmd) {
@@ -727,53 +779,26 @@ command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_permission_modify_power, permission::i_playlist_permission_modify_power); perr)
return command_result{perr};
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");
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
if(!pparser.validate(this->ref(), this->getClientDatabaseId()))
return pparser.build_command_result();
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
for(const auto& ppermission : pparser.iterate_valid_permissions())
ppermission.apply_to_channel(playlist->permission_manager(), permission::v2::PermissionUpdateType::delete_value, client_id);
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};
}
return pparser.build_command_result();
}
constexpr auto max_song_meta_info = 1024 * 512;
inline size_t estimated_song_info_size(const std::shared_ptr<ts::music::PlaylistEntryInfo>& song, bool extract_metadata) {
return 128 + std::min(song->metadata.json_string.length(), (size_t) max_song_meta_info) + extract_metadata * 256;
}
inline void fill_song_info(ts::command_builder_bulk bulk, const std::shared_ptr<ts::music::PlaylistEntryInfo>& song, bool extract_metadata) {
bulk.reserve(estimated_song_info_size(song, extract_metadata));
bulk.put("song_id", song->song_id);
bulk.put("song_invoker", song->invoker);
bulk.put("song_previous_song_id", song->previous_song_id);
bulk.put("song_url", song->original_url);
bulk.put("song_url_loader", song->url_loader);
bulk.put("song_loaded", song->metadata.is_loaded());
if(song->metadata.json_string.length() > 1024 * 1024 * 512) {
logWarning(LOG_GENERAL, "Dropping song metadata because its way to big. ({}bytes)", song->metadata.json_string.size());
} else {
bulk.put("song_metadata", song->metadata.json_string);
}
if(extract_metadata) {
bulk.reserve(256, true);
auto metadata = song->metadata.loaded_data;
if(extract_metadata && song->metadata.is_loaded() && metadata) {
bulk.put("song_metadata_title", metadata->title);
bulk.put("song_metadata_description", metadata->description);
bulk.put("song_metadata_url", metadata->url);
bulk.put("song_metadata_length", metadata->length.count());
if(auto thumbnail = static_pointer_cast<::music::ThumbnailUrl>(metadata->thumbnail); thumbnail && thumbnail->type() == ::music::THUMBNAIL_URL) {
bulk.put("song_metadata_thumbnail_url", thumbnail->url());
} else {
bulk.put("song_metadata_thumbnail_url", "none");
}
if (grant) {
playlist->permission_manager()->set_channel_permission(permType, client_id, {0, 0}, permission::v2::do_nothing, permission::v2::delete_value);
} else {
playlist->permission_manager()->set_channel_permission(permType, client_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing);
}
}
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd) {
@@ -791,32 +816,39 @@ command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd)
if(songs.empty())
return command_result{error::database_empty_result};
ts::command_builder result{this->notify_response_command("notifyplaylistsonglist")};
result.put(0, "version", 2); /* to signalize that we're sending the response bulked */
Command notify(this->notify_response_command("notifyplaylistsonglist"));
notify["playlist_id"] = playlist->playlist_id();
auto extract_metadata = cmd.hasParm("extract-metadata");
size_t index{0};
size_t index = 0;
for(const auto& song : songs) {
if(index == 0) {
result.put(0, "playlist_id", playlist->playlist_id());
}
fill_song_info(result.bulk(index), song, extract_metadata);
notify[index]["song_id"] = song->song_id;
notify[index]["song_invoker"] = song->invoker;
notify[index]["song_previous_song_id"] = song->previous_song_id;
notify[index]["song_url"] = song->original_url;
notify[index]["song_url_loader"] = song->url_loader;
notify[index]["song_loaded"] = song->metadata.is_loaded();
notify[index]["song_metadata"] = song->metadata.json_string;
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK && result.current_size() + estimated_song_info_size(song, extract_metadata) > 128 * 1024) {
this->sendCommand(result);
result.reset();
index = 0;
} else {
index++;
if(extract_metadata) {
auto metadata = song->metadata.loaded_data;
if(extract_metadata && song->metadata.is_loaded() && metadata) {
notify[index]["song_metadata_title"] = metadata->title;
notify[index]["song_metadata_description"] = metadata->description;
notify[index]["song_metadata_url"] = metadata->url;
notify[index]["song_metadata_length"] = metadata->length.count();
if(auto thumbnail = static_pointer_cast<::music::ThumbnailUrl>(metadata->thumbnail); thumbnail && thumbnail->type() == ::music::THUMBNAIL_URL) {
notify[index]["song_metadata_thumbnail_url"] = thumbnail->url();
} else {
notify[index]["song_metadata_thumbnail_url"] = "none";
}
}
}
index++;
}
if(index > 0)
this->sendCommand(result);
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) {
ts::command_builder finish{"notifyplaylistsonglistfinished"};
finish.put(0, "playlist_id", playlist->playlist_id());
this->sendCommand(finish);
}
this->sendCommand(notify);
return command_result{error::ok};
}
+194 -97
View File
@@ -4,15 +4,19 @@
#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 "src/server/file/LocalFileServer.h"
#include "../../server/file/FileServer.h"
#include "../../server/VoiceServer.h"
#include "../voice/VoiceClient.h"
#include "PermissionRegister.h"
#include "../../InstanceHandler.h"
#include "../../server/QueryServer.h"
#include "../file/FileClient.h"
@@ -21,22 +25,30 @@
#include "../../weblist/WebListManager.h"
#include "../../manager/ConversationManager.h"
#include "../../manager/PermissionNameMapper.h"
#include <experimental/filesystem>
#include <cstdint>
#include <StringVariable.h>
#include "helpers.h"
#include "./bulk_parsers.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;
using namespace ts::server::tokens;
#define QUERY_PASSWORD_LENGTH 12
@@ -212,14 +224,14 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) {
std::deque<std::string> keys;
bool group_update = false;
for (const auto& elm : toApplay) {
const auto& info = property::find<property::VirtualServerProperties>(elm.first);
if(info == property::VIRTUALSERVER_UNDEFINED) {
auto info = property::impl::info<property::VirtualServerProperties>(elm.first);
if(*info == property::VIRTUALSERVER_UNDEFINED) {
logCritical(target_server ? target_server->getServerId() : 0, "Missing server property " + elm.first);
continue;
}
if(!info.validate_input(elm.second)) {
logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + std::string{info.name} + "')");
if(!info->validate_input(elm.second)) {
logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + info->name + "')");
continue;
}
if(target_server)
@@ -228,7 +240,7 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) {
(*serverInstance->getDefaultServerProperties())[info] = elm.second;
keys.push_back(elm.first);
group_update |= info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP;
group_update |= *info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || *info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || *info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP;
}
if(target_server) {
@@ -250,38 +262,35 @@ command_result ConnectedClient::handleCommandServerRequestConnectionInfo(Command
CMD_REQ_SERVER;
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_connectioninfo_view, 1);
ts::command_builder result{"notifyserverconnectioninfo"};
auto first_bulk = result.bulk(0);
Command notify("notifyserverconnectioninfo");
auto total_stats = this->server->getServerStatistics()->total_stats();
auto minute_report = this->server->getServerStatistics()->minute_stats();
auto second_report = this->server->getServerStatistics()->second_stats();
auto network_report = this->server->generate_network_report();
auto statistics = this->server->getServerStatistics()->statistics();
auto report = this->server->getServerStatistics()->dataReport();
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT, minute_report.file_bytes_sent);
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, minute_report.file_bytes_received);
notify[0]["connection_filetransfer_bandwidth_sent"] = report.file_send;
notify[0]["connection_filetransfer_bandwidth_received"] = report.file_recv;
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, minute_report.file_bytes_sent);
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, minute_report.file_bytes_received);
notify[0]["connection_filetransfer_bytes_sent_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
notify[0]["connection_filetransfer_bytes_received_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
first_bulk.put_unchecked("connection_filetransfer_bytes_sent_month", this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as<string>());
first_bulk.put_unchecked("connection_filetransfer_bytes_received_month", this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as<string>());
notify[0]["connection_filetransfer_bytes_sent_month"] = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as<string>();
notify[0]["connection_filetransfer_bytes_received_month"] = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as<string>();
first_bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_TOTAL, std::accumulate(total_stats.connection_packets_sent.begin(), total_stats.connection_packets_sent.end(), (size_t) 0U));
first_bulk.put_unchecked(property::CONNECTION_BYTES_SENT_TOTAL, std::accumulate(total_stats.connection_bytes_sent.begin(), total_stats.connection_bytes_sent.end(), (size_t) 0U));
first_bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_TOTAL, std::accumulate(total_stats.connection_packets_received.begin(), total_stats.connection_packets_received.end(), (size_t) 0U));
first_bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_TOTAL, std::accumulate(total_stats.connection_bytes_received.begin(), total_stats.connection_bytes_received.end(), (size_t) 0U));
notify[0]["connection_packets_sent_total"] = (*statistics)[property::CONNECTION_PACKETS_SENT_TOTAL].as<string>();
notify[0]["connection_bytes_sent_total"] = (*statistics)[property::CONNECTION_BYTES_SENT_TOTAL].as<string>();
notify[0]["connection_packets_received_total"] = (*statistics)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].as<string>();
notify[0]["connection_bytes_received_total"] = (*statistics)[property::CONNECTION_BYTES_RECEIVED_TOTAL].as<string>();
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_TOTAL, std::accumulate(second_report.connection_bytes_sent.begin(), second_report.connection_bytes_sent.end(), (size_t) 0U));
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_TOTAL, std::accumulate(minute_report.connection_bytes_sent.begin(), minute_report.connection_bytes_sent.end(), (size_t) 0U));
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_TOTAL, std::accumulate(second_report.connection_bytes_received.begin(), second_report.connection_bytes_received.end(), (size_t) 0U));
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_TOTAL, std::accumulate(minute_report.connection_bytes_received.begin(), minute_report.connection_bytes_received.end(), (size_t) 0U));
notify[0]["connection_bandwidth_sent_last_second_total"] = report.send_second;
notify[0]["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
notify[0]["connection_bandwidth_received_last_second_total"] = report.recv_second;
notify[0]["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
first_bulk.put_unchecked(property::CONNECTION_CONNECTED_TIME, this->server->properties()[property::VIRTUALSERVER_UPTIME].as<string>());
first_bulk.put_unchecked(property::CONNECTION_PACKETLOSS_TOTAL, network_report.average_loss);
first_bulk.put_unchecked(property::CONNECTION_PING, network_report.average_ping);
notify[0]["connection_connected_time"] = this->server->properties()[property::VIRTUALSERVER_UPTIME].as<string>();
notify[0]["connection_packetloss_total"] = this->server->averagePacketLoss();
notify[0]["connection_ping"] = this->server->averagePing();
this->sendCommand(result);
this->sendCommand(notify);
return command_result{error::ok};
}
@@ -538,7 +547,7 @@ command_result ConnectedClient::handleCommandServerGroupAddClient(Command &cmd)
auto continue_on_error = cmd.hasParm("continueonerror");
{
auto permission_add_power = this->calculate_permission(permission::i_server_group_member_add_power, -1);
auto permission_self_add_power = this->calculate_permission(permission::i_server_group_self_add_power, -1);
auto permission_self_add_power = this->calculate_permission(permission::i_server_group_member_add_power, -1);
for(auto index = 0; index < cmd.bulkCount(); index++) {
auto group_id = cmd[index]["sgid"];
@@ -654,7 +663,7 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd)
auto continue_on_error = cmd.hasParm("continueonerror");
{
auto permission_remove_power = this->calculate_permission(permission::i_server_group_member_remove_power, -1);
auto permission_self_remove_power = this->calculate_permission(permission::i_server_group_self_remove_power, -1);
auto permission_self_remove_power = this->calculate_permission(permission::i_server_group_member_remove_power, -1);
for(auto index = 0; index < cmd.bulkCount(); index++) {
auto group_id = cmd[index]["sgid"];
@@ -742,9 +751,6 @@ command_result ConnectedClient::handleCommandServerGroupDelClient(Command &cmd)
for(const auto& _server : target_server ? std::deque<shared_ptr<VirtualServer>>{target_server} : serverInstance->getVoiceServerManager()->serverInstances()) {
for (const auto &targetClient : _server->findClientsByCldbId(target_cldbid)) {
ConnectedLockedClient clock{targetClient};
if(!clock) continue;
if (_server->notifyClientPropertyUpdates(targetClient, _server->groups->update_server_group_property(targetClient, true, targetClient->getChannel()))) {
for (const auto &client : _server->getClients()) {
if(client->isClientVisible(targetClient, true) || client == targetClient)
@@ -793,43 +799,73 @@ command_result ConnectedClient::handleCommandServerGroupAddPerm(Command &cmd) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
}
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
bool update_talk_power{false}, update_server_group_list{false};
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");
bool checkTp = false;
bool sgroupUpdate = false;
auto permissions = serverGroup->permissions();
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
update_server_group_list |= ppermission.is_group_property();
update_talk_power |= ppermission.is_client_view_property();
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
//permvalue='1' permnegated='0' permskip='0'
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
if(permission_require_granted_value(permType) && !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) {
permissions->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value);
} else {
permissions->set_permission(
permType,
{cmd[index]["permvalue"], 0},
permission::v2::PermissionUpdateType::set_value,
permission::v2::PermissionUpdateType::do_nothing,
cmd[index]["permskip"].as<bool>() ? 1 : 0,
cmd[index]["permnegated"].as<bool>() ? 1 : 0
);
sgroupUpdate |= permission_is_group_property(permType);
checkTp |= permission_is_client_property(permType);
}
}
if(update_server_group_list)
if(sgroupUpdate)
serverGroup->apply_properties_from_permissions();
//TODO may update for every server?
if(this->server) {
auto lock = this->_this.lock();
auto server = this->server;
threads::Thread([update_talk_power, update_server_group_list, serverGroup, lock, server]() {
if(update_server_group_list)
threads::Thread([checkTp, sgroupUpdate, serverGroup, lock, server]() {
if(sgroupUpdate)
server->forEachClient([](shared_ptr<ConnectedClient> cl) {
cl->notifyServerGroupList();
});
server->forEachClient([serverGroup, update_talk_power](shared_ptr<ConnectedClient> cl) {
server->forEachClient([serverGroup, checkTp](shared_ptr<ConnectedClient> cl) {
if (cl->serverGroupAssigned(serverGroup)) {
if(cl->update_cached_permissions()) /* update cached calculated permissions */
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
if (update_talk_power)
if (checkTp)
cl->updateChannelClientProperties(true, true);
cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
}
});
}).detach();
}
return pparser.build_command_result();
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) {
@@ -848,34 +884,49 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_serverinstance_modify_templates, 1);
}
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
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");
bool checkTp = false;
auto sgroupUpdate = false;
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
bool update_talk_power{false}, update_server_group_list{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
update_server_group_list |= ppermission.is_group_property();
update_talk_power |= ppermission.is_client_view_property();
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) {
serverGroup->permissions()->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value);
} else {
serverGroup->permissions()->set_permission(
permType,
permission::v2::empty_permission_values,
permission::v2::PermissionUpdateType::delete_value,
permission::v2::PermissionUpdateType::do_nothing
);
sgroupUpdate |= permission_is_group_property(permType);
checkTp |= permission_is_client_property(permType);
}
}
if(update_server_group_list)
if(sgroupUpdate)
serverGroup->apply_properties_from_permissions();
if(this->server) {
auto lock = this->_this.lock();
auto server = this->server;
threads::Thread([update_talk_power, update_server_group_list, serverGroup, lock, server]() {
if(update_server_group_list)
threads::Thread([checkTp, sgroupUpdate, serverGroup, lock, server]() {
if(sgroupUpdate)
server->forEachClient([](shared_ptr<ConnectedClient> cl) {
cl->notifyServerGroupList();
});
server->forEachClient([serverGroup, update_talk_power](shared_ptr<ConnectedClient> cl) {
server->forEachClient([serverGroup, checkTp](shared_ptr<ConnectedClient> cl) {
if (cl->serverGroupAssigned(serverGroup)) {
if(cl->update_cached_permissions()) /* update cached calculated permissions */
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
if (update_talk_power)
if (checkTp)
cl->updateChannelClientProperties(true, true);
cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
}
@@ -883,7 +934,7 @@ command_result ConnectedClient::handleCommandServerGroupDelPerm(Command &cmd) {
}).detach();
}
return pparser.build_command_result();
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command& cmd) {
@@ -913,37 +964,67 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command&
if(groups.empty())
return command_result{error::ok};
command::bulk_parser::PermissionBulksParser<true> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
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};
bool update_clients{false}, update_server_group_list{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
for(const auto& serverGroup : groups)
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::set_value);
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
update_server_group_list |= ppermission.is_group_property();
update_clients |= ppermission.is_client_view_property();
bool conOnError = cmd[0].has("continueonerror");
bool checkTp = false;
bool sgroupUpdate = false;
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
//permvalue='1' permnegated='0' permskip='0'end
auto val = cmd[index]["permvalue"].as<permission::PermissionValue>();
if(permission_require_granted_value(permType) && !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};
}
for(const auto& serverGroup : groups) {
if (grant) {
serverGroup->permissions()->set_permission(permType, {0, cmd[index]["permvalue"]}, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::set_value);
} else {
serverGroup->permissions()->set_permission(
permType,
{cmd[index]["permvalue"], 0},
permission::v2::PermissionUpdateType::set_value,
permission::v2::PermissionUpdateType::do_nothing,
cmd[index]["permskip"].as<bool>() ? 1 : 0,
cmd[index]["permnegated"].as<bool>() ? 1 : 0
);
}
}
sgroupUpdate |= permission_is_group_property(permType);
checkTp |= permission_is_client_property(permType);
}
if(update_server_group_list)
if(sgroupUpdate)
for(auto& group : groups)
group->apply_properties_from_permissions();
auto lock = this->_this.lock();
if(ref_server) {
threads::Thread([update_clients, update_server_group_list, groups, lock, ref_server]() {
if(update_server_group_list)
threads::Thread([checkTp, sgroupUpdate, groups, lock, ref_server]() {
if(sgroupUpdate)
ref_server->forEachClient([](shared_ptr<ConnectedClient> cl) {
cl->notifyServerGroupList();
});
ref_server->forEachClient([groups, update_clients](shared_ptr<ConnectedClient> cl) {
ref_server->forEachClient([groups, checkTp](shared_ptr<ConnectedClient> cl) {
for(const auto& serverGroup : groups) {
if (cl->serverGroupAssigned(serverGroup)) {
if(cl->update_cached_permissions()) {/* update cached calculated permissions */
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
}
if (update_clients) {
if (checkTp) {
cl->updateChannelClientProperties(true, true);
}
cl->join_state_id++; /* join permission may changed, all channels need to be recalculate if needed */
@@ -953,8 +1034,7 @@ command_result ConnectedClient::handleCommandServerGroupAutoAddPerm(ts::Command&
});
}).detach();
}
return pparser.build_command_result();
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command& cmd) {
@@ -983,37 +1063,54 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command&
if(groups.empty()) return command_result{error::ok};
command::bulk_parser::PermissionBulksParser<false> pparser{cmd};
if(!pparser.validate(this->ref(), 0))
return pparser.build_command_result();
auto ignore_granted_values = permission::v2::permission_granted(1, this->calculate_permission(permission::b_permission_modify_power_ignore, 0));
bool update_clients{false}, update_server_group_list{false};
for(const auto& ppermission : pparser.iterate_valid_permissions()) {
for(const auto& serverGroup : groups)
ppermission.apply_to(serverGroup->permissions(), permission::v2::PermissionUpdateType::delete_value);
bool conOnError = cmd[0].has("continueonerror");
bool checkTp = false;
auto sgroupUpdate = false;
for (int index = 0; index < cmd.bulkCount(); index++) {
PARSE_PERMISSION(cmd);
update_server_group_list |= ppermission.is_group_property();
update_clients |= ppermission.is_client_view_property();
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};
}
for(const auto& serverGroup : groups) {
if (grant) {
serverGroup->permissions()->set_permission(permType, permission::v2::empty_permission_values, permission::v2::PermissionUpdateType::do_nothing, permission::v2::PermissionUpdateType::delete_value);
} else {
serverGroup->permissions()->set_permission(
permType,
permission::v2::empty_permission_values,
permission::v2::PermissionUpdateType::delete_value,
permission::v2::PermissionUpdateType::do_nothing
);
sgroupUpdate |= permission_is_group_property(permType);
}
}
checkTp |= permission_is_client_property(permType);
}
if(update_server_group_list) {
if(sgroupUpdate) {
for(auto& group : groups)
group->apply_properties_from_permissions();
}
if(ref_server) {
auto lock = this->_this.lock();
threads::Thread([update_clients, update_server_group_list, groups, lock, ref_server]() {
if(update_server_group_list)
threads::Thread([checkTp, sgroupUpdate, groups, lock, ref_server]() {
if(sgroupUpdate)
ref_server->forEachClient([](shared_ptr<ConnectedClient> cl) {
cl->notifyServerGroupList();
});
ref_server->forEachClient([groups, update_clients](shared_ptr<ConnectedClient> cl) {
ref_server->forEachClient([groups, checkTp](shared_ptr<ConnectedClient> cl) {
for(const auto& serverGroup : groups) {
if (cl->serverGroupAssigned(serverGroup)) {
if(cl->update_cached_permissions()) /* update cached calculated permissions */
cl->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
if (update_clients)
if (checkTp)
cl->updateChannelClientProperties(true, true);
cl->join_state_id++; /* join permission may changed, all channels need to be recalculate dif needed */
break;
@@ -1023,7 +1120,7 @@ command_result ConnectedClient::handleCommandServerGroupAutoDelPerm(ts::Command&
}).detach();
}
return pparser.build_command_result();
return command_result{error::ok};
}
command_result ConnectedClient::handleCommandServerGroupsByClientId(Command &cmd) {
+2 -2
View File
@@ -1,5 +1,5 @@
#include <algorithm>
#include <src/server/file/LocalFileServer.h>
#include <src/server/file/FileServer.h>
#include <log/LogUtils.h>
#include "FileClient.h"
#include <src/InstanceHandler.h>
@@ -15,7 +15,7 @@ using namespace ts::server;
namespace fs = std::experimental::filesystem;
#define BUFFER_SIZE (size_t) 2048
FileClient::FileClient(LocalFileServer* handle, int socketFd) : handle(handle), clientFd(socketFd) {
FileClient::FileClient(FileServer* handle, int socketFd) : handle(handle), clientFd(socketFd) {
memtrack::allocated<FileClient>(this);
this->last_io_action = system_clock::now();
+5 -5
View File
@@ -3,7 +3,7 @@
#include <protocol/buffers.h>
#include <poll.h>
#include <fstream>
#include <src/server/file/LocalFileServer.h>
#include <src/server/file/FileServer.h>
#include <event.h>
#include <pipes/ws.h>
#include <pipes/ssl.h>
@@ -14,7 +14,7 @@ namespace ts {
class ConnectedClient;
class ConnectedClient;
class LocalFileServer;
class FileServer;
enum FTType {
Unknown,
@@ -28,7 +28,7 @@ namespace ts {
};
class FileClient {
friend class LocalFileServer;
friend class FileServer;
public:
enum TransferState {
T_INITIALIZE,
@@ -45,7 +45,7 @@ namespace ts {
uint16_t length = 0;
};
FileClient(LocalFileServer* handle, int socketFd);
FileClient(FileServer* handle, int socketFd);
~FileClient();
void disconnect(std::chrono::milliseconds = std::chrono::milliseconds(5000));
@@ -83,7 +83,7 @@ namespace ts {
void handle_ws_message(const pipes::WSMessage&);
bool handle_ts_message();
private:
LocalFileServer* handle;
FileServer* handle;
std::weak_ptr<FileClient> _this;
std::recursive_mutex bandwidth_lock;
+1 -1
View File
@@ -1,6 +1,6 @@
#include <algorithm>
#include <memory>
#include <src/server/file/LocalFileServer.h>
#include <src/server/file/FileServer.h>
#include <log/LogUtils.h>
#include <misc/std_unique_ptr.h>
#include <pipes/buffer.h>
+1 -1
View File
@@ -101,7 +101,7 @@ bool MusicClient::disconnect(const std::string &reason) {
return true;
}
bool server::MusicClient::notifyClientMoved(
bool MusicClient::notifyClientMoved(
const std::shared_ptr<ConnectedClient> &client,
const std::shared_ptr<BasicChannel> &target_channel,
ViewReasonId reason,
@@ -1,7 +1,7 @@
#include "ChannelProvider.h"
#include "../../MusicClient.h"
#include "../../../../InstanceHandler.h"
#include "src/server/file/LocalFileServer.h"
#include "../../../../server/file/FileServer.h"
#include "../../../../../../music/providers/ffmpeg/FFMpegProvider.h"
+60 -369
View File
@@ -8,6 +8,7 @@
#include "src/InstanceHandler.h"
#include <pipes/errors.h>
#include <misc/std_unique_ptr.h>
#include "./QueryClientConnection.h"
using namespace std;
using namespace std::chrono;
@@ -20,41 +21,20 @@ using namespace ts::server;
//#define DEBUG_TRAFFIC
QueryClient::QueryClient(QueryServer* handle, int sockfd) : ConnectedClient(handle->sql, nullptr), handle(handle), clientFd(sockfd) {
QueryClient::QueryClient(QueryServer* handle, int sockfd) : ConnectedClient(handle->sql, nullptr), handle(handle) {
memtrack::allocated<QueryClient>(this);
int enabled = 1;
int disabled = 0;
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled));
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0) {
logError(this->getServerId(), "[Query] Could not disable nopush for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this), errno, strerror(errno));
}
if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof enabled) < 0) {
logError(this->getServerId(), "[Query] Could not disable no delay for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this), errno, strerror(errno));
}
this->readEvent = event_new(this->handle->eventLoop, this->clientFd, EV_READ | EV_PERSIST, [](int a, short b, void* c){ ((QueryClient*) c)->handleMessageRead(a, b, c); }, this);
this->writeEvent = event_new(this->handle->eventLoop, this->clientFd, EV_WRITE, [](int a, short b, void* c){ ((QueryClient*) c)->handleMessageWrite(a, b, c); }, this);
this->connection = new server::query::QueryClientConnection{this, sockfd};
this->state = ConnectionState::CONNECTED;
this->state = ClientState::INITIALIZING;
connectedTimestamp = system_clock::now();
this->resetEventMask();
}
void QueryClient::applySelfLock(const std::shared_ptr<ts::server::QueryClient> &cl) {
bool QueryClient::initialize(std::string& error, const std::shared_ptr<ts::server::QueryClient> &cl) {
this->_this = cl;
}
QueryClient::~QueryClient() {
memtrack::freed<QueryClient>(this);
// if(this->closeLock.tryLock() != 0)
// logCritical("Query manager deleted, but is still in usage! (closeLock)");
// if(this->bufferLock.tryLock() != 0)
// logCritical("Query manager deleted, but is still in usage! (bufferLock)");
this->ssl_handler.finalize();
}
void QueryClient::preInitialize() {
this->properties()[property::CLIENT_TYPE] = ClientType::CLIENT_QUERY;
this->properties()[property::CLIENT_TYPE_EXACT] = ClientType::CLIENT_QUERY;
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = "UnknownQuery";
@@ -62,22 +42,27 @@ void QueryClient::preInitialize() {
DatabaseHelper::assignDatabaseId(this->sql, this->getServerId(), _this.lock());
if(ts::config::query::sslMode == 0) {
this->connectionType = ConnectionType::PLAIN;
this->postInitialize();
}
/* may already calls handle_connection_initialized() */
if(!this->connection->initialize(error))
return false;
return true;
}
void QueryClient::postInitialize() {
lock_guard<recursive_mutex> lock(this->lock_packet_handle); /* we dont want to handle anything while we're initializing */
QueryClient::~QueryClient() {
memtrack::freed<QueryClient>(this);
delete this->connection;
}
void QueryClient::handle_connection_initialized() {
std::lock_guard lock{this->command_lock}; /* we dont want to handle anything while we're initializing */
this->connectTimestamp = system_clock::now();
this->properties()[property::CLIENT_LASTCONNECTED] = duration_cast<seconds>(this->connectTimestamp.time_since_epoch()).count();
if(ts::config::query::sslMode == 1 && this->connectionType != ConnectionType::SSL_ENCRIPTED) {
if(ts::config::query::sslMode == 1 && this->connection->connection_type() != server::query::ConnectionType::SSL_ENCRYPTED) {
command_result error{error::failed_connection_initialisation, "Please use a SSL encryption!"};
this->notifyError(error);
error.release_data();
error.release_details();
this->disconnect("Please us a SSL encryption for more security.\nThe server denies also all other connections!");
return;
}
@@ -104,7 +89,7 @@ void QueryClient::postInitialize() {
}
if(!this->whitelisted) {
threads::MutexLock lock(this->handle->loginLock);
threads::MutexLock llock(this->handle->loginLock);
if(this->handle->queryBann.count(this->getPeerIp()) > 0) {
auto ban = this->handle->queryBann[this->getPeerIp()];
Command cmd("error");
@@ -120,12 +105,26 @@ void QueryClient::postInitialize() {
this->update_cached_permissions();
}
void QueryClient::writeMessage(const std::string& message) {
if(this->state == ConnectionState::DISCONNECTED || !this->handle) return;
void QueryClient::handle_connection_finalized() {
/* when this has been called there could not be any command executing! */
//TODO: Is this statement really true?
if(this->connectionType == ConnectionType::PLAIN) this->writeRawMessage(message);
else if(this->connectionType == ConnectionType::SSL_ENCRIPTED) this->ssl_handler.send(pipes::buffer_view{(void*) message.data(), message.length()});
else logCritical(LOG_GENERAL, "Invalid query connection type to write to!");
if(this->server) {
{
unique_lock channel_lock(this->server->channel_tree_lock);
this->server->unregisterClient(_this.lock(), "disconnected", channel_lock);
}
this->server->groups->disableCache(this->getClientDatabaseId());
this->server = nullptr;
}
if(this->handle)
this->handle->unregisterConnection(dynamic_pointer_cast<QueryClient>(_this.lock()));
}
void QueryClient::writeMessage(const std::string& message) {
if(this->state == ClientState::DISCONNECTED) return;
this->connection->send_data(message);
}
@@ -142,331 +141,25 @@ bool QueryClient::close_connection(const std::chrono::system_clock::time_point&
auto ownLock = dynamic_pointer_cast<QueryClient>(_this.lock());
if(!ownLock) return false;
unique_lock<std::recursive_mutex> handleLock(this->lock_packet_handle);
unique_lock<threads::Mutex> lock(this->closeLock);
bool flushing = flushTimeout.time_since_epoch().count() != 0;
if(this->state == ConnectionState::DISCONNECTED || (flushing && this->state == ConnectionState::DISCONNECTING)) return false;
this->state = flushing ? ConnectionState::DISCONNECTING : ConnectionState::DISCONNECTED;
if(this->readEvent) { //Attention dont trigger this within the read thread!
event_del_block(this->readEvent);
event_free(this->readEvent);
this->readEvent = nullptr;
}
if(this->server){
{
unique_lock channel_lock(this->server->channel_tree_lock);
this->server->unregisterClient(_this.lock(), "disconnected", channel_lock);
}
this->server->groups->disableCache(this->getClientDatabaseId());
this->server = nullptr;
}
if(flushing){
this->flushThread = new threads::Thread(THREAD_SAVE_OPERATIONS | THREAD_EXECUTE_LATER, [ownLock, flushTimeout](){
while(ownLock->state == ConnectionState::DISCONNECTING && flushTimeout > system_clock::now()){
{
std::lock_guard buffer_lock(ownLock->buffer_lock);
if(ownLock->readQueue.empty() && ownLock->writeQueue.empty()) break;
}
usleep(10 * 1000);
}
if(ownLock->state == ConnectionState::DISCONNECTING) ownLock->disconnectFinal();
});
flushThread->name("Flush thread QC").execute();
} else {
threads::MutexLock l1(this->flushThreadLock);
handleLock.unlock();
lock.unlock();
if(this->flushThread){
threads::NegatedMutexLock l(this->closeLock);
this->flushThread->join();
}
disconnectFinal();
}
this->connection->close_connection(flushTimeout);
return true;
}
void QueryClient::disconnectFinal() {
lock_guard<recursive_mutex> lock_tick(this->lock_query_tick);
lock_guard<recursive_mutex> lock_handle(this->lock_packet_handle);
threads::MutexLock lock_close(this->closeLock);
std::unique_lock buffer_lock(this->buffer_lock, try_to_lock);
bool QueryClient::process_next_command() {
using CommandAssembleState = server::query::CommandAssembleState;
if(final_disconnected) {
logError(LOG_QUERY, "Tried to disconnect a client twice!");
return;
}
final_disconnected = true;
this->state = ConnectionState::DISCONNECTED;
{
threads::MutexTryLock l(this->flushThreadLock);
if(!!l) {
if(this->flushThread) {
this->flushThread->detach();
delete this->flushThread; //Release the captured this lock
this->flushThread = nullptr;
}
}
}
lock_guard clock(this->command_lock);
if(!this->handle || this->state == ClientState::DISCONNECTED) return false;
if(this->writeEvent) {
event_del_block(this->writeEvent);
event_free(this->writeEvent);
this->writeEvent = nullptr;
}
if(this->readEvent) {
event_del_block(this->readEvent);
event_free(this->readEvent);
this->readEvent = nullptr;
}
if(this->clientFd > 0) {
if(shutdown(this->clientFd, SHUT_RDWR) < 0)
debugMessage(LOG_QUERY, "Could not shutdown query client socket! {} ({})", errno, strerror(errno));
if(close(this->clientFd) < 0)
debugMessage(LOG_QUERY, "Failed to close the query client socket! {} ({})", errno, strerror(errno));
this->clientFd = -1;
}
if(this->server) {
{
unique_lock channel_lock(this->server->channel_tree_lock);
this->server->unregisterClient(_this.lock(), "disconnected", channel_lock);
}
this->server->groups->disableCache(this->getClientDatabaseId());
this->server = nullptr;
}
this->readQueue.clear();
this->writeQueue.clear();
if(this->handle)
this->handle->unregisterConnection(dynamic_pointer_cast<QueryClient>(_this.lock()));
}
void QueryClient::writeRawMessage(const std::string &message) {
{
std::lock_guard lock(this->buffer_lock);
this->writeQueue.push_back(message);
}
if(this->writeEvent) event_add(this->writeEvent, nullptr);
}
void QueryClient::handleMessageWrite(int fd, short, void *) {
auto ownLock = _this.lock();
std::unique_lock buffer_lock(this->buffer_lock, try_to_lock);
if(this->state == ConnectionState::DISCONNECTED) return;
if(!buffer_lock.owns_lock()) {
if(this->writeEvent)
event_add(this->writeEvent, nullptr);
return;
}
int writes = 0;
string buffer;
while(writes < 10 && !this->writeQueue.empty()) {
if(buffer.empty()) {
buffer = std::move(this->writeQueue.front());
this->writeQueue.pop_front();
}
auto length = send(fd, buffer.data(), buffer.length(), MSG_NOSIGNAL);
#ifdef DEBUG_TRAFFIC
debugMessage("Write " + to_string(buffer.length()));
hexDump((void *) buffer.data(), buffer.length());
#endif
if(length == -1) {
if (errno == EINTR || errno == EAGAIN) {
if(this->writeEvent)
event_add(this->writeEvent, nullptr);
return;
}
else {
logError(LOG_QUERY, "{} Failed to write message: {} ({} => {})", CLIENT_STR_LOG_PREFIX, length, errno, strerror(errno));
threads::Thread([=](){ ownLock->close_connection(chrono::system_clock::now() + chrono::seconds{5}); }).detach();
return;
}
} else {
if(buffer.length() == length)
buffer = "";
else
buffer = buffer.substr(length);
}
writes++;
}
if(!buffer.empty())
this->writeQueue.push_front(buffer);
if(!this->writeQueue.empty() && this->writeEvent)
event_add(this->writeEvent, nullptr);
}
void QueryClient::handleMessageRead(int fd, short, void *) {
auto ownLock = dynamic_pointer_cast<QueryClient>(_this.lock());
if(!ownLock) {
logCritical(LOG_QUERY, "Could not get own lock!");
return;
}
string buffer(1024, 0);
auto length = read(fd, (void*) buffer.data(), buffer.length());
if(length <= 0){
if(errno == EINTR || errno == EAGAIN)
;//event_add(this->readEvent, nullptr);
else if(length == 0 && errno == 0) {
logMessage(LOG_QUERY, "{} Connection closed. Client disconnected.", CLIENT_STR_LOG_PREFIX);
event_del_noblock(this->readEvent);
std::thread([ownLock]{
ownLock->close_connection();
}).detach();
} else {
logError(LOG_QUERY, "{} Failed to read! Code: {} errno: {} message: {}", CLIENT_STR_LOG_PREFIX, length, errno, strerror(errno));
event_del_noblock(this->readEvent);
threads::Thread(THREAD_SAVE_OPERATIONS, [ownLock](){ ownLock->close_connection(); }).detach();
}
return;
}
buffer.resize(length);
{
std::lock_guard buffer_lock(this->buffer_lock);
if(this->state == ConnectionState::DISCONNECTED)
return;
this->readQueue.push_back(std::move(buffer));
#ifdef DEBUG_TRAFFIC
debugMessage("Read " + to_string(buffer.length()));
hexDump((void *) buffer.data(), buffer.length());
#endif
}
if(this->handle)
this->handle->executePool()->execute([ownLock]() {
int counter = 0;
while(ownLock->tickIOMessageProgress() && counter++ < 15);
});
}
bool QueryClient::tickIOMessageProgress() {
lock_guard<recursive_mutex> lock(this->lock_packet_handle);
if(!this->handle || this->state == ConnectionState::DISCONNECTED || this->state == ConnectionState::DISCONNECTING) return false;
string message;
bool next = false;
{
std::lock_guard buffer_lock(this->buffer_lock);
if(this->readQueue.empty()) return false;
message = std::move(this->readQueue.front());
this->readQueue.pop_front();
next |= this->readQueue.empty();
}
if(this->connectionType == ConnectionType::PLAIN) {
int count = 0;
while(this->handleMessage(pipes::buffer_view{(void*) message.data(), message.length()}) && count++ < 15) message = "";
next |= count == 15;
} else if(this->connectionType == ConnectionType::SSL_ENCRIPTED) {
this->ssl_handler.process_incoming_data(pipes::buffer_view{(void*) message.data(), message.length()});
} else if(this->connectionType == ConnectionType::UNKNOWN) {
if(config::query::sslMode != 0 && pipes::SSL::isSSLHeader(message)) {
this->initializeSSL();
/*
* - Content
* \x16
* -Version (1)
* \x03 \x00
* - length (2)
* \x00 \x04
*
* - Header
* \x00 -> hello request (3)
* \x05 -> length (4)
*/
//this->writeRawMessage(string("\x16\x03\x01\x00\x05\x00\x00\x00\x00\x00", 10));
} else {
this->connectionType = ConnectionType::PLAIN;
this->postInitialize();
}
next = true;
{
std::lock_guard buffer_lock(this->buffer_lock);
this->readQueue.push_front(std::move(message));
}
}
return next;
}
extern InstanceHandler* serverInstance;
void QueryClient::initializeSSL() {
this->connectionType = ConnectionType::SSL_ENCRIPTED;
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_IN, true);
this->ssl_handler.callback_data(std::bind(&QueryClient::handleMessage, this, placeholders::_1));
this->ssl_handler.callback_write(std::bind(&QueryClient::writeRawMessage, this, placeholders::_1));
this->ssl_handler.callback_initialized = std::bind(&QueryClient::postInitialize, this);
this->ssl_handler.callback_error([&](int code, const std::string& message) {
if(code == PERROR_SSL_ACCEPT) {
this->disconnect("invalid accept");
} else if(code == PERROR_SSL_TIMEOUT)
this->disconnect("invalid accept (timeout)");
else
logError(LOG_QUERY, "Got unknown ssl error ({} | {})", code, message);
});
{
auto context = serverInstance->sslManager()->getQueryContext();
auto options = make_shared<pipes::SSL::Options>();
options->type = pipes::SSL::SERVER;
options->context_method = TLS_method();
options->default_keypair({context->privateKey, context->certificate});
if(!this->ssl_handler.initialize(options)) {
logError(LOG_QUERY, "[{}] Failed to setup ssl!", CLIENT_STR_LOG_PREFIX);
}
}
}
bool QueryClient::handleMessage(const pipes::buffer_view& message) {
{
threads::MutexLock l(this->closeLock);
if(this->state == ConnectionState::DISCONNECTED)
return false;
}
#ifdef DEBUG_TRAFFIC
debugMessage("Handling message " + to_string(message.length()));
hexDump((void *) message.data(), message.length());
#endif
string command;
{
this->lineBuffer += message.string();
int length = 2;
auto pos = this->lineBuffer.find("\r\n");
if(pos == string::npos) pos = this->lineBuffer.find("\n\r");
if(pos == string::npos) {
length = 1;
pos = this->lineBuffer.find('\n');
}
if(pos != string::npos){
command = this->lineBuffer.substr(0, pos);
if(this->lineBuffer.size() > pos + length)
this->lineBuffer = this->lineBuffer.substr(pos + length);
else
this->lineBuffer.clear();
}
if(pos == string::npos) return false;
string command{};
bool more_pending{false};
switch (this->connection->next_command(command)) {
case CommandAssembleState::MORE_COMMANDS_PENDING:
more_pending = true;
case CommandAssembleState::SUCCESS:
break;
case CommandAssembleState::NO_COMMAND_PENDING:
return false; /* nothing to do */
}
if(command.empty() || command.find_first_not_of(' ') == string::npos) { //Empty command
@@ -501,25 +194,25 @@ bool QueryClient::handleMessage(const pipes::buffer_view& message) {
cmd = make_unique<Command>(Command::parse(pipes::buffer_view{(void*) command.data(), command.length()}, true, !ts::config::server::strict_ut8_mode));
} catch(std::invalid_argument& ex) {
logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (invalid argument): {}", this->getLoggingPeerIp(), this->getPeerPort(), command);
error.reset(command_result{error::parameter_convert});
error = command_result{error::parameter_convert};
goto handle_error;
} catch(std::exception& ex) {
logTrace(LOG_QUERY, "[{}:{}] Failed to parse command (exception: {}): {}", this->getLoggingPeerIp(), this->getPeerPort(), ex.what(), command);
error.reset(command_result{error::vs_critical, std::string{ex.what()}});
error = command_result{error::vs_critical, std::string{ex.what()}};
goto handle_error;
}
try {
this->handleCommandFull(*cmd);
} catch(std::exception& ex) {
error.reset(command_result{error::vs_critical, std::string{ex.what()}});
error = command_result{error::vs_critical, std::string{ex.what()}};
goto handle_error;
}
return true;
return more_pending;
handle_error:
this->notifyError(error);
error.release_data();
error.release_details();
return false;
}
@@ -539,15 +232,13 @@ void QueryClient::tick(const std::chrono::system_clock::time_point &time) {
}
void QueryClient::queryTick() {
lock_guard<recursive_mutex> lock_tick(this->lock_query_tick);
if(this->idleTimestamp.time_since_epoch().count() > 0 && system_clock::now() - this->idleTimestamp > minutes(5)){
debugMessage(LOG_QUERY, "Dropping client " + this->getLoggingPeerIp() + "|" + this->getDisplayName() + ". (Timeout)");
this->close_connection(system_clock::now() + seconds(1));
}
if(this->connectionType == ConnectionType::UNKNOWN && system_clock::now() - milliseconds(500) > connectedTimestamp) {
this->connectionType = ConnectionType::PLAIN;
this->postInitialize();
if(this->connection->connection_type() == server::query::ConnectionType::UNKNOWN && system_clock::now() - milliseconds{500} > connectedTimestamp) {
this->connection->enforce_text_connection();
}
}
+23 -45
View File
@@ -10,19 +10,18 @@ namespace ts::server {
class QueryServer;
class QueryAccount;
namespace server::query {
class QueryClientConnection;
}
class QueryClient : public ConnectedClient {
friend class QueryServer;
enum ConnectionType {
PLAIN,
SSL_ENCRIPTED,
UNKNOWN
};
friend class server::query::QueryClientConnection;
public:
QueryClient(QueryServer*, int sockfd);
~QueryClient() override;
[[nodiscard]] inline QueryServer* getQueryServer() { return this->handle; }
void writeMessage(const std::string&);
void sendCommand(const ts::Command &command, bool low = false) override;
@@ -30,7 +29,6 @@ namespace ts::server {
bool disconnect(const std::string &reason) override;
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
void disconnectFinal();
bool eventActive(QueryEventGroup, QueryEventSpecifier);
void toggleEvent(QueryEventGroup, QueryEventSpecifier, bool);
@@ -41,51 +39,26 @@ namespace ts::server {
inline std::shared_ptr<QueryAccount> getQueryAccount() { return this->query_account; }
protected:
void preInitialize();
void postInitialize();
/* Will be called as soon the connection has been initialized. This means directly within the initialize call or within the IO read callback. */
void handle_connection_initialized();
void handle_connection_finalized();
void tick(const std::chrono::system_clock::time_point &time) override;
void queryTick();
protected:
void initializeSSL();
/* returns true if more commands are pending */
bool process_next_command();
bool handleMessage(const pipes::buffer_view&);
bool tickIOMessageProgress();
void handleMessageRead(int, short, void*);
void handleMessageWrite(int, short, void*);
void writeRawMessage(const std::string&);
void applySelfLock(const std::shared_ptr<QueryClient> &cl);
[[nodiscard]] bool initialize(std::string& /* error */, const std::shared_ptr<QueryClient> &cl);
private:
QueryServer* handle;
ConnectionType connectionType = ConnectionType::UNKNOWN;
server::query::QueryClientConnection* connection{nullptr};
bool whitelisted = false;
int clientFd = -1;
::event* readEvent = nullptr;
::event* writeEvent = nullptr;
threads::Mutex closeLock;
pipes::SSL ssl_handler;
std::mutex buffer_lock;
std::deque<std::string> writeQueue;
std::deque<std::string> readQueue;
threads::Mutex flushThreadLock;
threads::Thread* flushThread = nullptr;
bool final_disconnected = false;
std::string lineBuffer;
std::chrono::time_point<std::chrono::system_clock> connectedTimestamp;
uint16_t eventMask[QueryEventGroup::QEVENTGROUP_MAX];
std::recursive_mutex lock_packet_handle;
std::recursive_mutex lock_query_tick;
std::shared_ptr<QueryAccount> query_account;
protected:
command_result handleCommand(Command &command) override;
@@ -99,7 +72,7 @@ namespace ts::server {
bool notifyServerUpdated(std::shared_ptr<ConnectedClient> ptr) override;
bool notifyClientPoke(std::shared_ptr<ConnectedClient> invoker, std::string msg) override;
bool notifyClientUpdated(const std::shared_ptr<ConnectedClient> &ptr, const std::deque<const property::PropertyDescription*> &deque, bool lock_channel_tree) override;
bool notifyClientUpdated(const std::shared_ptr<ConnectedClient> &ptr, const std::deque<std::shared_ptr<property::PropertyDescription>> &deque, bool lock_channel_tree) override;
bool notifyPluginCmd(std::string name, std::string msg,std::shared_ptr<ConnectedClient>) override;
bool notifyClientChatComposing(const std::shared_ptr<ConnectedClient> &ptr) override;
@@ -139,9 +112,15 @@ namespace ts::server {
bool notifyClientLeftView(const std::deque<std::shared_ptr<ConnectedClient>> &deque, const std::string &string, bool b, const ViewReasonServerLeftT &t) override;
bool notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client, const std::shared_ptr<BasicChannel> &target_channel, const std::string &message, std::shared_ptr<ConnectedClient> invoker, bool lock_channel_tree) override;
bool notifyClientLeftViewKicked(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
const std::string &message, std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree,
const std::shared_ptr<BasicChannel> &target_channel) override;
bool notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClient> &client, const std::string &message, std::shared_ptr<ConnectedClient> invoker, size_t length, bool lock_channel_tree) override;
bool notifyClientLeftViewBanned(const std::shared_ptr<ConnectedClient> &client, ChannelId channel_from,
std::shared_ptr<ConnectedClient> invoker, size_t length,
bool lock_channel_tree,
const std::string &message) override;
private:
command_result handleCommandExit(Command&);
@@ -171,7 +150,6 @@ namespace ts::server {
command_result handleCommandServerIdGetByPort(Command&);
command_result handleCommandServerSnapshotDeploy(Command&);
command_result handleCommandServerSnapshotDeployNew(const command_parser&);
command_result handleCommandServerSnapshotCreate(Command&);
command_result handleCommandServerProcessStop(Command&);
+54 -94
View File
@@ -10,7 +10,6 @@
#include <misc/base64.h>
#include <src/ShutdownHelper.h>
#include <ThreadPool/Timer.h>
#include <numeric>
#include "src/client/command_handler/helpers.h"
@@ -100,17 +99,9 @@ command_result QueryClient::handleCommand(Command& cmd) {
return this->handleCommandHostInfo(cmd);
case string_hash("bindinglist"):
return this->handleCommandBindingList(cmd);
case string_hash("serversnapshotdeploy"): {
#if 1
case string_hash("serversnapshotdeploy"):
return this->handleCommandServerSnapshotDeploy(cmd);
#else
auto cmd_str = cmd.build();
ts::command_parser parser{cmd_str};
if(!parser.parse(true)) return command_result{error::vs_critical};
return this->handleCommandServerSnapshotDeployNew(parser);
#endif
}
case string_hash("serversnapshotcreate"):
return this->handleCommandServerSnapshotCreate(cmd);
case string_hash("serverprocessstop"):
@@ -174,8 +165,8 @@ command_result QueryClient::handleCommandLogin(Command& cmd) {
if(!this->properties()[property::CLIENT_LOGIN_NAME].as<string>().empty()) {
Command log("logout");
auto result = this->handleCommandLogout(log);
if(result.has_error()) {
result.release_data();
if(result.error_code()) {
result.release_details();
logError(this->getServerId(), "Query client failed to login from old login.");
return command_result{error::vs_critical};
}
@@ -406,38 +397,37 @@ command_result QueryClient::handleCommandServerInfo(Command &) {
if(this->server && permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_connectioninfo_view, 0))) {
auto total_stats = this->server->getServerStatistics()->total_stats();
auto report_second = this->server->serverStatistics->second_stats();
auto report_minute = this->server->serverStatistics->minute_stats();
cmd["connection_bandwidth_sent_last_second_total"] = std::accumulate(report_second.connection_bytes_sent.begin(), report_second.connection_bytes_sent.end(), (size_t) 0U);
cmd["connection_bandwidth_sent_last_minute_total"] = std::accumulate(report_minute.connection_bytes_sent.begin(), report_minute.connection_bytes_sent.end(), (size_t) 0U);
cmd["connection_bandwidth_received_last_second_total"] = std::accumulate(report_second.connection_bytes_received.begin(), report_second.connection_bytes_received.end(), (size_t) 0U);
cmd["connection_bandwidth_received_last_minute_total"] = std::accumulate(report_minute.connection_bytes_received.begin(), report_minute.connection_bytes_received.end(), (size_t) 0U);
auto stats = this->server->getServerStatistics()->statistics();
auto report = this->server->serverStatistics->dataReport();
cmd["connection_bandwidth_sent_last_second_total"] = report.send_second;
cmd["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
cmd["connection_bandwidth_received_last_second_total"] = report.recv_second;
cmd["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
cmd["connection_filetransfer_bandwidth_sent"] = report_minute.file_bytes_sent;
cmd["connection_filetransfer_bandwidth_received"] = report_minute.file_bytes_received;
cmd["connection_filetransfer_bytes_sent_total"] = total_stats.file_bytes_sent;
cmd["connection_filetransfer_bytes_received_total"] = total_stats.file_bytes_received;
cmd["connection_filetransfer_bandwidth_sent"] = report.file_send;
cmd["connection_filetransfer_bandwidth_received"] = report.file_recv;
cmd["connection_filetransfer_bytes_sent_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
cmd["connection_filetransfer_bytes_received_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
cmd["connection_packets_sent_speech"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
cmd["connection_bytes_sent_speech"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
cmd["connection_packets_received_speech"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::VOICE];
cmd["connection_bytes_received_speech"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::VOICE];
cmd["connection_packets_sent_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT].value();
cmd["connection_bytes_sent_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED].value();
cmd["connection_packets_received_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].value();
cmd["connection_bytes_received_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].value();
cmd["connection_packets_sent_keepalive"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_packets_received_keepalive"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_bytes_received_keepalive"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_bytes_sent_keepalive"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_packets_sent_keepalive"] = (*stats)[property::CONNECTION_PACKETS_SENT_KEEPALIVE].value();
cmd["connection_packets_received_keepalive"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE].value();
cmd["connection_bytes_received_keepalive"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_KEEPALIVE].value();
cmd["connection_bytes_sent_keepalive"] = (*stats)[property::CONNECTION_BYTES_SENT_KEEPALIVE].value();
cmd["connection_packets_sent_control"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_bytes_sent_control"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_packets_received_control"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_bytes_received_control"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_packets_sent_control"] = (*stats)[property::CONNECTION_PACKETS_SENT_CONTROL].value();
cmd["connection_bytes_sent_control"] = (*stats)[property::CONNECTION_BYTES_SENT_CONTROL].value();
cmd["connection_packets_received_control"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_CONTROL].value();
cmd["connection_bytes_received_control"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_CONTROL].value();
cmd["connection_packets_sent_total"] = std::accumulate(total_stats.connection_packets_sent.begin(), total_stats.connection_packets_sent.end(), (size_t) 0U);
cmd["connection_bytes_sent_total"] = std::accumulate(total_stats.connection_bytes_sent.begin(), total_stats.connection_bytes_sent.end(), (size_t) 0U);
cmd["connection_packets_received_total"] = std::accumulate(total_stats.connection_packets_received.begin(), total_stats.connection_packets_received.end(), (size_t) 0U);
cmd["connection_bytes_received_total"] = std::accumulate(total_stats.connection_bytes_received.begin(), total_stats.connection_bytes_received.end(), (size_t) 0U);
cmd["connection_packets_sent_total"] = (*stats)[property::CONNECTION_PACKETS_SENT_TOTAL].value();
cmd["connection_bytes_sent_total"] = (*stats)[property::CONNECTION_BYTES_SENT_TOTAL].value();
cmd["connection_packets_received_total"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].value();
cmd["connection_bytes_received_total"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_TOTAL].value();
} else {
cmd["connection_bandwidth_sent_last_second_total"] = "0";
cmd["connection_bandwidth_sent_last_minute_total"] = "0";
@@ -486,13 +476,11 @@ command_result QueryClient::handleCommandChannelList(Command& cmd) {
command_builder result{"", 1024, entries.size()};
for(const auto& channel : entries){
if(!channel) continue;
const auto channel_clients = this->server ? this->server->getClientsByChannel(channel).size() : 0;
result.put_unchecked(index, "cid", channel->channelId());
result.put_unchecked(index, "pid", channel->properties()[property::CHANNEL_PID].as<string>());
result.put_unchecked(index, "channel_name", channel->name());
result.put_unchecked(index, "channel_order", channel->channelOrder());
result.put_unchecked(index, "total_clients", channel_clients);
result.put_unchecked(index, "total_clients", this->server ? this->server->getClientsByChannel(channel).size() : 0);
/* result.put_unchecked(index, "channel_needed_subscribe_power", channel->permissions()->getPermissionValue(permission::i_channel_needed_subscribe_power, channel, 0)); */
if(cmd.hasParm("flags")){
@@ -524,8 +512,8 @@ command_result QueryClient::handleCommandChannelList(Command& cmd) {
if(cmd.hasParm("topic")) {
result.put_unchecked(index, "channel_topic", channel->properties()[property::CHANNEL_TOPIC].as<string>());
}
if(cmd.hasParm("times") || cmd.hasParm("secondsempty")){
result.put_unchecked(index, "seconds_empty", channel_clients == 0 ? channel->emptySince() : 0);
if(cmd.hasParm("times")){
result.put_unchecked(index, "seconds_empty", channel->emptySince());
}
index++;
}
@@ -594,9 +582,9 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) {
time_wait = duration_cast<milliseconds>(end - start);
}
std::string host = cmd[0].has("virtualserver_host") ? cmd["virtualserver_host"].as<string>() : config::binding::DefaultVoiceHost;
uint16_t freePort = serverInstance->getVoiceServerManager()->next_available_port(host);
uint16_t freePort = serverInstance->getVoiceServerManager()->next_available_port();
std::string host = cmd[0].has("virtualserver_host") ? cmd["virtualserver_host"].as<string>() : config::binding::DefaultVoiceHost;
uint16_t port = cmd[0].has("virtualserver_port") ? cmd["virtualserver_port"].as<uint16_t>() : freePort;
{
auto _start = system_clock::now();
@@ -610,12 +598,12 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) {
if(key == "virtualserver_port") continue;
if(key == "virtualserver_host") continue;
const auto& info = property::find<property::VirtualServerProperties>(key);
if(info == property::VIRTUALSERVER_UNDEFINED) {
auto info = property::impl::info<property::VirtualServerProperties>(key);
if(*info == property::VIRTUALSERVER_UNDEFINED) {
logError(server->getServerId(), "Tried to change unknown server property " + key);
continue;
}
if(!info.validate_input(cmd[key].as<string>())) {
if(!info->validate_input(cmd[key].as<string>())) {
logError(server->getServerId(), "Tried to change " + key + " to an invalid value: " + cmd[key].as<string>());
continue;
}
@@ -734,9 +722,9 @@ command_result QueryClient::handleCommandInstanceEdit(Command& cmd) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_modify_settings, 1);
for(const auto &key : cmd[0].keys()){
const auto* info = &property::find<property::InstanceProperties>(key);
auto info = property::impl::info<property::InstanceProperties>(key);
if(key == "serverinstance_serverquery_max_connections_per_ip")
info = &property::describe(property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS_PER_IP);
info = property::impl::info(property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS_PER_IP);
if(*info == property::SERVERINSTANCE_UNDEFINED) {
logError(LOG_QUERY, "Query {} tried to change a non existing instance property: {}", this->getLoggingPeerIp(), key);
@@ -770,24 +758,22 @@ command_result QueryClient::handleCommandHostInfo(Command &) {
res["virtualservers_total_channels_online"] = vsReport.onlineChannels;
auto total_stats = serverInstance->getStatistics()->total_stats();
res["connection_packets_sent_total"] = std::accumulate(total_stats.connection_packets_sent.begin(), total_stats.connection_packets_sent.end(), (size_t) 0U);
res["connection_bytes_sent_total"] = std::accumulate(total_stats.connection_bytes_sent.begin(), total_stats.connection_bytes_sent.end(), (size_t) 0U);
res["connection_packets_received_total"] = std::accumulate(total_stats.connection_packets_received.begin(), total_stats.connection_packets_received.end(), (size_t) 0U);
res["connection_bytes_received_total"] = std::accumulate(total_stats.connection_bytes_received.begin(), total_stats.connection_bytes_received.end(), (size_t) 0U);
auto stats = serverInstance->getStatistics()->statistics();
res["connection_packets_sent_total"] = (*stats)[property::CONNECTION_PACKETS_SENT_TOTAL].as<string>();
res["connection_bytes_sent_total"] = (*stats)[property::CONNECTION_BYTES_SENT_TOTAL].as<string>();
res["connection_packets_received_total"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].as<string>();
res["connection_bytes_received_total"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_TOTAL].as<string>();
auto report_second = serverInstance->getStatistics()->second_stats();
auto report_minute = serverInstance->getStatistics()->minute_stats();
res["connection_bandwidth_sent_last_second_total"] = std::accumulate(report_second.connection_bytes_sent.begin(), report_second.connection_bytes_sent.end(), (size_t) 0U);
res["connection_bandwidth_sent_last_minute_total"] = std::accumulate(report_minute.connection_bytes_sent.begin(), report_minute.connection_bytes_sent.end(), (size_t) 0U);
res["connection_bandwidth_received_last_second_total"] = std::accumulate(report_second.connection_bytes_received.begin(), report_second.connection_bytes_received.end(), (size_t) 0U);
res["connection_bandwidth_received_last_minute_total"] = std::accumulate(report_minute.connection_bytes_received.begin(), report_minute.connection_bytes_received.end(), (size_t) 0U);
auto report = serverInstance->getStatistics()->dataReport();
res["connection_bandwidth_sent_last_second_total"] = report.send_second;
res["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
res["connection_bandwidth_received_last_second_total"] = report.recv_second;
res["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
res["connection_filetransfer_bandwidth_sent"] = report_minute.file_bytes_sent;
res["connection_filetransfer_bandwidth_received"] = report_minute.file_bytes_received;
res["connection_filetransfer_bytes_sent_total"] = total_stats.file_bytes_sent;
res["connection_filetransfer_bytes_received_total"] = total_stats.file_bytes_received;
res["connection_filetransfer_bandwidth_sent"] = report.file_send;
res["connection_filetransfer_bandwidth_received"] = report.file_recv;
res["connection_filetransfer_bytes_sent_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
res["connection_filetransfer_bytes_received_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
this->sendCommand(res);
return command_result{error::ok};
@@ -851,7 +837,7 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock);
if(port == 0)
port = serverInstance->getVoiceServerManager()->next_available_port(host);
port = serverInstance->getVoiceServerManager()->next_available_port();
auto result = serverInstance->getVoiceServerManager()->createServerFromSnapshot(this->server, host, port, cmd, error);
server_create_lock.unlock();
auto end = system_clock::now();
@@ -881,32 +867,6 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) {
CMD_RESET_IDLE;
if(this->server) {
return command_result{error::not_implemented};
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
//host = this->server->properties()[property::VIRTUALSERVER_HOST].as<string>();
//port = this->server->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>();
} else {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
}
std::string error{};
unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock);
//TODO: Create a server if no exists
server_create_lock.unlock();
//TODO: Stop the server completely
if(!serverInstance->getVoiceServerManager()->deploy_snapshot(error, 111, command)) {
//TODO: Delete server is it was new
return command_result{error::vs_critical, error};
}
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerSnapshotCreate(Command& cmd) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_create, 1);
CMD_RESET_IDLE;
@@ -0,0 +1,384 @@
//
// Created by WolverinDEV on 11/03/2020.
//
#include "./QueryClientConnection.h"
#include <netinet/tcp.h>
#include <log/LogUtils.h>
#include <pipes/errors.h>
#include <src/InstanceHandler.h>
#include "./QueryClient.h"
#include "../ConnectedClient.h"
#include "../../server/QueryServer.h"
#include "QueryClientConnection.h"
using namespace ts::server::server::query;
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
#define TCP_NOPUSH TCP_CORK
#endif
namespace ts::server::server::query {
/* will be set by the event loop */
thread_local bool thread_is_event_loop{false};
}
QueryClientConnection::QueryClientConnection(ts::server::QueryClient *client, int fd) : client_handle{client}, file_descriptor_{fd} {
TAILQ_INIT(&this->write_queue);
}
QueryClientConnection::~QueryClientConnection() {
this->finalize(true);
}
bool QueryClientConnection::initialize(std::string &error) {
assert(this->client_handle);
int enabled{1};
int disabled{0};
setsockopt(this->file_descriptor_, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled));
if(setsockopt(this->file_descriptor_, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof disabled) < 0)
logError(LOG_QUERY, "Could not disable nopush for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this->client_handle), errno, strerror(errno));
if(setsockopt(this->file_descriptor_, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof enabled) < 0)
logError(LOG_QUERY, "[Query] Could not disable no delay for {} ({}/{})", CLIENT_STR_LOG_PREFIX_(this->client_handle), errno, strerror(errno));
auto query_server = this->client_handle->getQueryServer();
this->readEvent = event_new(query_server->io_event_loop(), this->file_descriptor_, EV_READ | EV_PERSIST, [](int a1, short a2, void* _this) {
reinterpret_cast<QueryClientConnection*>(_this)->handle_event_read(a1, a2);
}, this);
this->writeEvent = event_new(query_server->io_event_loop(), this->file_descriptor_, EV_WRITE, [](int a1, short a2, void* _this){
reinterpret_cast<QueryClientConnection*>(_this)->handle_event_write(a1, a2);
}, this);
this->connection_state = ConnectionState::INITIALIZING;
if(ts::config::query::sslMode == 0) {
this->connection_state = ConnectionState::CONNECTED;
this->connection_type_ = ConnectionType::PLAIN_TEXT;
this->client_handle->handle_connection_initialized();
}
return true;
}
void QueryClientConnection::add_read_event() {
std::lock_guard elock{this->event_mutex};
if(this->readEvent) event_add(this->readEvent, nullptr);
}
void QueryClientConnection::finalize(bool is_destructor_call) {
auto old_state = this->connection_state;
this->connection_state = ConnectionState::DISCONNECTED;
/* unregister event handling */
{
std::unique_lock elock{this->event_mutex};
auto wevent = std::exchange(this->writeEvent, nullptr);
auto revent = std::exchange(this->readEvent, nullptr);
elock.unlock();
if(revent) {
if(thread_is_event_loop)
event_del_noblock(revent);
else
event_del_block(revent); /* may calls finalize() while we're waiting. But thats okey. */
event_free(revent);
}
if(wevent) {
if(thread_is_event_loop)
event_del_noblock(wevent);
else
event_del_block(wevent); /* may calls finalize() while we're waiting. But thats okey. */
event_free(wevent);
}
}
{
std::lock_guard block{this->buffer_lock};
/* Free the entire tail queue. */
while (auto buffer = TAILQ_FIRST(&this->write_queue)) {
TAILQ_REMOVE(&this->write_queue, buffer, tq);
free(buffer->original_ptr);
delete buffer;
}
TAILQ_INIT(&this->write_queue); /* just ensures a valid tailq */
::free(this->read_buffer.buffer);
this->read_buffer.buffer = nullptr;
this->read_buffer.length = 0;
this->read_buffer.fill_count = 0;
}
if(!is_destructor_call && old_state != ConnectionState::DISCONNECTED)
this->client_handle->handle_connection_finalized();
}
void QueryClientConnection::handle_event_read(int fd, short events) {
constexpr auto buffer_length{1024 * 4};
uint8_t buffer[buffer_length];
auto length = read(fd, (void *) buffer, buffer_length);
if (length <= 0) {
if (errno == EINTR || errno == EAGAIN)
return;
else if (length == 0) {
logMessage(LOG_QUERY, "{} Connection closed (r). Client disconnected.",
CLIENT_STR_LOG_PREFIX_(this->client_handle));
} else {
logError(LOG_QUERY, "{} Failed to read! Code: {} errno: {} message: {}",
CLIENT_STR_LOG_PREFIX_(this->client_handle), length, errno, strerror(errno));
}
event_del_noblock(this->readEvent);
this->close_connection(std::chrono::system_clock::time_point{});
return;
}
if (this->connection_type_ == ConnectionType::PLAIN_TEXT) {
plain_text_buffer_insert:
this->handle_decoded_message(buffer, length);
} else if (this->connection_type_ == ConnectionType::SSL_ENCRYPTED) {
ssl_buffer_insert:;
this->ssl_handler.process_incoming_data(pipes::buffer_view{(const char*) buffer, (size_t) length});;
} else {
if (config::query::sslMode != 0 && pipes::SSL::isSSLHeader(std::string{(const char *) buffer, (size_t) length})) {
if(!this->initialize_ssl()) return;
/*
* - Content
* \x16
* -Version (1)
* \x03 \x00
* - length (2)
* \x00 \x04
*
* - Header
* \x00 -> hello request (3)
* \x05 -> length (4)
*/
//this->writeRawMessage(string("\x16\x03\x01\x00\x05\x00\x00\x00\x00\x00", 10));
goto ssl_buffer_insert;
} else {
this->connection_type_ = ConnectionType::PLAIN_TEXT;
this->client_handle->handle_connection_initialized();
goto plain_text_buffer_insert;
}
}
}
void QueryClientConnection::handle_event_write(int fd, short events) {
bool readd_write{false};
if(events & EV_WRITE) {
/* Safe to access, because we're only reading the queue and the head could never change. Only within the IO loop itself. */
WriteBuffer* wbuffer;
while((wbuffer = TAILQ_FIRST(&this->write_queue))) {
auto written = send(fd, wbuffer->ptr, wbuffer->length, 0);
if(written <= 0) {
if(errno == EAGAIN) {
readd_write = true;
break;
}
if(written == 0) {
logMessage(LOG_QUERY, "{} Connection closed (w). Client disconnected.", CLIENT_STR_LOG_PREFIX_(this->client_handle));
} else {
logError(LOG_QUERY, "{} Failed to write! Code: {} errno: {} message: {}", CLIENT_STR_LOG_PREFIX_(this->client_handle), written, errno, strerror(errno));
}
event_del_noblock(this->readEvent);
this->close_connection(std::chrono::system_clock::time_point{});
return;
}
wbuffer->length -= written;
if(wbuffer->length == 0) {
std::lock_guard block{this->buffer_lock};
TAILQ_REMOVE(&this->write_queue, wbuffer, tq);
::free(wbuffer->original_ptr);
delete wbuffer;
} else {
wbuffer->ptr += written;
}
}
}
if(this->connection_state == ConnectionState::DISCONNECTING) {
if(!readd_write || (events & EV_TIMEOUT)) {
/* disconnect timeouted or nothing more to write */
this->finalize(false);
return;
} else /* if(readd_write) */ { /* check not needed because tested before already */
auto time_left = this->disconnect_timeout - std::chrono::system_clock::now();
timeval timeout{0, 1};
if(time_left.count() > 0) {
timeout.tv_sec = std::chrono::floor<std::chrono::seconds>(time_left).count();
timeout.tv_usec = std::chrono::floor<std::chrono::microseconds>(time_left).count() % 1000000ULL;
}
event_add(this->writeEvent, &timeout);
}
} else if(readd_write) {
event_add(this->writeEvent, nullptr);
}
}
bool QueryClientConnection::initialize_ssl() {
this->connection_type_ = ConnectionType::SSL_ENCRYPTED;
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_OUT, true);
this->ssl_handler.direct_process(pipes::PROCESS_DIRECTION_IN, true);
this->ssl_handler.callback_data([&](const pipes::buffer_view &buffer) {
this->handle_decoded_message(buffer.data_ptr<void>(), buffer.length());
});
this->ssl_handler.callback_write([&](const pipes::buffer_view &buffer) {
this->send_data_raw({buffer.data_ptr<char>(), buffer.length()});
});
this->ssl_handler.callback_initialized = [&] {
this->client_handle->handle_connection_initialized();
};
this->ssl_handler.callback_error([&](int code, const std::string& message) {
if(code == PERROR_SSL_ACCEPT) {
logError(LOG_QUERY, "{} Failed to initialize query ssl session ({})", CLIENT_STR_LOG_PREFIX_(this->client_handle), message);
this->close_connection(std::chrono::system_clock::time_point{});
} else if(code == PERROR_SSL_TIMEOUT) {
logError(LOG_QUERY, "{} Failed to initialize query ssl session (timeout: {})", CLIENT_STR_LOG_PREFIX_(this->client_handle), message);
this->close_connection(std::chrono::system_clock::time_point{});
} else
logError(LOG_QUERY, "{} Received SSL error ({} | {})", CLIENT_STR_LOG_PREFIX_(this->client_handle), code, message);
});
{
auto context = serverInstance->sslManager()->getQueryContext();
auto options = std::make_shared<pipes::SSL::Options>();
options->type = pipes::SSL::SERVER;
options->context_method = TLS_method();
options->default_keypair({context->privateKey, context->certificate});
if(!this->ssl_handler.initialize(options)) {
logError(LOG_QUERY, "[{}] Failed to setup ssl!", CLIENT_STR_LOG_PREFIX_(this->client_handle));
this->close_connection(std::chrono::system_clock::time_point{});
return false;
}
}
return true;
}
void QueryClientConnection::handle_decoded_message(const void *buffer, size_t size) {
{
std::lock_guard block{this->buffer_lock};
if((this->read_buffer.length - this->read_buffer.fill_count) < size) { /* !this->read_buffer.buffer is already implicitly implemented because by default read_buffer.length will be zero */
const auto new_size{this->read_buffer.length + size + 128};
auto new_buffer = ::malloc(new_size);
assert(new_buffer);
if(this->read_buffer.fill_count) memcpy(new_buffer, this->read_buffer.buffer, this->read_buffer.fill_count);
::free(this->read_buffer.buffer);
this->read_buffer.buffer = new_buffer;
this->read_buffer.length = new_size;
}
assert(this->read_buffer.buffer);
assert(this->read_buffer.length - this->read_buffer.fill_count >= size);
memcpy((char*) this->read_buffer.buffer + this->read_buffer.fill_count, buffer, size);
this->read_buffer.fill_count += size;
}
{
//TODO: Improve this command progress
auto qserver{this->client_handle->handle};
if(qserver) {
auto wlock{this->client_handle->_this};
qserver->executePool()->execute([wlock]() {
auto client{std::dynamic_pointer_cast<QueryClient>(wlock.lock())};
if(!client) return;
int counter = 0;
while(client->process_next_command() && counter++ < 15);
});
}
}
}
void QueryClientConnection::send_data(const std::string_view &buffer) {
if(this->connection_type_ == ConnectionType::PLAIN_TEXT)
this->send_data_raw(buffer);
else if(this->connection_type_ == ConnectionType::SSL_ENCRYPTED)
this->ssl_handler.send(pipes::buffer_view{buffer.data(), buffer.length()});
}
void QueryClientConnection::send_data_raw(const std::string_view &buffer) {
auto wbuf = new WriteBuffer{};
wbuf->original_ptr = (char*) malloc(buffer.length());
wbuf->ptr = wbuf->original_ptr;
memcpy(wbuf->ptr, buffer.data(), buffer.length());
wbuf->length = buffer.length();
{
std::lock_guard wlock{this->buffer_lock};
TAILQ_INSERT_TAIL(&this->write_queue, wbuf, tq);
}
{
std::lock_guard elock{this->event_mutex};
if(this->writeEvent)
event_add(this->writeEvent, nullptr);
}
}
void QueryClientConnection::close_connection(const std::chrono::system_clock::time_point &timeout) {
if(timeout.time_since_epoch().count() > 0) {
this->connection_state = ConnectionState::DISCONNECTING;
this->disconnect_timeout = timeout;
std::lock_guard elock{this->event_mutex};
if(this->writeEvent) {
event_add(this->writeEvent, nullptr);
return;
}
/* failed to add the write event, so call disconnect */
}
if(this->connection_state == ConnectionState::DISCONNECTED) return;
this->finalize(false);
}
void QueryClientConnection::enforce_text_connection() {
if(this->connection_state != ConnectionState::INITIALIZING) return;
this->connection_state = ConnectionState::CONNECTED;
this->connection_type_ = ConnectionType::PLAIN_TEXT;
this->client_handle->handle_connection_initialized();
}
CommandAssembleState QueryClientConnection::next_command(std::string &result) {
std::lock_guard block{this->buffer_lock};
auto new_line_idx = (char*) memchr(this->read_buffer.buffer, '\n', this->read_buffer.fill_count);
if(!new_line_idx) return CommandAssembleState::NO_COMMAND_PENDING;
const auto length = ((char*) this->read_buffer.buffer - new_line_idx) * sizeof(*new_line_idx);
auto line_length{length};
if(length > 0 && *(new_line_idx - 1) == '\r')
line_length--;
result.assign((char*) this->read_buffer.buffer, line_length);
//Do not copy the \r character
auto copy_bytes{this->read_buffer.fill_count - length};
if(copy_bytes > 0 && *(new_line_idx + 1) == '\r') {
copy_bytes--;
new_line_idx++;
}
memcpy(this->read_buffer.buffer, new_line_idx + 1, copy_bytes);
this->read_buffer.fill_count = copy_bytes;
return copy_bytes == 0 ? CommandAssembleState::SUCCESS : CommandAssembleState::MORE_COMMANDS_PENDING;
}
@@ -0,0 +1,98 @@
#pragma once
#include <chrono>
#include <event.h>
#include <deque>
#include <string>
#include <pipes/ssl.h>
#include <sys/queue.h>
namespace ts::server {
class QueryClient;
}
namespace ts::server::server::query {
enum struct ConnectionType {
UNKNOWN,
PLAIN_TEXT,
SSL_ENCRYPTED,
/* SSH */
};
enum struct ConnectionState {
INITIALIZING,
CONNECTED,
DISCONNECTING,
DISCONNECTED
};
enum struct CommandAssembleState {
SUCCESS,
MORE_COMMANDS_PENDING,
NO_COMMAND_PENDING
};
class QueryClientConnection {
public:
explicit QueryClientConnection(QueryClient* /* client */, int /* file descriptor */);
~QueryClientConnection();
[[nodiscard]] inline ConnectionType connection_type() const { return this->connection_type_; }
bool initialize(std::string& /* error */);
void add_read_event();
void finalize(bool /* is destructor call */);
void send_data(const std::string_view& /* payload */);
void send_data_raw(const std::string_view& /* payload */);
void enforce_text_connection();
[[nodiscard]] CommandAssembleState next_command(std::string& /* command */);
/* could be called from every thread (event IO thread) */
void close_connection(const std::chrono::system_clock::time_point& /* disconnect timeout */);
private:
struct WriteBuffer {
char* original_ptr;
char* ptr;
size_t length;
TAILQ_ENTRY(WriteBuffer) tq;
};
QueryClient* client_handle{nullptr};
ConnectionState connection_state{ConnectionState::INITIALIZING};
std::chrono::system_clock::time_point disconnect_timeout{};
ConnectionType connection_type_{ConnectionType::UNKNOWN};
int file_descriptor_{-1};
/* only delete the events within the event loop! */
std::mutex event_mutex{};
::event* readEvent{nullptr};
::event* writeEvent{nullptr};
pipes::SSL ssl_handler{};
std::mutex buffer_lock{};
struct {
void* buffer{nullptr};
size_t length{0};
size_t fill_count{0};
std::chrono::system_clock::time_point last_shrink{};
} read_buffer;
TAILQ_HEAD(, WriteBuffer) write_queue{};
void handle_event_write(int, short);
void handle_event_read(int, short);
bool initialize_ssl();
void handle_decoded_message(const void* /* message */, size_t /* length */);
};
}

Some files were not shown because too many files have changed in this diff Show More