Compare commits

..

No commits in common. "new-groups" and "1.4.10-openssl" have entirely different histories.

221 changed files with 18670 additions and 32080 deletions

View File

@ -1,66 +0,0 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: 0
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignOperands: true
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never

6
.gitmodules vendored
View File

@ -8,9 +8,3 @@
[submodule "music"]
path = music
url = https://github.com/TeaSpeak/TeaMusic-Providers.git
[submodule "rtclib"]
path = rtclib
url = https://git.did.science/TeaSpeak/Server/rtc.git
[submodule "file-rust"]
path = file-rust
url = https://git.did.science/TeaSpeak/Server/teafile.git

View File

@ -8,9 +8,7 @@ set(TEASPEAK_SERVER ON)
#end now
#set(MEMORY_DEBUG_FLAGS " -fsanitize=leak -fsanitize=address -fstack-protector-all ")
#set(MEMORY_DEBUG_FLAGS "-fsanitize=address -fstack-protector-all")
#set(MEMORY_DEBUG_FLAGS "-fstack-protector-all")
#set(MEMORY_DEBUG_FLAGS " -fsanitize=address -static-libasan")
#set(MEMORY_DEBUG_FLAGS "-fsanitize=address")
if (NOT BUILD_OS_ARCH)
set(BUILD_OS_ARCH $ENV{build_os_arch})
@ -25,6 +23,7 @@ if (BUILD_INCLUDE_FILE)
include(${BUILD_INCLUDE_FILE})
endif ()
set(CMAKE_PREFIX_PATH "/home/wolverindev/clib/qt/5.6.1/5.6/gcc_64/lib/cmake")
set(LIBEVENT_PATH "${LIBRARY_PATH}/event/build/lib/")
function(resolve_library VARIABLE FALLBACK PATHS)
@ -56,13 +55,12 @@ find_package(CXXTerminal REQUIRED)
find_package(StringVariable REQUIRED)
find_package(yaml-cpp REQUIRED)
find_package(jsoncpp REQUIRED)
find_package(Ed25519 REQUIRED)
find_package(DataPipes REQUIRED)
find_package(Opus REQUIRED)
find_package(spdlog REQUIRED)
find_package(Jemalloc REQUIRED)
find_package(Protobuf REQUIRED)
message("${zstd_DIR}")
find_package(zstd REQUIRED)
include_directories(${StringVariable_INCLUDE_DIR})
add_subdirectory(music/)
@ -94,6 +92,7 @@ endif()
#FIXME: Use module for this
include_directories(${breakpad_INCLUDE_DIR})
include_directories(${ed25519_INCLUDE_DIR})
include_directories(${ThreadPool_INCLUDE_DIR})
include_directories(${DataPipes_INCLUDE_DIR})
include_directories(${LIBEVENT_INCLUDE_DIRS})

@ -1 +0,0 @@
Subproject commit 29f95794358201811692f7e7b60f78fe9605f2a1

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.6)
project(TeaSpeak-Files)
#set(CMAKE_CXX_STANDARD 20)
#set(CMAKE_CXX_STANDARD 17)
#set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(TeaSpeak-FileServer STATIC
@ -12,36 +12,21 @@ add_library(TeaSpeak-FileServer STATIC
local_server/LocalFileTransferDisk.cpp
local_server/LocalFileTransferNetwork.cpp
local_server/clnpath.cpp
local_server/NetTools.cpp
local_server/Config.cpp
local_server/HTTPUtils.cpp
)
add_library(TeaSpeak-FileRust STATIC
local_rust/provider.cpp
local_rust/lib.cpp
)
target_include_directories(TeaSpeak-FileRust PUBLIC include/)
target_link_libraries(TeaSpeak-FileRust PUBLIC TeaSpeak ${CMAKE_SOURCE_DIR}/file-rust/target/debug/libteaspeak_file_capi.so)
# target_link_directories(TeaSpeak-FileRust PUBLIC ${CMAKE_SOURCE_DIR}/file-rust/target/debug/)
target_link_libraries(TeaSpeak-FileServer PUBLIC TeaSpeak ${StringVariable_LIBRARIES_STATIC} stdc++fs
libevent::core libevent::pthreads
# We're not linking this here, since we may later use DataPipes::shared linking
# DataPipes::core::static
DataPipes::core::static
openssl::ssl::shared
openssl::crypto::shared
)
target_include_directories(TeaSpeak-FileServer PUBLIC include/)
target_compile_options(TeaSpeak-FileServer PUBLIC "-Wswitch-enum")
target_compile_features(TeaSpeak-FileServer PUBLIC cxx_std_20)
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
DataPipes::core::static
CXXTerminal::static #Static
stdc++fs
)
target_compile_options(TeaSpeak-FileServerTest PUBLIC -static-libgcc -static-libstdc++)

View File

@ -1,13 +0,0 @@
#pragma once
#include <memory>
#include <pipes/ssl.h>
namespace ts::server::file::config {
enum struct Key {
SSL_OPTION_SUPPLIER
};
extern void value_updated(Key /* value */);
extern std::function<std::shared_ptr<pipes::SSL::Options>()> ssl_option_supplier;
}

View File

@ -1,77 +0,0 @@
#pragma once
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 auto response() const -> const response_t& { 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;
};
}

View File

@ -2,24 +2,88 @@
#include <string>
#include <chrono>
#include <utility>
#include <Definitions.h>
#include <condition_variable>
#include <variant>
#include <deque>
#include <functional>
#include <atomic>
#include <condition_variable>
#include "./ExecuteResponse.h"
#define TRANSFER_KEY_LENGTH (32)
#define TRANSFER_MEDIA_BYTES_LENGTH (32)
namespace ts::server::file {
class VirtualFileServer;
enum struct ExecuteStatus {
UNKNOWN,
WAITING,
SUCCESS,
ERROR
};
typedef uint64_t ChannelId;
typedef uint64_t ServerId;
typedef uint64_t ClientId;
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>
@ -35,9 +99,11 @@ namespace ts::server::file {
PATH_EXCEEDS_ROOT_PATH,
PATH_IS_A_FILE,
PATH_DOES_NOT_EXISTS,
FAILED_TO_LIST_FILES
FAILED_TO_LIST_FILES,
MAX
};
constexpr std::array<std::string_view, 5> directory_query_error_messages = {
constexpr std::array<std::string_view, (int) DirectoryQueryErrorType::MAX> directory_query_error_messages = {
"unknown error",
"path exceeds base path",
"path is a file",
@ -58,8 +124,7 @@ namespace ts::server::file {
std::string name{};
std::chrono::system_clock::time_point modified_at{};
size_t size{0}; /* file only */
bool empty{false}; /* directory only */
size_t size{0};
};
enum struct DirectoryModifyErrorType {
@ -78,39 +143,11 @@ namespace ts::server::file {
TARGET_PATH_ALREADY_EXISTS,
FAILED_TO_DELETE_FILES,
FAILED_TO_RENAME_FILE,
FAILED_TO_CREATE_DIRECTORIES,
SOME_FILES_ARE_LOCKED
};
typedef DetailedError<FileModifyErrorType> FileModifyError;
enum struct FileDeleteErrorType {
UNKNOWN,
};
typedef DetailedError<FileDeleteErrorType> FileDeleteError;
struct FileDeleteResponse {
enum struct StatusType {
SUCCESS,
PATH_EXCEEDS_ROOT_PATH,
PATH_DOES_NOT_EXISTS,
FAILED_TO_DELETE_FILES,
SOME_FILES_ARE_LOCKED
};
struct DeleteResult {
StatusType status{StatusType::SUCCESS};
std::string error_detail{};
DeleteResult(StatusType status, std::string errorDetail) : status{status},
error_detail{std::move(errorDetail)} {}
DeleteResult() = default;
};
std::vector<DeleteResult> delete_results{};
};
enum struct ServerCommandErrorType {
UNKNOWN,
FAILED_TO_CREATE_DIRECTORIES,
@ -118,77 +155,43 @@ namespace ts::server::file {
};
typedef DetailedError<ServerCommandErrorType> ServerCommandError;
struct FileInfoResponse {
enum struct StatusType {
SUCCESS,
PATH_EXCEEDS_ROOT_PATH,
PATH_DOES_NOT_EXISTS,
FAILED_TO_QUERY_INFO,
UNKNOWN_FILE_TYPE
};
struct FileInfo {
StatusType status{StatusType::SUCCESS};
std::string error_detail{};
DirectoryEntry info{};
FileInfo(StatusType status, std::string errorDetail, DirectoryEntry info) : status{status},
error_detail{std::move(errorDetail)}, info{std::move(info)} {}
FileInfo() = default;
};
std::vector<FileInfo> file_info{};
};
enum struct FileInfoErrorType {
UNKNOWN,
};
typedef DetailedError<FileInfoErrorType> FileInfoError;
class AbstractProvider {
public:
typedef ExecuteResponse<DirectoryQueryError, std::deque<DirectoryEntry>> directory_query_response_t;
/* server */
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
[[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<ExecuteResponse<FileInfoError, FileInfoResponse>> query_channel_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* files */) = 0;
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<DirectoryModifyError>> create_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_channel_files(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::vector<std::string>& /* paths */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> rename_channel_file(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */, ChannelId /* target channel */, const std::string& /* target */) = 0;
[[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<ExecuteResponse<FileInfoError, FileInfoResponse>> query_icon_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_icons(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
[[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<ExecuteResponse<FileInfoError, FileInfoResponse>> query_avatar_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_avatars(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
[[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 uint16_t transfer_id;
typedef uint32_t transfer_id;
struct Transfer {
transfer_id server_transfer_id{0};
transfer_id client_transfer_id{0};
std::shared_ptr<VirtualFileServer> server{nullptr};
ServerId server_id{0};
ClientId client_id{0};
ChannelId channel_id{0};
ClientId client_id{0};
std::string client_unique_id{};
std::string transfer_key{};
char transfer_key[TRANSFER_KEY_LENGTH]{};
std::chrono::system_clock::time_point initialized_timestamp{};
enum Direction {
DIRECTION_UNKNOWN,
@ -209,10 +212,6 @@ namespace ts::server::file {
TARGET_TYPE_AVATAR
} target_type{TARGET_TYPE_UNKNOWN};
std::string target_file_path{};
std::string absolute_file_path{};
std::string relative_file_path{};
std::string file_name{};
int64_t max_bandwidth{-1};
size_t expected_file_size{0}; /* incl. the offset! */
@ -233,31 +232,13 @@ namespace ts::server::file {
size_t file_start_offset{0};
size_t file_current_offset{0};
size_t file_total_size{0};
double average_speed{0};
double current_speed{0};
};
struct TransferInitError {
enum Type {
UNKNOWN,
INVALID_FILE_TYPE,
FILE_DOES_NOT_EXISTS,
FILE_IS_NOT_A_FILE,
CLIENT_TOO_MANY_TRANSFERS,
SERVER_TOO_MANY_TRANSFERS,
SERVER_QUOTA_EXCEEDED,
CLIENT_QUOTA_EXCEEDED,
IO_ERROR
UNKNOWN
} error_type{UNKNOWN};
std::string error_message{};
TransferInitError(Type errorType, std::string errorMessage) : error_type{errorType},
error_message{std::move(errorMessage)} {}
};
struct TransferActionError {
@ -282,142 +263,44 @@ namespace ts::server::file {
NETWORK_IO_ERROR,
UNEXPECTED_CLIENT_DISCONNECT,
UNEXPECTED_DISK_EOF,
USER_REQUEST
UNEXPECTED_DISK_EOF
} error_type{UNKNOWN};
std::string error_message{};
};
struct ActiveFileTransfer {
transfer_id client_transfer_id{0};
transfer_id server_transfer_id{0};
Transfer::Direction direction{Transfer::DIRECTION_UNKNOWN};
ClientId client_id{};
std::string client_unique_id{};
std::string file_path{};
std::string file_name{};
size_t expected_size{};
size_t size_done{};
enum Status {
NOT_STARTED,
RUNNING,
INACTIVE /* (not used yet) */
} status{Status::NOT_STARTED};
std::chrono::milliseconds runtime{};
double average_speed{0};
double current_speed{0};
};
enum struct TransferListError {
UNKNOWN
};
class AbstractProvider {
public:
struct TransferInfo {
std::string file_path{};
std::string client_unique_id{};
ClientId client_id{};
bool override_exiting{false}; /* only for upload valid */
size_t file_offset{0};
size_t expected_file_size{0};
int64_t max_bandwidth{-1};
int64_t max_concurrent_transfers{-1};
/* only used for upload, for download the quotas could be checked before */
size_t download_server_quota_limit{(size_t) -1};
size_t download_client_quota_limit{(size_t) -1};
};
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_channel_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, ChannelId /* channel */, const TransferInfo& /* info */) = 0;
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<TransferInitError, std::shared_ptr<Transfer>>>
initialize_icon_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, const TransferInfo& /* info */) = 0;
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_avatar_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, const TransferInfo& /* info */) = 0;
virtual std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> list_transfer() = 0;
virtual std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(const std::shared_ptr<VirtualFileServer>& /* server */, transfer_id /* id */, bool /* flush */) = 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{};
std::function<void(const std::shared_ptr<Transfer>&, const transfer::TransferStatistics&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transferring the data */
};
}
class VirtualFileServer {
public:
explicit VirtualFileServer(ServerId server_id, std::string unique_id) : server_id_{server_id}, unique_id_{std::move(unique_id)} {}
[[nodiscard]] inline auto unique_id() const -> const std::string& { return this->unique_id_; }
[[nodiscard]] inline auto server_id() const -> ServerId { return this->server_id_; }
[[nodiscard]] inline auto max_networking_upload_bandwidth() const -> int64_t { return this->max_networking_upload_bandwidth_; }
virtual void max_networking_upload_bandwidth(int64_t value) {
this->max_networking_upload_bandwidth_ = value;
}
[[nodiscard]] inline auto max_networking_download_bandwidth() const -> int64_t { return this->max_networking_download_bandwidth_; }
virtual void max_networking_download_bandwidth(int64_t value) {
this->max_networking_download_bandwidth_ = value;
}
[[nodiscard]] inline auto generate_transfer_id() {
return ++this->current_transfer_id;
}
private:
ServerId server_id_;
std::string unique_id_;
int64_t max_networking_upload_bandwidth_{-1};
int64_t max_networking_download_bandwidth_{-1};
std::atomic<transfer::transfer_id> current_transfer_id{0};
};
class AbstractFileServer {
public:
[[nodiscard]] virtual std::string file_base_path() const = 0;
[[nodiscard]] virtual filesystem::AbstractProvider& file_system() = 0;
[[nodiscard]] virtual transfer::AbstractProvider& file_transfer() = 0;
[[nodiscard]] inline auto virtual_servers() const -> std::deque<std::shared_ptr<VirtualFileServer>> {
std::lock_guard slock{this->servers_mutex};
return this->servers_;
}
[[nodiscard]] inline auto find_virtual_server(ServerId server_id) const -> std::shared_ptr<VirtualFileServer> {
std::lock_guard slock{this->servers_mutex};
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
return server->server_id() == server_id;
});
return it == this->servers_.end() ? nullptr : *it;
}
virtual std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) = 0;
virtual void unregister_server(ServerId /* server id */, bool /* delete tiles */) = 0;
protected:
mutable std::mutex servers_mutex{};
std::deque<std::shared_ptr<VirtualFileServer>> servers_{};
private:
};
extern bool initialize(std::string& /* error */, const std::string& /* host names */, uint16_t /* port */);
extern bool initialize(std::string& /* error */);
extern void finalize();
extern std::shared_ptr<AbstractFileServer> server();

View File

@ -1,54 +0,0 @@
//
// Created by WolverinDEV on 20/04/2021.
//
#pragma once
#include <cstdint>
#include <cstddef>
#ifdef __cplusplus
extern "C" {
#endif
/* Attention: Do not call any libteaspeak_file_* functions within being in the callback! */
struct TeaFileNativeCallbacks {
uint32_t version;
void(*log)(uint8_t /* level */, const void* /* callback data */, const char* /* message */, uint32_t /* length */);
};
struct TeaFilePath {
uint32_t type;
uint64_t channel_id;
const char* path;
};
struct TeaFilePathInfo {
int32_t query_result;
uint32_t file_type;
const char* name;
uint64_t modify_timestamp;
uint64_t file_size;
bool directory_empty;
};
extern const char* libteaspeak_file_version();
extern void libteaspeak_free_str(const char* /* ptr */);
extern const char* libteaspeak_file_initialize(const TeaFileNativeCallbacks* /* */, size_t /* size of the callback struct */);
extern void libteaspeak_file_finalize();
extern void libteaspeak_file_system_register_server(const char* /* server unique id */);
extern void libteaspeak_file_system_unregister_server(const char* /* server unique id */, bool /* delete files */);
extern void libteaspeak_file_free_file_info(const TeaFilePathInfo*);
extern const char* libteaspeak_file_system_query_file_info(const char* /* server unique id */, const TeaFilePath* /* file path */, size_t /* path count */, const TeaFilePathInfo ** /* result */);
extern const char* libteaspeak_file_system_query_directory(const char* /* server unique id */, const TeaFilePath* /* file path */, const TeaFilePathInfo ** /* result */);
extern const char* libteaspeak_file_system_delete_files(const char* /* server unique id */, const TeaFilePath* /* file path */, size_t /* path count */, const TeaFilePathInfo ** /* result */);
extern uint32_t libteaspeak_file_system_create_channel_directory(const char* /* server unique id */, uint32_t /* channel id */, const char* /* path */);
extern uint32_t libteaspeak_file_system_rename_channel_file(const char* /* server unique id */, uint32_t /* old channel id */, const char* /* old path */, uint32_t /* new channel id */, const char* /* new path */);
#ifdef __cplusplus
}
#endif

View File

@ -1,6 +0,0 @@
//
// Created by WolverinDEV on 20/04/2021.
//
#include "./lib.h"
#include "./imports.h"

View File

@ -1,5 +0,0 @@
//
// Created by WolverinDEV on 20/04/2021.
//
#pragma once

View File

@ -1,587 +0,0 @@
//
// Created by WolverinDEV on 20/04/2021.
//
#include <cassert>
#include <log/LogUtils.h>
#include "./provider.h"
#include "./imports.h"
using namespace ts::server;
using namespace ts::server::file;
using namespace ts::server::file::transfer;
using namespace ts::server::file::filesystem;
RustFileSystem::~RustFileSystem() = default;
std::shared_ptr<ExecuteResponse<ServerCommandError>> RustFileSystem::initialize_server(
const std::shared_ptr<VirtualFileServer> &) {
/* Nothing to do. */
auto response = this->create_execute_response<ServerCommandError>();
response->emplace_success();
return response;
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::query_channel_info(
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::tuple<ChannelId, std::string>> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& [ channel_id, path ] : files) {
file_paths.push_back(TeaFilePath{
.type = 0,
.channel_id = channel_id,
.path = path.c_str()
});
}
return this->execute_info(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::query_channel_directory(
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId channelId, const std::string &directory) {
TeaFilePath path{
.type = 0,
.channel_id = channelId,
.path = directory.c_str()
};
return this->execute_query(virtual_server->unique_id().c_str(), &path);
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::query_icon_info(
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& path : files) {
file_paths.push_back(TeaFilePath{
.type = 1,
.channel_id = 0,
.path = path.c_str()
});
}
return this->execute_info(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::query_icon_directory(
const std::shared_ptr<VirtualFileServer> &virtual_server) {
TeaFilePath path{
.type = 1,
.channel_id = 0,
.path = nullptr
};
return this->execute_query(virtual_server->unique_id().c_str(), &path);
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::query_avatar_info(
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& path : files) {
file_paths.push_back(TeaFilePath{
.type = 2,
.channel_id = 0,
.path = path.c_str()
});
}
return this->execute_info(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::query_avatar_directory(
const std::shared_ptr<VirtualFileServer> &virtual_server) {
TeaFilePath path{
.type = 2,
.channel_id = 0,
.path = nullptr
};
return this->execute_query(virtual_server->unique_id().c_str(), &path);
}
inline void path_info_to_directory_entry(DirectoryEntry& target, const TeaFilePathInfo& info) {
switch (info.file_type) {
case 1:
target.type = DirectoryEntry::FILE;
break;
case 2:
target.type = DirectoryEntry::DIRECTORY;
break;
default:
target.type = DirectoryEntry::UNKNOWN;
return;
}
assert(info.name);
target.name = std::string{info.name};
target.modified_at = std::chrono::system_clock::time_point{} + std::chrono::seconds{info.modify_timestamp};
target.size = info.file_size;
target.empty = info.directory_empty;
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::execute_info(const char *server_unique_id, void *paths,
size_t paths_count) {
auto response = this->create_execute_response<FileInfoError, FileInfoResponse>();
const TeaFilePathInfo* result{nullptr};
auto error_ptr = libteaspeak_file_system_query_file_info(server_unique_id, (const TeaFilePath*) paths, paths_count, &result);
if(error_ptr) {
libteaspeak_file_free_file_info(result);
response->emplace_fail(FileInfoErrorType::UNKNOWN, error_ptr);
libteaspeak_free_str(error_ptr);
} else {
assert(result);
std::vector<FileInfoResponse::FileInfo> file_infos{};
file_infos.reserve(paths_count);
auto info_ptr{result};
while(info_ptr->query_result >= 0) {
auto& info = file_infos.emplace_back();
switch (info_ptr->query_result) {
case 0:
/* success */
info.status = FileInfoResponse::StatusType::SUCCESS;
path_info_to_directory_entry(info.info, *info_ptr);
break;
case 1:
info.status = FileInfoResponse::StatusType::PATH_EXCEEDS_ROOT_PATH;
break;
case 2:
info.status = FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS;
break;
case 3:
info.status = FileInfoResponse::StatusType::UNKNOWN_FILE_TYPE;
break;
default:
info.status = FileInfoResponse::StatusType::FAILED_TO_QUERY_INFO;
info.error_detail = std::string{"invalid query result "} + std::to_string(info_ptr->query_result);
break;
}
info_ptr++;
}
assert(file_infos.size() == paths_count);
libteaspeak_file_free_file_info(result);
response->emplace_success(FileInfoResponse{file_infos});
}
return response;
}
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::execute_query(const char *server_unique_id, void *path) {
auto response = this->create_execute_response<DirectoryQueryError, std::deque<DirectoryEntry>>();
const TeaFilePathInfo* result{nullptr};
auto error_ptr = libteaspeak_file_system_query_directory(server_unique_id, (const TeaFilePath*) path, &result);
if(error_ptr) {
libteaspeak_file_free_file_info(result);
response->emplace_fail(DirectoryQueryErrorType::UNKNOWN, error_ptr);
libteaspeak_free_str(error_ptr);
} else {
assert(result);
/* result->query_result could be zero or minus one in both cases we're iterating the query result */
if(result->query_result > 0) {
/* An error occurred */
switch(result->query_result) {
case 1:
response->emplace_fail(DirectoryQueryErrorType::PATH_EXCEEDS_ROOT_PATH, "");
break;
case 2:
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
break;
case 4:
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
break;
case 5:
response->emplace_fail(DirectoryQueryErrorType::UNKNOWN, "io error");
break;
default:
response->emplace_fail(DirectoryQueryErrorType::UNKNOWN, std::string{"unknown error result "} + std::to_string(result->query_result));
break;
}
} else {
std::deque<DirectoryEntry> entries{};
auto info_ptr{result};
while(info_ptr->query_result >= 0) {
assert(info_ptr->query_result == 0);
path_info_to_directory_entry(entries.emplace_back(), *info_ptr);
info_ptr++;
}
response->emplace_success(std::move(entries));
}
libteaspeak_file_free_file_info(result);
}
return response;
}
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::delete_channel_files(
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId channel_id, const std::vector<std::string> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& path : files) {
file_paths.push_back(TeaFilePath{
.type = 0,
.channel_id = channel_id,
.path = path.c_str()
});
}
return this->execute_delete(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::delete_icons(
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& path : files) {
file_paths.push_back(TeaFilePath{
.type = 1,
.channel_id = 0,
.path = path.c_str()
});
}
return this->execute_delete(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::delete_avatars(
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& path : files) {
file_paths.push_back(TeaFilePath{
.type = 2,
.channel_id = 0,
.path = path.c_str()
});
}
return this->execute_delete(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
inline void path_info_to_delete_result(FileDeleteResponse::DeleteResult& result, const TeaFilePathInfo& info) {
switch (info.query_result) {
case 0:
result.status = FileDeleteResponse::StatusType::SUCCESS;
break;
case 1:
result.status = FileDeleteResponse::StatusType::PATH_EXCEEDS_ROOT_PATH;
break;
case 2:
result.status = FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS;
break;
case 5:
result.status = FileDeleteResponse::StatusType::FAILED_TO_DELETE_FILES;
break;
case 6:
result.status = FileDeleteResponse::StatusType::SOME_FILES_ARE_LOCKED;
break;
default:
result.status = FileDeleteResponse::StatusType::FAILED_TO_DELETE_FILES;
result.error_detail = std::string{"unknown delete error "} + std::to_string(info.query_result);
break;
}
}
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::execute_delete(const char *server_unique_id, void *paths,
size_t paths_count) {
auto response = this->create_execute_response<FileDeleteError, FileDeleteResponse>();
const TeaFilePathInfo* result{nullptr};
auto error_ptr = libteaspeak_file_system_delete_files(server_unique_id, (const TeaFilePath*) paths, paths_count, &result);
if(error_ptr) {
libteaspeak_file_free_file_info(result);
response->emplace_fail(FileDeleteErrorType::UNKNOWN, error_ptr);
libteaspeak_free_str(error_ptr);
} else {
assert(result);
std::vector<FileDeleteResponse::DeleteResult> results{};
results.reserve(paths_count);
auto info_ptr{result};
while(info_ptr->query_result >= 0) {
path_info_to_delete_result(results.emplace_back(), *info_ptr);
info_ptr++;
}
assert(results.size() == paths_count);
libteaspeak_file_free_file_info(result);
response->emplace_success(FileDeleteResponse{results});
}
return response;
}
std::shared_ptr<ExecuteResponse<DirectoryModifyError>> RustFileSystem::create_channel_directory(
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId channelId, const std::string &path) {
auto response = this->create_execute_response<DirectoryModifyError>();
auto result = libteaspeak_file_system_create_channel_directory(virtual_server->unique_id().c_str(), channelId, path.c_str());
switch (result) {
case 0:
response->emplace_success();
break;
case 1:
response->emplace_fail(DirectoryModifyErrorType::UNKNOWN, "server not found");
break;
case 2:
response->emplace_fail(DirectoryModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
break;
case 3:
response->emplace_fail(DirectoryModifyErrorType::PATH_ALREADY_EXISTS, "");
break;
case 4:
response->emplace_fail(DirectoryModifyErrorType::FAILED_TO_CREATE_DIRECTORIES, "");
break;
default:
response->emplace_fail(DirectoryModifyErrorType::UNKNOWN, std::string{"invalid return code "} + std::to_string(result));
break;
}
return response;
}
std::shared_ptr<ExecuteResponse<FileModifyError>> RustFileSystem::rename_channel_file(
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId old_channel_id, const std::string &old_path, ChannelId new_channel_id,
const std::string &new_path) {
auto response = this->create_execute_response<FileModifyError>();
auto result = libteaspeak_file_system_rename_channel_file(virtual_server->unique_id().c_str(), old_channel_id, old_path.c_str(), new_channel_id, new_path.c_str());
switch (result) {
case 0:
response->emplace_success();
break;
case 1:
response->emplace_fail(FileModifyErrorType::UNKNOWN, "server not found");
break;
case 2:
response->emplace_fail(FileModifyErrorType::UNKNOWN, "invalid path types");
break;
case 3:
response->emplace_fail(FileModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
break;
case 4:
response->emplace_fail(FileModifyErrorType::TARGET_PATH_EXCEEDS_ROOT_PATH, "");
break;
case 5:
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
break;
case 6:
response->emplace_fail(FileModifyErrorType::SOME_FILES_ARE_LOCKED, "");
break;
case 7:
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
break;
case 8:
response->emplace_fail(FileModifyErrorType::FAILED_TO_RENAME_FILE, "");
break;
default:
response->emplace_fail(FileModifyErrorType::UNKNOWN, std::string{"invalid return code "} + std::to_string(result));
break;
}
return response;
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
RustFileTransfer::initialize_channel_transfer(transfer::Transfer::Direction direction,
const std::shared_ptr<VirtualFileServer> &ptr, ChannelId id,
const transfer::AbstractProvider::TransferInfo &info) {
auto result = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
result->emplace_fail(TransferInitError::Type::UNKNOWN, "");
return result;
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
RustFileTransfer::initialize_icon_transfer(transfer::Transfer::Direction direction,
const std::shared_ptr<VirtualFileServer> &ptr,
const transfer::AbstractProvider::TransferInfo &info) {
auto result = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
result->emplace_fail(TransferInitError::Type::UNKNOWN, "");
return result;
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
RustFileTransfer::initialize_avatar_transfer(transfer::Transfer::Direction direction,
const std::shared_ptr<VirtualFileServer> &ptr,
const transfer::AbstractProvider::TransferInfo &info) {
auto result = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
result->emplace_fail(TransferInitError::Type::UNKNOWN, "");
return result;
}
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>>
RustFileTransfer::list_transfer() {
auto result = this->create_execute_response<TransferListError, std::vector<ActiveFileTransfer>>();
result->emplace_fail(TransferListError::UNKNOWN);
return result;
}
std::shared_ptr<ExecuteResponse<TransferActionError>>
RustFileTransfer::stop_transfer(const std::shared_ptr<VirtualFileServer> &ptr, transfer::transfer_id id,
bool b) {
auto result = this->create_execute_response<TransferActionError>();
result->emplace_fail(TransferActionError{TransferActionError::Type::UNKNOWN_TRANSFER});
return result;
}
RustFileProvider::RustFileProvider() :
file_system_{new RustFileSystem{}},
file_transfer_{new RustFileTransfer{}}
{}
RustFileProvider::~RustFileProvider() {
delete this->file_transfer_;
delete this->file_system_;
}
std::string RustFileProvider::file_base_path() const {
return "TODO!"; /* FIXME! */
}
filesystem::AbstractProvider & RustFileProvider::file_system() {
return *this->file_system_;
}
transfer::AbstractProvider & RustFileProvider::file_transfer() {
return *this->file_transfer_;
}
std::shared_ptr<VirtualFileServer> RustFileProvider::register_server(ServerId server_id) {
auto server = this->find_virtual_server(server_id);
if(server) return server;
server = std::make_shared<file::RustVirtualFileServer>(server_id, std::to_string(server_id));
libteaspeak_file_system_register_server(server->unique_id().c_str());
{
std::lock_guard slock{this->servers_mutex};
this->servers_.push_back(server);
}
return server;
}
void RustFileProvider::unregister_server(ServerId server_id, bool delete_files) {
auto server_unique_id = std::to_string(server_id);
std::shared_ptr<VirtualFileServer> server{};
{
std::lock_guard slock{this->servers_mutex};
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
return server->unique_id() == server_unique_id;
});
if(it == this->servers_.end()) {
return;
}
server = *it;
this->servers_.erase(it);
}
libteaspeak_file_system_unregister_server(server->unique_id().c_str(), delete_files);
}
void RustVirtualFileServer::max_networking_download_bandwidth(int64_t value) {
VirtualFileServer::max_networking_download_bandwidth(value);
}
void RustVirtualFileServer::max_networking_upload_bandwidth(int64_t value) {
VirtualFileServer::max_networking_upload_bandwidth(value);
}
#define log(...) \
switch(level) { \
case 0: \
logTrace(__VA_ARGS__); \
break; \
case 1: \
debugMessage(__VA_ARGS__); \
break; \
case 2: \
logMessage(__VA_ARGS__); \
break; \
case 3: \
logWarning(__VA_ARGS__); \
break; \
case 4: \
logError(__VA_ARGS__); \
break; \
case 5: \
logCritical(__VA_ARGS__); \
break; \
default: \
debugMessage(__VA_ARGS__); \
break; \
}
void libteaspeak_file_callback_log(uint8_t level, const void* callback_data_ptr, const char* message_ptr, uint32_t length) {
std::string_view message{message_ptr, length};
log(LOG_GENERAL, "{}", message);
}
#undef log
static TeaFileNativeCallbacks native_callbacks{
.version = 1,
.log = libteaspeak_file_callback_log,
};
std::shared_ptr<AbstractFileServer> file_instance{};
bool file::initialize(std::string& error, const std::string& host_names, uint16_t port) {
logMessage(LOG_FT, "Initializing file server with version {}", libteaspeak_file_version());
auto error_ptr = libteaspeak_file_initialize(&native_callbacks, sizeof native_callbacks);
if(error_ptr) {
error = error_ptr;
libteaspeak_free_str(error_ptr);
return false;
}
/* FIXME: Start server */
file_instance = std::make_shared<RustFileProvider>();
return true;
}
void file::finalize() {
file_instance = nullptr;
libteaspeak_file_finalize();
}
std::shared_ptr<AbstractFileServer> file::server() {
return file_instance;
}

View File

@ -1,123 +0,0 @@
//
// Created by WolverinDEV on 20/04/2021.
//
#pragma once
#include <files/FileServer.h>
namespace ts::server::file {
namespace filesystem {
class RustFileSystem : public filesystem::AbstractProvider {
using FileModifyError = filesystem::FileModifyError;
using DirectoryModifyError = filesystem::DirectoryModifyError;
public:
virtual ~RustFileSystem();
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> & /* server */) override;
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_channel_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* names */) override;
std::shared_ptr<directory_query_response_t>
query_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
create_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
delete_channel_files(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::vector<std::string> &string) override;
std::shared_ptr<ExecuteResponse<FileModifyError>>
rename_channel_file(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &, ChannelId, const std::string &) override;
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_icon_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> & id) override;
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
delete_icons(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_avatar_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> & id) override;
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
delete_avatars(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
private:
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<FileInfoError, FileInfoResponse>> execute_info(const char* /* server unique id */, void* /* query */, size_t /* query count */);
std::shared_ptr<directory_query_response_t> execute_query(const char* /* server unique id */, void* /* directory */);
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> execute_delete(const char* /* server unique id */, void* /* files */, size_t /* file count */);
std::mutex result_notify_mutex{};
std::condition_variable result_notify_cv{};
};
}
namespace transfer {
class RustFileTransfer : public AbstractProvider {
public:
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_channel_transfer(Transfer::Direction direction,
const std::shared_ptr<VirtualFileServer> &ptr, ChannelId id,
const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &ptr,
const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_avatar_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &ptr,
const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>>
list_transfer() override;
std::shared_ptr<ExecuteResponse<TransferActionError>>
stop_transfer(const std::shared_ptr<VirtualFileServer> &ptr, transfer_id id, bool b) override;
private:
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::mutex result_notify_mutex{};
std::condition_variable result_notify_cv{};
};
}
class RustVirtualFileServer : public VirtualFileServer {
public:
explicit RustVirtualFileServer(ServerId server_id, std::string unique_id) : VirtualFileServer{server_id, std::move(unique_id)} {}
void max_networking_upload_bandwidth(int64_t value) override;
void max_networking_download_bandwidth(int64_t value) override;
};
class RustFileProvider : public AbstractFileServer {
public:
RustFileProvider();
virtual ~RustFileProvider();
[[nodiscard]] std::string file_base_path() const override;
filesystem::AbstractProvider &file_system() override;
transfer::AbstractProvider &file_transfer() override;
std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) override;
void unregister_server(ServerId /* server id */, bool /* delete files */) override;
private:
filesystem::RustFileSystem* file_system_;
transfer::RustFileTransfer* file_transfer_;
};
}

View File

@ -1,13 +0,0 @@
//
// Created by WolverinDEV on 21/05/2020.
//
#include "files/Config.h"
using namespace ts::server::file;
std::function<std::shared_ptr<pipes::SSL::Options>()> config::ssl_option_supplier{nullptr};
void config::value_updated(ts::server::file::config::Key) {
/* we currently do nothing */
}

View File

@ -1,35 +0,0 @@
//
// Created by WolverinDEV on 22/05/2020.
//
#include <pipes/misc/http.h>
#include "HTTPUtils.h"
bool http::parse_url_parameters(const std::string_view &query, std::map<std::string, std::string>& result) {
const auto query_offset = query.find('?');
if(query_offset == std::string::npos) return false;
const auto query_end_offset = query.find('#', query_offset); /* fragment (if there is any) */
auto offset = query_offset + 1;
size_t next_param;
while(offset > 0) {
next_param = query.find('&', offset);
if(next_param >= query_end_offset)
next_param = query_end_offset;
if(offset >= next_param)
break;
/* parameter: [offset;next_param) */
const auto param_view = query.substr(offset, next_param - offset);
const auto assignment_index = param_view.find('=');
if(assignment_index == std::string::npos)
result[std::string{param_view}] = "";
else
result[std::string{param_view.substr(0, assignment_index)}] = http::decode_url(std::string{param_view.substr(assignment_index + 1)});
offset = next_param + 1;
}
return true;
}

View File

@ -1,7 +0,0 @@
#pragma once
#include <map>
namespace http {
bool parse_url_parameters(const std::string_view& /* url */, std::map<std::string, std::string>& /* result */);
}

View File

@ -3,64 +3,67 @@
//
#include <netinet/in.h>
#include <log/LogUtils.h>
#include "LocalFileProvider.h"
#include "LocalFileSystem.h"
#include "LocalFileTransfer.h"
using namespace ts::server;
using LocalFileServer = file::LocalFileProvider;
using LocalVirtualFileServer = file::LocalVirtualFileServer;
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, const std::string& hostnames, uint16_t port) {
bool file::initialize(std::string &error) {
server_instance = std::make_shared<LocalFileProvider>();
if(!server_instance->initialize(error)) {
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;
}
bool any_bind{false};
for(const auto& binding : net::resolve_bindings(hostnames, port)) {
if(!std::get<2>(binding).empty()) {
logError(LOG_FT, "Failed to resolve binding for {}: {}", std::get<0>(binding), std::get<2>(binding));
continue;
}
auto result = dynamic_cast<transfer::LocalFileTransfer&>(server_instance->file_transfer()).add_network_binding({ std::get<0>(binding), std::get<1>(binding) });
switch (result) {
case transfer::NetworkingBindResult::SUCCESS:
any_bind = true;
break;
case transfer::NetworkingBindResult::OUT_OF_MEMORY:
logWarning(LOG_FT, "Failed to listen to address {}: Out of memory", std::get<0>(binding));
continue;
case transfer::NetworkingBindResult::FAILED_TO_LISTEN:
logWarning(LOG_FT, "Failed to listen on {}: {}/{}", std::get<0>(binding), errno, strerror(errno));
continue;
case transfer::NetworkingBindResult::FAILED_TO_BIND:
logWarning(LOG_FT, "Failed to bind on {}: {}/{}", std::get<0>(binding), errno, strerror(errno));
continue;
case transfer::NetworkingBindResult::BINDING_ALREADY_EXISTS:
logWarning(LOG_FT, "Failed to bind on {}: binding already exists", std::get<0>(binding));
continue;
case transfer::NetworkingBindResult::NETWORKING_NOT_INITIALIZED:
logWarning(LOG_FT, "Failed to bind on {}: networking not initialized", std::get<0>(binding));
continue;
case transfer::NetworkingBindResult::FAILED_TO_ALLOCATE_SOCKET:
logWarning(LOG_FT, "Failed to allocate a socket for {}: {}/{}", std::get<0>(binding), errno, strerror(errno));
continue;
}
}
return any_bind;
return true;
}
void file::finalize() {
@ -74,101 +77,45 @@ std::shared_ptr<file::AbstractFileServer> file::server() {
return server_instance;
}
LocalFileServer::LocalFileProvider() {
this->file_system_ = new filesystem::LocalFileSystem();
this->file_transfer_ = new transfer::LocalFileTransfer(this->file_system_);
}
LocalFileServer::~LocalFileProvider() {
delete this->file_transfer_;
delete this->file_system_;
};
LocalFileServer::LocalFileProvider() : file_system_{}, file_transfer_{this->file_system_} {}
LocalFileServer::~LocalFileProvider() {}
bool LocalFileServer::initialize(std::string &error) {
if(!this->file_system_->initialize(error, "files/"))
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;
if(!this->file_transfer_->start()) {
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();
this->file_system_.finalize();
return false;
}
return true;
}
void LocalFileServer::finalize() {
this->file_transfer_->stop();
this->file_system_->finalize();
this->file_transfer_.stop();
this->file_system_.finalize();
}
file::filesystem::AbstractProvider &LocalFileServer::file_system() {
return *this->file_system_;
return this->file_system_;
}
file::transfer::AbstractProvider &LocalFileServer::file_transfer() {
return *this->file_transfer_;
}
std::string file::LocalFileProvider::file_base_path() const {
return this->file_system_->root_path();
}
std::shared_ptr<file::VirtualFileServer> LocalFileServer::register_server(ServerId server_id) {
auto server = this->find_virtual_server(server_id);
if(server) return server;
server = std::make_shared<file::LocalVirtualFileServer>(server_id, std::to_string(server_id));
{
std::lock_guard slock{this->servers_mutex};
this->servers_.push_back(server);
}
return server;
}
void LocalFileServer::unregister_server(ServerId server_id, bool delete_files) {
auto server_unique_id = std::to_string(server_id);
std::shared_ptr<VirtualFileServer> server{};
{
std::lock_guard slock{this->servers_mutex};
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
return server->unique_id() == server_unique_id;
});
if(it == this->servers_.end()) {
return;
}
server = *it;
this->servers_.erase(it);
}
using ErrorType = file::filesystem::ServerCommandErrorType;
auto delete_result = this->file_system_->delete_server(server);
if(!delete_result->wait_for(std::chrono::seconds{5})) {
logError(LOG_INSTANCE, "Failed to wait for file directory deletion.");
} else if(!delete_result->succeeded()) {
switch (delete_result->error().error_type) {
case ErrorType::FAILED_TO_DELETE_DIRECTORIES:
logError(LOG_INSTANCE, "Failed to delete server {} file directories ({}).", server->server_id(), delete_result->error().error_message);
break;
case ErrorType::UNKNOWN:
case ErrorType::FAILED_TO_CREATE_DIRECTORIES:
logError(LOG_INSTANCE, "Failed to delete server {} file directory due to an unknown error: {}/{}",
server->server_id(), (int) delete_result->error().error_type, delete_result->error().error_message);
break;
}
}
}
void LocalVirtualFileServer::max_networking_upload_bandwidth(int64_t value) {
VirtualFileServer::max_networking_upload_bandwidth(value);
this->upload_throttle.set_max_bandwidth(value);
}
void LocalVirtualFileServer::max_networking_download_bandwidth(int64_t value) {
VirtualFileServer::max_networking_download_bandwidth(value);
this->download_throttle.set_max_bandwidth(value);
file::transfer::AbstractProvider & LocalFileServer::file_transfer() {
return this->file_transfer_;
}

View File

@ -9,44 +9,539 @@
#include <pipes/ws.h>
#include <pipes/ssl.h>
#include <misc/net.h>
#include <misc/spin_mutex.h>
#include <random>
#include <misc/memtracker.h>
#include "./NetTools.h"
#define TRANSFER_MAX_CACHED_BYTES (1024 * 1024 * 1) // Buffer up to 1mb
namespace ts::server::file {
namespace filesystem { class LocalFileSystem; }
namespace transfer { class LocalFileTransfer; }
namespace filesystem {
#ifdef FS_INCLUDED
namespace fs = std::experimental::filesystem;
#endif
class LocalVirtualFileServer : public VirtualFileServer {
public:
explicit LocalVirtualFileServer(ServerId server_id, std::string unique_id) : VirtualFileServer{server_id, std::move(unique_id)} {}
class LocalFileSystem : public filesystem::AbstractProvider {
using FileModifyError = filesystem::FileModifyError;
using DirectoryModifyError = filesystem::DirectoryModifyError;
public:
enum struct FileCategory {
ICON,
AVATAR,
CHANNEL
};
void max_networking_upload_bandwidth(int64_t value) override;
void max_networking_download_bandwidth(int64_t value) override;
virtual ~LocalFileSystem();
networking::NetworkThrottle upload_throttle{};
networking::NetworkThrottle download_throttle{};
};
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 */);
[[nodiscard]] bool initialize(std::string& /* error */, const std::shared_ptr<pipes::SSL::Options>& /* ssl options */);
void finalize();
[[nodiscard]] std::string file_base_path() const override;
filesystem::AbstractProvider &file_system() override;
transfer::AbstractProvider &file_transfer() override;
std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) override;
void unregister_server(ServerId /* server id */, bool /* delete server */) override;
private:
filesystem::LocalFileSystem* file_system_;
transfer::LocalFileTransfer* file_transfer_;
filesystem::LocalFileSystem file_system_;
transfer::LocalFileTransfer file_transfer_;
};
}

View File

@ -5,8 +5,8 @@
#define FS_INCLUDED
#include <log/LogUtils.h>
#include "./LocalFileSystem.h"
#include "./clnpath.h"
#include "LocalFileProvider.h"
#include "clnpath.h"
using namespace ts::server::file;
using namespace ts::server::file::filesystem;
@ -36,12 +36,12 @@ bool LocalFileSystem::initialize(std::string &error_message, const std::string &
void LocalFileSystem::finalize() {}
fs::path LocalFileSystem::server_path(const std::shared_ptr<VirtualFileServer> &server) {
return fs::u8path(this->root_path_) / fs::u8path("server_" + std::to_string(server->server_id()));
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(const std::shared_ptr<VirtualFileServer> &server, ts::ChannelId cid) {
return this->server_path(server) / fs::u8path("channel_" + std::to_string(cid));
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) {
@ -67,32 +67,32 @@ bool LocalFileSystem::is_any_file_locked(const fs::path &base, const std::string
return false;
}
std::string LocalFileSystem::target_file_path(FileCategory type, const std::shared_ptr<VirtualFileServer> &server, ts::ChannelId cid, const std::string &path) {
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(server, cid) / path;
target_path = this->server_channel_path(sid, cid) / path;
break;
case FileCategory::ICON:
target_path = this->server_path(server) / "icons" / path;
target_path = this->server_path(sid) / "icons" / path;
break;
case FileCategory::AVATAR:
target_path = this->server_path(server) / "avatars" / path;
target_path = this->server_path(sid) / "avatars" / path;
break;
}
return clnpath(fs::absolute(target_path).string());
}
std::string LocalFileSystem::absolute_avatar_path(const std::shared_ptr<VirtualFileServer> &sid, const std::string &path) {
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(const std::shared_ptr<VirtualFileServer> &sid, const std::string &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(const std::shared_ptr<VirtualFileServer> &sid, ChannelId cid, const std::string &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);
}
@ -107,7 +107,7 @@ void LocalFileSystem::unlock_file(const std::string &c_path) {
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(const std::shared_ptr<VirtualFileServer> &id) {
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::initialize_server(ServerId id) {
auto path = this->server_path(id);
std::error_code error{};
@ -126,7 +126,7 @@ std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::initialize
return response;
}
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::delete_server(const std::shared_ptr<VirtualFileServer> &id) {
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::delete_server(ServerId id) {
auto path = this->server_path(id);
std::error_code error{};
@ -190,10 +190,6 @@ std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(con
dentry.type = DirectoryEntry::DIRECTORY;
dentry.name = entry.path().filename();
dentry.empty = fs::is_empty(entry.path(), error);
if(error)
logWarning(LOG_FT, "Failed to query directory empty state for directory {} ({}/{})", target_path.string(), error.value(), error.message());
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());
@ -221,19 +217,19 @@ std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(con
return response;
}
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_icon_directory(const std::shared_ptr<VirtualFileServer> &id) {
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(const std::shared_ptr<VirtualFileServer> &id) {
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(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string &path) {
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(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string &path) {
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{};
@ -260,21 +256,20 @@ std::shared_ptr<ExecuteResponse<DirectoryModifyError>> LocalFileSystem::create_c
return response;
}
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channel_file(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string &current_path_string, ChannelId targetChannelId, const std::string &new_path_string) {
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);
auto target_path_root = this->server_channel_path(id, targetChannelId);
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 = target_path_root / fs::u8path(new_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(target_path_root, target_path)) {
if(this->exceeds_base_path(channel_path_root, target_path)) {
response->emplace_fail(FileModifyErrorType::TARGET_PATH_EXCEEDS_ROOT_PATH, "");
return response;
}
@ -288,15 +283,6 @@ std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channe
return response;
}
if(!fs::exists(target_path.parent_path(), error)) {
if(!fs::create_directories(target_path.parent_path(), error)) {
response->emplace_fail(FileModifyErrorType::FAILED_TO_CREATE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
return response;
}
} else if(error) {
logWarning(LOG_FT, "Failed to test for target directory existence for {}: {}/{}. Assuming it does exists", target_path.parent_path().string(), error.value(), error.message());
}
if(fs::exists(target_path, error)) {
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
return response;
@ -321,137 +307,44 @@ std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channe
return response;
}
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_files(const fs::path &base,
const std::vector<std::string> &paths) {
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<FileDeleteError, FileDeleteResponse>();
auto response = this->create_execute_response<FileModifyError>();
auto target_path = base / fs::u8path(path);
std::vector<FileDeleteResponse::DeleteResult> delete_results{};
for(const auto& path : paths) {
auto target_path = base / fs::u8path(path);
if(!fs::exists(target_path, error)) {
delete_results.emplace_back(FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS, "");
continue;
} else if(error) {
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", target_path.string(), error.value(), error.message());
delete_results.emplace_back(FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS, "");
continue;
}
if(this->is_any_file_locked(base, path, locked_file)) {
delete_results.emplace_back(FileDeleteResponse::StatusType::SOME_FILES_ARE_LOCKED, locked_file);
continue;
}
if(!fs::remove_all(target_path, error) || error) {
delete_results.emplace_back(FileDeleteResponse::StatusType::FAILED_TO_DELETE_FILES, std::to_string(error.value()) + "/" + error.message());
continue;
}
delete_results.emplace_back(FileDeleteResponse::StatusType::SUCCESS, "");
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;
}
response->emplace_success(FileDeleteResponse{delete_results});
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<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_channel_files(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::vector<std::string> &path) {
return this->delete_files(this->server_channel_path(id, channelId), path);
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<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_icons(const std::shared_ptr<VirtualFileServer> &id, const std::vector<std::string> &icon) {
return this->delete_files(this->server_path(id) / fs::u8path("icons"), icon);
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<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_avatars(const std::shared_ptr<VirtualFileServer> &id, const std::vector<std::string> &avatar) {
return this->delete_files(this->server_path(id) / fs::u8path("avatars"), avatar);
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_file_info(const std::vector<std::tuple<fs::path, std::string>> &paths) {
std::error_code error{};
auto response = this->create_execute_response<FileInfoError, FileInfoResponse>();
std::vector<FileInfoResponse::FileInfo> file_infos{};
file_infos.reserve(paths.size());
for(const auto& [base, path] : paths) {
auto target_path = base / fs::u8path(path);
if(this->exceeds_base_path(base, target_path)) {
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_EXCEEDS_ROOT_PATH, "", DirectoryEntry{});
continue;
}
if(!fs::exists(target_path, error)) {
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS, "", DirectoryEntry{});
continue;
} else if(error) {
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS, "", DirectoryEntry{});
continue;
}
auto status = fs::status(target_path, error);
if(error) {
logWarning(LOG_FT, "Failed to query file status for {} ({}/{}). Skipping entry for file info query.", target_path.string(), error.value(), error.message());
file_infos.emplace_back(FileInfoResponse::StatusType::FAILED_TO_QUERY_INFO, "", DirectoryEntry{});
continue;
}
if(status.type() == fs::file_type::directory) {
DirectoryEntry dentry{};
dentry.type = DirectoryEntry::DIRECTORY;
dentry.name = target_path.filename();
dentry.empty = fs::is_empty(target_path, error);
if(error)
logWarning(LOG_FT, "Failed to query directory empty state for directory {} ({}/{})", target_path.string(), error.value(), error.message());
dentry.modified_at = fs::last_write_time(target_path, error);
if(error)
logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", target_path.string(), error.value(), error.message());
dentry.size = 0;
file_infos.emplace_back(FileInfoResponse::StatusType::SUCCESS, "", dentry);
} else if(status.type() == fs::file_type::regular) {
DirectoryEntry dentry{};
dentry.type = DirectoryEntry::FILE;
dentry.name = target_path.filename();
dentry.modified_at = fs::last_write_time(target_path, error);
if(error)
logWarning(LOG_FT, "Failed to query last write time for file {} ({}/{}).", target_path.string(), error.value(), error.message());
dentry.size = fs::file_size(target_path, error);
if(error)
logWarning(LOG_FT, "Failed to query size for file {} ({}/{}).", target_path.string(), error.value(), error.message());
file_infos.emplace_back(FileInfoResponse::StatusType::SUCCESS, "", dentry);
} else {
logWarning(LOG_FT, "File info query contains an unknown file type for file {} ({}).", target_path.string(), (int) status.type());
file_infos.emplace_back(FileInfoResponse::StatusType::UNKNOWN_FILE_TYPE, "", DirectoryEntry{});
}
}
response->emplace_success(FileInfoResponse{file_infos});
return response;
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_channel_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::tuple<ChannelId, std::string>>& files) {
std::vector<std::tuple<fs::path, std::string>> file_paths{};
for(const auto& [channelId, path] : files)
file_paths.emplace_back(this->server_channel_path(sid, channelId), path);
return this->query_file_info(file_paths);
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_icon_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::string> &paths) {
std::vector<std::tuple<fs::path, std::string>> file_paths{};
for(const auto& path : paths)
file_paths.emplace_back(this->server_path(sid) / fs::u8path("icons"), path);
return this->query_file_info(file_paths);
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse> > LocalFileSystem::query_avatar_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::string> &paths) {
std::vector<std::tuple<fs::path, std::string>> file_paths{};
for(const auto& path : paths)
file_paths.emplace_back(this->server_path(sid) / fs::u8path("avatars"), path);
return this->query_file_info(file_paths);
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);
}

View File

@ -1,117 +0,0 @@
//
// Created by WolverinDEV on 16/09/2020.
//
#pragma once
#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 <misc/spin_mutex.h>
#include <random>
#include <misc/memtracker.h>
#include "./NetTools.h"
namespace ts::server::file::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(const std::shared_ptr<VirtualFileServer> &, const std::string&);
[[nodiscard]] std::string absolute_icon_path(const std::shared_ptr<VirtualFileServer> &, const std::string&);
[[nodiscard]] std::string absolute_channel_path(const std::shared_ptr<VirtualFileServer> &, ChannelId, const std::string&);
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> & /* server */) override;
std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(const std::shared_ptr<VirtualFileServer> & /* server */);
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_channel_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* names */) override;
std::shared_ptr<directory_query_response_t>
query_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
create_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
delete_channel_files(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::vector<std::string> &string) override;
std::shared_ptr<ExecuteResponse<FileModifyError>>
rename_channel_file(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &, ChannelId, const std::string &) override;
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_icon_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> & id) override;
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
delete_icons(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_avatar_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> & id) override;
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
delete_avatars(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
private:
#ifdef FS_INCLUDED
[[nodiscard]] fs::path server_path(const std::shared_ptr<VirtualFileServer> &);
[[nodiscard]] fs::path server_channel_path(const std::shared_ptr<VirtualFileServer> &, 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<FileDeleteError, FileDeleteResponse>>
delete_files(const fs::path& /* base */, const std::vector<std::string> &string);
[[nodiscard]] std::shared_ptr<directory_query_response_t>
query_directory(const fs::path& /* base */, const std::string &string, bool);
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_file_info(const std::vector<std::tuple<fs::path, std::string>> &string);
#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, const std::shared_ptr<VirtualFileServer> &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_{};
};
}

View File

@ -7,10 +7,8 @@
#include <log/LogUtils.h>
#include <random>
#include "./LocalFileProvider.h"
#include "./LocalFileTransfer.h"
#include <experimental/filesystem>
#include "LocalFileProvider.h"
namespace fs = std::experimental::filesystem;
using namespace ts::server::file;
using namespace ts::server::file::transfer;
@ -19,28 +17,21 @@ Buffer* transfer::allocate_buffer(size_t size) {
auto buffer = (Buffer*) malloc(total_size);
new (buffer) Buffer{};
buffer->capacity = size;
buffer->ref_count = 1;
return buffer;
}
Buffer* transfer::ref_buffer(Buffer *buffer) {
buffer->ref_count++;
return buffer;
}
void transfer::deref_buffer(Buffer *buffer) {
if(--buffer->ref_count == 0) {
buffer->~Buffer();
free(buffer);
}
void transfer::free_buffer(Buffer* buffer) {
buffer->~Buffer();
free(buffer);
}
FileClient::~FileClient() {
this->flush_network_buffer();
this->flush_disk_buffer();
assert(!this->disk_buffer.buffer_head);
assert(!this->network_buffer.buffer_head);
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);
@ -50,13 +41,15 @@ FileClient::~FileClient() {
assert(!this->networking.event_write);
assert(this->state == STATE_DISCONNECTED);
memtrack::freed<FileClient>(this);
}
LocalFileTransfer::LocalFileTransfer(filesystem::LocalFileSystem *fs) : file_system_{fs} {}
LocalFileTransfer::LocalFileTransfer(filesystem::LocalFileSystem &fs) : file_system_{fs} {}
LocalFileTransfer::~LocalFileTransfer() = default;
bool LocalFileTransfer::start() {
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();
{
@ -75,15 +68,17 @@ bool LocalFileTransfer::start() {
{
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;
@ -106,59 +101,29 @@ void LocalFileTransfer::stop() {
this->shutdown_client_worker();
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
LocalFileTransfer::initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, const TransferInfo &info) {
return this->initialize_transfer(direction, server, 0, Transfer::TARGET_TYPE_ICON, info);
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, const std::shared_ptr<VirtualFileServer> &server, const TransferInfo &info) {
return this->initialize_transfer(direction, server, 0, Transfer::TARGET_TYPE_AVATAR, 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, const std::shared_ptr<VirtualFileServer> &server, ChannelId cid, const TransferInfo &info) {
return this->initialize_transfer(direction, server, cid, Transfer::TARGET_TYPE_CHANNEL_FILE, 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, const std::shared_ptr<VirtualFileServer> &server, ChannelId cid,
Transfer::Direction direction, ServerId sid, ChannelId cid,
Transfer::TargetType ttype,
const TransferInfo &info) {
auto response = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
std::lock_guard clock{this->transfer_create_mutex};
if(info.max_concurrent_transfers > 0) {
std::unique_lock tlock{this->transfers_mutex};
{
auto transfers = std::count_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& client) {
return client->transfer && client->transfer->client_unique_id == info.client_unique_id && client->state < FileClient::STATE_FLUSHING;
});
transfers += std::count_if(this->pending_transfers.begin(), this->pending_transfers.end(), [&](const std::shared_ptr<Transfer>& transfer) {
return transfer->client_unique_id == info.client_unique_id;
});
if(transfers >= info.max_concurrent_transfers) {
response->emplace_fail(TransferInitError::CLIENT_TOO_MANY_TRANSFERS, std::to_string(transfers));
return response;
}
}
{
auto server_transfers = this->pending_transfers.size();
server_transfers += std::count_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& client) {
return client->transfer;
});
if(server_transfers >= this->max_concurrent_transfers) {
response->emplace_fail(TransferInitError::SERVER_TOO_MANY_TRANSFERS, std::to_string(server_transfers));
return response;
}
}
}
/* TODO: test for a transfer limit */
auto transfer = std::make_shared<Transfer>();
transfer->server_transfer_id = server->generate_transfer_id();
transfer->server = server;
transfer->server_transfer_id = ++this->current_transfer_id;
transfer->server_id = sid;
transfer->channel_id = cid;
transfer->target_type = ttype;
transfer->direction = direction;
@ -177,111 +142,21 @@ std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> L
transfer->file_offset = info.file_offset;
transfer->expected_file_size = info.expected_file_size;
transfer->max_bandwidth = info.max_bandwidth;
transfer->client_unique_id = info.client_unique_id;
transfer->client_id = info.client_id;
constexpr static std::string_view kTokenCharacters{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"};
transfer->transfer_key.resize(TRANSFER_KEY_LENGTH);
for(auto& c : transfer->transfer_key) {
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::string absolute_path{};
switch (transfer->target_type) {
case Transfer::TARGET_TYPE_AVATAR:
absolute_path = this->file_system_->absolute_avatar_path(transfer->server, transfer->target_file_path);
break;
case Transfer::TARGET_TYPE_ICON:
absolute_path = this->file_system_->absolute_icon_path(transfer->server, transfer->target_file_path);
break;
case Transfer::TARGET_TYPE_CHANNEL_FILE:
absolute_path = this->file_system_->absolute_channel_path(transfer->server, transfer->channel_id, transfer->target_file_path);
break;
case Transfer::TARGET_TYPE_UNKNOWN:
default:
response->emplace_fail(TransferInitError::INVALID_FILE_TYPE, "");
return response;
}
transfer->absolute_file_path = absolute_path;
const auto root_path_length = this->file_system_->root_path().size();
if(root_path_length < absolute_path.size())
transfer->relative_file_path = absolute_path.substr(root_path_length);
else
transfer->relative_file_path = "error";
transfer->file_name = fs::u8path(absolute_path).filename();
}
if(direction == Transfer::DIRECTION_DOWNLOAD) {
auto path = fs::u8path(transfer->absolute_file_path);
std::error_code error{};
if(!fs::exists(path, error)) {
response->emplace_fail(TransferInitError::FILE_DOES_NOT_EXISTS, "");
return response;
} else if(error) {
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", transfer->absolute_file_path, error.value(), error.message());
response->emplace_fail(TransferInitError::FILE_DOES_NOT_EXISTS, "");
return response;
}
auto status = fs::status(path, error);
if(error) {
logWarning(LOG_FT, "Failed to status for file at {}: {}. Ignoring file transfer.", transfer->absolute_file_path, error.value(), error.message());
response->emplace_fail(TransferInitError::IO_ERROR, "stat");
return response;
}
if(status.type() != fs::file_type::regular) {
response->emplace_fail(TransferInitError::FILE_IS_NOT_A_FILE, "");
return response;
}
transfer->expected_file_size = fs::file_size(path, error);
if(error) {
logWarning(LOG_FT, "Failed to get file size for file at {}: {}. Ignoring file transfer.", transfer->absolute_file_path, error.value(), error.message());
response->emplace_fail(TransferInitError::IO_ERROR, "file_size");
return response;
}
if(info.download_client_quota_limit > 0 && info.download_client_quota_limit <= transfer->expected_file_size) {
response->emplace_fail(TransferInitError::CLIENT_QUOTA_EXCEEDED, "");
return response;
}
if(info.download_server_quota_limit > 0 && info.download_server_quota_limit <= transfer->expected_file_size) {
response->emplace_fail(TransferInitError::SERVER_QUOTA_EXCEEDED, "");
return response;
}
}
{
std::lock_guard tlock{this->transfers_mutex};
this->pending_transfers.push_back(transfer);
}
switch (transfer->target_type) {
case Transfer::TARGET_TYPE_AVATAR:
logMessage(LOG_FT, "Initialized avatar transfer for avatar \"{}\" ({} bytes, transferring {} bytes).", transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
break;
case Transfer::TARGET_TYPE_ICON:
logMessage(LOG_FT, "Initialized icon transfer for icon \"{}\" ({} bytes, transferring {} bytes).",
transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
break;
case Transfer::TARGET_TYPE_CHANNEL_FILE:
logMessage(LOG_FT, "Initialized channel transfer for file \"{}/{}\" ({} bytes, transferring {} bytes).",
transfer->channel_id, transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
break;
case Transfer::TARGET_TYPE_UNKNOWN:
default:
response->emplace_fail(TransferInitError::INVALID_FILE_TYPE, "");
return response;
}
if(auto callback{this->callback_transfer_registered}; callback)
callback(transfer);
@ -289,7 +164,7 @@ std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> L
return response;
}
std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_transfer(const std::shared_ptr<VirtualFileServer>& server, transfer_id id, bool flush) {
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{};
@ -299,13 +174,13 @@ std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_tr
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 && t->transfer->server == server;
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 && t->server == server;
return t->server_transfer_id == id;
});
if(t_it != this->pending_transfers.end()) {
transfer = *t_it;
@ -324,72 +199,14 @@ std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_tr
}
if(connected_transfer) {
this->invoke_aborted_callback(connected_transfer, { TransferError::USER_REQUEST, "" });
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 {
this->invoke_aborted_callback(transfer, { TransferError::USER_REQUEST, "" });
logMessage(LOG_FT, "Removing pending file transfer for id {}", id);
}
response->emplace_success();
return response;
}
inline void apply_transfer_info(const std::shared_ptr<Transfer>& transfer, ActiveFileTransfer& info) {
info.server_transfer_id = transfer->server_transfer_id;
info.client_transfer_id = transfer->client_transfer_id;
info.direction = transfer->direction;
info.client_id = transfer->client_id;
info.client_unique_id = transfer->client_unique_id;
info.file_path = transfer->relative_file_path;
info.file_name = transfer->file_name;
info.expected_size = transfer->expected_file_size;
}
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> LocalFileTransfer::list_transfer() {
std::vector<ActiveFileTransfer> transfer_infos{};
auto response = this->create_execute_response<TransferListError, std::vector<ActiveFileTransfer>>();
std::unique_lock tlock{this->transfers_mutex};
auto awaiting_transfers = this->pending_transfers;
auto running_transfers = this->transfers_;
tlock.unlock();
transfer_infos.reserve(awaiting_transfers.size() + running_transfers.size());
for(const auto& transfer : awaiting_transfers) {
ActiveFileTransfer info{};
apply_transfer_info(transfer, info);
info.size_done = transfer->file_offset;
info.status = ActiveFileTransfer::NOT_STARTED;
info.runtime = std::chrono::milliseconds{0};
info.average_speed = 0;
info.current_speed = 0;
transfer_infos.push_back(info);
}
for(const auto& client : running_transfers) {
auto transfer = client->transfer;
if(!transfer) continue;
ActiveFileTransfer info{};
apply_transfer_info(transfer, info);
info.size_done = transfer->file_offset + client->statistics.file_transferred.total_bytes;
info.status = ActiveFileTransfer::RUNNING;
info.runtime = std::chrono::floor<std::chrono::milliseconds>(std::chrono::system_clock::now() - client->timings.key_received);
info.average_speed = client->statistics.file_transferred.average_bandwidth();
info.current_speed = client->statistics.file_transferred.current_bandwidth();
transfer_infos.push_back(info);
}
response->emplace_success(std::move(transfer_infos));
return response;
}

View File

@ -1,449 +0,0 @@
//
// Created by WolverinDEV on 16/09/2020.
//
#pragma once
#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 <misc/spin_mutex.h>
#include <random>
#include <misc/memtracker.h>
#include "./NetTools.h"
#include "LocalFileSystem.h"
#define TRANSFER_MAX_CACHED_BYTES (1024 * 1024 * 1) // Buffer up to 1mb
namespace ts::server::file::transfer {
class LocalFileTransfer;
struct Buffer {
Buffer* next{nullptr};
std::atomic_uint32_t ref_count{0};
uint32_t capacity{0};
uint32_t length{0};
uint32_t offset{0};
char data[1]{};
};
[[nodiscard]] extern Buffer* allocate_buffer(size_t);
[[nodiscard]] extern Buffer* ref_buffer(Buffer*);
extern void deref_buffer(Buffer*);
/* 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_FLUSHING,
STATE_DISCONNECTED
} state{STATE_AWAITING_KEY};
bool finished_signal_send{false};
enum NetworkingProtocol {
PROTOCOL_UNKNOWN,
PROTOCOL_HTTPS,
PROTOCOL_TS_V1
};
enum HTTPUploadState {
HTTP_AWAITING_HEADER,
HTTP_STATE_AWAITING_BOUNDARY,
HTTP_STATE_AWAITING_BOUNDARY_END,
HTTP_STATE_UPLOADING,
HTTP_STATE_DOWNLOADING
};
struct {
bool file_locked{false};
int file_descriptor{0};
bool currently_processing{false};
FileClient* next_client{nullptr};
bool query_media_bytes{false};
uint8_t media_bytes[TRANSFER_MEDIA_BYTES_LENGTH]{0};
uint8_t media_bytes_length{0};
} file{};
struct {
size_t provided_bytes{0};
char key[TRANSFER_KEY_LENGTH]{0};
} transfer_key{};
struct {
std::mutex mutex{};
size_t bytes{0};
bool buffering_stopped{false};
bool write_disconnected{false};
Buffer* buffer_head{nullptr};
Buffer** buffer_tail{&buffer_head};
} network_buffer{};
struct {
std::mutex mutex{};
size_t bytes{0};
bool buffering_stopped{false};
bool write_disconnected{false};
Buffer* buffer_head{nullptr};
Buffer** buffer_tail{&buffer_head};
} disk_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{};
networking::NetworkThrottle client_throttle{};
/* the right side is the server throttle */
networking::DualNetworkThrottle throttle{&client_throttle, &networking::NetworkThrottle::kNoThrottle};
pipes::SSL pipe_ssl{};
bool pipe_ssl_init{false};
std::unique_ptr<Buffer, decltype(deref_buffer)*> http_header_buffer{nullptr, deref_buffer};
HTTPUploadState http_state{HTTPUploadState::HTTP_AWAITING_HEADER};
std::string http_boundary{};
/* 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 {
networking::TransferStatistics network_send{};
networking::TransferStatistics network_received{};
networking::TransferStatistics file_transferred{};
networking::TransferStatistics disk_bytes_read{};
networking::TransferStatistics disk_bytes_write{};
} 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} { memtrack::allocated<FileClient>(this); }
~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_network_buffer_bytes(const void* /* buffer */, size_t /* length */);
bool enqueue_disk_buffer_bytes(const void* /* buffer */, size_t /* length */);
/* these function clear the buffers and set the write disconnected flags to true so no new buffers will be enqueued */
size_t flush_network_buffer();
void flush_disk_buffer();
[[nodiscard]] bool buffers_flushed();
[[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
};
enum struct NetworkingBindResult {
SUCCESS,
BINDING_ALREADY_EXISTS,
NETWORKING_NOT_INITIALIZED,
FAILED_TO_ALLOCATE_SOCKET, /* errno is set */
FAILED_TO_BIND,
FAILED_TO_LISTEN,
OUT_OF_MEMORY,
};
enum struct NetworkingUnbindResult {
SUCCESS,
UNKNOWN_BINDING
};
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_SEEK_FAILED,
FILE_SIZE_MISMATCH,
FILE_IS_NOT_ACCESSIBLE,
FAILED_TO_READ_MEDIA_BYTES,
COUNT_NOT_CREATE_DIRECTORIES,
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_SEEK_FAILED */ "failed to seek to target file offset",
/* FILE_SIZE_MISMATCH */ "file size miss match",
/* FILE_IS_NOT_ACCESSIBLE */ "file is not accessible",
/* FAILED_TO_READ_MEDIA_BYTES */ "failed to read file media bytes",
/* COUNT_NOT_CREATE_DIRECTORIES */ "could not create required directories"
};
enum struct TransferKeyApplyResult {
SUCCESS,
FILE_ERROR,
UNKNOWN_KEY,
INTERNAL_ERROR
};
enum struct TransferUploadRawResult {
MORE_DATA_TO_RECEIVE,
FINISH,
FINISH_OVERFLOW,
/* 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::string hostname{};
sockaddr_storage address{};
};
struct ActiveNetworkBinding : std::enable_shared_from_this<ActiveNetworkBinding> {
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();
void stop();
[[nodiscard]] NetworkingBindResult add_network_binding(const NetworkBinding& /* binding */);
[[nodiscard]] std::vector<NetworkBinding> active_network_bindings();
[[nodiscard]] NetworkingUnbindResult remove_network_binding(const NetworkBinding& /* binding */);
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_channel_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, ChannelId channelId,
const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_avatar_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> list_transfer() override;
std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(const std::shared_ptr<VirtualFileServer>& /* server */, transfer_id id, bool) override;
private:
enum struct DiskIOLoopState {
STOPPED,
RUNNING,
STOPPING,
FORCE_STOPPING
};
filesystem::LocalFileSystem* file_system_;
size_t max_concurrent_transfers{1024};
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::mutex transfer_create_mutex{};
std::deque<std::shared_ptr<FileClient>> transfers_{};
std::deque<std::shared_ptr<Transfer>> pending_transfers{};
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 {
std::mutex mutex;
bool active{false};
std::thread dispatch_thread{};
struct event_base* event_base{nullptr};
std::deque<std::shared_ptr<ActiveNetworkBinding>> 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, const std::shared_ptr<VirtualFileServer> &, 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 network */);
[[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 test_disconnecting_state(const std::shared_ptr<FileClient>& /* client */);
[[nodiscard]] TransferUploadRawResult handle_transfer_upload_raw(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */, size_t* /* bytes written */);
[[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*);
void report_transfer_statistics(const std::shared_ptr<FileClient>& /* client */);
[[nodiscard]] TransferStatistics generate_transfer_statistics_report(const std::shared_ptr<FileClient>& /* client */);
void invoke_aborted_callback(const std::shared_ptr<FileClient>& /* client */, const TransferError& /* error */);
void invoke_aborted_callback(const std::shared_ptr<Transfer>& /* pending transfer */, const TransferError& /* error */);
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 */);
};
}

View File

@ -6,7 +6,7 @@
#include <event2/event.h>
#include <log/LogUtils.h>
#include "./LocalFileProvider.h"
#include "./LocalFileTransfer.h"
#include "LocalFileProvider.h"
using namespace ts::server::file;
using namespace ts::server::file::transfer;
@ -37,24 +37,40 @@ void LocalFileTransfer::shutdown_client_worker() {
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_FLUSHING && flush)) {
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_FLUSHING : FileClient::STATE_DISCONNECTED;
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->network_buffer.bytes) + std::chrono::seconds{10};
const auto network_flush_time = client->networking.throttle.expected_writing_time(client->buffer.bytes) + std::chrono::seconds{10};
del_ev_noblock(client->networking.event_read);
if(!client->transfer) {
del_ev_noblock(client->networking.event_read);
del_ev_noblock(client->networking.event_throttle);
client->add_network_write_event_nolock(false);
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());
/* 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);
client->add_network_write_event_nolock(false);
this->enqueue_disk_io(client);
/* 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);
@ -66,25 +82,13 @@ void LocalFileTransfer::disconnect_client(const std::shared_ptr<FileClient> &cli
#undef del_ev_noblock
}
void LocalFileTransfer::test_disconnecting_state(const std::shared_ptr<FileClient> &client) {
if(client->state != FileClient::STATE_FLUSHING)
return;
if(!client->buffers_flushed())
return;
debugMessage(LOG_FT, "{} Disk and network buffers are flushed. Closing connection.", client->log_prefix());
std::unique_lock s_lock{client->state_mutex};
this->disconnect_client(client, s_lock, false);
}
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::milliseconds {500}); /* report all 500ms the statistics */
provider->disconnect.notify_cv.wait_for(dlock, std::chrono::seconds{1});
}
/* run the disconnect worker at least once before exiting */
@ -97,24 +101,14 @@ void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
switch(transfer->state) {
case FileClient::STATE_TRANSFERRING:
break;
case FileClient::STATE_FLUSHING:
if(!transfer->transfer)
continue;
if(transfer->transfer->direction != Transfer::DIRECTION_DOWNLOAD)
continue;
if(transfer->buffers_flushed())
continue;
break; /* we're still transferring (sending data) */
case FileClient::STATE_AWAITING_KEY:
case FileClient::STATE_DISCONNECTED:
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);
provider->report_transfer_statistics(transfer->shared_from_this());
}
}
@ -126,15 +120,17 @@ void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
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{10} < now;
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)
provider->invoke_aborted_callback(pt, { TransferError::TRANSFER_TIMEOUT, "" });
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());
@ -156,11 +152,12 @@ void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
} 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_FLUSHING) {
} 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;
@ -179,29 +176,21 @@ void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
break;
case FileClient::STATE_TRANSFERRING:
logMessage(LOG_FT, "{} Networking timeout. Dropping client", client->log_prefix());
provider->invoke_aborted_callback(client, { TransferError::TRANSFER_TIMEOUT, "" });
if(auto callback{provider->callback_transfer_aborted}; callback)
callback(client->transfer, { TransferError::TRANSFER_TIMEOUT, "" });
break;
case FileClient::STATE_FLUSHING:
if(!client->buffers_flushed())
logMessage(LOG_FT, "{} Failed to flush connection. Dropping client", client->log_prefix());
else
; /* we just awaited a client disconnect */
case FileClient::STATE_DISCONNECTING:
logMessage(LOG_FT, "{} Failed to flush connection. Dropping client", client->log_prefix());
break;
case FileClient::STATE_DISCONNECTED:
default:
break;
}
{
std::unique_lock slock{client->state_mutex};
client->state = FileClient::STATE_DISCONNECTED;
/*
* First of all disconnect the client from the network so no actions could be triggered by that way.
* Secondly finalize all network components, so no data is pending anywhere
* Thirdly drop the client's disk worker (if it's an upload the data should be written already, else we don't care anyways)
*/
provider->finalize_networking(client, slock);
provider->finalize_client_ssl(client);
provider->finalize_file_io(client, slock);
provider->finalize_client_ssl(client);
provider->finalize_networking(client, slock);
}
debugMessage(LOG_FT, "{} Destroying transfer.", client->log_prefix());
@ -214,58 +203,20 @@ void LocalFileTransfer::report_transfer_statistics(const std::shared_ptr<FileCli
auto callback{this->callback_transfer_statistics};
if(!callback) return;
callback(client->transfer, this->generate_transfer_statistics_report(client));
}
TransferStatistics LocalFileTransfer::generate_transfer_statistics_report(const std::shared_ptr<FileClient> &client) {
TransferStatistics stats{};
stats.network_bytes_send = client->statistics.network_send.total_bytes;
stats.network_bytes_received = client->statistics.network_received.total_bytes;
stats.file_bytes_transferred = client->statistics.file_transferred.total_bytes;
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 = client->statistics.network_received.take_delta();
stats.delta_network_bytes_send = client->statistics.network_received.take_delta();
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 = client->statistics.file_transferred.take_delta();
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_transferred.total_bytes + 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;
stats.average_speed = client->statistics.file_transferred.average_bandwidth();
stats.current_speed = client->statistics.file_transferred.current_bandwidth();
return stats;
}
void LocalFileTransfer::invoke_aborted_callback(const std::shared_ptr<FileClient> &client,
const ts::server::file::transfer::TransferError &error) {
auto callback{this->callback_transfer_aborted};
if(!callback || !client->transfer) return;
callback(client->transfer, this->generate_transfer_statistics_report(client), error);
}
void LocalFileTransfer::invoke_aborted_callback(const std::shared_ptr<Transfer> &transfer,
const ts::server::file::transfer::TransferError &error) {
auto callback{this->callback_transfer_aborted};
if(!callback) return;
TransferStatistics stats{};
stats.network_bytes_send = 0;
stats.network_bytes_received = 0;
stats.file_bytes_transferred = 0;
stats.delta_network_bytes_received = 0;
stats.delta_network_bytes_send = 0;
stats.delta_file_bytes_transferred = 0;
stats.file_start_offset = transfer->file_offset;
stats.file_current_offset = transfer->file_offset;
stats.file_total_size = transfer->expected_file_size;
callback(transfer, stats, error);
callback(client->transfer, stats);
}

View File

@ -7,8 +7,8 @@
#include <experimental/filesystem>
#include <log/LogUtils.h>
#include "./LocalFileProvider.h"
#include "./LocalFileTransfer.h"
#include "duration_utils.h"
#include "./duration_utils.h"
#include "LocalFileProvider.h"
using namespace ts::server::file;
using namespace ts::server::file::transfer;
@ -29,9 +29,8 @@ void LocalFileTransfer::shutdown_disk_io() {
{
std::unique_lock qlock{this->disk_io.queue_lock};
this->disk_io.notify_work_awaiting.notify_all();
while(this->disk_io.queue_head) {
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.");
@ -63,18 +62,11 @@ void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) {
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) {
try {
client = provider->disk_io.queue_head->shared_from_this();
} catch (std::bad_weak_ptr& ex) {
logCritical(LOG_FT, "Disk worker encountered a bad weak ptr. This indicated something went horribly wrong! Please submit this on https://forum.teaspeak.de !!!");
client.reset();
continue;
}
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) {
if(!provider->disk_io.queue_head)
provider->disk_io.queue_tail = &provider->disk_io.queue_head;
}
}
if(provider->disk_io.state != DiskIOLoopState::RUNNING) {
@ -85,9 +77,8 @@ void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) {
} else {
/* force stopping without any flushing */
auto fclient = &*client;
while(fclient) {
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;
@ -95,9 +86,8 @@ void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) {
}
}
if(!client) {
if(!client)
continue;
}
client->file.currently_processing = true;
client->file.next_client = nullptr;
@ -108,65 +98,6 @@ void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) {
provider->disk_io.notify_client_processed.notify_all();
}
bool FileClient::enqueue_disk_buffer_bytes(const void *snd_buffer, size_t size) {
auto tbuffer = allocate_buffer(size);
tbuffer->length = size;
tbuffer->offset = 0;
memcpy(tbuffer->data, snd_buffer, size);
size_t buffer_size;
{
std::lock_guard block{this->disk_buffer.mutex};
if(this->disk_buffer.write_disconnected) {
goto write_disconnected;
}
*this->disk_buffer.buffer_tail = tbuffer;
this->disk_buffer.buffer_tail = &tbuffer->next;
buffer_size = (this->disk_buffer.bytes += size);
}
return buffer_size > TRANSFER_MAX_CACHED_BYTES;
write_disconnected:
deref_buffer(tbuffer);
return false;
}
void FileClient::flush_disk_buffer() {
Buffer* current_head;
{
std::lock_guard block{this->disk_buffer.mutex};
this->disk_buffer.write_disconnected = true;
this->disk_buffer.bytes = 0;
current_head = std::exchange(this->disk_buffer.buffer_head, nullptr);
this->disk_buffer.buffer_tail = &this->disk_buffer.buffer_head;
}
while(current_head) {
auto next = current_head->next;
deref_buffer(current_head);
current_head = next;
}
}
bool FileClient::buffers_flushed() {
{
std::lock_guard db_lock{this->disk_buffer.mutex};
if(this->disk_buffer.bytes > 0)
return false;
}
{
std::lock_guard nb_lock{this->network_buffer.mutex};
if(this->network_buffer.bytes > 0)
return false;
}
return true;
}
FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr<FileClient> &transfer) {
FileInitializeResult result{FileInitializeResult::SUCCESS};
assert(transfer->transfer);
@ -176,34 +107,21 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
assert(!file_data.file_descriptor);
assert(!file_data.next_client);
auto absolute_path = transfer->transfer->absolute_file_path;
{
unsigned int open_flags{0};
if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
open_flags = O_RDONLY;
std::error_code fs_error{};
if(absolute_path.empty() || !fs::exists(absolute_path, 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(), absolute_path, fs_error.value(), fs_error.message());
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) {
std::error_code fs_error{};
auto parent_path = fs::u8path(absolute_path).parent_path();
if(!fs::exists(parent_path)) {
if(!fs::create_directories(parent_path, fs_error) || fs_error) {
logError(LOG_FT, "{} Failed to create required directories for file upload for {}: {}/{}", transfer->log_prefix(), absolute_path, fs_error.value(), fs_error.message());
result = FileInitializeResult::COUNT_NOT_CREATE_DIRECTORIES;
goto error_exit;
}
} else if(fs_error) {
logWarning(LOG_FT, "{} Failed to check for directory existence of {}: {}/{}. Assuming it exists", transfer->log_prefix(), parent_path.string(), fs_error.value(), fs_error.message());
}
open_flags = (unsigned) O_WRONLY | (unsigned) O_CREAT;
if(transfer->transfer->override_exiting)
open_flags |= (unsigned) O_TRUNC;
@ -211,7 +129,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
return FileInitializeResult::INVALID_TRANSFER_DIRECTION;
}
file_data.file_descriptor = open(absolute_path.c_str(), (int) open_flags, 0644);
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_) {
@ -219,7 +137,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
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(), absolute_path);
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:
@ -238,8 +156,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
result = FileInitializeResult::DISK_IS_READ_ONLY;
break;
default:
logWarning(LOG_FT, "{} Failed to start file {} for file {}: {}/{}", transfer->log_prefix(),
transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD ? "download" : "upload", absolute_path, errno_, strerror(errno_));
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;
}
@ -247,7 +164,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
}
}
this->file_system_->lock_file(absolute_path);
this->file_system_.lock_file(transfer->file.absolute_path);
transfer->file.file_locked = true;
if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
@ -255,7 +172,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
const auto errno_ = errno;
switch (errno_) {
case EACCES:
logWarning(LOG_FT, "{} File {} got inaccessible on truncating, but not on opening.", transfer->log_prefix(), absolute_path);
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;
@ -264,16 +181,16 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
goto error_exit;
case EIO:
logWarning(LOG_FT, "{} A disk IO error occurred while resizing file {}.", transfer->log_prefix(), absolute_path);
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(), absolute_path);
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(), absolute_path, errno_, strerror(errno_));
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) {
@ -283,24 +200,7 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
result = FileInitializeResult::FILE_SIZE_MISMATCH;
goto error_exit;
}
if(transfer->file.query_media_bytes && file_size > 0) {
auto new_pos = lseek(file_data.file_descriptor, 0, SEEK_SET);
if(new_pos < 0) {
logWarning(LOG_FT, "{} Failed to seek to file start: {}/{}", transfer->log_prefix(), errno, strerror(errno));
result = FileInitializeResult::FILE_SEEK_FAILED;
goto error_exit;
}
auto read = ::read(file_data.file_descriptor, transfer->file.media_bytes, TRANSFER_MEDIA_BYTES_LENGTH);
if(read <= 0) {
logWarning(LOG_FT, "{} Failed to read file media bytes: {}/{}", transfer->log_prefix(), errno, strerror(errno));
result = FileInitializeResult::FAILED_TO_READ_MEDIA_BYTES;
goto error_exit;
}
transfer->file.media_bytes_length = read;
}
}
{
auto new_pos = lseek(file_data.file_descriptor, transfer->transfer->file_offset, SEEK_SET);
if(new_pos < 0) {
@ -312,13 +212,13 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
result = FileInitializeResult::FILE_SEEK_FAILED;
goto error_exit;
}
logTrace(LOG_FT, "{} Seek to file offset {}. New actual offset is {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
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(absolute_path);
this->file_system_.unlock_file(transfer->file.absolute_path);
if(file_data.file_descriptor > 0)
::close(file_data.file_descriptor);
@ -330,11 +230,6 @@ FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr
void LocalFileTransfer::finalize_file_io(const std::shared_ptr<FileClient> &transfer,
std::unique_lock<std::shared_mutex> &state_lock) {
assert(state_lock.owns_lock());
if(!transfer->transfer) {
return;
}
auto absolute_path = transfer->transfer->absolute_file_path;
auto& file_data = transfer->file;
@ -347,31 +242,33 @@ void LocalFileTransfer::finalize_file_io(const std::shared_ptr<FileClient> &tran
continue;
}
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 if(file_data.next_client || this->disk_io.queue_tail == &file_data.next_client) {
FileClient* head{this->disk_io.queue_head};
while(head->file.next_client != &*transfer) {
assert(head->file.next_client);
head = head->file.next_client;
}
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;
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;
}
file_data.next_client = nullptr;
break;
}
}
state_lock.lock();
if(std::exchange(file_data.file_locked, false)) {
this->file_system_->unlock_file(absolute_path);
}
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);
@ -379,22 +276,18 @@ void LocalFileTransfer::finalize_file_io(const std::shared_ptr<FileClient> &tran
}
void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr<FileClient> &client) {
if(!client->file.file_descriptor) {
if(!client->file.file_descriptor)
return;
}
if(!client->transfer) {
if(!client->transfer)
return;
}
if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
if(client->state != FileClient::STATE_TRANSFERRING) {
if(client->state != FileClient::STATE_TRANSFERRING)
return;
}
if(client->disk_buffer.bytes > TRANSFER_MAX_CACHED_BYTES) {
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 */
/*
@ -404,9 +297,8 @@ void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr<FileClient> &clien
}
std::lock_guard dlock{this->disk_io.queue_lock};
if(client->file.next_client || this->disk_io.queue_tail == &client->file.next_client) {
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;
@ -415,63 +307,57 @@ void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr<FileClient> &clien
}
void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &client) {
if(!client->transfer) {
return;
}
if(!client->transfer) return;
if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
Buffer* buffer;
size_t buffer_left_size;
Buffer* buffer{nullptr};
size_t buffer_left_size{0};
while(true) {
{
std::lock_guard block{client->disk_buffer.mutex};
if(!client->disk_buffer.buffer_head) {
assert(client->disk_buffer.bytes == 0);
buffer_left_size = 0;
break;
}
buffer_left_size = client->disk_buffer.bytes;
buffer = ref_buffer(client->disk_buffer.buffer_head);
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) {
deref_buffer(buffer);
if(errno == EAGAIN) {
//TODO: Timeout?
this->enqueue_disk_io(client);
break;
}
if(written == 0) {
/* EOF, how the hell is this event possible?! */
auto offset_written = client->statistics.disk_bytes_write.total_bytes + client->transfer->file_offset;
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) });
this->invoke_aborted_callback(client, { TransferError::UNEXPECTED_DISK_EOF, strerror(errno) });
client->flush_disk_buffer();
{
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client, slock, true);
}
} else {
auto offset_written = client->statistics.disk_bytes_write.total_bytes + client->transfer->file_offset;
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->invoke_aborted_callback(client, { TransferError::DISK_IO_ERROR, 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) });
client->flush_disk_buffer();
{
std::unique_lock slock{client->state_mutex};
client->handle->disconnect_client(client, slock, true);
@ -481,61 +367,48 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &clien
} else {
buffer->offset += written;
assert(buffer->offset <= buffer->length);
if(buffer->length == buffer->offset) {
{
std::lock_guard block{client->disk_buffer.mutex};
if(client->disk_buffer.buffer_head == buffer) {
client->disk_buffer.buffer_head = buffer->next;
if(!buffer->next) {
client->disk_buffer.buffer_tail = &client->disk_buffer.buffer_head;
}
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->disk_buffer.bytes >= written);
client->disk_buffer.bytes -= written;
buffer_left_size = client->disk_buffer.bytes;
/* We have to deref the buffer twice since we've removed it from the list which owns us one reference */
/* Will not trigger a memory free since we're still holding onto one reference */
deref_buffer(buffer);
} else {
/* The buffer got removed */
}
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->disk_buffer.mutex};
if(client->disk_buffer.buffer_head == buffer) {
assert(client->disk_buffer.bytes >= written);
client->disk_buffer.bytes -= written;
buffer_left_size = client->disk_buffer.bytes;
} else {
/* The buffer got removed */
}
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 */
}
deref_buffer(buffer);
client->statistics.disk_bytes_write.increase_bytes(written);
client->statistics.disk_bytes_write += written;
}
}
if(buffer_left_size > 0) {
if(buffer_left_size > 0)
this->enqueue_disk_io(client);
} else if(client->state == FileClient::STATE_FLUSHING) {
this->test_disconnecting_state(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->disk_buffer.buffering_stopped) {
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_FLUSHING) {
client->flush_disk_buffer(); /* just in case, file download usually don't write to the disk */
return;
}
if(client->state == FileClient::STATE_DISCONNECTING) return;
while(true) {
constexpr auto buffer_capacity{4096};
@ -543,14 +416,9 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &clien
auto read = ::read(client->file.file_descriptor, buffer, buffer_capacity);
if(read <= 0) {
if(errno == EAGAIN) {
this->enqueue_disk_io(client);
return;
}
if(read == 0) {
/* EOF */
auto offset_send = client->statistics.disk_bytes_read.total_bytes + client->transfer->file_offset;
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));
@ -559,25 +427,41 @@ void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &clien
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->invoke_aborted_callback(client, { TransferError::UNEXPECTED_DISK_EOF, "" });
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 {
logWarning(LOG_FT, "{} Failed to read from file {} ({}/{}). Aborting transfer.", client->log_prefix(), client->transfer->absolute_file_path, errno, strerror(errno));
if(errno == EAGAIN) {
this->enqueue_disk_io(client);
return;
}
this->invoke_aborted_callback(client, { TransferError::DISK_IO_ERROR, strerror(errno) });
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);
}
}
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.increase_bytes(read);
client->statistics.file_transferred.increase_bytes(read);
client->statistics.disk_bytes_read += read;
client->statistics.file_bytes_transferred += read;
std::shared_lock slock{client->state_mutex};
if(buffer_full) {
//logTrace(LOG_FT, "{} Stopping buffering from disk. Buffer full ({}bytes)", client->log_prefix(), client->buffer.bytes);
logMessage(LOG_FT, "{} Stopping buffering from disk. Buffer full ({}bytes)", client->log_prefix(), client->buffer.bytes);
break;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +0,0 @@
//
// Created by WolverinDEV on 12/05/2020.
//
#include "./NetTools.h"
using namespace ts::server::file::networking;
NetworkThrottle NetworkThrottle::kNoThrottle{-1};

View File

@ -1,169 +0,0 @@
#pragma once
#include <cstddef>
#include <chrono>
#include <mutex>
#include <cassert>
#include <array>
#include <misc/spin_mutex.h>
#include <numeric>
namespace ts::server::file::networking {
struct NetworkThrottle {
constexpr static auto kThrottleTimespanMs{250};
typedef uint8_t span_t;
static NetworkThrottle kNoThrottle;
ssize_t max_bytes{0};
span_t current_index{0};
size_t bytes_send{0};
mutable spin_mutex mutex{};
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);
std::lock_guard slock{this->mutex};
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) {
std::lock_guard slock{this->mutex};
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);
std::lock_guard slock{this->mutex};
if(this->max_bytes <= 0) return false; /* we've to test here again, else out arithmetic will fail */
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);
std::lock_guard slock{this->mutex};
if(this->max_bytes <= 0) return false; /* we've to test here again, else out arithmetic will fail */
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 {
std::lock_guard slock{this->mutex};
if(this->max_bytes <= 0) return std::chrono::milliseconds{0};
return std::chrono::seconds{bytes / (this->max_bytes * (1000 / kThrottleTimespanMs))};
}
};
struct DualNetworkThrottle {
NetworkThrottle *left, *right;
explicit DualNetworkThrottle(NetworkThrottle* left, NetworkThrottle* right) : left{left}, right{right} {
assert(left);
assert(right);
}
[[nodiscard]] inline size_t bytes_left() const {
return std::min(this->left->bytes_left(), this->right->bytes_left());
}
[[nodiscard]] inline std::chrono::milliseconds expected_writing_time(size_t bytes) const {
return std::max(this->left->expected_writing_time(bytes), this->right->expected_writing_time(bytes));
}
[[nodiscard]] inline bool should_throttle(timeval& next_timestamp) const {
bool throttle = false;
timeval right_timestamp{};
throttle |= this->left->should_throttle(next_timestamp);
throttle |= this->right->should_throttle(right_timestamp);
if(!throttle) return false;
if(right_timestamp.tv_sec > next_timestamp.tv_sec || (right_timestamp.tv_sec == next_timestamp.tv_sec && right_timestamp.tv_usec > next_timestamp.tv_usec)) {
next_timestamp = right_timestamp;
}
return true;
}
inline bool increase_bytes(size_t bytes) {
bool result = false;
result |= this->left->increase_bytes(bytes);
result |= this->right->increase_bytes(bytes);
return result;
}
};
struct TransferStatistics {
constexpr static auto kMeasureTimespanMs{1000};
constexpr static auto kAverageTimeCount{60};
typedef uint8_t span_t;
size_t total_bytes{0};
size_t delta_bytes{0}; /* used for statistics propagation */
span_t span_index{0};
size_t span_bytes{0};
std::array<size_t, kAverageTimeCount> history{};
spin_mutex mutex{};
inline void 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 / kMeasureTimespanMs);
std::lock_guard slock{this->mutex};
this->total_bytes += bytes;
if(this->span_index != current_span) {
this->history[this->span_index % kAverageTimeCount] = std::exchange(this->span_bytes, 0);
}
this->span_index = current_span;
this->span_bytes += bytes;
}
[[nodiscard]] inline size_t take_delta() {
std::lock_guard slock{this->mutex};
assert(this->delta_bytes <= this->total_bytes);
auto delta = this->total_bytes - this->delta_bytes;
this->delta_bytes = this->total_bytes;
return delta;
}
[[nodiscard]] inline double current_bandwidth() const {
return (this->history[(this->span_index - 1) % kAverageTimeCount] * (double) 1000) / (double) kMeasureTimespanMs;
}
[[nodiscard]] inline double average_bandwidth() const {
return (std::accumulate(this->history.begin(), this->history.end(), 0UL) * (double) 1000) / (double) (kMeasureTimespanMs * kAverageTimeCount);
}
};
}

View File

@ -8,8 +8,6 @@
#include <experimental/filesystem>
#include <local_server/clnpath.h>
#include <event2/thread.h>
#include <include/files/Config.h>
#include <local_server/HTTPUtils.h>
namespace fs = std::experimental::filesystem;
@ -57,50 +55,8 @@ inline void print_query(const std::string& message, const file::filesystem::Abst
logWarning(LOG_FT, "{}: Unknown response state ({})!", message, (int) response.status);
}
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;
}
int main() {
evthread_use_pthreads();
{
std::map<std::string, std::string> query{};
http::parse_url_parameters("http://www.example.org/suche?stichwort=wiki&no-arg&1arg=&ausgabe=liste&test=test#bla=d&blub=c", query);
for(const auto& [key, value] : query)
std::cout << key << " => " << value << std::endl;
return 0;
}
auto log_config = std::make_shared<logger::LoggerConfig>();
log_config->terminalLevel = spdlog::level::trace;
@ -115,23 +71,6 @@ int main() {
auto instance = file::server();
{
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});
}
file::config::ssl_option_supplier = [options]{
return options;
};
}
#if 0
auto& fs = instance->file_system();
{
@ -201,7 +140,7 @@ int main() {
}
#endif
#if 0
#if 1
auto& ft = instance->file_transfer();
ft.callback_transfer_finished = [](const std::shared_ptr<file::transfer::Transfer>& transfer) {
@ -212,7 +151,7 @@ int main() {
logMessage(0, "Transfer started");
};
ft.callback_transfer_aborted = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const transfer::TransferStatistics&, const file::transfer::TransferError& error) {
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);
};

View File

@ -1 +0,0 @@
Test HTTPS file upload!

@ -1 +1 @@
Subproject commit b1565e43b01349fb9c66eb876711c115214cd823
Subproject commit be826ccb507a84d97c9c07397b05a8fe731f7ce5

View File

@ -18,7 +18,7 @@ using namespace license;
/*
* Requests/license: SELECT `tmp`.`keyId`, `tmp`.`key`, `tmp`.`ip`, `tmp`.`count`, `license_info`.`username`, `tmp`.`type` FROM (SELECT DISTINCT `license_request`.`keyId`, `key`, `license_request`.`ip`, COUNT(`license_request`.`keyId`) AS `count`, `license`.`type` FROM `license_request` INNER JOIN `license` ON `license`.`keyId` = `license_request`.`keyId` GROUP BY (`license_request`.`ip`)) AS `tmp` INNER JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId`
* Different IP's: SELECT `tmp`.`keyId`, `license_info`.`username`, COUNT(`ip`) FROM (SELECT DISTINCT `ip`, `keyId` FROM `license_request`) AS `tmp` LEFT JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId` GROUP BY (`tmp`.`keyId`) ORDER BY COUNT(`ip`) DESC
* Different IP's: SELECT `tmp`.`keyId`, `license_info`.`username`, COUNT(`ip`) FROM (SELECT DISTINCT `ip`, `keyId` FROM `license_request`) AS `tmp` LEFT JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId` GROUP BY (`tmp`.`keyId`)
*
* Requests (license) / ip: SELECT DISTINCT `ip`, COUNT(`ip`) FROM `license_request` WHERE `keyId` = ? GROUP BY `ip`
*
@ -26,8 +26,6 @@ using namespace license;
* //462
*
* SELECT DISTINCT(`ip`), `license_request`.`keyId`, `license_info`.`username` FROM `license_request` LEFT JOIN `license_info` ON `license_info`.`keyId` = `license_request`.`keyId` WHERE `timestamp` > (UNIX_TIMESTAMP() - 2 * 60 * 60) * 1000
* License too many request looking: SELECT `keyId`, `username`, COUNT(`ip`) FROM `unique_license_requests` GROUP BY `keyId` ORDER BY COUNT(`ip`) DESC
*
*
*
* Online clients: SELECT SUM(`clients`) FROM (SELECT DISTINCT(`ip`), `clients` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a`
@ -36,18 +34,6 @@ using namespace license;
*
* Empty instances: curl -ik "https://stats.teaspeak.de:27788?type=request&request_type=general" -X GET 2>&1 | grep "data:"
*/
//SELECT SUM(`clients`) FROM `history_online` WHERE `keyId` = 701 OR `keyId` = 795 OR `keyId` = 792 OR `keyId` = 582 OR `keyId` = 753 OR `keyId` = 764 OR `keyId` = 761 OR `keyId` = 796 WHERE `timestamp` > (UNIX_TIMESTAMP() - 2.1 * 60 * 60) * 1000
/*
* Extra users:
* - ServerSponsoring
* - Davide550
* - xDeyego?
* - latters
* - Pamonha
* - vova3639 (5 licenses)
*/
bool handle_command(string& line);
shared_ptr<server::database::DatabaseHandler> license_manager;

View File

@ -117,12 +117,12 @@ bool DatabaseHandler::setup(std::string& error) {
SET_VERSION(6);
case 6:
CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` VARCHAR(32), `succeeded` TINYINT);");
CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` INT, `succeeded` TINYINT);");
CIDX("CREATE INDEX `upgrade_id_timestamp` ON `license_upgrade_log` (`upgrade_id`, `timestamp`)");
SET_VERSION(7);
default:;
}
}
return true;
}

View File

@ -5,8 +5,8 @@
#include <LicenseRequest.pb.h>
namespace license::server::database {
typedef uint64_t license_key_id_t;
typedef uint64_t upgrade_id_t;
typedef size_t license_key_id_t;
typedef size_t upgrade_id_t;
class DatabaseHandler;
class KeyIdCache {

View File

@ -295,17 +295,17 @@ bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &clie
this->manager->logStatistic(client->key, client->unique_identifier, client->address(), pkt);
//TODO test stuff if its possible!
ts::proto::license::WebCertificate* proto_web_certificate{nullptr};
ts::proto::license::WebCertificate* web_certificate{nullptr};
if(pkt.has_web_cert_revision()) {
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] -------------------------------");
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Web cert revision : " + hex::hex(pkt.web_cert_revision()));
auto cert = this->web_certificate;
if(cert && cert->revision != pkt.web_cert_revision()) {
proto_web_certificate = new ts::proto::license::WebCertificate{};
proto_web_certificate->set_key(cert->key);
proto_web_certificate->set_certificate(cert->certificate);
proto_web_certificate->set_revision(cert->revision);
web_certificate = new ts::proto::license::WebCertificate{};
web_certificate->set_key(cert->key);
web_certificate->set_certificate(cert->certificate);
web_certificate->set_revision(cert->revision);
}
}
@ -314,7 +314,7 @@ bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &clie
response.set_reset_speach(pkt.speach_total() < 0);
response.set_speach_total_remote(pkt.speach_total());
response.set_speach_varianz_corrector(0);
response.set_allocated_web_certificate(proto_web_certificate);
response.set_allocated_web_certificate(web_certificate);
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT, response});
this->disconnectClient(client, "finished");

View File

@ -7,7 +7,6 @@
#include <misc/net.h>
#include <misc/endianness.h>
#include <json/json.h>
#include <log/LogUtils.h>
#include "StatisticManager.h"
#include "WebAPI.h"

View File

@ -346,7 +346,7 @@ namespace license {
std::chrono::system_clock::time_point creation;
bool deleted{false};
uint64_t upgrade_id{0};
uint32_t upgrade_id{0};
inline bool isNotExpired() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); }
};
@ -422,7 +422,7 @@ namespace license {
#ifdef GOOGLE_PROTOBUF_MESSAGE_H__
packet(PacketType packetId, const ::google::protobuf::Message&);
#endif
packet(PacketType packetId, std::nullptr_t);
packet(PacketType packetId, nullptr_t);
};
}
}

View File

@ -45,13 +45,12 @@ message ServerValidation {
}
message LicenseResponse {
required bool valid = 1;
optional string invalid_reason = 5; /* in protocol version 2 the blacklist.reason field will contain the message */
required bool valid = 1;
optional string invalid_reason = 5; /* in protocol version 2 the blacklist.reason field will contain the message */
required Blacklist blacklist = 2;
optional LicenseInfo license_info = 3; //Only available when ServerValidation::license_info = true
optional bool update_pending = 4; /* if an update is pending */
optional bool deprecate_third_party_clients = 6;
required Blacklist blacklist = 2;
optional LicenseInfo license_info = 3; //Only available when ServerValidation::license_info = true
optional bool update_pending = 4; /* if an update is pending */
}
message PropertyUpdateRequest {

View File

@ -8,7 +8,6 @@
#include <ThreadPool/ThreadHelper.h>
#include <misc/endianness.h>
#include <shared/include/license/client.h>
#include <log/LogUtils.h>
#include "shared/include/license/client.h"
#include "crypt.h"

2
music

@ -1 +1 @@
Subproject commit a4dabbb5c4c22ae4f566e22419d30abf93068a25
Subproject commit 8e1ce32ae0b03f54efc54e76cd118cf01057159c

1
rtclib

@ -1 +0,0 @@
Subproject commit 6bb3ce60505d6e63e2501c4d4d178f1212e71a26

View File

@ -3,7 +3,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 -Werror=delete-non-virtual-dtor")
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_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3")
@ -43,20 +43,21 @@ set(SERVER_SOURCE_FILES
# MySQLLibSSLFix.c
src/client/ConnectedClient.cpp
src/server/PrecomputedPuzzles.cpp
src/client/voice/PrecomputedPuzzles.cpp
src/client/voice/VoiceClient.cpp
src/client/voice/VoiceClientHandschake.cpp
src/client/voice/VoiceClientCommandHandler.cpp
src/client/voice/VoiceClientConnectionPacketHandler.cpp
src/client/voice/VoiceClientPacketHandler.cpp
src/client/voice/VoiceClientView.cpp
src/client/voice/PacketStatistics.cpp
src/TS3ServerClientManager.cpp
src/VirtualServer.cpp
src/FileServerHandler.cpp
src/TS3ServerHeartbeat.cpp
src/SignalHandler.cpp
src/server/VoiceServer.cpp
src/server/VoiceServerSocket.cpp
src/server/POWHandler.cpp
src/client/voice/VoiceClientConnection.cpp
src/client/command_handler/groups.cpp
#src/client/ConnectedClientCommandHandler.cpp
src/client/command_handler/channel.cpp
src/client/command_handler/client.cpp
src/client/command_handler/server.cpp
@ -65,10 +66,16 @@ set(SERVER_SOURCE_FILES
src/client/ConnectedClientNotifyHandler.cpp
src/VirtualServerManager.cpp
src/server/file/LocalFileServer.cpp
src/channel/ServerChannel.cpp
src/channel/ClientChannelView.cpp
src/client/file/FileClient.cpp
src/client/file/FileClientIO.cpp
src/Group.cpp
src/manager/BanManager.cpp
src/client/InternalClient.cpp
#src/weblist/WeblistClient.cpp
#src/weblist/WebList.cpp
src/client/DataClient.cpp
src/server/QueryServer.cpp
@ -77,11 +84,10 @@ set(SERVER_SOURCE_FILES
src/client/query/QueryClientNotify.cpp
src/manager/IpListManager.cpp
src/server/GlobalNetworkEvents.cpp
src/ConnectionStatistics.cpp
src/manager/TokenManager.cpp
src/manager/TokeManager.cpp
src/terminal/CommandHandler.cpp
@ -91,14 +97,13 @@ set(SERVER_SOURCE_FILES
src/manager/LetterManager.cpp
src/manager/PermissionNameManager.cpp
src/pinteraction/ApplicationInteraction.cpp
src/ServerManagerSnapshot.cpp
src/ServerManagerSnapshotDeploy.cpp
src/client/music/Song.cpp
src/music/PlayablePlaylist.cpp
src/InstanceHandler.cpp
src/InstanceHandlerSetup.cpp
src/InstanceHandlerPermissions.cpp
src/PermissionCalculator.cpp
src/Configuration.cpp
@ -117,6 +122,8 @@ set(SERVER_SOURCE_FILES
src/client/query/XMacroEventTypes.h
src/server/VoiceIOManager.cpp
src/server/WebIoManager.cpp
src/client/SpeakingClient.cpp
../shared/src/ssl/SSLManager.cpp
@ -124,41 +131,22 @@ set(SERVER_SOURCE_FILES
src/manager/SqlDataManager.cpp
src/ShutdownHelper.cpp
src/client/music/MusicQueue.cpp
src/lincense/TeamSpeakLicense.cpp
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/snapshots/music.cpp
src/snapshots/parser.cpp
src/groups/Group.cpp
src/groups/GroupManager.cpp
src/groups/GroupAssignmentManager.cpp
src/manager/ActionLogger.cpp
src/manager/ActionLoggerImpl.cpp
src/manager/ConversationManager.cpp
src/client/SpeakingClientHandshake.cpp
src/client/command_handler/music.cpp src/client/command_handler/file.cpp
src/client/voice/PacketEncoder.cpp
src/client/shared/ServerCommandExecutor.cpp
src/client/voice/CryptSetupHandler.cpp
src/client/shared/WhisperHandler.cpp
src/terminal/PipedTerminal.cpp
src/server/voice/UDPVoiceServer.cpp
src/server/voice/DatagramPacket.cpp
src/rtc/lib.cpp
)
src/client/command_handler/music.cpp src/client/command_handler/file.cpp)
if (COMPILE_WEB_CLIENT)
add_definitions(-DCOMPILE_WEB_CLIENT)
@ -167,18 +155,51 @@ if (COMPILE_WEB_CLIENT)
src/server/WebServer.cpp
src/client/web/WebClient.cpp
# src/server/web/WebRTCServer.cpp
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)
target_link_libraries(PermHelper
TeaSpeak
${LIBRARY_PATH_ED255}
TeaSpeak #Static
TeaLicenseHelper #Static
TeaMusic #Static
${LIBRARY_PATH_THREAD_POOL} #Static
${LIBRARY_PATH_TERMINAL} #Static
${LIBRARY_PATH_VARIBALES}
${LIBRARY_PATH_YAML}
pthread
stdc++fs
CXXTerminal::static
libevent::core libevent::pthreads
${StringVariable_LIBRARIES_STATIC}
)
${LIBEVENT_PATH}/libevent.a
${LIBEVENT_PATH}/libevent_pthreads.a
${LIBRARY_PATH_OPUS}
${LIBRARY_PATH_JSON}
${LIBRARY_PATH_PROTOBUF}
#${LIBWEBRTC_LIBRARIES} #ATTENTIAN! WebRTC does not work with crypto! (Already contains a crypto version)
${LIBRARY_TOM_CRYPT}
${LIBRARY_TOM_MATH}
#We're forsed to use boringssl caused by the fact that boringssl is already within webrtc!
#Require a so
sqlite3
${LIBRARY_PATH_BREAKPAD}
${LIBRARY_PATH_JDBC}
${LIBRARY_PATH_PROTOBUF}
${LIBRARY_PATH_DATA_PIPES}
${LIBRARY_PATH_BORINGSSL_SSL}
${LIBRARY_PATH_BORINGSSL_CRYPTO}
dl
jemalloc
)
add_executable(PermMapHelper helpers/PermMapGen.cpp)
target_link_libraries(PermMapHelper
@ -221,8 +242,8 @@ target_link_libraries(PermMapHelper
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
SET(CPACK_PACKAGE_VERSION_MINOR "5")
SET(CPACK_PACKAGE_VERSION_PATCH "6")
SET(CPACK_PACKAGE_VERSION_MINOR "4")
SET(CPACK_PACKAGE_VERSION_PATCH "14")
if (BUILD_TYPE_NAME EQUAL OFF)
SET(CPACK_PACKAGE_VERSION_DATA "beta")
elseif (BUILD_TYPE_NAME STREQUAL "")
@ -244,8 +265,6 @@ target_link_libraries(TeaSpeakServer
TeaLicenseHelper #Static
TeaMusic #Static
CXXTerminal::static #Static
TeaSpeak-FileServer
# TeaSpeak-FileRust
${StringVariable_LIBRARIES_STATIC}
${YAML_CPP_LIBRARIES}
pthread
@ -256,32 +275,28 @@ target_link_libraries(TeaSpeakServer
#Require a so
sqlite3
DataPipes::core::static
DataPipes::rtc::shared
breakpad::static
protobuf::libprotobuf
#jemalloc::shared
jemalloc::shared
tomcrypt::static
tommath::static
jsoncpp_lib
zstd::libzstd_static
${ed25519_LIBRARIES_STATIC}
)
if(EXISTS "${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc.so" AND NOT NO_RELEASE_RTC)
message("Linking to release librtc file")
target_link_libraries(TeaSpeakServer
${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc.so
)
elseif(EXISTS "${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc_development.so")
message("Linkding against debug libteaspeak_rtc_development.so")
target_link_libraries(TeaSpeakServer
${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc_development.so
)
else()
message(FATAL_ERROR "Missing librtc library file")
endif()
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 ()
# include_directories(${LIBRARY_PATH}/boringssl/include/)
target_link_libraries(TeaSpeakServer
@ -321,9 +336,10 @@ target_link_libraries(Snapshots-Permissions-Test PUBLIC
#Require a so
sqlite3
DataPipes::core::static
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/)

View File

@ -1,7 +1,10 @@
#include <fstream>
#include <query/Command.h>
#include <cstring>
#include <utility>
#include <functional> /* required from permission manager */
#include "log/LogUtils.h"
#include "Definitions.h"
#include "PermissionManager.h"
@ -132,7 +135,7 @@ int main(int argc, char** argv) {
group.target = line == "2" ? TARGET_QUERY : TARGET_SERVER;
read_line(file, line);
auto data = "perms " + line;
ts::Command group_parms = ts::Command::parse(data);
ts::Command group_parms = ts::Command::parse(pipes::buffer_view(data.data(), data.length()));
map<permission::PermissionType, permission::update::UpdatePermission> grantMapping;
for (int index = 0; index < group_parms.bulkCount(); index++) {
@ -181,7 +184,7 @@ int main(int argc, char** argv) {
group.target = TARGET_CHANNEL;
read_line(file, line);
auto data = "perms " + line;
ts::Command group_parms = ts::Command::parse(data);
ts::Command group_parms = ts::Command::parse(pipes::buffer_view(data.data(), data.length()));
map<permission::PermissionType, permission::update::UpdatePermission> grantMapping;
for (int index = 0; index < group_parms.bulkCount(); index++) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,3 @@
Locking Order:
Channel Tree:
1. Server Channel Tree
2. Client Channel Tree
CommandResult handleCommandClientUpdate(Command&);
CommandResult handleCommandClientEdit(Command&);
CommandResult handleCommandClientEdit(Command&, const std::shared_ptr<ConnectedClient>& /* target */);
@ -61,4 +55,4 @@ Move client acts like access server channel tree: write lock channel_tree_lock =
TODO: Some kind of perm channel lock
TODO: Fix handleCommandChannelEdit
Test: Channel hide & show with clients! Multiple clients as well!
Test: Channel hide & show with clients! Multible clients as well!

View File

@ -1,6 +1,4 @@
//include <iterator> /* Required for breakpad */
//#include <client/linux/handler/exception_handler.h>
#include <client/linux/handler/exception_handler.h>
#include <iostream>
#include <misc/strobf.h>
#include <CXXTerminal/QuickTerminal.h>
@ -12,13 +10,11 @@
#include "src/VirtualServer.h"
#include "src/InstanceHandler.h"
#include "src/server/QueryServer.h"
#include "src/server/file/LocalFileServer.h"
#include "src/terminal/CommandHandler.h"
#include "src/client/InternalClient.h"
#include "src/SignalHandler.h"
#include <dlfcn.h>
#include <sys/time.h>
#include <sys/resource.h>
#include "src/build.h"
using namespace std;
using namespace std::chrono;
@ -48,10 +44,8 @@ extern void testTomMath();
#define DB_NAME "TeaData.sqlite"
#endif
#include "src/client/music/internal_provider/channel_replay/ChannelProvider.h"
#include <codecvt>
#include <src/rtc/lib.h>
#include <src/terminal/PipedTerminal.h>
#include "src/client/music/internal_provider/channel_replay/ChannelProvider.h"
class CLIParser {
public:
@ -119,82 +113,6 @@ int main(int argc, char** argv) {
(void*) malloc_conf;
#endif
#if 0
{
//ts::property::list<ts::property::InstanceProperties>()
std::cout << "| Name | Type | Flags | Default Value | Description | \n";
std::cout << "|:-- | -- | -- | -- |:-- | \n";
for(const auto& property : ts::property::list<ts::property::VirtualServerProperties>()) {
std::cout << "| `" << property->name << "` | ";
switch(property->type_value) {
case ts::property::TYPE_STRING:
std::cout << "String | ";
break;
case ts::property::TYPE_BOOL:
std::cout << "Boolean | ";
break;
case ts::property::TYPE_SIGNED_NUMBER:
std::cout << "Signed number | ";
break;
case ts::property::TYPE_UNSIGNED_NUMBER:
std::cout << "Unsigned number | ";
break;
case ts::property::TYPE_FLOAT:
std::cout << "Float | ";
break;
default:
std::cout << "Unknown | ";
break;
}
std::string flags{};
if(property->flags & ts::property::FLAG_INTERNAL) { flags += ", internal"; }
if(property->flags & ts::property::FLAG_GLOBAL) { flags += ", global"; }
if(property->flags & ts::property::FLAG_SNAPSHOT) { flags += ", snapshot"; }
if(property->flags & ts::property::FLAG_SAVE) { flags += ", saved"; }
if(property->flags & ts::property::FLAG_SAVE_MUSIC) { flags += ", saved (music)"; }
if(property->flags & ts::property::FLAG_NEW) { flags += ", new"; }
if(property->flags & ts::property::FLAG_SERVER_VARIABLE) { flags += ", server variable"; }
if(property->flags & ts::property::FLAG_SERVER_VIEW) { flags += ", server view variable"; }
if(property->flags & ts::property::FLAG_CLIENT_VARIABLE) { flags += ", client variable"; }
if(property->flags & ts::property::FLAG_CLIENT_VIEW) { flags += ", client view variable"; }
if(property->flags & ts::property::FLAG_CLIENT_INFO) { flags += ", client info variable"; }
if(property->flags & ts::property::FLAG_CHANNEL_VARIABLE) { flags += ", channel variable"; }
if(property->flags & ts::property::FLAG_CHANNEL_VIEW) { flags += ", channel view variable"; }
// FLAG_GROUP_VIEW = FLAG_CHANNEL_VIEW << 1UL,
if(property->flags & ts::property::FLAG_INSTANCE_VARIABLE) { flags += ", instance variable"; }
if(property->flags & ts::property::FLAG_PLAYLIST_VARIABLE) { flags += ", playlist variable"; }
if(property->flags & ts::property::FLAG_USER_EDITABLE) { flags += ", editable"; }
if(!flags.empty()) {
std::cout << flags.substr(2);
}
std::cout << "| ";
if(property->default_value.empty()) {
std::cout << "empty";
} else {
std::cout << "`" << property->default_value << "` ";
}
std::cout << "| ";
std::cout << "No description ";
std::cout << "| ";
std::cout << " \n";
}
return 0;
}
#endif
CLIParser arguments(argc, argv);
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
@ -208,7 +126,7 @@ int main(int argc, char** argv) {
if(!arguments.cmdOptionExists("--no-terminal")) {
terminal::install();
if(!terminal::active()) { cerr << "could not setup terminal!" << endl; return -1; }
if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
}
if(arguments.cmdOptionExists("--help") || arguments.cmdOptionExists("-h")) {
@ -298,21 +216,6 @@ int main(int argc, char** argv) {
}
}
/*
{
auto a = malloc(10); // 0xa04010
auto b = malloc(10); // 0xa04030
auto c = malloc(10); // 0xa04050
free(a);
free(b); // To bypass "double free or corruption (fasttop)" check
free(a); // Double Free !!
auto d = malloc(10); // 0xa04010
auto e = malloc(10); // 0xa04030
auto f = malloc(10); // 0xa04010 - Same as 'd' !
}
*/
/*
std::string error;
if(!interaction::waitForAttach(error)){
cerr << "Rsult: " << error << endl;
@ -327,16 +230,14 @@ int main(int argc, char** argv) {
if(true) return 0;
*/
//debugMessage(LOG_GENERAL, "Sizeof ViewEntry {} Sizeof LinkedTreeEntry {} Sizeof shared_ptr<ViewEntry> {} Sizeof ClientChannelView {}", sizeof(ts::ViewEntry), sizeof(ts::TreeView::LinkedTreeEntry), sizeof(shared_ptr<ts::ViewEntry>), sizeof(ts::ClientChannelView));
{
//http://git.mcgalaxy.de/WolverinDEV/tomcrypt/blob/develop/src/misc/crypt/crypt_inits.c#L40-86
std::string descriptors = "LTGE";
bool crypt_init = false;
for(const auto& c : descriptors) {
if((crypt_init |= crypt_mp_init(&c))) {
for(const auto& c : descriptors)
if((crypt_init |= crypt_mp_init(&c)))
break;
}
}
if(!crypt_init) {
logCritical(LOG_GENERAL, "Could not initialise libtomcrypt mp descriptors!");
return 1;
@ -386,18 +287,6 @@ int main(int argc, char** argv) {
};
logger::updateLogLevels();
logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true));
{
debugMessage(LOG_GENERAL, "Initializing RTP library version {}", ts::rtc::version());
std::string error;
if(!ts::rtc::initialize(error)) {
logCritical(LOG_GENERAL, "Failed to initialize RTC library: {}", error);
return EXIT_FAILURE;
}
}
if(ts::config::license_original && ts::config::license_original->data.type != license::LicenseType::DEMO){
logMessageFmt(true, LOG_GENERAL, strobf("[]---------------------------------------------------------[]").string());
logMessageFmt(true, LOG_GENERAL, strobf(" §aThank you for buying the TeaSpeak-§lPremium-§aSoftware! ").string());
@ -421,7 +310,7 @@ int main(int argc, char** argv) {
{
rlimit rlimit{0, 0};
//forum.teaspeak.de/index.php?threads/2570/
constexpr auto seek_help_message = "For more help visit the forum and read this thread (https://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));
@ -444,6 +333,7 @@ int main(int argc, char** argv) {
rlimit_updates:;
}
logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true));
logMessage(LOG_GENERAL, "Starting music providers");
if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§aloading music§7]");
@ -486,7 +376,7 @@ int main(int argc, char** argv) {
logCriticalFmt(true, LOG_GENERAL, "Could not initialize SQL!");
if(errorMessage.find("database is locked") != string::npos) {
logCriticalFmt(true, LOG_GENERAL, "----------------------------[ ATTENTION ]----------------------------");
logCriticalFmt(true, LOG_GENERAL, "{:^69}", "Your database is already in use!");
logCriticalFmt(true, LOG_GENERAL, "{:^69}", "You're database is already in use!");
logCriticalFmt(true, LOG_GENERAL, "{:^69}", "Stop the other instance first!");
logCriticalFmt(true, LOG_GENERAL, "----------------------------[ ATTENTION ]----------------------------");
} else {
@ -516,7 +406,6 @@ int main(int argc, char** argv) {
}
}
terminal::initialize_pipe(arguments.get_option("--pipe-path"));
if(terminal::instance()) terminal::instance()->setPrompt("§7> §f");
while(mainThreadActive) {
usleep(5 * 1000);
@ -524,23 +413,11 @@ int main(int argc, char** argv) {
if(terminal::instance()) {
if(terminal::instance()->linesAvailable() > 0){
while(!(line = terminal::instance()->readLine("§7> §f")).empty())
threads::Thread(THREAD_DETACHED, [line]{
terminal::chandler::CommandHandle handle{};
handle.command = line;
if(!terminal::chandler::handleCommand(handle)) {
for(const auto& response : handle.response)
logErrorFmt(true, LOG_GENERAL, "{}", response);
} else {
for(const auto& response : handle.response)
logMessageFmt(true, LOG_GENERAL, "{}", response);
}
});
threads::Thread(THREAD_DETACHED, [line](){ terminal::chandler::handleCommand(line); });
}
}
}
terminal::finalize_pipe();
stopApp:
logMessageFmt(true, LOG_GENERAL, "Stopping application");
@ -563,42 +440,3 @@ int main(int argc, char** argv) {
mainThreadDone = true;
return 0;
}
/* Fix for Virtuzzo 7 where sometimes the pthread create fails! */
typedef int (*pthread_create_t)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
pthread_create_t original_pthread_create{nullptr};
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) {
if(!original_pthread_create) {
original_pthread_create = (pthread_create_t) dlsym(RTLD_NEXT, "pthread_create");
if(!original_pthread_create) {
std::cerr << "[CRITICAL] Missing original pthread_create function. Aborting execution!" << std::endl;
std::abort();
}
}
int result, attempt{0}, sleep{5};
while((result = original_pthread_create(thread, attr, start_routine, arg)) != 0 && errno == EAGAIN) {
if(attempt > 55) {
std::cerr << "[CRITICAL] pthread_create(...) cause EAGAIN for the last 50 attempts (~4.7seconds)! Aborting application execution!" << std::endl;
std::abort();
} else if(attempt > 5) {
/* let some other threads do work */
pthread_yield();
} else if(attempt == 0) {
std::string message{"[CRITICAL] Failed to spawn thread (Resource temporarily unavailable). Trying to recover."};
std::cerr << message << std::endl;
}
//std::string message{"[CRITICAL] pthread_create(...) cause EAGAIN! Trying again in " + std::to_string(sleep) + "usec (Attempt: " + std::to_string(attempt) + ")"};
//std::cerr << message << std::endl;
usleep(sleep);
attempt++;
sleep = (int) (sleep * 1.25);
}
if(attempt > 0) {
std::string message{"[CRITICAL] Successfully recovered from pthread_create() EAGAIN error. Took " + std::to_string(attempt) + " attempts."};
std::cerr << message << std::endl;
}
return result;
}

View File

@ -11,7 +11,6 @@ cp env/buildVersion.txt .
rm -r env
mkdir env
cd env
[[ $? -ne 0 ]] && {
echo "Failed to create the env"
exit 1
@ -50,12 +49,6 @@ mv buildVersion.txt env/buildVersion.txt
exit 1
}
./make_symbol.sh || {
echo "Failed to generate debug symbols"
exit 1
}
./package_server.sh "${BUILD_PATH}" || {
echo "Failed to package server! ($?)"
exit 1

View File

@ -3,10 +3,15 @@
#Required libraries:
# "libssl.so"
# "libcrypto.so"
# "libDataPipes-Rtc-Shared.so"
# "libjemalloc.so.2"
# "libsqlite3.so.0"
# "libTeaMusic.so"
# "libteaspeak_rtc.so"
# "libnice.so.10"
# "libpcre.so.3" (only for web)
# "libgobject-2.0.so.0" (only for web)
# "libglib-2.0.so.0" (only for web)
# "libffi.so.7"
[[ -z "${build_os_type}" ]] && { echo "missing build os type"; exit 1; }
[[ -z "${build_os_arch}" ]] && { echo "missing build os arch"; exit 1; }
@ -44,6 +49,11 @@ cp "${library_path}" . || { echo "failed to copy libssl.so.1.1"; exit 1; }
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; }
_dp_path="$library_path"
# Setting up Sqlite3
query_system_link "libsqlite3.so.0"
cp "${library_path}" . || { echo "failed to copy libsqlite3.so.0"; exit 1; }
@ -56,9 +66,35 @@ cp "${library_path}" . || { echo "failed to copy libjemalloc.so.2"; exit 1; }
library_path=$(realpath "../../../../MusicBot/libs/libTeaMusic.so")
cp "$library_path" . || { echo "failed to copy libTeaMusic.so"; exit 1; }
query_system_link "libteaspeak_rtc.so"
cp "${library_path}" . || { echo "failed to copy libteaspeak_rtc.so"; exit 1; }
if ldd "../../../environment/TeaSpeakServer" | grep -q "libnice.so.10"; then
echo "Adding web libraries"
# Setting up libnice
library_path=$(realpath "${library_base}/libnice/${build_os_type}_${build_os_arch}/lib/libnice.so.10")
cp "$library_path" libnice.so.10 || { echo "failed to copy libnice.so.10"; exit 1; }
glib_libs=$(realpath "${library_base}//glibc/${build_os_type}_${build_os_arch}/lib/"*"/")
cp "$glib_libs/libgobject-2.0.so.0" . || { echo "failed to copy libgobject-2.0.so.0"; exit 1; }
cp "$glib_libs/libgmodule-2.0.so.0" . || { echo "failed to copy libgmodule-2.0.so.0"; exit 1; }
cp "$glib_libs/libglib-2.0.so.0" . || { echo "failed to copy libglib-2.0.so.0"; exit 1; }
cp "$glib_libs/libgio-2.0.so.0" . || { echo "failed to copy libgio-2.0.so.0"; exit 1; }
cp "$glib_libs/libffi.so.7" . || { echo "failed to copy libffi.so.7"; exit 1; }
# "libgobject-2.0.so.0" (only for web)
# "libglib-2.0.so.0" (only for web)
# Setting up libpcre
query_system_link "libpcre.so.3" "$_dp_path"
cp "${library_path}" . || { echo "failed to copy libpcre.so.3"; exit 1; }
fi
# Doing some prostprocessing
chmod 755 *
for file in *.so*; do
echo "Editing rpath for $file"
strip -s "$file"
patchelf --set-rpath "./libs/:./" "$file"
done
echo "All libraries have been copied successfully"

View File

@ -38,5 +38,4 @@ function create_dump() {
create_dump "env" "TeaSpeakServer"
create_dump "env/providers" "000ProviderFFMpeg.so"
create_dump "env/providers" "001ProviderYT.so"
create_dump "env/libs/" "libteaspeak_rtc.so"
echo "Created dump symbols!"

View File

@ -1,6 +1,5 @@
#!/usr/bin/env bash
# shellcheck disable=SC2207
BUILD_INFO=($(cat build_version.txt))
BUILD_FULL_NAME=${BUILD_INFO[0]}
BUILD_NAME=${BUILD_INFO[1]}
@ -23,22 +22,18 @@ echo -e "# Version: ${BUILD_FULL_NAME}
{\"build_name\": \"${BUILD_FULL_NAME}\", \"build_version\": \"${BUILD_NAME}\", \"build_index\": ${BUILD_VERSION}}" > buildVersion.txt
echo "Stripping symbols"
#Create a copy and save unstripped
cp TeaSpeakServer TeaSpeakServerTmp
rm TeaSpeakServer
mv TeaSpeakServerTmp TeaSpeakServer
strip -s -p -v TeaSpeakServer || { echo "failed to strip symbols!"; exit 1; }
patchelf --set-rpath ./libs/ TeaSpeakServer || { echo "failed to set rpath!"; exit 1; }
cd libs/ || exit 1
for file in *.so*; do
echo "Editing rpath for $file"
strip --strip-all "$file"
patchelf --set-rpath "./libs/:./" "$file"
done
cd ..
tar --dereference -cvf - * | gzip -f -9 > "../${BUILD_FILENAME}"
[[ $? -ne 0 ]] && { echo "failed to package server"; exit 1; }
cd ..
rm -r finalenv
./make_symbol.sh
echo "Package created (${BUILD_FILENAME})"

View File

@ -1,6 +1,5 @@
#include <utility>
#include <log/LogUtils.h>
#include "Configuration.h"
#include "build.h"
#include "../../license/shared/include/license/license.h"
@ -42,20 +41,11 @@ uint16_t ts::config::binding::DefaultFilePort;
std::string config::server::DefaultServerVersion;
std::string config::server::DefaultServerPlatform;
LicenseType config::server::DefaultServerLicense;
bool config::server::enable_teamspeak_weblist;
bool config::server::strict_ut8_mode;
bool config::server::show_invisible_clients_as_online;
bool config::server::disable_ip_saving;
bool config::server::default_music_bot;
/*
* namespace limits {
extern size_t poke_message_length;
extern size_t talk_power_request_message_length;
}
*/
size_t config::server::limits::poke_message_length;
size_t config::server::limits::talk_power_request_message_length;
size_t config::server::limits::afk_message_length;
ssize_t config::server::max_virtual_server;
bool config::server::badges::allow_badges;
bool config::server::badges::allow_overwolf;
@ -63,14 +53,13 @@ bool config::server::authentication::name;
bool config::server::clients::teamspeak;
std::string config::server::clients::extra_welcome_message_teamspeak;
std::string config::server::clients::teamspeak_not_allowed_message;
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;
std::string config::server::clients::teaweb_not_allowed_message;
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;
@ -91,7 +80,6 @@ bool config::voice::allow_session_reinitialize;
std::string config::query::motd;
std::string config::query::newlineCharacter;
size_t config::query::max_line_buffer;
int config::query::sslMode;
std::string config::query::ssl::certFile;
std::string config::query::ssl::keyFile;
@ -121,11 +109,16 @@ std::string config::messages::timeout::packet_resend_failed;
std::string config::messages::timeout::connection_reinitialized;
size_t config::threads::ticking;
size_t config::threads::command_execute;
size_t config::threads::network_events;
size_t config::threads::voice::execute_limit;
size_t config::threads::voice::execute_per_server;
size_t config::threads::voice::events_per_server;
size_t config::threads::voice::io_min;
size_t config::threads::voice::io_per_server;
size_t config::threads::voice::io_limit;
bool config::threads::voice::bind_io_thread_to_kernel_thread;
size_t config::threads::music::execute_limit;
size_t config::threads::music::execute_per_bot;
size_t config::threads::web::io_loops;
std::string config::messages::teamspeak_permission_editor;
bool config::web::activated;
@ -344,7 +337,7 @@ void read_bindings(YAML::Node& root, const std::deque<std::shared_ptr<EntryBindi
inline string apply_comments(stringstream &in, map<string, deque<string>>& comments);
std::deque<std::shared_ptr<EntryBinding>> create_local_bindings(int& version, std::string& license);
#define CURRENT_CONFIG_VERSION 16
#define CURRENT_CONFIG_VERSION 15
static std::string _config_path;
vector<string> config::parseConfig(const std::string& path) {
_config_path = path;
@ -459,13 +452,6 @@ vector<string> config::parseConfig(const std::string& path) {
nodes_key = "2";
}
}
case 15: {
auto nodes_key = resolveNode(config, "web.webrtc.stun.ip").back();
if(nodes_key.IsDefined() && nodes_key.as<std::string>() == "127.0.0.1") {
nodes_key.reset();
resolveNode(config, "web.webrtc.stun.enabled").back() = "1";
}
}
default:
break;
}
@ -580,14 +566,6 @@ std::vector<std::string> config::reload() {
auto bindings = create_bindings();
read_bindings(config, bindings, FLAG_RELOADABLE);
const auto& logConfig = logger::currentConfig();
if(logConfig) {
logConfig->logfileLevel = (spdlog::level::level_enum) ts::config::log::logfileLevel;
logConfig->terminalLevel = (spdlog::level::level_enum) ts::config::log::terminalLevel;
logger::updateLogLevels();
}
} catch(const YAML::Exception& ex) {
errors.emplace_back("Could not read config: " + ex.msg + " @" + to_string(ex.mark.line) + ":" + to_string(ex.mark.column));
return errors;
@ -1029,9 +1007,8 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
{
BIND_GROUP(log)
{
CREATE_BINDING("level", FLAG_RELOADABLE);
CREATE_BINDING("level", 0);
BIND_INTEGRAL(config::log::logfileLevel, spdlog::level::debug, spdlog::level::trace, spdlog::level::off);
ADD_NOTE_RELOADABLE();
ADD_DESCRIPTION("The log level within the log files");
ADD_DESCRIPTION("Available types:");
ADD_DESCRIPTION(" 0: Trace");
@ -1043,9 +1020,8 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION(" 6: Off");
}
{
CREATE_BINDING("terminal_level", FLAG_RELOADABLE);
CREATE_BINDING("terminal_level", 0);
BIND_INTEGRAL(config::log::terminalLevel, spdlog::level::info, spdlog::level::trace, spdlog::level::off);
ADD_NOTE_RELOADABLE();
ADD_DESCRIPTION("The log level within the TeaSpeak server terminal");
ADD_DESCRIPTION("Available types:");
ADD_DESCRIPTION(" 0: Trace");
@ -1123,18 +1099,13 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
{
BIND_GROUP(query);
{
CREATE_BINDING("nl_char", FLAG_RELOADABLE);
CREATE_BINDING("nl_char", 0);
BIND_STRING(config::query::newlineCharacter, "\n");
ADD_DESCRIPTION("Change the query newline character");
ADD_NOTE("NOTE: TS3 - Compatible bots may require \"\n\r\"");
}
{
CREATE_BINDING("max_line_buffer", FLAG_RELOADABLE);
BIND_INTEGRAL(config::query::max_line_buffer, 1024 * 1024, 1024 * 8, 1024 * 1024 * 512);
ADD_DESCRIPTION("Max number of characters one query command could contain.");
}
{
CREATE_BINDING("motd", FLAG_RELOADABLE);
CREATE_BINDING("motd", 0);
BIND_STRING(config::query::motd, "TeaSpeak\nWelcome on the TeaSpeak ServerQuery interface.\n");
ADD_DESCRIPTION("The query welcome message");
@ -1150,7 +1121,7 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION("Available modes:");
ADD_DESCRIPTION(" 0: Disabled");
ADD_DESCRIPTION(" 1: Enabled (Enforced encryption)");
ADD_DESCRIPTION(" 2: Hybrid (Prefer encryption but fallback when it isn't available)");
ADD_DESCRIPTION(" 2: Hybrid (Prefer encryption but fallback when it isnt available)");
}
{
BIND_GROUP(ssl);
@ -1177,17 +1148,17 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
"The start point for the server creation still apply.");
}
{
CREATE_BINDING("notifymute", FLAG_RELOADABLE);
CREATE_BINDING("notifymute", 0);
BIND_BOOL(config::voice::notifyMuted, false);
ADD_DESCRIPTION("Enable/disable the mute notify");
}
{
CREATE_BINDING("suppress_myts_warnings", FLAG_RELOADABLE);
CREATE_BINDING("suppress_myts_warnings", 0);
BIND_BOOL(config::voice::suppress_myts_warnings, true);
ADD_DESCRIPTION("Suppress the MyTS integration warnings");
}
{
CREATE_BINDING("allow_session_reinitialize", FLAG_RELOADABLE);
CREATE_BINDING("allow_session_reinitialize", 0);
BIND_BOOL(config::voice::allow_session_reinitialize, true);
ADD_DESCRIPTION("Enable/disable fast session reinitialisation.");
ADD_SENSITIVE();
@ -1221,19 +1192,19 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
}
}
{
CREATE_BINDING("connect_limit", FLAG_RELOADABLE);
CREATE_BINDING("connect_limit", 0);
BIND_INTEGRAL(config::voice::connectLimit, 10, 0, 1024);
ADD_DESCRIPTION("Maximum amount of join attempts per second.");
ADD_NOTE("A value of zero means unlimited");
}
{
CREATE_BINDING("client_connect_limit", FLAG_RELOADABLE);
CREATE_BINDING("client_connect_limit", 0);
BIND_INTEGRAL(config::voice::clientConnectLimit, 3, 0, 1024);
ADD_DESCRIPTION("Maximum amount of join attempts per second per ip.");
ADD_NOTE("A value of zero means unlimited");
}
{
CREATE_BINDING("protocol.experimental_31", FLAG_RELOADABLE);
CREATE_BINDING("protocol.experimental_31", 0);
BIND_BOOL(config::experimental_31, false);
ADD_DESCRIPTION("Enables the newer and safer protocol based on TeamSpeak's documented standard");
ADD_NOTE("An invalid protocol chain could lead clients to calculate a wrong shared secret result");
@ -1278,21 +1249,24 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
BIND_BOOL(config::server::delete_old_bans, true);
ADD_DESCRIPTION("Enable/disable the deletion of old bans within the database");
}
#if 0
{
CREATE_BINDING("delete_missing_icon_permissions", 0);
BIND_BOOL(config::server::delete_missing_icon_permissions, true);
ADD_DESCRIPTION("Enable/disable the deletion of invalid icon id permissions");
}
#endif
{
CREATE_BINDING("strict_ut8_mode", FLAG_RELOADABLE);
CREATE_BINDING("allow_weblist", 0);
BIND_BOOL(config::server::enable_teamspeak_weblist, true);
ADD_DESCRIPTION("Enable/disable weblist reports globally! (Server setting wount be disabled, they will be just not send)");
}
{
CREATE_BINDING("strict_ut8_mode", 0);
BIND_BOOL(config::server::strict_ut8_mode, false);
ADD_DESCRIPTION("If enabled an error will be throws on invalid UTF-8 characters within the protocol (Query & Client).");
ADD_DESCRIPTION("Else the property pair will be dropped silently!");
}
{
CREATE_BINDING("show_invisible_clients", FLAG_RELOADABLE);
CREATE_BINDING("show_invisible_clients", 0);
BIND_BOOL(config::server::show_invisible_clients_as_online, true);
ADD_DESCRIPTION("Allow anybody to send text messages to clients which are in invisible channels");
}
@ -1302,7 +1276,7 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION("Disable the saving of IP addresses within the database.");
}
{
CREATE_BINDING("default_music_bot", FLAG_RELOADABLE);
CREATE_BINDING("default_music_bot", 0);
BIND_BOOL(config::server::default_music_bot, true);
ADD_DESCRIPTION("Add by default a new music bot to each created virtual server.");
}
@ -1312,27 +1286,6 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION("Set the limit for maximal virtual servers. -1 means unlimited.");
ADD_NOTE_RELOADABLE();
}
{
BIND_GROUP(limits);
{
CREATE_BINDING("poke_message_length", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::limits::poke_message_length, 1024, 1, 262144);
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("talk_power_request_message_length", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::limits::talk_power_request_message_length, 50, 1, 262144);
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("afk_message_length", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::limits::afk_message_length, 50, 1, 262144);
ADD_NOTE_RELOADABLE();
}
}
{
/*
BIND_GROUP(badges);
@ -1369,27 +1322,19 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("not_allowed_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::teamspeak_not_allowed_message, "");
ADD_DESCRIPTION("Change the message, displayed when denying the access to the server");
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", 0); /* No reload flag else we could just manipulate the licensing thing */
BIND_STRING(config::server::clients::extra_welcome_message_teamspeak, "");
ADD_DESCRIPTION("Add an extra welcome message for TeamSpeak client users");
/* Don't make the reloadable else you could just disable the outdated client server join message */
//ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teamspeak_message_type", 0); /* No reload flag else we could just manipulate the licensing thing */
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");
/* Don't make the reloadable else you could just disable the outdated client server join message */
//ADD_NOTE_RELOADABLE();
ADD_NOTE_RELOADABLE();
}
/* TeaSpeak */
@ -1401,6 +1346,7 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_NOTE_RELOADABLE();
}
*/
config::server::clients::teaspeak = true;
{
CREATE_BINDING("teaspeak_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::extra_welcome_message_teaspeak, "");
@ -1425,12 +1371,6 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Web client to join the server.");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("not_allowed_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::teaweb_not_allowed_message, "");
ADD_DESCRIPTION("Chaneg the message, displayed when denying the access to the server");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaweb_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::extra_welcome_message_teaweb, "");
@ -1464,14 +1404,12 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION("Disable/enable the possibility to connect via the TeaSpeak web client");
ADD_NOTE("If you've disabled this feature the TeaClient wound be able to join too.");
}
/* LibNice has been build without this
{
CREATE_BINDING("upnp", 0);
BIND_BOOL(config::web::enable_upnp, false);
ADD_DESCRIPTION("Disable/enable UPNP support");
ADD_SENSITIVE();
}
*/
{
BIND_GROUP(ssl)
{
@ -1532,45 +1470,45 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
}
{
CREATE_BINDING("webrtc.port_min", FLAG_RELOADABLE);
CREATE_BINDING("webrtc.port_min", 0);
BIND_INTEGRAL(config::web::webrtc_port_min, 50000, 0, 65535);
ADD_DESCRIPTION("Define the port range within the web client and TeaClient operates in");
ADD_DESCRIPTION("A port of zero stands for no limit");
ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)");
}
{
CREATE_BINDING("webrtc.port_max", FLAG_RELOADABLE);
CREATE_BINDING("webrtc.port_max", 0);
BIND_INTEGRAL(config::web::webrtc_port_max, 56000, 0, 65535);
ADD_DESCRIPTION("Define the port range within the web client and TeaClient operates in");
ADD_DESCRIPTION("A port of zero stands for no limit");
ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)");
}
{
CREATE_BINDING("webrtc.stun.enabled", FLAG_RELOADABLE);
BIND_INTEGRAL(config::web::stun_enabled, true, false, true);
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", FLAG_RELOADABLE);
CREATE_BINDING("webrtc.stun.host", 0);
BIND_STRING(config::web::stun_host, "stun.l.google.com");
ADD_DESCRIPTION("The address of the stun server to use.");
ADD_DESCRIPTION("Stun hostname");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.stun.port", FLAG_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", FLAG_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", FLAG_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();
@ -1822,24 +1760,53 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
}
}
{
CREATE_BINDING("command_execute", 0);
BIND_INTEGRAL(config::threads::command_execute, 4, 1, 128);
ADD_DESCRIPTION("Command executors");
ADD_SENSITIVE();
}
{
CREATE_BINDING("network_events", 0);
BIND_INTEGRAL(config::threads::network_events, 4, 1, 128);
ADD_DESCRIPTION("Network event loops");
CREATE_BINDING("web.io_loops", 0);
BIND_INTEGRAL(config::threads::web::io_loops, 2, 1, 128);
ADD_DESCRIPTION("Thread pool size for the ticking task of a VirtualServer");
ADD_SENSITIVE();
}
{
BIND_GROUP(voice)
{
CREATE_BINDING("events_per_server", 0);
BIND_INTEGRAL(config::threads::voice::events_per_server, 4, 1, 128);
ADD_DESCRIPTION("Kernel events per server socket");
ADD_DESCRIPTION("This value is upper bound to threads.network_events and should not exceed it.");
BIND_INTEGRAL(config::threads::voice::events_per_server, 2, 1, 16);
ADD_DESCRIPTION("Kernel events per server");
ADD_SENSITIVE();
}
{
CREATE_BINDING("execute_per_server", 0);
BIND_INTEGRAL(config::threads::voice::execute_per_server, 2, 1, 128);
ADD_DESCRIPTION("Threads per server for command executing");
ADD_SENSITIVE();
}
{
CREATE_BINDING("execute_limit", 0);
BIND_INTEGRAL(config::threads::voice::execute_limit, 5, 1, 1024);
ADD_DESCRIPTION("Max number of threads for command handling threads within the instance");
ADD_SENSITIVE();
}
{
CREATE_BINDING("io_min", 0);
BIND_INTEGRAL(config::threads::voice::io_min, 2, 1, 1024);
ADD_DESCRIPTION("Minimum IO threads");
ADD_SENSITIVE();
}
{
CREATE_BINDING("io_per_server", 0);
BIND_INTEGRAL(config::threads::voice::io_per_server, 2, 1, 64);
ADD_DESCRIPTION("IO Thread increase per server");
ADD_SENSITIVE();
}
{
CREATE_BINDING("io_limit", 0);
BIND_INTEGRAL(config::threads::voice::io_limit, 5, 1, 1024);
ADD_DESCRIPTION("Max IO threads");
ADD_SENSITIVE();
}
{
CREATE_BINDING("bind_io_thread_to_kernel_thread", 0);
BIND_BOOL(config::threads::voice::bind_io_thread_to_kernel_thread, false);
ADD_DESCRIPTION("Bind each IO thread to one kernel thread to improve socket IO performance");
ADD_SENSITIVE();
}
}

View File

@ -73,16 +73,11 @@ namespace ts::config {
extern bool strict_ut8_mode;
extern bool enable_teamspeak_weblist;
extern bool show_invisible_clients_as_online;
extern bool disable_ip_saving;
extern bool default_music_bot;
namespace limits {
extern size_t poke_message_length;
extern size_t talk_power_request_message_length;
extern size_t afk_message_length;
}
namespace badges {
extern bool allow_overwolf;
extern bool allow_badges;
@ -102,15 +97,14 @@ namespace ts::config {
};
extern bool teamspeak;
extern std::string teamspeak_not_allowed_message;
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 teaweb_not_allowed_message;
extern std::string extra_welcome_message_teaweb;
extern WelcomeMessageType extra_welcome_message_type_teaweb;
@ -156,7 +150,6 @@ namespace ts::config {
namespace query {
extern std::string motd;
extern std::string newlineCharacter;
extern size_t max_line_buffer;
extern int sslMode;
namespace ssl {
@ -230,17 +223,27 @@ namespace ts::config {
namespace threads {
extern size_t ticking;
extern size_t command_execute;
extern size_t network_events;
namespace voice {
extern size_t execute_per_server;
extern size_t execute_limit;
extern size_t events_per_server;
extern size_t io_min;
extern size_t io_per_server;
extern size_t io_limit;
extern bool bind_io_thread_to_kernel_thread;
}
namespace music {
extern size_t execute_per_bot;
extern size_t execute_limit;
}
namespace web {
extern size_t io_loops;
}
}
namespace log {

View File

@ -36,9 +36,8 @@ void ConnectionStatistics::logOutgoingPacket(const category::value &category, si
this->statistics_second_current.connection_bytes_sent[category] += size;
this->statistics_second_current.connection_packets_sent[category] += 1;
if(this->handle) {
if(this->handle)
this->handle->logOutgoingPacket(category, size);
}
}
/* file transfer */
@ -72,14 +71,12 @@ void ConnectionStatistics::tick() {
this->total_statistics += current;
auto current_second = std::chrono::floor<std::chrono::seconds>(now.time_since_epoch()).count();
if(statistics_minute_offset == 0) {
if(statistics_minute_offset == 0)
statistics_minute_offset = current_second;
}
/* fill all "lost" with the current bandwidth as well */
while(statistics_minute_offset <= current_second) {
while(statistics_minute_offset <= current_second)
this->statistics_minute[statistics_minute_offset++ % this->statistics_minute.size()] = current_normalized;
}
this->last_second_tick = now;
}
}

View File

@ -127,6 +127,10 @@ namespace ts {
inline static category::value from_type(uint8_t type){
return lookup_table[type & 0xFU];
}
inline static category::value from_type(const protocol::PacketTypeInfo& type){
return from_type(type.type());
}
};
explicit ConnectionStatistics(std::shared_ptr<ConnectionStatistics> /* root */);
~ConnectionStatistics();

View File

@ -13,156 +13,146 @@ using namespace ts::permission;
//#define DISABLE_CACHING
struct ts::server::CachedPermissionManager {
ServerId server_id{0};
ClientDbId client_database_id{0};
std::weak_ptr<permission::v2::PermissionManager> instance{};
std::shared_ptr<permission::v2::PermissionManager> instance_ref{}; /* reference to the current instance, will be refreshed every time the instance gets accessed */
std::chrono::time_point<std::chrono::system_clock> last_access{};
};
struct ts::server::StartupCacheEntry {
ServerId sid{0};
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions{};
std::deque<std::unique_ptr<StartupPropertyEntry>> properties{};
};
DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {}
DatabaseHelper::~DatabaseHelper() {
this->cached_permission_managers.clear();
for(const auto& elm : cachedPermissionManagers)
delete elm;
cachedPermissionManagers.clear();
}
void DatabaseHelper::tick() {
auto cache_timeout = std::chrono::system_clock::now() - std::chrono::minutes{10};
{
std::lock_guard cp_lock{this->cached_permission_manager_lock};
threads::MutexLock l(this->permManagerLock);
auto cpy = this->cachedPermissionManagers;
for(const auto& mgr : cpy){
//if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) //TODO remove instand delete!
mgr->ownLock.reset();
if(mgr->manager.expired()){
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), mgr));
delete mgr;
}
}
}
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const std::unique_ptr<CachedPermissionManager>& manager) {
if(manager->last_access < cache_timeout)
manager->instance_ref = nullptr;
if(manager->instance.expired())
return true;
return false;
}), this->cached_permission_managers.end());
{
threads::MutexLock l(this->propsLock);
auto pcpy = this->cachedProperties;
for(const auto& mgr : pcpy) {
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5))
mgr->ownLock.reset();
if(mgr->properties.expired()) {
this->cachedProperties.erase(std::find(this->cachedProperties.begin(), this->cachedProperties.end(), mgr));
delete mgr;
}
}
}
}
constexpr static std::string_view kSqlBase{"SELECT `client_unique_id`, `client_database_id`, `client_nickname`, `client_created`, `client_last_connected`, `client_ip`, `client_total_connections` FROM `clients_server`"};
inline std::deque<std::shared_ptr<ClientDatabaseInfo>> query_database_client_info(sql::SqlManager* sql_manager, ServerId server_id, const std::string& query, const std::vector<variable>& variables) {
std::deque<std::shared_ptr<ClientDatabaseInfo>> result{};
int collectData(deque<shared_ptr<ClientDatabaseInfo>>* list, int length, char** values, char** columns){
shared_ptr<ClientDatabaseInfo> entry = std::make_shared<ClientDatabaseInfo>();
sql::command command{sql_manager, query};
for(const auto& variable : variables)
command.value(variable);
auto sql_result = command.query([&](int length, std::string* values, std::string* names) {
auto entry = std::make_shared<ClientDatabaseInfo>();
for(int index = 0; index < length; index++)
if(strcmp(columns[index], "cldbid") == 0)
entry->cldbid = static_cast<ClientDbId>(stol(values[index]));
else if(strcmp(columns[index], "clientUid") == 0)
entry->uniqueId = values[index];
else if(strcmp(columns[index], "firstConnect") == 0)
entry->created = time_point<system_clock>() + seconds(stoll(values[index]));
else if(strcmp(columns[index], "lastConnect") == 0)
entry->lastjoin = time_point<system_clock>() + seconds(stoll(values[index]));
else if(strcmp(columns[index], "connections") == 0)
entry->connections = static_cast<uint32_t>(stoi(values[index]));
else if(strcmp(columns[index], "lastName") == 0)
entry->lastName = values[index];
else if(strcmp(columns[index], "serverId") == 0);
else logError(LOG_GENERAL, "Invalid db key for manager data. Key: {}", columns[index]);
auto index{0};
try {
assert(names[index] == "client_unique_id");
entry->client_unique_id = values[index++];
list->push_back(entry);
return 0;
}
assert(names[index] == "client_database_id");
entry->client_database_id = std::stoull(values[index++]);
#define MAX_QUERY 32
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<VirtualServer>& server, const std::deque<ClientDbId>& list) {
if(list.empty()) return {};
assert(names[index] == "client_nickname");
entry->client_nickname = values[index++];
deque<shared_ptr<ClientDatabaseInfo>> result;
assert(names[index] == "client_created");
entry->client_created = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
if(list.size() <= MAX_QUERY){
std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND (";
for(auto elm : list)
query += " `cldbid` = " + to_string(elm) + " OR";
query = query.substr(0, query.length() - 3) + ")";
logTrace(server ? server->getServerId() : 0, "[SQL] queryDatabseInfo(...) -> {}", query);
auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(std::function<decltype(collectData)>(collectData), &result);
auto pf = LOG_SQL_CMD;
pf(state);
if(!state) return {};
} else {
std::deque<ClientDbId> sub;
do {
sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
assert(names[index] == "client_last_connected");
entry->client_last_connected = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
assert(names[index] == "client_ip");
entry->client_ip = values[index++];
assert(names[index] == "client_total_connections");
entry->client_total_connections = std::stoull(values[index++]);
assert(index == length);
} catch (std::exception& ex) {
logError(server_id, "Failed to parse client base properties at index {}: {}. Query: {}",
index - 1,
ex.what(),
query
);
}
result.push_back(std::move(entry));
});
if(!sql_result) {
logError(server_id, "Failed to query client database infos: {}; Query: {}", sql_result.fmtStr(), query);
return result;
auto res = this->queryDatabaseInfo(server, sub);
result.insert(result.end(), res.begin(), res.end());
sub.clear();
} while(!list.empty());
}
return result;
}
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<VirtualServer>& server, const std::deque<ClientDbId>& list) {
if(list.empty())
return {};
std::string valueList{};
for(const auto& element : list)
valueList += ", " + std::to_string(element);
valueList = valueList.substr(2);
auto serverId = server ? server->getServerId() : 0;
return query_database_client_info(this->sql, serverId, std::string{kSqlBase} + "WHERE `server_id` = :sid AND `client_database_id` IN (" + valueList + ")", {variable{":sid", serverId}});
}
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &server, std::deque<std::string> list) {
if(list.empty())
return {};
if(list.empty()) return {};
std::string valueList{};
for(size_t value_index{0}; value_index < list.size(); value_index++)
valueList += ", :v" + std::to_string(value_index);
valueList = valueList.substr(2);
deque<shared_ptr<ClientDatabaseInfo>> result;
auto serverId = server ? server->getServerId() : 0;
if(list.size() <= MAX_QUERY){
std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND (";
for(const auto &elm : list)
query += " `clientUid` = '" + elm + "' OR";
query = query.substr(0, query.length() - 3) + ")";
logTrace(server ? server->getServerId() : 0, "[SQL] queryDatabseInfoByUid(...) -> {}", query);
auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(function<decltype(collectData)>(collectData), &result);
auto pf = LOG_SQL_CMD;
pf(state);
if(!state) return {};
} else {
std::deque<std::string> sub;
do {
sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
std::vector<variable> values{};
values.reserve(list.size() + 1);
auto res = this->queryDatabaseInfoByUid(server, sub);
result.insert(result.end(), res.begin(), res.end());
sub.clear();
} while(!list.empty());
}
values.emplace_back(":sid", serverId);
for(size_t value_index{0}; value_index < list.size(); value_index++)
values.emplace_back(":v" + std::to_string(value_index), list[value_index]);
return query_database_client_info(this->sql, serverId, std::string{kSqlBase} + "WHERE `server_id` = :sid AND `client_unique_id` IN (" + valueList + ")", values);
return result;
}
bool DatabaseHelper::validClientDatabaseId(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) { return cldbid > 0; } //TODO here check
void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
auto serverId = (ServerId) (server ? server->getServerId() : 0);
ServerId sid = static_cast<ServerId>(server ? server->getServerId() : 0);
{
lock_guard lock{cached_permission_manager_lock};
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
return entry->server_id == serverId && entry->client_database_id == cldbid;
}), this->cached_permission_managers.end());
lock_guard<threads::Mutex> lock(permManagerLock);
for(auto permMgr : this->cachedPermissionManagers)
if(permMgr->cldbid == cldbid && permMgr->sid == sid) {
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
delete permMgr;
break;
}
}
//TODO remove from props cache?
sql::result state{};
state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", serverId}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", serverId}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
if(serverId == 0) {
state = sql::command(this->sql, "DELETE FROM `clients_server` WHERE `client_database_id` = :id", variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `clients` WHERE `client_database_id` = :id", variable{":id", cldbid}).execute();
} else {
state = sql::command(this->sql, "DELETE FROM `clients_server` WHERE `server_id` = :sid AND `client_database_id` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
}
auto state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", sid}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", sid}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `clients` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
//TODO delete letters
//TODO delete query
@ -170,11 +160,14 @@ void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server,
}
inline sql::result load_permissions_v2(
const ServerId& server_id,
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) {
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;
@ -209,68 +202,66 @@ inline sql::result load_permissions_v2(
logError(server_id, "[SQL] Cant load permissions! Failed to parse value! Command: {} Message: {} Key: {} Value: {}", command.sqlCommand(), ex.what(),names[index], values[index]);
return 0;
}
if(channel_id > 0 && test_channel) {
if(!server)
logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
else {
auto channel = server->getChannelTree()->findChannel(channel_id);
if(!channel)
logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
}
}
if(key == permission::undefined) {
debugMessage(server_id, "[SQL] Permission entry misses permission type! Command: {}", command.sqlCommand());
return 0;
}
if(channel_id == 0 || is_channel) {
if(channel_id == 0 || is_channel)
manager->load_permission(key, {value, granted}, skipped, negated, value != permNotGranted, granted != permNotGranted);
} else {
else
manager->load_permission(key, {value, granted}, channel_id, skipped, negated, value != permNotGranted, granted != permNotGranted);
}
return 0;
});
//auto end = system_clock::now();
//auto time = end - start;
//logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
auto end = system_clock::now();
auto time = end - start;
logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
}
constexpr static std::string_view kPermissionUpdateCommand{"UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
constexpr static std::string_view kPermissionInsertCommand{"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)"};
constexpr static std::string_view kPermissionDeleteCommand{"DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
#define UPDATE_COMMAND "UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
#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<permission::v2::PermissionManager> DatabaseHelper::find_cached_permission_manager(ServerId server_id,
ClientDbId client_database_id) {
for(auto it = this->cached_permission_managers.begin(); it != this->cached_permission_managers.end(); it++) {
auto& cached_manager = *it;
if(cached_manager->client_database_id == client_database_id && cached_manager->server_id == server_id) {
auto manager = cached_manager->instance.lock();
if(!manager){
this->cached_permission_managers.erase(it);
break;
}
cached_manager->last_access = system_clock::now();
cached_manager->instance_ref = manager;
return manager;
}
}
return nullptr;
}
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const ServerId& server_id, ClientDbId cldbid) {
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
auto server_id = server ? server->getServerId() : 0;
#ifndef DISABLE_CACHING
{
std::lock_guard lock{cached_permission_manager_lock};
auto manager = this->find_cached_permission_manager(server_id, cldbid);
if(manager) return manager;
lock_guard<threads::Mutex> lock(permManagerLock);
for(auto permMgr : this->cachedPermissionManagers)
if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)) {
auto ptr = permMgr->manager.lock();
if(!ptr){
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
delete permMgr;
break;
}
permMgr->lastAccess = system_clock::now();
return ptr;
}
}
#endif
logTrace(server_id, "[Permission] Loading client permission manager for client {}", cldbid);
auto permission_manager = std::make_shared<v2::PermissionManager>();
bool loaded = false;
if(this->use_startup_cache && server_id > 0) {
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_id) {
if(entries->sid == server->getServerId()) {
entry = entries;
break;
}
@ -279,11 +270,12 @@ 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) {
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 {
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);
else
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
}
}
}
loaded = true;
@ -292,31 +284,23 @@ 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_id},
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_USER},
variable{":id", cldbid});
LOG_SQL_CMD(load_permissions_v2(server_id, permission_manager.get(), command, true, false));
LOG_SQL_CMD(load_permissions_v2(server, permission_manager.get(), command, true, false));
}
#ifndef DISABLE_CACHING
auto cache_entry = std::make_unique<CachedPermissionManager>();
cache_entry->server_id = server_id;
cache_entry->instance = permission_manager;
cache_entry->instance_ref = permission_manager;
cache_entry->client_database_id = cldbid;
cache_entry->last_access = system_clock::now();
{
std::lock_guard cache_lock{this->cached_permission_manager_lock};
/* test if we might not got a second instance */
auto manager = this->find_cached_permission_manager(server_id, cldbid);
if(manager) return manager;
this->cached_permission_managers.push_back(std::move(cache_entry));
}
this->permManagerLock.lock();
auto entry = new CachedPermissionManager();
entry->sid = server_id;
entry->manager = permission_manager;
entry->ownLock = permission_manager;
entry->cldbid = cldbid;
entry->lastAccess = system_clock::now();
this->cachedPermissionManagers.push_back(entry);
this->permManagerLock.unlock();
#endif
return permission_manager;
}
@ -329,7 +313,7 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
auto permission_data = permission::resolvePermissionData(update.permission);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
@ -357,14 +341,14 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
}
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPermissions(const ServerId& server_id, ts::GroupId group_id, uint8_t /* target */) {
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>();
if(this->use_startup_cache && server_id > 0) {
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_id) {
if(entries->sid == server->getServerId()) {
entry = entries;
break;
}
@ -382,33 +366,34 @@ 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_id},
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id});
LOG_SQL_CMD(load_permissions_v2(server_id, result.get(), command, false, false));
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, false));
return result;
}
void DatabaseHelper::saveGroupPermissions(const ServerId &server_id, ts::GroupId group_id, uint8_t target, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::GroupId group_id, const std::shared_ptr<ts::permission::v2::PermissionManager> &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 ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
auto permission_data = permission::resolvePermissionData(update.permission);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
logTrace(server_id, "Updating group permission for group {}/{}: {}. New value: {}. New grant: {}. Query: {}",
target, group_id,
logTrace(server_id, "[CHANNEL] Updating group permission for group {}: {}. New value: {}. New grant: {}. Query: {}",
group_id,
permission_data->name,
value,
grant,
query
);
sql::command(this->sql, query,
variable{":serverId", server_id},
variable{":serverId", server ? server->getServerId() : 0},
variable{":id", group_id},
variable{":chId", 0},
variable{":type", permission::SQL_PERM_GROUP},
@ -456,7 +441,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadPlaylistP
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_PLAYLIST},
variable{":id", playlist_id});
LOG_SQL_CMD(load_permissions_v2(server ? server->getServerId() : 0, result.get(), command, false, false));
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, false));
return result;
}
@ -467,7 +452,7 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
auto permission_data = permission::resolvePermissionData(update.permission);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
@ -522,7 +507,7 @@ 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 ? server->getServerId() : 0, result.get(), command, false, true));
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, true));
return result;
}
@ -533,7 +518,7 @@ void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::Vi
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
@ -560,12 +545,12 @@ void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::Vi
}
}
std::shared_ptr<PropertyManager> DatabaseHelper::default_properties_client(std::shared_ptr<PropertyManager> properties, ClientType type){
if(!properties) {
properties = std::make_shared<PropertyManager>();
}
std::shared_ptr<Properties> DatabaseHelper::default_properties_client(std::shared_ptr<Properties> properties, ClientType type){
if(!properties)
properties = make_shared<Properties>();
properties->register_property_type<property::ClientProperties>();
properties->register_property_type<property::ConnectionProperties>();
if(type == ClientType::CLIENT_MUSIC || type == ClientType::CLIENT_QUERY){
(*properties)[property::CLIENT_INPUT_HARDWARE] = true;
@ -575,42 +560,52 @@ std::shared_ptr<PropertyManager> DatabaseHelper::default_properties_client(std::
return properties;
}
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId serverId, std::shared_ptr<DataClient> cl) {
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 exists, create a new one */
if(cl->getClientDatabaseId() == 0){ //Client does not exist
ClientDbId new_client_database_id{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);
auto pf = LOG_SQL_CMD;
pf(res);
if(!res) return false;
sql::result sql_result{};
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);
if(!res) return false;
std::string insert_or_ignore{sql->getType() == sql::TYPE_SQLITE ? "INSERT OR IGNORE" : "INSERT IGNORE"};
auto currentTimeSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
sql_result = sql::command{sql, insert_or_ignore + " INTO `clients` (`client_unique_id`, `client_created`) VALUES (:uniqueId, :now)",
variable{":uniqueId", cl->getUid()},
variable{":now", currentTimeSeconds}
}.execute();
if(!sql_result) {
logCritical(LOG_INSTANCE, "Failed to execute client insert command for {}: {}", cl->getUid(), sql_result.fmtStr());
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);
if(!res) return false;
debugMessage(LOG_INSTANCE, "Registered a new client. Unique id: {}, First server: {}, Database ID: {}", cl->getUid(), id, new_client_database_id);
} 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);
}
sql_result = sql::command{sql, "INSERT INTO `clients_server` (`server_id`, `client_unique_id`, `client_database_id`, `client_created`) SELECT :serverId, :uniqueId, `client_database_id`, :now FROM `clients` WHERE `client_unique_id` = :uniqueId;",
variable{":serverId", serverId},
variable{":uniqueId", cl->getUid()},
variable{":now", currentTimeSeconds}
}.execute();
if(!sql_result) {
logCritical(LOG_INSTANCE, "Failed to execute client server insert command for {}: {}", cl->getUid(), sql_result.fmtStr());
return false;
if(id != 0){ //Else already inserted
res = insertTemplate.command().values(variable{":serverId", id}, variable{":cldbid", new_client_database_id}).execute();
pf(res);
if(!res) return false;
}
if(!cl->loadDataForCurrentServer())
return false;
debugMessage(serverId, "Successfully registered client {} for server {} with database id {}.", cl->getUid(), serverId, cl->getClientDatabaseId());
return assignDatabaseId(sql, id, cl);
}
logTrace(id, "Loaded client successfully from database. Database id: {} Unique id: {}", cl->getClientDatabaseId(), cl->getUid());
return true;
}
@ -658,8 +653,8 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
return result;
}
std::shared_ptr<PropertyManager> DatabaseHelper::loadServerProperties(const std::shared_ptr<ts::server::VirtualServer>& server) {
auto props = std::make_shared<PropertyManager>();
std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shared_ptr<ts::server::VirtualServer>& server) {
auto props = std::make_shared<Properties>();
props->register_property_type<property::VirtualServerProperties>();
(*props)[property::VIRTUALSERVER_HOST] = config::binding::DefaultVoiceHost;
@ -710,14 +705,12 @@ std::shared_ptr<PropertyManager> DatabaseHelper::loadServerProperties(const std:
return;
}
auto weak_server = weak.lock();
if(!weak_server && serverId != 0) {
return;
}
if(!weak_server && serverId != 0) return;
string sql;
if(prop.hasDbReference()) {
if(prop.hasDbReference())
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key";
} else {
else {
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
}
@ -734,8 +727,8 @@ std::shared_ptr<PropertyManager> DatabaseHelper::loadServerProperties(const std:
return props;
}
std::shared_ptr<PropertyManager> DatabaseHelper::loadPlaylistProperties(const std::shared_ptr<ts::server::VirtualServer>& server, PlaylistId id) {
auto props = std::make_shared<PropertyManager>();
std::shared_ptr<Properties> DatabaseHelper::loadPlaylistProperties(const std::shared_ptr<ts::server::VirtualServer>& server, PlaylistId id) {
auto props = std::make_shared<Properties>();
props->register_property_type<property::PlaylistProperties>();
(*props)[property::PLAYLIST_ID] = id;
@ -788,9 +781,9 @@ std::shared_ptr<PropertyManager> DatabaseHelper::loadPlaylistProperties(const st
if(!weak_server && serverId != 0) return;
string sql;
if(prop.hasDbReference()) {
if(prop.hasDbReference())
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key";
} else {
else {
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
}
@ -807,9 +800,9 @@ std::shared_ptr<PropertyManager> DatabaseHelper::loadPlaylistProperties(const st
return props;
}
std::shared_ptr<PropertyManager> DatabaseHelper::loadChannelProperties(const shared_ptr<VirtualServer>& server, ChannelId channel) {
std::shared_ptr<Properties> DatabaseHelper::loadChannelProperties(const shared_ptr<VirtualServer>& server, ChannelId channel) {
ServerId serverId = server ? server->getServerId() : 0U;
auto props = std::make_shared<PropertyManager>();
auto props = std::make_shared<Properties>();
props->register_property_type<property::ChannelProperties>();
if(server) {
@ -888,12 +881,11 @@ std::shared_ptr<PropertyManager> DatabaseHelper::loadChannelProperties(const sha
return props;
}
std::shared_ptr<PropertyManager> DatabaseHelper::loadClientProperties(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid, ClientType type) {
std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid, ClientType type) {
auto props = DatabaseHelper::default_properties_client(nullptr, type);
if(server) {
props->operator[](property::CLIENT_DESCRIPTION) = server->properties()[property::VIRTUALSERVER_DEFAULT_CLIENT_DESCRIPTION].value();
}
bool loaded = false;
if(use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
@ -908,7 +900,7 @@ std::shared_ptr<PropertyManager> DatabaseHelper::loadClientProperties(const std:
}
if(entry) {
for(const auto& prop : entry->properties) {
if(prop->id == cldbid && (prop->type == property::PROP_TYPE_CLIENT)) {
if(prop->id == cldbid && (prop->type == property::PROP_TYPE_CLIENT || prop->type == property::PROP_TYPE_CONNECTION)) {
auto p = (*props)[prop->info];
p = prop->value;
p.setModified(true);
@ -918,9 +910,8 @@ std::shared_ptr<PropertyManager> DatabaseHelper::loadClientProperties(const std:
loaded = true;
}
}
if(!loaded) {
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", property::PropertyType::PROP_TYPE_CLIENT}, variable{":id", cldbid});
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type1", property::PropertyType::PROP_TYPE_CONNECTION}, variable{":type2", property::PropertyType::PROP_TYPE_CLIENT}, variable{":id", cldbid});
deque<unique_ptr<FastPropertyEntry>> property_list;
LOG_SQL_CMD(load_properties(server ? server->getServerId() : 0, property_list, command));
@ -947,23 +938,20 @@ std::shared_ptr<PropertyManager> DatabaseHelper::loadClientProperties(const std:
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + std::string{prop.type().name} + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
return;
}
auto handle = prop.get_handle();
if(!handle || !handle->isSaveEnabled()) {
return;
}
if(!prop.get_handle()) return;
if(!prop.get_handle()->isSaveEnabled()) return;
if(!prop.hasDbReference() && (prop.default_value() == prop.value())) return; //No changes to default value
prop.setModified(false);
std::string sqlCommand;
std::string sql;
if(prop.hasDbReference())
sqlCommand = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key";
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key";
else {
prop.setDbReference(true);
sqlCommand = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
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());
sql::command(this->sql, sqlCommand,
sql::command(this->sql, sql,
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", prop.type().type_property},
variable{":id", cldbid},
@ -979,66 +967,21 @@ std::shared_ptr<PropertyManager> DatabaseHelper::loadClientProperties(const std:
return;
}
std::string column;
if(prop.type().type_property == property::PROP_TYPE_CLIENT) {
switch (prop.type().property_index) {
case property::CLIENT_NICKNAME:
column = "client_nickname";
break;
case property::CLIENT_LASTCONNECTED:
column = "client_last_connected";
break;
case property::CLIENT_TOTALCONNECTIONS:
column = "client_total_connections";
break;
case property::CLIENT_MONTH_BYTES_UPLOADED:
column = "client_month_upload";
break;
case property::CLIENT_TOTAL_BYTES_UPLOADED:
column = "client_total_upload";
break;
case property::CLIENT_MONTH_BYTES_DOWNLOADED:
column = "client_month_download";
break;
case property::CLIENT_TOTAL_BYTES_DOWNLOADED:
column = "client_total_download";
break;
default:
return;
}
}
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '{}' for {} (New value: {}, Column: {})",
prop.type().name,
cldbid,
prop.value(),
column
);
sql::command(this->sql, "UPDATE `clients_server` SET `" + column + "` = :value WHERE `server_id` = :serverId AND `client_database_id` = :cldbid",
variable{":serverId", server ? server->getServerId() : 0},
variable{":cldbid", cldbid},
variable{":value", prop.value()}
).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
std::string query;
if(prop.type() == property::CLIENT_TOTALCONNECTIONS)
query = "UPDATE `clients` SET `connections` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
else if(prop.type() == property::CLIENT_NICKNAME)
query = "UPDATE `clients` SET `lastName` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
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 + ")");
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"});
});
return props;
}
void DatabaseHelper::updateClientIpAddress(const ServerId &server_id, ClientDbId client_database_id, const std::string &ip_address) {
sql::command(this->sql, "UPDATE `clients_server` SET `client_ip` = :value WHERE `server_id` = :serverId AND `client_database_id` = :cldbid",
variable{":serverId", server_id},
variable{":cldbid", client_database_id},
variable{":value", ip_address}
).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
void DatabaseHelper::loadStartupCache() {
this->loadStartupPermissionCache();
this->loadStartupPropertyCache();
@ -1080,15 +1023,6 @@ void DatabaseHelper::clearStartupCache(ts::ServerId sid) {
}
}
void DatabaseHelper::handleServerDelete(ServerId server_id) {
{
std::lock_guard pm_lock{this->cached_permission_manager_lock};
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
return entry->server_id == server_id;
}), this->cached_permission_managers.end());
}
}
//SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`
//SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`
struct StartupPermissionArgument {
@ -1230,19 +1164,13 @@ void DatabaseHelper::loadStartupPropertyCache() {
}, &arg);
}
void DatabaseHelper::deleteGroupArtifacts(ServerId server_id, GroupId group_id) {
sql::result result{};
result = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server_id},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id}).execute();
LOG_SQL_CMD(result);
result = sql::command(this->sql, "DELETE FROM `tokens` WHERE `serverId` = :serverId AND `targetGroup` = :id",
variable{":serverId", server_id},
variable{":id", group_id}).execute();
LOG_SQL_CMD(result);
bool DatabaseHelper::deleteGroupPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::GroupId group_id) {
auto command = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id}).execute();
LOG_SQL_CMD(command);
return !!command;
}
bool DatabaseHelper::deleteChannelPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::ChannelId channel_id) {
@ -1284,81 +1212,3 @@ bool DatabaseHelper::deletePlaylist(const std::shared_ptr<ts::server::VirtualSer
return true;
}
constexpr static auto kDBListQuery{R"(
SELECT `clients`.*, `properties`.`value` as `client_description` FROM (
SELECT
`clients_server`.`client_database_id`,
`clients_server`.`client_unique_id`,
`clients_server`.`client_nickname`,
`clients_server`.`client_ip`,
`clients_server`.`client_created`,
`clients_server`.`client_last_connected`,
`clients_server`.`client_total_connections`,
`clients`.`client_login_name` FROM `clients_server`
INNER JOIN `clients` ON `clients`.`client_database_id` = `clients_server`.`client_database_id`
WHERE `server_id` = :serverId LIMIT :offset, :limit
) AS `clients`
LEFT JOIN `properties` ON `properties`.serverId = :serverId AND `properties`.key = 'client_description' AND `properties`.`id` = `clients`.`client_database_id`
)"};
void DatabaseHelper::listDatabaseClients(
ServerId server_id,
const std::optional<int64_t>& offset,
const std::optional<int64_t>& limit,
void (* callback)(void *, const DatabaseClient &),
void *user_argument) {
DatabaseClient client;
size_t set_index{0};
auto sqlResult = sql::command{this->sql, kDBListQuery,
variable{":serverId", server_id},
variable{":offset", offset.has_value() ? *offset : 0},
variable{":limit", limit.has_value() ? *limit : -1}
}.query([&](int length, std::string* values, std::string* names) {
set_index++;
auto index{0};
try {
assert(names[index] == "client_database_id");
client.client_database_id = std::stoull(values[index++]);
assert(names[index] == "client_unique_id");
client.client_unique_id = values[index++];
assert(names[index] == "client_nickname");
client.client_nickname = values[index++];
assert(names[index] == "client_ip");
client.client_ip = values[index++];
assert(names[index] == "client_created");
client.client_created = values[index++];
assert(names[index] == "client_last_connected");
client.client_last_connected = values[index++];
assert(names[index] == "client_total_connections");
client.client_total_connections = values[index++];
assert(names[index] == "client_login_name");
client.client_login_name = values[index++];
assert(names[index] == "client_description");
client.client_description = values[index++];
assert(index == length);
} catch (std::exception& ex) {
logError(server_id, "Failed to parse client base properties at index {}: {}. Offset: {} Limit: {} Set: {}",
index - 1,
ex.what(),
offset.has_value() ? std::to_string(*offset) : "not given",
limit.has_value() ? std::to_string(*limit) : "not given",
set_index - 1
);
return;
}
callback(user_argument, client);
});
}

View File

@ -8,129 +8,126 @@
#include <Properties.h>
#include <cstdint>
namespace ts::server {
class VirtualServer;
class DataClient;
namespace ts {
namespace server {
class VirtualServer;
class DataClient;
struct ClientDatabaseInfo {
ServerId server_id;
struct ClientDatabaseInfo {
ServerId sid;
ClientDbId cldbid;
std::chrono::time_point<std::chrono::system_clock> created;
std::chrono::time_point<std::chrono::system_clock> lastjoin;
std::string uniqueId;
std::string lastName;
uint32_t connections;
};
ClientDbId client_database_id;
std::string client_unique_id;
std::string client_nickname;
std::string client_ip;
struct CachedPermissionManager {
ServerId sid;
ClientDbId cldbid;
std::weak_ptr<permission::v2::PermissionManager> manager;
std::shared_ptr<permission::v2::PermissionManager> ownLock;
std::chrono::time_point<std::chrono::system_clock> lastAccess;
};
std::chrono::time_point<std::chrono::system_clock> client_created;
std::chrono::time_point<std::chrono::system_clock> client_last_connected;
struct CachedProperties {
ServerId sid;
ClientDbId cldbid;
std::weak_ptr<Properties> properties;
std::shared_ptr<Properties> ownLock;
std::chrono::time_point<std::chrono::system_clock> lastAccess;
};
uint32_t client_total_connections;
};
/*
CREATE_TABLE("properties", "`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT", command_append_utf8);
CREATE_TABLE("permissions", "`serverId` INT NOT NULL, `type` INT, `id` INT, `channelId` INT, `permId` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` INT, `grant` INT", command_append_utf8);
*/
struct StartupPermissionEntry {
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
uint64_t id = 0;
ChannelId channelId = 0;
std::shared_ptr<permission::PermissionTypeEntry> permission = permission::PermissionTypeEntry::unknown;
permission::PermissionValue value = 0;
permission::PermissionValue grant = 0;
struct StartupPermissionEntry {
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
uint64_t id = 0;
ChannelId channelId = 0;
std::shared_ptr<permission::PermissionTypeEntry> permission = permission::PermissionTypeEntry::unknown;
permission::PermissionValue value = 0;
permission::PermissionValue grant = 0;
bool flag_skip = false;
bool flag_negate = false;
};
bool flag_skip = false;
bool flag_negate = false;
};
struct StartupPropertyEntry {
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
uint64_t id{0};
const property::PropertyDescription* info{&property::undefined_property_description};
std::string value;
};
struct StartupPropertyEntry {
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
uint64_t id{0};
const property::PropertyDescription* info{&property::undefined_property_description};
std::string value;
};
struct StartupCacheEntry {
ServerId sid;
struct DatabaseClient {
ClientDbId client_database_id;
std::string client_unique_id;
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions;
std::deque<std::unique_ptr<StartupPropertyEntry>> properties;
};
std::string client_nickname;
std::string client_ip;
struct FastPropertyEntry {
const property::PropertyDescription* type;
std::string value;
};
std::string client_created; /* seconds since epoch */
std::string client_last_connected; /* seconds since epoch */
std::string client_total_connections;
class DatabaseHelper {
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;
std::string client_login_name;
std::string client_description; /* optional and only given sometimes */
};
explicit DatabaseHelper(sql::SqlManager*);
~DatabaseHelper();
struct FastPropertyEntry {
const property::PropertyDescription* type;
std::string value;
};
void loadStartupCache();
size_t cacheBinarySize();
void clearStartupCache(ServerId sid = 0);
struct CachedPermissionManager;
struct StartupCacheEntry;
class DatabaseHelper {
public:
static std::shared_ptr<PropertyManager> default_properties_client(std::shared_ptr<PropertyManager> /* properties */, ClientType /* type */);
static bool assignDatabaseId(sql::SqlManager *, ServerId serverId, std::shared_ptr<DataClient>);
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
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>);
explicit DatabaseHelper(sql::SqlManager*);
~DatabaseHelper();
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 */);
void loadStartupCache();
size_t cacheBinarySize();
void clearStartupCache(ServerId sid = 0);
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 */);
void handleServerDelete(ServerId /* server id */);
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 */);
void listDatabaseClients(
ServerId /* server id */,
const std::optional<int64_t>& offset,
const std::optional<int64_t>& limit,
void(* /* callback */)(void* /* user argument */, const DatabaseClient& /* client */),
void* /* user argument */);
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 */);
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
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<Properties> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
std::shared_ptr<Properties> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
std::shared_ptr<Properties> loadChannelProperties(const std::shared_ptr<VirtualServer>&, ChannelId); //Read and write
std::shared_ptr<Properties> loadClientProperties(const std::shared_ptr<VirtualServer>&, ClientDbId, ClientType);
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const ServerId&, ClientDbId);
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
bool deleteGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
bool deleteChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
bool deletePlaylist(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
std::deque<std::unique_ptr<FastPropertyEntry>> query_properties(ServerId /* server */, property::PropertyType /* type */, uint64_t /* id */); /* required for server snapshots */
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 */);
void tick();
private:
void loadStartupPermissionCache();
void loadStartupPropertyCache();
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const ServerId& server_id, GroupId, uint8_t /* target */);
void saveGroupPermissions(const ServerId&, GroupId, uint8_t /* target */, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
bool use_startup_cache = false;
threads::Mutex startup_lock;
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
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<PropertyManager> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
std::shared_ptr<PropertyManager> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
std::shared_ptr<PropertyManager> loadChannelProperties(const std::shared_ptr<VirtualServer>&, ChannelId); //Read and write
std::shared_ptr<PropertyManager> loadClientProperties(const std::shared_ptr<VirtualServer>&, ClientDbId, ClientType);
void updateClientIpAddress(const ServerId& /* server id */, ClientDbId /* target database id */, const std::string& /* ip address */);
void deleteGroupArtifacts(ServerId, GroupId);
bool deleteChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
bool deletePlaylist(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
std::deque<std::unique_ptr<FastPropertyEntry>> query_properties(ServerId /* server */, property::PropertyType /* type */, uint64_t /* id */); /* required for server snapshots */
void tick();
private:
void loadStartupPermissionCache();
void loadStartupPropertyCache();
bool use_startup_cache = false;
threads::Mutex startup_lock;
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
sql::SqlManager* sql = nullptr;
threads::Mutex cached_permission_manager_lock;
std::deque<std::unique_ptr<CachedPermissionManager>> cached_permission_managers;
/* Attention: cached_permission_manager_lock should be locked! */
[[nodiscard]] inline std::shared_ptr<permission::v2::PermissionManager> find_cached_permission_manager(ServerId /* server id */, ClientDbId /* client id */);
};
sql::SqlManager* sql = nullptr;
threads::Mutex permManagerLock;
std::deque<CachedPermissionManager*> cachedPermissionManagers;
threads::Mutex propsLock;
std::deque<CachedProperties*> cachedProperties;
};
}
}

View File

@ -1,220 +0,0 @@
//
// Created by WolverinDEV on 12/05/2020.
//
#include <files/FileServer.h>
#include <files/Config.h>
#include "./client/ConnectedClient.h"
#include "FileServerHandler.h"
using namespace ts::server;
using namespace ts::server::file;
FileServerHandler::FileServerHandler(ts::server::InstanceHandler *instance) : instance_{instance} {}
bool FileServerHandler::initialize(std::string &error) {
if(!file::initialize(error,
serverInstance->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].value(),
serverInstance->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as_or<uint16_t>(30303))) {
return false;
}
#if 1
file::config::ssl_option_supplier = [&]{
return this->instance_->sslManager()->web_ssl_options();
};
#endif
auto server = file::server();
assert(server);
auto& transfer = server->file_transfer();
transfer.callback_transfer_registered = std::bind(&FileServerHandler::callback_transfer_registered, this, std::placeholders::_1);
transfer.callback_transfer_started = std::bind(&FileServerHandler::callback_transfer_started, this, std::placeholders::_1);
transfer.callback_transfer_finished = std::bind(&FileServerHandler::callback_transfer_finished, this, std::placeholders::_1);
transfer.callback_transfer_aborted = std::bind(&FileServerHandler::callback_transfer_aborted, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
transfer.callback_transfer_statistics = std::bind(&FileServerHandler::callback_transfer_statistics, this, std::placeholders::_1, std::placeholders::_2);
return true;
}
void FileServerHandler::finalize() {
file::finalize();
}
void FileServerHandler::callback_transfer_registered(const std::shared_ptr<transfer::Transfer> &transfer) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) return; /* well that's bad */
const auto bytes = transfer->expected_file_size - transfer->file_offset;
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(bytes);
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].increment_by<int64_t>(bytes);
} else {
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
}
auto client = server->find_client_by_id(transfer->client_id);
if(client && client->getUid() == transfer->client_unique_id) {
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
client->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(bytes);
client->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].increment_by<int64_t>(bytes);
} else {
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
}
}
}
void FileServerHandler::callback_transfer_aborted(const std::shared_ptr<transfer::Transfer> &transfer,
const transfer::TransferStatistics &statistics,
const ts::server::file::transfer::TransferError &error) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) return; /* well that's bad */
if(statistics.file_total_size < statistics.file_current_offset)
return;
const int64_t bytes_left = statistics.file_total_size - statistics.file_current_offset;
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
} else {
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
}
auto client = server->find_client_by_id(transfer->client_id);
if(client && client->getUid() == transfer->client_unique_id) {
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
client->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
client->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
} else {
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
}
ts::command_builder notify{"notifystatusfiletransfer"};
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
notify.put(0, "size", 0);
ts::command_result status{};
using ErrorType = ts::server::file::transfer::TransferError::Type;
switch (error.error_type) {
case ErrorType::TRANSFER_TIMEOUT:
status.reset(ts::command_result{error::file_transfer_connection_timeout});
break;
case ErrorType::DISK_IO_ERROR:
case ErrorType::DISK_TIMEOUT:
case ErrorType::DISK_INITIALIZE_ERROR:
status.reset(ts::command_result{error::file_io_error});
break;
case ErrorType::UNKNOWN:
case ErrorType::NETWORK_IO_ERROR:
status.reset(ts::command_result{error::file_connection_lost});
break;
case ErrorType::UNEXPECTED_CLIENT_DISCONNECT:
case ErrorType::UNEXPECTED_DISK_EOF:
status.reset(ts::command_result{error::file_transfer_interrupted});
case ErrorType::USER_REQUEST:
status.reset(ts::command_result{error::file_transfer_canceled});
break;
}
client->writeCommandResult(notify, status, "status");
client->sendCommand(notify);
}
}
void FileServerHandler::callback_transfer_statistics(const std::shared_ptr<transfer::Transfer> &transfer,
const ts::server::file::transfer::TransferStatistics &statistics) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) return; /* well that's bad */
auto client = server->find_client_by_id(transfer->client_id);
if(!client || client->getUid() != transfer->client_unique_id) {
/* client not online anymore, but we could still log this as server traffic */
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
server->getServerStatistics()->logFileTransferIn(statistics.delta_file_bytes_transferred);
} else {
server->getServerStatistics()->logFileTransferOut(statistics.delta_file_bytes_transferred);
}
return;
}
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
client->getConnectionStatistics()->logFileTransferIn(statistics.delta_file_bytes_transferred);
} else {
client->getConnectionStatistics()->logFileTransferOut(statistics.delta_file_bytes_transferred);
}
if(client->getType() == ClientType::CLIENT_TEAMSPEAK) {
return; /* TS3 does not know this notify */
}
ts::command_builder notify{"notifyfiletransferprogress"};
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
notify.put_unchecked(0, "file_bytes_transferred", statistics.file_bytes_transferred);
notify.put_unchecked(0, "network_bytes_send", statistics.network_bytes_send);
notify.put_unchecked(0, "network_bytes_received", statistics.network_bytes_received);
notify.put_unchecked(0, "file_start_offset", statistics.file_start_offset);
notify.put_unchecked(0, "file_current_offset", statistics.file_current_offset);
notify.put_unchecked(0, "file_total_size", statistics.file_total_size);
notify.put_unchecked(0, "network_current_speed", statistics.current_speed);
notify.put_unchecked(0, "network_average_speed", statistics.average_speed);
client->sendCommand(notify);
}
void FileServerHandler::callback_transfer_started(const std::shared_ptr<transfer::Transfer> &transfer) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) return; /* well that's bad */
auto client = server->find_client_by_id(transfer->client_id);
if(!client || client->getUid() != transfer->client_unique_id) {
return;
}
ts::command_builder notify{"notifyfiletransferstarted"};
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
client->sendCommand(notify);
}
void FileServerHandler::callback_transfer_finished(const std::shared_ptr<transfer::Transfer> &transfer) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) {
return; /* well that's bad */
}
auto client = server->find_client_by_id(transfer->client_id);
if(!client || client->getUid() != transfer->client_unique_id) {
return;
}
if(client->getType() == ClientType::CLIENT_TEAMSPEAK) {
return;
}
ts::command_builder notify{"notifystatusfiletransfer"};
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
notify.put(0, "size", transfer->expected_file_size); /* not sure where TeamSpeak counts from */
notify.put_unchecked(0, "status", (int) error::file_transfer_complete);
notify.put_unchecked(0, "msg", findError(error::file_transfer_complete).message);
/* TODO: Some stats? */
client->sendCommand(notify);
}

View File

@ -1,25 +0,0 @@
#pragma once
#include <string>
#include <files/FileServer.h>
#include "./InstanceHandler.h"
namespace ts::server::file {
class FileServerHandler {
public:
explicit FileServerHandler(InstanceHandler*);
bool initialize(std::string& /* error */);
void finalize();
private:
InstanceHandler* instance_;
void callback_transfer_registered(const std::shared_ptr<transfer::Transfer>&);
void callback_transfer_started(const std::shared_ptr<transfer::Transfer>&);
void callback_transfer_finished(const std::shared_ptr<transfer::Transfer>&);
void callback_transfer_aborted(const std::shared_ptr<transfer::Transfer>&, const transfer::TransferStatistics&, const transfer::TransferError&);
void callback_transfer_statistics(const std::shared_ptr<transfer::Transfer>&, const transfer::TransferStatistics&);
};
}

946
server/src/Group.cpp Normal file
View File

@ -0,0 +1,946 @@
#include <algorithm>
#include <utility>
#include <log/LogUtils.h>
#include <misc/memtracker.h>
#include "Group.h"
#include "VirtualServer.h"
#include "src/client/ConnectedClient.h"
#include "InstanceHandler.h"
#include "src/server/file/LocalFileServer.h"
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::permission;
extern InstanceHandler* serverInstance;
Group::Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId groupId) : _target(target), _type(type) {
memtrack::allocated<Group>(this);
this->handle = handle;
this->_properties = new Properties();
this->_properties->register_property_type<property::GroupProperties>();
this->setPermissionManager(make_shared<permission::v2::PermissionManager>());
this->properties()[property::GROUP_ID] = groupId;
this->properties()[property::GROUP_TYPE] = type;
/*
this->_properties->registerProperty("sgid", groupId, PROP_GROUP_INIT_SERVER);
this->_properties->registerProperty("cgid", groupId, PROP_GROUP_INIT_CHANNEL);
this->_properties->registerProperty("gid", groupId, PROP_PRIVATE_TEMP);
this->_properties->registerProperty("type", (uint8_t) type, PROP_GROUP_INIT);
this->_properties->registerProperty("name", "Undefined group #" + to_string(groupId), PROP_GROUP_INIT | PROP_SNAPSHOT);
this->_properties->registerProperty("sortid", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("savedb", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("namemode", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("iconid", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("default_group", false, PROP_PRIVATE_TEMP);
*/
this->_properties->registerNotifyHandler([&](Property& prop){
if((prop.type().flags & property::FLAG_SAVE) == 0) return;
if(!this->handle) return;
std::string sql;
if(prop.hasDbReference()){
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :gid AND `key` = :key";
} else {
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :gid, :key, :value)";
}
sql::command(this->handle->sql, sql,
variable{":sid", this->handle->getServerId()}, variable{":type", property::PropertyType::PROP_TYPE_GROUP}, variable{":gid", this->groupId()}, variable{":key", prop.type().name}, variable{":value", prop.value()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
});
}
void Group::setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager> &manager) {
this->_permissions = manager;
this->apply_properties_from_permissions();
}
void Group::apply_properties_from_permissions() {
{
auto permission = this->_permissions->permission_value_flagged(permission::i_icon_id);
this->properties()[property::GROUP_ICONID] = permission.has_value ? permission.value : 0;
}
{
auto permission = this->_permissions->permission_value_flagged(permission::i_group_show_name_in_tree);
this->properties()[property::GROUP_NAMEMODE] = permission.has_value ? permission.value : 0;
}
{
auto permission = this->_permissions->permission_value_flagged(permission::i_group_sort_id);
this->properties()[property::GROUP_SORTID] = permission.has_value ? permission.value : 0;
}
{
auto permission = this->_permissions->permission_value_flagged(permission::b_group_is_permanent);
this->properties()[property::GROUP_SAVEDB] = permission.has_value ? permission.value : 0;
}
}
Group::~Group() {
delete this->_properties;
memtrack::freed<Group>(this);
}
GroupManager::GroupManager(const shared_ptr<VirtualServer> &server, sql::SqlManager *sql, std::shared_ptr<GroupManager> root) : server(server), sql(sql), root(std::move(root)) { }
GroupManager::~GroupManager() {}
bool GroupManager::loadGroupFormDatabase(GroupId id) {
std::lock_guard glock{this->group_lock};
if(id == 0){
this->groups.clear();
auto res = sql::command(this->sql, "SELECT * FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).query(&GroupManager::insertGroupFromDb, this);
LOG_SQL_CMD(res);
return true;
} else {
for(const auto &e : this->groups)
if(e->groupId() == id){
this->groups.erase(find(this->groups.begin(), this->groups.end(), e));
break;
}
auto res = sql::command(this->sql, "SELECT * FROM `groups` WHERE `serverId` = :sid AND `groupId` = :gid",
variable{":sid", this->getServerId()}, variable{":gid", id}).query(&GroupManager::insertGroupFromDb, this);
auto fn = LOG_SQL_CMD;
fn(res);
return res;
}
}
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);
}
if(root && this->root){
auto elm = this->root->availableGroups();
for(const auto& e : elm)
response.push_back(e);
}
return response;
}
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);
}
if(root && this->root){
auto elm = this->root->availableServerGroups();
for(const auto& e : elm)
response.push_back(e);
}
return response;
}
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);
}
if(root && this->root){
auto elm = this->root->availableChannelGroups(true);
for(const auto& e : elm)
response.push_back(e);
}
return response;
}
int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
GroupId groupId = 0;
auto target = (GroupTarget) 0xff;
auto type = (GroupType) 0xff;
std::string targetName;
for(int index = 0; index < count; index++){
if(strcmp(column[index], "target") == 0)
target = (GroupTarget) stoll(values[index]);
else if(strcmp(column[index], "type") == 0)
type = (GroupType) stoll(values[index]);
else if(strcmp(column[index], "groupId") == 0)
groupId = (GroupType) stoll(values[index]);
else if(strcmp(column[index], "displayName") == 0)
targetName = values[index];
//else cerr << "Invalid group table row " << column[index] << endl;
}
if((size_t) groupId == 0 || (size_t) target == 0xff || (size_t) type == 0xff || targetName.empty()) {
logCritical(this->getServerId(), "Found invalid group ad database! (GroupId " + to_string(groupId) + ", Target " + to_string(target) + ", Type " + to_string(type) + ", Name '" + targetName + "')");
return 0;
}
/*
assert(groupId != 0);
assert(target != 0xff);
assert(type != 0xff);
assert(!targetName.empty());
*/
shared_ptr<Group> group = std::make_shared<Group>(this, target, type, groupId);
/*
auto res = sql::command(this->sql, "SELECT `key`, `value` FROM `properties` WHERE `serverId` = :sid AND `type` = :type AND `id` = :gid",
variable{":sid", this->getServerId()}, variable{":type", property::PROP_TYPE_GROUP}, variable{":gid", group->groupId()}).query([this](Group* g, int length, char** values, char** columns){
string key, value;
for(int index = 0; index < length; index++)
if(strcmp(columns[index], "key") == 0)
key = values[index];
else if(strcmp(columns[index], "value") == 0)
value = values[index];
auto info = property::info<property::GroupProperties>(key);
if(info == property::GROUP_UNDEFINED) {
logError(this->getServerId(), "Invalid property for group: " + key);
return 0;
}
auto prop = g->properties()[info.property];
prop.setDbReference(true);
prop.value(value, false);
return 0;
}, group.get());
auto print = LOG_SQL_CMD;
print(res);
*/
//FIXME load group properties view database helper (or drop it full because no saved properties)
group->properties()[property::GROUP_NAME] = targetName;
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
debugMessage(this->getServerId(), "Push back group -> " + to_string(group->groupId()) + " - " + group->name());
this->groups.push_back(group);
auto iconId = (IconId) group->icon_id();
if(iconId != 0 && serverInstance && serverInstance->getFileServer() && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) {
logMessage(this->getServerId(), "[FILE] Missing group icon (" + to_string(iconId) + ").");
if(config::server::delete_missing_icon_permissions) group->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing);
}
return 0;
}
void GroupManager::handleChannelDeleted(const ChannelId& channel_id) {
unique_lock cache_lock(this->cacheLock);
auto cached_clients = std::vector<shared_ptr<CachedClient>>{this->cachedClients.begin(), this->cachedClients.end()};
cache_lock.unlock();
for(auto& entry : cached_clients) {
lock_guard entry_lock(entry->lock);
entry->channel_groups.erase(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();
}
std::shared_ptr<Group> GroupManager::defaultGroup(GroupTarget type, bool enforce_property) {
threads::MutexLock lock(this->cacheLock);
if(this->groups.empty()) return nullptr;
auto server = this->server.lock();
auto id =
server ?
server->properties()[type == GroupTarget::GROUPTARGET_SERVER ? property::VIRTUALSERVER_DEFAULT_SERVER_GROUP : property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_save<GroupId>() :
serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save<GroupId>();
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;
}
return nullptr; //Worst case!
}
std::shared_ptr<Group> GroupManager::findGroup(GroupId groupId) {
auto result = this->findGroupLocal(groupId);
if(!result && this->root) result = this->root->findGroup(groupId);
return result;
}
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;
}
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);
}
if(this->root) {
auto r = root->findGroup(target, name);
for(const auto &e : r) res.push_back(e);
}
return res;
}
ServerId GroupManager::getServerId() {
auto l = this->server.lock();
return l ? l->getServerId() : 0;
}
std::shared_ptr<Group> GroupManager::createGroup(GroupTarget target, GroupType type, std::string name) {
if(type != GROUP_TYPE_NORMAL && this->root) return root->createGroup(target, type, name);
auto rawId = generateGroupId(this->sql);
if(rawId <= 0) {
logError(this->getServerId(), "Could not create a new group! ({})", "Could not generate group id");
return nullptr;
}
auto groupId = (GroupId) rawId;
auto res = sql::command(this->sql, "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", this->getServerId()}, variable{":gid", groupId}, variable{":target", target}, variable{":type", type}, variable{":name", name}).execute();
auto print = LOG_SQL_CMD;
print(res);
if(!res) return nullptr;
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};
this->groups.push_back(group);
return group;
}
GroupId GroupManager::copyGroup(std::shared_ptr<Group> group, GroupType type, std::string name, ServerId targetServerId) {
auto group_server = group->handle->getServerId();
auto groupId = generateGroupId(this->sql);
auto print = LOG_SQL_CMD;
auto res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":target", groupId}, variable{":ssid", group_server}, variable{":tsid", targetServerId}, variable{":source", group->groupId()}, variable{":type", SQL_PERM_GROUP}).execute();
print(res);
//Properties not used currently
res = sql::command(this->sql, "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, `key`, `value` FROM `properties` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":target", groupId}, variable{":ssid", group_server}, variable{":tsid", targetServerId}, variable{":source", group->groupId()}, variable{":type", property::PropertyType::PROP_TYPE_GROUP}).execute();
print(res);
res = sql::command(this->sql, "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", targetServerId}, variable{":gid", groupId}, variable{":target", group->target()}, variable{":type", type}, variable{":name", name}).execute();
print(res);
if(targetServerId == (this->getServerId()))
this->loadGroupFormDatabase(groupId);
else if(this->root)
this->root->loadGroupFormDatabase(groupId);
return groupId;
}
bool GroupManager::copyGroupPermissions(const shared_ptr<Group> &source, const shared_ptr<Group> &target) {
auto targetServer = target->handle->getServerId();
auto sourceServer = source->handle->getServerId();
auto res = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":id", target->groupId()}).execute();
LOG_SQL_CMD(res);
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()));
LOG_SQL_CMD(res);
return true;
}
bool GroupManager::reloadGroupPermissions(std::shared_ptr<Group> group) {
if(!isLocalGroup(group)){
if(this->root) return this->root->reloadGroupPermissions(group);
return false;
}
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
return true;
}
bool GroupManager::renameGroup(std::shared_ptr<Group> group, std::string name) {
if(!isLocalGroup(group)){
if(this->root) return this->root->renameGroup(group, name);
return false;
}
// CREATE_TABLE("groups", "`serverId` INT NOT NULL, `groupId` INTEGER, `target` INT, `type` INT, `displayName` TEXT");
group->properties()[property::GROUP_NAME] = name;
sql::command(this->sql, "UPDATE `groups` SET `displayName` = :name WHERE `serverId` = :sid AND `groupId` = :gid AND `target` = :target",
variable{":name", name}, variable{":gid", group->groupId()}, variable{":target", group->target()}, variable{":sid", this->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed"});
return true;
}
bool GroupManager::deleteAllGroups() {
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type", variable{":sid", this->getServerId()}, variable{":type", SQL_PERM_GROUP}).execute());
{
lock_guard cache_lock(this->cacheLock);
for(const auto& entry : this->cachedClients) {
lock_guard entry_lock(entry->lock);
entry->server_groups.clear();
entry->channel_groups.clear();
}
}
this->groups.clear();
return true;
}
bool GroupManager::deleteGroup(std::shared_ptr<Group> group) {
if(!isLocalGroup(group)){
if(this->root) return this->root->deleteGroup(group);
return false;
}
{
std::lock_guard glock{this->group_lock};
this->groups.erase(std::find(this->groups.begin(), this->groups.end(), group));
}
/* erase the group out of our cache */
{
lock_guard cache_lock(this->cacheLock);
for(auto& entry : this->cachedClients) {
lock_guard entry_lock(entry->lock);
entry->server_groups.erase(std::remove_if(entry->server_groups.begin(), entry->server_groups.end(), [&](const std::shared_ptr<GroupAssignment>& group_assignment) {
return group_assignment->group == group;
}), entry->server_groups.end());
for(auto it = entry->channel_groups.begin(); it != entry->channel_groups.end();) {
if(it->second->group == group)
it = entry->channel_groups.erase(it);
else
it++;
}
}
}
bool flag_sql = false;
auto res = sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}).execute();
LOG_SQL_CMD(res);
flag_sql |= !res;
res = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}).execute();
LOG_SQL_CMD(res);
flag_sql |= !res;
flag_sql &= serverInstance->databaseHelper()->deleteGroupPermissions(this->server.lock(), group->groupId());
if(flag_sql)
logError(this->getServerId(), "Could not delete group {} ({}) from database. May leader to invalid data", group->name(), group->groupId());
return true;
}
int64_t GroupManager::generateGroupId(sql::SqlManager* sql) {
int64_t hightestGroupId = 0;
sql::command(sql, "SELECT `groupId` FROM `groups` ORDER BY `groupId` DESC LIMIT 1").query([](int64_t* ptr, int, char** values, char**){
*ptr = stoul(values[0]);
return 0;
}, &hightestGroupId);
return hightestGroupId + 1;
}
std::deque<property::ClientProperties> GroupManager::update_server_group_property(const shared_ptr<server::ConnectedClient> &client, bool channel_lock, const std::shared_ptr<BasicChannel>& channel) {
std::deque<property::ClientProperties> changed;
//Server groups
{
auto groups = this->getServerGroups(client->getClientDatabaseId(), client->getType());
string group_string;
for(const auto& group : groups)
if(group_string.empty()) group_string += to_string(group->group->groupId());
else group_string += "," + to_string(group->group->groupId());
if(client->properties()[property::CLIENT_SERVERGROUPS] != group_string) {
client->properties()[property::CLIENT_SERVERGROUPS] = group_string;
changed.push_back(property::CLIENT_SERVERGROUPS);
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());
}
}
//Channel groups
if(channel){
shared_ptr<GroupAssignment> group = this->getChannelGroup(client->getClientDatabaseId(), channel, true);
if(client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] != group->channelId) {
client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] = group->channelId;
changed.push_back(property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID);
}
if(client->properties()[property::CLIENT_CHANNEL_GROUP_ID] != group->group->groupId()) {
client->properties()[property::CLIENT_CHANNEL_GROUP_ID] = group->group->groupId();
changed.push_back(property::CLIENT_CHANNEL_GROUP_ID);
unique_lock chan_lock(client->channel_lock, defer_lock);
if(channel_lock)
chan_lock.lock();
client->cached_channel_group = group->group->groupId();
}
}
if(!changed.empty())
client->join_state_id++; /* groups have changed :) */
return changed;
}
void GroupManager::cleanupAssignments(ClientDbId client) {
if(this->root)
this->root->cleanupAssignments(client);
for(const auto& assignment : this->getAssignedServerGroups(client)) {
if(!assignment->group->is_permanent())
this->removeServerGroup(client, assignment->group);
}
}
void GroupManager::enableCache(const ClientDbId& client_database_id) {
if(this->root)
this->root->enableCache(client_database_id);
unique_lock cache_lock(this->cacheLock);
/* test if we're already having the client */
for(auto& entry : this->cachedClients)
if(entry->client_database_id == client_database_id) {
entry->use_count++;
return; /* client already cached, no need to cache client */
}
auto entry = std::make_shared<CachedClient>();
entry->client_database_id = client_database_id;
entry->use_count++;
this->cachedClients.push_back(entry);
lock_guard client_cache_lock(entry->lock); /* lock the client because we're currently loading the cache. */
cache_lock.unlock();
auto res = sql::command(this->sql, "SELECT `groupId`, `channelId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->getServerId()}, variable{":cldbid", client_database_id})
.query([&](int length, std::string* value, std::string* column) {
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
ChannelId channelId = 0;
for(int index = 0; index < length; index++){
try {
if(column[index] == "groupId"){
group = this->findGroup(stoll(value[index]));
} else if(column[index] == "until"){
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
} else if(column[index] == "channelId"){
channelId = stoll(value[index]);
} else {
logError(this->getServerId(), "Unknown column in group assignment query: {}", column[index]);
continue;
}
} catch(std::exception& ex) {
logError(this->getServerId(), "Failed to load group assignment from database for client {}. Column {} contains an invalid value: {}", client_database_id, column[index], value[index]);
return 0;
}
}
if(!group) {
return 0;
}
auto assignment = std::make_shared<GroupAssignment>();
assignment->group = group;
assignment->until = until;
assignment->parent = &*entry;
assignment->channelId = channelId;
assignment->server = this->getServerId();
if(channelId == 0)
entry->server_groups.push_back(assignment);
else
entry->channel_groups[channelId] = assignment;
return 0;
});
}
//FIXME: This method till get far more often then it should be. We should add a flag if the group cache is loaded for each std::shared_ptr<ConnectedClient> instance
void GroupManager::disableCache(const ClientDbId& client_database_id) {
if(this->root) {
this->root->disableCache(client_database_id);
}
lock_guard cache_lock(this->cacheLock);
this->cachedClients.erase(std::remove_if(this->cachedClients.begin(), this->cachedClients.end(), [&](const std::shared_ptr<CachedClient>& client) {
if(client->client_database_id != client_database_id)
return false;
lock_guard client_lock{client->lock};
return (--client->use_count) == 0;
}), this->cachedClients.end());
}
void GroupManager::clearCache() {
if(this->root) this->root->clearCache();
lock_guard lock(this->cacheLock);
this->cachedClients.clear();
}
bool GroupManager::isClientCached(const ClientDbId& client_database_id) {
return this->resolve_cached_client(client_database_id) == nullptr;
}
typedef std::vector<std::shared_ptr<GroupMember>> ResList;
std::vector<std::shared_ptr<GroupMember>> GroupManager::listGroupMembers(std::shared_ptr<Group> group, bool names) { //TODO juse inner join only on names = true
if(!isLocalGroup(group)){
if(this->root) return this->root->listGroupMembers(group, names);
return {};
}
ResList result;
sql::command(this->sql,
"SELECT assignedGroups.cldbid, assignedGroups.channelId, assignedGroups.until, clients.clientUid, clients.lastName FROM assignedGroups INNER JOIN clients ON (clients.cldbid = assignedGroups.cldbid AND clients.serverId = assignedGroups.serverId) WHERE assignedGroups.`serverId` = :sid AND `groupId` = :gid;",
variable{":sid", this->getServerId()}, variable{":gid", group->groupId()})
.query([&](ResList* list, int columnCount, char** values, char** columnName){
std::shared_ptr<GroupMember> member = std::make_shared<GroupMember>();
member->displayName = "undefined";
member->uid = "undefined";
for(int index = 0; index < columnCount; index++){
if(values[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + columnName[index]);
continue;
}
if(strcmp(columnName[index], "cldbid") == 0)
member->cldbId = stoll(values[index]);
else if(strcmp(columnName[index], "until") == 0)
member->until = time_point<system_clock>() + milliseconds(stoll(values[index]));
else if(strcmp(columnName[index], "clientUid") == 0)
member->uid = values[index];
else if(strcmp(columnName[index], "lastName") == 0)
member->displayName = values[index];
else if(strcmp(columnName[index], "channelId") == 0)
member->channelId = stoll(values[index]);
else cerr << "Invalid column name " << columnName[index] << endl;
}
list->push_back(member);
return 0;
}, &result);
return result;
}
vector<shared_ptr<GroupAssignment>> GroupManager::listGroupAssignments(ClientDbId cldbId) {
vector<std::shared_ptr<GroupAssignment>> result;
sql::result res;
auto cached = resolve_cached_client(cldbId);
if(cached) {
{
lock_guard lock{cached->lock};
for(const auto &serverGroup : cached->server_groups)
result.push_back(serverGroup);
for(auto& channelGroup : cached->channel_groups)
result.push_back(channelGroup.second);
}
if(this->root){
auto append = this->root->listGroupAssignments(cldbId);
for(const auto &elm : append)
result.push_back(elm);
}
return result;
}
res = sql::command(this->sql, "SELECT `groupId`, `until`, `channelId` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->getServerId()}, variable{":cldbid", cldbId})
.query([&](int length, char** value, char** column){
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
uint64_t channelId = 0;
for(int index = 0; index < length; index++){
if(value[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
continue;
}
if(strcmp(column[index], "groupId") == 0){
group = this->findGroup(stoll(value[index]));
} else if(strcmp(column[index], "until") == 0){
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
} else if(strcmp(column[index], "channelId") == 0){
channelId = stoll(value[index]);
} else cerr << "Invalid column " << column[index] << endl;
}
if(!group)
return 0;
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
assignment->parent = nullptr;
assignment->group = group;
assignment->until = until;
assignment->channelId = channelId;
assignment->server = this->getServerId();
result.push_back(assignment);
return 0;
});
(LOG_SQL_CMD)(res);
if(this->root){
auto append = this->root->listGroupAssignments(cldbId);
for(const auto &elm : append)
result.push_back(elm);
}
return result;
}
std::shared_ptr<CachedClient> GroupManager::resolve_cached_client(ClientDbId client_database_id) {
{
lock_guard lock(this->cacheLock);
for(auto& entry : this->cachedClients)
if(entry->client_database_id == client_database_id)
return entry;
}
return nullptr;
}
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::getAssignedServerGroups(ClientDbId cldbid) {
auto cached = this->resolve_cached_client(cldbid);
sql::result res;
std::vector<std::shared_ptr<GroupAssignment>> result;
if(this->root) {
auto client_groups = this->root->getAssignedServerGroups(cldbid);
result.insert(result.begin(), client_groups.begin(), client_groups.end());
}
if(cached) {
lock_guard cache_lock{cached->lock};
result.insert(result.end(), cached->server_groups.begin(), cached->server_groups.end());
return result;
}
debugMessage(this->getServerId(), "Query client groups for client {} on server {}.", cldbid, this->getServerId());
res = sql::command(this->sql, "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = 0", variable{":sid", this->getServerId()}, variable{":cldbid", cldbid}).query([&](int length, char** value, char** column){
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
for(int index = 0; index < length; index++) {
if(value[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
continue;
}
if(strcmp(column[index], "groupId") == 0 && value[index] != nullptr){
group = this->findGroup(stoll(value[index]));
} else if(strcmp(column[index], "until") == 0){
until = time_point<system_clock>() + milliseconds(stoll(value[index] == nullptr ? "0" : value[index]));
} else cerr << "Invalid column " << column[index] << endl;
}
if(!group)
return 0;
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
assignment->parent = nullptr;
assignment->group = group;
assignment->until = until;
assignment->server = this->getServerId();
result.push_back(assignment);
return 0;
});
LOG_SQL_CMD(res);
return result;
}
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::getServerGroups(ClientDbId cldbid, server::ClientType type) {
auto result = this->getAssignedServerGroups(cldbid);
if(result.empty()) return this->defaultServerGroupGroupAssignments(cldbid, type);
return result;
}
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::defaultServerGroupGroupAssignments(ClientDbId client, ClientType type) {
std::vector<std::shared_ptr<GroupAssignment>> result;
if(type == ClientType::CLIENT_QUERY && this->root) {
auto root = this->root->defaultServerGroupGroupAssignments(client, type);
result.insert(result.begin(), root.begin(), root.end());
} else if(type == ClientType::CLIENT_MUSIC) {
threads::MutexLock lock(this->cacheLock);
auto server = this->server.lock();
auto id =
server ?
server->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP].as_save<GroupId>() :
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_save<GroupId>();
auto group = this->findGroupLocal(id);
if(group) {
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, group, time_point<system_clock>()));
return result;
}
}
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, this->defaultGroup(GroupTarget::GROUPTARGET_SERVER), time_point<system_clock>()));
return result;
}
std::shared_ptr<GroupAssignment> GroupManager::getChannelGroupExact(ClientDbId cldbId, const std::shared_ptr<BasicChannel>& channel, bool assign_default) {
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
if(cached->channel_groups.count(channel->channelId()) > 0) {
return cached->channel_groups[channel->channelId()];
} else
return assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : nullptr;
}
std::shared_ptr<GroupAssignment> result;
auto res = sql::command(this->sql, "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid", variable{":sid", this->getServerId()}, variable{":cldbid", cldbId}, variable{":chid", channel->channelId()}).query([&](int length, char** value, char** column){
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
for(int index = 0; index < length; index++){
if(value[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
continue;
}
if(strcmp(column[index], "groupId") == 0){
group = this->findGroup(stoll(value[index]));
} else if(strcmp(column[index], "until") == 0){
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
} else cerr << "Invalid column " << column[index] << endl;
}
if(!group)
return 0;
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
assignment->parent = nullptr;
assignment->group = group;
assignment->until = until;
assignment->server = this->getServerId();
assignment->channelId = channel->channelId();
result = std::move(assignment);
return 0;
});
(LOG_SQL_CMD)(res);
return !result && assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : result;
}
std::shared_ptr<GroupAssignment> GroupManager::getChannelGroup(ClientDbId cldbId, const shared_ptr<BasicChannel> &channel, bool assign_default) {
shared_ptr<GroupAssignment> group;
std::shared_ptr<BasicChannel> inheritance_channel = channel;
while(inheritance_channel && !group) {
group = this->getChannelGroupExact(cldbId, inheritance_channel, false);
if(!group) {
auto inheritance = inheritance_channel->permissions()->permission_value_flagged(permission::b_channel_group_inheritance_end);
if(inheritance.has_value && inheritance.value == 1)
break;
inheritance_channel = inheritance_channel->parent();
}
}
return !group && assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : group;
}
std::shared_ptr<GroupAssignment> GroupManager::defaultChannelGroupAssignment(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &channel) {
return std::make_shared<GroupAssignment>(nullptr, this->getServerId(), channel->channelId(), this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL), time_point<system_clock>());
}
void GroupManager::addServerGroup(ClientDbId cldbId, std::shared_ptr<Group> group, time_point<system_clock> until) {
/*
if(!this->isLocalGroup(group)) {
if(this->root) this->root->addServerGroup(cldbId, group, until);
return;
}
if(hasServerGroup(cldbId, group)) return;
*/
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
cached->server_groups.push_back(std::make_shared<GroupAssignment>(cached.get(), this->getServerId(), 0, group, until));
}
sql::command(this->sql, "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":gid", group->groupId()},
variable{":chid", 0},
variable{":until", time_point_cast<milliseconds>(until).time_since_epoch().count()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
void GroupManager::removeServerGroup(ClientDbId cldbId, std::shared_ptr<Group> group) {
if(!this->hasServerGroupAssigned(cldbId, group)) return;
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
cached->server_groups.erase(std::remove_if(cached->server_groups.begin(), cached->server_groups.end(), [&](const std::shared_ptr<GroupAssignment>& group_assignment) {
return group_assignment->group == group;
}), cached->server_groups.end());
}
sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `groupId` = :gid AND `channelId` = :chid",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":gid", group->groupId()},
variable{":chid", 0})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
void GroupManager::setChannelGroup(ClientDbId cldbId, std::shared_ptr<Group> group, std::shared_ptr<BasicChannel> channel, time_point<system_clock> until) {
auto old_group = getChannelGroupExact(cldbId, channel, false);
if(old_group) {
if(old_group->group == group) return;
} else if(!group) return;
auto default_group = !group || group == this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
if(default_group)
cached->channel_groups.erase(channel->channelId());
else
cached->channel_groups[channel->channelId()] = std::make_shared<GroupAssignment>(cached.get(), this->getServerId(), channel->channelId(), group, until);
}
sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":chid", channel->channelId()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
if(!default_group) {
sql::command(this->sql, "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":gid", group->groupId()},
variable{":chid", channel->channelId()},
variable{":until", time_point_cast<milliseconds>(until).time_since_epoch().count()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
}

View File

@ -12,9 +12,227 @@
#include <sql/SqlQuery.h>
namespace ts {
namespace server {
class VirtualServer;
class ConnectedClient;
}
struct CachedClient;
class GroupManager;
class Group;
enum GroupType {
GROUP_TYPE_TEMPLATE,
GROUP_TYPE_NORMAL,
GROUP_TYPE_QUERY //Only aplies for servergroups
};
enum GroupTarget {
GROUPTARGET_SERVER,
GROUPTARGET_CHANNEL
};
enum GroupNameMode {
GROUP_NAMEMODE_HIDDEN,
GROUP_NAMEMODE_BEFORE,
GROUP_NAMEMODE_BEHIND
};
}
DEFINE_TRANSFORMS(ts::GroupType, uint8_t);
DEFINE_TRANSFORMS(ts::GroupTarget, uint8_t);
DEFINE_TRANSFORMS(ts::GroupNameMode, uint8_t);
namespace ts {
struct GroupMember {
std::string uid;
uint64_t cldbId;
uint64_t channelId;
std::string displayName;
std::chrono::time_point<std::chrono::system_clock> until;
};
struct GroupAssignment {
GroupAssignment() {}
GroupAssignment(CachedClient *parent, ServerId server, uint64_t channelId, const std::shared_ptr<Group> &group, const std::chrono::time_point<std::chrono::system_clock> &until) : parent(parent), server(server), channelId(channelId), group(group), until(until) {}
CachedClient* parent = nullptr; //Could be null!
ServerId server = 0;
uint64_t channelId = 0;
std::shared_ptr<Group> group;
std::chrono::time_point<std::chrono::system_clock> until;
inline bool isPermanent(){ return std::chrono::time_point_cast<std::chrono::milliseconds>(until).time_since_epoch().count() == 0; }
};
struct CachedClient {
ClientDbId client_database_id;
std::vector<std::shared_ptr<GroupAssignment>> server_groups;
std::map<ts::ChannelId, std::shared_ptr<GroupAssignment>> channel_groups;
size_t use_count = 0;
std::mutex lock; /* never lock this lock before the general client cache! */
};
class Group {
friend class GroupManager;
public:
Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId groupId);
~Group();
std::shared_ptr<permission::v2::PermissionManager> permissions(){ return this->_permissions; }
Properties& properties(){ return *this->_properties; }
GroupNameMode nameMode(){ return (GroupNameMode) (uint8_t) properties()[property::GROUP_NAMEMODE]; }
std::string name(){ return properties()[property::GROUP_NAME]; }
GroupId groupId(){ return properties()[property::GROUP_ID]; }
GroupTarget target() { return _target; }
GroupType type() { return _type; }
void apply_properties_from_permissions();
inline permission::PermissionValue updateType() {
auto permission_manager = this->permissions(); /* copy the manager */
assert(permission_manager);
const auto data = permission_manager->permission_value_flagged(permission::i_group_auto_update_type);
return data.has_value ? data.value : 0;
}
inline bool permission_granted(const permission::PermissionType& permission, const permission::v2::PermissionFlaggedValue& granted_value, bool require_granted_value) {
auto permission_manager = this->permissions(); /* copy the manager */
assert(permission_manager);
const auto data = permission_manager->permission_value_flagged(permission);
if(!data.has_value) {
return !require_granted_value || granted_value.has_value;
}
if(!granted_value.has_value) {
return false;
}
if(data.value == -1) {
return granted_value.value == -1;
}
return granted_value.value >= data.value;
}
inline bool is_permanent() {
auto permission_manager = this->permissions(); /* copy the manager */
assert(permission_manager);
const auto data = permission_manager->permission_value_flagged(permission::b_group_is_permanent);
return data.has_value ? data.value == 1 : false;
}
inline IconId icon_id() {
auto permission_manager = this->permissions(); /* copy the manager */
assert(permission_manager);
const auto data = permission_manager->permission_value_flagged(permission::i_icon_id);
return data.has_value ? data.value : 0;
}
private:
void setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager>& manager);
GroupManager* handle;
std::shared_ptr<permission::v2::PermissionManager> _permissions;
Properties* _properties;
GroupTarget _target;
GroupType _type;
};
class GroupManager {
friend class ServerChannelTree;
friend class Group;
friend class server::VirtualServer;
public:
static int64_t generateGroupId(sql::SqlManager* sql);
GroupManager(const std::shared_ptr<server::VirtualServer> &, sql::SqlManager *, std::shared_ptr<GroupManager> root = nullptr);
~GroupManager();
bool loadGroupFormDatabase(GroupId id = 0);
std::vector<std::shared_ptr<Group>> availableGroups(bool root = true);
std::vector<std::shared_ptr<Group>> availableServerGroups(bool root = true);
std::vector<std::shared_ptr<Group>> availableChannelGroups(bool root = true);
std::vector<std::shared_ptr<GroupAssignment>> getAssignedServerGroups(ClientDbId cldbid);
inline bool hasServerGroupAssigned(uint64_t cldbId, const std::shared_ptr<Group>& group){
for(const auto& assign : this->getAssignedServerGroups(cldbId)) if(assign->group == group) return true;
return false;
}
std::shared_ptr<GroupAssignment> get_group_assignment(const ClientDbId& client_database_id, const std::shared_ptr<Group>& group) {
for(const auto& assign : this->getAssignedServerGroups(client_database_id))
if(assign->group == group) return assign;
return nullptr;
}
std::vector<std::shared_ptr<GroupAssignment>> getServerGroups(ClientDbId cldbid, server::ClientType type);
inline bool hasServerGroup(uint64_t cldbId, server::ClientType type, const std::shared_ptr<Group>& group){
for(const auto& assign : this->getServerGroups(cldbId, type)) if(assign->group == group) return true;
return false;
}
//Gets the channel group (may inherited)
std::shared_ptr<GroupAssignment> getChannelGroup(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &, bool assign_default);
//Gets the channel group within the channel
std::shared_ptr<GroupAssignment> getChannelGroupExact(ClientDbId cldbId, const std::shared_ptr<BasicChannel>&, bool assign_default);
std::vector<std::shared_ptr<GroupAssignment>> defaultServerGroupGroupAssignments(ClientDbId, server::ClientType);
std::shared_ptr<GroupAssignment> defaultChannelGroupAssignment(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &);
void addServerGroup(ClientDbId cldbId, std::shared_ptr<Group>, std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
void removeServerGroup(ClientDbId cldbId, std::shared_ptr<Group>);
void setChannelGroup(ClientDbId cldbId, std::shared_ptr<Group>, std::shared_ptr<BasicChannel> , std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
std::shared_ptr<Group> createGroup(GroupTarget target, GroupType type, std::string name);
GroupId copyGroup(std::shared_ptr<Group> group, GroupType type, std::string name, ServerId targetServerId);
bool copyGroupPermissions(const std::shared_ptr<Group>& source, const std::shared_ptr<Group>& target);
bool reloadGroupPermissions(std::shared_ptr<Group>);
bool renameGroup(std::shared_ptr<Group>, std::string);
bool deleteGroup(std::shared_ptr<Group>);
bool deleteAllGroups();
std::vector<std::shared_ptr<GroupMember>> listGroupMembers(std::shared_ptr<Group>, bool names = false);
std::vector<std::shared_ptr<GroupAssignment>> listGroupAssignments(ClientDbId client);
void cleanupAssignments(ClientDbId);
std::shared_ptr<Group> findGroup(GroupId);
std::shared_ptr<Group> findGroupLocal(GroupId);
std::vector<std::shared_ptr<Group>> findGroup(GroupTarget target, std::string);
std::shared_ptr<Group> defaultGroup(GroupTarget type, bool enforce_property = false);
std::deque<property::ClientProperties> update_server_group_property(const std::shared_ptr<server::ConnectedClient> &client, bool channel_lock, const std::shared_ptr<BasicChannel>& channel);
void enableCache(const ClientDbId& /* client database id */); /* if this called disableCache(...) MUST be called to decrease the reference count */
void disableCache(const ClientDbId& /* client database id */);
bool isClientCached(const ClientDbId& /* client database id */);
void clearCache();
bool isLocalGroup(std::shared_ptr<Group>);
protected:
void handleChannelDeleted(const ChannelId& /* channel id */);
private:
std::shared_ptr<GroupManager> root = nullptr;
std::weak_ptr<server::VirtualServer> server;
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;
int insertGroupFromDb(int count, char** values, char** column);
inline std::shared_ptr<CachedClient> resolve_cached_client(ClientDbId client_database_id);
};
}

View File

@ -3,30 +3,31 @@
#define XFREE undefined_free
#define XREALLOC undefined_realloc
#include <netdb.h>
#include "src/weblist/WebListManager.h"
#include <log/LogUtils.h>
#include "InstanceHandler.h"
#include "src/client/InternalClient.h"
#include "src/server/QueryServer.h"
#include "src/server/file/LocalFileServer.h"
#include "SignalHandler.h"
#include "src/manager/PermissionNameMapper.h"
#include "./FileServerHandler.h"
#include "./server/GlobalNetworkEvents.h"
#include <ThreadPool/Timer.h>
#include "ShutdownHelper.h"
#include <sys/utsname.h>
#include "build.h"
#include <misc/digest.h>
#include <misc/base64.h>
#include <misc/hex.h>
#include <misc/rnd.h>
#include <misc/strobf.h>
#include <jemalloc/jemalloc.h>
#include <protocol/buffers.h>
#include "./groups/GroupManager.h"
#ifndef _POSIX_SOURCE
#define _POSIX_SOURCE
#endif
#include <unistd.h>
#include "./manager/ActionLogger.h"
#include "./client/shared/ServerCommandExecutor.h"
#undef _POSIX_SOURCE
using namespace std;
@ -34,24 +35,12 @@ using namespace std::chrono;
using namespace ts;
using namespace ts::server;
#define INSTANCE_TICK_NAME "instance"
extern bool mainThreadActive;
InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
serverInstance = this;
this->general_task_executor_ = std::make_shared<task_executor>(ts::config::threads::ticking, "instance tick ");
this->general_task_executor_->set_exception_handler([](const std::string& task_name, const std::exception_ptr& exception) {
std::string message{};
try {
std::rethrow_exception(exception);
} catch (const std::exception& ex) {
message = "std::exception::what() -> " + std::string{ex.what()};
} catch(...) {
message = "unknown exception";
}
logCritical(LOG_INSTANCE, "Instance task executor received exception: {}", message);
});
this->tick_manager = make_shared<threads::Scheduler>(config::threads::ticking, "tick task ");
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr);
std::string error_message{};
@ -61,14 +50,7 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
}
this->dbHelper = new DatabaseHelper(this->getSql());
this->action_logger_ = std::make_unique<log::ActionLogger>();
if(!this->action_logger_->initialize(error_message)) {
logCritical(LOG_INSTANCE, "Failed to initialize instance action logs: {}", error_message);
logCritical(LOG_INSTANCE, "Action log has been disabled.");
this->action_logger_->finalize();
}
this->_properties = std::make_shared<PropertyManager>();
this->_properties = new Properties();
this->_properties->register_property_type<property::InstanceProperties>();
this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT] = ts::config::binding::DefaultFilePort;
this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST] = ts::config::binding::DefaultFileHost;
@ -120,57 +102,18 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
this->properties()[property::SERVERINSTANCE_PERMISSIONS_VERSION] = this->sql->get_permissions_version();
this->globalServerAdmin = std::make_shared<ts::server::InternalClient>(this->getSql(), nullptr, "serveradmin", true);
this->globalServerAdmin->initialize_weak_reference(this->globalServerAdmin);
globalServerAdmin = std::make_shared<ts::server::InternalClient>(this->getSql(), nullptr, "serveradmin", true);
static_pointer_cast<ts::server::InternalClient>(globalServerAdmin)->setSharedLock(globalServerAdmin);
ts::server::DatabaseHelper::assignDatabaseId(this->getSql(), 0, globalServerAdmin);
this->_musicRoot = std::make_shared<InternalClient>(this->getSql(), nullptr, "Music Manager", false);
dynamic_pointer_cast<InternalClient>(this->_musicRoot)->initialize_weak_reference(this->_musicRoot);
static_pointer_cast<InternalClient>(this->_musicRoot)->setSharedLock(this->_musicRoot);
{
using GroupLoadResult = groups::GroupLoadResult;
this->groupManager = std::make_shared<GroupManager>(nullptr, this->getSql());
this->groupManager->loadGroupFormDatabase();
this->group_manager_ = std::make_shared<groups::GroupManager>(this->getSql(), 0, nullptr);
if(!this->group_manager_->initialize(this->group_manager_, error_message)) {
logCritical(LOG_INSTANCE, "Failed to initialize instance group manager: {}", error_message);
mainThreadActive = false;
return;
}
bool initialize_groups{false};
switch(this->group_manager_->server_groups()->load_data(true)) {
case GroupLoadResult::SUCCESS:
break;
case GroupLoadResult::NO_GROUPS:
initialize_groups = true;
break;
case GroupLoadResult::DATABASE_ERROR:
logCritical(LOG_INSTANCE, "Failed to load instance server groups (Database error)");
mainThreadActive = false;
return;
}
switch(this->group_manager_->channel_groups()->load_data(true)) {
case GroupLoadResult::SUCCESS:
break;
case GroupLoadResult::NO_GROUPS:
initialize_groups = true;
break;
case GroupLoadResult::DATABASE_ERROR:
logCritical(LOG_INSTANCE, "Failed to load instance channel groups (Database error)");
mainThreadActive = false;
return;
}
if(!this->group_manager_->assignments().load_data(error_message)) {
logCritical(LOG_INSTANCE, "Failed to load instance group assignments: {}", error_message);
mainThreadActive = false;
return;
}
if (initialize_groups) {
if (this->groupManager->availableServerGroups(false).empty()) {
if(!this->setupDefaultGroups()){
logCritical(LOG_INSTANCE, "Could not setup server instance! Stopping...");
mainThreadActive = false;
@ -178,7 +121,30 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
}
}
this->validate_default_groups();
debugMessage(LOG_INSTANCE, "Instance admin group id " + to_string(this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP].as<GroupId>()));
auto instance_server_admin = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP].as<GroupId>());
if (!instance_server_admin) {
instance_server_admin = this->groupManager->availableServerGroups(false).front();
logCritical(LOG_INSTANCE, "Missing instance server admin group! Using first available (" + (instance_server_admin ? instance_server_admin->name() : "nil") + ")");
}
if(this->groupManager->listGroupAssignments(this->globalServerAdmin->getClientDatabaseId()).empty())
this->groupManager->addServerGroup(this->globalServerAdmin->getClientDatabaseId(), instance_server_admin);
debugMessage(LOG_INSTANCE, "Server guest group id " + to_string(this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as<GroupId>()));
auto instance_server_guest = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save<GroupId>());
if (!instance_server_guest) {
instance_server_guest = this->groupManager->availableServerGroups(false).front();
logCritical(LOG_INSTANCE, "Missing instance server guest group! Using first available (" + (instance_server_guest ? instance_server_guest->name() : "nil") + ")");
}
this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] = instance_server_guest->groupId();
debugMessage(LOG_INSTANCE, "Server music group id " + to_string(this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as<GroupId>()));
auto instance_server_music = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_save<GroupId>());
if (!instance_server_music) {
instance_server_music = instance_server_guest;
logCritical(LOG_INSTANCE, "Missing instance server music group! Using serverguest (" + (instance_server_music ? instance_server_music->name() : "nil") + ")");
}
this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP] = instance_server_music->groupId();
}
{
@ -206,12 +172,8 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
this->save_channel_permissions();
}
}
if(!this->default_tree->getDefaultChannel()) {
this->default_tree->setDefaultChannel(this->default_tree->findChannel("[cspacer04]Default Channel", nullptr));
}
if(!this->default_tree->getDefaultChannel()) {
this->default_tree->setDefaultChannel(*this->default_tree->channels().begin());
}
if(!this->default_tree->getDefaultChannel()) this->default_tree->setDefaultChannel(this->default_tree->findChannel("[cspacer04]Default Channel", nullptr));
if(!this->default_tree->getDefaultChannel()) this->default_tree->setDefaultChannel(*this->default_tree->channels().begin());
assert(this->default_tree->getDefaultChannel());
}
@ -220,24 +182,43 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
}
if(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as_or<int64_t>(0) == 0) {
if(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as<int64_t>() == 0) {
debugMessage(LOG_INSTANCE, "Setting up monthly reset timestamp!");
this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}
this->banMgr = new BanManager(this->getSql());
this->banMgr->loadBans();
this->web_list = make_shared<weblist::WebListManager>();
}
void InstanceHandler::executeTick(VirtualServer* server) {
auto str = "server_" + to_string(server->getServerId());
if(!this->tick_manager->schedule(str, std::bind(&VirtualServer::executeServerTick, server), milliseconds(500))) {
logCritical(LOG_INSTANCE, "Could not schedule server ticking task!");
}
}
void InstanceHandler::cancelExecute(VirtualServer* server) {
auto str = "server_" + to_string(server->getServerId());
if(!this->tick_manager->cancelTask(str)){
logError(LOG_INSTANCE, "Could not stop server tick task!");
}
}
InstanceHandler::~InstanceHandler() {
delete this->_properties;
delete this->banMgr;
delete this->dbHelper;
group_manager_ = nullptr;
groupManager = nullptr;
globalServerAdmin = nullptr;
_musicRoot = nullptr;
statistics = nullptr;
tick_manager = nullptr;
}
inline string strip(std::string message) {
@ -263,21 +244,12 @@ inline vector<string> split_hosts(const std::string& message, char delimiter) {
}
bool InstanceHandler::startInstance() {
if (this->active) {
if (this->active)
return false;
}
active = true;
string errorMessage;
this->server_command_executor_ = std::make_shared<ServerCommandExecutor>(ts::config::threads::command_execute);
this->network_event_loop_ = std::make_unique<NetworkEventLoop>(ts::config::threads::network_events);
if(!this->network_event_loop_->initialize()) {
this->server_command_executor_ = nullptr;
this->network_event_loop_ = nullptr;
logCritical(LOG_INSTANCE, "Failed to initialize network event loop");
return false;
}
this->web_list->enabled = ts::config::server::enable_teamspeak_weblist;
this->permission_mapper = make_shared<permission::PermissionNameMapper>();
if(!this->permission_mapper->initialize(config::permission_mapping_file, errorMessage)) {
@ -309,11 +281,31 @@ bool InstanceHandler::startInstance() {
}
this->loadWebCertificate();
fileServer = new ts::server::LocalFileServer();
{
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;
this->file_server_handler_ = new file::FileServerHandler{this};
if(!this->file_server_handler_->initialize(errorMessage)) {
logCritical(LOG_FT, "Failed to initialize server: {}", errorMessage);
return false;
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>();
memcpy(&entry->address, &get<1>(binding), sizeof(sockaddr_storage));
entry->file_descriptor = -1;
entry->event_accept = nullptr;
bindings.push_back(entry);
}
logMessage(LOG_FT, "Starting server on {}:{}", bindings_string, port);
if(!fileServer->start(bindings, errorMessage)) {
logCritical(LOG_FT, "Failed to start server: {}", errorMessage);
return false;
}
}
if(config::query::sslMode > 0) {
@ -341,8 +333,8 @@ bool InstanceHandler::startInstance() {
}
{
auto query_bindings_string = this->properties()[property::SERVERINSTANCE_QUERY_HOST].value();
auto query_port = this->properties()[property::SERVERINSTANCE_QUERY_PORT].as_or<uint16_t>(0);
auto query_bindings_string = this->properties()[property::SERVERINSTANCE_QUERY_HOST].as<string>();
auto query_port = this->properties()[property::SERVERINSTANCE_QUERY_PORT].as<uint16_t>();
auto query_bindings = net::resolve_bindings(query_bindings_string, query_port);
deque<shared_ptr<QueryServer::Binding>> bindings;
@ -384,6 +376,7 @@ FwIDAQAB
logCritical(LOG_GENERAL, "Failed to initialize WebClient TeaForum key! ({})", error);
return false;
}
this->web_event_loop = make_shared<webio::LoopManager>();
}
#endif
@ -420,14 +413,7 @@ FwIDAQAB
startTimestamp = system_clock::now();
this->voiceServerManager->executeAutostart();
this->general_task_executor()->schedule_repeating(
this->tick_task_id,
"instance ticker",
std::chrono::milliseconds{500},
[&](const auto&) {
this->tickInstance();
}
);
this->scheduler()->schedule(INSTANCE_TICK_NAME, bind(&InstanceHandler::tickInstance, this), milliseconds{500});
return true;
}
@ -438,13 +424,10 @@ void InstanceHandler::stopInstance() {
this->active = false;
this->activeCon.notify_all();
}
this->server_command_executor_->shutdown();
/* TODO: Block on canceling. */
this->general_task_executor()->cancel_task(this->tick_task_id);
this->tick_task_id = 0;
this->web_list->enabled = false;
threads::MutexLock lock_tick(this->lock_tick);
this->scheduler()->cancelTask(INSTANCE_TICK_NAME);
debugMessage(LOG_INSTANCE, "Stopping all virtual servers");
if (this->voiceServerManager)
@ -460,32 +443,25 @@ void InstanceHandler::stopInstance() {
debugMessage(LOG_QUERY, "Query server stopped");
debugMessage(LOG_FT, "Stopping file server");
file::finalize();
if (this->fileServer) this->fileServer->stop();
delete this->fileServer;
this->fileServer = nullptr;
debugMessage(LOG_FT, "File server stopped");
this->save_channel_permissions();
this->save_group_permissions();
if(this->file_server_handler_) {
this->file_server_handler_->finalize();
delete std::exchange(this->file_server_handler_, nullptr);
}
delete this->sslMgr;
this->sslMgr = nullptr;
this->license_service_->shutdown();
this->server_command_executor_ = nullptr;
this->web_event_loop = nullptr;
this->network_event_loop_->shutdown();
this->network_event_loop_ = nullptr;
this->license_service_->shutdown();
}
void InstanceHandler::tickInstance() {
threads::MutexLock lock(this->lock_tick);
if(!this->active) {
return;
}
if(!this->active) return;
auto now = system_clock::now();
@ -514,8 +490,7 @@ void InstanceHandler::tickInstance() {
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick [monthly]", milliseconds(2));
auto month_timestamp = system_clock::time_point() + seconds(
this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as_or<int64_t>(0));
auto month_timestamp = system_clock::time_point() + seconds(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as<int64_t>());
auto time_t_old = system_clock::to_time_t(month_timestamp);
auto time_t_new = system_clock::to_time_t(system_clock::now());
@ -536,13 +511,18 @@ void InstanceHandler::tickInstance() {
if(memcleanTimestamp + minutes(10) < now) {
memcleanTimestamp = now;
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5));
auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS);
if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0) {
logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal);
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5));
auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS);
if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0)
logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal);
}
}
}
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> fileserver tick", milliseconds(5));
if(this->fileServer) this->fileServer->instanceTick();
}
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> sql_test tick", milliseconds(5));
if(this->sql && this->active) {
@ -552,7 +532,7 @@ void InstanceHandler::tickInstance() {
auto command = this->sql->sql()->getType() == sql::TYPE_SQLITE ? "SELECT * FROM `sqlite_master`" : "SHOW TABLES";
auto result = sql::command(this->getSql(), command).query([command](int, string*, string*){ return 0; });
if(!result) {
logCritical(LOG_INSTANCE, "Dummy sql connection test failed! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr());
logCritical(LOG_INSTANCE, "Dummy sql connection test faild! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr());
logCritical(LOG_INSTANCE, "Stopping instance!");
ts::server::shutdownInstance("invalid sql connection!");
}
@ -574,14 +554,25 @@ void InstanceHandler::tickInstance() {
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE] = this->calculateSpokenTime().count();
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL] =
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as_or<uint64_t>(0) +
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as_or<uint64_t>(0) +
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as_or<uint64_t>(0);
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>() +
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>() +
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
}
this->web_list->tick();
}
void InstanceHandler::save_group_permissions() {
this->group_manager()->save_permissions();
auto groups = this->getGroupManager()->availableGroups(false);
for(auto& group : groups) {
auto permissions = group->permissions();
if(permissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveGroupPermissions(nullptr, 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());
}
}
}
void InstanceHandler::save_channel_permissions() {
@ -654,15 +645,15 @@ std::shared_ptr<ts::server::license::InstanceLicenseInfo> InstanceHandler::gener
auto request = std::make_shared<license::InstanceLicenseInfo>();
request->license = config::license;
request->metrics.servers_online = this->voiceServerManager->runningServers();
auto report = this->voiceServerManager->instanceSlotUsageReport();
request->metrics.client_online = report.clients_teamspeak;
request->metrics.web_clients_online = report.clients_teaweb;
request->metrics.bots_online = report.music_bots;
auto report = this->voiceServerManager->clientReport();
request->metrics.client_online = report.clients_ts;
request->metrics.web_clients_online = report.clients_web;
request->metrics.bots_online = report.bots;
request->metrics.queries_online = report.queries;
request->metrics.speech_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as_or<uint64_t>(0);
request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as_or<uint64_t>(0);
request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as_or<uint64_t>(0);
request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as_or<uint64_t>(0);
request->metrics.speech_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as<uint64_t>();
request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>();
request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>();
static std::string null_str{"\0\0\0\0\0\0\0\0", 8}; /* we need at least some characters */
request->web_certificate_revision = this->web_cert_revision.empty() ? null_str : this->web_cert_revision;
@ -685,11 +676,11 @@ std::shared_ptr<ts::server::license::InstanceLicenseInfo> InstanceHandler::gener
{ /* unique id */
auto property_unique_id = this->properties()[property::SERVERINSTANCE_UNIQUE_ID];
if(property_unique_id.value().empty())
if(property_unique_id.as<string>().empty())
property_unique_id = rnd_string(64);
auto hash = digest::sha256(request->info.uname);
hash = digest::sha256(hash + property_unique_id.value() + get_mac_address());
hash = digest::sha256(hash + property_unique_id.as<string>() + get_mac_address());
request->info.unique_id = base64::encode(hash);
}
}
@ -902,61 +893,35 @@ void InstanceHandler::loadWebCertificate() {
this->web_cert_revision = revision;
}
bool InstanceHandler::validate_default_groups() {
using groups::GroupCalculateMode;
using groups::GroupAssignmentCalculateMode;
{
auto property = this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP];
auto group_id = property.as_or<GroupId>(0);
debugMessage(LOG_INSTANCE, "Instance admin query group id {}", group_id);
auto group_instance = this->group_manager_->server_groups()->find_group(GroupCalculateMode::GLOBAL, group_id);
if(!group_instance) {
auto available_groups = this->group_manager_->server_groups()->available_groups(GroupCalculateMode::GLOBAL);
if(available_groups.empty()) {
logCritical(LOG_INSTANCE, "Missing instance server groups.");
return false;
}
group_instance = available_groups.front();
logCritical(LOG_INSTANCE, "Missing instance server admin query group. Using first available ({})", group_instance->group_id());
}
property.update_value(group_instance->group_id());
{
auto& assignments = this->group_manager_->assignments();
auto client_assignments = assignments.server_groups_of_client(GroupAssignmentCalculateMode::GLOBAL, this->globalServerAdmin->getClientDatabaseId());
if(client_assignments.empty()) {
assignments.add_server_group(this->globalServerAdmin->getClientDatabaseId(), group_instance->group_id(), false);
}
}
}
{
auto property = this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP];
auto group_id = property.as_or<GroupId>(0);
debugMessage(LOG_INSTANCE, "Instance guest query group id {}", group_id);
auto group_instance = this->group_manager_->server_groups()->find_group(GroupCalculateMode::GLOBAL, group_id);
if(!group_instance) {
auto available_groups = this->group_manager_->server_groups()->available_groups(GroupCalculateMode::GLOBAL);
if(available_groups.empty()) {
logCritical(LOG_INSTANCE, "Missing instance server groups.");
return false;
}
group_instance = available_groups.front();
logCritical(LOG_INSTANCE, "Missing instance server guest query group. Using first available ({})", group_instance->group_id());
}
property.update_value(group_instance->group_id());
}
return true;
permission::v2::PermissionFlaggedValue InstanceHandler::calculate_permission(permission::PermissionType permission,
ClientDbId cldbid, ClientType type, ChannelId channel, bool granted, std::shared_ptr<CalculateCache> cache) {
auto result = this->calculate_permissions({permission}, cldbid, type, channel, granted, cache);
if(result.empty()) return {0, false};
return result.front().second;
}
std::shared_ptr<groups::ServerGroup> InstanceHandler::guest_query_group() {
auto group_id = this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_or(0);
return this->group_manager_->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id);
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue> > InstanceHandler::calculate_permissions(
const std::deque<permission::PermissionType> &permissions, ClientDbId cldbid, ClientType type, ChannelId channel, bool granted, std::shared_ptr<CalculateCache> cache) {
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> result{};
//TODO: Negate?
//TODO: May move this part to the instance?
auto server_groups = this->getGroupManager()->getServerGroups(cldbid, type);
for(const auto& permission : permissions) {
permission::v2::PermissionFlaggedValue value{0, false};
for(const auto &gr : server_groups){
auto group_permissions = gr->group->permissions();
auto flagged_permissions = granted ? group_permissions->permission_granted_flagged(permission) : group_permissions->permission_value_flagged(permission);
if(flagged_permissions.has_value)
if(!value.has_value || flagged_permissions.value > value.value || flagged_permissions.value == -1)
value = flagged_permissions;
}
result.emplace_back(permission, value);
}
return result;
}

View File

@ -7,9 +7,13 @@
#include <src/lincense/LicenseService.h>
#include "manager/SqlDataManager.h"
#include "lincense/TeamSpeakLicense.h"
#include <misc/task_executor.h>
#include "server/WebIoManager.h"
namespace ts {
namespace weblist {
class WebListManager;
}
namespace permission {
class PermissionNameMapper;
}
@ -19,22 +23,6 @@ namespace ts {
class LicenseService;
}
namespace file {
class FileServerHandler;
}
namespace log {
class ActionLogger;
}
namespace groups {
class GroupManager;
}
class NetworkEventLoop;
class ServerCommandExecutor;
class InstanceHandler;
class InstanceHandler {
public:
explicit InstanceHandler(SqlDataManager*);
@ -43,32 +31,29 @@ namespace ts {
bool startInstance();
void stopInstance();
inline PropertyWrapper properties() { return PropertyWrapper{this->_properties}; }
inline const PropertyWrapper properties() const { return PropertyWrapper{this->_properties}; }
ts::Properties& properties(){
return *_properties;
}
std::shared_ptr<ts::server::InternalClient> getInitialServerAdmin(){ return globalServerAdmin; }
const auto& group_manager(){ return this->group_manager_; }
/**
* Get the default instance server query group.
* @return the default group or `nullptr` if the group doesn't exists any more.
*/
[[nodiscard]] std::shared_ptr<groups::ServerGroup> guest_query_group();
std::shared_ptr<ts::GroupManager> getGroupManager(){ return groupManager; }
std::shared_ptr<ts::ServerChannelTree> getChannelTree() { return this->default_tree; }
std::shared_mutex& getChannelTreeLock() { return this->default_tree_lock; }
VirtualServerManager* getVoiceServerManager(){ return this->voiceServerManager; }
LocalFileServer* getFileServer(){ return fileServer; }
QueryServer* getQueryServer(){ return queryServer; }
DatabaseHelper* databaseHelper(){ return this->dbHelper; }
BanManager* banManager(){ return this->banMgr; }
ssl::SSLManager* sslManager(){ return this->sslMgr; }
sql::SqlManager* getSql(){ return sql->sql(); }
log::ActionLogger* action_logger() { return &*this->action_logger_; }
file::FileServerHandler* getFileServerHandler() { return this->file_server_handler_; }
std::chrono::time_point<std::chrono::system_clock> getStartTimestamp(){ return startTimestamp; }
void executeTick(VirtualServer*);
void cancelExecute(VirtualServer*);
bool reloadConfig(std::vector<std::string>& /* errors */, bool /* reload file */);
void setWebCertRoot(const std::string& /* key */, const std::string& /* certificate */, const std::string& /* revision */);
@ -79,19 +64,35 @@ namespace ts {
bool resetMonthlyStats();
[[nodiscard]] inline const auto& general_task_executor(){ return this->general_task_executor_; }
[[nodiscard]] inline const auto& network_event_loop(){ return this->network_event_loop_; }
std::shared_ptr<stats::ConnectionStatistics> getStatistics(){ return statistics; }
std::shared_ptr<threads::Scheduler> scheduler(){ return this->tick_manager; }
std::shared_ptr<license::InstanceLicenseInfo> generateLicenseData();
[[nodiscard]] inline std::shared_ptr<stats::ConnectionStatistics> getStatistics(){ return statistics; }
[[nodiscard]] std::shared_ptr<license::InstanceLicenseInfo> generateLicenseData();
std::shared_ptr<TeamSpeakLicense> getTeamSpeakLicense() { return this->teamspeak_license; }
std::shared_ptr<ts::Properties> getDefaultServerProperties() { return this->default_server_properties; }
std::shared_ptr<webio::LoopManager> getWebIoLoop() { return this->web_event_loop; }
std::shared_ptr<weblist::WebListManager> getWebList() { return this->web_list; }
[[nodiscard]] inline std::shared_ptr<TeamSpeakLicense> getTeamSpeakLicense() { return this->teamspeak_license; }
[[nodiscard]] inline PropertyWrapper getDefaultServerProperties() { return PropertyWrapper{this->default_server_properties}; }
std::shared_ptr<permission::PermissionNameMapper> getPermissionMapper() { return this->permission_mapper; }
std::shared_ptr<ts::event::EventExecutor> getConversationIo() { return this->conversation_io; }
[[nodiscard]] inline std::shared_ptr<permission::PermissionNameMapper> getPermissionMapper() { return this->permission_mapper; }
[[nodiscard]] inline std::shared_ptr<ts::event::EventExecutor> getConversationIo() { return this->conversation_io; }
permission::v2::PermissionFlaggedValue calculate_permission(
permission::PermissionType,
ClientDbId,
ClientType type,
ChannelId channel,
bool granted = false,
std::shared_ptr<CalculateCache> cache = nullptr
);
[[nodiscard]] inline const auto& server_command_executor() { return this->server_command_executor_; }
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> calculate_permissions(
const std::deque<permission::PermissionType>&,
ClientDbId,
ClientType type,
ChannelId channel,
bool granted = false,
std::shared_ptr<CalculateCache> cache = nullptr
);
[[nodiscard]] inline std::shared_ptr<license::LicenseService> license_service() { return this->license_service_; }
private:
@ -99,8 +100,6 @@ namespace ts {
std::condition_variable activeCon;
bool active = false;
task_id tick_task_id{};
std::chrono::system_clock::time_point startTimestamp;
std::chrono::system_clock::time_point sqlTestTimestamp;
std::chrono::system_clock::time_point speachUpdateTimestamp;
@ -111,34 +110,30 @@ namespace ts {
std::chrono::system_clock::time_point memcleanTimestamp;
SqlDataManager* sql;
LocalFileServer* fileServer = nullptr;
QueryServer* queryServer = nullptr;
VirtualServerManager* voiceServerManager = nullptr;
DatabaseHelper* dbHelper = nullptr;
BanManager* banMgr = nullptr;
ssl::SSLManager* sslMgr = nullptr;
file::FileServerHandler* file_server_handler_{nullptr};
std::unique_ptr<log::ActionLogger> action_logger_{nullptr};
std::unique_ptr<NetworkEventLoop> network_event_loop_{nullptr};
std::shared_ptr<ts::PropertyManager> _properties{};
std::shared_ptr<ServerCommandExecutor> server_command_executor_{};
ts::Properties* _properties = nullptr;
std::shared_ptr<ts::event::EventExecutor> conversation_io = nullptr;
std::shared_ptr<webio::LoopManager> web_event_loop = nullptr;
std::shared_ptr<weblist::WebListManager> web_list = nullptr;
std::shared_ptr<ts::PropertyManager> default_server_properties = nullptr;
std::shared_mutex default_tree_lock;
std::shared_ptr<ts::Properties> default_server_properties = nullptr;
std::shared_ptr<ts::ServerChannelTree> default_tree = nullptr;
std::shared_ptr<groups::GroupManager> group_manager_{nullptr};
std::shared_mutex default_tree_lock;
std::shared_ptr<ts::GroupManager> groupManager = nullptr;
std::shared_ptr<ts::server::InternalClient> globalServerAdmin = nullptr;
std::shared_ptr<ConnectedClient> _musicRoot = nullptr;
std::shared_ptr<license::LicenseService> license_service_{nullptr};
std::shared_ptr<stats::ConnectionStatistics> statistics = nullptr;
std::shared_ptr<task_executor> general_task_executor_{nullptr};
std::shared_ptr<threads::Scheduler> tick_manager = nullptr;
std::shared_ptr<permission::PermissionNameMapper> permission_mapper = nullptr;
std::shared_ptr<TeamSpeakLicense> teamspeak_license = nullptr;
@ -154,7 +149,6 @@ namespace ts {
void save_channel_permissions();
void loadWebCertificate();
bool validate_default_groups();
};
}
}

View File

@ -1,10 +0,0 @@
//
// Created by WolverinDEV on 26/02/2021.
//
#include "./InstanceHandler.h"
#include "./groups/GroupManager.h"
#include "./PermissionCalculator.h"
using namespace ts;
using namespace ts::server;

View File

@ -1,10 +1,12 @@
#include <netdb.h>
#include <deque>
#include <tuple>
#include <fstream>
#include <log/LogUtils.h>
#include "InstanceHandler.h"
#include "src/client/InternalClient.h"
#include "src/server/QueryServer.h"
#include "./groups/GroupManager.h"
#include "src/server/file/LocalFileServer.h"
using namespace std;
using namespace std::chrono;
@ -22,9 +24,9 @@ struct GroupInfo {
*/
int target;
std::deque<string> properties;
std::string name;
string name;
/* permission type, value, granted, skip, negate */
std::deque<std::tuple<permission::PermissionType, permission::PermissionValue, permission::PermissionValue, bool, bool>> permissions;
deque<tuple<permission::PermissionType, permission::PermissionValue, permission::PermissionValue, bool, bool>> permissions;
};
/* TODO may use a transaction here? */
@ -118,45 +120,17 @@ bool InstanceHandler::setupDefaultGroups() {
groups.push_back(group);
}
debugMessage(LOG_INSTANCE, "Read {} default groups", groups.size());
debugMessage(LOG_INSTANCE, "Read " + to_string(groups.size()) + " default groups");
for(const auto& info : groups) {
debugMessage(LOG_INSTANCE, "Creating default group {} with type {}", info->name, to_string(info->target));
//Query groups
std::shared_ptr<groups::Group> created_group{};
groups::GroupCreateResult create_result{};
if(info->target == 2) {
std::shared_ptr<groups::ChannelGroup> c_group{};
create_result = serverInstance->group_manager()->channel_groups()->create_group(groups::GroupType::GROUP_TYPE_TEMPLATE, info->name, c_group);
created_group = c_group;
} else {
std::shared_ptr<groups::ServerGroup> s_group{};
create_result = serverInstance->group_manager()->server_groups()->create_group(info->target == 0 ? groups::GroupType::GROUP_TYPE_QUERY : groups::GroupType::GROUP_TYPE_TEMPLATE, info->name, s_group);
created_group = s_group;
}
switch(create_result) {
case groups::GroupCreateResult::SUCCESS:
break;
case groups::GroupCreateResult::DATABASE_ERROR:
logCritical(LOG_INSTANCE, "Failed to insert template group {} (Database error)", info->name);
return false;
case groups::GroupCreateResult::NAME_TOO_LONG:
case groups::GroupCreateResult::NAME_ALREADY_IN_USED:
case groups::GroupCreateResult::NAME_TOO_SHORT:
logCritical(LOG_INSTANCE, "Failed to insert template group {} (Name issue)", info->name);
return false;
default:
logCritical(LOG_INSTANCE, "Failed to insert template group {} (Unkown error)", info->name);
return false;
}
auto group = this->groupManager->createGroup(
info->target != 2 ? GroupTarget::GROUPTARGET_SERVER : GroupTarget::GROUPTARGET_CHANNEL,
info->target == 0 ? GroupType::GROUP_TYPE_QUERY : GroupType::GROUP_TYPE_TEMPLATE,
info->name
);
for(auto perm : info->permissions) {
created_group->permissions()->set_permission(get<0>(perm), {get<1>(perm), get<2>(perm)}, get<1>(perm) == permNotGranted ? permission::v2::do_nothing : permission::v2::set_value, get<2>(perm) == permNotGranted ? permission::v2::do_nothing : permission::v2::set_value, get<3>(perm), get<4>(perm));
group->permissions()->set_permission(get<0>(perm), {get<1>(perm), get<2>(perm)}, permission::v2::set_value, permission::v2::set_value, get<3>(perm), get<4>(perm));
}
for(const auto& property : info->properties) {
@ -164,7 +138,7 @@ bool InstanceHandler::setupDefaultGroups() {
if(prop.is_undefined()) {
logCritical(LOG_INSTANCE, "Invalid template property name: " + property);
} else {
this->properties()[prop] = created_group->group_id();
this->properties()[prop] = group->groupId();
}
}
}

View File

@ -1,408 +0,0 @@
//
// Created by WolverinDEV on 27/02/2021.
//
#include "./PermissionCalculator.h"
#include "./InstanceHandler.h"
#include "./groups/Group.h"
#include "./client/DataClient.h"
#include <PermissionManager.h>
#include <log/LogUtils.h>
#include <src/groups/GroupManager.h>
using namespace ts::server;
using ts::permission::PermissionType;
using ts::permission::v2::PermissionFlaggedValue;
ClientPermissionCalculator::ClientPermissionCalculator(DataClient *client, ChannelId channel_id) {
/* Note: Order matters! */
this->initialize_client(client);
this->initialize_default_groups(client->getServer());
auto server = client->getServer();
if(server && channel_id > 0) {
std::shared_ptr<BasicChannel> channel{};
try {
std::shared_lock channel_lock{server->get_channel_tree_lock()};
this->channel_ = server->getChannelTree()->findChannel(channel_id);
} catch (std::system_error& e) {
if(e.code() != std::errc::resource_deadlock_would_occur) {
throw;
}
/* tree already write locked, no need to lock it again */
this->channel_ = server->getChannelTree()->findChannel(channel_id);
}
}
}
ClientPermissionCalculator::ClientPermissionCalculator(DataClient *client, const std::shared_ptr<BasicChannel> &channel) {
/* Note: Order matters! */
this->initialize_client(client);
this->initialize_default_groups(client->getServer());
this->channel_ = channel;
}
ClientPermissionCalculator::ClientPermissionCalculator(
const std::shared_ptr<VirtualServer> &server,
ClientDbId client_database_id,
ClientType client_type,
ChannelId channel_id) {
this->client_database_id = client_database_id;
this->client_type = client_type;
if(server) {
this->virtual_server_id = server->getServerId();
this->group_manager_ = server->group_manager();
try {
std::shared_lock channel_lock{server->get_channel_tree_lock()};
this->channel_ = server->getChannelTree()->findChannel(channel_id);
} catch (std::system_error& e) {
if(e.code() != std::errc::resource_deadlock_would_occur) {
throw;
}
/* tree already write locked, no need to lock it again */
this->channel_ = server->getChannelTree()->findChannel(channel_id);
}
} else {
this->virtual_server_id = 0;
this->group_manager_ = serverInstance->group_manager();
}
this->initialize_default_groups(server);
}
void ClientPermissionCalculator::initialize_client(DataClient* client) {
this->virtual_server_id = client->getServerId();
this->client_database_id = client->getClientDatabaseId();
this->client_type = client->getType();
this->client_permissions_ = client->permissions();
auto server = client->getServer();
if(server) {
this->group_manager_ = server->group_manager();
} else {
this->group_manager_ = serverInstance->group_manager();
}
}
void ClientPermissionCalculator::initialize_default_groups(const std::shared_ptr<VirtualServer> &server) {
if(this->client_type == ClientType::CLIENT_QUERY) {
this->default_server_group = []{ return serverInstance->guest_query_group(); };
} else if(server) {
this->default_server_group = [server]{ return server->default_server_group(); };
}
if(server) {
this->default_channel_group = [server]{ return server->default_channel_group(); };
}
}
PermissionFlaggedValue ClientPermissionCalculator::calculate_permission(permission::PermissionType permission,
bool granted) {
auto result = this->calculate_permissions({permission}, granted);
if(result.empty()) {
return { permNotGranted, false };
}
return result.front().second;
}
std::vector<std::pair<PermissionType, PermissionFlaggedValue>> ClientPermissionCalculator::calculate_permissions(
const std::deque<permission::PermissionType> &permissions,
bool calculate_granted
) {
if(permissions.empty()) {
return {};
}
std::vector<std::pair<PermissionType, PermissionFlaggedValue>> result;
result.reserve(permissions.size());
auto client_permissions = this->client_permissions();
assert(client_permissions);
/*
* server_group_data[0] := Server group id
* server_group_data[1] := Skip flag
* server_group_data[2] := Negate flag
* server_group_data[3] := Permission value
*/
typedef std::tuple<GroupId, bool, bool, permission::PermissionValue> GroupData;
bool server_group_data_initialized = false;
std::vector<GroupData> server_group_data;
GroupData* active_server_group;
auto initialize_group_data = [&](const permission::PermissionType& permission_type) {
server_group_data_initialized = true;
active_server_group = nullptr;
auto assigned_groups = this->assigned_server_groups();
server_group_data.resize(assigned_groups.size());
auto it = server_group_data.begin();
for(auto& group : assigned_groups) {
auto group_permissions = group->permissions();
auto permission_flags = group_permissions->permission_flags(permission_type);
auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set;
if(!flag_set) {
continue;
}
//TODO: Test if there is may a group channel permissions
auto value = group_permissions->permission_values(permission_type);
*it = std::make_tuple(group->group_id(), (bool) permission_flags.skip, (bool) permission_flags.negate, calculate_granted ? value.grant : value.value);
it++;
}
if(it == server_group_data.begin()) {
return; /* no server group has that permission */
}
server_group_data.erase(it, server_group_data.end()); /* remove unneeded */
auto found_negate = false;
for(auto& group : server_group_data) {
if(std::get<2>(group)) {
found_negate = true;
break;
}
}
if(found_negate) {
server_group_data.erase(remove_if(server_group_data.begin(), server_group_data.end(), [](auto data) { return !std::get<2>(data); }), server_group_data.end());
logTrace(this->virtual_server_id, "[Permission] Found negate flag within server groups. Groups left: {}", server_group_data.size());
if(server_group_data.empty()) {
logTrace(this->virtual_server_id, "[Permission] After non negated groups have been kicked out the negated groups are empty! This should not happen! Permission: {}, Client ID: {}", permission_type, this->client_database_id);
}
permission::PermissionValue current_lowest = 0;
for(auto& group : server_group_data) {
if(!active_server_group || (std::get<3>(group) < current_lowest && std::get<3>(group) != -1)) {
current_lowest = std::get<3>(group);
active_server_group = &group;
}
}
} else {
permission::PermissionValue current_highest = 0;
for(auto& group : server_group_data) {
if(!active_server_group || (std::get<3>(group) > current_highest || std::get<3>(group) == -1)) {
current_highest = std::get<3>(group);
active_server_group = &group;
}
}
}
};
for(const auto& permission : permissions) {
server_group_data_initialized = false; /* reset all group data */
auto client_permission_flags = client_permissions->permission_flags(permission);
/* lets try to resolve the channel specific permission */
if(this->channel_ && client_permission_flags.channel_specific) {
auto data = client_permissions->channel_permission(permission, this->channel_->channelId());
if(calculate_granted ? data.flags.grant_set : data.flags.value_set) {
result.push_back({permission, {calculate_granted ? data.values.grant : data.values.value, true}});
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Client channel permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, data.values.value);
continue;
}
}
bool skip_channel_permissions = !this->channel_ || this->has_global_skip_permission();
if(!skip_channel_permissions) {
/* We dont have a global skip flag. Lets see if the target permission has skip enabled */
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
/* okey the client has the permission, this counts */
skip_channel_permissions = client_permission_flags.skip;
} else {
if(!server_group_data_initialized) {
initialize_group_data(permission);
}
if(active_server_group) {
skip_channel_permissions = std::get<1>(*active_server_group);
}
}
}
if(!skip_channel_permissions) {
/* lookup the channel group */
{
auto channel_assignment = this->assigned_channel_group();
if(channel_assignment) {
auto group_permissions = channel_assignment->permissions();
auto permission_flags = group_permissions->permission_flags(permission);
auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set;
if(flag_set) {
auto value = group_permissions->permission_values(permission);
result.push_back({permission, {calculate_granted ? value.grant : value.value, true}});
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Channel group permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, calculate_granted ? value.grant : value.value);
continue;
}
}
}
/* lookup the channel permissions. Whyever? */
if(this->channel_) {
auto channel_permissions = this->channel_->permissions();
auto data = calculate_granted ? channel_permissions->permission_granted_flagged(permission) : channel_permissions->permission_value_flagged(permission);
if(data.has_value) {
result.push_back({permission, {data.value, true}});
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Channel permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, data.value);
continue;
}
}
}
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
auto client_value = client_permissions->permission_values(permission);
result.push_back({permission, {calculate_granted ? client_value.grant : client_value.value, true}});
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Client permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, client_value.value);
continue;
}
if(!server_group_data_initialized) {
initialize_group_data(permission);
}
if(active_server_group) {
result.push_back({permission, {get<3>(*active_server_group), true}});
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Server group permission of group {})", this->client_database_id, permission::resolvePermissionData(permission)->name, get<3>(*active_server_group), get<0>(*active_server_group));
continue;
}
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned in no permission.", this->client_database_id, permission::resolvePermissionData(permission)->name);
result.push_back({permission, { permNotGranted, false }});
}
return result;
}
const std::shared_ptr<ts::permission::v2::PermissionManager> & ClientPermissionCalculator::client_permissions() {
if(!this->client_permissions_) {
this->client_permissions_ = serverInstance->databaseHelper()->loadClientPermissionManager(this->virtual_server_id, this->client_database_id);
if(!this->client_permissions_) {
logCritical(this->virtual_server_id, "Failed to load client permissions for client {}. Using empty permission set.", this->client_database_id);
this->client_permissions_ = std::make_shared<permission::v2::PermissionManager>();
}
}
return this->client_permissions_;
}
const std::vector<std::shared_ptr<groups::ServerGroup>>& ClientPermissionCalculator::assigned_server_groups() {
if(this->assigned_server_groups_.has_value()) {
return *this->assigned_server_groups_;
}
auto assignments = this->group_manager_->assignments().server_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->client_database_id);
this->assigned_server_groups_.emplace();
this->assigned_server_groups_->reserve(assignments.size());
for(const auto& group_id : assignments) {
auto group = this->group_manager_->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(group) {
this->assigned_server_groups_->push_back(group);
}
}
if(this->assigned_server_groups_->empty() && this->default_server_group) {
auto default_group = this->default_server_group();
if(default_group) {
this->assigned_server_groups_->push_back(default_group);
}
}
return *this->assigned_server_groups_;
}
const std::shared_ptr<groups::ChannelGroup>& ClientPermissionCalculator::assigned_channel_group() {
if(this->assigned_channel_group_.has_value()) {
return *this->assigned_channel_group_;
}
this->assigned_channel_group_.emplace();
if(!this->channel_) {
return *this->assigned_channel_group_;
}
std::shared_ptr<BasicChannel> inherited_channel{this->channel_};
auto channel_group_assignment = this->group_manager_->assignments().calculate_channel_group_of_client(
groups::GroupAssignmentCalculateMode::GLOBAL,
this->client_database_id,
inherited_channel
);
if(channel_group_assignment.has_value()) {
assert(inherited_channel);
auto channel_group = this->group_manager_->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, *channel_group_assignment);
if(channel_group) {
this->assigned_channel_group_.emplace(std::move(channel_group));
logTrace(this->virtual_server_id, "[Permission] Using calculated channel group with id {} (Channel id: {}, Inherited channel id: {}).",
*channel_group_assignment, this->channel_->channelId(), inherited_channel->channelId());
} else {
logTrace(this->virtual_server_id, "[Permission] Missing calculated channel group with id {} (Channel id: {}, Inherited channel id: {}). Using default channel group.",
*channel_group_assignment, this->channel_->channelId(), inherited_channel->channelId());
}
} else {
logTrace(this->virtual_server_id, "[Permission] Using default channel group.");
}
if(!this->assigned_channel_group_.has_value()) {
assigned_channel_group_.emplace(
this->default_channel_group()
);
}
return *this->assigned_channel_group_;
}
bool ClientPermissionCalculator::has_global_skip_permission() {
if(this->skip_enabled.has_value()) {
return *this->skip_enabled;
}
/* test for skip permission within the client permission manager */
{
auto client_permissions = this->client_permissions();
auto skip_value = client_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions);
if(skip_value.has_value) {
this->skip_enabled = std::make_optional(permission::v2::permission_granted(1, skip_value));
logTrace(this->virtual_server_id, "[Permission] Found skip permission in client permissions. Value: {}", *this->skip_enabled);
}
}
/* test for skip permission within all server groups */
if(!this->skip_enabled.has_value()) {
for(const auto& assignment : this->assigned_server_groups()) {
auto group_permissions = assignment->permissions();
auto flagged_value = group_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions);
if(flagged_value.has_value) {
this->skip_enabled = std::make_optional(permission::v2::permission_granted(1, flagged_value));
if(*this->skip_enabled) {
logTrace(this->virtual_server_id, "[Permission] Found skip permission in client server group. Group: {} ({})", assignment->group_id(), assignment->display_name());
break;
}
}
}
}
if(!this->skip_enabled.has_value()) {
this->skip_enabled = std::make_optional(false);
}
return *this->skip_enabled;
}
bool ClientPermissionCalculator::permission_granted(const permission::PermissionType &permission,
const permission::PermissionValue &required_value, bool granted) {
return this->permission_granted(permission, { required_value, true }, granted);
}
bool ClientPermissionCalculator::permission_granted(const permission::PermissionType &permission,
const permission::v2::PermissionFlaggedValue &required_value, bool granted) {
return permission::v2::permission_granted(required_value, this->calculate_permission(permission, granted));
}

View File

@ -1,114 +0,0 @@
#pragma once
#include <Definitions.h>
#include <PermissionManager.h>
#include <vector>
#include <memory>
namespace ts {
class BasicChannel;
}
namespace ts::server {
class DataClient;
class VirtualServer;
namespace groups {
class ChannelGroup;
class ServerGroup;
class GroupManager;
}
/**
* Helper for calculating the client permissions for a certain channel.
* Note: All functions are not thread save!
*/
class ClientPermissionCalculator {
public:
/* When providing the pointer to the channel the channel tree **should** not be locked in any way! */
explicit ClientPermissionCalculator(DataClient* /* client */, const std::shared_ptr<BasicChannel>& /* target channel */);
explicit ClientPermissionCalculator(DataClient* /* client */, ChannelId /* target channel id */);
explicit ClientPermissionCalculator(
const std::shared_ptr<VirtualServer>& /* server */,
ClientDbId /* client database id */,
ClientType /* client type */,
ChannelId /* target channel id */
);
/**
* Calculate the given permissions.
* This method can be called from everywhere without any locking needed.
* @param granted
* @return
*/
[[nodiscard]] permission::v2::PermissionFlaggedValue calculate_permission(
permission::PermissionType,
bool granted = false
);
/**
* Calculate the given permissions.
* This method can be called from everywhere without any locking needed.
* @param channel
* @param calculate_granted
* @return
*/
[[nodiscard]] std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> calculate_permissions(
const std::deque<permission::PermissionType>&,
bool calculate_granted = false
);
/**
* Test if the target has the target permission granted.
* If the client does not have any value assigned for the permission `false` will be returned.
*/
[[nodiscard]] bool permission_granted(
const permission::PermissionType& /* target permission */,
const permission::PermissionValue& /* required value */,
bool /* granted permission */ = false
);
/**
* Test if the target has the target permission granted.
* If the client does not have any value assigned for the permission,
* `true` will be returned if the `required value` contains no value
* otherwise false will be returned.
*
* This method should be used when testing permissions which are allowed by default except if they're
* specified otherwise. An example permission would be if we're testing against the channel needed join power.
*/
[[nodiscard]] bool permission_granted(
const permission::PermissionType& /* target permission */,
const permission::v2::PermissionFlaggedValue& /* required value */,
bool /* granted permission */ = false
);
//const PermissionValue& required, const PermissionFlaggedValue& given, bool requires_given = true
private:
/* given fields */
ServerId virtual_server_id;
ClientDbId client_database_id;
ClientType client_type;
std::shared_ptr<BasicChannel> channel_;
std::shared_ptr<groups::GroupManager> group_manager_{};
std::function<std::shared_ptr<groups::ChannelGroup>()> default_channel_group{[]{ return nullptr; }};
std::function<std::shared_ptr<groups::ServerGroup>()> default_server_group{[]{ return nullptr; }};
/* fields which will be set when calculating permissions */
std::shared_ptr<permission::v2::PermissionManager> client_permissions_{};
std::optional<bool> skip_enabled{};
std::optional<std::shared_ptr<groups::ChannelGroup>> assigned_channel_group_{};
std::optional<std::vector<std::shared_ptr<groups::ServerGroup>>> assigned_server_groups_{};
void initialize_client(DataClient* /* client */);
void initialize_default_groups(const std::shared_ptr<VirtualServer>& /* server */);
[[nodiscard]] const std::vector<std::shared_ptr<groups::ServerGroup>>& assigned_server_groups();
[[nodiscard]] const std::shared_ptr<groups::ChannelGroup>& assigned_channel_group();
[[nodiscard]] const std::shared_ptr<permission::v2::PermissionManager>& client_permissions();
[[nodiscard]] bool has_global_skip_permission();
};
}

View File

@ -1,11 +1,12 @@
#include <algorithm>
#include <utility>
#include <log/LogUtils.h>
#include <misc/std_unique_ptr.h>
#include <Properties.h>
#include "VirtualServerManager.h"
#include "src/server/VoiceServer.h"
#include "InstanceHandler.h"
#include "./groups/GroupManager.h"
#include "InstanceHandler.h"
//TODO: When using the new command builder make sure you're using a std::deque as the underlying bulk type!
@ -15,6 +16,13 @@ using namespace ts::server;
#define PREFIX string("[SNAPSHOT] ")
inline std::string avArguments(const ts::Command& cmd, int index){
stringstream res;
for(const auto &key : cmd[index].keys())
res << key << "=" << cmd[index][key].as<std::string>() << " ";
return res.str();
}
//TeamSpeak: permid=i_channel_needed_permission_modify_power permvalue=75 permskip=0 permnegated=0
//TeaSpeak: perm=i_channel_needed_permission_modify_power value=75 granted=75 flag_skip=0 flag_negated=0
struct SnapshotPermissionEntry {
@ -24,9 +32,104 @@ struct SnapshotPermissionEntry {
bool negated = false;
bool skipped = false;
SnapshotPermissionEntry(const std::shared_ptr<permission::PermissionTypeEntry>& type, permission::PermissionValue value, permission::PermissionValue grant) : type(std::move(type)), value(value), grant(grant) {}
SnapshotPermissionEntry(const std::shared_ptr<permission::PermissionTypeEntry>& type, permission::PermissionValue value, permission::PermissionValue grant, bool negated, bool skipped) : type(type), value(value), grant(grant), negated(negated), skipped(skipped) {}
SnapshotPermissionEntry() = default;
static deque<std::unique_ptr<SnapshotPermissionEntry>> parse(const Command& cmd, int& index, permission::teamspeak::GroupType type, int version, const std::vector<std::string>& ends, bool ignore_first = false) {
deque<std::unique_ptr<SnapshotPermissionEntry>> result{};
while(true) {
bool end_reached = false;
for(const auto& e : ends)
if(cmd[index].has(e)) { end_reached = true; break; }
if(end_reached || cmd.bulkCount() < index) {
if(ignore_first) ignore_first = false;
else break;
}
if(version == 0) {
auto permission_name = cmd[index]["permid"].string();
for(const auto& mapped : permission::teamspeak::map_key(permission_name, type)) {
auto permission = permission::resolvePermissionData(mapped);
if(permission->type == permission::unknown) {
logError(0, "Failed to parse permission {}. Original: {}", mapped, permission_name);
index++;
continue;
}
bool found = false;
for(auto& e : result)
if(e->type->type == permission->type) {
found = true;
if(permission->grantName() == mapped)
e->grant = cmd[index]["permvalue"];
else {
e->value = cmd[index]["permvalue"];
e->negated = cmd[index]["permnegated"];
e->skipped = cmd[index]["permskip"];
}
}
if(!found) {
auto entry = make_unique<SnapshotPermissionEntry>();
entry->type = permission;
if(permission->grantName() == mapped)
entry->grant = cmd[index]["permvalue"];
else {
entry->value = cmd[index]["permvalue"];
entry->negated = cmd[index]["permnegated"];
entry->skipped = cmd[index]["permskip"];
}
result.push_back(std::move(entry));
}
}
} else if(version >= 1) {
auto permission_name = cmd[index]["perm"].string();
auto permission = permission::resolvePermissionData(permission_name);
if(permission->type == permission::unknown) {
logError(0, "Failed to parse permission {}", permission_name);
index++;
continue;
}
auto entry = make_unique<SnapshotPermissionEntry>();
entry->type = permission;
entry->value = cmd[index]["value"];
entry->grant = cmd[index]["grant"];
entry->skipped = cmd[index]["flag_skip"];
entry->negated = cmd[index]["flag_negated"];
result.push_back(std::move(entry));
} else {
logError(0, "Failed to parse snapshot permission entry! Invalid version!");
index++;
continue;
}
index++;
}
deque<std::unique_ptr<SnapshotPermissionEntry>> addings{};
for(const auto& perm : result) {
if(perm->type->type == permission::i_group_auto_update_type) { //Migrate this type
for(const auto& e : permission::update::migrate)
if(e.type == perm->value) {
auto _type = permission::resolvePermissionData(e.permission.name);
if(_type->type == permission::unknown) {
logError(0, "Invalid update permission for update group {}. Permission name: {}", e.type, e.permission.name);
} else {
addings.push_back(make_unique<SnapshotPermissionEntry>(_type, e.permission.value, e.permission.granted, e.permission.negated, e.permission.skipped));
}
}
}
}
for(auto& a : addings)
result.push_back(move(a));
return result;
}
void write(Command& cmd, int& index, permission::teamspeak::GroupType type, int version) {
if(version == 0) {
if(this->value != permNotGranted) {
@ -60,6 +163,542 @@ struct SnapshotPermissionEntry {
}
};
std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(shared_ptr<VirtualServer> old, std::string host,
uint16_t port, const ts::Command &arguments,
std::string &error) {
ServerId serverId = 0;
map<ClientDbId, map<ChannelId, ClientDbId>> channelGroupRelations; //cid is the new cgid
map<GroupId, GroupId> channelGroupMapping;
map<ClientDbId, vector<GroupId>> serverGroupRelations; //gid is the new gid
map<GroupId, GroupId> serverGroupMapping;
map<ClientDbId, ClientDbId> db_mapping_client_id;
deque<pair<std::string, threads::Future<sql::result>>> futures;
bool sid_success = false;
serverId = this->next_available_server_id(sid_success);
if(!sid_success) {
error = "failed to generate new sid";
return nullptr;
}
ServerId log_server_id = old ? old->getServerId() : serverId;
futures.emplace_back(
"server id register",
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", host}, variable{":port", port}).executeLater()
);
sql::model sql_insert_channel(this->handle->getSql(), "INSERT INTO `channels` (`serverId`, `channelId`, `type`, `parentId`) VALUES (:sid, :id, :type, :pid)", variable{":sid", serverId});
sql::model sql_insert_group_assignment(this->handle->getSql(), "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)", variable{":sid", serverId}, variable{":until", 0});
sql::model sql_insert_permission(this->handle->getSql(), "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)",
variable{":serverId", serverId});
sql::model sql_update_permission_grant(this->handle->getSql(), "UPDATE `permissions` SET `grant` = :grant WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `channelId` = :chid AND `permId` = :key", variable{":sid", serverId});
sql::model sql_insert_property(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)", variable{":sid", serverId});
sql::model sql_insert_bot(this->handle->getSql(), "INSERT INTO `musicbots` (`serverId`, `botId`, `uniqueId`, `owner`) VALUES (:server_id, :bot_id, :unique_id, :owner)", variable{":server_id", serverId});
sql::model sql_insert_playlist(this->handle->getSql(), "INSERT INTO `playlists` (`serverId`, `playlist_id`) VALUES (:server_id, :playlist_id)", variable{":server_id", serverId});
sql::model sql_insert_playlist_song(this->handle->getSql(), "INSERT INTO `playlist_songs` (`serverId`, `playlist_id`, `song_id`, `order_id`, `invoker_dbid`, `url`, `url_loader`, `loaded`, `metadata`) VALUES (:server_id, :playlist_id, :song_id, :order_id, :invoker_dbid, :url, :url_loader, :loaded, :metadata)", variable{":server_id", serverId});
int index = 0;
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()){
if(key == "end_virtualserver") continue;
if(key == "begin_virtualserver") continue;
if(snapshot_version == 0) {
if(key == "virtualserver_download_quota" || key == "virtualserver_upload_quota" || key == "virtualserver_max_download_total_bandwidth" || key == "virtualserver_max_upload_total_bandwidth") {
auto value = arguments[index][key].string();
try {
arguments[index][key] = to_string((int64_t) std::stoull(value));
} catch(const std::exception& ex) {
continue;
}
}
}
debugMessage(LOG_GENERAL, PREFIX + " Having server key {} = {}", key, arguments[index][key].as<std::string>());
futures.emplace_back(
"server property import proprty=" + key,
sql_insert_property.command().values(
variable{":type", property::PropertyType::PROP_TYPE_SERVER},
variable{":id", 0},
variable{":key", key},
variable{":value", arguments[index][key].string()}
).executeLater()
);
}
if(arguments[index].has("end_virtualserver")) break;
index++;
}
//`serverId` INT NOT NULL, `playlist_id` INT, `song_id` INT, `order_id` INT, `invoker_dbid` INT, `url` TEXT, `url_loader` TEXT
/*
* sql::command(this->handle->getSql(), "INSERT INTO `playlists` (`serverId`, `playlist_id`) VALUES (:server_id, :playlist_id)",
variable{":server_id", this->ref_server()->getServerId()},
variable{":playlist_id", playlist_id}
)
*/
index++;
for(;index < arguments.bulkCount(); index++) {
if (arguments[index].has("begin_channels")) {
while (true) {
if (arguments[index].has("end_channels")) break;
auto channel_id = arguments[index]["channel_id"].string();
futures.emplace_back(
"channel register cid=" + channel_id,
sql_insert_channel.command().values(variable{":id", channel_id}, variable{":type", -1}, variable{":pid", arguments[index]["channel_pid"].string()}).executeLater()
); //TODO findout type!
for (const auto &key : arguments[index].keys()) {
if(key == "channel_id") continue;
if(key == "channel_pid") continue;
if(key == "channel_security_salt") continue;
if(key == "channel_filepath") continue; //TODO implement channel_filepath?
if(key == "begin_channels") continue;
debugMessage(log_server_id, PREFIX + "Having channel key " + key + "=" + arguments[index][key].as<std::string>() + " for channel " + arguments[index]["channel_id"].as<std::string>());
futures.push_back({
"channel prop register cid=" + channel_id + " property=" + key,
sql_insert_property.command().values(variable{":sid", serverId},
variable{":type", property::PropertyType::PROP_TYPE_CHANNEL},
variable{":id", channel_id},
variable{":key", key},
variable{":value", arguments[index][key].as<std::string>()}
).executeLater()
});
}
index++;
}
} else if (arguments[index].has("begin_clients")) {
while (true) {
if (arguments[index].has("end_clients")) break;
debugMessage(log_server_id, PREFIX + "Create client {}/{} -> end: {}",arguments[index]["client_id"].as<std::string>(), arguments[index]["client_nickname"].as<std::string>(), to_string(arguments[index].has("end_clients")));
ClientDbId oldId = 0;
auto res = sql::command(this->handle->getSql(), "SELECT `cldbid` FROM `clients` WHERE `clientUid` = :uid AND `serverId` = 0", variable{":uid", arguments[index]["client_unique_id"].as<std::string>()}).query([](ClientDbId* ptr, int, char** v, char**){ *ptr = stoll(v[0]); return 0;}, &oldId);
LOG_SQL_CMD(res);
if(oldId == 0){
res = sql::command(this->handle->getSql(), "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;
}, &oldId);
LOG_SQL_CMD(res);
oldId += 1;
//Create a new client
sql::command(this->handle->getSql(), "INSERT INTO `clients` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) VALUES (:sid, :cldbid, :cluid, :firstConnect, :lastConnect, :connections, :name)",
variable{":sid", 0}, variable{":cldbid", oldId}, variable{":cluid", arguments[index]["client_unique_id"].as<std::string>()},
variable{":firstConnect", arguments[index]["client_created"].as<std::string>()},
variable{":lastConnect", "0"}, variable{":connections", "0"}, variable{":name", arguments[index]["client_nickname"].as<std::string>()}).execute(); //TODO log error
}
db_mapping_client_id[arguments[index]["client_id"].as<ClientDbId>()] = oldId;
sql::command(this->handle->getSql(), "INSERT INTO `clients` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) VALUES (:sid, :cldbid, :cluid, :firstConnect, :lastConnect, :connections, :name)",
variable{":sid", serverId}, variable{":cldbid", oldId}, variable{":cluid", arguments[index]["client_unique_id"].as<std::string>()},
variable{":firstConnect", arguments[index]["client_created"].as<std::string>()},
variable{":lastConnect", "0"}, variable{":connections", "0"}, variable{":name", arguments[index]["client_nickname"].as<std::string>()}).execute(); //TODO log error
//TODO here
index++;
}
} else if (arguments[index].has("begin_permissions")) {
while (true) {
index++;
if (arguments[index].has("end_permissions")) break;
else if (arguments[index].has("server_groups")) {
GroupId currentGroupId = 1;
while (true) {
if (arguments[index].has("end_groups")) break;
if(!arguments[index].has("id")){ //new group
logError(0, "Invalid server group permission entry! Missing group id!");
index++;
continue;
}
currentGroupId = static_cast<GroupId>(GroupManager::generateGroupId(this->handle->getSql()));
debugMessage(log_server_id, PREFIX + "Insert group: " + to_string(currentGroupId) + " - " + arguments[index]["name"].as<string>());
serverGroupMapping[arguments[index]["id"].as<GroupId>()] = currentGroupId;
LOG_SQL_CMD(sql::command(this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", serverId},
variable{":gid", currentGroupId},
variable{":target", GROUPTARGET_SERVER},
variable{":type", GroupType::GROUP_TYPE_NORMAL},
variable{":name", arguments[index]["name"].as<string>()})
.execute());
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::SERVER, snapshot_version, {"end_group"});
for(const auto& entry : permissions) {
futures.push_back({
"server group permission sgid=" + to_string(currentGroupId) + " permId=" + entry->type->name,
sql_insert_permission.command().values(
variable{":id", currentGroupId},
variable{":type", permission::SQL_PERM_GROUP},
variable{":chId", 0},
variable{":permId", entry->type->name},
variable{":value", entry->value},
variable{":grant", entry->grant},
variable{":flag_skip", entry->skipped},
variable{":flag_negate", entry->negated}
).executeLater()
});
}
index++;
}
//Relations for sgroups after list
index++;
while (true) {
if (arguments[index].has("end_relations")) break;
serverGroupRelations[db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]].push_back(serverGroupMapping[arguments[index]["gid"].as<GroupId>()]);
futures.push_back({
"server group relation",
sql_insert_group_assignment.command().values(variable{":cldbid", db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]}, variable{":gid", serverGroupMapping[arguments[index]["gid"].as<GroupId>()]}, variable{":chid", "0"}).executeLater()
});
index++;
}
} else if(arguments[index].has("channel_groups")){
GroupId currentGroupId = 1;
while (true) {
if (arguments[index].has("end_groups")) break;
if(!arguments[index].has("id")){ //new group
logError(0, "Invalid server group permission entry! Missing group id!");
index++;
continue;
}
currentGroupId = static_cast<GroupId>(GroupManager::generateGroupId(this->handle->getSql()));
debugMessage(log_server_id, PREFIX + "Insert channel group: " + to_string(currentGroupId) + " - " + arguments[index]["name"].as<string>());
channelGroupMapping[arguments[index]["id"].as<GroupId>()] = currentGroupId;
LOG_SQL_CMD(sql::command(this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", serverId},
variable{":gid", currentGroupId},
variable{":target", GROUPTARGET_CHANNEL},
variable{":type", GroupType::GROUP_TYPE_NORMAL},
variable{":name", arguments[index]["name"].as<string>()})
.execute());
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::SERVER, snapshot_version, {"end_group"});
for(const auto& entry : permissions) {
futures.push_back({
"channel group property insert cgid=" + to_string(currentGroupId) + " property=" + entry->type->name,
sql_insert_permission.command().values(
variable{":id", currentGroupId},
variable{":type", permission::SQL_PERM_GROUP},
variable{":chId", 0},
variable{":permId", entry->type->name},
variable{":value", entry->value},
variable{":grant", entry->grant},
variable{":flag_skip", entry->skipped},
variable{":flag_negate", entry->negated}
).executeLater()
});
}
index++;
}
//Relations for sgroups after list
ChannelId chId = 0;
index++;
while (true) {
if (arguments[index].has("end_relations")) break;
if(arguments[index].has("iid")) chId = arguments[index]["iid"];
channelGroupRelations[db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]][chId] = channelGroupMapping[arguments[index]["gid"].as<GroupId>()];
futures.push_back({
"channel group relation",
sql_insert_group_assignment.command().values(variable{":cldbid", db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]}, variable{":gid", channelGroupMapping[arguments[index]["gid"].as<GroupId>()]}, variable{":chid", chId}).executeLater()
});
index++;
}
} else if (arguments[index].has("client_flat")) {
ClientDbId currentId = 0;
while (true) {
if (arguments[index].has("end_flat")) break;
if(arguments[index].has("id1"))
currentId = arguments[index]["id1"];
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CLIENT, snapshot_version, {"id1", "end_flat"}, true);
for(const auto& entry : permissions) {
futures.push_back({
"client permission insert clientId=" + to_string(currentId) + " permId=" + entry->type->name,
sql_insert_permission.command().values(
variable{":id", currentId},
variable{":type", permission::SQL_PERM_USER},
variable{":chId", 0},
variable{":permId", entry->type->name},
variable{":value", entry->value},
variable{":grant", entry->grant},
variable{":flag_skip", entry->skipped},
variable{":flag_negate", entry->negated}
).executeLater()
});
}
}
} else if (arguments[index].has("channel_flat")) {
ChannelId currentChannelId = 0;
while (true) {
if (arguments[index].has("end_flat")) break;
if (arguments[index].has("id1"))
currentChannelId = arguments[index]["id1"]; //We also have id2 unknown for what this is. Maybe parent?
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CHANNEL, snapshot_version, {"id1", "end_flat"}, true);
for(const auto& entry : permissions) {
futures.push_back({
"channel permission insert channelId=" + to_string(currentChannelId) + " permId=" + entry->type->name,
sql_insert_permission.command().values(
variable{":id", 0},
variable{":type", permission::SQL_PERM_CHANNEL},
variable{":chId", currentChannelId},
variable{":permId", entry->type->name},
variable{":value", entry->value},
variable{":grant", entry->grant},
variable{":flag_skip", entry->skipped},
variable{":flag_negate", entry->negated}
).executeLater()
});
}
}
} else if (arguments[index].has("channel_client_flat")) {
ClientDbId currentClientId = 0;
ChannelId currentChannelId = 0;
while (true) {
if (arguments[index].has("end_flat")) break;
//id1 = channel id
//id2 = client id
if(arguments[index].has("id1"))
currentChannelId = arguments[index]["id1"];
if(arguments[index].has("id2"))
currentClientId = db_mapping_client_id[arguments[index]["id2"]];
if(currentChannelId > 0 && currentClientId > 0){
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CLIENT, snapshot_version, {"id1", "id2", "end_flat"}, true);
for(const auto& entry : permissions) {
futures.push_back({
"client channel permission insert",
sql_insert_permission.command().values(
variable{":id", currentClientId},
variable{":type", permission::SQL_PERM_CHANNEL},
variable{":chId", currentChannelId},
variable{":permId", entry->type->name},
variable{":value", entry->value},
variable{":grant", entry->grant},
variable{":flag_skip", entry->skipped},
variable{":flag_negate", entry->negated}
).executeLater()
});
}
} else index++;
}
} else {
error = "Invalid tag in permissions. Available: " + avArguments(arguments, index);
return nullptr;
}
}
} else if(arguments[index].has("begin_music")) {
PlaylistId playlist_index = 0;
PlaylistId playlist_error = 0xFFFFFFFF;
map<PlaylistId, PlaylistId> db_mapping_playlist;
/*
//gather static info (not needed because its a new server)
{
auto sql_result = sql::command(this->handle->getSql(), "SELECT `playlist_id` FROM `playlists` WHERE `serverId` = :server_id ORDER BY `playlist_id` DESC", variable{":server_id", serverId}).query([&](int length, string* values, string* names){
if(length != 1) return;
try {
playlist_index = (PlaylistId) stoll(values[0]);
} catch(const std::exception& ex) {
error = "Failed to parse playlist id from database. ID: " + values[0];
return;
}
});
LOG_SQL_CMD(sql_result);
if(playlist_index == 0) {
error = "failed to gather info";
return nullptr;
}
}
*/
while(!arguments[index].has("end_music")) {
if(arguments[index].has("begin_bots")) {
while(!arguments[index].has("end_bots")) {
ClientDbId bot_id = arguments[index]["bot_id"];
if(db_mapping_client_id.find(bot_id) == db_mapping_client_id.end()) {
logError(log_server_id, PREFIX + "Failed to insert music bot within id {}. (Mapping not found)", bot_id);
index++;
continue;
}
ClientDbId new_bot_id = db_mapping_client_id[bot_id];
//begin_bots
auto sql_result = sql_insert_bot.command().values(
variable{":bot_id", new_bot_id},
variable{":unique_id", arguments[index]["bot_unique_id"].string()},
variable{":owner", arguments[index]["bot_owner_id"].string()}
).execute();
if(!sql_result) {
logError(log_server_id, PREFIX + "Failed to insert music bot with id {} (old: {}). ({})", new_bot_id, bot_id, sql_result.fmtStr());
index++;
continue;
}
for(const auto& key : arguments[index].keys()) {
if(key == "begin_music") continue;
if(key == "begin_bots") continue;
if(key == "bot_unique_id") continue;
if(key == "bot_owner_id") continue;
if(key == "bot_id") continue;
const auto& property = property::find<property::ClientProperties>(key);
if(property.is_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;
}
futures.push_back({
"music bot insert",
sql_insert_property.command().values(
variable{":type", property::PropertyType::PROP_TYPE_CLIENT},
variable{":id", new_bot_id},
variable{":key", key},
variable{":value", arguments[index][key].string()}
).executeLater()
});
}
index++;
}
} else if(arguments[index].has("begin_playlist")) {
while(!arguments[index].has("end_playlist")) {
PlaylistId playlist_id = arguments[index]["playlist_id"];
db_mapping_playlist[++playlist_index] = playlist_id;
auto sql_result = sql_insert_playlist.command().values(variable{":playlist_id", playlist_index}).execute();
if(!sql_result) {
db_mapping_playlist[playlist_index] = playlist_error;
logError(log_server_id, PREFIX + "Failed to insert playlist with id {} (old: {}). ({})", playlist_index, playlist_id, sql_result.fmtStr());
index++;
continue;
}
for(const auto& key : arguments[index].keys()) {
if(key == "begin_music") continue;
if(key == "begin_playlist") continue;
if(key == "playlist_id") continue;
const auto& property = property::find<property::ClientProperties>(key);
if(property.is_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;
}
futures.push_back({
"playlist insert",
sql_insert_property.command().values(
variable{":type", property::PropertyType::PROP_TYPE_PLAYLIST},
variable{":id", playlist_index},
variable{":key", key},
variable{":value", arguments[index][key].string()}
).executeLater()
});
}
index++;
}
} else if(arguments[index].has("begin_playlist_songs")) {
PlaylistId current_playlist = 0;
PlaylistId current_playlist_mapped = 0;
while(!arguments[index].has("end_playlist_songs")) {
if(arguments[index].has("song_playlist_id")) {
current_playlist = arguments[index]["song_playlist_id"];
current_playlist_mapped = (PlaylistId) db_mapping_client_id[current_playlist];
if(current_playlist_mapped == 0) {
logError(log_server_id, PREFIX + "Failed to insert playlist song entry {}. Playlist id {} hasn't been mapped", arguments[index]["song_id"].value(), current_playlist);
current_playlist_mapped = playlist_error;
}
}
if(current_playlist_mapped == playlist_error) { /* current list is an error */
index++;
continue;
}
auto sql_future = sql_insert_playlist_song.command().values(
variable{":playlist_id", current_playlist_mapped},
variable{":song_id", arguments[index]["song_id"].value()},
variable{":order_id", arguments[index]["song_order"].value()},
variable{":invoker_dbid", arguments[index]["song_invoker"].value()},
variable{":url", arguments[index]["song_url"].value()},
variable{":url_loader", arguments[index]["song_url_loader"].value()},
variable{":loaded", arguments[index]["song_loaded"].value()},
variable{":metadata", arguments[index]["song_metadata"].value()}
).executeLater();
futures.push_back({
"song insert",
sql_future
});
index++;
}
}
index++;
}
} else {
error = "Invalid root tag. Available: " + avArguments(arguments, index);
return nullptr;
}
}
debugMessage(serverId, "Wait for success!");
for(const auto& future : futures) {
auto result = future.second.waitAndGet({-1, "timeout"}, chrono::system_clock::now() + chrono::seconds(5));
if(!result) {
logError(serverId, "Failed to execute snapshotdeploy command {}: {}", future.first, result.fmtStr());
}
}
if(old)
this->deleteServer(old);
//Now we load the server
auto server = make_shared<VirtualServer>(serverId, this->handle->getSql());
server->self = server;
if(!server->initialize(false)) {
//FIXME Error handling!
}
server->properties()[property::VIRTUALSERVER_HOST] = host;
server->properties()[property::VIRTUALSERVER_PORT] = port;
server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
{
threads::MutexLock l(this->instanceLock);
this->instances.push_back(server);
}
this->adjust_executor_threads();
server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] = serverGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP].as<GroupId>()];
server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = channelGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as<GroupId>()];
server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = channelGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].as<GroupId>()];
server->ensureValidDefaultGroups();
return server;
}
struct CommandTuple {
Command& cmd;
int& index;
int version;
};
struct PermissionCommandTuple {
Command& cmd;
int& index;
@ -132,8 +771,8 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
int index = 0;
if(version == -1) version = 3; //Auto versioned
if(version < 0 || version > 3) {
if(version == -1) version = 2; //Auto versioned
if(version < 0 || version > 2) {
error = "Invalid snapshot version!";
return false;
}
@ -141,14 +780,14 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
//Server
{
cmd[index]["begin_virtualserver"] = "";
for(const auto& serverProperty : server->properties()->list_properties(property::FLAG_SNAPSHOT)) {
for(const auto& serverProperty : server->properties().list_properties(property::FLAG_SNAPSHOT)) {
if(version == 0) {
switch (serverProperty.type().property_index) {
case property::VIRTUALSERVER_DOWNLOAD_QUOTA:
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_or<int64_t>(0);
cmd[index][std::string{serverProperty.type().name}] = (uint64_t) serverProperty.as_save<int64_t>();
default:
break;
}
@ -162,14 +801,13 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
{
cmd[index]["begin_channels"] = "";
for(const auto& channel : server->getChannelTree()->channels()) {
for(const auto& channelProperty : channel->properties()->list_properties(property::FLAG_SNAPSHOT)) {
if(channelProperty.type() == property::CHANNEL_ID) {
cmd[index]["channel_id"] = channelProperty.value();
} else if(channelProperty.type() == property::CHANNEL_PID) {
cmd[index]["channel_pid"] = channelProperty.value();
} else {
cmd[index][std::string{channelProperty.type().name}] = channelProperty.value();
}
for(const auto& channelProperty : channel->properties().list_properties(property::FLAG_SNAPSHOT)) {
if(channelProperty.type() == property::CHANNEL_ID)
cmd[index]["channel_id"] = channelProperty.as<string>();
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>();
}
index++;
}
@ -179,26 +817,44 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
//Clients
{
cmd[index]["begin_clients"] = "";
CommandTuple parm{cmd, index, version};
auto res = sql::command(server->getSql(), "SELECT `cldbid`, `clientUid`, `firstConnect`, `lastName` FROM `clients` WHERE `serverId` = :sid",
variable{":sid", server->getServerId()}
).query([&](CommandTuple* commandIndex, int length, char** value, char** name) {
ClientDbId cldbid = 0;
int64_t clientCreated = 0;
string clientUid;
string lastName;
string description; //TODO description
struct CallbackArgument {
Command& command;
int& index;
int version;
};
for(int idx = 0; idx < length; idx++) {
try {
if(strcmp(name[idx], "cldbid") == 0)
cldbid = value[idx] && strlen(value[idx]) > 0 ? stoul(value[idx]) : 0;
else if(strcmp(name[idx], "clientUid") == 0)
clientUid = value[idx];
else if(strcmp(name[idx], "firstConnect") == 0)
clientCreated = value[idx] && strlen(value[idx]) > 0 ? stoll(value[idx]) : 0L;
else if(strcmp(name[idx], "lastName") == 0)
lastName = value[idx];
} catch (std::exception& ex) {
logError(0, "Failed to write snapshot client (Skipping it)! Message: {} @ {} => {}", ex.what(), name[idx], value[idx]);
return 0;
}
}
CallbackArgument callback_argument{cmd, index, version};
this->handle->databaseHelper()->listDatabaseClients(server->getServerId(), std::nullopt, std::nullopt, [](void* ptr_argument, const DatabaseClient& client) {
auto argument = (CallbackArgument*) ptr_argument;
argument->command[argument->index]["client_id"] = client.client_database_id;
argument->command[argument->index]["client_unique_id"] = client.client_unique_id;
argument->command[argument->index]["client_nickname"] = client.client_nickname;
argument->command[argument->index]["client_created"] = client.client_created;
argument->command[argument->index]["client_description"] = client.client_description;
if(argument->version == 0)
argument->command[argument->index]["client_unread_messages"] = 0;
argument->index++;
}, &callback_argument);
commandIndex->cmd[commandIndex->index]["client_id"] = cldbid;
commandIndex->cmd[commandIndex->index]["client_unique_id"] = clientUid;
commandIndex->cmd[commandIndex->index]["client_nickname"] = lastName;
commandIndex->cmd[commandIndex->index]["client_created"] = clientCreated;
commandIndex->cmd[commandIndex->index]["client_description"] = description;
if(commandIndex->version == 0)
commandIndex->cmd[commandIndex->index]["client_unread_messages"] = 0;
commandIndex->index++;
return 0;
}, &parm);
LOG_SQL_CMD(res);
cmd[index++]["end_clients"] = "";
}
@ -338,13 +994,10 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
//List groups
{
cmd[index]["server_groups"] = "";
auto server_groups = server->group_manager()->server_groups();
for(const auto& group : server_groups->available_groups(groups::GroupCalculateMode::LOCAL)) {
cmd[index]["id"] = group->group_id();
cmd[index]["name"] = group->display_name();
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) {
break;
}
for(const auto& group : server->getGroupManager()->availableServerGroups(false)) {
cmd[index]["id"] = group->groupId();
cmd[index]["name"] = group->name();
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) break;
cmd[index++]["end_group"] = "";
}
cmd[index++]["end_groups"] = "";
@ -363,13 +1016,10 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
//List groups
{
cmd[index]["channel_groups"] = "";
auto server_groups = server->group_manager()->channel_groups();
for(const auto& group : server_groups->available_groups(groups::GroupCalculateMode::LOCAL)) {
cmd[index]["id"] = group->group_id();
cmd[index]["name"] = group->display_name();
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) {
break;
}
for(const auto& group : server->getGroupManager()->availableChannelGroups(false)) {
cmd[index]["id"] = group->groupId();
cmd[index]["name"] = group->name();
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) break;
cmd[index++]["end_group"] = "";
}
cmd[index++]["end_groups"] = "";

View File

@ -1,7 +1,6 @@
#include <log/LogUtils.h>
#include <StringVariable.h>
#include <ThreadPool/ThreadHelper.h>
#include <src/terminal/PipedTerminal.h>
#include "ShutdownHelper.h"
#include "InstanceHandler.h"
@ -22,7 +21,6 @@ void ts::server::shutdownInstance(const std::string& message) {
logCriticalFmt(true, 0, "Could not shutdown server within 30 seconds! (Hangup!)");
logCriticalFmt(true, 0, "Killing server!");
terminal::finalize_pipe();
auto force_kill = std::thread([]{
threads::self::sleep_for(chrono::seconds(5));
logCriticalFmt(true, 0, "Failed to exit normally!");
@ -45,10 +43,6 @@ void ts::server::shutdownInstance(const std::string& message) {
mainThreadActive = false;
}
bool ts::server::isShuttingDown() {
return shuttingDown;
}
std::shared_ptr<server::ShutdownData> currentShutdown = nullptr;
std::shared_ptr<server::ShutdownData> server::scheduledShutdown() { return currentShutdown; }

View File

@ -5,7 +5,7 @@
namespace ts {
namespace server {
extern void shutdownInstance(const std::string& reason = ts::config::messages::applicationStopped);
extern bool isShuttingDown();
struct ShutdownData {
std::string reason;

View File

@ -1,4 +1,5 @@
#include <breakpad/client/linux/handler/exception_handler.h>
#include "VirtualServer.h"
#include "SignalHandler.h"
#include "VirtualServerManager.h"
@ -7,26 +8,29 @@
#include <csignal>
#include <log/LogUtils.h>
#include <experimental/filesystem>
#include <src/terminal/PipedTerminal.h>
#include <iterator>
#define BREAKPAD_EXCEPTION_HANDLER 1
#ifdef BREAKPAD_EXCEPTION_HANDLER
#include <breakpad/client/linux/handler/exception_handler.h>
#endif
using namespace std;
namespace fs = std::experimental::filesystem;
#ifdef BREAKPAD_EXCEPTION_HANDLER
google_breakpad::ExceptionHandler* globalExceptionHandler = nullptr;
#endif
#define SIG(s, c) \
if(signal(s, c) != nullptr) logError(LOG_GENERAL, "Cant setup signal handler for " #s);
void print_current_exception() {
extern bool mainThreadDone;
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) {
logCritical(LOG_GENERAL, "The server crashed!");
try {
if(!fs::exists(fs::u8path(ts::config::crash_path)))
fs::create_directories(fs::u8path(ts::config::crash_path));
auto path = fs::u8path(descriptor.path());
path = fs::u8path(ts::config::crash_path + "crash_dump_" + path.filename().string());
fs::rename(fs::u8path(descriptor.path()), path);
logCritical(LOG_GENERAL, "Wrote crash dump to " + path.relative_path().string());
} catch (...) {
logCritical(LOG_GENERAL, "Failed to write/move crash dump!");
}
if(std::current_exception()) {
logCritical(LOG_GENERAL, "Exception reached stack root and cause the server to crash!");
logCritical(LOG_GENERAL, " Type: {}", std::current_exception().__cxa_exception_type()->name());
@ -36,61 +40,23 @@ void print_current_exception() {
logCritical(LOG_GENERAL, " Message: {}", ex.what());
} catch(...) {}
}
}
extern bool mainThreadDone;
#ifdef BREAKPAD_EXCEPTION_HANDLER
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) {
if(ts::server::isShuttingDown()) {
/* We don't care about this crash dump. Remove it. */
std::error_code error_code{};
fs::remove(fs::u8path(descriptor.path()), error_code);
return true;
}
logCritical(LOG_GENERAL, "The server crashed!");
try {
if(!fs::exists(fs::u8path(ts::config::crash_path))) {
fs::create_directories(fs::u8path(ts::config::crash_path));
}
auto path = fs::u8path(descriptor.path());
path = fs::u8path(ts::config::crash_path + "crash_dump_" + path.filename().string());
fs::rename(fs::u8path(descriptor.path()), path);
logCritical(LOG_GENERAL, "Wrote crash dump to " + path.relative_path().string());
} catch (...) {
logCritical(LOG_GENERAL, "Failed to write/move crash dump!");
}
print_current_exception();
logCritical(LOG_GENERAL, "Please report this crash to the TeaSpeak maintainer WolverinDEV");
logCritical(LOG_GENERAL, "Official issue and bug tracker url: https://github.com/TeaSpeak/TeaSpeak/issues");
logCritical(LOG_GENERAL, "Any reports of crashes are useless if you not provide the above generated crashlog!");
logCritical(LOG_GENERAL, "Stopping server");
terminal::finalize_pipe();
ts::server::shutdownInstance(ts::config::messages::applicationCrashed);
while(!mainThreadDone) {
threads::self::sleep_for(chrono::seconds(1));
}
while(!mainThreadDone) threads::self::sleep_for(chrono::seconds(1));
return succeeded;
}
#endif
std::atomic spawn_failed_count = 0;
bool ts::syssignal::setup() {
logMessage(LOG_GENERAL, "Setting up exception handler");
#ifdef BREAKPAD_EXCEPTION_HANDLER
globalExceptionHandler = new google_breakpad::ExceptionHandler(google_breakpad::MinidumpDescriptor("."), nullptr, dumpCallback, nullptr, true, -1);
#endif
SIG(SIGTERM, &ts::syssignal::handleStopSignal);
if(isatty(fileno(stdin))) {
//We cant listen for this signal if stdin ist a atty
if(isatty(fileno(stdin))) //We cant listen for this signal if stdin ist a atty
SIG(SIGINT, &ts::syssignal::handleStopSignal);
}
//SIG(SIGABRT, &ts::syssignal::handleAbortSignal);
std::set_terminate(ts::syssignal::handleTerminate);
return true;
}
@ -133,13 +99,3 @@ void ts::syssignal::handleStopSignal(int signal) {
}
ts::server::shutdownInstance();
}
void ts::syssignal::handleAbortSignal(int) {
logCritical(0, "The server crashed (Abort signal received)!");
print_current_exception();
}
void ts::syssignal::handleTerminate() {
logCritical(0, "The server crashed (Received a terminate signal)!");
print_current_exception();
}

View File

@ -8,7 +8,5 @@ namespace ts {
extern bool setup();
extern bool setup_threads();
extern void handleStopSignal(int);
extern void handleAbortSignal(int);
extern void handleTerminate();
}
}

View File

@ -1,15 +1,12 @@
#include <cstring>
#include <protocol/buffers.h>
#include "./PermissionCalculator.h"
#include "client/voice/VoiceClient.h"
#include "client/InternalClient.h"
#include "VirtualServer.h"
#include <misc/timer.h>
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <src/manager/ActionLogger.h>
#include "InstanceHandler.h"
#include "./groups/GroupManager.h"
using namespace std;
using namespace ts::server;
@ -22,234 +19,192 @@ bool VirtualServer::registerClient(shared_ptr<ConnectedClient> client) {
sassert(client);
{
std::lock_guard clients_lock{this->clients_mutex};
lock_guard lock(this->clients.lock);
if(client->getClientId() > 0) {
logCritical(this->getServerId(), "Client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid());
return false;
}
ClientId client_id{0};
while(this->clients.count(client_id)) {
client_id++;
}
ClientId client_id = 0;
ClientId max_client_id = this->clients.clients.size();
this->clients.emplace(client_id, client);
while(client_id < max_client_id && this->clients.clients[client_id])
client_id++;
if(client_id == max_client_id)
this->clients.clients.push_back(client);
else
this->clients.clients[client_id] = client;
this->clients.count++;
client->setClientId(client_id);
}
{
std::lock_guard lock{this->client_nickname_lock};
lock_guard lock(this->client_nickname_lock);
auto login_name = client->getDisplayName();
if(client->getExternalType() == ClientType::CLIENT_TEAMSPEAK) {
client->properties()[property::CLIENT_LOGIN_NAME] = login_name;
}
while(login_name.length() < 3) {
while(login_name.length() < 3)
login_name += ".";
}
std::shared_ptr<ConnectedClient> found_client{nullptr};
auto registered_clients = this->getClients();
if(client->getExternalType() == ClientType::CLIENT_TEAMSPEAK)
client->properties()[property::CLIENT_LOGIN_NAME] = login_name;
auto client_name{login_name};
size_t counter{0};
std::shared_ptr<ConnectedClient> found_client = nullptr;
auto client_name = login_name;
size_t counter = 0;
{
lock_guard clients_lock(this->clients.lock);
while(true) {
for(auto& _client : registered_clients) {
if(_client->getDisplayName() == client_name && _client != client) {
for(auto& _client : this->clients.clients) {
if(!_client) continue;
if(_client->getDisplayName() == client_name && _client != client)
goto increase_name;
}
}
goto nickname_valid;
increase_name:
client_name = login_name + std::to_string(++counter);
client_name = login_name + to_string(++counter);
}
}
nickname_valid:
client->setDisplayName(client_name);
}
switch(client->getType()) {
case ClientType::CLIENT_TEAMSPEAK:
case ClientType::CLIENT_TEASPEAK:
case ClientType::CLIENT_WEB:
this->properties()[property::VIRTUALSERVER_CLIENT_CONNECTIONS].increment_by<uint64_t>(1); //increase manager connections
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
break;
case ClientType::CLIENT_QUERY:
this->properties()[property::VIRTUALSERVER_LAST_QUERY_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
this->properties()[property::VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS].increment_by<uint64_t>(1); //increase manager connections
break;
case ClientType::CLIENT_MUSIC:
case ClientType::CLIENT_INTERNAL:
break;
case ClientType::MAX:
default:
assert(false);
break;
if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_WEB) {
this->properties()[property::VIRTUALSERVER_CLIENT_CONNECTIONS] ++; //increase manager connections
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}
else if(client->getType() == ClientType::CLIENT_QUERY) {
this->properties()[property::VIRTUALSERVER_LAST_QUERY_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
this->properties()[property::VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS] ++; //increase manager connections
}
return true;
}
bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> client, std::string reason, std::unique_lock<std::shared_mutex>& chan_tree_lock) {
/* FIXME: Reenable this for the web client as soon we've fixed the web client disconnect method */
if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_TEASPEAK/* || client->getType() == ClientType::CLIENT_WEB */) {
sassert(client->state == ConnectionState::DISCONNECTED);
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);
}
auto client_id = cl->getClientId();
if(client_id == 0) return false; /* not registered */
{
lock_guard lock(this->clients.lock);
if(client_id >= this->clients.clients.size()) {
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister client.", cl->getDisplayName(), client_id, cl->getUid());
} else {
auto& client_container = this->clients.clients[client_id];
if(client_container != cl) {
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister client.", cl->getDisplayName(), client_id, cl->getUid());
} else {
client_container.reset();
this->clients.count--;
}
}
}
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK || cl->getType() == ClientType::CLIENT_WEB)
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
else if(cl->getType() == ClientType::CLIENT_QUERY)
this->properties()[property::VIRTUALSERVER_LAST_QUERY_DISCONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
{
if(!chan_tree_lock.owns_lock()) {
if(!chan_tree_lock.owns_lock())
chan_tree_lock.lock();
}
if(client->currentChannel) {
//We dont have to make him invisible if he hasnt even a channel
this->client_move(client, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock);
}
chan_tree_lock.unlock();
if(cl->currentChannel) //We dont have to make him invisible if he hasnt even a channel
this->client_move(cl, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock);
}
{
std::lock_guard clients_lock{this->clients_mutex};
auto client_id = client->getClientId();
if(client_id == 0) {
return false; /* not registered */
}
if(!this->clients.erase(client_id)) {
client->setClientId(0);
logError(this->getServerId(), "Tried to unregister a not registered client {}/{} ({})", client->getDisplayName(), client->getUid(), client_id);
return false;
}
client->setClientId(0);
}
auto current_time_seconds = std::chrono::duration_cast<seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
switch(client->getType()) {
case ClientType::CLIENT_TEAMSPEAK:
case ClientType::CLIENT_TEASPEAK:
case ClientType::CLIENT_WEB:
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT] = current_time_seconds;
break;
case ClientType::CLIENT_QUERY:
this->properties()[property::VIRTUALSERVER_LAST_QUERY_DISCONNECT] = current_time_seconds;
break;
case ClientType::CLIENT_MUSIC:
case ClientType::CLIENT_INTERNAL:
case ClientType::MAX:
default:
break;
}
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), client->getClientDatabaseId(), client->clientPermissions);
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
cl->setClientId(0);
return true;
}
void VirtualServer::registerInternalClient(std::shared_ptr<ConnectedClient> client) {
client->state = ConnectionState::CONNECTED;
this->registerClient(client);
{
lock_guard lock(this->clients.lock);
if(client->getClientId() > 0) {
logCritical(this->getServerId(), "Internal client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid());
return;
}
ClientId client_id = 0;
ClientId max_client_id = this->clients.clients.size();
while(client_id < max_client_id && this->clients.clients[client_id])
client_id++;
if(client_id == max_client_id)
this->clients.clients.push_back(client);
else
this->clients.clients[client_id] = client;
this->clients.clients[client_id] = client;
this->clients.count++;
client->setClientId(client_id);
}
}
void VirtualServer::unregisterInternalClient(std::shared_ptr<ConnectedClient> client) {
client->state = ConnectionState::DISCONNECTED;
std::unique_lock tree_lock{this->channel_tree_mutex};
this->unregisterClient(client, "internal disconnect", tree_lock);
{
auto client_id = client->getClientId();
lock_guard lock(this->clients.lock);
if(client_id >= this->clients.clients.size()) {
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister internal client.", client->getDisplayName(), client_id, client->getUid());
} else {
auto& client_container = this->clients.clients[client_id];
if(client_container != client) {
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister internal client.", client->getDisplayName(), client_id, client->getUid());
} else {
this->clients.count--;
client_container.reset();
}
}
}
}
bool VirtualServer::assignDefaultChannel(const shared_ptr<ConnectedClient>& client, bool join) {
std::shared_ptr<BasicChannel> channel{};
std::unique_lock server_channel_lock{this->channel_tree_mutex};
auto requested_channel_path = client->properties()[property::CLIENT_DEFAULT_CHANNEL].value();
if(!requested_channel_path.empty()) {
if (requested_channel_path[0] == '/' && requested_channel_path.find_first_not_of("0123456789", 1) == std::string::npos) {
ChannelId channel_id{0};
try {
channel_id = std::stoull(requested_channel_path.substr(1));
} catch (std::exception&) {
logTrace(this->getServerId(), "{} Failed to parse provided channel path as channel id.");
}
if(channel_id > 0) {
channel = this->channelTree->findChannel(channel_id);
}
} else {
channel = this->channelTree->findChannelByPath(requested_channel_path);
}
}
if(channel) {
/* Client proposes a target channel */
auto& channel_whitelist = client->join_whitelisted_channel;
auto whitelist_entry = std::find_if(channel_whitelist.begin(), channel_whitelist.end(), [&](const auto& entry) { return entry.first == channel->channelId(); });
auto client_channel_password = client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD].value();
if(whitelist_entry != channel_whitelist.end()) {
debugMessage(this->getServerId(), "{} Allowing client to join channel {} because the token he used explicitly allowed it.", client->getLoggingPrefix(), channel->channelId());
if(whitelist_entry->second != "ignore") {
if (!channel->verify_password(std::make_optional(client_channel_password), true)) {
if (!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
channel = nullptr;
goto skip_permissions;
}
}
}
goto skip_permissions;
}
if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) {
debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't enough join power.", client->getLoggingPrefix(), channel->channelId());
channel = nullptr;
goto skip_permissions;
}
if (!channel->verify_password(std::make_optional(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD].value()), true)) {
if(!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't given the right channel password.", client->getLoggingPrefix(), channel->channelId());
shared_lock server_channel_lock(this->channel_tree_lock);
std::shared_ptr<BasicChannel> channel = nullptr;
if(client->properties()->hasProperty(property::CLIENT_DEFAULT_CHANNEL) && !client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<string>().empty()) {
auto str = client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>();
if(str[0] == '/' && str.find_first_not_of("0123456789", 1) == string::npos)
channel = this->channelTree->findChannel(static_cast<ChannelId>(stoll(str.substr(1))));
else
channel = this->channelTree->findChannelByPath(str);
if (channel) {
if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) {
logMessage(this->serverId, "{} Client tried to connect to a channel which he hasn't permission for. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name());
channel = nullptr;
} else if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true) && !permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
logMessage(this->serverId, "{} Client tried to connect to a channel which is password protected and he hasn't the right password. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name());
channel = nullptr;
goto skip_permissions;
}
}
skip_permissions:;
} else
logMessage(this->serverId, "{} Client {}/{} tried to join on a not existing channel. Name: {}",
CLIENT_STR_LOG_PREFIX_(client),
client->getDisplayName(), client->getUid(),
client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>());
}
if(!channel) channel = this->channelTree->getDefaultChannel();
if(!channel) return false;
/* Clear these parameters. We don't need them any more after we initially payed attention. */
client->properties()[property::CLIENT_DEFAULT_CHANNEL] = "";
client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD] = "";
if(!channel) {
/* Client did not propose a channel or the proposed channel got rejected */
channel = this->channelTree->getDefaultChannel();
if(!channel) {
logCritical(this->getServerId(), "Channel tree is missing the default channel.");
return false;
}
}
debugMessage(this->getServerId(), "{} Using channel {} as default client channel.", client->getLoggingPrefix(), channel->channelId());
if(join) {
this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_lock);
server_channel_lock.unlock();
unique_lock server_channel_w_lock(this->channel_tree_lock);
this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_w_lock);
} else {
client->currentChannel = channel;
}
return true;
}
@ -265,17 +220,51 @@ void VirtualServer::testBanStateChange(const std::shared_ptr<ConnectedClient>& i
});
}
bool VirtualServer::could_default_create_channel() {
{
auto default_group = this->getGroupManager()->defaultGroup(GroupTarget::GROUPTARGET_SERVER);
if(default_group) {
auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1;
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1;
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1;
if(flag)
return true;
}
}
{
auto default_group = this->getGroupManager()->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
if(default_group) {
auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1;
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1;
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1;
if(flag)
return true;
}
}
return false;
}
/*
*
for (auto &cl : this->server->getClients())
if (cl->isClientVisible(client) || client == cl)
cl->notifyClientLeftViewKicked(client, client->currentChannel, nullptr, cmd["reasonmsg"].as<std::string>(), this);
*/
void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target, const std::shared_ptr<ts::server::ConnectedClient> &invoker, const std::string &reason, size_t time) {
/* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */
lock_guard command_lock(target->command_lock);
unique_lock server_channel_lock(this->channel_tree_mutex); /* we're "moving" a client! */
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
if(target->currentChannel) {
for(const auto& client : this->getClients()) {
if(!client || client == target)
continue;
unique_lock client_channel_lock(client->channel_tree_mutex);
unique_lock client_channel_lock(client->channel_lock);
if(client->isClientVisible(target, false))
client->notifyClientLeftViewBanned(target, reason, invoker, time, false);
}
@ -285,7 +274,7 @@ void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target,
}
/* now disconnect the target itself */
unique_lock client_channel_lock(target->channel_tree_mutex);
unique_lock client_channel_lock(target->channel_lock);
target->notifyClientLeftViewBanned(target, reason, invoker, time, false);
target->currentChannel = nullptr;
}
@ -298,32 +287,29 @@ void VirtualServer::notify_client_kick(
if(target_channel) {
/* use the move! */
unique_lock server_channel_lock(this->channel_tree_mutex, defer_lock);
unique_lock server_channel_lock(this->channel_tree_lock, defer_lock);
this->client_move(target, target_channel, invoker, reason, ViewReasonId::VREASON_CHANNEL_KICK, true, server_channel_lock);
} else {
/* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */
lock_guard command_lock(target->command_lock);
unique_lock server_channel_lock(this->channel_tree_mutex); /* we're "moving" a client! */
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
if(target->currentChannel) {
for(const auto& client : this->getClients()) {
if(!client || client == target)
continue;
unique_lock client_channel_lock(client->channel_tree_mutex);
unique_lock client_channel_lock(client->channel_lock);
if(client->isClientVisible(target, false))
client->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
}
auto s_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
s_channel->unregister_client(target);
if(auto client{dynamic_pointer_cast<SpeakingClient>(target)}; client) {
this->rtc_server().assign_channel(client->rtc_client_id, 0);
}
}
/* now disconnect the target itself */
unique_lock client_channel_lock(target->channel_tree_mutex);
unique_lock client_channel_lock(target->channel_lock);
target->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
target->currentChannel = nullptr;
}
@ -337,14 +323,11 @@ void VirtualServer::notify_client_kick(
*
* Note: channel cant be a ref because the channel itself gets deleted!
*/
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock, bool temp_delete) {
if(!tree_lock.owns_lock()) {
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock) {
if(!tree_lock.owns_lock())
tree_lock.lock();
}
if(channel->deleted) {
if(channel->deleted)
return;
}
deque<std::shared_ptr<ConnectedClient>> clients;
{
@ -361,61 +344,29 @@ void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const
channel->deleted = true;
}
auto default_channel = this->channelTree->getDefaultChannel();
tree_lock.unlock();
deque<unique_lock<threads::Mutex>> command_locks;
for(const auto& client : clients) {
command_locks.push_back(move(unique_lock(client->command_lock)));
}
for(const auto& client : clients) {
for(const auto& client : clients)
this->client_move(client, default_channel, invoker, kick_message, ViewReasonId::VREASON_CHANNEL_KICK, true, tree_lock);
}
if(!tree_lock.owns_lock()) {
/* This case should never happen. client_move should never unlock the tree lock! */
tree_lock.lock();
}
if(!tree_lock.owns_lock())
tree_lock.lock(); /* no clients left within that tree */
command_locks.clear();
auto deleted_channels = this->channelTree->delete_channel_root(channel);
log::ChannelDeleteReason delete_reason{temp_delete ? log::ChannelDeleteReason::EMPTY : log::ChannelDeleteReason::USER_ACTION};
for(const auto& deleted_channel : deleted_channels) {
serverInstance->action_logger()->channel_logger.log_channel_delete(this->serverId, invoker, deleted_channel->channelId(), channel == deleted_channel ? delete_reason : log::ChannelDeleteReason::PARENT_DELETED);
}
auto channel_ids = this->channelTree->delete_channel_root(channel);
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
unique_lock client_channel_lock(client->channel_tree_mutex);
client->notifyChannelDeleted(client->channel_tree->delete_channel_root(channel), invoker);
unique_lock client_channel_lock(client->channel_lock);
client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker);
});
{
std::vector<ChannelId> deleted_channel_ids{};
deleted_channel_ids.reserve(deleted_channels.size());
for(const auto& deleted_channel : deleted_channels) {
deleted_channel_ids.push_back(deleted_channel->channelId());
}
auto ref_self = this->ref();
task_id task_id{};
serverInstance->general_task_executor()->schedule(task_id, "database cleanup after channel delete", [ref_self, deleted_channel_ids]{
for(const auto& deleted_channel_id : deleted_channel_ids) {
ref_self->tokenManager->handle_channel_deleted(deleted_channel_id);
}
for(const auto& deleted_channel_id : deleted_channel_ids) {
ref_self->group_manager()->assignments().handle_channel_deleted(deleted_channel_id);
}
});
}
}
/*
* This method had previously owned the clients command lock but that's not really needed.
* Everything which is related to the server channel tree or the client channel tree should be locked with
* the appropriate mutexes.
*/
void VirtualServer::client_move(
const shared_ptr<ts::server::ConnectedClient> &target_client,
const shared_ptr<ts::server::ConnectedClient> &target,
shared_ptr<ts::BasicChannel> target_channel,
const std::shared_ptr<ts::server::ConnectedClient> &invoker,
const std::string &reason_message,
@ -424,183 +375,171 @@ void VirtualServer::client_move(
std::unique_lock<std::shared_mutex> &server_channel_write_lock) {
TIMING_START(timings);
if(!server_channel_write_lock.owns_lock()) {
server_channel_write_lock.lock();
}
if(server_channel_write_lock.owns_lock())
server_channel_write_lock.unlock();
lock_guard client_command_lock(target->command_lock);
server_channel_write_lock.lock();
TIMING_STEP(timings, "chan tree l");
if(target_client->currentChannel == target_channel) {
if(target->currentChannel == target_channel)
return;
}
/* first step: verify thew source and target channel */
/* first step: resolve the target channel / or fix missing */
auto s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
auto s_source_channel = dynamic_pointer_cast<ServerChannel>(target_client->currentChannel);
assert(!target_client->currentChannel || s_source_channel != nullptr);
auto s_source_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
assert(!target->currentChannel || s_source_channel != nullptr);
std::deque<property::ClientProperties> updated_client_properties{};
deque<property::ClientProperties> client_updates;
std::deque<property::ClientProperties> changed_groups{};
if(target_channel) {
assert(s_target_channel);
if(s_target_channel->deleted) {
return;
target_channel = this->channelTree->getDefaultChannel();
s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
assert(s_target_channel);
}
}
/* update the group properties here already, so for all enter views we could just send the new props directly */
changed_groups = this->groups->update_server_group_property(target, true, s_target_channel);
client_updates.insert(client_updates.end(), changed_groups.begin(), changed_groups.end()); //TODO: Only update for clients which have no enter?
}
auto l_target_channel = s_target_channel ? this->channelTree->findLinkedChannel(s_target_channel->channelId()) : nullptr;
auto l_source_channel = s_source_channel ? this->channelTree->findLinkedChannel(s_source_channel->channelId()) : nullptr;
TIMING_STEP(timings, "channel res");
/* second step: show the target channel to the client if its not shown and let him subscribe to the channel */
/* second step: show the target channel to the client if its not shown and let him subscibe to the channel */
if(target_channel && notify_client) {
std::unique_lock client_channel_lock{target_client->channel_tree_mutex};
unique_lock client_channel_lock(target->channel_lock);
bool success{false};
bool success = false;
/* TODO: Use a bunk here and not a notify for every single */
for(const auto& channel : target_client->channel_tree->show_channel(l_target_channel, success)) {
target_client->notifyChannelShow(channel->channel(), channel->previous_channel);
}
for(const auto& channel : target->channels->show_channel(l_target_channel, success))
target->notifyChannelShow(channel->channel(), channel->previous_channel);
sassert(success);
if(!success) {
if(!success)
return;
}
target_client->subscribeChannel({ target_channel }, false, true);
target->subscribeChannel({target_channel}, false, true);
}
TIMING_STEP(timings, "target show");
if(s_source_channel) {
s_source_channel->unregister_client(target_client);
}
if(target_channel) {
ClientPermissionCalculator target_client_permissions{&*target_client, target_channel};
auto needed_view_power = target_client_permissions.calculate_permission(permission::i_client_needed_serverquery_view_power);
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
if (!notify_client && client == target) return;
/* ct_... is for client channel tree */
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& client) {
if (!notify_client && client == target_client) {
return;
}
unique_lock client_channel_lock(client->channel_lock);
auto chan_target = client->channels->find_channel(target_channel);
bool move_target_client_visible{true};
if(target_client->getType() == ClientType::CLIENT_QUERY) {
auto query_view_power = client->calculate_permission(permission::i_client_serverquery_view_power, target_channel->channelId());
move_target_client_visible = permission::v2::permission_granted(needed_view_power, query_view_power);
}
std::unique_lock client_channel_lock{client->channel_tree_mutex};
auto ct_target_channel = move_target_client_visible ? client->channel_tree->find_channel(target_channel) : nullptr;
if(ct_target_channel) {
auto ct_source_channel = client->channel_tree->find_channel(s_source_channel);
if(ct_source_channel) {
/* Source and target channel are visible for the client. Just a "normal" move. */
if (ct_target_channel->subscribed || client == target_client) {
if (client == target_client || client->isClientVisible(target_client, false)) {
client->notifyClientMoved(target_client, s_target_channel, reason_id, reason_message, invoker, false);
if(chan_target) {
auto chan_source = client->channels->find_channel(s_source_channel);
if(chan_source) {
if (chan_target->subscribed || client == target) {
if (client == target || client->isClientVisible(target, false)) {
client->notifyClientMoved(target, s_target_channel, reason_id, reason_message, invoker, false);
} else {
client->notifyClientEnterView(target_client, invoker, reason_message, s_target_channel, reason_id, s_source_channel, false);
client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, reason_id, s_source_channel, false);
}
} else if(client->isClientVisible(target_client, false)){
/* Client has been moved into an unsubscribed channel */
client->notifyClientLeftView(target_client, s_target_channel, reason_id, reason_message.empty() ? string("view left") : reason_message, invoker, false);
} else if(client->isClientVisible(target, false)){
//Client got out of view
client->notifyClientLeftView(target, s_target_channel, reason_id, reason_message.empty() ? string("view left") : reason_message, invoker, false);
}
} else if(ct_target_channel->subscribed) {
/* Target client entered the view from an invisible channel */
client->notifyClientEnterView(target_client, invoker, reason_message, s_target_channel, ViewReasonId::VREASON_USER_ACTION, nullptr, false);
} else {
if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC)
logCritical(this->getServerId(), "{} Client enters visibility twice!", CLIENT_STR_LOG_PREFIX_(client));
//Client entered view
if(chan_target->subscribed)
client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, ViewReasonId::VREASON_USER_ACTION, nullptr, false);
}
} else {
if(client->isClientVisible(target_client, false)) {
/* Client has been moved out of view into an invisible channel */
if(reason_id == ViewReasonId::VREASON_USER_ACTION) {
client->notifyClientLeftView(target_client, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "joined a hidden channel" : reason_message, invoker, false);
} else {
client->notifyClientLeftView(target_client, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "moved to a hidden channel" : reason_message, invoker, false);
}
/* target channel isn't visible => so client gone out of view */
if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC)
logCritical(this->getServerId(), "{} Moving own client into a not visible channel! This shall not happen!", CLIENT_STR_LOG_PREFIX_(client));
//Test for in view? (Notify already does but nvm)
if(client->isClientVisible(target, false)){
//Client got out of view
if(reason_id == ViewReasonId::VREASON_USER_ACTION)
client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "joined a hidden channel" : reason_message, invoker, false);
else
client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "moved to a hidden channel" : reason_message, invoker, false);
}
}
});
s_target_channel->register_client(target_client);
if(auto client{dynamic_pointer_cast<SpeakingClient>(target_client)}; client) {
this->rtc_server().assign_channel(client->rtc_client_id, s_target_channel->rtc_channel_id);
}
if(auto client{dynamic_pointer_cast<VoiceClient>(target_client)}; client) {
/* Start normal broadcasting, what the client expects */
this->rtc_server().start_broadcast_audio(client->rtc_client_id, 1);
client->clear_video_unsupported_message_flag();
}
if(s_source_channel)
s_source_channel->unregister_client(target);
s_target_channel->register_client(target);
} else {
/* client left the server */
if(target_client->currentChannel) {
if(target->currentChannel) {
for(const auto& client : this->getClients()) {
if(!client || client == target_client)
if(!client || client == target)
continue;
unique_lock client_channel_lock(client->channel_tree_mutex);
if(client->isClientVisible(target_client, false)) {
client->notifyClientLeftView(target_client, nullptr, reason_id, reason_message, invoker, false);
}
unique_lock client_channel_lock(client->channel_lock);
if(client->isClientVisible(target, false))
client->notifyClientLeftView(target, nullptr, reason_id, reason_message, invoker, false);
}
if(auto client{dynamic_pointer_cast<SpeakingClient>(target_client)}; client) {
this->rtc_server().assign_channel(client->rtc_client_id, 0);
}
s_source_channel->unregister_client(target);
}
}
TIMING_STEP(timings, "notify view");
target_client->currentChannel = target_channel;
target->currentChannel = target_channel;
server_channel_write_lock.unlock();
/* third step: update stuff for the client (remember: the client cant execute anything at the moment!) */
unique_lock client_channel_lock{target_client->channel_tree_mutex};
shared_lock server_channel_read_lock(this->channel_tree_lock);
unique_lock client_channel_lock(target->channel_lock);
TIMING_STEP(timings, "lock own tr");
if (s_source_channel) {
s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = std::chrono::duration_cast<chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
this->group_manager()->assignments().cleanup_temporary_channel_assignment(target_client->getClientDatabaseId(), s_source_channel->channelId());
if(target_client->properties()[property::CLIENT_IS_TALKER].update_value("0")) {
updated_client_properties.push_back(property::CLIENT_IS_TALKER);
s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
auto source_channel_group = this->groups->getChannelGroupExact(target->getClientDatabaseId(), s_source_channel, false);
if(source_channel_group) {
auto default_data = source_channel_group->group->permissions()->permission_value_flagged(permission::b_group_is_permanent);
if(!default_data.has_value || default_data.value != 1)
this->groups->setChannelGroup(target->getClientDatabaseId(), nullptr, s_source_channel);
}
if(target_client->properties()[property::CLIENT_TALK_REQUEST].update_value("0")) {
updated_client_properties.push_back(property::CLIENT_TALK_REQUEST);
}
if(target_client->properties()[property::CLIENT_TALK_REQUEST_MSG].update_value("")) {
updated_client_properties.push_back(property::CLIENT_TALK_REQUEST_MSG);
auto update = target->properties()[property::CLIENT_IS_TALKER].as<bool>() || target->properties()[property::CLIENT_TALK_REQUEST].as<int64_t>() > 0;
if(update) {
target->properties()[property::CLIENT_IS_TALKER] = 0;
target->properties()[property::CLIENT_TALK_REQUEST] = 0;
target->properties()[property::CLIENT_TALK_REQUEST_MSG] = "";
client_updates.push_back(property::CLIENT_IS_TALKER);
client_updates.push_back(property::CLIENT_TALK_REQUEST);
client_updates.push_back(property::CLIENT_TALK_REQUEST_MSG);
}
TIMING_STEP(timings, "src chan up");
}
if (s_target_channel) {
target_client->task_update_needed_permissions.enqueue();
target_client->task_update_displayed_groups.enqueue();
if(target->update_cached_permissions()) /* update cached calculated permissions */
target->sendNeededPermissions(false);
TIMING_STEP(timings, "perm gr upd");
if(s_source_channel) {
deque<ChannelId> deleted;
for(const auto& channel : target_client->channel_tree->test_channel(l_source_channel, l_target_channel)) {
for(const auto& channel : target->channels->test_channel(l_source_channel, l_target_channel))
deleted.push_back(channel->channelId());
}
if(!deleted.empty()) {
target_client->notifyChannelHide(deleted, false);
}
if(!deleted.empty())
target->notifyChannelHide(deleted, false);
auto i_source_channel = s_source_channel->channelId();
if(std::find(deleted.begin(), deleted.end(), i_source_channel) == deleted.end()) {
auto source_channel_sub_power = target_client->calculate_permission(permission::i_channel_subscribe_power, i_source_channel);
auto source_channel_sub_power = target->calculate_permission(permission::i_channel_subscribe_power, i_source_channel);
if(!s_source_channel->permission_granted(permission::i_channel_needed_subscribe_power, source_channel_sub_power, false)) {
auto source_channel_sub_power_ignore = target_client->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel);
if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore)) {
auto source_channel_sub_power_ignore = target->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel);
if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore, true)) {
logTrace(this->serverId, "Force unsubscribing of client {} for channel {}/{}. (Channel switch and no permissions)",
CLIENT_STR_LOG_PREFIX_(target_client), s_source_channel->name(),
CLIENT_STR_LOG_PREFIX_(target), s_source_channel->name(),
i_source_channel
);
target_client->unsubscribeChannel({ s_source_channel }, false); //Unsubscribe last channel (hasn't permissions)
target->unsubscribeChannel({s_source_channel}, false); //Unsubscribe last channel (hasn't permissions)
}
}
}
@ -608,13 +547,12 @@ void VirtualServer::client_move(
}
}
client_channel_lock.unlock();
/* both methods lock if they require stuff */
this->notifyClientPropertyUpdates(target_client, updated_client_properties, s_source_channel ? true : false);
this->notifyClientPropertyUpdates(target, client_updates, s_source_channel ? true : false);
TIMING_STEP(timings, "notify cpro");
if(s_target_channel) {
target_client->updateChannelClientProperties(false, s_source_channel ? true : false);
target->updateChannelClientProperties(false, s_source_channel ? true : false);
TIMING_STEP(timings, "notify_t_pr");
}
debugMessage(this->getServerId(), "{} Client move timings: {}", CLIENT_STR_LOG_PREFIX_(target_client), TIMING_FINISH(timings));
debugMessage(this->getServerId(), "{} Client move timings: {}", CLIENT_STR_LOG_PREFIX_(target), TIMING_FINISH(timings));
}

View File

@ -6,7 +6,6 @@
#include "VirtualServer.h"
#include "./manager/ConversationManager.h"
#include "./music/MusicBotManager.h"
#include "./groups/GroupManager.h"
using namespace std;
using namespace std::chrono;
@ -17,10 +16,10 @@ using namespace ts::buffer;
inline void banClientFlood(VirtualServer* server, const shared_ptr<ConnectedClient>& cl, time_point<system_clock> until){
auto time = until.time_since_epoch().count() == 0 ? 0L : chrono::ceil<chrono::seconds>(until - system_clock::now()).count();
std::string reason{"You're flooding too much"};
std::string reason = "You're flooding too much";
serverInstance->banManager()->registerBan(server->getServerId(), cl->getClientDatabaseId(), reason, cl->getUid(), cl->getLoggingPeerIp(), "", "", until);
for(const auto &client : server->findClientsByUid(cl->getUid())) {
for(const auto &client : server->findClientsByUid(cl->getUid())) {
server->notify_client_ban(client, server->getServerRoot(), reason, time);
client->close_connection(system_clock::now() + seconds(1));
}
@ -32,27 +31,29 @@ timing_end = system_clock::now(); \
variable = duration_cast<decltype(variable)>(timing_end - timing_begin);
void VirtualServer::executeServerTick() {
std::shared_lock state_lock{this->state_mutex};
if(!this->running()) {
threads::MutexTryLock l(this->stateLock); //Should not attempt to shutdown or start the server while ticking
if(!l) {
if(this->running())
logError(this->getServerId(), "Failed to lock tick mutex!");
return;
}
if(!this->running()) return;
auto tick_timestamp = std::chrono::system_clock::now();
try {
if(this->lastTick.time_since_epoch().count() > 0) {
auto delay = tick_timestamp - this->lastTick;
auto delay_ms = std::chrono::duration_cast<std::chrono::milliseconds>(delay).count();
if(lastTick.time_since_epoch().count() != 0) {
auto delay = system_clock::now() - lastTick;
auto delay_ms = duration_cast<milliseconds>(delay).count();
if(delay_ms > 510) {
if(delay_ms < 750) {
logWarning(this->getServerId(),
"Found variances within the server tick! (Supposed: 500ms Hold: {}ms)", delay_ms);
"Found varianzes within the server tick! (Supposed: 500ms Hold: {}ms)", delay_ms);
} else {
logError(this->getServerId(),
"Found variances within the server tick! This long delay could be an issue. (Supposed: 500ms Hold: {}ms)", delay_ms);
"Found varianzes within the server tick! This long delay could be an issue. (Supposed: 500ms Hold: {}ms)", delay_ms);
}
}
}
this->lastTick = tick_timestamp;
lastTick = system_clock::now();
system_clock::time_point timing_begin, timing_end;
milliseconds timing_update_states, timing_client_tick, timing_channel, timing_statistic, timing_groups, timing_ccache, music_manager;
@ -62,52 +63,29 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
size_t clientOnline{0};
size_t queryOnline{0};
size_t clientOnline = 0;
size_t queryOnline = 0;
for(const auto& conn : client_list){
switch(conn->connectionState()) {
case ConnectionState::CONNECTED:
case ConnectionState::INIT_HIGH:
break;
case ConnectionState::INIT_LOW:
case ConnectionState::DISCONNECTING:
case ConnectionState::DISCONNECTED:
continue;
case ConnectionState::UNKNWON:
default:
assert(false);
continue;
}
switch (conn->getType()) {
switch (conn->getType()){
case ClientType::CLIENT_TEAMSPEAK:
case ClientType::CLIENT_TEASPEAK:
case ClientType::CLIENT_WEB:
clientOnline++;
break;
case ClientType::CLIENT_QUERY:
case ClientType::CLIENT_MUSIC:
queryOnline++;
break;
case ClientType::CLIENT_INTERNAL:
case ClientType::MAX:
default:
break;
}
}
properties()[property::VIRTUALSERVER_UPTIME] = std::chrono::duration_cast<std::chrono::seconds>(tick_timestamp - this->startTimestamp).count();
properties()[property::VIRTUALSERVER_UPTIME] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - this->startTimestamp).count();
properties()[property::VIRTUALSERVER_CLIENTS_ONLINE] = clientOnline + queryOnline;
properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE] = queryOnline;
if(clientOnline + queryOnline == 0) {
//We don't need to tick, when server is empty!
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;
END_TIMINGS(timing_update_states);
@ -116,8 +94,8 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
auto flood_decrease = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_TICK_REDUCE].as_or<FloodPoints>(0);
auto flood_block = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_IP_BLOCK].as_or<FloodPoints>(0);
auto flood_decrease = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_TICK_REDUCE].as<FloodPoints>();
auto flood_block = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_IP_BLOCK].as<FloodPoints>();
bool flag_update_spoken = this->spoken_time_timestamp + seconds(30) < system_clock::now();
@ -126,34 +104,37 @@ void VirtualServer::executeServerTick() {
tick_client_begin = tick_client_end;
if(cl->server != this) {
logError(this->getServerId(), "Got registered client, but client does not think hes bound to this server!");
std::unique_lock tree_lock{this->channel_tree_mutex};
this->unregisterClient(cl, "invalid server handle", tree_lock);
continue;
}
{
lock_guard lock(this->clients.lock);
for(auto& client : this->clients.clients) {
if(client != cl) continue;
client.reset();
this->clients.count--;
break;
}
}
continue; //Fully ha?
}
if(cl->floodPoints > flood_block){
if(!cl->ignoresFlood()) {
banClientFlood(this, cl, system_clock::now() + minutes(10));
continue;
}
}
if(cl->floodPoints > flood_decrease) {
if(cl->floodPoints > flood_decrease)
cl->floodPoints -= flood_decrease;
} else {
cl->floodPoints = 0;
}
cl->tick_server(tick_client_end);
else cl->floodPoints = 0;
cl->tick(tick_client_end);
auto voice = dynamic_pointer_cast<SpeakingClient>(cl);
if(flag_update_spoken && voice) {
if(flag_update_spoken && voice)
this->spoken_time += voice->takeSpokenTime();
}
tick_client_end = system_clock::now();
auto passed_time = tick_client_end - tick_client_begin;
if(passed_time > microseconds{2500}) {
if(passed_time > milliseconds{10}) {
if(passed_time > microseconds(2500)) {
if(passed_time > milliseconds(10)) {
logError(this->serverId, "Ticking of client {:1} ({:2}) needs more that 2500 microseconds! ({:3} microseconds)",
cl->getLoggingPeerIp() + ":" + to_string(cl->getPeerPort()),
cl->getDisplayName(),
@ -168,18 +149,15 @@ void VirtualServer::executeServerTick() {
}
}
auto client_permissions = cl->clientPermissions;
if(client_permissions->require_db_updates()) {
if(cl->clientPermissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), client_permissions);
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), 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());
}
}
if(flag_update_spoken) {
if(flag_update_spoken)
this->spoken_time_timestamp = system_clock::now();
}
END_TIMINGS(timing_client_tick);
}
@ -188,33 +166,31 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
std::unique_lock channel_lock{this->channel_tree_mutex};
unique_lock channel_lock(this->channel_tree_lock);
auto channels = this->channelTree->channels();
for(const auto& channel : channels) {
auto server_channel = dynamic_pointer_cast<ServerChannel>(channel);
assert(server_channel);
channel_lock.unlock();
for(const auto& channel : this->channelTree->channels()){
if(channel->channelType() == ChannelType::temporary) {
if(server_channel->client_count() > 0 || !this->isChannelRootEmpty(channel, false)) {
auto server_channel = dynamic_pointer_cast<ServerChannel>(channel);
assert(server_channel);
if(server_channel->client_count() > 0 || !this->getClientsByChannelRoot(channel, true).empty())
continue;
}
/* seconds */
auto channel_delete_timeout = channel->properties()[property::CHANNEL_DELETE_DELAY].as_or<uint64_t>(0);
auto empty_seconds = channel->empty_seconds();
seconds deleteTimeout(0);
if(channel->properties().hasProperty(property::CHANNEL_DELETE_DELAY))
deleteTimeout = seconds(channel->properties()[property::CHANNEL_DELETE_DELAY].as<uint64_t>());
if(empty_seconds > channel_delete_timeout) {
this->delete_channel(server_channel, this->serverRoot, "temporary auto delete", channel_lock, true);
if(!channel_lock.owns_lock()) {
channel_lock.lock();
}
auto last_left = time_point<system_clock>() + milliseconds(channel->properties()[property::CHANNEL_LAST_LEFT].as<int64_t>());
auto channel_created = channel->createdTimestamp();
/* no need to tick the channel any more since it has been deleted */
continue;
}
if(system_clock::now() - last_left < deleteTimeout) continue; //One second stay
if(system_clock::now() - channel_created < deleteTimeout + seconds(1)) continue; //One second stay
this->delete_channel(server_channel, this->serverRoot, "temporary autodelete", channel_lock);
if(channel_lock.owns_lock())
channel_lock.unlock();
}
{
auto permission_manager = channel->permissions();
if(permission_manager->require_db_updates()) {
@ -232,15 +208,34 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
this->server_statistics_->tick();
{
lock_guard lock{this->join_attempts_lock};
if(tick_timestamp > this->join_last_decrease + seconds(5)) {
std::erase_if(this->join_attempts, [](auto& entry) {
return --entry.second <= 0;
});
this->serverStatistics->tick();
this->join_last_decrease = tick_timestamp;
if(fileStatisticsTimestamp + seconds(5) < system_clock::now()) {
fileStatisticsTimestamp = system_clock::now();
auto update = this->serverStatistics->mark_file_bytes();
if(update.first > 0) {
this->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED] += update.first;
this->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED] += update.first;
}
if(update.second > 0) {
this->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED] += update.second;
this->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED] += update.second;
}
}
{
lock_guard<threads::Mutex> lock(this->join_attempts_lock);
if(system_clock::now() > this->join_last_decrease + seconds(5)) {
for(auto& elm : this->join_attempts)
if(elm.second > 0) elm.second--;
auto copy = this->join_attempts;
for(const auto& elm : copy)
if(elm.second == 0){
auto found = find(this->join_attempts.begin(), this->join_attempts.end(), elm);
if(found != this->join_attempts.end()) this->join_attempts.erase(found);
}
this->join_last_decrease = system_clock::now();
}
}
END_TIMINGS(timing_statistic);
@ -248,7 +243,18 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
this->group_manager()->save_permissions();
auto groups = this->getGroupManager()->availableGroups(false);
for(auto& group : groups) {
auto permissions = group->permissions();
if(permissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveGroupPermissions(this->ref(), 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());
}
}
END_TIMINGS(timing_groups);
}
@ -256,7 +262,7 @@ void VirtualServer::executeServerTick() {
BEGIN_TIMINGS();
if(this->conversation_cache_cleanup_timestamp + minutes(15) < system_clock::now()) {
debugMessage(this->serverId, "Cleaning up conversation cache.");
this->conversation_manager_->cleanup_cache();
this->_conversation_manager->cleanup_cache();
conversation_cache_cleanup_timestamp = system_clock::now();
}
END_TIMINGS(timing_ccache);
@ -264,13 +270,13 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
this->music_manager_->execute_tick();
this->musicManager->execute_tick();
END_TIMINGS(music_manager);
}
if(system_clock::now() - lastTick > milliseconds(100)) {
//milliseconds timing_update_states, timing_client_tick, timing_channel, timing_statistic;
logError(this->serverId, "Server tick took to long ({}ms => Status updates: {}ms Client tick: {}ms, Channel tick: {}ms, Statistic tick: {}ms, Groups: {}ms, Conversation cache: {}ms)",
logError(this->serverId, "Server tick tooks to long ({}ms => Status updates: {}ms Client tick: {}ms, Channel tick: {}ms, Statistic tick: {}ms, Groups: {}ms, Conversation cache: {}ms)",
duration_cast<milliseconds>(system_clock::now() - lastTick).count(),
timing_update_states.count(),
timing_client_tick.count(),

File diff suppressed because it is too large Load Diff

View File

@ -15,14 +15,12 @@
#include "manager/BanManager.h"
#include "Definitions.h"
#include "ConnectionStatistics.h"
#include "manager/TokenManager.h"
#include "manager/TokeManager.h"
#include "manager/ComplainManager.h"
#include "DatabaseHelper.h"
#include "manager/LetterManager.h"
#include "Configuration.h"
#include "protocol/ringbuffer.h"
#include "absl/btree/map.h"
#include <misc/task_executor.h>
#include <tomcrypt.h>
#undef byte
@ -50,10 +48,6 @@ namespace ts {
class MusicBotManager;
}
namespace rtc {
class Server;
}
namespace server {
class ConnectedClient;
class VoiceClient;
@ -64,20 +58,16 @@ namespace ts {
class InstanceHandler;
class VoiceServer;
class QueryServer;
class LocalFileServer;
class SpeakingClient;
class WebControlServer;
namespace conversation {
class ConversationManager;
}
namespace groups {
class ServerGroup;
class ChannelGroup;
class GroupManager;
}
struct ServerState {
enum value {
OFFLINE,
@ -105,39 +95,32 @@ namespace ts {
}
};
struct ServerSlotUsageReport {
size_t server_count{0};
size_t max_clients{0};
size_t reserved_clients{0};
size_t clients_teamspeak{0};
size_t clients_teaspeak{0};
size_t clients_teaweb{0};
size_t queries{0};
size_t music_bots{0};
size_t online_channels{0};
[[nodiscard]] inline size_t voice_clients() const {
return this->clients_teaspeak + this->clients_teamspeak + this->clients_teaweb;
}
inline ServerSlotUsageReport& operator+=(const ServerSlotUsageReport& other) {
this->server_count += other.server_count;
this->max_clients += other.max_clients;
this->reserved_clients += other.reserved_clients;
this->clients_teamspeak += other.clients_teamspeak;
this->clients_teaspeak += other.clients_teaspeak;
this->clients_teaweb += other.clients_teaweb;
this->queries += other.queries;
this->music_bots += other.music_bots;
this->online_channels += other.online_channels;
return *this;
}
struct OnlineClientReport {
uint16_t clients_ts = 0;
uint16_t clients_web = 0;
uint16_t queries = 0;
uint16_t bots = 0;
};
struct CalculateCache {};
struct CalculateCache {
bool global_skip = false;
bool global_skip_set = false;
std::shared_ptr<permission::v2::PermissionManager> client_permissions;
std::vector<std::shared_ptr<GroupAssignment>> assignment_server_groups;
bool assignment_server_groups_set = false;
ChannelId assignment_channel_group_channel;
std::shared_ptr<GroupAssignment> assignment_channel_group;
bool assignment_channel_group_set = false;
std::shared_ptr<BasicChannel> server_channel;
ChannelId last_server_channel = 0;
inline std::vector<std::shared_ptr<GroupAssignment>> getGroupAssignments(VirtualServer* server, ClientDbId cldbid, ClientType type);
inline std::shared_ptr<GroupAssignment> getChannelAssignment(VirtualServer* server, ClientDbId client_dbid, ChannelId channel);
inline std::shared_ptr<BasicChannel> getServerChannel(VirtualServer*, ChannelId);
};
class VirtualServer {
friend class WebClient;
@ -168,7 +151,9 @@ namespace ts {
void preStop(const std::string&);
void stop(const std::string& reason, bool /* disconnect query */);
ServerSlotUsageReport onlineStats();
size_t onlineClients();
OnlineClientReport onlineStats();
size_t onlineChannels(){ return this->channelTree->channel_count(); }
std::shared_ptr<ConnectedClient> find_client_by_id(ClientId /* client id */);
std::deque<std::shared_ptr<ConnectedClient>> findClientsByCldbId(ClientDbId cldbId);
std::deque<std::shared_ptr<ConnectedClient>> findClientsByUid(ClientUid uid);
@ -179,8 +164,6 @@ namespace ts {
std::vector<std::shared_ptr<ConnectedClient>> getClients();
std::deque<std::shared_ptr<ConnectedClient>> getClientsByChannel(std::shared_ptr<BasicChannel>);
std::deque<std::shared_ptr<ConnectedClient>> getClientsByChannelRoot(const std::shared_ptr<BasicChannel> &, bool lock_channel_tree);
[[nodiscard]] size_t countChannelRootClients(const std::shared_ptr<BasicChannel> &, size_t /* limit */, bool /* lock the channel tree */);
[[nodiscard]] bool isChannelRootEmpty(const std::shared_ptr<BasicChannel> &, bool lock_channel_tree);
template <typename ClType>
std::vector<std::shared_ptr<ClType>> getClientsByChannel(const std::shared_ptr<BasicChannel>& ch) {
@ -194,36 +177,21 @@ namespace ts {
ecc_key* serverKey(){ return _serverKey; }
std::string publicServerKey();
inline PropertyWrapper properties() { return PropertyWrapper{this->_properties}; }
inline const PropertyWrapper properties() const { return PropertyWrapper{this->_properties}; }
Properties& properties(){ return *this->_properties; }
inline sql::SqlManager * getSql(){ return this->sql; }
sql::AsyncSqlPool* getSqlPool(){ return this->sql->pool; }
inline ServerId getServerId(){ return this->serverId; }
inline ServerChannelTree* getChannelTree(){ return this->channelTree; }
inline rtc::Server& rtc_server() { return *this->rtc_server_; }
[[nodiscard]] inline auto& getTokenManager() {
return *this->tokenManager;
}
[[nodiscard]] inline auto group_manager() { return this->groups_manager_; }
[[nodiscard]] std::shared_ptr<groups::ServerGroup> default_server_group();
[[nodiscard]] std::shared_ptr<groups::ChannelGroup> default_channel_group();
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! */
inline bool notifyClientPropertyUpdates(const std::shared_ptr<ConnectedClient>& client, const std::deque<property::ClientProperties>& keys, bool selfNotify = true) {
if(keys.empty()) {
return false;
}
if(keys.empty()) return false;
std::deque<const property::PropertyDescription*> _keys{};
for(const auto& key : keys) {
_keys.push_back(&property::describe(key));
}
for(const auto& key : keys) _keys.push_back(&property::describe(key));
return this->notifyClientPropertyUpdates(client, _keys, selfNotify);
};
@ -239,7 +207,7 @@ namespace ts {
std::string getDisplayName(){ return properties()[property::VIRTUALSERVER_NAME]; }
std::shared_ptr<stats::ConnectionStatistics> getServerStatistics(){ return server_statistics_; }
std::shared_ptr<stats::ConnectionStatistics> getServerStatistics(){ return serverStatistics; }
std::shared_ptr<VoiceServer> getVoiceServer(){ return this->udpVoiceServer; }
WebControlServer* getWebServer(){ return this->webControlServer; }
@ -250,7 +218,8 @@ namespace ts {
ClientDbId,
ClientType type,
ChannelId channel,
bool granted = false
bool granted = false,
std::shared_ptr<CalculateCache> cache = nullptr
);
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> calculate_permissions(
@ -258,10 +227,11 @@ namespace ts {
ClientDbId,
ClientType type,
ChannelId channel,
bool granted = false
bool granted = false,
std::shared_ptr<CalculateCache> cache = nullptr
);
[[nodiscard]] bool verifyServerPassword(std::string /* password */, bool /* hashed */);
bool verifyServerPassword(std::string, bool hashed = false);
void testBanStateChange(const std::shared_ptr<ConnectedClient>& invoker);
@ -272,6 +242,8 @@ namespace ts {
ServerState::value getState() { return this->state; }
bool could_default_create_channel();
inline std::shared_ptr<VirtualServer> ref() { return this->self.lock(); }
inline bool disable_ip_saving() { return this->_disable_ip_saving; }
inline std::chrono::system_clock::time_point start_timestamp() { return this->startTimestamp; };
@ -299,21 +271,16 @@ namespace ts {
std::shared_ptr<ServerChannel> /* target channel */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
const std::string& /* kick message */,
std::unique_lock<std::shared_mutex>& /* tree lock */,
bool temporary_auto_delete
std::unique_lock<std::shared_mutex>& /* tree lock */
);
void send_text_message(const std::shared_ptr<BasicChannel>& /* channel */, const std::shared_ptr<ConnectedClient>& /* sender */, const std::string& /* message */);
inline int voice_encryption_mode() { return this->_voice_encryption_mode; }
inline std::shared_ptr<conversation::ConversationManager> conversation_manager() { return this->conversation_manager_; }
inline std::shared_ptr<conversation::ConversationManager> conversation_manager() { return this->_conversation_manager; }
inline auto& get_channel_tree_lock() { return this->channel_tree_mutex; }
void update_channel_from_permissions(const std::shared_ptr<BasicChannel>& /* channel */, const std::shared_ptr<ConnectedClient>& /* issuer */);
inline void enqueue_notify_channel_group_list() { this->task_notify_channel_group_list.enqueue(); }
inline void enqueue_notify_server_group_list() { this->task_notify_server_group_list.enqueue(); }
protected:
bool registerClient(std::shared_ptr<ConnectedClient>);
bool unregisterClient(std::shared_ptr<ConnectedClient>, std::string, std::unique_lock<std::shared_mutex>& channel_tree_lock);
@ -323,10 +290,8 @@ namespace ts {
std::weak_ptr<VirtualServer> self;
//Locks by tick, start and stop
std::shared_mutex state_mutex{};
ServerState::value state{ServerState::OFFLINE};
task_id tick_task_id{};
threads::Mutex stateLock;
ServerState::value state = ServerState::OFFLINE;
std::chrono::system_clock::time_point lastTick;
void executeServerTick();
@ -335,10 +300,9 @@ namespace ts {
token::TokenManager* tokenManager = nullptr;
ComplainManager* complains = nullptr;
letter::LetterManager* letters = nullptr;
std::shared_ptr<music::MusicBotManager> music_manager_;
std::shared_ptr<stats::ConnectionStatistics> server_statistics_;
std::shared_ptr<conversation::ConversationManager> conversation_manager_;
std::unique_ptr<rtc::Server> rtc_server_;
std::shared_ptr<music::MusicBotManager> musicManager;
std::shared_ptr<stats::ConnectionStatistics> serverStatistics;
std::shared_ptr<conversation::ConversationManager> _conversation_manager;
sql::SqlManager* sql;
@ -349,34 +313,35 @@ namespace ts {
std::chrono::system_clock::time_point conversation_cache_cleanup_timestamp;
//The client list
std::mutex clients_mutex{};
btree::map<ClientId, std::shared_ptr<ConnectedClient>> clients{};
struct {
size_t count = 0;
std::mutex lock;
std::vector<std::shared_ptr<ConnectedClient>> clients;
} clients;
std::recursive_mutex client_nickname_lock;
//General server properties
ecc_key* _serverKey = nullptr;
std::shared_ptr<PropertyManager> _properties;
std::shared_ptr<Properties> _properties;
int _voice_encryption_mode = 2; /* */
ServerChannelTree* channelTree = nullptr;
std::shared_mutex channel_tree_mutex; /* lock if access channel tree! */
std::shared_mutex channel_tree_lock; /* lock if access channel tree! */
std::shared_ptr<groups::GroupManager> groups_manager_{};
GroupManager* groups = nullptr;
std::shared_ptr<ConnectedClient> serverRoot = nullptr;
std::shared_ptr<ConnectedClient> serverAdmin = nullptr;
std::mutex join_attempts_lock;
threads::Mutex join_attempts_lock;
std::map<std::string, uint16_t > join_attempts;
threads::Mutex join_lock;
std::chrono::system_clock::time_point join_last_decrease;
std::chrono::milliseconds spoken_time{0};
std::chrono::system_clock::time_point spoken_time_timestamp;
multi_shot_task task_notify_channel_group_list{};
multi_shot_task task_notify_server_group_list{};
bool _disable_ip_saving = false;
};
}

View File

@ -4,10 +4,9 @@
#include "src/server/VoiceServer.h"
#include "src/client/query/QueryClient.h"
#include "InstanceHandler.h"
#include "src/server/file/LocalFileServer.h"
#include "src/client/ConnectedClient.h"
#include <ThreadPool/ThreadHelper.h>
#include <files/FileServer.h>
#include <set>
using namespace std;
using namespace std::chrono;
@ -15,6 +14,12 @@ using namespace ts::server;
VirtualServerManager::VirtualServerManager(InstanceHandler* handle) : handle(handle) {
this->puzzles = new udp::PuzzleManager{};
this->handshakeTickers = new threads::Scheduler(1, "handshake ticker");
this->execute_loop = new event::EventExecutor("executor #");
//this->join_loop = new event::EventExecutor("joiner #");
this->_ioManager = new io::VoiceIOManager();
this->handshakeTickers->schedule("ticker", [&](){ this->tickHandshakeClients(); }, seconds(1));
}
VirtualServerManager::~VirtualServerManager() {
@ -34,9 +39,31 @@ VirtualServerManager::~VirtualServerManager() {
delete this->puzzles;
this->puzzles = nullptr;
if(this->execute_loop)
this->execute_loop->shutdown();
delete this->execute_loop;
this->execute_loop = nullptr;
if(this->join_loop)
this->join_loop->shutdown();
delete this->join_loop;
this->join_loop = nullptr;
if(this->handshakeTickers) {
this->handshakeTickers->shutdown();
}
delete this->handshakeTickers;
this->handshakeTickers = nullptr;
if(this->_ioManager) this->_ioManager->shutdownGlobally();
delete this->_ioManager;
this->_ioManager = nullptr;
}
bool VirtualServerManager::initialize(bool autostart) {
this->execute_loop->initialize(1);
this->state = State::STARTING;
logMessage(LOG_INSTANCE, "Generating server puzzles...");
auto start = system_clock::now();
@ -83,9 +110,6 @@ bool VirtualServerManager::initialize(bool autostart) {
if(id == 0) {
logError(LOG_INSTANCE, "Failed to load virtual server from database. Server id is zero!");
return 0;
} else if(id == 0xFFFF) {
/* snapshot server */
return 0;
}
if(host.empty()) {
@ -112,7 +136,7 @@ bool VirtualServerManager::initialize(bool autostart) {
this->instances.push_back(server);
}
if(autostart && server->properties()[property::VIRTUALSERVER_AUTOSTART].as_or<bool>(false)) {
if(autostart && server->properties()[property::VIRTUALSERVER_AUTOSTART].as<bool>()) {
logMessage(server->getServerId(), "Starting server");
string msg;
try {
@ -135,6 +159,7 @@ bool VirtualServerManager::initialize(bool autostart) {
(float) server_count / (time / 1024 == 0 ? 1 : time / 1024)
);
this->handle->databaseHelper()->clearStartupCache(0);
this->adjust_executor_threads();
{
this->acknowledge.executor = std::thread([&]{
@ -168,24 +193,26 @@ shared_ptr<VirtualServer> VirtualServerManager::findServerById(ServerId sid) {
shared_ptr<VirtualServer> VirtualServerManager::findServerByPort(uint16_t port) {
for(const auto& server : this->serverInstances()){
if(server->properties()[property::VIRTUALSERVER_PORT] == port) {
return server;
}
if(server->properties()[property::VIRTUALSERVER_PORT] == port) return server;
if(server->running() && server->getVoiceServer())
for(const auto& binding : server->getVoiceServer()->activeBindings())
if(binding->address_port() == port) return server;
}
return nullptr;
}
uint16_t VirtualServerManager::next_available_port(const std::string& host_string) {
auto instances_ = this->serverInstances();
std::set<uint16_t> unallowed_ports{};
auto instances = this->serverInstances();
std::vector<uint16_t> unallowed_ports{};
unallowed_ports.reserve(instances.size());
for(const auto& instance : instances_) {
unallowed_ports.insert(instance->properties()[property::VIRTUALSERVER_PORT].as_or<uint16_t>(0));
for(const auto& instance : instances) {
unallowed_ports.push_back(instance->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
auto vserver = instance->getVoiceServer();
if(instance->running() && vserver) {
for(const auto& socket : vserver->getSockets()) {
unallowed_ports.insert(net::port(socket->address()));
for(const auto& binding : vserver->activeBindings()) {
unallowed_ports.push_back(binding->address_port());
}
}
}
@ -196,9 +223,8 @@ uint16_t VirtualServerManager::next_available_port(const std::string& host_strin
if(port < 1024) goto next_port;
for(auto& p : unallowed_ports) {
if(p == port) {
if(p == port)
goto next_port;
}
}
for(auto& binding : bindings) {
@ -210,18 +236,12 @@ uint16_t VirtualServerManager::next_available_port(const std::string& host_strin
switch (net::address_available(baddress, net::binding_type::TCP)) {
case net::binding_result::ADDRESS_USED:
goto next_port;
case net::binding_result::ADDRESS_FREE:
case net::binding_result::INTERNAL_ERROR:
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;
case net::binding_result::ADDRESS_FREE:
case net::binding_result::INTERNAL_ERROR:
default:
break; /* if we've an internal error we ignore it */
}
@ -235,8 +255,7 @@ uint16_t VirtualServerManager::next_available_port(const std::string& host_strin
}
ts::ServerId VirtualServerManager::next_available_server_id(bool& success) {
auto server_id_base = this->handle->properties()[property::SERVERINSTANCE_VIRTUAL_SERVER_ID_INDEX].as_or<ServerId>(0);
/* ensure we're not using 0xFFFF (This is the snapshot server) */
auto server_id_base = this->handle->properties()[property::SERVERINSTANCE_VIRTUAL_SERVER_ID_INDEX].as<ServerId>();
if(server_id_base > 65530) {
success = false;
return 0;
@ -272,14 +291,30 @@ ts::ServerId VirtualServerManager::next_available_server_id(bool& success) {
return serverId;
}
ServerSlotUsageReport VirtualServerManager::instanceSlotUsageReport() {
ServerSlotUsageReport result{};
for(const auto& server : this->serverInstances()) {
if(!server->running()) {
continue;
ServerReport VirtualServerManager::report() {
ServerReport result{};
for(const auto& sr : this->serverInstances()) {
result.avariable++;
if(sr->running()) {
result.online++;
result.slots += sr->properties()[property::VIRTUALSERVER_MAXCLIENTS].as<size_t>();
result.onlineClients += sr->onlineClients();
result.onlineChannels += sr->onlineChannels();
}
result += server->onlineStats();
}
return result;
}
OnlineClientReport VirtualServerManager::clientReport() {
OnlineClientReport result{};
for(const auto& server : this->serverInstances()) {
if(!server->running()) continue;
auto sr = server->onlineStats();
result.bots += sr.bots;
result.queries += sr.queries;
result.clients_web += sr.clients_web;
result.clients_ts += sr.clients_ts;
}
return result;
}
@ -291,6 +326,13 @@ size_t VirtualServerManager::runningServers() {
return res;
}
size_t VirtualServerManager::usedSlots() {
size_t res = 0;
for(const auto& sr : this->serverInstances())
res += sr->properties()[property::VIRTUALSERVER_MAXCLIENTS].as<size_t>();
return res;
}
shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts, uint16_t port) {
bool sid_success = false;
@ -298,7 +340,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
if(!sid_success)
return nullptr;
this->delete_server_in_db(serverId, false); /* just to ensure */
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"});
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;",
@ -315,7 +357,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
server->properties()[property::VIRTUALSERVER_HOST] = hosts;
server->properties()[property::VIRTUALSERVER_PORT] = port;
if(config::server::default_music_bot) {
auto bot = server->music_manager_->createBot(0);
auto bot = server->musicManager->createBot(0);
if(!bot) {
logCritical(server->getServerId(), "Failed to create default music bot!");
}
@ -324,6 +366,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
threads::MutexLock l(this->instanceLock);
this->instances.push_back(server);
}
this->adjust_executor_threads();
return server;
}
@ -341,6 +384,7 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
return s == server;
}), this->instances.end());
}
this->adjust_executor_threads();
if(server->getState() != ServerState::OFFLINE)
server->stop("server deleted", true);
@ -349,7 +393,7 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
cl->close_connection(chrono::system_clock::now());
} else if(cl->getType() == CLIENT_QUERY){
auto qc = dynamic_pointer_cast<QueryClient>(cl);
qc->disconnect_from_virtual_server("server delete");
qc->disconnect_from_virtual_server();
} else if(cl->getType() == CLIENT_MUSIC) {
cl->disconnect("");
cl->currentChannel = nullptr;
@ -369,15 +413,13 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
}
}
{
std::unique_lock state_lock{server->state_mutex};
threads::MutexLock locK(server->stateLock);
server->state = ServerState::DELETING;
}
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].increment_by(server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as_or<uint64_t>(0));
this->delete_server_in_db(server->serverId, false);
this->handle->databaseHelper()->handleServerDelete(server->serverId);
file::server()->unregister_server(server->getServerId(), true);
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
this->delete_server_in_db(server->serverId);
this->handle->getFileServer()->deleteServer(server);
return true;
}
@ -385,7 +427,7 @@ void VirtualServerManager::executeAutostart() {
threads::MutexLock l(this->instanceLock);
auto lastStart = system_clock::time_point();
for(const auto& server : this->instances){
if(!server->running() && server->properties()[property::VIRTUALSERVER_AUTOSTART].as_or<bool>(false)){
if(!server->running() && server->properties()[property::VIRTUALSERVER_AUTOSTART].as<bool>()){
threads::self::sleep_until(lastStart + milliseconds(10)); //Don't start all server at the same point (otherwise all servers would tick at the same moment)
lastStart = system_clock::now();
logMessage(server->getServerId(), "Starting server");
@ -406,9 +448,19 @@ void VirtualServerManager::shutdownAll(const std::string& msg) {
for(const auto &server : this->serverInstances()){
if(server->running()) server->stop(msg, true);
}
this->execute_loop->shutdown();
}
void VirtualServerManager::delete_server_in_db(ts::ServerId server_id, bool data_only) {
void VirtualServerManager::tickHandshakeClients() {
for(const auto& server : this->serverInstances()) {
auto vserver = server->getVoiceServer();
if(vserver)
vserver->tickHandshakingClients();
}
}
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) { \
@ -418,60 +470,21 @@ if(!result) { \
sql::result result{};
if(!data_only) {
execute_delete("DELETE FROM `servers` WHERE `serverId` = :sid");
}
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 `complains` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `letters` 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");
execute_delete("DELETE FROM `conversations` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `conversation_blocks` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `ban_trigger` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `clients_server` WHERE `server_id` = :sid");
}
#define execute_change(table, column) \
result = sql::command(this->handle->getSql(), "UPDATE `" table "` SET `" column "` = :nsid WHERE `" column "` = :osid;", \
variable{":osid", old_id}, variable{":nsid", new_id}).execute(); \
if(!result) { \
logWarning(LOG_INSTANCE, "Failed to execute server id change on table {} (column {}): {}", table, column, result.fmtStr()); \
result = sql::result{}; \
}
void VirtualServerManager::change_server_id_in_db(ts::ServerId old_id, ts::ServerId new_id) {
sql::result result{};
execute_change("tokens", "serverId");
execute_change("properties", "serverId");
execute_change("permissions", "serverId");
execute_change("channels", "serverId");
execute_change("bannedClients", "serverId");
execute_change("groups", "serverId");
execute_change("assignedGroups", "serverId");
execute_change("complains", "serverId");
execute_change("letters", "serverId");
execute_change("musicbots", "serverId");
execute_change("playlists", "serverId");
execute_change("playlist_songs", "serverId");
execute_change("conversations", "server_id");
execute_change("conversation_blocks", "server_id");
execute_change("ban_trigger", "server_id");
execute_change("clients_server", "server_id");
execute_change("servers", "serverId");
}

View File

@ -2,7 +2,8 @@
#include <deque>
#include <EventLoop.h>
#include "src/server/PrecomputedPuzzles.h"
#include "client/voice/PrecomputedPuzzles.h"
#include "server/VoiceIOManager.h"
#include "VirtualServer.h"
#include <query/command3.h>
#include "snapshots/snapshot.h"
@ -10,6 +11,14 @@
namespace ts::server {
class InstanceHandler;
struct ServerReport {
size_t avariable;
size_t online;
size_t slots;
size_t onlineClients;
size_t onlineChannels;
};
class VirtualServerManager {
public:
enum State {
@ -19,16 +28,6 @@ namespace ts::server {
STOPPING
};
enum struct SnapshotDeployResult {
SUCCESS,
REACHED_SOFTWARE_SERVER_LIMIT,
REACHED_CONFIG_SERVER_LIMIT,
REACHED_SERVER_ID_LIMIT,
CUSTOM_ERROR /* error message set */
};
explicit VirtualServerManager(InstanceHandler*);
~VirtualServerManager();
@ -47,19 +46,34 @@ namespace ts::server {
return instances;
}
ServerSlotUsageReport instanceSlotUsageReport();
ServerReport report();
OnlineClientReport clientReport();
size_t runningServers();
size_t usedSlots();
void executeAutostart();
void shutdownAll(const std::string&);
//Don't use shared_ptr references to keep sure that they be hold in memory
//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);
SnapshotDeployResult deploy_snapshot(std::string& /* error */, std::shared_ptr<VirtualServer>& /* target server */, const command_parser& /* source */);
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 */);
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
/* This must be recursive */
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();
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;
State getState() { return this->state; }
@ -70,15 +84,25 @@ namespace ts::server {
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;
struct {
std::thread executor{};
std::condition_variable condition;
std::mutex lock;
} acknowledge;
void delete_server_in_db(ServerId /* server id */, bool /* data only */);
void change_server_id_in_db(ServerId /* old id */, ServerId /* new id */);
void tickHandshakeClients();
bool try_deploy_snapshot(std::string& /* error */, ServerId /* target server id */, ServerId /* logging server id */, const command_parser& /* source */);
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 */);
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,280 +0,0 @@
/*
* Copyright (c) 2019 German Mendez Bravo (Kronuz)
* Copyright (c) 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* A btree::map<> implements the STL unique sorted associative container
* interface and the pair associative container interface (a.k.a map<>) using a
* btree. See btree.h for details of the btree implementation and caveats.
*/
#ifndef BTREE_MAP_H__
#define BTREE_MAP_H__
#include "btree.h"
#include <stdexcept>
namespace btree {
// A common base class for map and safe_map.
template <typename Tree>
class btree_map_container : public btree_unique_container<Tree> {
typedef btree_map_container<Tree> self_type;
typedef btree_unique_container<Tree> super_type;
public:
typedef typename Tree::key_type key_type;
typedef typename Tree::data_type data_type;
typedef typename Tree::value_type value_type;
typedef typename Tree::mapped_type mapped_type;
typedef typename Tree::key_compare key_compare;
typedef typename Tree::allocator_type allocator_type;
typedef typename Tree::iterator iterator;
typedef typename Tree::const_iterator const_iterator;
public:
// Default constructor.
btree_map_container(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(comp, alloc) {
}
// Copy constructor.
btree_map_container(const self_type& x)
: super_type(x) {
}
// Range constructor.
template <class InputIterator>
btree_map_container(InputIterator b, InputIterator e,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(b, e, comp, alloc) {
}
template <typename... Args>
std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) {
return this->__tree.emplace_unique_key_args(key,
std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(std::forward<Args>(args)...));
}
template <typename... Args>
std::pair<iterator, bool> try_emplace(key_type&& key, Args&&... args) {
return this->__tree.emplace_unique_key_args(key,
std::piecewise_construct,
std::forward_as_tuple(std::move(key)),
std::forward_as_tuple(std::forward<Args>(args)...));
}
template <typename... Args>
iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) {
return this->__tree.emplace_hint_unique_key_args(hint, key,
std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(std::forward<Args>(args)...));
}
template <typename... Args>
iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) {
return this->__tree.emplace_hint_unique_key_args(hint, key,
std::piecewise_construct,
std::forward_as_tuple(std::move(key)),
std::forward_as_tuple(std::forward<Args>(args)...));
}
// Access specified element with bounds checking.
mapped_type& at(const key_type& key) {
auto it = this->find(key);
if (it == this->end()) {
throw std::out_of_range("map::at: key not found");
}
return it->second;
}
const mapped_type& at(const key_type& key) const {
auto it = this->find(key);
if (it == this->end()) {
throw std::out_of_range("map::at: key not found");
}
return it->second;
}
// Insertion routines.
data_type& operator[](const key_type& key) {
return this->try_emplace(key).first->second;
}
data_type& operator[](key_type&& key) {
return this->try_emplace(std::move(key)).first->second;
}
};
// The map class is needed mainly for its constructors.
template <typename Key, typename Value,
typename Compare = std::less<Key>,
typename Alloc = std::allocator<std::pair<const Key, Value>>,
int TargetNodeSize = 256>
class map : public btree_map_container<
btree<btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize>>> {
typedef map<Key, Value, Compare, Alloc, TargetNodeSize> self_type;
typedef btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize> params_type;
typedef btree<params_type> btree_type;
typedef btree_map_container<btree_type> super_type;
public:
typedef typename btree_type::key_compare key_compare;
typedef typename btree_type::allocator_type allocator_type;
public:
// Default constructor.
map(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(comp, alloc) {
}
// Copy constructor.
map(const self_type& x)
: super_type(x) {
}
// Range constructor.
template <class InputIterator>
map(InputIterator b, InputIterator e,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(b, e, comp, alloc) {
}
};
} // namespace btree
template <typename K, typename V, typename C, typename A, int N>
bool operator==(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
template <typename K, typename V, typename C, typename A, int N>
bool operator<(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename K, typename V, typename C, typename A, int N>
bool operator!=(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return !(lhs == rhs);
}
template <typename K, typename V, typename C, typename A, int N>
bool operator>(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return rhs < lhs;
}
template <typename K, typename V, typename C, typename A, int N>
bool operator>=(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return !(lhs < rhs);
}
template <typename K, typename V, typename C, typename A, int N>
bool operator<=(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return !(rhs < lhs);
}
template <typename K, typename V, typename C, typename A, int N>
inline void swap(btree::map<K, V, C, A, N>& x, btree::map<K, V, C, A, N>& y) {
x.swap(y);
}
namespace btree {
// The multimap class is needed mainly for its constructors.
template <typename Key, typename Value,
typename Compare = std::less<Key>,
typename Alloc = std::allocator<std::pair<const Key, Value> >,
int TargetNodeSize = 256>
class multimap : public btree_multi_container<
btree<btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize> > > {
typedef multimap<Key, Value, Compare, Alloc, TargetNodeSize> self_type;
typedef btree_map_params< Key, Value, Compare, Alloc, TargetNodeSize> params_type;
typedef btree<params_type> btree_type;
typedef btree_multi_container<btree_type> super_type;
public:
typedef typename btree_type::key_compare key_compare;
typedef typename btree_type::allocator_type allocator_type;
typedef typename btree_type::data_type data_type;
typedef typename btree_type::mapped_type mapped_type;
public:
// Default constructor.
multimap(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(comp, alloc) {
}
// Copy constructor.
multimap(const self_type& x)
: super_type(x) {
}
// Range constructor.
template <class InputIterator>
multimap(InputIterator b, InputIterator e,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(b, e, comp, alloc) {
}
};
} // namespace btree
template <typename K, typename V, typename C, typename A, int N>
bool operator==(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
template <typename K, typename V, typename C, typename A, int N>
bool operator<(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename K, typename V, typename C, typename A, int N>
bool operator!=(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return !(lhs == rhs);
}
template <typename K, typename V, typename C, typename A, int N>
bool operator>(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return rhs < lhs;
}
template <typename K, typename V, typename C, typename A, int N>
bool operator>=(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return !(lhs < rhs);
}
template <typename K, typename V, typename C, typename A, int N>
bool operator<=(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return !(rhs < lhs);
}
template <typename K, typename V, typename C, typename A, int N>
inline void swap(btree::multimap<K, V, C, A, N>& x, btree::multimap<K, V, C, A, N>& y) {
x.swap(y);
}
#endif // BTREE_MAP_H__

View File

@ -1,183 +0,0 @@
/*
* Copyright (c) 2019 German Mendez Bravo (Kronuz)
* Copyright (c) 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* A btree::set<> implements the STL unique sorted associative container
* interface (a.k.a set<>) using a btree. See btree.h for details of the btree
* implementation and caveats.
*/
#ifndef BTREE_SET_H__
#define BTREE_SET_H__
#include "btree.h"
namespace btree {
// The set class is needed mainly for its constructors.
template <typename Key,
typename Compare = std::less<Key>,
typename Alloc = std::allocator<Key>,
int TargetNodeSize = 256>
class set : public btree_unique_container<
btree<btree_set_params<Key, Compare, Alloc, TargetNodeSize> > > {
typedef set<Key, Compare, Alloc, TargetNodeSize> self_type;
typedef btree_set_params<Key, Compare, Alloc, TargetNodeSize> params_type;
typedef btree<params_type> btree_type;
typedef btree_unique_container<btree_type> super_type;
public:
typedef typename btree_type::key_compare key_compare;
typedef typename btree_type::allocator_type allocator_type;
public:
// Default constructor.
set(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(comp, alloc) {
}
// Copy constructor.
set(const self_type& x)
: super_type(x) {
}
// Range constructor.
template <class InputIterator>
set(InputIterator b, InputIterator e,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(b, e, comp, alloc) {
}
};
} // namespace btree
template <typename K, typename C, typename A, int N>
bool operator==(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
template <typename K, typename C, typename A, int N>
bool operator<(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename K, typename C, typename A, int N>
bool operator!=(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return !(lhs == rhs);
}
template <typename K, typename C, typename A, int N>
bool operator>(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return rhs < lhs;
}
template <typename K, typename C, typename A, int N>
bool operator>=(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return !(lhs < rhs);
}
template <typename K, typename C, typename A, int N>
bool operator<=(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return !(rhs < lhs);
}
template <typename K, typename C, typename A, int N>
inline void swap(btree::set<K, C, A, N>& x, btree::set<K, C, A, N>& y) {
x.swap(y);
}
namespace btree {
// The multiset class is needed mainly for its constructors.
template <typename Key,
typename Compare = std::less<Key>,
typename Alloc = std::allocator<Key>,
int TargetNodeSize = 256>
class multiset : public btree_multi_container<
btree<btree_set_params<Key, Compare, Alloc, TargetNodeSize> > > {
typedef multiset<Key, Compare, Alloc, TargetNodeSize> self_type;
typedef btree_set_params<Key, Compare, Alloc, TargetNodeSize> params_type;
typedef btree<params_type> btree_type;
typedef btree_multi_container<btree_type> super_type;
public:
typedef typename btree_type::key_compare key_compare;
typedef typename btree_type::allocator_type allocator_type;
public:
// Default constructor.
multiset(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(comp, alloc) {
}
// Copy constructor.
multiset(const self_type& x)
: super_type(x) {
}
// Range constructor.
template <class InputIterator>
multiset(InputIterator b, InputIterator e,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(b, e, comp, alloc) {
}
};
} // namespace btree
template <typename K, typename C, typename A, int N>
bool operator==(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
template <typename K, typename C, typename A, int N>
bool operator<(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename K, typename C, typename A, int N>
bool operator!=(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return !(lhs == rhs);
}
template <typename K, typename C, typename A, int N>
bool operator>(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return rhs < lhs;
}
template <typename K, typename C, typename A, int N>
bool operator>=(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return !(lhs < rhs);
}
template <typename K, typename C, typename A, int N>
bool operator<=(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return !(rhs < lhs);
}
template <typename K, typename C, typename A, int N>
inline void swap(btree::multiset<K, C, A, N>& x, btree::multiset<K, C, A, N>& y) {
x.swap(y);
}
#endif // BTREE_SET_H__

View File

@ -8,7 +8,7 @@ namespace build {
enum BuildType {
STABLE,
BETA,
NIGHTLY,
ALPHA,
PRIVATE
};

View File

@ -98,7 +98,7 @@ std::shared_ptr<ViewEntry> ClientChannelView::find_channel(const std::shared_ptr
while(heads.front()) {
auto parent = heads.front()->parent();
if(!parent && heads.front()->properties()[property::CHANNEL_PID].as_or<ChannelId>(0) != 0) {
if(!parent && heads.front()->properties()[property::CHANNEL_PID].as<ChannelId>() != 0) {
head = this->find_linked_entry(channel->channelId(), nullptr);//We're searching for a deleted head! So lets iterate over everything
deep_search = true;
break;
@ -123,10 +123,11 @@ std::shared_ptr<ViewEntry> ClientChannelView::find_channel(const std::shared_ptr
return head ? static_pointer_cast<ViewEntry>(head->entry) : nullptr;
}
std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::insert_channels(shared_ptr<TreeView::LinkedTreeEntry> head, bool test_permissions, bool first_only) {
std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::insert_channels(shared_ptr<TreeView::LinkedTreeEntry> head, bool test_permissions, bool first_only, std::shared_ptr<server::CalculateCache> cache) {
std::deque<std::shared_ptr<ViewEntry>> result;
bool has_perm = !test_permissions || permission::v2::permission_granted(1, owner->calculate_permission(permission::b_channel_ignore_view_power, 0, false));
if(!cache && test_permissions) cache = make_shared<CalculateCache>();
bool has_perm = !test_permissions || permission::v2::permission_granted(1, owner->calculate_permission(permission::b_channel_ignore_view_power, 0, false, cache));
bool first = true;
while(head) {
if(!first && first_only) break;
@ -135,7 +136,7 @@ std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::insert_channels(shared
auto channel = dynamic_pointer_cast<BasicChannel>(head->entry);
if(this->channel_visible(channel)) {
if(head->child_head) {
for(const auto& sub : this->insert_channels(head->child_head, test_permissions, false))
for(const auto& sub : this->insert_channels(head->child_head, test_permissions, false, cache))
result.push_back(sub);
}
@ -171,7 +172,7 @@ std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::insert_channels(shared
continue;
};
auto now_prv = this->find_channel(entry->previousChannelId());
logTrace(this->getServerId(), "{}[CHANNELS] Insert channel {} ({}) after {} ({}). Original view prv: {} ({}). Original prv: {} ({})",
debugMessage(this->getServerId(), "{}[CHANNELS] Insert channel {} ({}) after {} ({}). Original view prv: {} ({}). Original prv: {} ({})",
CLIENT_STR_LOG_PREFIX_(this->owner),
channel->channelId(), channel->name(),
entry->previousChannelId(), now_prv ? now_prv->channel()->name() : "",
@ -182,7 +183,7 @@ std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::insert_channels(shared
result.push_back(entry);
if(head->child_head) {
for(const auto& sub : this->insert_channels(head->child_head, test_permissions, false))
for(const auto& sub : this->insert_channels(head->child_head, test_permissions, false, cache))
result.push_back(sub);
}
head = head->next;
@ -224,7 +225,7 @@ std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::show_channel(std::shar
remote_previous = remote_previous->previous;
}
auto previous_channel = previous ? previous->channel() : nullptr; //weak could be may nullptr
logTrace(this->getServerId(), "{}[CHANNELS] Insert channel {} ({}) after {} ({})",
debugMessage(this->getServerId(), "{}[CHANNELS] Insert channel {} ({}) after {} ({})",
CLIENT_STR_LOG_PREFIX_(this->owner),
channel->channelId(), channel->name(),
previous ? previous->channelId() : 0, previous_channel ? previous_channel->name() : ""
@ -243,9 +244,11 @@ std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::show_channel(std::shar
}
std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::test_channel(std::shared_ptr<ts::TreeView::LinkedTreeEntry> l_old,
std::shared_ptr<ts::TreeView::LinkedTreeEntry> channel_new) {
std::shared_ptr<ts::TreeView::LinkedTreeEntry> channel_new, shared_ptr<CalculateCache> cache) {
if(!cache) cache = make_shared<CalculateCache>();
std::deque<std::shared_ptr<ViewEntry>> result;
bool has_perm = permission::v2::permission_granted(1, owner->calculate_permission(permission::b_channel_ignore_view_power, 0, false));
bool has_perm = permission::v2::permission_granted(1, owner->calculate_permission(permission::b_channel_ignore_view_power, 0, false, cache));
if(has_perm) return {};
deque<shared_ptr<TreeView::LinkedTreeEntry>> parents = {l_old};
@ -281,13 +284,14 @@ std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::test_channel(std::shar
}
std::deque<std::pair<bool, std::shared_ptr<ViewEntry>>> ClientChannelView::update_channel(
std::shared_ptr<ts::TreeView::LinkedTreeEntry> l_channel, std::shared_ptr<ts::TreeView::LinkedTreeEntry> l_own) {
return update_channel_path(std::move(l_channel), std::move(l_own), 1);
std::shared_ptr<ts::TreeView::LinkedTreeEntry> l_channel, std::shared_ptr<ts::TreeView::LinkedTreeEntry> l_own, shared_ptr<CalculateCache> cache) {
return update_channel_path(std::move(l_channel), std::move(l_own), std::move(cache), 1);
}
std::deque<std::pair<bool, std::shared_ptr<ViewEntry>>> ClientChannelView::update_channel_path(std::shared_ptr<ts::TreeView::LinkedTreeEntry> l_channel, std::shared_ptr<ts::TreeView::LinkedTreeEntry> l_own, ssize_t length) {
std::deque<std::pair<bool, std::shared_ptr<ViewEntry>>> ClientChannelView::update_channel_path(std::shared_ptr<ts::TreeView::LinkedTreeEntry> l_channel, std::shared_ptr<ts::TreeView::LinkedTreeEntry> l_own, shared_ptr<CalculateCache> cache, ssize_t length) {
if(!cache) cache = make_shared<CalculateCache>();
std::deque<std::pair<bool, std::shared_ptr<ViewEntry>>> result;
bool has_perm = permission::v2::permission_granted(1, owner->calculate_permission(permission::b_channel_ignore_view_power, 0, false));
bool has_perm = permission::v2::permission_granted(1, owner->calculate_permission(permission::b_channel_ignore_view_power, 0, false, cache));
while(l_channel && length-- != 0) {
auto b_channel = dynamic_pointer_cast<BasicChannel>(l_channel->entry);
@ -309,20 +313,20 @@ std::deque<std::pair<bool, std::shared_ptr<ViewEntry>>> ClientChannelView::updat
if(visible) {
for(const auto& entry : this->show_channel(l_channel, visible))
result.emplace_back(true, entry);
for(const auto& entry : this->insert_channels(l_channel->child_head, true, false))
for(const auto& entry : this->insert_channels(l_channel->child_head, true, false, cache))
result.emplace_back(true, entry);
}
l_channel = l_channel->next;
continue; /* all subchannels had been checked */
} else if(visible && !has_perm) {
for(const auto& entry : this->test_channel(l_channel, l_own))
for(const auto& entry : this->test_channel(l_channel, l_own, cache))
result.emplace_back(false, entry);
}
//Root node is okey, test children
if(l_channel->child_head) {
auto entries = this->update_channel_path(l_channel->child_head, l_own, -1);
auto entries = this->update_channel_path(l_channel->child_head, l_own, cache, -1);
result.insert(result.end(), entries.begin(), entries.end());
}

View File

@ -56,7 +56,8 @@ namespace ts {
std::deque<std::shared_ptr<ViewEntry>> insert_channels(
std::shared_ptr<TreeView::LinkedTreeEntry> /* head */,
bool test_permissions,
bool first_only
bool first_only,
std::shared_ptr<server::CalculateCache> cache = nullptr
);
/* shows the specific channel and their parents */
@ -68,19 +69,22 @@ namespace ts {
/* remove invalid channel */
std::deque<std::shared_ptr<ViewEntry>> test_channel(
std::shared_ptr<TreeView::LinkedTreeEntry> /* old channel */,
std::shared_ptr<TreeView::LinkedTreeEntry> /* new channel */
std::shared_ptr<TreeView::LinkedTreeEntry> /* new channel */,
std::shared_ptr<server::CalculateCache> cache = nullptr
);
/* [{ add := true | delete := false, channel}] */
std::deque<std::pair<bool, std::shared_ptr<ViewEntry>>> update_channel(
std::shared_ptr<TreeView::LinkedTreeEntry> /* channel */,
std::shared_ptr<TreeView::LinkedTreeEntry> /* own channel */
std::shared_ptr<TreeView::LinkedTreeEntry> /* own channel */,
std::shared_ptr<server::CalculateCache> cache = nullptr
);
/* [{ add := true | delete := false, channel}] */
std::deque<std::pair<bool, std::shared_ptr<ViewEntry>>> update_channel_path(
std::shared_ptr<TreeView::LinkedTreeEntry> /* channel */,
std::shared_ptr<TreeView::LinkedTreeEntry> /* own channel */,
std::shared_ptr<server::CalculateCache> cache = nullptr,
ssize_t length = -1
);

View File

@ -2,20 +2,20 @@
#include <log/LogUtils.h>
#include <misc/memtracker.h>
#include <misc/sassert.h>
#include <misc/utf8.h>
#include "misc/rnd.h"
#include "src/VirtualServer.h"
#include "src/client/ConnectedClient.h"
#include "src/server/file/LocalFileServer.h"
#include "src/InstanceHandler.h"
#include "../manager/ConversationManager.h"
#include "../groups/GroupManager.h"
using namespace std;
using namespace ts;
using namespace ts::server;
ServerChannel::ServerChannel(uint32_t rtc_channel_id, ChannelId parentId, ChannelId channelId) : BasicChannel(parentId, channelId),
rtc_channel_id{rtc_channel_id} {
extern InstanceHandler* serverInstance;
ServerChannel::ServerChannel(ChannelId parentId, ChannelId channelId) : BasicChannel(parentId, channelId) {
memtrack::allocated<ServerChannel>(this);
}
@ -56,23 +56,22 @@ size_t ServerChannel::client_count() {
return result;
}
void ServerChannel::setProperties(const std::shared_ptr<PropertyManager> &ptr) {
void ServerChannel::setProperties(const std::shared_ptr<Properties> &ptr) {
BasicChannel::setProperties(ptr);
}
ServerChannelTree::ServerChannelTree(const std::shared_ptr<server::VirtualServer>& server, sql::SqlManager* sql) : sql(sql), server_ref(server) { }
ServerChannelTree::ServerChannelTree(const std::shared_ptr<server::VirtualServer>& server, sql::SqlManager* sql) : sql(sql), server(server) { }
ServerChannelTree::~ServerChannelTree() { }
void ServerChannelTree::deleteSemiPermanentChannels() {
loop:
for(const auto& ch : this->channels()) {
for(const auto& ch : this->channels())
if(ch->channelType() == ChannelType::semipermanent || ch->channelType() == ChannelType::temporary){ //We also delete private channels
this->delete_channel_root(ch);
goto loop;
}
}
}
ChannelId ServerChannelTree::generateChannelId() {
@ -92,16 +91,12 @@ std::shared_ptr<BasicChannel> ServerChannelTree::createChannel(ChannelId parentI
std::shared_ptr<BasicChannel> channel = BasicChannelTree::createChannel(parentId, orderId, name);
if(!channel) return channel;
/* TODO: Speed up (skip the database query) */
auto properties = serverInstance->databaseHelper()->loadChannelProperties(this->server_ref.lock(), channel->channelId());
for(const auto& prop : channel->properties()->list_properties()) {
if(prop.isModified()) { //Copy the already set properties
auto properties = serverInstance->databaseHelper()->loadChannelProperties(this->server.lock(), channel->channelId());
for(const auto& prop : channel->properties().list_properties())
if(prop.isModified()) //Copy the already set properties
(*properties)[prop.type()] = prop.value();
}
}
static_pointer_cast<ServerChannel>(channel)->setProperties(properties);
static_pointer_cast<ServerChannel>(channel)->setPermissionManager(serverInstance->databaseHelper()->loadChannelPermissions(this->server_ref.lock(), channel->channelId()));
static_pointer_cast<ServerChannel>(channel)->setPermissionManager(serverInstance->databaseHelper()->loadChannelPermissions(this->server.lock(), channel->channelId()));
channel->properties()[property::CHANNEL_CREATED_AT] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
channel->properties()[property::CHANNEL_LAST_LEFT] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
@ -127,7 +122,7 @@ inline std::shared_ptr<TreeView::LinkedTreeEntry> findLinkedChannelByPool(const
}
ServerId ServerChannelTree::getServerId() {
auto s = this->server_ref.lock();
auto s = this->server.lock();
return s ? s->getServerId() : 0UL;
}
@ -137,12 +132,11 @@ bool ServerChannelTree::initializeTempParents() {
auto channel = dynamic_pointer_cast<BasicChannel>(linked_channel->entry);
assert(channel);
if(channel->properties()[property::CHANNEL_PID].as_or<ChannelId>(0) != 0){
if(channel->properties().hasProperty(property::CHANNEL_PID) && channel->properties()[property::CHANNEL_PID].as<ChannelId>() != 0){
if(!channel->parent())
linked_channel->parent = findLinkedChannelByPool(this->tmpChannelList, channel->properties()[property::CHANNEL_PID]);
if(!channel->parent()){
logError(this->getServerId(), "Invalid channel parent (Channel does not exists). Channel id: {} ({}) Missing parent id: {}", channel->channelId(), channel->name(),
channel->properties()[property::CHANNEL_PID].as_or<ChannelId>(0));
logError(this->getServerId(), "Invalid channel parent (Channel does not exists). Channel id: {} ({}) Missing parent id: {}", channel->channelId(), channel->name(), channel->properties()[property::CHANNEL_PID].as<ChannelId>());
logError(this->getServerId(), "Resetting parent");
channel->properties()[property::CHANNEL_PID] = 0;
}
@ -317,7 +311,7 @@ inline std::shared_ptr<TreeView::LinkedTreeEntry> buildChannelTree(ServerId serv
}
auto evaluated_parent_id = channel->parent() ? channel->parent()->channelId() : 0;
if(evaluated_parent_id != channel->properties()[property::CHANNEL_PID].as_or<ChannelId>(0)) {
if(evaluated_parent_id != channel->properties()[property::CHANNEL_PID].as<ChannelId>()) {
debugMessage(serverId, "Fixed parent id for channel {} ({}). New parent channel {}", entry->entry->channelId(), channel->name(), evaluated_parent_id);
channel->properties()[property::CHANNEL_PID] = evaluated_parent_id;
}
@ -427,8 +421,8 @@ bool ServerChannelTree::validateChannelNames() {
}
*/
for(const auto &channel : this->channels()) {
auto name_length = utf8::count_characters(channel->name());
for(const auto &channel : this->channels()){
auto name_length = count_characters(channel->name());
if(name_length > 40) {
logError(this->getServerId(), "Channel {} loaded an invalid name from the database (name to long). Cutting channel name");
channel->properties()[property::CHANNEL_NAME] = channel->name().substr(0, 40); //FIXME count UTF8
@ -485,7 +479,6 @@ bool ServerChannelTree::validateChannelNames() {
}
bool ServerChannelTree::validateChannelIcons() {
#if 0
for(const auto &channel : this->channels()) {
auto iconId = (IconId) channel->properties()[property::CHANNEL_ICON_ID];
if(iconId != 0 && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) {
@ -496,7 +489,6 @@ bool ServerChannelTree::validateChannelIcons() {
}
}
}
#endif
return true;
}
@ -536,19 +528,9 @@ int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column)
//assert(type != 0xFF);
assert(channelId != 0);
if(channelId == 0)
return 0;
auto server = this->server_ref.lock();
std::shared_ptr<ServerChannel> channel;
if(server) {
auto rtc_channel_id = server->rtc_server().create_channel();
channel = std::make_shared<ServerChannel>(rtc_channel_id, parentId, channelId);
} else {
channel = std::make_shared<ServerChannel>(0, parentId, channelId);
}
auto channel = std::make_shared<ServerChannel>(parentId, channelId);
auto server = this->server.lock();
static_pointer_cast<ServerChannel>(channel)->setProperties(serverInstance->databaseHelper()->loadChannelProperties(server, channelId));
static_pointer_cast<ServerChannel>(channel)->setPermissionManager(serverInstance->databaseHelper()->loadChannelPermissions(server, channel->channelId()));
@ -559,30 +541,24 @@ int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column)
}
deque<ChannelId> ServerChannelTree::deleteChannelRoot(const std::shared_ptr<BasicChannel> &channel) {
auto server = this->server_ref.lock();
auto server = this->server.lock();
auto channels = this->delete_channel_root(channel);
deque<ChannelId> channel_ids;
for(const auto& channel : channels) {
for(const auto& channel : channels)
channel_ids.push_back(channel->channelId());
}
return channel_ids;
}
void ServerChannelTree::on_channel_entry_deleted(const shared_ptr<BasicChannel> &channel) {
BasicChannelTree::on_channel_entry_deleted(channel);
auto server_channel = dynamic_pointer_cast<ServerChannel>(channel);
assert(server_channel);
auto server = this->server_ref.lock();
auto server = this->server.lock();
if(server) {
server->group_manager()->assignments().handle_channel_deleted(channel->channelId());
server->getGroupManager()->handleChannelDeleted(channel->channelId());
server->conversation_manager()->delete_conversation(channel->channelId());
server->rtc_server().destroy_channel(server_channel->rtc_channel_id);
} else {
serverInstance->group_manager()->assignments().handle_channel_deleted(channel->channelId());
}
} else
serverInstance->getGroupManager()->handleChannelDeleted(channel->channelId());
auto sql_result = sql::command(this->sql, "DELETE FROM `channels` WHERE `serverId` = '" + to_string(this->getServerId()) + "' AND `channelId` = '" + to_string(channel->channelId()) + "'").execute();
@ -591,19 +567,11 @@ void ServerChannelTree::on_channel_entry_deleted(const shared_ptr<BasicChannel>
sql_result = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = '" + to_string(this->getServerId()) + "' AND `id` = '" + to_string(channel->channelId()) + "' AND `type` = " + to_string(property::PropertyType::PROP_TYPE_CHANNEL)).execute();
LOG_SQL_CMD(sql_result);
serverInstance->databaseHelper()->deleteChannelPermissions(this->server_ref.lock(), channel->channelId());
serverInstance->databaseHelper()->deleteChannelPermissions(this->server.lock(), channel->channelId());
sql_result = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = '" + to_string(this->getServerId()) + "' AND `channelId` = '" + to_string(channel->channelId()) + "'").execute();
LOG_SQL_CMD(sql_result);
}
std::shared_ptr<BasicChannel> ServerChannelTree::allocateChannel(const shared_ptr<BasicChannel> &parent, ChannelId channelId) {
auto server = this->server_ref.lock();
auto parent_channel_id = parent ? parent->channelId() : 0;
if(server) {
auto rtc_channel_id = server->rtc_server().create_channel();
return std::make_shared<ServerChannel>(rtc_channel_id, parent_channel_id, channelId);
} else {
return std::make_shared<ServerChannel>(0, parent_channel_id, channelId);
}
return std::make_shared<ServerChannel>(parent ? parent->channelId() : 0, channelId);
}

View File

@ -1,11 +1,11 @@
#pragma once
#include <stdint.h>
#include <cstdlib>
#include "Properties.h"
#include "PermissionManager.h"
#include "BasicChannel.h"
#include "../Group.h"
#include "../rtc/lib.h"
#include <memory>
#include <sql/SqlQuery.h>
@ -19,12 +19,10 @@ namespace ts {
class ServerChannel : public BasicChannel {
friend class ServerChannelTree;
public:
ServerChannel(uint32_t rtc_channel_id, ChannelId parentId, ChannelId channelId);
~ServerChannel() override;
ServerChannel(ChannelId parentId, ChannelId channelId);
void setProperties(const std::shared_ptr<PropertyManager> &ptr) override;
uint32_t rtc_channel_id;
~ServerChannel();
void setProperties(const std::shared_ptr<Properties> &ptr) override;
std::shared_mutex client_lock;
std::deque<std::weak_ptr<server::ConnectedClient>> clients;
@ -39,24 +37,24 @@ namespace ts {
class ServerChannelTree : public BasicChannelTree {
public:
ServerChannelTree(const std::shared_ptr<server::VirtualServer>&, sql::SqlManager*);
~ServerChannelTree() override;
virtual ~ServerChannelTree();
void loadChannelsFromDatabase();
std::shared_ptr<BasicChannel> createChannel(ChannelId parentId, ChannelId orderId, const std::string &name) override;
virtual std::shared_ptr<BasicChannel> createChannel(ChannelId parentId, ChannelId orderId, const std::string &name) override;
virtual std::deque<ChannelId> deleteChannelRoot(const std::shared_ptr<BasicChannel> &channel);
void deleteSemiPermanentChannels();
std::shared_ptr<LinkedTreeEntry> tree_head() { return this->head; }
protected:
ChannelId generateChannelId() override;
virtual ChannelId generateChannelId() override;
void on_channel_entry_deleted(const std::shared_ptr<BasicChannel> &channel) override;
virtual void on_channel_entry_deleted(const std::shared_ptr<BasicChannel> &channel) override;
std::shared_ptr<BasicChannel> allocateChannel(const std::shared_ptr<BasicChannel> &parent, ChannelId channelId) override;
private:
std::weak_ptr<server::VirtualServer> server_ref;
std::weak_ptr<server::VirtualServer> server;
ServerId getServerId();
sql::SqlManager* sql;

File diff suppressed because it is too large Load Diff

View File

@ -4,22 +4,16 @@
#include <misc/net.h>
#include <cstdint>
#include <src/music/PlayablePlaylist.h>
#include <misc/task_executor.h>
#include <misc/sassert.h>
#include "music/Song.h"
#include "../channel/ClientChannelView.h"
#include "DataClient.h"
#include "query/command3.h"
#define CLIENT_STR_LOG_PREFIX_(this) (this->getLoggingPrefix())
#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)
#define CMD_REQ_SERVER \
do { \
if(!this->server) { \
return command_result{error::server_invalid_id}; \
} \
} while(0)
if(!this->server) return command_result{error::server_invalid_id};
/* TODO: Play lock the server here with read? So the client dosn't get kicked within that moment */
#define CMD_REF_SERVER(variable_name) \
@ -39,15 +33,14 @@ if(!cmd[0].has(parm)) return command_result{error::parameter_not_found};
//the message here is show to the manager!
#define CMD_CHK_AND_INC_FLOOD_POINTS(num) \
do {\
this->increaseFloodPoints(num); \
if(this->shouldFloodBlock()) return command_result{error::ban_flooding}; \
} while(0)
this->increaseFloodPoints(num); \
if(this->shouldFloodBlock()) return command_result{error::ban_flooding};
#define CMD_CHK_PARM_COUNT(count) \
if(cmd.bulkCount() != count) return command_result{error::parameter_invalid_count};
namespace ts {
class GroupManager;
namespace connection {
class VoiceClientConnection;
}
@ -58,12 +51,6 @@ namespace ts {
class WebClient;
class MusicClient;
namespace groups {
class Group;
class ServerGroup;
class ChannelGroup;
}
struct ConnectionInfoData {
std::chrono::time_point<std::chrono::system_clock> timestamp;
std::map<std::string, std::string> properties;
@ -71,6 +58,7 @@ namespace ts {
class ConnectedClient : public DataClient {
friend class VirtualServer;
friend class VoiceServer;
friend class VoiceClient;
friend class MusicClient;
friend class WebClient;
@ -80,6 +68,7 @@ namespace ts {
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);
@ -87,14 +76,10 @@ namespace ts {
ConnectionState 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 net::to_string(this->remote_address, false); }
uint16_t getPeerPort(){ return net::port(this->remote_address); }
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); }
std::string getHardwareId(){ return properties()[property::CLIENT_HARDWARE_ID]; }
[[nodiscard]] inline std::string getLoggingPrefix() {
return std::string{"["} + this->getLoggingPeerIp() + ":" + std::to_string(this->getPeerPort()) + "/" + this->getDisplayName() + " | " + std::to_string(this->getClientId()) + "]";
}
//General connection stuff
bool isAddressV4() { return this->remote_address.ss_family == AF_INET; }
const sockaddr_in* getAddressV4(){ return (sockaddr_in*) &this->remote_address; }
@ -112,38 +97,37 @@ namespace ts {
inline std::shared_ptr<BasicChannel> getChannel(){ return this->currentChannel; }
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; }
/* If called without locking the client channel tree **must** be write locked */
void subscribeChannel(const std::deque<std::shared_ptr<BasicChannel>>& target, bool /* lock server and client channel tree */, bool /* enforce */);
/* If called without locking the client channel tree **must** be write locked */
void unsubscribeChannel(const std::deque<std::shared_ptr<BasicChannel>>& target, bool /* lock server and client channel tree */);
//bool channelSubscribed(const std::shared_ptr<BasicChannel>&);
/* if lock_channel == false then channel_lock must be write locked! */
std::deque<std::shared_ptr<BasicChannel>> subscribeChannel(const std::deque<std::shared_ptr<BasicChannel>>& target, bool lock_channel, bool /* enforce */);
/* if lock_channel == false then channel_lock must be write locked! */
std::deque<std::shared_ptr<BasicChannel>> unsubscribeChannel(const std::deque<std::shared_ptr<BasicChannel>>& target, bool lock_channel);
bool isClientVisible(const std::shared_ptr<ConnectedClient>&, bool /* lock channel lock */);
inline std::deque<std::weak_ptr<ConnectedClient>> getVisibleClients(bool lock_channel) {
std::shared_lock lock(this->channel_tree_mutex, std::defer_lock);
if(lock_channel) {
inline std::deque<std::weak_ptr<ConnectedClient>> getVisibleClients(bool lock_channel){
std::shared_lock lock(this->channel_lock, std::defer_lock);
if(lock_channel)
lock.lock();
}
assert(mutex_shared_locked(this->channel_tree_mutex));
return this->visibleClients;
}
/** Notifies general stuff **/
virtual bool notifyError(const command_result&, const std::string& retCode = "");
virtual void writeCommandResult(ts::command_builder&, const command_result&, const std::string& errorCodeKey = "id");
/** Notifies (after request) */
bool sendNeededPermissions(bool /* force an update */); /* invoke this because it dosn't spam the client */
virtual bool notifyClientNeededPermissions();
virtual bool notifyGroupPermList(const std::shared_ptr<groups::Group>&, bool);
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 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>> &);
virtual bool notifyChannelUnsubscribed(const std::deque<std::shared_ptr<BasicChannel>> &);
virtual bool notifyServerGroupList(std::optional<ts::command_builder>& /* generated notify */, bool /* as notify */);
virtual bool notifyChannelGroupList(std::optional<ts::command_builder>& /* generated notify */, bool /* as notify */);
/** Notifies (without request) */
//Group server
virtual bool notifyServerUpdated(std::shared_ptr<ConnectedClient>);
@ -163,44 +147,20 @@ namespace ts {
inline void sendChannelMessage(const std::shared_ptr<ConnectedClient>& sender, const std::string& textMessage){
this->notifyTextMessage(ChatMessageMode::TEXTMODE_CHANNEL, sender, this->currentChannel ? this->currentChannel->channelId() : 0, 0, std::chrono::system_clock::now(), textMessage);
}
/**
* Notify the client that a client has received a new server group.
* If the target client isn't visible no notify will be send.
*
* Note:
* 1. This method will lock the channel tree in shared mode!
* 2. For TS3 clients if the client isn't in view the client will disconnect.
*/
virtual bool notifyServerGroupClientAdd(std::optional<ts::command_builder>& /* generated notify */, const std::shared_ptr<ConnectedClient> &/* invoker */, const std::shared_ptr<ConnectedClient> &/* target client */, const GroupId& /* group id */);
/**
* Notify the client that a client has removed a server group.
* If the target client isn't visible no notify will be send.
*
* Note:
* 1. This method will lock the channel tree in shared mode!
* 2. For TS3 clients if the client isn't in view the client will disconnect.
*/
virtual bool notifyServerGroupClientRemove(std::optional<ts::command_builder>& /* generated notify */, const std::shared_ptr<ConnectedClient> &/* invoker */, const std::shared_ptr<ConnectedClient> &/* target client */, const GroupId& /* group id */);
/**
* Notify that a client has received a new channel group.
* If the target client isn't visible no notify will be send.
*
* Note:
* 1. This method will lock the channel tree in shared mode!
* 2. For TS3 clients if the client isn't in view the client will disconnect.
*/
//Group Client Groups
virtual bool notifyServerGroupClientAdd(const std::shared_ptr<ConnectedClient> &invoker, const std::shared_ptr<ConnectedClient> &client, const std::shared_ptr<Group> &group);
virtual bool notifyServerGroupClientRemove(std::shared_ptr<ConnectedClient> invoker, std::shared_ptr<ConnectedClient> client, std::shared_ptr<Group> group);
/* invalid client id causes error: invalid clientID */
/* invalid channel id causes error: invalid channelID */
/* an invalid channel or not a client's channel causes: invalid channelID */
virtual bool notifyClientChannelGroupChanged(
std::optional<ts::command_builder>& /* generated notify */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
const std::shared_ptr<ConnectedClient>& /* target client */,
const ChannelId& /* channel */,
const ChannelId& /* inherited channel */,
const GroupId& /* group id */
const std::shared_ptr<ConnectedClient>& invoker,
const std::shared_ptr<ConnectedClient>& client,
const std::shared_ptr<BasicChannel>& /*channel*/,
const std::shared_ptr<BasicChannel>& /*inherited channel*/,
const std::shared_ptr<Group>& group,
bool lock_channel_tree /* server channel tree AND client tree must be at least read locked! */
);
//Group channel
virtual bool notifyChannelMoved(const std::shared_ptr<BasicChannel> &channel, ChannelId order, const std::shared_ptr<ConnectedClient> &invoker);
virtual bool notifyChannelDescriptionChanged(std::shared_ptr<BasicChannel> channel);
@ -273,16 +233,7 @@ namespace ts {
virtual bool notifyConversationMessageDelete(const ChannelId /* conversation id */, const std::chrono::system_clock::time_point& /* begin timestamp */, const std::chrono::system_clock::time_point& /* begin end */, ClientDbId /* client id */, size_t /* messages */);
/**
* Close the network connection.
*
* Note:
* This method could be called from any thread with any locks in hold.
* It's not blocking.
*
* @param timeout The timestamp when to drop the client if not all data has been send.
* @returns `true` if the connection has been closed and `false` if the connection is already closed.
*/
/* this method should be callable from everywhere; the method is non blocking! */
virtual bool close_connection(const std::chrono::system_clock::time_point &timeout) = 0;
/* this method should be callable from everywhere; the method is non blocking! */
@ -295,7 +246,8 @@ namespace ts {
virtual bool ignoresFlood() { return !this->block_flood; }
std::shared_ptr<ConnectionInfoData> request_connection_info(const std::shared_ptr<ConnectedClient> & /* receiver */, bool& /* send temporary (no client response yet) */);
void updateTalkRights(permission::v2::PermissionFlaggedValue talk_power);
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);
@ -303,40 +255,27 @@ namespace ts {
return this->connectionStatistics;
}
inline std::shared_ptr<ClientChannelView> channel_view() { return this->channel_tree; }
inline std::shared_ptr<ClientChannelView> channel_view() { return this->channels; }
[[nodiscard]] inline std::shared_ptr<ConnectedClient> ref() { return this->_this.lock(); }
[[nodiscard]] inline std::weak_ptr<ConnectedClient> weak_ref() { return this->_this; }
std::shared_mutex& get_channel_lock() { return this->channel_tree_mutex; }
/* Attention: Ensure that channel_lock has been locked */
[[nodiscard]] inline std::vector<GroupId>& current_server_groups() { return this->cached_server_groups; }
[[nodiscard]] inline GroupId& current_channel_group() { return this->cached_channel_group; }
/**
* Attention: This method should never be called directly (except in some edge cases)!
* Use `task_update_channel_client_properties` instead to schedule an update.
*/
virtual void updateChannelClientProperties(bool /* lock channel tree */, bool /* notify our self */);
inline std::shared_ptr<ConnectedClient> ref() { return _this.lock(); }
std::shared_mutex& get_channel_lock() { return this->channel_lock; }
/*
* permission stuff
*/
/**
* Attention: This method should never be called directly!
* Use `task_update_needed_permissions` instead to schedule an update.
* @returns `true` is a permission updated happened.
*/
bool update_client_needed_permissions();
/*
inline permission::PermissionValue cached_permission_value(permission::PermissionType type) const {
std::lock_guard lock(this->cached_permissions_lock);
auto index = this->cached_permissions.find(type);
if(index != this->cached_permissions.end())
return index->second;
/**
* Note: `server_groups_changed` and `channel_group_changed` could be equal.
* If so it will be true if any changes have been made.
* Attention: This method should never be called directly!
* Use `task_update_displayed_groups` instead to schedule an update.
We're only caching permissions which are granted to reduce memory
//logError(this->getServerId(), "{} Looked up cached permission, which hasn't been cached!", CLIENT_STR_LOG_PREFIX);
return permNotGranted;
}
*/
void update_displayed_client_groups(bool& server_groups_changed, bool& channel_group_changed);
bool update_cached_permissions();
std::shared_lock<std::shared_mutex> require_connected_state(bool blocking = false) {
//try_to_lock_t
@ -358,20 +297,17 @@ namespace ts {
}
inline bool playlist_subscribed(const std::shared_ptr<ts::music::Playlist>& playlist) const {
return this->subscribed_playlist_.lock() == playlist;
return this->_subscribed_playlist.lock() == playlist;
}
permission::PermissionType calculate_and_get_join_state(const std::shared_ptr<BasicChannel>&);
protected:
std::weak_ptr<ConnectedClient> _this;
sockaddr_storage remote_address;
//General states
/* TODO: Make this a shared lock and lock the client state via holding the lock when executing commands etc */
std::mutex state_lock{};
std::mutex state_lock;
ConnectionState state{ConnectionState::UNKNWON};
bool allowedToTalk{false};
bool allowedToTalk = false;
std::shared_mutex finalDisconnectLock; /* locked before state lock! */
@ -380,7 +316,7 @@ namespace ts {
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>> open_private_conversations{}; /* variable locked with channel_lock */
std::deque<std::weak_ptr<ConnectedClient>> openChats{}; /* variable locked with channel_lock */
std::chrono::system_clock::time_point lastNeededNotify;
std::shared_ptr<BasicChannel> lastNeededPermissionNotifyChannel = nullptr;
@ -413,36 +349,26 @@ namespace ts {
bool block_flood = true;
FloodPoints floodPoints = 0;
std::shared_ptr<ClientChannelView> channel_tree{};
std::shared_mutex channel_tree_mutex{};
std::shared_ptr<ClientChannelView> channels;
std::shared_mutex channel_lock;
/* The permission overview which the client itself has (for basic client actions ) */
std::mutex client_needed_permissions_lock;
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> client_needed_permissions;
std::mutex cached_permissions_lock;
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> cached_permissions; /* contains all needed permissions which are set */
permission::v2::PermissionFlaggedValue channels_view_power{0, false};
permission::v2::PermissionFlaggedValue channels_ignore_view{0, false};
permission::v2::PermissionFlaggedValue cpmerission_whisper_power{0, false};
permission::v2::PermissionFlaggedValue cpmerission_needed_whisper_power{0, false};
virtual void initialize_weak_reference(const std::shared_ptr<ConnectedClient>& /* self reference */);
bool subscribeToAll{false};
uint16_t join_state_id{1}; /* default channel value is 0 and by default we need to calculate at least once, so we use 1 */
/* (ChannelId, ChannelPasswordHash!) (If empty no password/permissions, if "ignore" ignore permissions granted) */
std::vector<std::pair<ChannelId, std::string>> join_whitelisted_channel{}; /* Access only when the command mutex is acquired */
bool subscribeToAll = false;
uint16_t join_state_id = 1; /* default channel value is 0 and by default we need to calculate at least once, so we use 1 */
permission::PermissionType calculate_and_get_join_state(const std::shared_ptr<BasicChannel>&);
std::weak_ptr<MusicClient> selectedBot;
std::weak_ptr<MusicClient> subscribed_bot;
std::weak_ptr<ts::music::Playlist> subscribed_playlist_{};
std::weak_ptr<ts::music::Playlist> _subscribed_playlist{};
multi_shot_task task_update_needed_permissions{};
multi_shot_task task_update_channel_client_properties{};
multi_shot_task task_update_displayed_groups{};
bool loadDataForCurrentServer() override;
virtual void tick_server(const std::chrono::system_clock::time_point &time);
virtual void tick(const std::chrono::system_clock::time_point &time);
//Locked by everything who has something todo with command handling
threads::Mutex command_lock; /* Note: This mutex must be recursive! */
std::vector<std::function<void()>> postCommandHandler;
@ -484,13 +410,13 @@ namespace ts {
command_result handleCommandChannelDelPerm(Command&);
//Server group manager management
command_result handleCommandServerGroupAdd(Command&);
command_result handleCommandServerGroupCopy(Command&);
command_result handleCommandServerGroupAdd(Command&);
command_result handleCommandServerGroupRename(Command&);
command_result handleCommandServerGroupDel(Command&);
command_result handleCommandServerGroupClientList(Command&);
command_result handleCommandServerGroupAddClient(Command&);
command_result handleCommandServerGroupDelClient(Command&);
command_result handleCommandServerGroupAddClient(Command&);
command_result handleCommandServerGroupPermList(Command&);
command_result handleCommandServerGroupAddPerm(Command&);
command_result handleCommandServerGroupDelPerm(Command&);
@ -528,9 +454,10 @@ namespace ts {
command_result handleCommandFTInitUpload(Command&);
command_result handleCommandFTInitDownload(Command&);
command_result handleCommandFTGetFileInfo(Command&);
command_result handleCommandFTRenameFile(Command&);
command_result handleCommandFTList(Command&);
command_result handleCommandFTStop(Command&);
//CMD_TODO handleCommandFTGetFileInfo -> 5 points
//CMD_TODO handleCommandFTStop -> 5 points
//CMD_TODO handleCommandFTRenameFile -> 5 points
//CMD_TODO handleCommandFTList -> 5 points
command_result handleCommandBanList(Command&);
command_result handleCommandBanAdd(Command&);
@ -541,9 +468,7 @@ namespace ts {
command_result handleCommandBanTriggerList(Command&);
command_result handleCommandTokenList(Command&);
command_result handleCommandTokenActionList(Command&);
command_result handleCommandTokenAdd(Command&);
command_result handleCommandTokenEdit(Command&);
command_result handleCommandTokenUse(Command&);
command_result handleCommandTokenDelete(Command&);
@ -667,10 +592,7 @@ namespace ts {
command_result handleCommandConversationMessageDelete(Command&);
command_result handleCommandLogView(Command&);
command_result handleCommandLogQuery(Command&);
command_result handleCommandLogAdd(Command&);
command_result handleCommandListFeatureSupport(Command &cmd);
//CMD_TODO handleCommandLogAdd
//handleCommandClientSiteReport() -> return findError(0x00)
//handleCommandChannelCreatePrivate() -> return findError(0x02)
@ -681,7 +603,7 @@ namespace ts {
//handleCommandDummy_ConnectFailed
//handleCommandDummy_ConnectionLost
//Not needed - completely useless
//Not needed - completly useless
//CMD_TODO handleCommandCustomInfo
//CMD_TODO handleCommandCustomSearch
//CMD_TODO serverquerycmd
@ -689,19 +611,10 @@ namespace ts {
void sendChannelList(bool lock_channel_tree);
void sendServerInit();
void sendTSPermEditorWarning();
void sendChannelDescription(const std::shared_ptr<BasicChannel>&, bool lock_tree);
bool handleTextMessage(ChatMessageMode, std::string, const std::shared_ptr<ConnectedClient>& /* sender target */);
/**
* Call this method only when command handling is locked (aka the client can't do anything).
* All other locks shall be free.
*
* Note: This will not increase the token use count.
* The callee will have to do so.
*
*/
void useToken(token::TokenId);
typedef std::function<void(const std::shared_ptr<ConnectedClient>& /* sender */, const std::string& /* message */)> handle_text_command_fn_t;
bool handle_text_command(
ChatMessageMode,
@ -711,25 +624,11 @@ namespace ts {
const std::shared_ptr<ConnectedClient>& /* sender target */
);
/* Function to execute the channel edit. We're not checking for any permissions */
ts::command_result execute_channel_edit(
ChannelId& /* channel id */,
const std::map<property::ChannelProperties, std::string>& /* values */,
bool /* is channel create */
);
inline std::string notify_response_command(const std::string_view& notify) {
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK)
return std::string(notify);
return "";
}
command_result handleCommandGroupAdd(Command&, GroupTarget);
command_result handleCommandGroupCopy(Command&, GroupTarget);
command_result handleCommandGroupRename(Command&, GroupTarget);
command_result handleCommandGroupDel(Command&, GroupTarget);
command_result executeGroupPermissionEdit(Command&, const std::vector<std::shared_ptr<groups::Group>>& /* groups */, const std::shared_ptr<VirtualServer>& /* target server */, permission::v2::PermissionUpdateType /* mode */);
};
template <typename T>

View File

@ -1,22 +1,29 @@
#include <iostream>
#include <bitset>
#include <algorithm>
#include "ConnectedClient.h"
#include "voice/VoiceClient.h"
#include "src/server/file/LocalFileServer.h"
#include "../server/VoiceServer.h"
#include "../InstanceHandler.h"
#include "../server/QueryServer.h"
#include "../manager/PermissionNameMapper.h"
#include "music/MusicClient.h"
#include <misc/sassert.h>
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <misc/timer.h>
#include "./web/WebClient.h"
#include "../groups/GroupManager.h"
#include "query/command3.h"
using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
# define INVOKER(command, invoker) \
extern ts::server::InstanceHandler* serverInstance;
#define INVOKER(command, invoker) \
do { \
if(invoker) { \
command["invokerid"] = invoker->getClientId(); \
@ -42,83 +49,41 @@ do { \
} \
} while(0)
template <typename T>
inline void build_group_notify(ts::command_builder& notify, bool is_channel_groups, const std::vector<std::shared_ptr<T>>& available_groups) {
std::string group_id_key{};
permission::PermissionType permission_modify{};
permission::PermissionType permission_add{};
permission::PermissionType permission_remove{};
bool ConnectedClient::notifyServerGroupList() {
Command cmd(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyservergrouplist" : "");
int index = 0;
if(is_channel_groups) {
group_id_key = "cgid";
for (const auto& group : (this->server ? this->server->groups : serverInstance->getGroupManager().get())->availableServerGroups(true)) {
if(group->target() == GroupTarget::GROUPTARGET_CHANNEL) {
cmd[index]["cgid"] = group->groupId();
} else {
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();
permission_modify = permission::i_channel_group_needed_modify_power;
permission_add = permission::i_channel_group_needed_member_add_power;
permission_remove = permission::i_channel_group_needed_member_remove_power;
} else {
group_id_key = "sgid";
permission_modify = permission::i_server_group_needed_modify_power;
permission_add = permission::i_server_group_needed_member_add_power;
permission_remove = permission::i_server_group_needed_member_remove_power;
auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power);
auto add_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_add_power);
auto remove_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_remove_power);
cmd[index]["n_modifyp"] = modify_power.has_value ? modify_power.value : 0;
cmd[index]["n_member_addp"] = add_power.has_value ? add_power.value : 0;
cmd[index]["n_member_removep"] = remove_power.has_value ? remove_power.value : 0;
index++;
}
size_t index{0};
for(const auto& group : available_groups) {
auto bulk = notify.bulk(index++);
bulk.put_unchecked(group_id_key, group->group_id());
bulk.put_unchecked("type", (uint8_t) group->group_type());
bulk.put_unchecked("name", group->display_name());
bulk.put_unchecked("sortid", group->sort_id());
bulk.put_unchecked("savedb", group->is_permanent());
bulk.put_unchecked("namemode", (uint8_t) group->name_mode());
bulk.put_unchecked("iconid", (int32_t) group->icon_id());
auto modify_power = group->permissions()->permission_value_flagged(permission_modify);
auto add_power = group->permissions()->permission_value_flagged(permission_add);
auto remove_power = group->permissions()->permission_value_flagged(permission_remove);
bulk.put_unchecked("n_modifyp", modify_power.has_value ? modify_power.value : 0);
bulk.put_unchecked("n_member_addp", add_power.has_value ? add_power.value : 0);
bulk.put_unchecked("n_member_removep", remove_power.has_value ? remove_power.value : 0);
}
}
bool ConnectedClient::notifyServerGroupList(std::optional<ts::command_builder> &generated_notify, bool as_notify) {
if(!generated_notify.has_value()) {
auto server_ref = this->server;
auto group_manager = server_ref ? server_ref->group_manager() : serverInstance->group_manager();
auto available_groups = group_manager->server_groups()->available_groups(groups::GroupCalculateMode::GLOBAL);
build_group_notify(generated_notify.emplace(as_notify ? "notifyservergrouplist" : ""), false, available_groups);
}
this->sendCommand(*generated_notify);
this->sendCommand(cmd);
return true;
}
bool ConnectedClient::notifyChannelGroupList(std::optional<ts::command_builder>& generated_notify, bool as_notify) {
if(!generated_notify.has_value()) {
auto server_ref = this->server;
auto group_manager = server_ref ? server_ref->group_manager() : serverInstance->group_manager();
auto available_groups = group_manager->channel_groups()->available_groups(groups::GroupCalculateMode::GLOBAL);
build_group_notify(generated_notify.emplace(as_notify ? "notifychannelgrouplist" : ""), true, available_groups);
}
bool ConnectedClient::notifyGroupPermList(const std::shared_ptr<Group>& group, bool as_sid) {
ts::command_builder result{this->getExternalType() == CLIENT_TEAMSPEAK ? group->target() == GROUPTARGET_SERVER ? "notifyservergrouppermlist" : "notifychannelgrouppermlist" : ""};
this->sendCommand(*generated_notify);
return true;
}
bool ConnectedClient::notifyGroupPermList(const std::shared_ptr<groups::Group>& group, bool as_sid) {
auto is_channel_group = !!dynamic_pointer_cast<groups::ChannelGroup>(group);
ts::command_builder result{this->notify_response_command(is_channel_group ? "notifychannelgrouppermlist" : "notifyservergrouppermlist")};
if (!is_channel_group) {
result.put_unchecked(0, "sgid", group->group_id());
} else {
result.put_unchecked(0, "cgid", group->group_id());
}
if (group->target() == GROUPTARGET_SERVER)
result.put_unchecked(0, "sgid", group->groupId());
else
result.put_unchecked(0, "cgid", group->groupId());
int index = 0;
@ -211,6 +176,32 @@ bool ConnectedClient::notifyClientPermList(ClientDbId cldbid, const std::shared_
return true;
}
bool ConnectedClient::notifyChannelGroupList() {
Command cmd(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifychannelgrouplist" : "");
int index = 0;
for (const auto &group : (this->server ? this->server->groups : serverInstance->getGroupManager().get())->availableChannelGroups(true)) {
if(group->target() == GroupTarget::GROUPTARGET_CHANNEL) {
cmd[index]["cgid"] = group->groupId();
} else {
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();
auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power);
auto add_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_add_power);
auto remove_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_member_remove_power);
cmd[index]["n_modifyp"] = modify_power.has_value ? modify_power.value : 0;
cmd[index]["n_member_addp"] = add_power.has_value ? add_power.value : 0;
cmd[index]["n_member_removep"] = remove_power.has_value ? remove_power.value : 0;
index++;
}
if(index == 0) return false;
this->sendCommand(cmd);
return true;
}
bool ConnectedClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr<ConnectedClient> &invoker, uint64_t targetId, ChannelId channel_id, const std::chrono::system_clock::time_point& timestamp, const string &textMessage) {
//notifytextmessage targetmode=1 msg=asdasd target=2 invokerid=1 invokername=WolverinDEV invokeruid=xxjnc14LmvTk+Lyrm8OOeo4tOqw=
Command cmd("notifytextmessage");
@ -225,95 +216,57 @@ bool ConnectedClient::notifyTextMessage(ChatMessageMode mode, const shared_ptr<C
return true;
}
bool ConnectedClient::notifyServerGroupClientAdd(
std::optional<ts::command_builder>& notify,
const std::shared_ptr<ConnectedClient> &invoker,
const std::shared_ptr<ConnectedClient> &target_client,
const GroupId& group_id) {
bool ConnectedClient::notifyServerGroupClientAdd(const shared_ptr<ConnectedClient> &invoker, const shared_ptr<ConnectedClient> &client, const shared_ptr<Group> &group) {
Command cmd("notifyservergroupclientadded");
INVOKER(cmd, invoker);
/* Deny any client moves 'till we've send the notify */
std::shared_lock<std::shared_mutex> channel_tree_lock{};
if(this->server) {
channel_tree_lock = std::shared_lock{this->server->channel_tree_mutex};
}
cmd["sgid"] = group->groupId();
cmd["clid"] = client->getClientId();
cmd["name"] = client->getDisplayName();
cmd["cluid"] = client->getUid();
if(!this->isClientVisible(target_client, true)) {
return false;
}
if(!notify.has_value()) {
notify.emplace("notifyservergroupclientadded");
INVOKER_NEW((*notify), invoker);
notify->put_unchecked(0, "sgid", group_id);
notify->put_unchecked(0, "clid", target_client->getClientId());
notify->put_unchecked(0, "name", target_client->getDisplayName());
notify->put_unchecked(0, "cluid", target_client->getUid());
}
this->sendCommand(*notify);
this->sendCommand(cmd);
return true;
}
bool ConnectedClient::notifyServerGroupClientRemove(
std::optional<ts::command_builder>& notify,
const std::shared_ptr<ConnectedClient> &invoker,
const std::shared_ptr<ConnectedClient> &target_client,
const GroupId& group_id) {
bool ConnectedClient::notifyServerGroupClientRemove(std::shared_ptr<ConnectedClient> invoker, std::shared_ptr<ConnectedClient> client, std::shared_ptr<Group> group) {
Command cmd("notifyservergroupclientdeleted");
INVOKER(cmd, invoker);
/* Deny any client moves 'till we've send the notify */
std::shared_lock<std::shared_mutex> channel_tree_lock{};
if(this->server) {
channel_tree_lock = std::shared_lock{this->server->channel_tree_mutex};
}
cmd["sgid"] = group->groupId();
cmd["clid"] = client->getClientId();
cmd["name"] = client->getDisplayName();
cmd["cluid"] = client->getUid();
if(!this->isClientVisible(target_client, true)) {
return false;
}
if(!notify.has_value()) {
notify.emplace("notifyservergroupclientdeleted");
INVOKER_NEW((*notify), invoker);
notify->put_unchecked(0, "sgid", group_id);
notify->put_unchecked(0, "clid", target_client->getClientId());
notify->put_unchecked(0, "name", target_client->getDisplayName());
notify->put_unchecked(0, "cluid", target_client->getUid());
}
this->sendCommand(*notify);
this->sendCommand(cmd);
return true;
}
bool ConnectedClient::notifyClientChannelGroupChanged(std::optional<ts::command_builder> &notify,
const std::shared_ptr<ConnectedClient> &invoker,
const std::shared_ptr<ConnectedClient> &target_client,
const ChannelId &channel_id,
const ChannelId &inherited_channel_id,
const GroupId &group_id) {
/* Deny any client moves 'till we've send the notify */
std::shared_lock<std::shared_mutex> channel_tree_lock{};
if(this->server) {
channel_tree_lock = std::shared_lock{this->server->channel_tree_mutex};
}
bool ConnectedClient::notifyClientChannelGroupChanged(
const std::shared_ptr<ConnectedClient> &invoker,
const std::shared_ptr<ConnectedClient> &client,
const std::shared_ptr<BasicChannel>& channel,
const std::shared_ptr<BasicChannel>& inherited,
const std::shared_ptr<Group>& group,
bool lock_channel_tree) {
assert(!lock_channel_tree); /* not supported */
/* No need to check if the channel is visible since if this is the case the client would not be visible as well. */
if(!this->isClientVisible(target_client, true)) {
return false;
}
if(!this->isClientVisible(client, false) && client != this) return false;
if(client->getChannel() != channel) return false;
if(!notify.has_value()) {
notify.emplace("notifyclientchannelgroupchanged");
INVOKER_NEW((*notify), invoker);
assert(client);
assert(channel);
assert(inherited);
assert(group);
Command cmd("notifyclientchannelgroupchanged");
INVOKER(cmd, invoker);
notify->put_unchecked(0, "cgid", group_id);
notify->put_unchecked(0, "clid", target_client->getClientId());
notify->put_unchecked(0, "name", target_client->getDisplayName());
notify->put_unchecked(0, "cid", channel_id);
notify->put_unchecked(0, "cgi", inherited_channel_id == 0 ? channel_id : inherited_channel_id);
}
cmd["cgid"] = group->groupId();
cmd["clid"] = client->getClientId();
cmd["cid"] = channel->channelId();
cmd["cgi"] = inherited ? inherited->channelId() : channel->channelId(); //The inherited channel
this->sendCommand(*notify);
this->sendCommand(cmd);
return true;
}
@ -380,9 +333,8 @@ bool ConnectedClient::notifyConnectionInfo(const shared_ptr<ConnectedClient> &ta
}
if(info) {
for(const auto& [key, value] : info->properties) {
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) {
@ -404,15 +356,13 @@ bool ConnectedClient::notifyConnectionInfo(const shared_ptr<ConnectedClient> &ta
bulk.put(property::CONNECTION_PACKETS_SENT_SPEECH, stats.connection_packets_sent[stats::ConnectionStatistics::category::VOICE]);
}
}
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());
}
#ifdef COMPILE_WEB_CLIENT
else if(dynamic_pointer_cast<WebClient>(target)) {
else if(dynamic_pointer_cast<WebClient>(target))
bulk.put(property::CONNECTION_PING, floor<milliseconds>(dynamic_pointer_cast<WebClient>(target)->client_ping()).count());
}
#endif
if(auto vc = dynamic_pointer_cast<VoiceClient>(target); vc){
@ -445,8 +395,6 @@ bool ConnectedClient::notifyClientMoved(const shared_ptr<ConnectedClient> &clien
assert(client->getClientId() > 0);
assert(client->currentChannel);
assert(target_channel);
sassert(mutex_shared_locked(this->channel_tree_mutex));
sassert(mutex_shared_locked(client->channel_tree_mutex));
assert(this->isClientVisible(client, false) || &*client == this);
Command mv("notifyclientmoved");
@ -465,12 +413,10 @@ bool ConnectedClient::notifyClientMoved(const shared_ptr<ConnectedClient> &clien
}
bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &client, const deque<const property::PropertyDescription*> &props, bool lock) {
std::shared_lock channel_lock(this->channel_tree_mutex, defer_lock);
if(lock) {
shared_lock channel_lock(this->channel_lock, defer_lock);
if(lock)
channel_lock.lock();
}
sassert(mutex_shared_locked(this->channel_tree_mutex));
if(!this->isClientVisible(client, false) && client != this)
return false;
@ -483,7 +429,7 @@ bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient>
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))
response[prop->name] = client->properties()[prop].as_or<int64_t>(0) + duration_cast<seconds>(system_clock::now() - client->lastOnlineTimestamp).count();
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();
}
@ -518,7 +464,7 @@ bool ConnectedClient::notifyClientChatClosed(const shared_ptr<ConnectedClient> &
}
bool ConnectedClient::notifyChannelMoved(const std::shared_ptr<BasicChannel> &channel, ChannelId order, const std::shared_ptr<ConnectedClient> &invoker) {
if(!channel || !this->channel_tree->channel_visible(channel)) return false;
if(!channel || !this->channels->channel_visible(channel)) return false;
Command notify("notifychannelmoved");
INVOKER(notify, invoker);
@ -532,7 +478,7 @@ bool ConnectedClient::notifyChannelMoved(const std::shared_ptr<BasicChannel> &ch
bool ConnectedClient::notifyChannelCreate(const std::shared_ptr<BasicChannel> &channel, ChannelId orderId, const std::shared_ptr<ConnectedClient> &invoker) {
Command notify("notifychannelcreated");
for (auto &prop : channel->properties()->list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
for (auto &prop : channel->properties().list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
if(prop.type() == property::CHANNEL_ORDER)
notify[prop.type().name] = orderId;
else if(prop.type() == property::CHANNEL_DESCRIPTION)
@ -549,13 +495,13 @@ bool ConnectedClient::notifyChannelHide(const std::deque<ChannelId> &channel_ids
if(channel_ids.empty()) return true;
if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { //Voice hasnt that event
shared_lock server_channel_lock(this->server->channel_tree_mutex, defer_lock);
unique_lock client_channel_lock(this->channel_tree_mutex, defer_lock);
shared_lock server_channel_lock(this->server->channel_tree_lock, defer_lock);
unique_lock client_channel_lock(this->channel_lock, defer_lock);
if(lock_channel_tree) {
server_channel_lock.lock();
client_channel_lock.lock();
} else {
assert(mutex_locked(this->channel_tree_mutex));
assert(mutex_locked(this->channel_lock));
}
deque<shared_ptr<ConnectedClient>> clients_to_remove;
@ -589,37 +535,32 @@ bool ConnectedClient::notifyChannelHide(const std::deque<ChannelId> &channel_ids
}
bool ConnectedClient::notifyChannelShow(const std::shared_ptr<ts::BasicChannel> &channel, ts::ChannelId orderId) {
if(!channel) {
if(!channel)
return false;
}
auto result = false;
if(this->getType() == ClientType::CLIENT_TEAMSPEAK) {
/* The TeamSpeak 3 client dosn't know about hidden channels */
if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { //Voice hasn't that event
result = this->notifyChannelCreate(channel, orderId, this->server->serverRoot);
} else {
Command notify("notifychannelshow");
for (auto &prop : channel->properties()->list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, (uint16_t) 0)) {
for (auto &prop : channel->properties().list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
if(prop.type() == property::CHANNEL_ORDER) {
notify[prop.type().name] = orderId;
} else if(prop.type() == property::CHANNEL_DESCRIPTION) {
continue;
} else {
notify[prop.type().name] = prop.value();
}
else
notify[prop.type().name] = prop.value();
}
this->sendCommand(notify);
}
if(result && this->subscribeToAll) {
if(result && this->subscribeToAll)
this->subscribeChannel({channel}, false, true);
}
return true;
}
bool ConnectedClient::notifyChannelDescriptionChanged(std::shared_ptr<BasicChannel> channel) {
if(!this->channel_tree->channel_visible(channel)) return false;
if(!this->channels->channel_visible(channel)) return false;
Command notifyDesChanges("notifychanneldescriptionchanged");
notifyDesChanges["cid"] = channel->channelId();
this->sendCommand(notifyDesChanges);
@ -627,7 +568,7 @@ bool ConnectedClient::notifyChannelDescriptionChanged(std::shared_ptr<BasicChann
}
bool ConnectedClient::notifyChannelPasswordChanged(std::shared_ptr<BasicChannel> channel) {
if(!this->channel_tree->channel_visible(channel)) return false;
if(!this->channels->channel_visible(channel)) return false;
Command notifyDesChanges("notifychannelpasswordchanged");
notifyDesChanges["cid"] = channel->channelId();
this->sendCommand(notifyDesChanges);
@ -638,7 +579,6 @@ bool ConnectedClient::notifyClientEnterView(const std::shared_ptr<ConnectedClien
sassert(client && client->getClientId() > 0);
sassert(to);
sassert(!lock_channel_tree); /* we don't support locking */
sassert(mutex_locked(this->channel_tree_mutex));
sassert(!this->isClientVisible(client, false) || &*client == this);
switch (reasonId) {
@ -652,15 +592,6 @@ bool ConnectedClient::notifyClientEnterView(const std::shared_ptr<ConnectedClien
;//invoker = this->server->serverRoot.get();
}
break;
case ViewReasonId::VREASON_SYSTEM:
case ViewReasonId::VREASON_TIMEOUT:
case ViewReasonId::VREASON_SERVER_STOPPED:
case ViewReasonId::VREASON_SERVER_LEFT:
case ViewReasonId::VREASON_CHANNEL_UPDATED:
case ViewReasonId::VREASON_EDITED:
case ViewReasonId::VREASON_SERVER_SHUTDOWN:
case ViewReasonId::VREASON_USER_ACTION:
default:
break;
}
@ -678,15 +609,6 @@ bool ConnectedClient::notifyClientEnterView(const std::shared_ptr<ConnectedClien
case ViewReasonId::VREASON_SERVER_KICK:
builder.put_unchecked(0, "reasonmsg", reason);
break;
case ViewReasonId::VREASON_SYSTEM:
case ViewReasonId::VREASON_TIMEOUT:
case ViewReasonId::VREASON_SERVER_STOPPED:
case ViewReasonId::VREASON_SERVER_LEFT:
case ViewReasonId::VREASON_CHANNEL_UPDATED:
case ViewReasonId::VREASON_EDITED:
case ViewReasonId::VREASON_SERVER_SHUTDOWN:
case ViewReasonId::VREASON_USER_ACTION:
default:
break;
}
@ -702,11 +624,9 @@ bool ConnectedClient::notifyClientEnterView(const std::shared_ptr<ConnectedClien
}
bool ConnectedClient::notifyClientEnterView(const std::deque<std::shared_ptr<ConnectedClient>> &clients, const ts::ViewReasonSystemT &_vrs) {
if(clients.empty()) {
if(clients.empty())
return true;
}
assert(mutex_locked(this->channel_tree_mutex));
assert(mutex_locked(this->channel_lock));
Command cmd("notifycliententerview");
@ -719,9 +639,8 @@ bool ConnectedClient::notifyClientEnterView(const std::deque<std::shared_ptr<Con
while(it != clients.end()) {
auto client = *(it++);
if(this->isClientVisible(client, false)) {
if(this->isClientVisible(client, false))
continue;
}
auto channel = client->getChannel();
sassert(!channel || channel->channelId() != 0);
@ -760,34 +679,29 @@ bool ConnectedClient::notifyChannelEdited(
const std::vector<property::ChannelProperties> &properties,
const std::shared_ptr<ConnectedClient> &invoker,
bool) {
auto v_channel = this->channel_tree->find_channel(channel->channelId());
auto v_channel = this->channels->find_channel(channel->channelId());
if(!v_channel) return false; //Not visible? Important do not remove!
bool send_description_change{false};
size_t property_count{0};
Command notify("notifychanneledited");
for(auto prop : properties) {
const auto& prop_info = property::describe(prop);
if(prop == property::CHANNEL_ORDER) {
if(prop == property::CHANNEL_ORDER)
notify[prop_info.name] = v_channel->previous_channel;
property_count++;
} else if(prop == property::CHANNEL_DESCRIPTION) {
else if(prop == property::CHANNEL_DESCRIPTION) {
send_description_change = true;
} else {
notify[prop_info.name] = channel->properties()[prop].value();
property_count++;
notify[prop_info.name] = channel->properties()[prop].as<string>();
}
}
if(property_count > 0) {
notify["cid"] = channel->channelId();
notify["reasonid"] = ViewReasonId::VREASON_EDITED;
notify["cid"] = channel->channelId();
notify["reasonid"] = ViewReasonId::VREASON_EDITED;
INVOKER(notify, invoker);
this->sendCommand(notify);
}
INVOKER(notify, invoker);
this->sendCommand(notify);
if(send_description_change) {
Command notify_dchange{"notifychanneldescriptionchanged"};
@ -818,7 +732,7 @@ bool ConnectedClient::notifyChannelDeleted(const deque<ChannelId>& channel_ids,
bool ConnectedClient::notifyServerUpdated(std::shared_ptr<ConnectedClient> invoker) {
Command response("notifyserverupdated");
for (const auto& elm : this->server->properties()->list_properties(property::FLAG_SERVER_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
for (const auto& elm : this->server->properties().list_properties(property::FLAG_SERVER_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
if(elm.type() == property::VIRTUALSERVER_MIN_WINPHONE_VERSION)
continue;
@ -844,7 +758,7 @@ bool ConnectedClient::notifyChannelSubscribed(const deque<shared_ptr<BasicChanne
Command notify("notifychannelsubscribed");
int index = 0;
for (const auto& ch : channels) {
notify[index]["es"] = this->server->getClientsByChannel(ch).empty() ? ch->empty_seconds() : 0;
notify[index]["es"] = this->server->getClientsByChannel(ch).empty() ? ch->emptySince() : 0;
notify[index++]["cid"] = ch->channelId();
}
this->sendCommand(notify);
@ -855,7 +769,7 @@ bool ConnectedClient::notifyChannelUnsubscribed(const deque<shared_ptr<BasicChan
Command notify("notifychannelunsubscribed");
int index = 0;
for (const auto& ch : channels) {
notify[index]["es"] = this->server->getClientsByChannel(ch).empty() ? ch->empty_seconds() : 0;
notify[index]["es"] = this->server->getClientsByChannel(ch).empty() ? ch->emptySince() : 0;
notify[index++]["cid"] = ch->channelId();
}
this->sendCommand(notify);

View File

@ -9,6 +9,7 @@
#include "../music/MusicBotManager.h"
#include "../client/music/MusicClient.h"
#include "../client/voice/VoiceClient.h"
#include "./ConnectedClient.h"
using namespace ts;
using namespace ts::server;
@ -35,18 +36,18 @@ do { \
return true; \
} while(false)
#define TLEN(index) if(arguments.size() < index) ERR(music_root, "Invalid argument count");
#define TLEN(index) if(arguments.size() < index) ERR(serverInstance->musicRoot(), "Invalid argument count");
#define TARG(index, type) (arguments.size() > index && arguments[index] == type)
#define TMUSIC(bot) if(!bot->current_player()) ERR(music_root, "Im not playing a song!");
#define TMUSIC(bot) if(!bot->current_player()) ERR(serverInstance->musicRoot(), "Im not playing a song!");
#define GBOT(var, ignore_disabled) \
auto var = this->selectedBot.lock(); \
if(!var) var = dynamic_pointer_cast<MusicClient>(target); \
if(!var) { \
send_message(music_root, "Please select a music bot! (" + ts::config::music::command_prefix + "mbot select <id>)"); \
send_message(serverInstance->musicRoot(), "Please select a music bot! (" + ts::config::music::command_prefix + "mbot select <id>)"); \
return true; \
} \
if(!ignore_disabled && var->properties()[property::CLIENT_DISABLED].as<bool>()) { \
send_message(music_root, strobf("This bot has been disabled. Upgrade your TeaSpeak license to use more bots.").string()); \
send_message(serverInstance->musicRoot(), strobf("This bot has been disabled. Upgrade your TeaSpeak license to use more bots.").string()); \
return true; \
}
@ -99,20 +100,20 @@ bool ConnectedClient::handleTextMessage(ChatMessageMode mode, std::string text,
#define PERM_CHECK_BOT(perm, reqperm, err) \
if(bot->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId() && \
!permission::v2::permission_granted(bot->calculate_permission(permission::reqperm, bot->getChannelId()), this->calculate_permission(permission::perm, bot->getChannelId()))) { \
send_message(music_root, err); \
send_message(serverInstance->musicRoot(), err); \
return true; \
}
//TODO: Correct error message print!
#define HANDLE_CMD_ERROR(_message) \
send_message(music_root, string(_message) + ": action failed");
send_message(serverInstance->musicRoot(), string(_message) + ": action failed");
/*
if(result.extraProperties.count("extra_msg") > 0) \
send_message(music_root, string(_message) + ": " + result.extraProperties["extra_msg"]); \
send_message(serverInstance->musicRoot(), string(_message) + ": " + result.extraProperties["extra_msg"]); \
else if(result.extraProperties.count("failed_permid") > 0) \
send_message(music_root, string(_message) + ". (Missing permission " + permission::resolvePermissionData((permission::PermissionType) stoull(result.extraProperties["failed_permid"]))->name + ")"); \
send_message(serverInstance->musicRoot(), string(_message) + ". (Missing permission " + permission::resolvePermissionData((permission::PermissionType) stoull(result.extraProperties["failed_permid"]))->name + ")"); \
else \
send_message(music_root, string(_message) + ": " + result.error.message);
send_message(serverInstance->musicRoot(), string(_message) + ": " + result.error.message);
*/
#define JOIN_ARGS(variable, index) \
@ -149,10 +150,9 @@ bool ConnectedClient::handle_text_command(
const function<void(const shared_ptr<ConnectedClient> &, const string &)> &send_message,
const shared_ptr<ConnectedClient>& target) {
auto music_root = dynamic_pointer_cast<ConnectedClient>(serverInstance->musicRoot());
if (command == "mbot") {
if(!config::music::enabled) {
send_message(music_root, "Music bots are not enabled! Enable them via the server config!");
send_message(serverInstance->musicRoot(), "Music bots are not enabled! Enable them via the server config!");
return true;
}
if (TARG(0, "create")) {
@ -163,7 +163,7 @@ bool ConnectedClient::handle_text_command(
HANDLE_CMD_ERROR("Failed to create music bot");
return true;
}
send_message(music_root, "Bot created");
send_message(serverInstance->musicRoot(), "Bot created");
return true;
} else if (TARG(0, "list")) {
bool server = false;
@ -172,19 +172,19 @@ bool ConnectedClient::handle_text_command(
string locationStr = server ? "on this server" : "in this channel";
if(!permission::v2::permission_granted(1, this->calculate_permission(server ? permission::b_client_music_server_list : permission::b_client_music_channel_list, this->getChannelId()))) {
send_message(music_root, "You don't have the permission to list all music bots " + locationStr);
send_message(serverInstance->musicRoot(), "You don't have the permission to list all music bots " + locationStr);
return true;
}
auto mbots = this->server->getClientsByChannel<MusicClient>(server ? nullptr : this->currentChannel);
if (mbots.empty())
send_message(music_root, "There are no music bots " + locationStr);
send_message(serverInstance->musicRoot(), "There are no music bots " + locationStr);
else {
send_message(music_root, "There are " + to_string(mbots.size()) + " music bots " + locationStr + ":");
send_message(serverInstance->musicRoot(), "There are " + to_string(mbots.size()) + " music bots " + locationStr + ":");
for (const auto &mbot : mbots) {
if(mbot->properties()[property::CLIENT_DISABLED].as_or<bool>(false)) {
send_message(music_root, " - [color=red]" + to_string(mbot->getClientDatabaseId()) + " | " + mbot->getDisplayName() + " [DISABLED][/color]");
if(mbot->properties()[property::CLIENT_DISABLED].as<bool>()) {
send_message(serverInstance->musicRoot(), " - [color=red]" + to_string(mbot->getClientDatabaseId()) + " | " + mbot->getDisplayName() + " [DISABLED][/color]");
} else {
send_message(music_root, " - [color=green]" + to_string(mbot->getClientDatabaseId()) + " | " + mbot->getDisplayName() + "[/color]");
send_message(serverInstance->musicRoot(), " - [color=green]" + to_string(mbot->getClientDatabaseId()) + " | " + mbot->getDisplayName() + "[/color]");
}
}
}
@ -192,21 +192,21 @@ bool ConnectedClient::handle_text_command(
} else if (TARG(0, "select")) {
TLEN(2);
if(arguments[1].find_first_not_of("0123456789") != std::string::npos) {
send_message(music_root, "Invalid bot id");
send_message(serverInstance->musicRoot(), "Invalid bot id");
return true;
}
auto botId = static_cast<ClientDbId>(stoll(arguments[1]));
auto bot = this->server->music_manager_->findBotById(botId);
if (!bot) ERR(music_root, "Could not find target bot");
auto bot = this->server->musicManager->findBotById(botId);
if (!bot) ERR(serverInstance->musicRoot(), "Could not find target bot");
if(bot->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId() &&
!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_music_channel_list, this->getChannelId())) &&
!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_music_server_list, this->getChannelId()))) { //No perms for listing
send_message(music_root, "You don't have the permission to select a music bot");
send_message(serverInstance->musicRoot(), "You don't have the permission to select a music bot");
return true;
}
this->selectedBot = bot;
send_message(music_root, "You successfully select the bot " + to_string(botId) + " | " + bot->getDisplayName());
send_message(serverInstance->musicRoot(), "You successfully select the bot " + to_string(botId) + " | " + bot->getDisplayName());
return true;
} else if (TARG(0, "rename")) {
TLEN(2);
@ -225,12 +225,12 @@ bool ConnectedClient::handle_text_command(
result.release_data();
return true;
}
send_message(music_root, "Name successfully changed!");
send_message(serverInstance->musicRoot(), "Name successfully changed!");
return true;
} else if (TARG(0, "delete")) {
GBOT(bot, true);
PERM_CHECK_BOT(i_client_music_delete_power, i_client_music_needed_delete_power, "You don't have the permission to rename this music bot");
this->server->music_manager_->deleteBot(bot);
this->server->musicManager->deleteBot(bot);
send_message(bot, "You successfully deleted this music bot!");
return true;
} else if(TARG(0, "yt") || TARG(0, "soundcloud") || TARG(0, "sc")){
@ -391,8 +391,8 @@ bool ConnectedClient::handle_text_command(
for(const auto& song : bot_playlist->list_songs()) {
string invoker = "unknown";
for(const auto& e : dbinfo) {
if(e->client_database_id == song->invoker) {
invoker = "[URL=client://0/" + e->client_unique_id + "~" + e->client_nickname + "]" + e->client_nickname + "[/URL]";
if(e->cldbid == song->invoker) {
invoker = "[URL=client://0/" + e->uniqueId + "~" + e->lastName + "]" + e->lastName + "[/URL]";
break;
}
}
@ -460,7 +460,7 @@ bool ConnectedClient::handle_text_command(
for(const auto& fmt : prots)
ss << " - " << fmt << endl;
}
send_message(music_root, ss.str());
send_message(serverInstance->musicRoot(), ss.str());
return true;
} else if(TARG(0, "settings")) {
GBOT(bot, false);
@ -513,7 +513,7 @@ bool ConnectedClient::handle_text_command(
result.release_data();
return true;
}
send_message(music_root, "Property successfully changed!");
send_message(serverInstance->musicRoot(), "Property successfully changed!");
return true;
}
@ -566,7 +566,7 @@ bool ConnectedClient::handle_text_command(
return true;
}
} else if (command == "help") {
//send_message(music_root, " ̶.̶̶m̶̶b̶̶o̶̶t̶̶ ̶̶f̶̶o̶̶r̶̶m̶̶a̶̶t̶̶s (Not supported yet)");
//send_message(serverInstance->musicRoot(), " ̶.̶̶m̶̶b̶̶o̶̶t̶̶ ̶̶f̶̶o̶̶r̶̶m̶̶a̶̶t̶̶s (Not supported yet)");
stringstream ss;
ss << "Available music bot commands: ([color=green]green[/color] = permission granted | [color=red]red[/color] = insufficient permissions)" << endl;
@ -597,39 +597,38 @@ bool ConnectedClient::handle_text_command(
permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot <playlist|pl>", permission::i_client_music_play_power);
permissionableCommand(this, ss, " " + ts::config::music::command_prefix + "mbot settings <playlist|bot> [name] [value]", permission::i_client_music_modify_power);
send_message(music_root, ss.str());
send_message(serverInstance->musicRoot(), ss.str());
return true;
} else if (command == "dummy") {
if(TARG(0, "timerevent")) {
send_message(this->ref(), "Sending command dummy_timerevent");
send_message(_this.lock(), "Sending command dummy_timerevent");
this->sendCommand(Command("dummy_timerevent"));
return true;
} else if(TARG(0, "rd")) {
send_message(this->ref(), "Sending command rd");
send_message(_this.lock(), "Sending command rd");
Command cmd("rd");
cmd["msg"] = "Hello world";
this->sendCommand(cmd);
return true;
} else if(TARG(0, "connectfailed")) {
send_message(this->ref(), "Sending command dummy_connectfailed");
send_message(_this.lock(), "Sending command dummy_connectfailed");
Command cmd("dummy_connectfailed");
cmd["status"] = 1797;
this->sendCommand(cmd);
return true;
}
send_message(this->ref(), "Invalid dummy command!");
send_message(this->ref(), "- timerevent");
send_message(this->ref(), "- connectfailed");
send_message(this->ref(), "- rd");
send_message(_this.lock(), "Invalid dummy command!");
send_message(_this.lock(), "- timerevent");
send_message(_this.lock(), "- connectfailed");
send_message(_this.lock(), "- rd");
return true;
} else if(command == "protocol") {
if(TARG(0, "generation")) {
auto vc = dynamic_pointer_cast<VoiceClient>(this->ref());
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false;
send_message(this->ref(), "Packet generations:");
/*
send_message(_this.lock(), "Packet generations:");
for(const auto& type : {
protocol::PacketTypeInfo::Command,
protocol::PacketTypeInfo::CommandLow,
@ -640,101 +639,88 @@ 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 = vc->getConnection()->getPacketIdManager().currentPacketId(type);
auto gen = vc->getConnection()->getPacketIdManager().generationId(type);
auto& genestis = vc->getConnection()->get_incoming_generation_estimators();
//send_message(this->ref(), " OUT " + type.name() + " => generation: " + to_string(gen) + " id: " + to_string(id));
//send_message(this->ref(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id()));
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, "rtt")) {
auto vc = dynamic_pointer_cast<VoiceClient>(this->ref());
} else if(TARG(0, "ping")) {
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false;
auto& ack = vc->connection->packet_encoder().acknowledge_manager();
send_message(this->ref(), "Command retransmission values:");
send_message(this->ref(), " RTO : " + std::to_string(ack.current_rto()));
send_message(this->ref(), " RTTVAR: " + std::to_string(ack.current_rttvar()));
send_message(this->ref(), " SRTT : " + std::to_string(ack.current_srtt()));
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->ref());
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->ref(), "Invalid type");
send_message(_this.lock(), "Invalid type");
return true;
}
genestis[type].set_last_state(pid, generation);
*/
} catch(std::exception& ex) {
send_message(this->ref(), "Failed to parse argument");
send_message(_this.lock(), "Failed to parse argument");
return true;
}
return true;
} else if(TARG(0, "ping")) {
auto vc = dynamic_pointer_cast<VoiceClient>(this->ref());
if(!vc) return false;
auto ping_handler = vc->getConnection()->ping_handler();
send_message(this->ref(), "Ping: " + std::to_string(ping_handler.current_ping().count()));
auto last_response_ms = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::system_clock::now() - ping_handler.last_ping_response()).count();
send_message(this->ref(), "Last ping response: " + std::to_string(last_response_ms));
return true;
} else if(TARG(0, "disconnect")) {
auto vc = dynamic_pointer_cast<VoiceClient>(this->ref());
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false;
send_message(this->ref(), "You'll timeout");
send_message(_this.lock(), "You'll timeout");
Command cmd("notifyclientupdated");
vc->sendCommand(cmd);
return true;
} else if(TARG(0, "resetip")) {
auto vc = dynamic_pointer_cast<VoiceClient>(this->ref());
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false;
send_message(this->ref(), "I lost your IP address. I'm so dump :)");
vc->connection->reset_remote_address();
send_message(_this.lock(), "I lost your IP address. I'm so dump :)");
memset(&vc->remote_address, 0, sizeof(vc->remote_address));
send_message(this->ref(), "Hey, we got the address back");
memset(&vc->address_info, 0, sizeof(vc->address_info));
send_message(_this.lock(), "Hey, we got the address back");
return true;
} else if(TARG(0, "fb")) {
this->increaseFloodPoints(0xFF8F);
send_message(this->ref(), "Done :)");
send_message(_this.lock(), "Done :)");
return true;
} else if(TARG(0, "binary")) {
send_message(this->ref(), "Send binary message");
send_message(_this.lock(), "Send binary message");
this->sendCommand(Command{"\02\03\04 \22"});
return true;
}
send_message(this->ref(), "Invalid protocol command!");
send_message(this->ref(), "- generation");
send_message(this->ref(), "- disconnect");
send_message(this->ref(), "- resetip");
send_message(this->ref(), "- fb");
send_message(_this.lock(), "Invalid protocol command!");
send_message(_this.lock(), "- generation");
send_message(_this.lock(), "- disconnect");
send_message(_this.lock(), "- resetip");
send_message(_this.lock(), "- fb");
return true;
} else if (command == "sleep") {
if(arguments.empty() || arguments[0].find_first_not_of("0123456789") != string::npos) {
send_message(this->ref(), "Invalid argument! Requires a number in ms.");
send_message(_this.lock(), "Invalid argument! Requires a number in ms.");
return true;
}
send_message(this->ref(), "Sleeping for " + to_string(stoll(arguments[0])) + "!");
send_message(_this.lock(), "Sleeping for " + to_string(stoll(arguments[0])) + "!");
auto end = system_clock::now() + milliseconds(stoll(arguments[0]));
threads::self::sleep_until(end);
send_message(this->ref(), "Done!");
send_message(_this.lock(), "Done!");
return true;
} else if(command == "conversation") {
if(TARG(0, "history")) {
@ -753,25 +739,27 @@ bool ConnectedClient::handle_text_command(
}
auto time_str = [](const system_clock::time_point& tp) {
using system_clock_duration = std::chrono::system_clock::duration;
auto converted_timep = std::chrono::time_point_cast<system_clock_duration>(tp);
auto seconds_since_epoch = std::chrono::system_clock::to_time_t(tp);
ostringstream os;
os << std::put_time(std::localtime(&seconds_since_epoch), "%Y %b %d %H:%M:%S");
return os.str();
};
send_message(this->ref(), "Looking up history from " + time_str(timestamp_end) + " to " + time_str(timestamp_begin) + ". Max messages: " + to_string(message_count));
send_message(_this.lock(), "Looking up history from " + time_str(timestamp_end) + " to " + time_str(timestamp_begin) + ". Max messages: " + to_string(message_count));
auto conversation = this->server->conversation_manager()->get_or_create(this->currentChannel->channelId());
auto data = conversation->message_history(timestamp_begin, message_count, timestamp_end);
send_message(this->ref(), "Entries: " + to_string(data.size()));
send_message(_this.lock(), "Entries: " + to_string(data.size()));
for(auto& entry : data) {
send_message(this->ref(), "<" + time_str(entry->message_timestamp) + ">" + entry->sender_name + ": " + entry->message);
send_message(_this.lock(), "<" + time_str(entry->message_timestamp) + ">" + entry->sender_name + ": " + entry->message);
}
return true;
}
}
send_message(music_root, "Invalid channel command.");
send_message(music_root, "Type " + ts::config::music::command_prefix + "help for a command overview");
send_message(serverInstance->musicRoot(), "Invalid channel command.");
send_message(serverInstance->musicRoot(), "Type " + ts::config::music::command_prefix + "help for a command overview");
return false;
}

View File

@ -1,14 +1,12 @@
#include <log/LogUtils.h>
#include <src/client/voice/VoiceClient.h>
#include <misc/sassert.h>
#include <misc/hex.h>
#include <misc/base64.h>
#include "./DataClient.h"
#include "../InstanceHandler.h"
#include "../groups/GroupManager.h"
#include "../groups/GroupAssignmentManager.h"
#include "../PermissionCalculator.h"
#include "DataClient.h"
#include "ConnectedClient.h"
#include "src/server/file/LocalFileServer.h"
#include "src/InstanceHandler.h"
#include "misc/base64.h"
using namespace std;
using namespace ts;
@ -25,80 +23,52 @@ DataClient::~DataClient() {
this->_properties = nullptr;
}
constexpr static std::string_view kClientLoadCommand{R"(
SELECT
`client_database_id`,
`client_created`,
`client_last_connected`,
`client_total_connections`,
`client_month_upload`,
`client_month_download`,
`client_total_upload`,
`client_total_download`
FROM `clients_server` WHERE `server_id` = :sid AND `client_unique_id` = :uid
)"};
bool DataClient::loadDataForCurrentServer() {
auto uniqueId = this->getUid();
if(uniqueId.empty()) {
return false;
}
bool DataClient::loadDataForCurrentServer() { //TODO for query
if(this->getUid().empty()) return false;
auto ref_server = this->server;
auto server_id = ref_server ? ref_server->getServerId() : 0;
properties()[property::CLIENT_DATABASE_ID] = 0;
this->clientPermissions = std::make_shared<permission::v2::PermissionManager>();
properties()[property::CLIENT_CREATED] = 0;
properties()[property::CLIENT_TOTALCONNECTIONS] = 0;
auto properties = this->properties();
sql::command{this->sql, std::string{kClientLoadCommand}, variable{":uid", uniqueId}, variable{":sid", server_id}}.query([&](int length, std::string* values, std::string* names) {
auto index{0};
try {
assert(names[index] == "client_database_id");
properties[property::CLIENT_DATABASE_ID] = std::stoull(values[index++]);
assert(names[index] == "client_created");
properties[property::CLIENT_CREATED] = std::stoull(values[index++]);
assert(names[index] == "client_last_connected");
properties[property::CLIENT_LASTCONNECTED] = std::stoull(values[index++]);
assert(names[index] == "client_total_connections");
properties[property::CLIENT_TOTALCONNECTIONS] = std::stoull(values[index++]);
assert(names[index] == "client_month_upload");
properties[property::CLIENT_MONTH_BYTES_UPLOADED] = std::stoull(values[index++]);
assert(names[index] == "client_month_download");
properties[property::CLIENT_MONTH_BYTES_DOWNLOADED] = std::stoull(values[index++]);
assert(names[index] == "client_total_upload");
properties[property::CLIENT_TOTAL_BYTES_UPLOADED] = std::stoull(values[index++]);
assert(names[index] == "client_total_download");
properties[property::CLIENT_TOTAL_BYTES_DOWNLOADED] = std::stoull(values[index++]);
assert(index == length);
} catch (std::exception& ex) {
logError(server_id, "Failed to load client {} base properties from database: {} (Index {})",
this->getUid(),
ex.what(),
index - 1
);
properties[property::CLIENT_DATABASE_ID] = 0;
ClientDbId client_db_id = 0;
sql::command(this->sql, "SELECT `cldbid`,`firstConnect`,`connections` FROM `clients` WHERE `serverId` = :sid AND `clientUid` = :uid LIMIT 1",
variable{":sid", server_id},
variable{":uid", this->getUid()}
).query([&](DataClient* cl, int length, string* values, string* names){
for (int index = 0; index < length; index++) {
try {
if (names[index] == "cldbid") {
client_db_id = stoull(values[index]);
} else if (names[index] == "firstConnect") {
cl->properties()[property::CLIENT_CREATED] = values[index];
} else if (names[index] == "connections") {
cl->properties()[property::CLIENT_TOTALCONNECTIONS] = values[index];
} else {
logWarning(LOG_INSTANCE, "Received unknown column with name {} within client list", names[index]);
}
} catch(const std::exception& ex) {
logError(server_id, "Failed to load client {} base properties from database. Colum parsing for column {} failed. Value: {}. Message: {}",
this->getUid(),
names[index],
values[index],
ex.what()
);
return 0;
}
}
});
return 0;
}, this);
if(this->properties()[property::CLIENT_DATABASE_ID].as_or<ClientDbId>(0) == 0) {
if(client_db_id == 0)
return false;
}
this->properties()[property::CLIENT_DATABASE_ID] = client_db_id; /* do this before the property saving (it saved the cldbid as well!)*/
//Load general properties
std::deque<ts::Property> copied;
deque<ts::PropertyWrapper> copied;
for(const auto& prop : this->_properties->list_properties()){
if((prop.type().flags & property::FLAG_GLOBAL) == 0) continue;
if(prop.type().default_value == prop.value()) continue;
@ -106,26 +76,45 @@ bool DataClient::loadDataForCurrentServer() {
}
if(!ref_server) {
if(this->getType() == ClientType::CLIENT_WEB || this->getType() == ClientType::CLIENT_TEAMSPEAK) {
if(this->getType() == ClientType::CLIENT_WEB || this->getType() == ClientType::CLIENT_TEAMSPEAK)
logCritical(LOG_INSTANCE, "Got a voice or web client, which is unbound to any server!");
}
return false;
}
this->_properties = serverInstance->databaseHelper()->loadClientProperties(ref_server, this->getClientDatabaseId(), this->getType());
auto client_type = this->getType();
if(client_type == CLIENT_TEAMSPEAK || ref_server) {
this->_properties = serverInstance->databaseHelper()->loadClientProperties(ref_server, this->getClientDatabaseId(), client_type);
} else {
this->_properties = DatabaseHelper::default_properties_client(nullptr, client_type);
this->_properties->registerNotifyHandler([&, server_id, client_db_id](Property& prop){
std::string query;
if(prop.type() == property::CLIENT_TOTALCONNECTIONS)
query = "UPDATE `clients` SET `connections` = :value WHERE `serverId` = :sid AND `cldbid` = :cldbid";
else if(prop.type() == property::CLIENT_NICKNAME)
query = "UPDATE `clients` SET `lastName` = :value WHERE `serverId` = :sid AND `cldbid` = :cldbid";
else if(prop.type() == property::CLIENT_LASTCONNECTED)
query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :sid AND `cldbid` = :cldbid";
else
return;
debugMessage(server_id, "[Property] Updating general client table property for client {}. Key: {} Value: {}", client_db_id, prop.type().name, prop.value());
sql::command(this->sql, query, variable{":sid", 0}, variable{":cldbid", client_db_id}, variable{":value", prop.value()}).executeLater()
.waitAndGetLater(LOG_SQL_CMD, {1, "failed to update general client properties"});
});
}
this->_properties->toggleSave(false);
for(const auto& e : copied) {
auto p = this->properties()->get(e.type().type_property, e.type().property_index);
auto p = this->properties()->find(e.type().type_property, e.type().property_index);
p = e.value();
p.setModified(false);
}
this->_properties->toggleSave(true);
this->clientPermissions = serverInstance->databaseHelper()->loadClientPermissionManager(ref_server->getServerId(), this->getClientDatabaseId());
this->clientPermissions = serverInstance->databaseHelper()->loadClientPermissionManager(ref_server, this->getClientDatabaseId());
//Setup / fix stuff
#if 0
if(!this->properties()[property::CLIENT_FLAG_AVATAR].as<string>().empty()){
if(
!ref_server ||
@ -134,17 +123,9 @@ bool DataClient::loadDataForCurrentServer() {
this->properties()[property::CLIENT_FLAG_AVATAR] = "";
}
}
#endif
if(ref_server) {
if(ref_server)
this->properties()[property::CLIENT_UNREAD_MESSAGES] = ref_server->letters->unread_letter_count(this->getUid());
}
if(this->server) {
this->temporary_assignments_lock = server->group_manager()->assignments().create_tmp_assignment_lock(this->getClientDatabaseId());
} else {
this->temporary_assignments_lock = serverInstance->group_manager()->assignments().create_tmp_assignment_lock(this->getClientDatabaseId());
}
return true;
}
@ -152,91 +133,53 @@ bool DataClient::loadDataForCurrentServer() {
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> DataClient::calculate_permissions(
const std::deque<permission::PermissionType> &permissions,
ChannelId channel,
bool granted) {
bool granted,
std::shared_ptr<CalculateCache> cache) {
if(permissions.empty()) return {};
ts::server::ClientPermissionCalculator calculator{this, channel};
return calculator.calculate_permissions(permissions, granted);
if(!cache)
cache = std::make_shared<CalculateCache>();
if(!cache->client_permissions) /* so we don't have to load that shit later */
cache->client_permissions = this->clientPermissions;
if(channel == -1)
channel = this->currentChannel ? this->currentChannel->channelId() : 0;
auto ref_server = this->server;
if(ref_server)
return ref_server->calculate_permissions(permissions, this->getClientDatabaseId(), this->getType(), channel, granted, cache);
else
return serverInstance->calculate_permissions(permissions, this->getClientDatabaseId(), this->getType(), channel, granted, cache);
}
permission::v2::PermissionFlaggedValue DataClient::calculate_permission(
permission::PermissionType permission, ChannelId channel, bool granted) {
ts::server::ClientPermissionCalculator calculator{this, channel};
return calculator.calculate_permission(permission, granted);
permission::PermissionType permission, ChannelId channel, bool granted, std::shared_ptr<CalculateCache> cache) {
auto result = this->calculate_permissions({permission}, channel, granted, cache);
if(result.empty()) return {0, false};
return result.back().second;
}
std::vector<std::shared_ptr<groups::ServerGroup>> DataClient::assignedServerGroups() {
auto ref_server = this->server;
auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager();
auto assignments = group_manager->assignments().server_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId());
std::vector<std::shared_ptr<groups::ServerGroup>> result{};
result.reserve(assignments.size());
for(const auto& group_id : assignments) {
auto group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(group) {
result.push_back(group);
}
}
if(result.empty() && ref_server) {
auto default_group_id = ref_server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP].as_or<GroupId>(0);
auto default_group = group_manager->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, default_group_id);
if(default_group) {
result.push_back(default_group);
}
}
return result;
std::vector<std::shared_ptr<GroupAssignment>> DataClient::assignedServerGroups() {
if(!this->server) return serverInstance->getGroupManager()->getServerGroups(this->getClientDatabaseId(), this->getType());
return this->server->groups->getServerGroups(this->getClientDatabaseId(), this->getType());
}
std::shared_ptr<groups::ChannelGroup> DataClient::assignedChannelGroup(std::shared_ptr<BasicChannel> &channel) {
auto ref_server = this->server;
assert(channel);
if(!channel || !ref_server) {
return nullptr;
}
std::shared_ptr<BasicChannel> original_channel{channel};
auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager();
auto assigned_group_id = group_manager->assignments().calculate_channel_group_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId(), channel);
std::shared_ptr<groups::ChannelGroup> result{};
if(assigned_group_id.has_value()) {
result = group_manager->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, *assigned_group_id);
}
if(!result) {
result = ref_server->default_channel_group();
channel = original_channel;
}
assert(result);
return result;
std::shared_ptr<GroupAssignment> DataClient::assignedChannelGroup(const shared_ptr<BasicChannel> &channel) {
if(!this->server || !channel) return {};
return this->server->groups->getChannelGroup(this->getClientDatabaseId(), channel, true);
}
bool DataClient::serverGroupAssigned(const shared_ptr<groups::ServerGroup> &group) {
auto ref_server = this->server;
auto group_manager = ref_server ? ref_server->group_manager() : serverInstance->group_manager();
auto assignments = group_manager->assignments().server_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->getClientDatabaseId());
for(const auto& assigned_group_id : assignments) {
if(assigned_group_id == group->group_id()) {
return true;
}
}
bool DataClient::serverGroupAssigned(const shared_ptr<Group> &group) {
for(const auto &gr : this->assignedServerGroups())
if(gr->group == group) return true;
return false;
}
bool DataClient::channelGroupAssigned(const shared_ptr<groups::ChannelGroup> &group, const shared_ptr<BasicChannel> &channel) {
if(!channel) {
return false;
}
std::shared_ptr<BasicChannel> interited_channel{channel};
return this->assignedChannelGroup(interited_channel) == group;
bool DataClient::channelGroupAssigned(const shared_ptr<Group> &group, const shared_ptr<BasicChannel> &channel) {
if(!channel) return false;
auto gr = this->assignedChannelGroup(channel);
sassert(gr);
if(!gr) return false;
return gr->group == group;
}
std::string DataClient::getAvatarId() {

View File

@ -19,65 +19,75 @@ namespace ts {
namespace server {
class VirtualServer;
namespace groups {
class ServerGroup;
class ChannelGroup;
typedef void TemporaryAssignmentsLock;
}
class DataClient {
friend class VirtualServer;
friend class QueryServer;
friend class music::MusicBotManager;
public:
struct PropertyWrapper {
std::shared_ptr<Properties> handle;
Properties* operator->() {
return handle.get();
}
const Properties* operator->() const {
return handle.get();
}
template<class F>
std::result_of_t<F(Properties &)> operator->*(F &&f) {
return std::forward<F>(f)(*handle);
}
template<class F>
std::result_of_t<F(Properties const &)> operator->*(F &&f) const {
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>&);
virtual ~DataClient();
inline PropertyWrapper properties() { return PropertyWrapper{this->_properties}; }
inline const PropertyWrapper properties() const { return PropertyWrapper{this->_properties}; }
[[nodiscard]] inline auto permissions(){ return this->clientPermissions; }
PropertyWrapper properties(){ return { this->_properties }; }
/* main permission calculate function */
/**
* Calculate the given permissions.
* This method can be called from everywhere without any locking needed.
* @param channel
* @param granted
* @return
*/
permission::v2::PermissionFlaggedValue calculate_permission(
permission::PermissionType,
ChannelId channel,
bool granted = false
bool granted = false,
std::shared_ptr<CalculateCache> cache = nullptr
);
/**
* Calculate the given permissions.
* This method can be called from everywhere without any locking needed.
* @param channel
* @param granted
* @return
*/
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> calculate_permissions(
const std::deque<permission::PermissionType>&,
ChannelId channel,
bool granted = false
bool granted = false,
std::shared_ptr<CalculateCache> cache = nullptr
);
virtual std::vector<std::shared_ptr<groups::ServerGroup>> assignedServerGroups();
virtual std::shared_ptr<groups::ChannelGroup> assignedChannelGroup(std::shared_ptr<BasicChannel> &);
virtual bool serverGroupAssigned(const std::shared_ptr<groups::ServerGroup> &);
virtual bool channelGroupAssigned(const std::shared_ptr<groups::ChannelGroup> &, const std::shared_ptr<BasicChannel> &);
virtual std::vector<std::shared_ptr<GroupAssignment>> assignedServerGroups();
virtual std::shared_ptr<GroupAssignment> assignedChannelGroup(const std::shared_ptr<BasicChannel> &);
virtual bool serverGroupAssigned(const std::shared_ptr<Group> &);
virtual bool channelGroupAssigned(const std::shared_ptr<Group> &, const std::shared_ptr<BasicChannel> &);
virtual std::string getDisplayName() { return this->properties()[property::CLIENT_NICKNAME]; }
virtual std::string getLoginName() { return this->properties()[property::CLIENT_LOGIN_NAME]; }
virtual void setDisplayName(std::string displayName) { this->properties()[property::CLIENT_NICKNAME] = displayName; }
[[nodiscard]] inline std::shared_ptr<VirtualServer> getServer() { return this->server; }
[[nodiscard]] inline ServerId getServerId() {
auto server_ref = this->getServer();
return server_ref ? server_ref->getServerId() : (ServerId) 0;
}
virtual ClientType getExternalType(){
uint8_t type = this->properties()[property::CLIENT_TYPE];
@ -100,10 +110,9 @@ namespace ts {
std::shared_ptr<VirtualServer> server;
std::shared_ptr<permission::v2::PermissionManager> clientPermissions = nullptr;
std::shared_ptr<PropertyManager> _properties;
std::shared_ptr<Properties> _properties;
std::shared_ptr<BasicChannel> currentChannel = nullptr;
std::shared_ptr<groups::TemporaryAssignmentsLock> temporary_assignments_lock{};
};
}
}

View File

@ -29,13 +29,13 @@ bool InternalClient::close_connection(const std::chrono::system_clock::time_poin
logError(this->getServerId(), "Internal client is force to disconnect?");
if(this->server)
this->server->unregisterInternalClient(static_pointer_cast<InternalClient>(this->ref()));
this->server->unregisterInternalClient(static_pointer_cast<InternalClient>(_this.lock()));
this->properties()[property::CLIENT_ID] = 0;
return true;
}
void InternalClient::tick_server(const std::chrono::system_clock::time_point &time) {
ConnectedClient::tick_server(time);
void InternalClient::tick(const std::chrono::system_clock::time_point &time) {
ConnectedClient::tick(time);
}
bool InternalClient::disconnect(const std::string &reason) {

View File

@ -2,22 +2,24 @@
#include "ConnectedClient.h"
namespace ts::server {
class VirtualServer;
class InternalClient : public ConnectedClient {
public:
InternalClient(sql::SqlManager*, const std::shared_ptr<VirtualServer>&, std::string, bool);
~InternalClient();
namespace ts {
namespace server {
class InternalClient : public ConnectedClient {
public:
InternalClient(sql::SqlManager*, const std::shared_ptr<server::VirtualServer>&, std::string, bool);
~InternalClient();
void initialize_weak_reference(const std::shared_ptr<ConnectedClient> &client) override {
ConnectedClient::initialize_weak_reference(client);
}
void setSharedLock(const std::shared_ptr<ConnectedClient>& _this){
assert(_this.get() == this);
this->_this = _this;
}
void sendCommand(const ts::Command &command, bool low) override;
void sendCommand(const ts::command_builder &command, bool low) override;
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
bool disconnect(const std::string &reason) override;
protected:
void tick_server(const std::chrono::system_clock::time_point &time) override;
};
void sendCommand(const ts::Command &command, bool low) override;
void sendCommand(const ts::command_builder &command, bool low) override;
bool close_connection(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point()) override;
bool disconnect(const std::string &reason) override;
protected:
void tick(const std::chrono::system_clock::time_point &time) override;
};
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,113 +1,100 @@
#pragma once
#include "ConnectedClient.h"
#include "./shared/WhisperHandler.h"
#include <json/json.h>
#include <src/rtc/lib.h>
#include "ConnectedClient.h"
namespace ts::server {
class VirtualServer;
class SpeakingClient : public ConnectedClient {
public:
struct VoicePacketFlags {
bool encrypted : 1;
bool head : 1;
bool fragmented : 1; /* used by MONO. IDK What this is */
bool new_protocol : 1;
char _unused : 4;
namespace ts {
namespace server {
class VirtualServer;
class SpeakingClient : public ConnectedClient {
public:
struct VoicePacketFlags {
bool encrypted : 1;
bool head : 1;
bool fragmented : 1; /* used by MONO. IDK What this is */
bool new_protocol : 1;
char _unused : 4;
VoicePacketFlags() : encrypted{false}, head{false}, fragmented{false}, new_protocol{false}, _unused{0} { }
};
static_assert(sizeof(VoicePacketFlags) == 1);
VoicePacketFlags() : encrypted{false}, head{false}, fragmented{false}, new_protocol{false}, _unused{0} { }
};
static_assert(sizeof(VoicePacketFlags) == 1);
enum HandshakeState {
BEGIN,
IDENTITY_PROOF,
SUCCEEDED
};
enum IdentityType : uint8_t {
TEASPEAK_FORUM,
TEAMSPEAK,
NICKNAME,
enum HandshakeState {
BEGIN,
IDENTITY_PROOF,
SUCCEEDED
};
enum IdentityType : uint8_t {
TEASPEAK_FORUM,
TEAMSPEAK,
NICKNAME,
UNSET = 0xff
};
UNSET = 0xff
};
SpeakingClient(sql::SqlManager* a, const std::shared_ptr<VirtualServer>& b);
~SpeakingClient() override;
SpeakingClient(sql::SqlManager* a, const std::shared_ptr<VirtualServer>& b) : ConnectedClient(a, b) {
speak_begin = std::chrono::system_clock::now();
speak_last_packet = std::chrono::system_clock::now();
};
~SpeakingClient() override = default;
/* TODO: Remove this method. Currently only the music but uses that to broadcast his audio */
virtual void send_voice_packet(const pipes::buffer_view& /* voice packet data */, const VoicePacketFlags& /* flags */) = 0;
bool should_handle_voice_packet(size_t /* size */);
//Voice
virtual void send_voice_packet(const pipes::buffer_view& /* voice packet data */, const VoicePacketFlags& /* flags */) = 0;
virtual bool shouldReceiveVoice(const std::shared_ptr<ConnectedClient> &sender);
virtual bool shouldReceiveVoice(const std::shared_ptr<ConnectedClient> &sender);
bool shouldReceiveVoiceWhisper(const std::shared_ptr<ConnectedClient> &sender);
//Whisper
bool shouldReceiveVoiceWhisper(const std::shared_ptr<ConnectedClient> &sender);
virtual void send_voice_whisper_packet(const pipes::buffer_view& /* voice packet data */, const VoicePacketFlags& /* flags */) = 0;
inline std::chrono::milliseconds takeSpokenTime() {
auto time = this->speak_time;
this->speak_time = std::chrono::milliseconds(0);
return time;
}
inline std::chrono::milliseconds takeSpokenTime() {
auto time = this->speak_time;
this->speak_time = std::chrono::milliseconds(0);
return time;
}
protected:
void tick(const std::chrono::system_clock::time_point &time) override;
[[nodiscard]] inline whisper::WhisperHandler& whisper_handler() { return this->whisper_handler_; }
protected:
void tick_server(const std::chrono::system_clock::time_point &time) override;
protected:
public:
void updateChannelClientProperties(bool channel_lock, bool notify) override;
public:
void updateChannelClientProperties(bool channel_lock, bool notify) override;
protected:
command_result handleCommand(Command &command) override;
protected:
command_result handleCommand(Command &command) override;
public:
void handlePacketVoice(const pipes::buffer_view&, bool head, bool fragmented);
virtual void handlePacketVoiceWhisper(const pipes::buffer_view&, bool /* new */);
public:
virtual void processJoin();
void processLeave();
void processJoin();
void processLeave();
virtual command_result handleCommandHandshakeBegin(Command&);
virtual command_result handleCommandHandshakeIdentityProof(Command &);
virtual command_result handleCommandClientInit(Command&);
virtual command_result handleCommandHandshakeBegin(Command&);
virtual command_result handleCommandHandshakeIdentityProof(Command &);
virtual command_result handleCommandClientInit(Command&);
virtual command_result handleCommandRtcSessionDescribe(Command &command);
virtual command_result handleCommandRtcSessionReset(Command &command);
virtual command_result handleCommandRtcIceCandidate(Command &);
virtual command_result handleCommandBroadcastAudio(Command &);
virtual command_result handleCommandBroadcastVideo(Command &);
virtual command_result handleCommandBroadcastVideoJoin(Command &);
virtual command_result handleCommandBroadcastVideoLeave(Command &);
virtual command_result handleCommandBroadcastVideoConfig(Command &);
virtual command_result handleCommandBroadcastVideoConfigure(Command &);
void triggerVoiceEnd();
inline void updateSpeak(bool onlyUpdate, const std::chrono::system_clock::time_point &time);
std::chrono::milliseconds speak_accuracy = std::chrono::seconds{1};
/* clientinit method helpers */
command_result applyClientInitParameters(Command&);
command_result resolveClientInitBan();
threads::Mutex speak_lock;
std::chrono::milliseconds speak_time = std::chrono::milliseconds{0};
std::chrono::system_clock::time_point speak_begin;
std::chrono::system_clock::time_point speak_last_packet;
void triggerVoiceEnd();
void updateSpeak(bool onlyUpdate, const std::chrono::system_clock::time_point &time);
std::chrono::milliseconds speak_accuracy{1000};
std::chrono::system_clock::time_point speak_last_no_whisper_target;
std::chrono::system_clock::time_point speak_last_too_many_whisper_targets;
std::mutex speak_mutex;
std::chrono::milliseconds speak_time{0};
std::chrono::system_clock::time_point speak_begin;
std::chrono::system_clock::time_point speak_last_packet;
permission::v2::PermissionFlaggedValue max_idle_time{permission::v2::empty_permission_flagged_value};
struct {
HandshakeState state{HandshakeState::BEGIN};
std::chrono::system_clock::time_point speak_last_no_whisper_target;
std::chrono::system_clock::time_point speak_last_too_many_whisper_targets;
permission::v2::PermissionFlaggedValue max_idle_time{permission::v2::empty_permission_flagged_value};
struct {
HandshakeState state{HandshakeState::BEGIN};
IdentityType identityType{IdentityType::UNSET};
std::string proof_message;
//TeamSpeak
std::shared_ptr<ecc_key> identityKey;
//TeaSpeak
std::shared_ptr<Json::Value> identityData;
} handshake;
whisper::WhisperHandler whisper_handler_;
bool rtc_session_pending_describe{false};
rtc::RTCClientId rtc_client_id{0};
};
IdentityType identityType{IdentityType::UNSET};
std::string proof_message;
//TeamSpeak
std::shared_ptr<ecc_key> identityKey;
//TeaSpeak
std::shared_ptr<Json::Value> identityData;
} handshake;
};
}
}

View File

@ -35,7 +35,7 @@ command_result SpeakingClient::handleCommandHandshakeBegin(Command& cmd) { //If
if(authenticationMethod == IdentityType::TEAMSPEAK) {
this->handshake.identityType = IdentityType::TEAMSPEAK;
auto identity = base64::decode(cmd["publicKey"].string());
auto identity = base64::decode(cmd["publicKey"]);
this->properties()[property::CLIENT_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(cmd["publicKey"].string()));
this->handshake.identityKey = shared_ptr<ecc_key>(new ecc_key{}, free_ecc);
@ -144,7 +144,7 @@ command_result SpeakingClient::handleCommandHandshakeIdentityProof(Command& cmd)
this->properties()[property::CLIENT_TEAFORO_NAME] = (*this->handshake.identityData)["user_name"].asString();
this->handshake.state = HandshakeState::SUCCEEDED;
} else if(this->handshake.identityType == IdentityType::TEAMSPEAK) {
auto proof = base64::decode(cmd["proof"].string());
auto proof = base64::decode(cmd["proof"]);
int result;
if(ecc_verify_hash((u_char*) proof.data(), proof.length(), (u_char*) this->handshake.proof_message.data(), this->handshake.proof_message.length(), &result, this->handshake.identityKey.get()) != CRYPT_OK)

View File

@ -2,5 +2,4 @@
// Created by WolverinDEV on 07/05/2020.
//
#include <log/LogUtils.h>
#include "bulk_parsers.h"

View File

@ -6,12 +6,12 @@
#include <PermissionManager.h>
#include <src/client/ConnectedClient.h>
#include "./helpers.h"
#include "../../manager/ActionLogger.h"
namespace ts::command::bulk_parser {
template <bool kParseValue>
class PermissionBulkParser {
public:
explicit PermissionBulkParser(ts::ParameterBulk& bulk, bool parse_value) {
explicit PermissionBulkParser(ts::ParameterBulk& bulk) {
if(bulk.has("permid")) {
auto type = bulk["permid"].as<permission::PermissionType>();
if ((type & PERM_ID_GRANT) != 0) {
@ -33,7 +33,7 @@ namespace ts::command::bulk_parser {
return;
}
if(parse_value) {
if(kParseValue) {
if(!bulk.has("permvalue")) {
this->error_.reset(ts::command_result{error::parameter_missing, "permvalue"});
return;
@ -45,7 +45,7 @@ namespace ts::command::bulk_parser {
}
}
PermissionBulkParser(const PermissionBulkParser&) = delete;
PermissionBulkParser(PermissionBulkParser&&) noexcept = default;
PermissionBulkParser(PermissionBulkParser&&) = default;
~PermissionBulkParser() {
this->error_.release_data();
@ -78,9 +78,9 @@ namespace ts::command::bulk_parser {
inline void apply_to(const std::shared_ptr<permission::v2::PermissionManager>& manager, permission::v2::PermissionUpdateType mode) const {
if(this->is_grant_permission()) {
this->old_value = manager->set_permission(this->permission_type(), { 0, this->value() }, permission::v2::PermissionUpdateType::do_nothing, mode);
manager->set_permission(this->permission_type(), { 0, this->value() }, permission::v2::PermissionUpdateType::do_nothing, mode);
} else {
this->old_value = manager->set_permission(
manager->set_permission(
this->permission_type(),
{ this->value(), 0 },
mode,
@ -91,61 +91,11 @@ namespace ts::command::bulk_parser {
}
}
inline void log_update(
server::log::PermissionActionLogger& logger,
ServerId sid,
const std::shared_ptr<server::ConnectedClient>& issuer,
server::log::PermissionTarget target,
permission::v2::PermissionUpdateType mode,
uint64_t id1, const std::string& id1_name,
uint64_t id2, const std::string& id2_name
) const {
if(this->is_grant_permission()) {
switch (mode) {
case permission::v2::delete_value:
if(!this->old_value.flags.grant_set)
return;
logger.log_permission_remove_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.grant);
break;
case permission::v2::set_value:
if(this->old_value.flags.grant_set) {
logger.log_permission_edit_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.grant, this->value_);
} else {
logger.log_permission_add_grant(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->value_);
}
break;
case permission::v2::do_nothing:
break;
}
} else {
switch (mode) {
case permission::v2::delete_value:
if(!this->old_value.flags.value_set)
return;
logger.log_permission_remove_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip);
break;
case permission::v2::set_value:
if(this->old_value.flags.value_set) {
logger.log_permission_edit_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip, this->value_, this->flag_negated_, this->flag_skip_);
} else {
logger.log_permission_add_value(sid, issuer, target, id1, id1_name, id2, id2_name, *this->permission_, this->old_value.values.value, this->old_value.flags.negate, this->old_value.flags.skip);
}
break;
case permission::v2::do_nothing:
break;
}
}
}
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()) {
this->old_value = manager->set_channel_permission(this->permission_type(), channel_id, { this->value(), true }, permission::v2::PermissionUpdateType::do_nothing, mode);
manager->set_channel_permission(this->permission_type(), channel_id, { this->value(), true }, permission::v2::PermissionUpdateType::do_nothing, mode);
} else {
this->old_value = manager->set_channel_permission(
manager->set_channel_permission(
this->permission_type(),
channel_id,
{ this->value(), true },
@ -172,16 +122,16 @@ namespace ts::command::bulk_parser {
permission::PermissionValue value_{0};
ts::command_result error_{error::ok};
mutable permission::v2::PermissionContainer old_value{};
#ifndef NDEBUG
bool error_released_{false};
#endif
};
template <bool kParseValue>
class PermissionBulksParser {
public:
PermissionBulksParser(const PermissionBulksParser&) = delete;
PermissionBulksParser(PermissionBulksParser&&) noexcept = default;
PermissionBulksParser(PermissionBulksParser&&) = default;
template <typename base_iterator>
struct FilteredPermissionIterator : public base_iterator {
@ -217,7 +167,7 @@ namespace ts::command::bulk_parser {
};
struct FilteredPermissionListIterable {
typedef typename std::vector<PermissionBulkParser>::const_iterator const_iterator;
typedef typename std::vector<PermissionBulkParser<kParseValue>>::const_iterator const_iterator;
public:
FilteredPermissionListIterable(const_iterator begin, const_iterator end) noexcept : begin_{begin}, end_{end} {}
@ -233,11 +183,10 @@ namespace ts::command::bulk_parser {
const_iterator end_;
};
explicit PermissionBulksParser(ts::Command& command, bool parse_value) : parse_value{parse_value} {
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], parse_value);
}
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) {
@ -245,24 +194,19 @@ namespace ts::command::bulk_parser {
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& permission : this->permissions_) {
if(permission.has_error()) {
continue;
}
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& permission = this->permissions_[index];
if(permission.has_error()) {
continue;
}
PermissionBulkParser<kParseValue>& permission = this->permissions_[index];
if(permission.has_error()) continue;
if(this->parse_value && permission_require_granted_value(permission.permission_type()) && !permission::v2::permission_granted(permission.value(), max_value)) {
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;
}
@ -284,15 +228,13 @@ namespace ts::command::bulk_parser {
assert(!std::exchange(this->result_created_, true));
ts::command_result_bulk result{};
for(auto& permission : this->permissions_) {
for(auto& permission : this->permissions_)
result.insert_result(std::forward<ts::command_result>(permission.release_error()));
}
return ts::command_result{std::move(result)};
}
private:
bool parse_value;
std::vector<PermissionBulkParser> permissions_{};
std::vector<PermissionBulkParser<kParseValue>> permissions_{};
#ifndef NDEBUG
bool result_created_{false};
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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