44 Commits

Author SHA1 Message Date
WolverinDEV ee69287813 Merge remote-tracking branch 'origin/1.4.10-openssl' into 1.4.10-openssl 2020-04-14 11:49:43 +02:00
WolverinDEV f205075d97 Changed for new deploy algorithm 2020-04-14 11:49:07 +02:00
WolverinDEV d570d03307 Updated music rev 2020-04-12 12:38:28 +00:00
WolverinDEV 483e188c37 New snapshot system proposal 2020-04-11 20:41:09 +02:00
WolverinDEV 0f1665c97d Fixed invalid channel flags for changing the default channel 2020-04-11 12:31:07 +02:00
WolverinDEV 7ef77c3160 Some updates 2020-04-10 23:29:51 +02:00
WolverinDEV 74fa735004 Some smaller updates 2020-04-08 13:50:03 +02:00
WolverinDEV eb61daab43 A lot of updates for 1.4.12 2020-04-08 13:01:41 +02:00
WolverinDEV a2f52d98db Deleted the finally obsolete big handler file 2020-04-08 03:23:21 +02:00
WolverinDEV b0f0710b5b Updated the property system and added a packet loss calculator 2020-04-08 02:56:08 +02:00
WolverinDEV 421f04fe60 Fixed some small stuff 2020-04-04 12:22:20 +02:00
WolverinDEV 3d90e8b57a Removed a debugging message 2020-04-04 12:01:10 +02:00
WolverinDEV a1ea11a196 Music bot fix 2020-04-04 11:59:33 +02:00
WolverinDEV f830a8023d Updated some small stuff 2020-04-04 01:38:37 +02:00
WolverinDEV a36f0dbf02 Fixed log 2020-04-03 19:46:18 +02:00
WolverinDEV 824aec6322 Fixed some stuff 2020-04-03 19:26:22 +02:00
WolverinDEV 8e4d52ddd2 License server memory validation 2020-04-03 19:07:03 +02:00
WolverinDEV 240052da3a Fixed a missing ! for the web client 2020-04-03 14:27:02 +02:00
WolverinDEV 8d42156383 Fixed TeaClient join 2020-04-03 13:56:39 +02:00
WolverinDEV 95d52e4997 Updating some revs 2020-04-03 13:50:20 +02:00
WolverinDEV e439d4bc39 Fixed web hangup 2020-04-02 20:12:29 +02:00
WolverinDEV 702dd87c41 Updating rev 2020-04-02 19:29:05 +02:00
WolverinDEV ca2de244d8 Added another debug possibility 2020-04-02 19:24:57 +02:00
WolverinDEV b594c9566c Added some debug info 2020-04-02 19:18:58 +02:00
WolverinDEV 1865a3b20d Fixed the permfind command 2020-04-02 14:10:33 +02:00
WolverinDEV 924e553664 Updated revs 2020-04-01 20:13:35 +02:00
WolverinDEV b005016c48 Some updates 2020-03-31 22:01:47 +02:00
WolverinDEV c4eff7c743 Fixing zombie processes after a stream has been closed 2020-03-31 21:51:58 +02:00
WolverinDEV d669708989 Updated the changelog & command docs 2020-03-30 22:53:47 +02:00
WolverinDEV 90353c2bc5 Fixed some stuff 2020-03-30 22:53:15 +02:00
WolverinDEV 5c408948f6 Fixed stuff attempt 3 2020-03-28 23:11:41 +01:00
WolverinDEV 63219434d3 Tryfix again 2020-03-28 23:08:11 +01:00
WolverinDEV f596151c42 Fixed command dropping 2020-03-28 22:49:22 +01:00
WolverinDEV b7b22dc89e Fixed two music bot hangups 2020-03-26 17:34:31 +01:00
WolverinDEV 0778b04b6b Fixed lib gen script 2020-03-25 20:48:35 +01:00
WolverinDEV c627690011 Optimized 1.4.11 2020-03-25 20:36:44 +01:00
WolverinDEV d52496600f Some fixes 2020-03-23 10:58:07 +01:00
WolverinDEV 1a4a6721a1 Added messages 2020-03-20 17:58:58 +01:00
WolverinDEV 124844b9d4 Fixed the web client 2020-03-20 17:00:15 +01:00
WolverinDEV c74804ccf5 Fixed that music bots now not appear anymore... 2020-03-20 01:05:39 +01:00
WolverinDEV e5f7b3bd32 Some changes 2020-03-20 00:56:36 +01:00
WolverinDEV 0705c68b7b Fixed some missing permissions 2020-03-19 01:47:48 +01:00
WolverinDEV 82e65c712b Updating git rev 2020-03-11 10:36:55 +01:00
WolverinDEV 3f9ee1c444 Fixed the query account password change parameter 2020-03-11 10:33:39 +01:00
81 changed files with 3216 additions and 9223 deletions
+6 -3
View File
@@ -30,9 +30,12 @@ void AbstractMusicPlayer::unregisterEventHandler(const std::string& string) {
}
void AbstractMusicPlayer::fireEvent(MusicEvent event) {
std::lock_guard lock(this->eventLock);
auto listCopy = this->eventHandlers; //Copy for remove while fire
for(const auto& entry : listCopy)
decltype(this->eventHandlers) handlers{};
{
std::lock_guard lock(this->eventLock);
handlers = this->eventHandlers; //Copy for remove while fire
}
for(const auto& entry : handlers)
entry.second(event);
}
+1 -1
View File
@@ -533,7 +533,7 @@ std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> DatabaseHa
bool DatabaseHandler::register_license_upgrade(license_key_id_t old_key_id, license_key_id_t new_key_id,
const std::chrono::system_clock::time_point &begin_timestamp, const std::chrono::system_clock::time_point &end_timestamp, const std::string &license_key) {
auto upgrade_id = std::chrono::system_clock::now().time_since_epoch().count();
auto upgrade_id = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
auto sql_result = sql::command(this->sql(), "INSERT INTO `license_upgrades` (`upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license`) VALUES"
"(:upgrade_id, :old_key_id, :new_key_id, :timestamp_begin, :timestamp_end, 1, 0, :license)",
variable{":upgrade_id", upgrade_id},
+7 -1
View File
@@ -198,9 +198,15 @@ bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client,
}
}
this->manager->logRequest(remote_license->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid());
} else {
} else {
/* shall never happen, by default each server has the default license */
response.set_valid(true);
}
if(pkt.has_memory_valid() && !pkt.memory_valid()) {
response.set_invalid_reason("server memory seems to be invalid");
response.set_valid(false);
logError(LOG_GENERAL, "Server {} has patched license memory!", client->address());
}
if(client->protocol.version == 2) {
if(response.valid())
+1 -1
View File
@@ -8,7 +8,7 @@
#include "license.h"
namespace license::client {
class LicenseServerClient {
class LicenseServerClient : public std::enable_shared_from_this<LicenseServerClient> {
public:
enum ConnectionState {
CONNECTING,
@@ -41,6 +41,7 @@ message ServerValidation {
required bool license_info = 2;
optional bytes license = 3;
optional ServerInfo info = 4; //Change somewhere to required but its currently for legacy support
optional bool memory_valid = 5;
}
message LicenseResponse {
+18 -8
View File
@@ -7,6 +7,7 @@
#include <event.h>
#include <ThreadPool/ThreadHelper.h>
#include <misc/endianness.h>
#include <shared/include/license/client.h>
#include "shared/include/license/client.h"
#include "crypt.h"
@@ -42,11 +43,16 @@ LicenseServerClient::LicenseServerClient(const sockaddr_in &address, int pversio
}
LicenseServerClient::~LicenseServerClient() {
this->close_connection();
const auto is_event_loop = this->network.event_dispatch.get_id() == std::this_thread::get_id();
{
std::unique_lock slock{this->connection_lock};
this->connection_state = ConnectionState::UNCONNECTED;
}
this->cleanup_network_resources(); /* force cleanup ignoring the previous state */
if(is_event_loop) this->network.event_dispatch.detach();
if(this->buffers.read)
Buffer::free(this->buffers.read);
threads::save_join(this->network.event_dispatch, false);
}
bool LicenseServerClient::start_connection(std::string &error) {
@@ -88,14 +94,17 @@ bool LicenseServerClient::start_connection(std::string &error) {
this->network.event_base = event_base_new();
this->network.event_read = event_new(this->network.event_base, this->network.file_descriptor, EV_READ | EV_PERSIST, [](int, short e, void* _this) {
auto client = reinterpret_cast<LicenseServerClient*>(_this);
auto client_ref = client->shared_from_this(); /* We're not allowed to delete outself while hading data. This will lead to dangling pointers */
client->callback_read(e);
client_ref.reset();
}, this);
this->network.event_write = event_new(this->network.event_base, this->network.file_descriptor, EV_WRITE, [](int, short e, void* _this) {
auto client = reinterpret_cast<LicenseServerClient*>(_this);
auto client_ref = client->shared_from_this(); /* We're not allowed to delete outself while hading data. This will lead to dangling pointers */
client->callback_write(e);
client_ref.reset();
}, this);
event_dispatch_spawned = true;
this->network.event_dispatch = std::thread([&] {
signal(SIGPIPE, SIG_IGN);
@@ -114,10 +123,6 @@ bool LicenseServerClient::start_connection(std::string &error) {
return true;
error_cleanup:
this->cleanup_network_resources();
if(!event_dispatch_spawned) {
event_base_free(this->network.event_base);
this->network.event_base = nullptr;
}
this->connection_state = ConnectionState::UNCONNECTED;
return false;
}
@@ -243,8 +248,11 @@ void LicenseServerClient::callback_write(short events) {
}
if(events & EV_WRITE) {
if(this->connection_state == ConnectionState::CONNECTING)
if(this->connection_state == ConnectionState::CONNECTING) {
this->callback_socket_connected();
if(this->connection_state == ConnectionState::UNCONNECTED) /* state may change in the callback */
return;
}
ssize_t written_bytes{0};
@@ -365,6 +373,8 @@ void LicenseServerClient::handle_data(void *recv_buffer, size_t length) {
if(buffer_length < header->length + sizeof(protocol::packet_header)) return;
this->handle_raw_packet(header->packetId, buffer_ptr + buffer_offset + sizeof(protocol::packet_header), header->length);
if(this->connection_state == ConnectionState::UNCONNECTED) return; /* state may change while we're handing the packet */
buffer_offset += header->length + sizeof(protocol::packet_header);
buffer_length -= header->length + sizeof(protocol::packet_header);
}
+1 -1
Submodule music updated: ad24c38923...8e1ce32ae0
+31 -2
View File
@@ -49,6 +49,7 @@ set(SERVER_SOURCE_FILES
src/client/voice/VoiceClientCommandHandler.cpp
src/client/voice/VoiceClientPacketHandler.cpp
src/client/voice/VoiceClientView.cpp
src/client/voice/PacketStatistics.cpp
src/TS3ServerClientManager.cpp
src/VirtualServer.cpp
src/TS3ServerHeartbeat.cpp
@@ -136,6 +137,13 @@ set(SERVER_SOURCE_FILES
src/weblist/WebListManager.cpp
src/weblist/TeamSpeakWebClient.cpp
src/snapshots/permission.cpp
src/snapshots/client.cpp
src/snapshots/channel.cpp
src/snapshots/server.cpp
src/snapshots/groups.cpp
src/snapshots/deploy.cpp
src/manager/ConversationManager.cpp
src/client/SpeakingClientHandshake.cpp
src/client/command_handler/music.cpp src/client/command_handler/file.cpp)
@@ -235,7 +243,7 @@ target_link_libraries(PermMapHelper
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
SET(CPACK_PACKAGE_VERSION_MINOR "4")
SET(CPACK_PACKAGE_VERSION_PATCH "10")
SET(CPACK_PACKAGE_VERSION_PATCH "12")
if (BUILD_TYPE_NAME EQUAL OFF)
SET(CPACK_PACKAGE_VERSION_DATA "beta")
elseif (BUILD_TYPE_NAME STREQUAL "")
@@ -307,4 +315,25 @@ if (NOT DISABLE_JEMALLOC)
jemalloc
)
add_definitions(-DHAVE_JEMALLOC)
endif ()
endif ()
add_executable(Snapshots-Permissions-Test src/snapshots/permission.cpp tests/snapshots/permission.cpp)
target_link_libraries(Snapshots-Permissions-Test PUBLIC
TeaSpeak
CXXTerminal::static #Static
${StringVariable_LIBRARIES_STATIC}
${YAML_CPP_LIBRARIES}
pthread
stdc++fs
libevent::core libevent::pthreads
#Require a so
sqlite3
DataPipes::rtc::shared
tomcrypt::static
tommath::static
${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10
)
target_include_directories(Snapshots-Permissions-Test PUBLIC ${CMAKE_SOURCE_DIR}/server/src/)
+30 -10
View File
@@ -129,7 +129,6 @@ int main(int argc, char** argv) {
terminal::install();
if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
}
assert(ts::property::impl::validateUnique());
if(arguments.cmdOptionExists("--help") || arguments.cmdOptionExists("-h")) {
#define HELP_FMT " {} {} | {}"
@@ -309,6 +308,32 @@ int main(int argc, char** argv) {
logMessageFmt(true, LOG_GENERAL, strobf("[]---------------------------------------------------------[]").string());
}
{
rlimit rlimit{0, 0};
//forum.teaspeak.de/index.php?threads/2570/
constexpr auto seek_help_message = "Fore more help visit the forum and read this thread (https://forum.teaspeak.de/index.php?threads/2570/).";
if(getrlimit(RLIMIT_NOFILE, &rlimit) != 0) {
//prlimit -n4096 -p pid_of_process
logWarningFmt(true, LOG_INSTANCE, "Failed to get open file rlimit ({}). Please ensure its over 16384.", strerror(errno));
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
} else {
const auto original = rlimit.rlim_cur;
rlimit.rlim_cur = std::max(rlimit.rlim_cur, std::min(rlimit.rlim_max, (rlim_t) 16384));
if(original != rlimit.rlim_cur) {
if(setrlimit(RLIMIT_NOFILE, &rlimit) != 0) {
logErrorFmt(true, LOG_INSTANCE, "Failed to set open file rlimit to {} ({}). Please ensure its over 16384.", rlimit.rlim_cur, strerror(errno));
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
goto rlimit_updates;
}
}
if(rlimit.rlim_cur < 16384) {
logWarningFmt(true, LOG_INSTANCE, "Open file rlimit is bellow 16384 ({}). Please increase the system file descriptor limits.", rlimit.rlim_cur);
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
}
}
rlimit_updates:;
}
logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true));
logMessage(LOG_GENERAL, "Starting music providers");
@@ -371,18 +396,13 @@ int main(int argc, char** argv) {
auto password = arguments.cmdOptionExists("-q") ? arguments.get_option("-q") : arguments.get_option("--set_query_password");
if(!password.empty()) {
logMessageFmt(true, LOG_GENERAL, "Updating server admin query password to \"{}\"", password);
auto accounts = serverInstance->getQueryServer()->find_query_accounts_by_unique_id(serverInstance->getInitialServerAdmin()->getUid());
bool found = false;
for(const auto& account : accounts) {
if(account->bound_server != 0) continue;
auto account = serverInstance->getQueryServer()->find_query_account_by_name("serveradmin");
if(!account) {
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!");
} else {
if(!serverInstance->getQueryServer()->change_query_password(account, password)) {
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! (Internal error)");
}
found = true;
break;
}
if(!found) {
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!");
}
}
}
+3 -3
View File
@@ -3,7 +3,7 @@
#Required libraries:
# "libssl.so"
# "libcrypto.so"
# "libDataPipes.so"
# "libDataPipes-Rtc-Shared.so"
# "libjemalloc.so.2"
# "libsqlite3.so.0"
# "libTeaMusic.so"
@@ -50,8 +50,8 @@ query_system_link "libcrypto.so.1.1"
cp "${library_path}" . || { echo "failed to copy libcrypto.so.1.1"; exit 1; }
# Setting up DataPipes
library_path=$(realpath "${library_base}/DataPipes/${build_path}/lib/libDataPipes-RTC.so")
cp "$library_path" . || { echo "failed to copy libDataPipes-RTC.so"; exit 1; }
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
+68 -4
View File
@@ -52,8 +52,16 @@ bool config::server::badges::allow_overwolf;
bool config::server::authentication::name;
bool config::server::clients::teamspeak;
std::string config::server::clients::extra_welcome_message_teamspeak;
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teamspeak;
bool config::server::clients::teaweb;
std::string config::server::clients::extra_welcome_message_teaweb;
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaweb;
bool config::server::clients::teaspeak;
std::string config::server::clients::extra_welcome_message_teaspeak;
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaspeak;
uint16_t config::voice::default_voice_port;
size_t config::voice::DefaultPuzzlePrecomputeSize;
@@ -509,7 +517,7 @@ vector<string> config::parseConfig(const std::string& path) {
}
}
auto currentVersion = strobf("TeaSpeak ").string() + build::version()->string(true);
auto currentVersion = config::server::default_version();
if(currentVersion != config::server::DefaultServerVersion) {
auto ref = config::server::DefaultServerVersion;
try {
@@ -1317,13 +1325,60 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
}
}
{
using WelcomeMessageType = config::server::clients::WelcomeMessageType;
BIND_GROUP(clients);
/* TeamSpeak */
{
CREATE_BINDING("teamspeak", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teamspeak, true);
ADD_DESCRIPTION("Allow/disallow the TeamSpeak 3 client to join the server.");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teamspeak_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::extra_welcome_message_teamspeak, "");
ADD_DESCRIPTION("Add an extra welcome message for TeamSpeak client users");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teamspeak_message_type", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teamspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
ADD_DESCRIPTION("The welcome message type modes");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
ADD_NOTE_RELOADABLE();
}
/* TeaSpeak */
/*
{
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teaspeak, true);
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
ADD_NOTE_RELOADABLE();
}
*/
config::server::clients::teaspeak = true;
{
CREATE_BINDING("teaspeak_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::extra_welcome_message_teaspeak, "");
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Client users");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaspeak_message_type", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
ADD_DESCRIPTION("The welcome message type modes");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
ADD_NOTE_RELOADABLE();
}
/* TeaWeb */
{
CREATE_BINDING("teaweb", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teaweb, true);
@@ -1331,9 +1386,18 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teaspeak, true);
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
CREATE_BINDING("teaweb_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::extra_welcome_message_teaweb, "");
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Web client users");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaweb_message_type", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaweb, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
ADD_DESCRIPTION("The welcome message type modes");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
ADD_NOTE_RELOADABLE();
}
}
+23
View File
@@ -7,8 +7,10 @@
#undef byte
#endif
#include <spdlog/common.h>
#include <misc/strobf.h>
#include "geo/GeoLocation.h"
#include "../../license/shared/include/license/license.h"
#include "build.h"
namespace YAML {
class Node;
@@ -86,12 +88,33 @@ namespace ts::config {
}
namespace clients {
enum WelcomeMessageType {
WELCOME_MESSAGE_TYPE_MIN,
WELCOME_MESSAGE_TYPE_NONE = WELCOME_MESSAGE_TYPE_MIN,
WELCOME_MESSAGE_TYPE_CHAT,
WELCOME_MESSAGE_TYPE_POKE,
WELCOME_MESSAGE_TYPE_MAX
};
extern bool teamspeak;
extern std::string extra_welcome_message_teamspeak;
extern WelcomeMessageType extra_welcome_message_type_teamspeak;
extern bool teaspeak;
extern std::string extra_welcome_message_teaspeak;
extern WelcomeMessageType extra_welcome_message_type_teaspeak;
extern bool teaweb;
extern std::string extra_welcome_message_teaweb;
extern WelcomeMessageType extra_welcome_message_type_teaweb;
}
extern ssize_t max_virtual_server;
__attribute__((always_inline)) inline std::string default_version() { return strobf("TeaSpeak ").string() + build::version()->string(true); }
__attribute__((always_inline)) inline bool check_server_version_with_license() {
return default_version() == DefaultServerVersion || (license->isPremium() && license->isValid());
}
}
namespace voice {
+45 -267
View File
@@ -3,8 +3,9 @@
//
#include <misc/memtracker.h>
#include <utility>
#include "ConnectionStatistics.h"
#include "VirtualServer.h"
using namespace std;
using namespace std::chrono;
@@ -13,321 +14,98 @@ using namespace ts::server;
using namespace ts::stats;
using namespace ts::protocol;
ConnectionStatistics::ConnectionStatistics(const shared_ptr<ConnectionStatistics>& handle, bool properties) : handle(handle) {
ConnectionStatistics::ConnectionStatistics(shared_ptr<ConnectionStatistics> handle) : handle(std::move(handle)) {
memtrack::allocated<ConnectionStatistics>(this);
if(properties) {
this->properties = make_shared<Properties>(); //TODO load etc?
this->properties->register_property_type<property::ConnectionProperties>();
}
/*
this->properties->registerProperty("connection_packets_sent_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_sent_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_sent_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_sent_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_sent_last_second_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_sent_last_minute_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_received_last_second_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_received_last_minute_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bandwidth_sent", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bandwidth_received", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bytes_sent_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bytes_received_total", 0, PROP_STATISTIC);
*/
}
ConnectionStatistics::~ConnectionStatistics() {
memtrack::freed<ConnectionStatistics>(this);
{
lock_guard lock(this->history_lock_incoming);
for(auto entry : this->history_incoming)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
for(auto entry : this->history_file_incoming)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_incoming.clear();
this->history_file_incoming.clear();
}
{
lock_guard lock(this->history_lock_outgoing);
for(auto entry : this->history_outgoing)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
for(auto entry : this->history_file_outgoing)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_outgoing.clear();
this->history_file_outgoing.clear();
}
}
std::shared_ptr<Properties> ConnectionStatistics::statistics() {
return this->properties;
}
void ConnectionStatistics::logIncomingPacket(const category::value &category, size_t size) {
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = uint16_t(size);
assert(category >= 0 && category <= 2);
this->statistics_second_current.connection_bytes_received[category] += size;
this->statistics_second_current.connection_packets_received[category] += 1;
this->_log_incoming_packet(info_entry, category);
}
void ConnectionStatistics::_log_incoming_packet(ts::stats::StatisticEntry *info_entry, int8_t index) {
if(index >= 0 && index <= 3) {
this->connection_packets_received[index] ++;
this->connection_bytes_received[index] += info_entry->size;
}
this->connection_packets_received[0] ++;
this->connection_bytes_received[0] += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_incoming);
this->history_incoming.push_back(info_entry);
}
if(this->handle)
this->handle->_log_incoming_packet(info_entry, index);
this->handle->logIncomingPacket(category, size);
}
void ConnectionStatistics::logOutgoingPacket(const category::value &category, size_t size) {
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = uint16_t(size);
assert(category >= 0 && category <= 2);
this->statistics_second_current.connection_bytes_sent[category] += size;
this->statistics_second_current.connection_packets_sent[category] += 1;
this->_log_outgoing_packet(info_entry, category);
}
void ConnectionStatistics::_log_outgoing_packet(ts::stats::StatisticEntry *info_entry, int8_t index) {
if(index >= 0 && index <= 3) {
this->connection_packets_sent[index] ++;
this->connection_bytes_sent[index] += info_entry->size;
}
this->connection_packets_sent[0] ++;
this->connection_bytes_sent[0] += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_outgoing);
this->history_outgoing.push_back(info_entry);
}
if(this->handle)
this->handle->_log_outgoing_packet(info_entry, index);
this->handle->logOutgoingPacket(category, size);
}
/* file transfer */
void ConnectionStatistics::logFileTransferIn(uint64_t bytes) {
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = bytes;
this->_log_incoming_file_packet(info_entry);
}
void ConnectionStatistics::_log_incoming_file_packet(ts::stats::StatisticEntry *info_entry) {
this->file_bytes_received += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_incoming);
this->history_file_incoming.push_back(info_entry);
}
void ConnectionStatistics::logFileTransferIn(uint32_t bytes) {
this->statistics_second_current.file_bytes_received += bytes;
this->file_bytes_received += bytes;
if(this->handle)
this->handle->_log_incoming_file_packet(info_entry);
this->handle->logFileTransferIn(bytes);
}
void ConnectionStatistics::logFileTransferOut(uint64_t bytes) {
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = bytes;
this->_log_outgoing_file_packet(info_entry);
}
void ConnectionStatistics::_log_outgoing_file_packet(ts::stats::StatisticEntry *info_entry) {
this->file_bytes_sent += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_outgoing);
this->history_file_outgoing.push_back(info_entry);
}
void ConnectionStatistics::logFileTransferOut(uint32_t bytes) {
this->statistics_second_current.file_bytes_sent += bytes;
this->file_bytes_sent += bytes;
if(this->handle)
this->handle->_log_outgoing_file_packet(info_entry);
this->handle->logFileTransferOut(bytes);
}
void ConnectionStatistics::tick() {
StatisticEntry* entry;
{
auto timeout_min = system_clock::now() - minutes(1);
auto now = std::chrono::system_clock::now();
auto time_difference = this->last_second_tick.time_since_epoch().count() > 0 ? now - this->last_second_tick : std::chrono::seconds{1};
if(time_difference >= std::chrono::seconds{1}) {
BandwidthEntry<uint32_t> current{};
current.atomic_exchange(this->statistics_second_current);
lock_guard lock(this->history_lock_incoming);
auto period_ms = std::chrono::floor<std::chrono::milliseconds>(time_difference).count();
auto current_normalized = current.mul<long double>(1000.0 / period_ms);
while(!this->history_incoming.empty() && (entry = this->history_incoming[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->statistics_second = this->statistics_second.mul<long double>(.2) + current_normalized.mul<long double>(.8);
this->total_statistics += current;
this->history_incoming.pop_front();
}
auto current_second = std::chrono::floor<std::chrono::seconds>(now.time_since_epoch()).count();
if(statistics_minute_offset == 0)
statistics_minute_offset = current_second;
while(!this->history_file_incoming.empty() && (entry = this->history_file_incoming[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_file_incoming.pop_front();
}
}
{
auto timeout_min = system_clock::now() - minutes(1);
lock_guard lock(this->history_lock_outgoing);
while(!this->history_outgoing.empty() && (entry = this->history_outgoing[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_outgoing.pop_front();
}
while(!this->history_file_outgoing.empty() && (entry = this->history_file_outgoing[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_file_outgoing.pop_front();
}
}
if(this->properties) {
auto& _properties = *this->properties;
#define M(type, index) \
_properties[property::CONNECTION_BYTES_SENT_ ##type] = (uint64_t) this->connection_bytes_sent[index]; \
_properties[property::CONNECTION_PACKETS_SENT_ ##type] = (uint64_t) this->connection_packets_sent[index]; \
_properties[property::CONNECTION_BYTES_RECEIVED_ ##type] = (uint64_t) this->connection_bytes_received[index]; \
_properties[property::CONNECTION_PACKETS_RECEIVED_ ##type] = (uint64_t) this->connection_packets_received[index]; \
M(TOTAL, 0);
M(CONTROL, 1);
M(KEEPALIVE, 2);
M(SPEECH, 3);
_properties[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL] = (uint64_t) this->file_bytes_received;
_properties[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL] = (uint64_t) this->file_bytes_sent;
_properties[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL] = (uint64_t) this->file_bytes_received;
_properties[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL] = (uint64_t) this->file_bytes_sent;
/* fill all "lost" with the current bandwidth as well */
while(statistics_minute_offset <= current_second)
this->statistics_minute[statistics_minute_offset++ % this->statistics_minute.size()] = current_normalized;
this->last_second_tick = now;
}
}
DataSummery ConnectionStatistics::dataReport() {
DataSummery report{};
auto minTimeout = system_clock::now() - seconds(1);
{
lock_guard lock(this->history_lock_incoming);
for(const auto& elm : this->history_incoming){
if(elm->timestamp >= minTimeout) {
report.recv_second += elm->size;
}
report.recv_minute += elm->size;
}
for(const auto& elm : this->history_file_incoming) {
report.file_recv += elm->size;
}
}
{
lock_guard lock(this->history_lock_outgoing);
for(const auto& elm : this->history_outgoing){
if(elm->timestamp >= minTimeout) {
report.send_second += elm->size;
}
report.send_minute += elm->size;
}
for(const auto& elm : this->history_file_outgoing) {
report.file_send += elm->size;
}
}
report.recv_minute /= 60;
report.send_minute /= 60;
return report;
BandwidthEntry<uint32_t> ConnectionStatistics::minute_stats() const {
BandwidthEntry<uint32_t> result{};
for(const auto& second : this->statistics_minute)
result += second;
return result.mul<uint32_t>(1. / (double) this->statistics_minute.size());
}
FullReport ConnectionStatistics::full_report() {
FullReport report{};
FileTransferStatistics ConnectionStatistics::file_stats() {
FileTransferStatistics result{};
for(size_t index = 0 ; index < 4; index++) {
report.connection_bytes_sent[index] = (uint64_t) this->connection_bytes_sent[index];
report.connection_packets_sent[index] = (uint64_t) this->connection_packets_sent[index];
report.connection_bytes_received[index] = (uint64_t) this->connection_bytes_received[index];
report.connection_packets_received[index] = (uint64_t) this->connection_packets_received[index];
}
result.bytes_received = this->file_bytes_received;
result.bytes_sent = this->file_bytes_sent;
report.file_bytes_sent = this->file_bytes_sent;
report.file_bytes_received = this->file_bytes_received;
return report;
return result;
}
std::pair<uint64_t, uint64_t> ConnectionStatistics::mark_file_bytes() {
std::pair<uint64_t, uint64_t> result;
{
lock_guard lock(this->history_lock_incoming);
if(this->mark_file_bytes_received < this->file_bytes_received)
result.second = this->file_bytes_received - this->mark_file_bytes_received;
this->mark_file_bytes_received = (uint64_t) this->file_bytes_received;
}
{
lock_guard lock(this->history_lock_outgoing);
if(this->mark_file_bytes_sent < this->file_bytes_sent)
result.first = this->file_bytes_sent - this->mark_file_bytes_sent;
this->mark_file_bytes_sent = (uint64_t) this->file_bytes_sent;
+102 -66
View File
@@ -11,41 +11,97 @@ namespace ts {
}
namespace stats {
struct StatisticEntry {
std::atomic<int8_t> use_count{0};
std::chrono::time_point<std::chrono::system_clock> timestamp;
uint16_t size = 0;
template <typename value_t>
struct BandwidthEntry {
std::array<value_t, 3> connection_packets_sent{};
std::array<value_t, 3> connection_bytes_sent{};
std::array<value_t, 3> connection_packets_received{};
std::array<value_t, 3> connection_bytes_received{};
value_t file_bytes_sent{0};
value_t file_bytes_received{0};
template <typename other_type>
inline BandwidthEntry& operator=(const BandwidthEntry<other_type>& other) {
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
this->connection_packets_sent[index] = other.connection_packets_sent[index];
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
this->connection_bytes_sent[index] = other.connection_bytes_sent[index];
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
this->connection_packets_received[index] = other.connection_packets_received[index];
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
this->connection_bytes_received[index] = other.connection_bytes_received[index];
this->file_bytes_sent = other.file_bytes_sent;
this->file_bytes_received = other.file_bytes_received;
return *this;
}
template <typename target_t>
inline BandwidthEntry<target_t> mul(double factor) const {
BandwidthEntry<target_t> result{};
result = *this;
for(auto& val : result.connection_packets_sent) val *= factor;
for(auto& val : result.connection_bytes_sent) val *= factor;
for(auto& val : result.connection_packets_received) val *= factor;
for(auto& val : result.connection_bytes_received) val *= factor;
result.file_bytes_sent *= factor;
result.file_bytes_received *= factor;
return result;
}
template <typename other_type>
inline BandwidthEntry& operator+=(const BandwidthEntry<other_type>& other) {
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
this->connection_packets_sent[index] += other.connection_packets_sent[index];
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
this->connection_bytes_sent[index] += other.connection_bytes_sent[index];
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
this->connection_packets_received[index] += other.connection_packets_received[index];
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
this->connection_bytes_received[index] += other.connection_bytes_received[index];
this->file_bytes_sent += other.file_bytes_sent;
this->file_bytes_received += other.file_bytes_received;
return *this;
}
template <typename other_type>
inline BandwidthEntry operator+(const BandwidthEntry<other_type>& other) {
return BandwidthEntry{*this} += other;
}
template <typename atomic_t>
inline void atomic_exchange(BandwidthEntry<std::atomic<atomic_t>>& source) {
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
this->connection_packets_sent[index] = source.connection_packets_sent[index].exchange(0);
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
this->connection_bytes_sent[index] = source.connection_bytes_sent[index].exchange(0);
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
this->connection_packets_received[index] = source.connection_packets_received[index].exchange(0);
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
this->connection_bytes_received[index] = source.connection_bytes_received[index].exchange(0);
this->file_bytes_sent = source.file_bytes_sent.exchange(0);
this->file_bytes_received = source.file_bytes_received.exchange(0);
}
};
struct DataSummery {
uint32_t send_minute;
uint32_t send_second;
uint32_t recv_minute;
uint32_t recv_second;
uint32_t file_recv;
uint32_t file_send;
};
struct FullReport {
uint64_t connection_packets_sent[4]{0, 0, 0, 0};
uint64_t connection_bytes_sent[4]{0, 0, 0, 0};
uint64_t connection_packets_received[4]{0, 0, 0, 0};
uint64_t connection_bytes_received[4]{0, 0, 0, 0};
uint64_t file_bytes_sent = 0;
uint64_t file_bytes_received = 0;
struct FileTransferStatistics {
uint64_t bytes_received{0};
uint64_t bytes_sent{0};
};
class ConnectionStatistics {
public:
struct category {
/* Only three categories. Map unknown to category 0 */
enum value {
COMMAND,
ACK,
KEEP_ALIVE,
VOICE,
UNKNOWN
UNKNOWN = COMMAND
};
constexpr static std::array<category::value, 16> lookup_table{
@@ -53,10 +109,10 @@ namespace ts {
VOICE, /* VoiceWhisper */
COMMAND, /* Command */
COMMAND, /* CommandLow */
ACK, /* Ping */
ACK, /* Pong */
ACK, /* Ack */
ACK, /* AckLow */
KEEP_ALIVE, /* Ping */
KEEP_ALIVE, /* Pong */
COMMAND, /* Ack */
COMMAND, /* AckLow */
COMMAND, /* */
UNKNOWN,
@@ -76,58 +132,38 @@ namespace ts {
return from_type(type.type());
}
};
explicit ConnectionStatistics(const std::shared_ptr<ConnectionStatistics>& /* root */, bool /* spawn properties */);
explicit ConnectionStatistics(std::shared_ptr<ConnectionStatistics> /* root */);
~ConnectionStatistics();
std::shared_ptr<Properties> statistics();
inline void logIncomingPacket(const protocol::ClientPacket& packet) { this->logIncomingPacket(category::from_type(packet.type()), packet.length()); }
void logIncomingPacket(const category::value& /* category */, size_t /* length */);
inline void logOutgoingPacket(const protocol::ServerPacket& packet) { this->logOutgoingPacket(category::from_type(packet.type()), packet.length()); }
void logOutgoingPacket(const category::value& /* category */, size_t /* length */);
void logFileTransferIn(uint64_t);
void logFileTransferOut(uint64_t);
void logFileTransferIn(uint32_t);
void logFileTransferOut(uint32_t);
void tick();
DataSummery dataReport();
FullReport full_report();
[[nodiscard]] inline const BandwidthEntry<uint32_t>& total_stats() const { return this->total_statistics; }
[[nodiscard]] inline BandwidthEntry<uint32_t> second_stats() const { return this->statistics_second; }
[[nodiscard]] BandwidthEntry<uint32_t> minute_stats() const;
FileTransferStatistics file_stats();
std::pair<uint64_t, uint64_t> mark_file_bytes();
inline bool measure_bandwidths() { return this->_measure_bandwidths; }
void measure_bandwidths(bool flag) { this->_measure_bandwidths = flag; }
inline bool has_properties() { return !!this->properties; }
private:
bool _measure_bandwidths = true;
std::shared_ptr<ConnectionStatistics> handle;
std::shared_ptr<Properties> properties;
BandwidthEntry<uint32_t> total_statistics{};
std::atomic<uint64_t> connection_packets_sent[4]{0, 0, 0, 0};
std::atomic<uint64_t> connection_bytes_sent[4]{0, 0, 0, 0};
std::atomic<uint64_t> connection_packets_received[4]{0, 0, 0, 0};
std::atomic<uint64_t> connection_bytes_received[4]{0, 0, 0, 0};
BandwidthEntry<std::atomic<uint64_t>> statistics_second_current{};
BandwidthEntry<uint32_t> statistics_second{}; /* will be updated every second by the stats from the "current_second" */
std::array<BandwidthEntry<uint32_t>, 60> statistics_minute{};
uint32_t statistics_minute_offset{0}; /* pointing to the upcoming minute */
std::chrono::system_clock::time_point last_second_tick{};
std::atomic<uint64_t> file_bytes_sent = 0;
std::atomic<uint64_t> file_bytes_received = 0;
std::atomic<uint64_t> file_bytes_sent{0};
std::atomic<uint64_t> file_bytes_received{0};
std::atomic<uint64_t> mark_file_bytes_sent = 0;
std::atomic<uint64_t> mark_file_bytes_received = 0;
spin_lock history_lock_outgoing;
spin_lock history_lock_incoming;
std::deque<StatisticEntry*> history_file_incoming{};
std::deque<StatisticEntry*> history_file_outgoing{};
std::deque<StatisticEntry*> history_incoming{};
std::deque<StatisticEntry*> history_outgoing{};
void _log_incoming_packet(StatisticEntry */* statistics */, int8_t /* type index */);
void _log_outgoing_packet(StatisticEntry* /* statistics */, int8_t /* type index */);
void _log_incoming_file_packet(StatisticEntry */* statistics */);
void _log_outgoing_file_packet(StatisticEntry* /* statistics */);
uint64_t mark_file_bytes_sent{0};
uint64_t mark_file_bytes_received{0};
};
}
}
+26 -19
View File
@@ -37,7 +37,7 @@ void DatabaseHelper::tick() {
{
threads::MutexLock l(this->propsLock);
auto pcpy = this->cachedProperties;
for(const auto& mgr : pcpy){
for(const auto& mgr : pcpy) {
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5))
mgr->ownLock.reset();
if(mgr->properties.expired()) {
@@ -159,7 +159,12 @@ void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server,
//TODO delete complains
}
inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& server, v2::PermissionManager* manager, sql::command& command, bool test_channel /* only used for client permissions (client channel permissions) */) {
inline sql::result load_permissions_v2(
const std::shared_ptr<VirtualServer>& server,
v2::PermissionManager* manager,
sql::command& command,
bool test_channel, /* only used for client permissions (client channel permissions) */
bool is_channel) {
auto start = system_clock::now();
auto server_id = server ? server->getServerId() : 0;
@@ -213,7 +218,7 @@ inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& ser
return 0;
}
if(channel_id == 0)
if(channel_id == 0 || is_channel)
manager->load_permission(key, {value, granted}, skipped, negated, value != permNotGranted, granted != permNotGranted);
else
manager->load_permission(key, {value, granted}, channel_id, skipped, negated, value != permNotGranted, granted != permNotGranted);
@@ -282,7 +287,7 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_USER},
variable{":id", cldbid});
LOG_SQL_CMD(load_permissions_v2(server, permission_manager.get(), command, true));
LOG_SQL_CMD(load_permissions_v2(server, permission_manager.get(), command, true, false));
}
@@ -364,7 +369,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPerm
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id});
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, false));
return result;
}
@@ -436,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, result.get(), command, false));
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, false));
return result;
}
@@ -502,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, result.get(), command, false));
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false, true));
return result;
}
@@ -568,7 +573,9 @@ bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::sh
if(!res) return false;
auto insertTemplate = sql::model(sql, "INSERT INTO `clients` (`serverId`, `cldbId`, `clientUid`, `lastName`,`firstConnect`,`lastConnect`, `connections`) VALUES (:serverId, :cldbid, :cluid, :name, :fconnect, :lconnect, :connections)",
variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()}, variable{":fconnect", duration_cast<seconds>(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0}, variable{":connections", 0});
variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()},
variable{":fconnect", duration_cast<seconds>(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0},
variable{":connections", 0});
if(cldbid == 0){ //Completly new user
res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([](ClientDbId* ptr, int length, char** values, char** names){
*ptr = static_cast<ClientDbId>(stoll(values[0]));
@@ -617,8 +624,8 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
}
}
const auto &info = property::impl::info_key(type, key);
if(info->name == "undefined") {
const auto &info = property::find(type, key);
if(info.name == "undefined") {
logError(sid, "Found unknown property in database! ({})", key);
return 0;
}
@@ -630,9 +637,9 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
prop.setDbReference(true);
*/
auto data = make_unique<FastPropertyEntry>();
auto data = std::make_unique<FastPropertyEntry>();
data->type = &info;
data->value = value;
data->type = info;
properties.push_back(move(data));
return 0;
});
@@ -705,7 +712,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shar
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
}
logTrace(serverId, "Updating server property: " + prop.type().name + ". New value: " + prop.value() + ". Query: " + sql);
logTrace(serverId, "Updating server property: " + std::string{prop.type().name} + ". New value: " + prop.value() + ". Query: " + sql);
sql::command(this->sql, sql,
variable{":sid", serverId},
variable{":type", property::PropertyType::PROP_TYPE_SERVER},
@@ -925,7 +932,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
if(!prop.isModified()) return;
if((prop.type().flags & property::FLAG_SAVE) == 0 && (type != ClientType::CLIENT_MUSIC || (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0)) {
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + prop.type().name + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + std::string{prop.type().name} + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
return;
}
if(!prop.get_handle()) return;
@@ -940,7 +947,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
}
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + prop.type().name + " value: " + prop.value());
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + std::string{prop.type().name} + " value: " + prop.value());
sql::command(this->sql, sql,
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", prop.type().type_property},
@@ -965,7 +972,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
else if(prop.type() == property::CLIENT_LASTCONNECTED)
query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
if(query.empty()) return;
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '" + prop.type().name + "' for " + to_string(cldbid) + " (New value: " + prop.value() + ", SQL: " + query + ")");
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"});
});
@@ -1119,8 +1126,8 @@ void DatabaseHelper::loadStartupPropertyCache() {
}
}
auto info = property::impl::info_key(type, key);
if(info == property::PropertyDescription::unknown) {
const auto& info = property::find(type, key);
if(info.is_undefined()) {
logError(serverId, "Invalid property ({} | {})", key, type);
return 0;
}
@@ -1145,7 +1152,7 @@ void DatabaseHelper::loadStartupPropertyCache() {
}
auto entry = make_unique<StartupPropertyEntry>();
entry->info = info;
entry->info = &info;
entry->value = value;
entry->id = id;
entry->type = type;
+3 -3
View File
@@ -57,8 +57,8 @@ namespace ts {
struct StartupPropertyEntry {
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
uint64_t id = 0;
std::shared_ptr<property::PropertyDescription> info = property::PropertyDescription::unknown;
uint64_t id{0};
const property::PropertyDescription* info{&property::undefined_property_description};
std::string value;
};
@@ -70,7 +70,7 @@ namespace ts {
};
struct FastPropertyEntry {
std::shared_ptr<property::PropertyDescription> type;
const property::PropertyDescription* type;
std::string value;
};
-1
View File
@@ -215,7 +215,6 @@ namespace ts {
bool isClientCached(const ClientDbId& /* client database id */);
void clearCache();
bool isLocalGroup(std::shared_ptr<Group>);
protected:
void handleChannelDeleted(const ChannelId& /* channel id */);
+9 -10
View File
@@ -41,8 +41,7 @@ extern bool mainThreadActive;
InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
serverInstance = this;
this->tick_manager = make_shared<threads::Scheduler>(config::threads::ticking, "tick task ");
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr, true);
this->statistics->measure_bandwidths(true);
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr);
std::string error_message{};
this->license_service_ = std::make_shared<license::LicenseService>();
@@ -70,8 +69,8 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
}
}
const auto &info = property::impl::info<property::InstanceProperties>(key);
if(*info == property::SERVERINSTANCE_UNDEFINED) {
const auto &info = property::find<property::InstanceProperties>(key);
if(info == property::SERVERINSTANCE_UNDEFINED) {
logError(0, "Got an unknown instance property " + key);
return 0;
}
@@ -414,7 +413,7 @@ FwIDAQAB
startTimestamp = system_clock::now();
this->voiceServerManager->executeAutostart();
this->scheduler()->schedule(INSTANCE_TICK_NAME, bind(&InstanceHandler::tickInstance, this), milliseconds(100));
this->scheduler()->schedule(INSTANCE_TICK_NAME, bind(&InstanceHandler::tickInstance, this), milliseconds{500});
return true;
}
@@ -482,12 +481,12 @@ void InstanceHandler::tickInstance() {
ALARM_TIMER(t, "InstanceHandler::tickInstance -> flush", milliseconds(5));
//logger::flush();
}
if(statisticsUpdateTimestamp + seconds(5) < now) {
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
this->statistics->tick();
}
if(statisticsUpdateTimestamp + seconds(1) < now) {
statisticsUpdateTimestamp = now;
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
this->statistics->tick();
}
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick [monthly]", milliseconds(2));
+2 -2
View File
@@ -134,8 +134,8 @@ bool InstanceHandler::setupDefaultGroups() {
}
for(const auto& property : info->properties) {
const auto& prop = property::impl::info<property::InstanceProperties>(property);
if(*prop == property::SERVERINSTANCE_UNDEFINED) {
const auto& prop = property::find<property::InstanceProperties>(property);
if(prop.is_undefined()) {
logCritical(LOG_INSTANCE, "Invalid template property name: " + property);
} else {
this->properties()[prop] = group->groupId();
+10 -10
View File
@@ -201,7 +201,7 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
auto snapshot_version = arguments[index].has("snapshot_version") ? arguments[index++]["snapshot_version"] : 0;
debugMessage(0, "Got server snapshot with version {}", snapshot_version);
while(true){
for(auto &key : arguments[index].keys()){
for(const auto &key : arguments[index].keys()){
if(key == "end_virtualserver") continue;
if(key == "begin_virtualserver") continue;
if(snapshot_version == 0) {
@@ -559,8 +559,8 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
if(key == "bot_owner_id") continue;
if(key == "bot_id") continue;
const auto& property = property::info<property::ClientProperties>(key);
if(property->property_index == property::CLIENT_UNDEFINED) {
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;
}
@@ -597,8 +597,8 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
if(key == "begin_playlist") continue;
if(key == "playlist_id") continue;
const auto& property = property::info<property::ClientProperties>(key);
if(property->property_index == property::CLIENT_UNDEFINED) {
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;
}
@@ -787,12 +787,12 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
case property::VIRTUALSERVER_UPLOAD_QUOTA:
case property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH:
case property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH:
cmd[index][serverProperty.type().name] = (uint64_t) serverProperty.as_save<int64_t>();
cmd[index][std::string{serverProperty.type().name}] = (uint64_t) serverProperty.as_save<int64_t>();
default:
break;
}
}
cmd[index][serverProperty.type().name] = serverProperty.value();
cmd[index][std::string{serverProperty.type().name}] = serverProperty.value();
}
cmd[index++]["end_virtualserver"] = "";
}
@@ -807,7 +807,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
else if(channelProperty.type() == property::CHANNEL_PID)
cmd[index]["channel_pid"] = channelProperty.as<string>();
else
cmd[index][channelProperty.type().name] = channelProperty.as<string>();
cmd[index][std::string{channelProperty.type().name}] = channelProperty.as<string>();
}
index++;
}
@@ -899,7 +899,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue;
if(property->value == property->type->default_value) continue;
cmd[index][property->type->name] = property->value;
cmd[index][std::string{property->type->name}] = property->value;
}
index++;
@@ -931,7 +931,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue;
if(property->value == property->type->default_value) continue;
cmd[index][property->type->name] = property->value;
cmd[index][std::string{property->type->name}] = property->value;
}
index++;
+1 -2
View File
@@ -87,8 +87,7 @@ void VirtualServer::executeServerTick() {
if(clientOnline + queryOnline == 0) //We don't need to tick, when server is empty!
return;
properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = this->channelTree->channel_count();
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->averagePing();
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->generate_network_report().average_ping;
END_TIMINGS(timing_update_states);
}
+26 -27
View File
@@ -180,7 +180,7 @@ bool VirtualServer::initialize(bool test_properties) {
letters = new letter::LetterManager(this);
serverStatistics = make_shared<stats::ConnectionStatistics>(serverInstance->getStatistics(), true);
serverStatistics = make_shared<stats::ConnectionStatistics>(serverInstance->getStatistics());
this->serverRoot = std::make_shared<InternalClient>(this->sql, self.lock(), this->properties()[property::VIRTUALSERVER_NAME].as<string>(), false);
static_pointer_cast<InternalClient>(this->serverRoot)->setSharedLock(this->serverRoot);
@@ -216,12 +216,13 @@ bool VirtualServer::initialize(bool test_properties) {
property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH,
property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH,
}) {
auto info = property::impl::info(type);
const auto& info = property::describe(type);
auto prop = this->properties()[type];
if(prop.default_value() == prop.value()) continue;
if(!info->validate_input(this->properties()[type].value())) {
this->properties()[type] = info->default_value;
logMessage(this->getServerId(), "Server property " + info->name + " contains an invalid value! Resetting it.");
if(!info.validate_input(this->properties()[type].value())) {
this->properties()[type] = info.default_value;
logMessage(this->getServerId(), "Server property " + std::string{info.name} + " contains an invalid value! Resetting it.");
}
}
@@ -711,8 +712,8 @@ bool VirtualServer::notifyServerEdited(std::shared_ptr<ConnectedClient> invoker,
cmd["invokeruid"] = invoker->getUid();
cmd["reasonid"] = ViewReasonId::VREASON_EDITED;
for(const auto& key : keys) {
auto info = property::impl::info<property::VirtualServerProperties>(key);
if(*info == property::VIRTUALSERVER_UNDEFINED) {
const auto& info = property::find<property::VirtualServerProperties>(key);
if(info == property::VIRTUALSERVER_UNDEFINED) {
logError(this->getServerId(), "Tried to broadcast a server update with an unknown info: " + key);
continue;
}
@@ -724,7 +725,7 @@ bool VirtualServer::notifyServerEdited(std::shared_ptr<ConnectedClient> invoker,
return true;
}
bool VirtualServer::notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient> client, const deque<shared_ptr<property::PropertyDescription>>& keys, bool selfNotify) {
bool VirtualServer::notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient> client, const deque<const property::PropertyDescription*>& keys, bool selfNotify) {
if(keys.empty()) return false;
this->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
shared_lock client_channel_lock(client->channel_lock);
@@ -1017,35 +1018,33 @@ bool VirtualServer::verifyServerPassword(std::string password, bool hashed) {
return password == this->properties()[property::VIRTUALSERVER_PASSWORD].as<std::string>();
}
float VirtualServer::averagePacketLoss() {
//TODO Average packet loss
return 0.f;
}
VirtualServer::NetworkReport VirtualServer::generate_network_report() {
double total_ping{0}, total_loss{0};
size_t pings_counted{0}, loss_counted{0};
float VirtualServer::averagePing() {
float count = 0;
float sum = 0;
this->forEachClient([&count, &sum](shared_ptr<ConnectedClient> client) {
auto type = client->getType();
if(type == ClientType::CLIENT_TEAMSPEAK || type == ClientType::CLIENT_TEASPEAK) {
count++;
sum += duration_cast<milliseconds>(dynamic_pointer_cast<VoiceClient>(client)->calculatePing()).count();
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& client) {
if(auto vc = dynamic_pointer_cast<VoiceClient>(client); vc) {
total_ping += vc->current_ping().count();
total_loss += vc->current_packet_loss();
pings_counted++;
loss_counted++;
}
#ifdef COMPILE_WEB_CLIENT
else if(type == ClientType::CLIENT_WEB) {
count++;
sum += duration_cast<milliseconds>(dynamic_pointer_cast<WebClient>(client)->client_ping()).count();
else if(client->getType() == ClientType::CLIENT_WEB) {
pings_counted++;
total_ping += duration_cast<milliseconds>(dynamic_pointer_cast<WebClient>(client)->client_ping()).count();
}
#endif
});
if(count == 0) return 0;
return sum / count;
VirtualServer::NetworkReport result{};
if(loss_counted) result.average_loss = total_loss / loss_counted;
if(pings_counted) result.average_ping = total_ping / pings_counted;
return result;
}
bool VirtualServer::resetPermissions(std::string& token) {
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` != :channel_type", variable{":serverId", this->serverId}, variable{":channel_type", permission::SQL_PERM_CHANNEL}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
+9 -5
View File
@@ -136,6 +136,11 @@ namespace ts {
friend class InstanceHandler;
friend class VirtualServerManager;
public:
struct NetworkReport {
float average_ping{0};
float average_loss{0};
};
VirtualServer(ServerId serverId, sql::SqlManager*);
~VirtualServer();
@@ -182,11 +187,11 @@ namespace ts {
inline GroupManager* getGroupManager() { return this->groups; }
bool notifyServerEdited(std::shared_ptr<ConnectedClient>, std::deque<std::string> keys);
bool notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient>, const std::deque<std::shared_ptr<property::PropertyDescription>>& keys, bool selfNotify = true); /* execute only with at least channel tree read lock! */
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;
std::deque<std::shared_ptr<property::PropertyDescription>> _keys;
for(const auto& key : keys) _keys.push_back(property::impl::info<property::ClientProperties>(key));
std::deque<const property::PropertyDescription*> _keys{};
for(const auto& key : keys) _keys.push_back(&property::describe(key));
return this->notifyClientPropertyUpdates(client, _keys, selfNotify);
};
@@ -230,8 +235,7 @@ namespace ts {
void testBanStateChange(const std::shared_ptr<ConnectedClient>& invoker);
float averagePing();
float averagePacketLoss();
[[nodiscard]] NetworkReport generate_network_report();
bool resetPermissions(std::string&);
void ensureValidDefaultGroups();
+33 -17
View File
@@ -13,7 +13,7 @@ using namespace std::chrono;
using namespace ts::server;
VirtualServerManager::VirtualServerManager(InstanceHandler* handle) : handle(handle) {
this->puzzles = new protocol::PuzzleManager();
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 #");
@@ -67,7 +67,8 @@ bool VirtualServerManager::initialize(bool autostart) {
this->state = State::STARTING;
logMessage(LOG_INSTANCE, "Generating server puzzles...");
auto start = system_clock::now();
this->puzzles->precomputePuzzles(config::voice::DefaultPuzzlePrecomputeSize);
if(!this->puzzles->precompute_puzzles(config::voice::DefaultPuzzlePrecomputeSize))
logCritical(LOG_INSTANCE, "Failed to precompute RSA puzzles");
logMessage(LOG_INSTANCE, "Puzzles generated! Time required: " + to_string(duration_cast<milliseconds>(system_clock::now() - start).count()) + "ms");
size_t serverCount = 0;
@@ -317,7 +318,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
return nullptr;
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", hosts}, variable{":port", port}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
//`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT
auto prop_copy = sql::command(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :target_sid AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `type` = :type AND `id` = 0 AND `serverId` = 0;",
variable{":target_sid", serverId},
variable{":type", property::PROP_TYPE_SERVER}).execute();
@@ -393,21 +394,8 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
}
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
sql::command(this->handle->getSql(), "DELETE FROM `tokens` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `properties` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `permissions` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `groups` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `clients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `channels` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `bannedClients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `servers` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `musicbots` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `bannedClients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `ban_trigger` WHERE `server_id` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
this->delete_server_in_db(server->serverId);
this->handle->getFileServer()->deleteServer(server);
return true;
}
@@ -446,4 +434,32 @@ void VirtualServerManager::tickHandshakeClients() {
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) { \
logWarning(LOG_INSTANCE, "Failed to execute SQL command {}: {}", statement, result.fmtStr()); \
result = sql::result{}; \
}
sql::result result{};
execute_delete("DELETE FROM `tokens` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `properties` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `permissions` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `clients` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `channels` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `bannedClients` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `ban_trigger` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `assignedGroups` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `servers` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `musicbots` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `conversations` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `conversation_blocks` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `playlists` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `playlist_songs` WHERE `serverId` = :sid");
}
+81 -72
View File
@@ -5,95 +5,104 @@
#include "client/voice/PrecomputedPuzzles.h"
#include "server/VoiceIOManager.h"
#include "VirtualServer.h"
#include <query/command3.h>
#include "snapshots/snapshot.h"
namespace ts {
namespace server {
class InstanceHandler;
namespace ts::server {
class InstanceHandler;
struct ServerReport {
size_t avariable;
size_t online;
struct ServerReport {
size_t avariable;
size_t online;
size_t slots;
size_t onlineClients;
size_t onlineChannels;
};
class VirtualServerManager {
public:
enum State {
STOPPED,
STARTING,
STARTED,
STOPPING
};
size_t slots;
size_t onlineClients;
size_t onlineChannels;
};
class VirtualServerManager {
public:
enum State {
STOPPED,
STARTING,
STARTED,
STOPPING
};
explicit VirtualServerManager(InstanceHandler*);
~VirtualServerManager();
explicit VirtualServerManager(InstanceHandler*);
~VirtualServerManager();
bool initialize(bool execute_autostart = true);
bool initialize(bool execute_autostart = true);
std::shared_ptr<VirtualServer> create_server(std::string hosts, uint16_t port);
bool deleteServer(std::shared_ptr<VirtualServer>);
std::shared_ptr<VirtualServer> create_server(std::string hosts, uint16_t port);
bool deleteServer(std::shared_ptr<VirtualServer>);
std::shared_ptr<VirtualServer> findServerById(ServerId);
std::shared_ptr<VirtualServer> findServerByPort(uint16_t);
uint16_t next_available_port();
ServerId next_available_server_id(bool& /* success */);
std::deque<std::shared_ptr<VirtualServer>> serverInstances(){
threads::MutexLock l(this->instanceLock);
return instances;
}
std::shared_ptr<VirtualServer> findServerById(ServerId);
std::shared_ptr<VirtualServer> findServerByPort(uint16_t);
uint16_t next_available_port();
ServerId next_available_server_id(bool& /* success */);
ServerReport report();
OnlineClientReport clientReport();
size_t runningServers();
size_t usedSlots();
std::deque<std::shared_ptr<VirtualServer>> serverInstances(){
threads::MutexLock l(this->instanceLock);
return instances;
}
void executeAutostart();
void shutdownAll(const std::string&);
ServerReport report();
OnlineClientReport clientReport();
size_t runningServers();
size_t usedSlots();
//Dotn use shared_ptr references to keep sure that they be hold in memory
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
void executeAutostart();
void shutdownAll(const std::string&);
protocol::PuzzleManager* rsaPuzzles() { return this->puzzles; }
//Dotn use shared_ptr references to keep sure that they be hold in memory
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
bool deploy_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
event::EventExecutor* get_join_loop() { return this->join_loop; }
event::EventExecutor* get_executor_loop() { return this->execute_loop; }
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
inline void adjust_executor_threads() {
std::unique_lock instance_lock(this->instanceLock);
auto instance_count = this->instances.size();
instance_lock.unlock();
event::EventExecutor* get_join_loop() { return this->join_loop; }
event::EventExecutor* get_executor_loop() { return this->execute_loop; }
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; }
inline void adjust_executor_threads() {
std::unique_lock instance_lock(this->instanceLock);
auto instance_count = this->instances.size();
instance_lock.unlock();
threads::Mutex server_create_lock;
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; }
State getState() { return this->state; }
private:
State state = State::STOPPED;
InstanceHandler* handle;
threads::Mutex instanceLock;
std::deque<std::shared_ptr<VirtualServer>> instances;
protocol::PuzzleManager* puzzles = nullptr;
threads::Mutex server_create_lock;
event::EventExecutor* execute_loop = nullptr;
event::EventExecutor* join_loop = nullptr;
threads::Scheduler* handshakeTickers = nullptr;
io::VoiceIOManager* _ioManager = nullptr;
State getState() { return this->state; }
private:
State state = State::STOPPED;
InstanceHandler* handle;
threads::Mutex instanceLock;
std::deque<std::shared_ptr<VirtualServer>> instances;
udp::PuzzleManager* puzzles{nullptr};
struct {
std::thread executor{};
std::condition_variable condition;
std::mutex lock;
} acknowledge;
event::EventExecutor* execute_loop = nullptr;
event::EventExecutor* join_loop = nullptr;
threads::Scheduler* handshakeTickers = nullptr;
io::VoiceIOManager* _ioManager = nullptr;
void tickHandshakeClients();
};
}
struct {
std::thread executor{};
std::condition_variable condition;
std::mutex lock;
} acknowledge;
void tickHandshakeClients();
void delete_server_in_db(ServerId /* server id */);
/* methods used to preprocess a snapshot */
bool deploy_ts3_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
bool deploy_teaspeak_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */);
/* actual deploy method */
bool deploy_raw_snapshot(std::string& /* error */, ServerId /* target server id */, const command_parser& /* source */, const std::string& /* hash */, size_t /* offset */, snapshots::type /* type */, snapshots::version_t /* version */);
};
}
+4 -9
View File
@@ -29,9 +29,7 @@ ConnectedClient::ConnectedClient(sql::SqlManager* db, const std::shared_ptr<Virt
memtrack::allocated<ConnectedClient>(this);
memset(&this->remote_address, 0, sizeof(this->remote_address));
connectionStatistics = make_shared<stats::ConnectionStatistics>(server ? server->getServerStatistics() : nullptr, false);
this->connectionStatistics->measure_bandwidths(false); /* done by the client and we trust this */
connectionStatistics = make_shared<stats::ConnectionStatistics>(server ? server->getServerStatistics() : nullptr);
channels = make_shared<ClientChannelView>(this);
}
@@ -381,7 +379,7 @@ bool ConnectedClient::notifyClientLeftView(
std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree) {
assert(!lock_channel_tree); /* not supported yet! */
assert(client && client->getClientId() != 0);
assert(client == this || (client && client->getClientId() != 0));
assert(client->currentChannel || &*client == this);
if(client != this) {
@@ -744,17 +742,14 @@ void ConnectedClient::tick(const std::chrono::system_clock::time_point &time) {
}
if(this->last_statistics_tick + seconds(5) < time) {
this->last_statistics_tick = time;
this->connectionStatistics->tick();
}
this->connectionStatistics->tick();
}
void ConnectedClient::sendServerInit() {
Command command("initserver");
for(const auto& prop : this->server->properties().list_properties(property::FLAG_SERVER_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
command[prop.type().name] = prop.value();
command[std::string{prop.type().name}] = prop.value();
}
command["virtualserver_maxclients"] = 32;
+1 -2
View File
@@ -135,7 +135,7 @@ namespace ts {
virtual bool notifyClientPoke(std::shared_ptr<ConnectedClient> invoker, std::string msg);
virtual bool notifyClientUpdated(
const std::shared_ptr<ConnectedClient> &,
const std::deque<std::shared_ptr<property::PropertyDescription>> &,
const std::deque<const property::PropertyDescription*> &,
bool lock_channel_tree
); /* invalid client id causes error: invalid clientID */
@@ -326,7 +326,6 @@ namespace ts {
std::chrono::system_clock::time_point lastOnlineTimestamp;
std::chrono::system_clock::time_point lastTransfareTimestamp;
std::chrono::system_clock::time_point idleTimestamp;
std::chrono::system_clock::time_point last_statistics_tick;
struct {
std::mutex lock;
File diff suppressed because it is too large Load Diff
@@ -60,7 +60,7 @@ bool ConnectedClient::notifyServerGroupList() {
cmd[index]["sgid"] = group->groupId();
}
for (const auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
cmd[index][prop.type().name] = prop.value();
cmd[index][std::string{prop.type().name}] = prop.value();
auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power);
@@ -186,7 +186,7 @@ bool ConnectedClient::notifyChannelGroupList() {
cmd[index]["sgid"] = group->groupId();
}
for (auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
cmd[index][prop.type().name] = prop.value();
cmd[index][std::string{prop.type().name}] = prop.value();
auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power);
@@ -271,108 +271,116 @@ bool ConnectedClient::notifyClientChannelGroupChanged(
}
bool ConnectedClient::notifyConnectionInfo(const shared_ptr<ConnectedClient> &target, const shared_ptr<ConnectionInfoData> &info) {
Command notify("notifyconnectioninfo");
notify["clid"] = target->getClientId();
command_builder notify{"notifyconnectioninfo"};
auto bulk = notify.bulk(0);
bulk.put_unchecked("clid", target->getClientId());
auto not_set = this->getType() == CLIENT_TEAMSPEAK ? 0 : -1;
/* we deliver data to the web client as well, because its a bit dump :D */
if(target->getClientId() != this->getClientId()) {
auto report = target->connectionStatistics->full_report();
auto file_stats = target->connectionStatistics->file_stats();
/* default values which normally sets the client */
notify["connection_bandwidth_received_last_minute_control"] = not_set;
notify["connection_bandwidth_received_last_minute_keepalive"] = not_set;
notify["connection_bandwidth_received_last_minute_speech"] = not_set;
notify["connection_bandwidth_received_last_second_control"] = not_set;
notify["connection_bandwidth_received_last_second_keepalive"] = not_set;
notify["connection_bandwidth_received_last_second_speech"] = not_set;
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_SPEECH, not_set);
notify["connection_bandwidth_sent_last_minute_control"] = not_set;
notify["connection_bandwidth_sent_last_minute_keepalive"] = not_set;
notify["connection_bandwidth_sent_last_minute_speech"] = not_set;
notify["connection_bandwidth_sent_last_second_control"] = not_set;
notify["connection_bandwidth_sent_last_second_keepalive"] = not_set;
notify["connection_bandwidth_sent_last_second_speech"] = not_set;
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_SPEECH, not_set);
/* its flipped here because the report is out of the clients view */
notify["connection_bytes_received_control"] = not_set;
notify["connection_bytes_received_keepalive"] = not_set;
notify["connection_bytes_received_speech"] = not_set;
notify["connection_bytes_sent_control"] = not_set;
notify["connection_bytes_sent_keepalive"] = not_set;
notify["connection_bytes_sent_speech"] = not_set;
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_SPEECH, not_set);
/* its flipped here because the report is out of the clients view */
notify["connection_packets_received_control"] = not_set;
notify["connection_packets_received_keepalive"] = not_set;
notify["connection_packets_received_speech"] = not_set;
notify["connection_packets_sent_control"] = not_set;
notify["connection_packets_sent_keepalive"] = not_set;
notify["connection_packets_sent_speech"] = not_set;
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_SPEECH, not_set);
notify["connection_server2client_packetloss_control"] = not_set;
notify["connection_server2client_packetloss_keepalive"] = not_set;
notify["connection_server2client_packetloss_speech"] = not_set;
notify["connection_server2client_packetloss_total"] = not_set;
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL, not_set);
notify["connection_ping"] = 0;
notify["connection_ping_deviation"] = 0;
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, not_set);
notify["connection_connected_time"] = 0;
notify["connection_idle_time"] = 0;
bulk.put_unchecked(property::CONNECTION_PING, 0);
bulk.put_unchecked(property::CONNECTION_PING_DEVIATION, 0);
bulk.put_unchecked(property::CONNECTION_CONNECTED_TIME, 0);
bulk.put_unchecked(property::CONNECTION_IDLE_TIME, 0);
/* its flipped here because the report is out of the clients view */
notify["connection_filetransfer_bandwidth_sent"] = report.file_bytes_received;
notify["connection_filetransfer_bandwidth_received"] = report.file_bytes_sent;
bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT, file_stats.bytes_received);
bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, file_stats.bytes_sent);
}
if(info) {
for(const auto& elm : info->properties) {
notify[elm.first] = elm.second;
}
for(const auto& [key, value] : info->properties)
bulk.put(key, value);
} else {
//Fill in some server stuff
if(dynamic_pointer_cast<VoiceClient>(target))
notify["connection_ping"] = floor<milliseconds>(dynamic_pointer_cast<VoiceClient>(target)->calculatePing()).count();
//Fill in what we can, else we trust the client
if(target->getType() == ClientType::CLIENT_TEASPEAK || target->getType() == ClientType::CLIENT_TEAMSPEAK || target->getType() == ClientType::CLIENT_WEB) {
auto& stats = target->connectionStatistics->total_stats();
/* its flipped here because the report is out of the clients view */
bulk.put(property::CONNECTION_BYTES_RECEIVED_CONTROL, stats.connection_bytes_received[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_BYTES_RECEIVED_KEEPALIVE, stats.connection_bytes_received[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_BYTES_RECEIVED_SPEECH, stats.connection_bytes_received[stats::ConnectionStatistics::category::VOICE]);
bulk.put(property::CONNECTION_BYTES_SENT_CONTROL, stats.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_BYTES_SENT_KEEPALIVE, stats.connection_bytes_sent[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_BYTES_SENT_SPEECH, stats.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE]);
/* its flipped here because the report is out of the clients view */
bulk.put(property::CONNECTION_PACKETS_RECEIVED_CONTROL, stats.connection_packets_received[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE, stats.connection_packets_received[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_PACKETS_RECEIVED_SPEECH, stats.connection_packets_received[stats::ConnectionStatistics::category::VOICE]);
bulk.put(property::CONNECTION_PACKETS_SENT_CONTROL, stats.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_PACKETS_SENT_KEEPALIVE, stats.connection_packets_sent[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_PACKETS_SENT_SPEECH, stats.connection_packets_sent[stats::ConnectionStatistics::category::VOICE]);
}
}
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))
notify["connection_ping"] = floor<milliseconds>(dynamic_pointer_cast<WebClient>(target)->client_ping()).count();
else if(dynamic_pointer_cast<WebClient>(target))
bulk.put(property::CONNECTION_PING, floor<milliseconds>(dynamic_pointer_cast<WebClient>(target)->client_ping()).count());
#endif
if(target->getType() == ClientType::CLIENT_TEASPEAK || target->getType() == ClientType::CLIENT_TEAMSPEAK || target->getType() == ClientType::CLIENT_WEB) {
auto report = target->connectionStatistics->full_report();
/* its flipped here because the report is out of the clients view */
notify["connection_bytes_received_control"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_bytes_received_keepalive"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_bytes_received_speech"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
notify["connection_bytes_sent_control"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_bytes_sent_keepalive"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_bytes_sent_speech"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
/* its flipped here because the report is out of the clients view */
notify["connection_packets_received_control"] = report.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_packets_received_keepalive"] = report.connection_packets_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_packets_received_speech"] = report.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
notify["connection_packets_sent_control"] = report.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_packets_sent_keepalive"] = report.connection_packets_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_packets_sent_speech"] = report.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
}
if(auto vc = dynamic_pointer_cast<VoiceClient>(target); vc){
auto& calculator = vc->connection->packet_statistics();
auto report = calculator.loss_report();
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, std::to_string(report.voice_loss()));
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, std::to_string(report.keep_alive_loss()));
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, std::to_string(report.control_loss()));
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, std::to_string(report.total_loss()));
}
if(target->getClientId() == this->getClientId() || permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, this->getChannelId()))) {
notify["connection_client_ip"] = target->getLoggingPeerIp();
notify["connection_client_port"] = target->getPeerPort();
bulk.put(property::CONNECTION_CLIENT_IP, target->getLoggingPeerIp());
bulk.put(property::CONNECTION_CLIENT_PORT, target->getPeerPort());
}
//Needs to be filled out
notify["connection_client2server_packetloss_speech"] = not_set;
notify["connection_client2server_packetloss_keepalive"] = not_set;
notify["connection_client2server_packetloss_control"] = not_set;
notify["connection_client2server_packetloss_total"] = not_set;
notify["connection_connected_time"] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->connectTimestamp).count();
notify["connection_idle_time"] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->idleTimestamp).count();
bulk.put(property::CONNECTION_CONNECTED_TIME, chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->connectTimestamp).count());
bulk.put(property::CONNECTION_IDLE_TIME, chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->idleTimestamp).count());
this->sendCommand(notify);
return true;
}
@@ -404,7 +412,7 @@ bool ConnectedClient::notifyClientMoved(const shared_ptr<ConnectedClient> &clien
return true;
}
bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &client, const deque<shared_ptr<property::PropertyDescription>> &props, bool lock) {
bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &client, const deque<const property::PropertyDescription*> &props, bool lock) {
shared_lock channel_lock(this->channel_lock, defer_lock);
if(lock)
channel_lock.lock();
@@ -420,7 +428,7 @@ bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient>
Command response("notifyclientupdated");
response["clid"] = client_id;
for (const auto &prop : props) {
if(lastOnlineTimestamp.time_since_epoch().count() > 0 && (prop->property_index == property::CLIENT_TOTAL_ONLINE_TIME || prop->property_index == property::CLIENT_MONTH_ONLINE_TIME))
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<int64_t>() + duration_cast<seconds>(system_clock::now() - client->lastOnlineTimestamp).count();
else
response[prop->name] = client->properties()[prop].value();
@@ -650,7 +658,7 @@ bool ConnectedClient::notifyClientEnterView(const std::deque<std::shared_ptr<Con
this->visibleClients.push_back(client);
for (const auto &elm : client->properties()->list_properties(property::FLAG_CLIENT_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
cmd[index][elm.type().name] = elm.value();
cmd[index][std::string{elm.type().name}] = elm.value();
}
index++;
@@ -678,14 +686,14 @@ bool ConnectedClient::notifyChannelEdited(
Command notify("notifychanneledited");
for(auto prop : properties) {
const auto& prop_info = property::impl::info(prop);
const auto& prop_info = property::describe(prop);
if(prop == property::CHANNEL_ORDER)
notify[prop_info->name] = v_channel->previous_channel;
notify[prop_info.name] = v_channel->previous_channel;
else if(prop == property::CHANNEL_DESCRIPTION) {
send_description_change = true;
} else {
notify[prop_info->name] = channel->properties()[prop].as<string>();
notify[prop_info.name] = channel->properties()[prop].as<string>();
}
}
@@ -486,7 +486,7 @@ bool ConnectedClient::handle_text_command(
for(const auto& property : bot->properties()->list_properties(~0)) {
if(find(editable_properties.begin(), editable_properties.end(), property.type().name) == editable_properties.end()) continue;
send_message(bot, " - " + property.type().name + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
send_message(bot, " - " + std::string{property.type().name} + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
}
} else if(arguments.size() < 4) {
if(find(editable_properties.begin(), editable_properties.end(), arguments[2]) == editable_properties.end()) {
@@ -494,14 +494,14 @@ bool ConnectedClient::handle_text_command(
return true;
}
const std::shared_ptr<property::PropertyDescription> &property_info = property::info<property::ClientProperties>(arguments[2]);
if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::CLIENT_UNDEFINED) {
const auto &property_info = property::find<property::ClientProperties>(arguments[2]);
if(property_info.is_undefined()) {
send_message(bot, "Unknown property " + arguments[2] + ".");
return true;
}
auto prop = bot->properties()[(property::ClientProperties) property_info->property_index];
send_message(bot, "Bot property " + property_info->name + " = " + prop.value() + " " + (property_info->default_value == prop.value() ? "(default)" : ""));
auto prop = bot->properties()[(property::ClientProperties) property_info.property_index];
send_message(bot, "Bot property " + std::string{property_info.name} + " = " + prop.value() + " " + (property_info.default_value == prop.value() ? "(default)" : ""));
return true;
} else {
Command cmd("");
@@ -527,36 +527,36 @@ bool ConnectedClient::handle_text_command(
if(arguments.size() < 3) {
send_message(bot, "Playlist properties:");
for(const auto& property : playlist->properties().list_properties(property::FLAG_PLAYLIST_VARIABLE)) {
send_message(bot, " - " + property.type().name + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
send_message(bot, " - " + std::string{property.type().name} + " = " + property.value() + " " + (property.default_value() == property.value() ? "(default)" : ""));
}
} else if(arguments.size() < 4) {
const std::shared_ptr<property::PropertyDescription> &property_info = property::info<property::PlaylistProperties>(arguments[2]);
if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::PLAYLIST_UNDEFINED) {
const auto &property_info = property::find<property::PlaylistProperties>(arguments[2]);
if(property_info.is_undefined()) {
send_message(bot, "Unknown property " + arguments[2] + ".");
return true;
}
auto prop = playlist->properties()[(property::PlaylistProperties) property_info->property_index];
send_message(bot, "Bot property " + property_info->name + " = " + prop.value() + " " + (property_info->default_value == prop.value() ? "(default)" : ""));
auto prop = playlist->properties()[(property::PlaylistProperties) property_info.property_index];
send_message(bot, "Bot property " + std::string{property_info.name} + " = " + prop.value() + " " + (property_info.default_value == prop.value() ? "(default)" : ""));
} else {
const std::shared_ptr<property::PropertyDescription> &property_info = property::info<property::PlaylistProperties>(arguments[2]);
if(!property_info || property_info->type_property == property::PROP_TYPE_UNKNOWN || property_info->property_index == property::PLAYLIST_UNDEFINED) {
const auto &property_info = property::find<property::PlaylistProperties>(arguments[2]);
if(property_info.is_undefined()) {
send_message(bot, "Unknown property " + arguments[2] + ".");
return true;
}
JOIN_ARGS(value, 3);
if(!property_info->validate_input(value)) {
if(!property_info.validate_input(value)) {
send_message(bot, "Please enter a valid value!");
return true;
}
if((property_info->flags & property::FLAG_USER_EDITABLE) == 0) {
if((property_info.flags & property::FLAG_USER_EDITABLE) == 0) {
send_message(bot, "This property isnt changeable!");
return true;
}
playlist->properties()[(property::PlaylistProperties) property_info->property_index] = value;
playlist->properties()[(property::PlaylistProperties) property_info.property_index] = value;
send_message(bot, "Property successfully changed");
return true;
}
@@ -641,9 +641,42 @@ bool ConnectedClient::handle_text_command(
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.lock(), " OUT " + type.name() + " => generation: " + to_string(gen) + " id: " + to_string(id));
//auto& buffer = vc->getConnection()->packet_buffers()[type.type()];
//send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(buffer.generation(0)) + " id: " + to_string(buffer.current_index()));
send_message(_this.lock(), " IN " + type.name() + " => generation: " + to_string(genestis[type.type()].generation()) + " id: " + to_string(genestis[type.type()].current_packet_id()));
}
return true;
} else if(TARG(0, "ping")) {
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false;
auto& ack = vc->connection->getAcknowledgeManager();
send_message(_this.lock(), "Command retransmission values:");
send_message(_this.lock(), " RTO : " + std::to_string(ack.current_rto()));
send_message(_this.lock(), " RTTVAR: " + std::to_string(ack.current_rttvar()));
send_message(_this.lock(), " SRTT : " + std::to_string(ack.current_srtt()));
return true;
} else if(TARG(0, "sgeneration")) {
TLEN(4);
try {
auto type = stol(arguments[1]);
auto generation = stol(arguments[2]);
auto pid = stol(arguments[3]);
auto vc = dynamic_pointer_cast<VoiceClient>(_this.lock());
if(!vc) return false;
auto& genestis = vc->getConnection()->get_incoming_generation_estimators();
if(type >= genestis.size()) {
send_message(_this.lock(), "Invalid type");
return true;
}
genestis[type].set_last_state(pid, generation);
} catch(std::exception& ex) {
send_message(_this.lock(), "Failed to parse argument");
return true;
}
return true;
} else if(TARG(0, "disconnect")) {
+7
View File
@@ -45,10 +45,17 @@ namespace ts {
return std::forward<F>(f)(*handle);
}
/*
template <typename T>
ts::PropertyWrapper operator[](T type) {
return (*handle)[type];
}
*/
template <typename T>
ts::PropertyWrapper operator[](const T& type) {
return (*handle)[type];
}
};
DataClient(sql::SqlManager*, const std::shared_ptr<VirtualServer>&);
+25 -5
View File
@@ -65,7 +65,6 @@ void SpeakingClient::handlePacketVoice(const pipes::buffer_view& data, bool head
if(!speaking_client) return true;
return !speaking_client->shouldReceiveVoice(self);
}), target_clients.end());
if(target_clients.empty()) {
return;
@@ -480,13 +479,13 @@ command_result SpeakingClient::handleCommandClientInit(Command& cmd) {
}
}
const auto &info = property::info<property::ClientProperties>(key);
if(*info == property::CLIENT_UNDEFINED) {
const auto &info = property::find<property::ClientProperties>(key);
if(info.is_undefined()) {
logError(this->getServerId(), "{} Tried to pass a unknown value {}. Please report this, if you're sure that this key should be known!", CLIENT_STR_LOG_PREFIX, key);
continue;
//return {findError("parameter_invalid"), "Unknown property " + key};
}
if(!info->validate_input(cmd[key].as<string>()))
if(!info.validate_input(cmd[key].as<string>()))
return command_result{error::parameter_invalid};
this->properties()[info] = cmd[key].as<std::string>();
@@ -743,7 +742,7 @@ void SpeakingClient::processJoin() {
unique_lock server_channel_lock(this->server->channel_tree_lock);
this->server->client_move(this->ref(), channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_lock);
this->subscribeChannel({this->currentChannel}, false, true);
if(this->getType() != ClientType::CLIENT_TEAMSPEAK) this->subscribeChannel({this->currentChannel}, false, true); /* su "improve" the TS3 clients join speed we send the channel clients a bit later, when the TS3 client gets his own client variables */
}
TIMING_STEP(timings, "join move ");
@@ -761,6 +760,27 @@ void SpeakingClient::processJoin() {
this->connectTimestamp = chrono::system_clock::now();
this->idleTimestamp = chrono::system_clock::now();
TIMING_STEP(timings, "welcome msg");
{
std::string message{};
config::server::clients::WelcomeMessageType type{config::server::clients::WELCOME_MESSAGE_TYPE_NONE};
if(this->getType() == ClientType::CLIENT_TEASPEAK) {
message = config::server::clients::extra_welcome_message_teaspeak;
type = config::server::clients::extra_welcome_message_type_teaspeak;
} else if(this->getType() == ClientType::CLIENT_TEAMSPEAK) {
message = config::server::clients::extra_welcome_message_teamspeak;
type = config::server::clients::extra_welcome_message_type_teamspeak;
} else if(this->getType() == ClientType::CLIENT_WEB) {
message = config::server::clients::extra_welcome_message_teaweb;
type = config::server::clients::extra_welcome_message_type_teaweb;
}
if(type == config::server::clients::WELCOME_MESSAGE_TYPE_POKE) {
this->notifyClientPoke(this->server->serverRoot, message);
} else if(type == config::server::clients::WELCOME_MESSAGE_TYPE_CHAT) {
this->notifyTextMessage(ChatMessageMode::TEXTMODE_SERVER, this->server->serverRoot, 0, 0, std::chrono::system_clock::now(), message);
}
}
debugMessage(this->getServerId(), "{} Client join timings: {}", CLIENT_STR_LOG_PREFIX, TIMING_FINISH(timings));
}
+119 -109
View File
@@ -529,51 +529,66 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(25);
CMD_CHK_PARM_COUNT(1);
//TODO: Use for this here the cache as well!
auto permission_cache = make_shared<CalculateCache>();
if (cmd[0].has("cpid") && cmd["cpid"].as<uint64_t>() != 0) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_child, 1, permission_cache);
if (cmd[0].has("channel_order")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_sortorder, 1, permission_cache);
if(!cmd[0].has("channel_flag_permanent")) cmd[0]["channel_flag_permanent"] = false;
if(!cmd[0].has("channel_flag_semi_permanent")) cmd[0]["channel_flag_semi_permanent"] = false;
if(!cmd[0].has("channel_flag_default")) cmd[0]["channel_flag_default"] = false;
if(!cmd[0].has("channel_flag_password")) cmd[0]["channel_flag_password"] = false;
if (cmd[0]["channel_flag_permanent"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_permanent, 1, permission_cache);
else if (cmd[0]["channel_flag_semi_permanent"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_semi_permanent, 1, permission_cache);
else ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_temporary, 1, permission_cache);
if (!cmd[0]["channel_flag_permanent"].as<bool>() && !this->server) return command_result{error::parameter_invalid, "You can only create a permanent channel"};
if (cmd[0]["channel_flag_default"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_default, 1, permission_cache);
if (cmd[0]["channel_flag_password"].as<bool>()) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_password, 1, permission_cache);
else if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_create_modify_with_force_password, 0, false, permission_cache)))
return command_result{permission::b_channel_create_modify_with_force_password};
if(cmd[0].has("channel_password") && this->getType() == ClientType::CLIENT_QUERY)
cmd["channel_password"] = base64::decode(digest::sha1(cmd["channel_password"].string()));
if (cmd[0].has("channel_description")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_description, 1, permission_cache);
if (cmd[0].has("channel_maxclients") || (cmd[0].has("channel_flag_maxclients_unlimited") && !cmd["channel_flag_maxclients_unlimited"].as<bool>())) {
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_maxclients, 1, permission_cache);
if(!cmd[0]["channel_flag_permanent"].as<bool>() && !cmd[0]["channel_flag_semi_permanent"].as<bool>()) {
cmd["channel_maxclients"] = -1;
cmd["channel_flag_maxclients_unlimited"] = 1;
}
}
if (cmd[0].has("channel_maxfamilyclients")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_maxfamilyclients, 1, permission_cache);
if (cmd[0].has("channel_needed_talk_power")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_needed_talk_power, 1, permission_cache);
if (cmd[0].has("channel_topic")) ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_with_topic, 1, permission_cache);
std::shared_ptr<TreeView::LinkedTreeEntry> parent = nullptr;
std::shared_ptr<BasicChannel> created_channel = nullptr, old_default_channel;
auto target_tree = this->server ? this->server->channelTree : serverInstance->getChannelTree().get();
auto& tree_lock = this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock();
unique_lock tree_channel_lock(tree_lock);
if (cmd[0].has("cpid") && cmd["cpid"].as<ChannelId>() != 0 && cmd["cpid"].as<int>() != -1) {
parent = target_tree->findLinkedChannel(cmd["cpid"].as<ChannelId>());
if (!parent) return command_result{error::channel_invalid_id, "Cant resolve parent channel"};
}
ChannelId parent_channel_id = parent ? parent->entry->channelId() : 0;
#define test_permission(required, permission_type) \
do {\
if(!permission::v2::permission_granted(required, this->calculate_permission(permission_type, parent_channel_id, false, permission_cache))) \
return command_result{permission_type};\
} while(0)
//TODO: Use for this here the cache as well!
auto permission_cache = make_shared<CalculateCache>();
if(parent) test_permission(1, permission::b_channel_create_child);
if (cmd[0].has("channel_order")) test_permission(1, permission::b_channel_create_with_sortorder);
if(!cmd[0].has("channel_flag_permanent")) cmd[0]["channel_flag_permanent"] = false;
if(!cmd[0].has("channel_flag_semi_permanent")) cmd[0]["channel_flag_semi_permanent"] = false;
if(!cmd[0].has("channel_flag_default")) cmd[0]["channel_flag_default"] = false;
if(!cmd[0].has("channel_flag_password")) cmd[0]["channel_flag_password"] = false;
if (cmd[0]["channel_flag_permanent"].as<bool>()) test_permission(1, permission::b_channel_create_permanent);
else if (cmd[0]["channel_flag_semi_permanent"].as<bool>()) test_permission(1, permission::b_channel_create_semi_permanent);
else test_permission(1, permission::b_channel_create_temporary);
if (!cmd[0]["channel_flag_permanent"].as<bool>() && !this->server) return command_result{error::parameter_invalid, "You can only create a permanent channel"};
if (cmd[0]["channel_flag_default"].as<bool>()) test_permission(1, permission::b_channel_create_with_default);
if (cmd[0]["channel_flag_password"].as<bool>()) test_permission(1, permission::b_channel_create_with_password);
else if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_create_modify_with_force_password, parent_channel_id, false, permission_cache)))
return command_result{permission::b_channel_create_modify_with_force_password};
if(cmd[0].has("channel_password") && this->getType() == ClientType::CLIENT_QUERY)
cmd["channel_password"] = base64::decode(digest::sha1(cmd["channel_password"].string()));
if (cmd[0].has("channel_description")) test_permission(1, permission::b_channel_create_with_description);
if (cmd[0].has("channel_maxclients") || (cmd[0].has("channel_flag_maxclients_unlimited") && !cmd["channel_flag_maxclients_unlimited"].as<bool>())) {
test_permission(1, permission::b_channel_create_with_maxclients);
if(!cmd[0]["channel_flag_permanent"].as<bool>() && !cmd[0]["channel_flag_semi_permanent"].as<bool>()) {
cmd["channel_maxclients"] = -1;
cmd["channel_flag_maxclients_unlimited"] = 1;
}
}
if (cmd[0].has("channel_maxfamilyclients")) test_permission(1, permission::b_channel_create_with_maxfamilyclients);
if (cmd[0].has("channel_needed_talk_power")) test_permission(1,permission::b_channel_create_with_needed_talk_power);
if (cmd[0].has("channel_topic")) test_permission(1,permission::b_channel_create_with_topic);
if(cmd[0].has("channel_conversation_history_length")) {
auto value = cmd["channel_conversation_history_length"].as<int64_t>();
if(value == 0) {
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::b_channel_create_modify_conversation_history_unlimited, 1, permission_cache);
test_permission(1, permission::b_channel_create_modify_conversation_history_unlimited);
} else {
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::i_channel_create_modify_conversation_history_length, 1, permission_cache);
test_permission(1, permission::i_channel_create_modify_conversation_history_length);
}
}
@@ -585,9 +600,10 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
else
cmd["channel_delete_delay"] = 0;
} else {
ACTION_REQUIRES_GLOBAL_PERMISSION_CACHED(permission::i_channel_create_modify_with_temp_delete_delay, cmd["channel_delete_delay"].as<permission::PermissionValue>(), permission_cache);
test_permission(cmd["channel_delete_delay"].as<permission::PermissionValue>(), permission::i_channel_create_modify_with_temp_delete_delay);
}
}
#undef test_permission
{
size_t created_total = 0, created_tmp = 0, created_semi = 0, created_perm = 0;
@@ -607,14 +623,14 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
if(this->server && created_total >= this->server->properties()[property::VIRTUALSERVER_MAX_CHANNELS].as<uint64_t>())
return command_result{error::channel_limit_reached};
auto max_channels = this->calculate_permission(permission::i_client_max_channels, 0, false, permission_cache);
auto max_channels = this->calculate_permission(permission::i_client_max_channels, parent_channel_id, false, permission_cache);
if(max_channels.has_value) {
if(!permission::v2::permission_granted(created_perm + created_semi + created_tmp + 1, max_channels))
return command_result{permission::i_client_max_channels};
}
if (cmd[0]["channel_flag_permanent"].as<bool>()) {
max_channels = this->calculate_permission(permission::i_client_max_permanent_channels, 0, false, permission_cache);
max_channels = this->calculate_permission(permission::i_client_max_permanent_channels, parent_channel_id, false, permission_cache);
if(max_channels.has_value) {
if(!permission::v2::permission_granted(created_perm + 1, max_channels))
@@ -622,7 +638,7 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
}
}
else if (cmd[0]["channel_flag_semi_permanent"].as<bool>()) {
max_channels = this->calculate_permission(permission::i_client_max_semi_channels, 0, false, permission_cache);
max_channels = this->calculate_permission(permission::i_client_max_semi_channels, parent_channel_id, false, permission_cache);
if(max_channels.has_value) {
if(!permission::v2::permission_granted(created_semi + 1, max_channels))
@@ -630,7 +646,7 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
}
}
else {
max_channels = this->calculate_permission(permission::i_client_max_temporary_channels, 0, false, permission_cache);
max_channels = this->calculate_permission(permission::i_client_max_temporary_channels, parent_channel_id, false, permission_cache);
if(max_channels.has_value) {
if(!permission::v2::permission_granted(created_tmp + 1, max_channels))
@@ -640,20 +656,12 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
}
//TODO check voice (opus etc)
std::shared_ptr<TreeView::LinkedTreeEntry> parent = nullptr;
std::shared_ptr<BasicChannel> created_channel = nullptr, old_default_channel;
//bool enforce_permanent_parent = cmd[0]["channel_flag_default"].as<bool>(); //TODO check parents here
{ //Checkout the parent(s)
if (cmd[0].has("cpid") && cmd["cpid"].as<ChannelId>() != 0 && cmd["cpid"].as<int>() != -1) {
parent = target_tree->findLinkedChannel(cmd["cpid"].as<ChannelId>());
if (!parent) return command_result{error::channel_invalid_id, "Cant resolve parent channel"};
}
{
auto min_channel_deep = this->calculate_permission(permission::i_channel_min_depth, 0, false, permission_cache);
auto max_channel_deep = this->calculate_permission(permission::i_channel_max_depth, 0, false, permission_cache);
auto min_channel_deep = this->calculate_permission(permission::i_channel_min_depth, parent_channel_id, false, permission_cache);
auto max_channel_deep = this->calculate_permission(permission::i_channel_max_depth, parent_channel_id, false, permission_cache);
if(min_channel_deep.has_value || max_channel_deep.has_value) {
auto channel_deep = 0;
@@ -704,8 +712,8 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
created_channel->properties()[property::CHANNEL_CREATED_BY] = this->getClientDatabaseId();
{
auto default_modify_power = this->calculate_permission(permission::i_channel_modify_power, 0, false, permission_cache);
auto default_delete_power = this->calculate_permission(permission::i_channel_delete_power, 0, false, permission_cache);
auto default_modify_power = this->calculate_permission(permission::i_channel_modify_power, parent_channel_id, false, permission_cache);
auto default_delete_power = this->calculate_permission(permission::i_channel_delete_power, parent_channel_id, false, permission_cache);
auto permission_manager = created_channel->permissions();
permission_manager->set_permission(
@@ -729,14 +737,14 @@ command_result ConnectedClient::handleCommandChannelCreate(Command &cmd) {
if (prop == "cpid") continue;
if (prop == "cid") continue;
const auto &property = property::info<property::ChannelProperties>(prop);
if(*property == property::CHANNEL_UNDEFINED) {
const auto &property = property::find<property::ChannelProperties>(prop);
if(property == property::CHANNEL_UNDEFINED) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a not existing channel property " + prop);
continue;
}
if(!property->validate_input(cmd[prop].as<string>())) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as<string>() + "', Property: '" + property->name + "')");
if(!property.validate_input(cmd[prop].as<string>())) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[prop].as<string>() + "', Property: '" + std::string{property.name} + "')");
continue;
}
created_channel->properties()[property] = cmd[prop].as<std::string>();
@@ -839,7 +847,7 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
return command_result{error::ok};
}
std::deque<std::shared_ptr<property::PropertyDescription>> keys;
std::deque<const property::PropertyDescription*> keys;
bool require_write_lock = false;
bool update_max_clients = false;
bool update_max_family_clients = false;
@@ -858,23 +866,23 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
if(key == "return_code")
continue;
const auto &property = property::info<property::ChannelProperties>(key);
if(*property == property::CHANNEL_UNDEFINED) {
const auto &property = property::find<property::ChannelProperties>(key);
if(property == property::CHANNEL_UNDEFINED) {
logError(this->getServerId(), R"({} Tried to edit a not existing channel property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if((property->flags & property::FLAG_USER_EDITABLE) == 0) {
if((property.flags & property::FLAG_USER_EDITABLE) == 0) {
logError(this->getServerId(), "{} Tried to change a channel property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if(!property->validate_input(cmd[key].as<string>())) {
if(!property.validate_input(cmd[key].as<string>())) {
logError(this->getServerId(), "{} Tried to change a channel property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if(channel->properties()[*property].as<string>() == cmd[key].as<string>())
if(channel->properties()[property].as<string>() == cmd[key].as<string>())
continue; /* we dont need to update stuff which is the same */
if(key == "channel_icon_id") {
@@ -964,7 +972,7 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
);
continue;
}
keys.push_back(property);
keys.push_back(&property);
}
unique_lock server_channel_w_lock(this->server ? this->server->channel_tree_lock : serverInstance->getChannelTreeLock(), defer_lock);
@@ -984,11 +992,11 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
return command_result{error::parameter_missing};
else
cmd["channel_password"] = ""; /* no password set */
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_PASSWORD));
keys.push_back(&property::describe(property::CHANNEL_PASSWORD));
}
if(!cmd[0].has("channel_flag_password")) {
cmd["channel_flag_password"] = !cmd["channel_password"].string().empty();
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_PASSWORD));
keys.push_back(&property::describe(property::CHANNEL_FLAG_PASSWORD));
}
if(cmd["channel_flag_password"].as<bool>()) {
@@ -1004,27 +1012,28 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
}
/* test the default channel update */
if(cmd[0].has("channel_flag_default") || channel->defaultChannel()) {
const auto target_will_be_default = cmd[0].has("channel_flag_default") ? cmd["channel_flag_default"].as<bool>() : channel->defaultChannel();
if(target_will_be_default) {
if(target_channel_type != ChannelType::permanent)
return command_result{error::channel_default_require_permanent}; /* default channel is not allowed to be non permanent */
if((cmd[0].has("channel_flag_password") && cmd["channel_flag_password"].as<bool>()) || channel->properties()[property::CHANNEL_FLAG_PASSWORD]) {
cmd["channel_flag_password"] = false;
cmd["channel_password"] = "";
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_PASSWORD));
keys.push_back(&property::describe(property::CHANNEL_FLAG_PASSWORD));
}
if(cmd[0].has("channel_flag_default")) {
if(target_will_be_default) {
cmd["channel_maxclients"] = -1;
cmd["channel_flag_maxclients_unlimited"] = true;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
keys.push_back(&property::describe(property::CHANNEL_MAXCLIENTS));
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
update_max_clients = true;
cmd["channel_maxfamilyclients"] = -1;
cmd["channel_flag_maxfamilyclients_inherited"] = true;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
cmd["channel_flag_maxfamilyclients_inherited"] = false;
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
update_max_family_clients = true;
}
}
@@ -1035,15 +1044,15 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
if(channel->properties()[property::CHANNEL_MAXCLIENTS].as<int>() != -1) {
cmd["channel_maxclients"] = -1;
cmd["channel_flag_maxclients_unlimited"] = true;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
keys.push_back(&property::describe(property::CHANNEL_MAXCLIENTS));
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
update_max_clients = true;
}
if(channel->properties()[property::CHANNEL_MAXFAMILYCLIENTS].as<int>() != -1) {
cmd["channel_maxfamilyclients"] = -1;
cmd["channel_flag_maxfamilyclients_inherited"] = true;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
update_max_family_clients = true;
}
}
@@ -1066,12 +1075,12 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
cmd["channel_maxclients"] = -1;
else
return command_result{error::parameter_missing, "channel_maxclients"}; /* max clients must be specified */
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXCLIENTS));
keys.push_back(&property::describe(property::CHANNEL_MAXCLIENTS));
}
if(!cmd[0].has("channel_flag_maxclients_unlimited")) {
cmd["channel_flag_maxclients_unlimited"] = cmd["channel_maxclients"].as<int>() < 0;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
keys.push_back(&property::describe(property::CHANNEL_FLAG_MAXCLIENTS_UNLIMITED));
}
if(cmd["channel_flag_maxclients_unlimited"].as<bool>() && cmd["channel_maxclients"].as<int>() != -1)
@@ -1083,32 +1092,24 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
/* test the max family clients parameters */
if(update_max_family_clients) {
//auto channel_maxfamilyclients = cmd[0].has("channel_maxfamilyclients") ? std::optional<int>{cmd["channel_maxfamilyclients"].as<int>()} : std::nullopt;
//auto channel_flag_maxfamilyclients_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") ? std::optional<bool>{cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>()} : std::nullopt;
//auto channel_flag_maxfamilyclients_inherited = cmd[0].has("channel_flag_maxfamilyclients_inherited") ? std::optional<bool>{cmd["channel_flag_maxfamilyclients_inherited"].as<bool>()} : std::nullopt;
/* update actual count from flags */
if(!cmd[0].has("channel_maxfamilyclients")) {
if(cmd[0].has("channel_flag_maxfamilyclients_unlimited")) {
if(cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>())
cmd["channel_flag_maxfamilyclients_inherited"] = false;
else
cmd["channel_flag_maxfamilyclients_inherited"] = true;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_INHERITED));
} else if(cmd[0].has("channel_flag_maxfamilyclients_inherited")) {
if(cmd["channel_flag_maxfamilyclients_inherited"].as<bool>())
cmd["channel_flag_maxfamilyclients_unlimited"] = false;
else
cmd["channel_flag_maxfamilyclients_unlimited"] = true;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_FLAG_MAXFAMILYCLIENTS_UNLIMITED));
} else /* not really possible */
return command_result{error::parameter_missing, "channel_maxfamilyclients"}; /* family max clients must be */
cmd["channel_maxfamilyclients"] = -1;
keys.push_back(property::info<property::ChannelProperties>(property::CHANNEL_MAXFAMILYCLIENTS));
}
//keep this order because this command: "channeledit cid=<x> channel_maxfamilyclients=-1" should set max family clients mode to inherited
if(!cmd[0].has("channel_flag_maxfamilyclients_inherited")) {
auto flag_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>();
if(flag_unlimited)
cmd["channel_flag_maxfamilyclients_inherited"] = false;
else
cmd["channel_flag_maxfamilyclients_inherited"] = cmd["channel_maxfamilyclients"].as<int>() < 0;
if(cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>()) {
cmd["channel_maxfamilyclients"] = -1;
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
} else if(cmd[0].has("channel_flag_maxfamilyclients_inherited") && cmd["channel_flag_maxfamilyclients_inherited"].as<bool>()) {
cmd["channel_maxfamilyclients"] = -1;
keys.push_back(&property::describe(property::CHANNEL_MAXFAMILYCLIENTS));
} else {
return command_result{error::parameter_missing, "channel_maxfamilyclients"}; /* since its not unlimited or inherited, channel_maxfamilyclients must be specified */
}
}
//Update the flags from channel_maxfamilyclients if needed
if(!cmd[0].has("channel_flag_maxfamilyclients_unlimited")) {
auto flag_inherited = cmd[0].has("channel_flag_maxfamilyclients_inherited") && cmd["channel_flag_maxfamilyclients_inherited"].as<bool>();
if(flag_inherited)
@@ -1117,6 +1118,15 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
cmd["channel_flag_maxfamilyclients_unlimited"] = cmd["channel_maxfamilyclients"].as<int>() < 0;
}
if(!cmd[0].has("channel_flag_maxfamilyclients_inherited")) {
auto flag_unlimited = cmd[0].has("channel_flag_maxfamilyclients_unlimited") && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>();
if(flag_unlimited)
cmd["channel_flag_maxfamilyclients_inherited"] = false;
else
cmd["channel_flag_maxfamilyclients_inherited"] = cmd["channel_maxfamilyclients"].as<int>() < 0;
}
/* final checkup */
if(cmd["channel_flag_maxfamilyclients_inherited"].as<bool>() && cmd["channel_flag_maxfamilyclients_unlimited"].as<bool>())
return command_result{error::channel_invalid_flags}; /* both at the same time are not possible */
@@ -1140,10 +1150,10 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
auto self_ref = this->ref();
shared_ptr<BasicChannel> old_default_channel;
deque<shared_ptr<BasicChannel>> child_channel_updated;
for(const std::shared_ptr<property::PropertyDescription>& key : keys) {
for(const property::PropertyDescription* key : keys) {
if(*key == property::CHANNEL_ORDER) {
/* TODO: May move that up because if it fails may some other props have already be applied */
if (!channel_tree->change_order(channel, cmd[key->name]))
if (!channel_tree->change_order(channel, cmd[std::string{key->name}]))
return command_result{error::channel_invalid_order, "Can't change order id"};
if(this->server) {
@@ -1186,7 +1196,7 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
continue;
}
if(!cmd[key->name].as<bool>()) {
if(!cmd[std::string{key->name}].as<bool>()) {
old_default_channel = nullptr;
continue;
}
@@ -1245,13 +1255,13 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
if(conversation_manager) {
auto conversation = conversation_manager->get(channel->channelId());
if(conversation)
conversation->set_history_length(cmd[key->name]);
conversation->set_history_length(cmd[std::string{key->name}]);
}
} else if(*key == property::CHANNEL_NEEDED_TALK_POWER) {
channel->permissions()->set_permission(permission::i_client_needed_talk_power, {cmd[key->name].as<int>(), 0}, permission::v2::set_value, permission::v2::do_nothing);
}
channel->properties()[key] = cmd[key->name].string();
channel->properties()[*key] = cmd[std::string{key->name}].string();
}
if(this->server) {
vector<property::ChannelProperties> key_vector;
+39 -35
View File
@@ -39,17 +39,21 @@ using namespace ts::token;
command_result ConnectedClient::handleCommandClientGetVariables(Command &cmd) {
CMD_REQ_SERVER;
ConnectedLockedClient client{this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
shared_lock tree_lock(this->channel_lock);
{
shared_lock tree_lock(this->channel_lock);
if (!client || (client.client != this && !this->isClientVisible(client.client, false)))
return command_result{error::client_invalid_id, ""};
if (!client || (client.client != this && !this->isClientVisible(client.client, false)))
return command_result{error::client_invalid_id, ""};
deque<shared_ptr<property::PropertyDescription>> props;
for (auto &prop : client->properties()->list_properties(property::FLAG_CLIENT_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
props.push_back(property::info((property::ClientProperties) prop.type().property_index));
deque<const property::PropertyDescription*> props;
for (auto &prop : client->properties()->list_properties(property::FLAG_CLIENT_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
props.push_back(&prop.type());
}
this->notifyClientUpdated(client.client, props, false);
}
this->notifyClientUpdated(client.client, props, false);
if(client.client == this && this->getType() == ClientType::CLIENT_TEAMSPEAK)
this->subscribeChannel({this->currentChannel}, true, true); /* lets show the clients in the current channel because we've not done that while joining (speed improvement ;))*/
return command_result{error::ok};
}
@@ -391,13 +395,13 @@ command_result ConnectedClient::handleCommandClientDBEdit(Command &cmd) {
for (auto &elm : cmd[0].keys()) {
if (elm == "cldbid") continue;
auto info = property::info<property::ClientProperties>(elm);
if(*info == property::CLIENT_UNDEFINED) {
const auto& info = property::find<property::ClientProperties>(elm);
if(info == property::CLIENT_UNDEFINED) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change someone's db entry, but the entry in unknown: " + elm);
continue;
}
if(!info->validate_input(cmd[elm].as<string>())) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[elm].as<string>() + "', Property: '" + info->name + "')");
if(!info.validate_input(cmd[elm].as<string>())) {
logError(this->getServerId(), "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + cmd[elm].as<string>() + "', Property: '" + std::string{info.name} + "')");
continue;
}
(*props)[info] = cmd[elm].string();
@@ -423,29 +427,29 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
bool update_talk_rights = false;
unique_ptr<lock_guard<std::recursive_mutex>> nickname_lock;
deque<pair<property::ClientProperties, string>> keys;
std::deque<std::pair<const property::PropertyDescription*, std::string>> keys;
for(const auto& key : cmd[0].keys()) {
if(key == "return_code") continue;
if(key == "clid") continue;
const auto &info = property::info<property::ClientProperties>(key);
if(*info == property::CLIENT_UNDEFINED) {
const auto &info = property::find<property::ClientProperties>(key);
if(info == property::CLIENT_UNDEFINED) {
logError(this->getServerId(), R"([{}] Tried to change a not existing client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string());
continue;
}
if((info->flags & property::FLAG_USER_EDITABLE) == 0) {
if((info.flags & property::FLAG_USER_EDITABLE) == 0) {
logError(this->getServerId(), R"([{}] Tried to change a not user editable client property for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string());
continue;
}
if(!info->validate_input(cmd[key].as<string>())) {
if(!info.validate_input(cmd[key].as<string>())) {
logError(this->getServerId(), R"([{}] Tried to change a client property to an invalid value for {}. (Key: "{}", Value: "{}"))", CLIENT_STR_LOG_PREFIX, CLIENT_STR_LOG_PREFIX_(client), key, cmd[key].string());
continue;
}
if(client->properties()[info].as<string>() == cmd[key].as<string>()) continue;
if(client->properties()[&info].as<string>() == cmd[key].as<string>()) continue;
if (*info == property::CLIENT_DESCRIPTION) {
if (info == property::CLIENT_DESCRIPTION) {
if (self) {
ACTION_REQUIRES_PERMISSION(permission::b_client_modify_own_description, 1, client->getChannelId());
} else if(client->getType() == ClientType::CLIENT_MUSIC) {
@@ -458,16 +462,16 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
string value = cmd["client_description"].string();
if (count_characters(value) > 200) return command_result{error::parameter_invalid, "Invalid description length. A maximum of 200 characters is allowed!"};
} else if (*info == property::CLIENT_IS_TALKER) {
} else if (info == property::CLIENT_IS_TALKER) {
ACTION_REQUIRES_PERMISSION(permission::b_client_set_flag_talker, 1, client->getChannelId());
cmd["client_is_talker"] = cmd["client_is_talker"].as<bool>();
cmd["client_talk_request"] = 0;
update_talk_rights = true;
keys.emplace_back(property::CLIENT_IS_TALKER, "client_is_talker");
keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request");
keys.emplace_back(&property::describe(property::CLIENT_IS_TALKER), "client_is_talker");
keys.emplace_back(&property::describe(property::CLIENT_TALK_REQUEST), "client_talk_request");
continue;
} else if(*info == property::CLIENT_NICKNAME) {
} else if(info == property::CLIENT_NICKNAME) {
if(!self) {
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
@@ -500,7 +504,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
continue;
}
}
} else if(*info == property::CLIENT_PLAYER_VOLUME) {
} else if(info == property::CLIENT_PLAYER_VOLUME) {
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
@@ -515,7 +519,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
return command_result{permission::i_client_music_create_modify_max_volume};
bot->volume_modifier(cmd["player_volume"]);
} else if(*info == property::CLIENT_IS_CHANNEL_COMMANDER) {
} else if(info == property::CLIENT_IS_CHANNEL_COMMANDER) {
if(!self) {
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
@@ -525,7 +529,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
if(cmd["client_is_channel_commander"].as<bool>())
ACTION_REQUIRES_PERMISSION(permission::b_client_use_channel_commander, 1, client->getChannelId());
} else if(*info == property::CLIENT_IS_PRIORITY_SPEAKER) {
} else if(info == property::CLIENT_IS_PRIORITY_SPEAKER) {
//FIXME allow other to remove this thing
if(!self) {
if(client->getType() != ClientType::CLIENT_MUSIC)
@@ -544,7 +548,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
cmd["client_talk_request"] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
else
cmd["client_talk_request"] = 0;
keys.emplace_back(property::CLIENT_TALK_REQUEST, "client_talk_request");
keys.emplace_back(&property::describe(property::CLIENT_TALK_REQUEST), "client_talk_request");
continue;
} else if (self && key == "client_badges") {
std::string str = cmd[key];
@@ -576,7 +580,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
}
} else if(!self && (*info == property::CLIENT_FLAG_NOTIFY_SONG_CHANGE/* || *info == property::CLIENT_NOTIFY_SONG_MESSAGE*/)) {
} else if(!self && (info == property::CLIENT_FLAG_NOTIFY_SONG_CHANGE/* || info == property::CLIENT_NOTIFY_SONG_MESSAGE*/)) {
if(client->getType() != ClientType::CLIENT_MUSIC) return command_result{error::client_invalid_type};
if(client->properties()[property::CLIENT_OWNER] != this->getClientDatabaseId()) {
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
@@ -596,8 +600,8 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
cmd["client_lastconnected"] = value;
}
keys.emplace_back(property::CLIENT_LASTCONNECTED, "client_lastconnected");
} else if(!self && *info == property::CLIENT_BOT_TYPE) {
keys.emplace_back(&property::describe(property::CLIENT_LASTCONNECTED), "client_lastconnected");
} else if(!self && info == property::CLIENT_BOT_TYPE) {
ACTION_REQUIRES_PERMISSION(permission::i_client_music_modify_power, client->calculate_permission(permission::i_client_music_needed_modify_power, client->getChannelId()), client->getChannelId());
auto type = cmd["client_bot_type"].as<MusicClient::Type::value>();
if(type == MusicClient::Type::TEMPORARY) {
@@ -608,7 +612,7 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
ACTION_REQUIRES_PERMISSION(permission::b_client_music_modify_permanent, 1, client->getChannelId());
} else
return command_result{error::parameter_invalid};
} else if(*info == property::CLIENT_AWAY_MESSAGE) {
} else if(info == property::CLIENT_AWAY_MESSAGE) {
if(!self) continue;
if(cmd["client_away_message"].string().length() > 256)
@@ -617,12 +621,12 @@ command_result ConnectedClient::handleCommandClientEdit(Command &cmd, const std:
continue;
}
keys.emplace_back((property::ClientProperties) info->property_index, key);
keys.emplace_back(&info, key);
}
deque<property::ClientProperties> updates;
deque<const property::PropertyDescription*> updates;
for(const auto& key : keys) {
if(key.first == property::CLIENT_IS_PRIORITY_SPEAKER) {
if(*key.first == property::CLIENT_IS_PRIORITY_SPEAKER) {
client->clientPermissions->set_permission(permission::b_client_is_priority_speaker, {1, 0}, cmd["client_is_priority_speaker"].as<bool>() ? permission::v2::PermissionUpdateType::set_value : permission::v2::PermissionUpdateType::delete_value, permission::v2::PermissionUpdateType::do_nothing);
}
client->properties()[key.first] = cmd[0][key.second].value();
@@ -1116,7 +1120,7 @@ command_result ConnectedClient::handleCommandClientInfo(Command &cmd) {
}
for (const auto &key : client->properties()->list_properties(property::FLAG_CLIENT_VIEW | property::FLAG_CLIENT_VARIABLE | property::FLAG_CLIENT_INFO, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
res[result_index][key.type().name] = key.value();
res[result_index][std::string{key.type().name}] = key.value();
if(view_remote)
res[result_index]["connection_client_ip"] = client->properties()[property::CONNECTION_CLIENT_IP].as<string>();
else
@@ -137,6 +137,10 @@ inline bool permission_require_granted_value(ts::permission::PermissionType type
case permission::i_client_max_permanent_channels:
case permission::i_client_max_semi_channels:
case permission::i_client_max_temporary_channels:
case permission::i_channel_create_modify_with_temp_delete_delay:
case permission::i_client_talk_power:
case permission::i_client_needed_talk_power:
case permission::b_channel_create_with_needed_talk_power:
case permission::i_channel_max_depth:
case permission::i_channel_min_depth:
+159 -80
View File
@@ -366,7 +366,7 @@ command_result ConnectedClient::handleCommandPermissionList(Command &cmd) {
#define M(ptype) \
do { \
for(const auto& prop : property::impl::list<ptype>()) { \
for(const auto& prop : property::list<ptype>()) { \
if((prop->flags & property::FLAG_INTERNAL) > 0) continue; \
response[index]["name"] = prop->name; \
response[index]["flags"] = prop->flags; \
@@ -767,7 +767,9 @@ command_result ConnectedClient::handleCommandBanClient(Command &cmd) {
CMD_RESET_IDLE;
CMD_CHK_AND_INC_FLOOD_POINTS(25);
string uid;
std::string target_unique_id{};
ClientDbId target_database_id{0};
string reason = cmd[0].has("banreason") ? cmd["banreason"].string() : "";
auto time = cmd[0].has("time") ? cmd["time"].as<uint64_t>() : 0UL;
chrono::time_point<chrono::system_clock> until = time > 0 ? chrono::system_clock::now() + chrono::seconds(time) : chrono::time_point<chrono::system_clock>();
@@ -776,43 +778,40 @@ command_result ConnectedClient::handleCommandBanClient(Command &cmd) {
const auto no_hwid = cmd.hasParm("no-hardware-id");
const auto no_ip = cmd.hasParm("no-ip");
deque<shared_ptr<ConnectedClient>> target_clients;
std::deque<std::shared_ptr<ConnectedClient>> target_clients;
if (cmd[0].has("uid")) {
target_clients = this->server->findClientsByUid(uid = cmd["uid"].string());
for(const auto& client : target_clients)
if(client->getType() == ClientType::CLIENT_MUSIC)
return command_result{error::client_invalid_id, "You cant ban a music bot!"};
target_clients = this->server->findClientsByUid(target_unique_id = cmd["uid"].string());
} else if(cmd[0].has("cldbid")) {
target_clients = this->server->findClientsByCldbId(target_database_id = cmd["cldbid"].as<ClientDbId>());
} else {
target_clients = {this->server->find_client_by_id(cmd["clid"].as<ClientId>())};
if(!target_clients[0]) {
return command_result{error::client_invalid_id, "Could not find target client"};
}
if(target_clients[0]->getType() == ClientType::CLIENT_MUSIC) {
}
for(const auto& client : target_clients)
if(client->getType() == ClientType::CLIENT_MUSIC)
return command_result{error::client_invalid_id, "You cant ban a music bot!"};
}
uid = target_clients[0]->getUid();
}
ClientDbId target_dbid = 0;
if (!target_clients.empty()) {
target_dbid = target_clients[0]->getClientDatabaseId();
} else {
auto info = serverInstance->databaseHelper()->queryDatabaseInfoByUid(this->getServer(), {uid});
if (!info.empty())
target_dbid = info[0]->cldbid;
else
return command_result{error::client_unknown};
}
if(!target_clients.empty()) {
if(target_unique_id.empty())
target_unique_id = target_clients.back()->getUid();
if(!permission::v2::permission_granted(this->server->calculate_permission(permission::i_client_needed_ban_power, target_dbid, ClientType::CLIENT_TEAMSPEAK, 0), this->calculate_permission(permission::i_client_ban_power, 0)))
if(!target_database_id)
target_database_id = target_clients.back()->getClientDatabaseId();
}
if(!permission::v2::permission_granted(this->server->calculate_permission(permission::i_client_needed_ban_power, target_database_id, ClientType::CLIENT_TEAMSPEAK, 0), this->calculate_permission(permission::i_client_ban_power, 0)))
return command_result{permission::i_client_ban_power};
if (permission::v2::permission_granted(1, this->server->calculate_permission(permission::b_client_ignore_bans, target_dbid, ClientType::CLIENT_TEAMSPEAK, 0)))
if (permission::v2::permission_granted(1, this->server->calculate_permission(permission::b_client_ignore_bans, target_database_id, ClientType::CLIENT_TEAMSPEAK, 0)))
return command_result{permission::b_client_ignore_bans};
deque<BanId> ban_ids;
auto _id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, uid, "", "", "", until);
ban_ids.push_back(_id);
if(!target_unique_id.empty()) {
auto _id = serverInstance->banManager()->registerBan(this->getServer()->getServerId(), this->getClientDatabaseId(), reason, target_unique_id, "", "", "", until);
ban_ids.push_back(_id);
}
auto b_ban_name = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_name, 0), false);
auto b_ban_ip = permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ban_ip, 0), false);
@@ -1329,7 +1328,9 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
CMD_CHK_AND_INC_FLOOD_POINTS(5);
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_permission_find, 1);
deque<pair<pair<string, permission::PermissionType>, bool>> permissions;
std::vector<std::tuple<std::string, permission::PermissionType, bool>> requested_permissions{};
requested_permissions.reserve(cmd.bulkCount());
std::shared_ptr<permission::PermissionTypeEntry> permission;
for(size_t index = 0; index < cmd.bulkCount(); index++) {
bool granted = false;
@@ -1349,22 +1350,21 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
continue;
}
permissions.emplace_back(pair<pair<string, permission::PermissionType>, bool>{{permission->name, permission->type}, granted});
requested_permissions.emplace_back(permission->name, permission->type, granted);
}
if(permissions.empty())
if(requested_permissions.empty())
return command_result{error::database_empty_result};
map<string, uint8_t> flags;
map<string, permission::PermissionType> quick_mapping;
string query_string;
for(const auto& entry : permissions) {
if(flags[entry.first.first] == 0) {
quick_mapping[entry.first.first] = entry.first.second;
query_string += string(query_string.empty() ? "" : " OR ") + "`permId` = '" + entry.first.first + "'";
std::map<std::string, std::tuple<permission::PermissionType, uint8_t>> db_lookup_mapping{};
std::string query_string{};
for(const auto& [name, id, as_granted] : requested_permissions) {
auto& mapping = db_lookup_mapping[name];
if(std::get<0>(mapping) == 0) {
std::get<0>(mapping) = id;
query_string += std::string{query_string.empty() ? "" : " OR "} + "`permId` = '" + name + "'";
}
flags[entry.first.first] |= entry.second ? 2 : 1;
std::get<1>(mapping) |= (1U << as_granted);
}
deque<unique_ptr<PermissionEntry>> entries;
@@ -1373,13 +1373,14 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
variable{":sid", this->server->getServerId()},
variable{":playlist", permission::SQL_PERM_PLAYLIST}
).query([&](int length, string* values, string* columns) {
permission::PermissionSqlType type = permission::SQL_PERM_GROUP;
uint64_t id = 0;
ChannelId channel_id = 0;
permission::PermissionValue value = 0,
granted_value = 0;
string permission_name;
permission::PermissionSqlType type{permission::SQL_PERM_GROUP};
uint64_t id{0};
ChannelId channel_id{0};
permission::PermissionValue value{0}, granted_value{0};
string permission_name{};
bool negate = false, skip = false;
#if 0
for (int index = 0; index < length; index++) {
try {
if(columns[index] == "type")
@@ -1403,35 +1404,48 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
return 0;
}
}
#else
assert(length == 8);
try {
type = static_cast<permission::PermissionSqlType>(stoll(values[1]));
permission_name = values[0];
id = static_cast<uint64_t>(stoll(values[2]));
channel_id = static_cast<ChannelId>(stoll(values[3]));
value = static_cast<permission::PermissionValue>(stoll(values[4]));
granted_value = static_cast<permission::PermissionValue>(stoll(values[5]));
negate = values[7] == "1";
skip = values[6] == "1";
} catch(std::exception& ex) {
return 0;
}
#endif
auto request = db_lookup_mapping.find(permission_name);
if(request == db_lookup_mapping.end()) return 0; /* shall not happen */
auto flags = std::get<1>(request->second);
/* value */
if((flags[permission_name] & 0x1) > 0 && value > 0) {
if((flags & 0x1U) > 0 && value > 0) {
auto result = make_unique<PermissionEntry>();
result->permission_type = quick_mapping[permission_name];
result->permission_type = std::get<0>(request->second);
result->permission_value = value;
result->type = type;
result->channel_id = channel_id;
result->negate = negate;
result->skip = skip;
if (type == permission::SQL_PERM_GROUP) {
auto gr = this->server->groups->findGroup(id);
if (!gr) return 0;
result->group_id = id;
if(gr->target() == GROUPTARGET_CHANNEL)
result->channel_id = 1;
} else if(type == permission::SQL_PERM_USER) {
result->client_id = id;
}
if(result)
entries.push_back(std::move(result));
entries.push_back(std::move(result));
}
/* granted */
if((flags[permission_name] & 0x2) > 0 && granted_value > 0) {
if((flags & 0x2U) > 0 && granted_value > 0) {
auto result = make_unique<PermissionEntry>();
result->permission_type = (permission::PermissionType) (quick_mapping[permission_name] | PERM_ID_GRANT);
result->permission_type = (permission::PermissionType) (std::get<0>(request->second) | PERM_ID_GRANT);
result->permission_value = granted_value;
result->type = type;
result->channel_id = channel_id;
@@ -1439,21 +1453,17 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
result->skip = skip;
if (type == permission::SQL_PERM_GROUP) {
auto gr = this->server->groups->findGroup(id);
if (!gr) return 0;
result->group_id = id;
if(gr->target() == GROUPTARGET_CHANNEL)
result->channel_id = 1;
} else if(type == permission::SQL_PERM_USER) {
result->client_id = id;
}
if(result)
entries.push_back(std::move(result));
entries.push_back(std::move(result));
}
return 0;
});
if(entries.empty())
return command_result{error::database_empty_result};
struct CommandPerm {
permission::PermissionType p;
@@ -1465,13 +1475,13 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
std::vector<CommandPerm> perms;
perms.resize(entries.size());
size_t index = 0;
size_t index{0};
auto all_groups = this->server->groups->availableGroups(true);
for(const auto& entry : entries) {
auto& perm = perms[index++];
perm.p = entry->permission_type;
perm.v = entry->permission_value;
#if 0 /* TS3 switched the oder and YatQa as well, to keep compatibility we do it as well */
if(entry->type == permission::SQL_PERM_USER) {
if(entry->channel_id > 0) {
perm.id1 = entry->client_id;
@@ -1497,7 +1507,42 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
perm.t = 0; /* server group */
}
}
#else
if(entry->type == permission::SQL_PERM_USER) {
if(entry->channel_id > 0) {
perm.id1 = entry->channel_id;
perm.id2 = entry->client_id;
perm.t = 4; /* client channel */
} else {
perm.id1 = entry->client_id;
perm.id2 = 0;
perm.t = 1; /* client server */
}
} else if(entry->type == permission::SQL_PERM_CHANNEL) {
perm.id1 = entry->channel_id;
perm.id2 = 0;
perm.t = 2; /* channel permission */
} else if(entry->type == permission::SQL_PERM_GROUP) {
auto group = std::find_if(all_groups.begin(), all_groups.end(), [&](const auto& group) { return group->groupId() == entry->group_id; });
if(group == all_groups.end()) {
index--; /* unknown group */
continue;
}
if((*group)->target() == GroupTarget::GROUPTARGET_CHANNEL) {
perm.id1 = 0;
perm.id2 = entry->group_id;
perm.t = 3; /* channel group */
} else {
perm.id1 = entry->group_id;
perm.id2 = 0;
perm.t = 0; /* server group */
}
}
#endif
perm.p = entry->permission_type;
perm.v = entry->permission_value;
}
perms.erase(perms.begin() + index, perms.end());
sort(perms.begin(), perms.end(), [](const CommandPerm& a, const CommandPerm& b) {
@@ -1516,6 +1561,7 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
return &a > &b;
});
#if 0
Command result(this->notify_response_command("notifypermfind"));
index = 0;
@@ -1528,9 +1574,22 @@ command_result ConnectedClient::handleCommandPermFind(Command &cmd) {
result[index]["t"] = e.t;
index++;
}
if(index == 0) return command_result{error::database_empty_result};
this->sendCommand(result);
#else
command_builder result{this->notify_response_command("notifypermfind"), 64, perms.size()};
index = 0;
for(const auto& e : perms) {
auto bulk = result.bulk(index++);
bulk.put("t", e.t);
bulk.put("p", (uint16_t) e.p);
bulk.put("v", e.v);
bulk.put("id1", e.id1);
bulk.put("id2", e.id2);
}
this->sendCommand(result);
#endif
return command_result{error::ok};
}
@@ -2088,6 +2147,7 @@ command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) {
OptionalServerId server_id = this->getServerId();
if(cmd[0].has("server_id"))
server_id = cmd["server_id"];
if(cmd[0].has("sid"))
server_id = cmd["sid"];
@@ -2095,24 +2155,43 @@ command_result ConnectedClient::handleCommandQueryCreate(ts::Command &cmd) {
if(!server && server_id != EmptyServerId && server_id != 0)
return command_result{error::server_invalid_id};
if(server) {
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create};
} else {
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create};
}
auto username = cmd["client_login_name"].as<string>();
auto password = cmd[0].has("client_login_password") ? cmd["client_login_password"].as<string>() : "";
if(password.empty())
password = rnd_string(QUERY_PASSWORD_LENGTH);
auto account = serverInstance->getQueryServer()->find_query_account_by_name(username);
if(account) return command_result{error::query_already_exists};
account = serverInstance->getQueryServer()->create_query_account(username, server_id, this->getUid(), password);
std::string uid = this->getUid();
if(cmd[0].has("cldbid")){
if(!serverInstance->databaseHelper()->validClientDatabaseId(server, cmd["cldbid"].as<ClientDbId>()))
return command_result{error::database_empty_result};
if(server) {
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create};
} else {
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create};
}
auto info = serverInstance->databaseHelper()->queryDatabaseInfo(server, {cmd["cldbid"].as<ClientDbId>()});
if(info.empty())
return command_result{error::database_empty_result};
uid = info[0]->uniqueId;
} else {
if(server) {
if(!permission::v2::permission_granted(1, server->calculate_permission(permission::b_client_query_create_own, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create_own};
} else {
if(!permission::v2::permission_granted(1, serverInstance->calculate_permission(permission::b_client_query_create_own, this->getClientDatabaseId(), this->getType(), 0)))
return command_result{permission::b_client_query_create_own};
}
}
if(password.empty())
password = rnd_string(QUERY_PASSWORD_LENGTH);
account = serverInstance->getQueryServer()->create_query_account(username, server_id, uid, password);
if(!account)
return command_result{error::vs_critical};
@@ -2262,7 +2341,7 @@ command_result ConnectedClient::handleCommandDummy_IpChange(ts::Command &cmd) {
if(geoloc::provider) {
auto loc = this->isAddressV4() ? geoloc::provider->resolveInfoV4(this->getPeerIp(), false) : geoloc::provider->resolveInfoV6(this->getPeerIp(), false);
if(loc) {
logError(this->getServerId(), "[{}] Received new ip location. IP {} traced to {} ({}).", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp(), loc->name, loc->identifier);
logMessage(this->getServerId(), "[{}] Received new ip location. IP {} traced to {} ({}).", CLIENT_STR_LOG_PREFIX, this->getLoggingPeerIp(), loc->name, loc->identifier);
this->properties()[property::CLIENT_COUNTRY] = loc->identifier;
server->notifyClientPropertyUpdates(_this.lock(), deque<property::ClientProperties>{property::CLIENT_COUNTRY});
new_country = loc->identifier;
+66 -35
View File
@@ -447,41 +447,41 @@ command_result ConnectedClient::handleCommandPlaylistEdit(ts::Command &cmd) {
if(auto perr = playlist->client_has_permissions(this->ref(), permission::i_playlist_needed_modify_power, permission::i_playlist_modify_power); perr)
return command_result{perr};
deque<pair<shared_ptr<property::PropertyDescription>, string>> properties;
deque<pair<const property::PropertyDescription*, string>> properties;
for(const auto& key : cmd[0].keys()) {
if(key == "playlist_id") continue;
if(key == "return_code") continue;
auto property = property::info<property::PlaylistProperties>(key);
if(*property == property::PLAYLIST_UNDEFINED) {
const auto& property = property::find<property::PlaylistProperties>(key);
if(property == property::PLAYLIST_UNDEFINED) {
logError(this->getServerId(), R"([{}] Tried to edit a not existing playlist property "{}" to "{}")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if((property->flags & property::FLAG_USER_EDITABLE) == 0) {
if((property.flags & property::FLAG_USER_EDITABLE) == 0) {
logError(this->getServerId(), "[{}] Tried to change a playlist property which is not changeable. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if(!property->validate_input(cmd[key].as<string>())) {
if(!property.validate_input(cmd[key].as<string>())) {
logError(this->getServerId(), "[{}] Tried to change a playlist property to an invalid value. (Key: {}, Value: \"{}\")", CLIENT_STR_LOG_PREFIX, key, cmd[key].string());
continue;
}
if(*property == property::PLAYLIST_CURRENT_SONG_ID) {
if(property == property::PLAYLIST_CURRENT_SONG_ID) {
auto song_id = cmd[key].as<SongId>();
auto song = song_id > 0 ? playlist->find_song(song_id) : nullptr;
if(song_id != 0 && !song)
return command_result{error::playlist_invalid_song_id};
} else if(*property == property::PLAYLIST_MAX_SONGS) {
} else if(property == property::PLAYLIST_MAX_SONGS) {
auto value = cmd[key].as<int32_t>();
auto max_value = this->calculate_permission(permission::i_max_playlist_size, this->getChannelId());
if(max_value.has_value && !permission::v2::permission_granted(value, max_value))
return command_result{permission::i_max_playlist_size};
}
properties.emplace_back(property, key);
properties.emplace_back(&property, key);
}
for(const auto& property : properties) {
if(*property.first == property::PLAYLIST_CURRENT_SONG_ID) {
@@ -801,6 +801,44 @@ command_result ConnectedClient::handleCommandPlaylistClientDelPerm(ts::Command &
return command_result{error::ok};
}
constexpr auto max_song_meta_info = 1024 * 512;
inline size_t estimated_song_info_size(const std::shared_ptr<ts::music::PlaylistEntryInfo>& song, bool extract_metadata) {
return 128 + std::min(song->metadata.json_string.length(), (size_t) max_song_meta_info) + extract_metadata * 256;
}
inline void fill_song_info(ts::command_builder_bulk bulk, const std::shared_ptr<ts::music::PlaylistEntryInfo>& song, bool extract_metadata) {
bulk.reserve(estimated_song_info_size(song, extract_metadata));
bulk.put("song_id", song->song_id);
bulk.put("song_invoker", song->invoker);
bulk.put("song_previous_song_id", song->previous_song_id);
bulk.put("song_url", song->original_url);
bulk.put("song_url_loader", song->url_loader);
bulk.put("song_loaded", song->metadata.is_loaded());
if(song->metadata.json_string.length() > 1024 * 1024 * 512) {
logWarning(LOG_GENERAL, "Dropping song metadata because its way to big. ({}bytes)", song->metadata.json_string.size());
} else {
bulk.put("song_metadata", song->metadata.json_string);
}
if(extract_metadata) {
bulk.reserve(256, true);
auto metadata = song->metadata.loaded_data;
if(extract_metadata && song->metadata.is_loaded() && metadata) {
bulk.put("song_metadata_title", metadata->title);
bulk.put("song_metadata_description", metadata->description);
bulk.put("song_metadata_url", metadata->url);
bulk.put("song_metadata_length", metadata->length.count());
if(auto thumbnail = static_pointer_cast<::music::ThumbnailUrl>(metadata->thumbnail); thumbnail && thumbnail->type() == ::music::THUMBNAIL_URL) {
bulk.put("song_metadata_thumbnail_url", thumbnail->url());
} else {
bulk.put("song_metadata_thumbnail_url", "none");
}
}
}
}
command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd) {
CMD_REF_SERVER(ref_server);
CMD_RESET_IDLE;
@@ -816,39 +854,32 @@ command_result ConnectedClient::handleCommandPlaylistSongList(ts::Command &cmd)
if(songs.empty())
return command_result{error::database_empty_result};
Command notify(this->notify_response_command("notifyplaylistsonglist"));
notify["playlist_id"] = playlist->playlist_id();
ts::command_builder result{this->notify_response_command("notifyplaylistsonglist")};
result.put(0, "version", 2); /* to signalize that we're sending the response bulked */
auto extract_metadata = cmd.hasParm("extract-metadata");
size_t index = 0;
size_t index{0};
for(const auto& song : songs) {
notify[index]["song_id"] = song->song_id;
notify[index]["song_invoker"] = song->invoker;
notify[index]["song_previous_song_id"] = song->previous_song_id;
notify[index]["song_url"] = song->original_url;
notify[index]["song_url_loader"] = song->url_loader;
notify[index]["song_loaded"] = song->metadata.is_loaded();
notify[index]["song_metadata"] = song->metadata.json_string;
if(extract_metadata) {
auto metadata = song->metadata.loaded_data;
if(extract_metadata && song->metadata.is_loaded() && metadata) {
notify[index]["song_metadata_title"] = metadata->title;
notify[index]["song_metadata_description"] = metadata->description;
notify[index]["song_metadata_url"] = metadata->url;
notify[index]["song_metadata_length"] = metadata->length.count();
if(auto thumbnail = static_pointer_cast<::music::ThumbnailUrl>(metadata->thumbnail); thumbnail && thumbnail->type() == ::music::THUMBNAIL_URL) {
notify[index]["song_metadata_thumbnail_url"] = thumbnail->url();
} else {
notify[index]["song_metadata_thumbnail_url"] = "none";
}
}
if(index == 0) {
result.put(0, "playlist_id", playlist->playlist_id());
}
fill_song_info(result.bulk(index), song, extract_metadata);
index++;
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK && result.current_size() + estimated_song_info_size(song, extract_metadata) > 128 * 1024) {
this->sendCommand(result);
result.reset();
index = 0;
} else {
index++;
}
}
if(index > 0)
this->sendCommand(result);
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK) {
ts::command_builder finish{"notifyplaylistsonglistfinished"};
finish.put(0, "playlist_id", playlist->playlist_id());
this->sendCommand(finish);
}
this->sendCommand(notify);
return command_result{error::ok};
}
+30 -26
View File
@@ -224,14 +224,14 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) {
std::deque<std::string> keys;
bool group_update = false;
for (const auto& elm : toApplay) {
auto info = property::impl::info<property::VirtualServerProperties>(elm.first);
if(*info == property::VIRTUALSERVER_UNDEFINED) {
const auto& info = property::find<property::VirtualServerProperties>(elm.first);
if(info == property::VIRTUALSERVER_UNDEFINED) {
logCritical(target_server ? target_server->getServerId() : 0, "Missing server property " + elm.first);
continue;
}
if(!info->validate_input(elm.second)) {
logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + info->name + "')");
if(!info.validate_input(elm.second)) {
logError(target_server ? target_server->getServerId() : 0, "Client " + this->getDisplayName() + " tried to change a property to an invalid value. (Value: '" + elm.second + "', Property: '" + std::string{info.name} + "')");
continue;
}
if(target_server)
@@ -240,7 +240,7 @@ command_result ConnectedClient::handleCommandServerEdit(Command &cmd) {
(*serverInstance->getDefaultServerProperties())[info] = elm.second;
keys.push_back(elm.first);
group_update |= *info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || *info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || *info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP;
group_update |= info == property::VIRTUALSERVER_DEFAULT_SERVER_GROUP || info == property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP || info == property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP;
}
if(target_server) {
@@ -262,35 +262,39 @@ command_result ConnectedClient::handleCommandServerRequestConnectionInfo(Command
CMD_REQ_SERVER;
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_connectioninfo_view, 1);
Command notify("notifyserverconnectioninfo");
ts::command_builder result{"notifyserverconnectioninfo"};
auto first_bulk = result.bulk(0);
auto statistics = this->server->getServerStatistics()->statistics();
auto report = this->server->getServerStatistics()->dataReport();
auto total_stats = this->server->getServerStatistics()->total_stats();
auto minute_report = this->server->getServerStatistics()->minute_stats();
auto second_report = this->server->getServerStatistics()->second_stats();
auto network_report = this->server->generate_network_report();
notify[0]["connection_filetransfer_bandwidth_sent"] = report.file_send;
notify[0]["connection_filetransfer_bandwidth_received"] = report.file_recv;
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT, minute_report.file_bytes_sent);
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, minute_report.file_bytes_received);
notify[0]["connection_filetransfer_bytes_sent_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
notify[0]["connection_filetransfer_bytes_received_total"] = (*statistics)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL, minute_report.file_bytes_sent);
first_bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL, minute_report.file_bytes_received);
notify[0]["connection_filetransfer_bytes_sent_month"] = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as<string>();
notify[0]["connection_filetransfer_bytes_received_month"] = this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as<string>();
first_bulk.put_unchecked("connection_filetransfer_bytes_sent_month", this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].as<string>());
first_bulk.put_unchecked("connection_filetransfer_bytes_received_month", this->server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].as<string>());
notify[0]["connection_packets_sent_total"] = (*statistics)[property::CONNECTION_PACKETS_SENT_TOTAL].as<string>();
notify[0]["connection_bytes_sent_total"] = (*statistics)[property::CONNECTION_BYTES_SENT_TOTAL].as<string>();
notify[0]["connection_packets_received_total"] = (*statistics)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].as<string>();
notify[0]["connection_bytes_received_total"] = (*statistics)[property::CONNECTION_BYTES_RECEIVED_TOTAL].as<string>();
first_bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_TOTAL, std::accumulate(total_stats.connection_packets_sent.begin(), total_stats.connection_packets_sent.end(), 0U));
first_bulk.put_unchecked(property::CONNECTION_BYTES_SENT_TOTAL, std::accumulate(total_stats.connection_bytes_sent.begin(), total_stats.connection_bytes_sent.end(), 0U));
first_bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_TOTAL, std::accumulate(total_stats.connection_packets_received.begin(), total_stats.connection_packets_received.end(), 0U));
first_bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_TOTAL, std::accumulate(total_stats.connection_bytes_received.begin(), total_stats.connection_bytes_received.end(), 0U));
notify[0]["connection_bandwidth_sent_last_second_total"] = report.send_second;
notify[0]["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
notify[0]["connection_bandwidth_received_last_second_total"] = report.recv_second;
notify[0]["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
notify[0]["connection_connected_time"] = this->server->properties()[property::VIRTUALSERVER_UPTIME].as<string>();
notify[0]["connection_packetloss_total"] = this->server->averagePacketLoss();
notify[0]["connection_ping"] = this->server->averagePing();
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_TOTAL, std::accumulate(second_report.connection_bytes_sent.begin(), second_report.connection_bytes_sent.end(), 0U));
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_TOTAL, std::accumulate(minute_report.connection_bytes_sent.begin(), minute_report.connection_bytes_sent.end(), 0U));
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_TOTAL, std::accumulate(second_report.connection_bytes_received.begin(), second_report.connection_bytes_received.end(), 0U));
first_bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_TOTAL, std::accumulate(minute_report.connection_bytes_received.begin(), minute_report.connection_bytes_received.end(), 0U));
this->sendCommand(notify);
first_bulk.put_unchecked(property::CONNECTION_CONNECTED_TIME, this->server->properties()[property::VIRTUALSERVER_UPTIME].as<string>());
first_bulk.put_unchecked(property::CONNECTION_PACKETLOSS_TOTAL, network_report.average_loss);
first_bulk.put_unchecked(property::CONNECTION_PING, network_report.average_ping);
this->sendCommand(result);
return command_result{error::ok};
}
+2 -1
View File
@@ -99,7 +99,7 @@ namespace ts::server {
bool notifyServerUpdated(std::shared_ptr<ConnectedClient> ptr) override;
bool notifyClientPoke(std::shared_ptr<ConnectedClient> invoker, std::string msg) override;
bool notifyClientUpdated(const std::shared_ptr<ConnectedClient> &ptr, const std::deque<std::shared_ptr<property::PropertyDescription>> &deque, bool lock_channel_tree) override;
bool notifyClientUpdated(const std::shared_ptr<ConnectedClient> &ptr, const std::deque<const property::PropertyDescription*> &deque, bool lock_channel_tree) override;
bool notifyPluginCmd(std::string name, std::string msg,std::shared_ptr<ConnectedClient>) override;
bool notifyClientChatComposing(const std::shared_ptr<ConnectedClient> &ptr) override;
@@ -171,6 +171,7 @@ namespace ts::server {
command_result handleCommandServerIdGetByPort(Command&);
command_result handleCommandServerSnapshotDeploy(Command&);
command_result handleCommandServerSnapshotDeployNew(const command_parser&);
command_result handleCommandServerSnapshotCreate(Command&);
command_result handleCommandServerProcessStop(Command&);
+80 -47
View File
@@ -10,6 +10,7 @@
#include <misc/base64.h>
#include <src/ShutdownHelper.h>
#include <ThreadPool/Timer.h>
#include <numeric>
#include "src/client/command_handler/helpers.h"
@@ -99,9 +100,14 @@ command_result QueryClient::handleCommand(Command& cmd) {
return this->handleCommandHostInfo(cmd);
case string_hash("bindinglist"):
return this->handleCommandBindingList(cmd);
case string_hash("serversnapshotdeploy"):
return this->handleCommandServerSnapshotDeploy(cmd);
case string_hash("serversnapshotdeploy"): {
//return this->handleCommandServerSnapshotDeploy(cmd);
auto cmd_str = cmd.build();
ts::command_parser parser{cmd_str};
if(!parser.parse(true)) return command_result{error::vs_critical};
return this->handleCommandServerSnapshotDeployNew(parser);
}
case string_hash("serversnapshotcreate"):
return this->handleCommandServerSnapshotCreate(cmd);
case string_hash("serverprocessstop"):
@@ -397,37 +403,36 @@ command_result QueryClient::handleCommandServerInfo(Command &) {
if(this->server && permission::v2::permission_granted(1, this->calculate_permission(permission::b_virtualserver_connectioninfo_view, 0))) {
auto stats = this->server->getServerStatistics()->statistics();
auto report = this->server->serverStatistics->dataReport();
cmd["connection_bandwidth_sent_last_second_total"] = report.send_second;
cmd["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
cmd["connection_bandwidth_received_last_second_total"] = report.recv_second;
cmd["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
auto total_stats = this->server->getServerStatistics()->total_stats();
auto report_second = this->server->serverStatistics->second_stats();
auto report_minute = this->server->serverStatistics->minute_stats();
cmd["connection_bandwidth_sent_last_second_total"] = std::accumulate(report_second.connection_bytes_sent.begin(), report_second.connection_bytes_sent.end(), 0U);
cmd["connection_bandwidth_sent_last_minute_total"] = std::accumulate(report_minute.connection_bytes_sent.begin(), report_minute.connection_bytes_sent.end(), 0U);
cmd["connection_bandwidth_received_last_second_total"] = std::accumulate(report_second.connection_bytes_received.begin(), report_second.connection_bytes_received.end(), 0U);
cmd["connection_bandwidth_received_last_minute_total"] = std::accumulate(report_minute.connection_bytes_received.begin(), report_minute.connection_bytes_received.end(), 0U);
cmd["connection_filetransfer_bandwidth_sent"] = report.file_send;
cmd["connection_filetransfer_bandwidth_received"] = report.file_recv;
cmd["connection_filetransfer_bytes_sent_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
cmd["connection_filetransfer_bytes_received_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
cmd["connection_filetransfer_bandwidth_sent"] = report_minute.file_bytes_sent;
cmd["connection_filetransfer_bandwidth_received"] = report_minute.file_bytes_received;
cmd["connection_packets_sent_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT].value();
cmd["connection_bytes_sent_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED].value();
cmd["connection_packets_received_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].value();
cmd["connection_bytes_received_speech"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].value();
cmd["connection_packets_sent_speech"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
cmd["connection_bytes_sent_speech"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
cmd["connection_packets_received_speech"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::VOICE];
cmd["connection_bytes_received_speech"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::VOICE];
cmd["connection_packets_sent_keepalive"] = (*stats)[property::CONNECTION_PACKETS_SENT_KEEPALIVE].value();
cmd["connection_packets_received_keepalive"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE].value();
cmd["connection_bytes_received_keepalive"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_KEEPALIVE].value();
cmd["connection_bytes_sent_keepalive"] = (*stats)[property::CONNECTION_BYTES_SENT_KEEPALIVE].value();
cmd["connection_packets_sent_keepalive"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_packets_received_keepalive"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_bytes_received_keepalive"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_bytes_sent_keepalive"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::KEEP_ALIVE];
cmd["connection_packets_sent_control"] = (*stats)[property::CONNECTION_PACKETS_SENT_CONTROL].value();
cmd["connection_bytes_sent_control"] = (*stats)[property::CONNECTION_BYTES_SENT_CONTROL].value();
cmd["connection_packets_received_control"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_CONTROL].value();
cmd["connection_bytes_received_control"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_CONTROL].value();
cmd["connection_packets_sent_control"] = total_stats.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_bytes_sent_control"] = total_stats.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_packets_received_control"] = total_stats.connection_packets_received[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_bytes_received_control"] = total_stats.connection_bytes_received[stats::ConnectionStatistics::category::COMMAND];
cmd["connection_packets_sent_total"] = (*stats)[property::CONNECTION_PACKETS_SENT_TOTAL].value();
cmd["connection_bytes_sent_total"] = (*stats)[property::CONNECTION_BYTES_SENT_TOTAL].value();
cmd["connection_packets_received_total"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].value();
cmd["connection_bytes_received_total"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_TOTAL].value();
cmd["connection_packets_sent_total"] = std::accumulate(report_second.connection_packets_sent.begin(), report_second.connection_packets_sent.end(), 0U);
cmd["connection_bytes_sent_total"] = std::accumulate(report_second.connection_bytes_sent.begin(), report_second.connection_bytes_sent.end(), 0U);
cmd["connection_packets_received_total"] = std::accumulate(report_second.connection_packets_received.begin(), report_second.connection_packets_received.end(), 0U);
cmd["connection_bytes_received_total"] = std::accumulate(report_second.connection_bytes_received.begin(), report_second.connection_bytes_received.end(), 0U);
} else {
cmd["connection_bandwidth_sent_last_second_total"] = "0";
cmd["connection_bandwidth_sent_last_minute_total"] = "0";
@@ -598,12 +603,12 @@ command_result QueryClient::handleCommandServerCreate(Command& cmd) {
if(key == "virtualserver_port") continue;
if(key == "virtualserver_host") continue;
auto info = property::impl::info<property::VirtualServerProperties>(key);
if(*info == property::VIRTUALSERVER_UNDEFINED) {
const auto& info = property::find<property::VirtualServerProperties>(key);
if(info == property::VIRTUALSERVER_UNDEFINED) {
logError(server->getServerId(), "Tried to change unknown server property " + key);
continue;
}
if(!info->validate_input(cmd[key].as<string>())) {
if(!info.validate_input(cmd[key].as<string>())) {
logError(server->getServerId(), "Tried to change " + key + " to an invalid value: " + cmd[key].as<string>());
continue;
}
@@ -722,9 +727,9 @@ command_result QueryClient::handleCommandInstanceEdit(Command& cmd) {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_serverinstance_modify_settings, 1);
for(const auto &key : cmd[0].keys()){
auto info = property::impl::info<property::InstanceProperties>(key);
const auto* info = &property::find<property::InstanceProperties>(key);
if(key == "serverinstance_serverquery_max_connections_per_ip")
info = property::impl::info(property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS_PER_IP);
info = &property::describe(property::SERVERINSTANCE_QUERY_MAX_CONNECTIONS_PER_IP);
if(*info == property::SERVERINSTANCE_UNDEFINED) {
logError(LOG_QUERY, "Query {} tried to change a non existing instance property: {}", this->getLoggingPeerIp(), key);
@@ -758,22 +763,24 @@ command_result QueryClient::handleCommandHostInfo(Command &) {
res["virtualservers_total_channels_online"] = vsReport.onlineChannels;
auto stats = serverInstance->getStatistics()->statistics();
res["connection_packets_sent_total"] = (*stats)[property::CONNECTION_PACKETS_SENT_TOTAL].as<string>();
res["connection_bytes_sent_total"] = (*stats)[property::CONNECTION_BYTES_SENT_TOTAL].as<string>();
res["connection_packets_received_total"] = (*stats)[property::CONNECTION_PACKETS_RECEIVED_TOTAL].as<string>();
res["connection_bytes_received_total"] = (*stats)[property::CONNECTION_BYTES_RECEIVED_TOTAL].as<string>();
auto total_stats = serverInstance->getStatistics()->total_stats();
res["connection_packets_sent_total"] = std::accumulate(total_stats.connection_packets_sent.begin(), total_stats.connection_packets_sent.end(), 0U);
res["connection_bytes_sent_total"] = std::accumulate(total_stats.connection_bytes_sent.begin(), total_stats.connection_bytes_sent.end(), 0U);
res["connection_packets_received_total"] = std::accumulate(total_stats.connection_packets_received.begin(), total_stats.connection_packets_received.end(), 0U);
res["connection_bytes_received_total"] = std::accumulate(total_stats.connection_bytes_received.begin(), total_stats.connection_bytes_received.end(), 0U);
auto report = serverInstance->getStatistics()->dataReport();
res["connection_bandwidth_sent_last_second_total"] = report.send_second;
res["connection_bandwidth_sent_last_minute_total"] = report.send_minute;
res["connection_bandwidth_received_last_second_total"] = report.recv_second;
res["connection_bandwidth_received_last_minute_total"] = report.recv_minute;
auto report_second = serverInstance->getStatistics()->second_stats();
auto report_minute = serverInstance->getStatistics()->minute_stats();
res["connection_bandwidth_sent_last_second_total"] = std::accumulate(report_second.connection_bytes_sent.begin(), report_second.connection_bytes_sent.end(), 0U);
res["connection_bandwidth_sent_last_minute_total"] = std::accumulate(report_minute.connection_bytes_sent.begin(), report_minute.connection_bytes_sent.end(), 0U);
res["connection_bandwidth_received_last_second_total"] = std::accumulate(report_second.connection_bytes_received.begin(), report_second.connection_bytes_received.end(), 0U);
res["connection_bandwidth_received_last_minute_total"] = std::accumulate(report_minute.connection_bytes_received.begin(), report_minute.connection_bytes_received.end(), 0U);
res["connection_filetransfer_bandwidth_sent"] = report.file_send;
res["connection_filetransfer_bandwidth_received"] = report.file_recv;
res["connection_filetransfer_bytes_sent_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL].as<string>();
res["connection_filetransfer_bytes_received_total"] = (*stats)[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL].as<string>();
res["connection_filetransfer_bandwidth_sent"] = report_minute.file_bytes_sent;
res["connection_filetransfer_bandwidth_received"] = report_minute.file_bytes_received;
res["connection_filetransfer_bytes_sent_total"] = total_stats.file_bytes_sent;
res["connection_filetransfer_bytes_received_total"] = total_stats.file_bytes_received;
this->sendCommand(res);
return command_result{error::ok};
@@ -867,6 +874,32 @@ command_result QueryClient::handleCommandServerSnapshotDeploy(Command& cmd) {
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerSnapshotDeployNew(const ts::command_parser &command) {
CMD_RESET_IDLE;
if(this->server) {
return command_result{error::not_implemented};
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
//host = this->server->properties()[property::VIRTUALSERVER_HOST].as<string>();
//port = this->server->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>();
} else {
ACTION_REQUIRES_INSTANCE_PERMISSION(permission::b_virtualserver_snapshot_deploy, 1);
}
std::string error{};
unique_lock server_create_lock(serverInstance->getVoiceServerManager()->server_create_lock);
//TODO: Create a server if no exists
server_create_lock.unlock();
//TODO: Stop the server completely
if(!serverInstance->getVoiceServerManager()->deploy_snapshot(error, 111, command)) {
//TODO: Delete server is it was new
return command_result{error::vs_critical, error};
}
return command_result{error::ok};
}
command_result QueryClient::handleCommandServerSnapshotCreate(Command& cmd) {
ACTION_REQUIRES_GLOBAL_PERMISSION(permission::b_virtualserver_snapshot_create, 1);
CMD_RESET_IDLE;
@@ -42,7 +42,7 @@ bool QueryClient::notifyServerUpdated(shared_ptr<ConnectedClient> ptr) {
return ConnectedClient::notifyServerUpdated(ptr);
}
bool QueryClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &ptr, const std::deque<std::shared_ptr<property::PropertyDescription>> &deque, bool lock_channel_tree) {
bool QueryClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &ptr, const std::deque<const property::PropertyDescription*> &deque, bool lock_channel_tree) {
CHK_EVENT(QEVENTGROUP_CLIENT_MISC, QEVENTSPECIFIER_CLIENT_MISC_UPDATE);
return ConnectedClient::notifyClientUpdated(ptr, deque, lock_channel_tree);
}
@@ -0,0 +1,123 @@
//
// Created by WolverinDEV on 06/04/2020.
//
#include "PacketStatistics.h"
using namespace ts::server::client;
void PacketStatistics::received_packet(ts::protocol::PacketType type, uint32_t pid) {
std::lock_guard lock{this->data_mutex};
switch (type) {
case protocol::PacketType::VOICE:
this->calculator_voice.packet_received(pid);
return;
case protocol::PacketType::VOICE_WHISPER:
this->calculator_voice_whisper.packet_received(pid);
return;
case protocol::PacketType::COMMAND:
case protocol::PacketType::COMMAND_LOW:
return;
case protocol::PacketType::ACK:
this->calculator_ack.packet_received(pid);
return;
case protocol::PacketType::ACK_LOW:
this->calculator_ack_low.packet_received(pid);
return;
case protocol::PacketType::PING:
this->calculator_ping.packet_received(pid);
return;
default:
/* some invalid packet lul */
return;
}
}
void PacketStatistics::send_command(ts::protocol::PacketType type, uint32_t pid) {
std::lock_guard lock{this->data_mutex};
if(type == protocol::PacketType::COMMAND)
this->calculator_command.packet_send(pid);
else if(type == protocol::PacketType::COMMAND_LOW)
this->calculator_command_low.packet_send(pid);
}
void PacketStatistics::received_acknowledge(ts::protocol::PacketType type, uint32_t pid) {
std::lock_guard lock{this->data_mutex};
if(type == protocol::PacketType::ACK)
this->calculator_command.ack_received(pid);
else if(type == protocol::PacketType::ACK_LOW)
this->calculator_command_low.ack_received(pid);
}
PacketStatistics::PacketLossReport PacketStatistics::loss_report() const {
PacketStatistics::PacketLossReport result{};
result.received_voice = this->calculator_voice.received_packets() + this->calculator_voice_whisper.received_packets();
result.lost_voice = this->calculator_voice.lost_packets() + this->calculator_voice_whisper.lost_packets();
result.received_keep_alive = this->calculator_ping.received_packets();
result.lost_keep_alive = this->calculator_ping.lost_packets();
result.received_control = this->calculator_command.received_packets() + this->calculator_command_low.received_packets();
result.lost_control = this->calculator_command.lost_packets() + this->calculator_command_low.lost_packets();
//result.lost_control -= this->calculator_ack.lost_packets() + this->calculator_ack_low.lost_packets(); /* subtract the lost acks (command received but ack got lost) */
result.received_control += this->calculator_ack.received_packets() + this->calculator_ack_low.received_packets();
//result.lost_control += this->calculator_ack.lost_packets() + this->calculator_ack_low.lost_packets(); /* this cancels out the line above */
return result;
}
void PacketStatistics::tick() {
auto now = std::chrono::system_clock::now();
if(now + std::chrono::seconds{15} > this->last_short) {
this->last_short = now;
std::lock_guard lock{this->data_mutex};
this->calculator_command.short_stats();
this->calculator_command_low.short_stats();
this->calculator_ack.short_stats();
this->calculator_ack_low.short_stats();
this->calculator_voice.short_stats();
this->calculator_voice_whisper.short_stats();
this->calculator_ping.short_stats();
}
}
void PacketStatistics::reset() {
std::lock_guard lock{this->data_mutex};
this->calculator_command.reset();
this->calculator_command_low.reset();
this->calculator_ack.reset();
this->calculator_ack_low.reset();
this->calculator_voice.reset();
this->calculator_voice_whisper.reset();
this->calculator_ping.reset();
}
void PacketStatistics::reset_offsets() {
std::lock_guard lock{this->data_mutex};
this->calculator_command.reset_offsets();
this->calculator_command_low.reset_offsets();
this->calculator_ack.reset_offsets();
this->calculator_ack_low.reset_offsets();
this->calculator_voice.reset_offsets();
this->calculator_voice_whisper.reset_offsets();
this->calculator_ping.reset_offsets();
}
float PacketStatistics::current_packet_loss() const {
auto report = this->loss_report();
return report.total_loss();
}
@@ -0,0 +1,68 @@
#pragma once
#include <protocol/PacketLossCalculator.h>
#include <protocol/Packet.h>
#include <misc/spin_lock.h>
namespace ts::server::client {
class PacketStatistics {
public:
struct PacketLossReport {
uint32_t lost_voice{0};
uint32_t lost_control{0};
uint32_t lost_keep_alive{0};
uint32_t received_voice{0};
uint32_t received_control{0};
uint32_t received_keep_alive{0};
[[nodiscard]] inline float voice_loss() const {
const auto total_packets = this->received_voice + this->lost_voice;
if(total_packets == 0) return 0;
return this->lost_voice / (float) total_packets;
}
[[nodiscard]] inline float control_loss() const {
const auto total_packets = this->received_control + this->lost_control;
//if(total_packets == 0) return 0; /* not possible so remove this to speed it up */
return this->lost_control / (float) total_packets;
}
[[nodiscard]] inline float keep_alive_loss() const {
const auto total_packets = this->received_keep_alive + this->lost_keep_alive;
if(total_packets == 0) return 0;
return this->lost_keep_alive / (float) total_packets;
}
[[nodiscard]] inline float total_loss() const {
const auto total_lost = this->lost_voice + this->lost_control + this->lost_keep_alive;
const auto total_received = this->received_control + this->received_voice + this->received_keep_alive;
//if(total_received + total_lost == 0) return 0; /* not possible to speed this up */
return total_lost / (float) (total_lost + total_received);
}
};
[[nodiscard]] PacketLossReport loss_report() const;
[[nodiscard]] float current_packet_loss() const;
void send_command(protocol::PacketType /* type */, uint32_t /* packet id */);
void received_acknowledge(protocol::PacketType /* type */, uint32_t /* packet id */);
void received_packet(protocol::PacketType /* type */, uint32_t /* packet id */);
void tick();
void reset();
void reset_offsets();
private:
std::chrono::system_clock::time_point last_short{};
spin_lock data_mutex{};
protocol::UnorderedPacketLossCalculator calculator_voice_whisper{};
protocol::UnorderedPacketLossCalculator calculator_voice{};
protocol::UnorderedPacketLossCalculator calculator_ack_low{};
protocol::UnorderedPacketLossCalculator calculator_ack{};
protocol::UnorderedPacketLossCalculator calculator_ping{};
protocol::CommandPacketLossCalculator calculator_command{};
protocol::CommandPacketLossCalculator calculator_command_low{};
};
}
+34 -33
View File
@@ -1,50 +1,51 @@
#include "PrecomputedPuzzles.h"
#include "../../Configuration.h"
#include "../ConnectedClient.h"
#include "./PrecomputedPuzzles.h"
#include "src/Configuration.h"
#include <tomcrypt.h>
using namespace std;
using namespace ts;
using namespace ts::protocol;
using namespace ts::server::udp;
PuzzleManager::PuzzleManager() {}
PuzzleManager::~PuzzleManager() {}
PuzzleManager::PuzzleManager() = default;
PuzzleManager::~PuzzleManager() = default;
size_t PuzzleManager::precomputedPuzzleCount() { return this->cached.size(); }
bool PuzzleManager::precomputePuzzles(size_t limit) {
while(precomputedPuzzleCount() < limit) generatePuzzle();
return true;
size_t PuzzleManager::precomputed_puzzle_count() {
std::lock_guard lock{this->cache_lock};
return this->cached_puzzles.size();
}
std::shared_ptr<Puzzle> PuzzleManager::nextPuzzle() {
this->indexLock.lock();
size_t index = this->cacheIndex++ % this->cached.size();
this->indexLock.unlock();
return this->cached[index];
bool PuzzleManager::precompute_puzzles(size_t amount) {
std::random_device rd{};
std::mt19937 mt{rd()};
amount = 5;
while(this->precomputed_puzzle_count() < amount)
this->generate_puzzle(mt);
return this->precomputed_puzzle_count() > 0;
}
inline void rndNum(mp_int *result, int byteLength){
uint8_t buffer[byteLength];
std::shared_ptr<Puzzle> PuzzleManager::next_puzzle() {
std::lock_guard lock{this->cache_lock};
return this->cached_puzzles[this->cache_index++ % this->cached_puzzles.size()];
}
for(int index = 0; index < byteLength; index++) {
int rnd = rand();
uint8_t urnd = static_cast<uint8_t>(rnd & 0xFF);
buffer[index] = urnd; //TODO more secure!
inline void random_number(std::mt19937& generator, mp_int *result, int length){
std::uniform_int_distribution<uint8_t> dist{};
}
uint8_t buffer[length];
for(auto& byte : buffer)
byte = dist(generator);
mp_zero(result);
mp_read_unsigned_bin(result, buffer, byteLength);
mp_read_unsigned_bin(result, buffer, length);
}
inline bool solvePuzzle(Puzzle *puzzle){
inline bool solve_puzzle(Puzzle *puzzle) {
mp_int exp{};
mp_init(&exp);
mp_2expt(&exp, puzzle->level);
if (mp_exptmod(&puzzle->x, &exp, &puzzle->n, &puzzle->result) != CRYPT_OK) { //Sometimes it fails (unknow why :D)
if (mp_exptmod(&puzzle->x, &exp, &puzzle->n, &puzzle->result) != CRYPT_OK) { //Sometimes it fails (unknown why :D)
mp_clear(&exp);
return false;
}
@@ -66,17 +67,17 @@ inline bool write_bin_data(mp_int& data, uint8_t* result, size_t length) {
return true;
}
void PuzzleManager::generatePuzzle() {
void PuzzleManager::generate_puzzle(std::mt19937& random_generator) {
auto puzzle = new Puzzle{};
puzzle->level = ts::config::voice::RsaPuzzleLevel;
mp_init_multi(&puzzle->x, &puzzle->n, &puzzle->result, nullptr);
generate_new:
rndNum(&puzzle->x, 64);
rndNum(&puzzle->n, 64);
puzzle->level = ts::config::voice::RsaPuzzleLevel;
random_number(random_generator, &puzzle->x, 64);
random_number(random_generator, &puzzle->n, 64);
if(!solvePuzzle(puzzle))
if(!solve_puzzle(puzzle))
goto generate_new;
auto valid_x = mp_unsigned_bin_size(&puzzle->x) <= 64;
@@ -94,7 +95,7 @@ void PuzzleManager::generatePuzzle() {
if(!write_bin_data(puzzle->result, puzzle->data_result, 64))
goto generate_new;
this->cached.push_back(shared_ptr<Puzzle>(puzzle, [](Puzzle* elm){
this->cached_puzzles.push_back(shared_ptr<Puzzle>(puzzle, [](Puzzle* elm){
mp_clear_multi(&elm->n, &elm->x, &elm->result, nullptr);
delete elm;
}));
+26 -31
View File
@@ -1,44 +1,39 @@
#pragma once
#include <ThreadPool/Mutex.h>
#include <tommath.h>
#include <memory>
#include <deque>
#include <vector>
#include <misc/spin_lock.h>
#include <random>
namespace ts {
namespace server {
class ConnectedClient;
}
namespace ts::server::udp {
struct Puzzle {
mp_int x;
mp_int n;
int level;
namespace protocol {
struct Puzzle {
mp_int x;
mp_int n;
int level;
mp_int result;
mp_int result;
uint8_t data_x[64];
uint8_t data_n[64];
uint8_t data_result[64];
};
uint8_t data_x[64];
uint8_t data_n[64];
uint8_t data_result[64];
};
class PuzzleManager {
public:
PuzzleManager();
~PuzzleManager();
class PuzzleManager {
public:
PuzzleManager();
~PuzzleManager();
bool precomputePuzzles(size_t limit);
[[nodiscard]] bool precompute_puzzles(size_t amount);
size_t precomputedPuzzleCount();
[[nodiscard]] size_t precomputed_puzzle_count();
std::shared_ptr<Puzzle> nextPuzzle();
private:
void generatePuzzle();
[[nodiscard]] std::shared_ptr<Puzzle> next_puzzle();
private:
void generate_puzzle(std::mt19937&);
threads::Mutex indexLock;
size_t cacheIndex = 0;
std::deque<std::shared_ptr<Puzzle>> cached;
};
}
size_t cache_index{0};
spin_lock cache_lock{};
std::vector<std::shared_ptr<Puzzle>> cached_puzzles{};
};
}
+10
View File
@@ -108,6 +108,8 @@ void VoiceClient::tick(const std::chrono::system_clock::time_point &time) {
} else
this->sendPingRequest();
}
this->connection->packet_statistics().tick();
} else if(this->state == ConnectionState::INIT_LOW || this->state == ConnectionState::INIT_HIGH) {
if(this->last_packet_handshake.time_since_epoch().count() != 0) {
if(time - this->last_packet_handshake > seconds(5)) {
@@ -309,4 +311,12 @@ void VoiceClient::send_voice_whisper_packet(const pipes::buffer_view &voice_buff
memcpy(packet->data().data_ptr<void>(), voice_buffer.data_ptr<void>(), voice_buffer.length());
this->connection->sendPacket(packet, false, false);
}
float VoiceClient::current_ping_deviation() {
return this->connection->getAcknowledgeManager().current_rttvar();
}
float VoiceClient::current_packet_loss() const {
return this->connection->packet_statistics().current_packet_loss();
}
+6 -1
View File
@@ -63,7 +63,11 @@ namespace ts {
connection::VoiceClientConnection* getConnection(){ return connection; }
std::shared_ptr<VoiceServer> getVoiceServer(){ return voice_server; }
std::chrono::milliseconds calculatePing(){ return ping; }
[[nodiscard]] inline std::chrono::milliseconds current_ping(){ return ping; }
[[nodiscard]] float current_ping_deviation();
[[nodiscard]] float current_packet_loss() const;
private:
connection::VoiceClientConnection* connection;
@@ -120,6 +124,7 @@ namespace ts {
bool client_init = false;
bool new_protocol = false;
bool protocol_encrypted = false;
bool is_teaspeak_client = false;
uint32_t client_time = 0;
std::string alpha;
@@ -78,7 +78,6 @@ void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& b
}
#endif
#endif
ClientPacketParser packet_parser{buffer};
if(!packet_parser.valid()) {
logTrace(this->client->getServerId(), "{} Received invalid packet. Dropping.", CLIENT_STR_LOG_PREFIX_(this->client));
@@ -87,6 +86,15 @@ void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& b
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
packet_parser.set_estimated_generation(this->incoming_generation_estimators[packet_parser.type()].visit_packet(packet_parser.packet_id()));
#ifndef CONNECTION_NO_STATISTICS
if(this->client) {
auto stats = this->client->connectionStatistics;
stats->logIncomingPacket(stats::ConnectionStatistics::category::from_type(packet_parser.type()), buffer.length() + 96); /* 96 for the UDP packet overhead */
}
this->packet_statistics().received_packet((protocol::PacketType) packet_parser.type(), packet_parser.full_packet_id());
#endif
auto is_command = packet_parser.type() == protocol::COMMAND || packet_parser.type() == protocol::COMMAND_LOW;
/* pretest if the packet is worth the effort of decoding it */
if(is_command) {
@@ -168,11 +176,6 @@ void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& b
return;
}
#ifndef CONNECTION_NO_STATISTICS
if(this->client && this->client->getServer())
this->client->connectionStatistics->logIncomingPacket(stats::ConnectionStatistics::category::from_type(packet_parser.type()), buffer.length());
#endif
#ifdef LOG_INCOMPING_PACKET_FRAGMENTS
debugMessage(lstream << CLIENT_LOG_PREFIX << "Recived packet. PacketId: " << packet->packetId() << " PacketType: " << packet->type().name() << " Flags: " << packet->flags() << " - " << packet->data() << endl);
#endif
@@ -252,8 +255,10 @@ void VoiceClientConnection::execute_handle_command_packets(const std::chrono::sy
buffer_execute_lock.unlock();
auto voice_server = this->client->voice_server;
if(voice_server && reexecute_handle)
if(voice_server && (reexecute_handle || this->should_reassembled_reschedule)) {
should_reassembled_reschedule = false;
this->client->voice_server->schedule_command_handling(this->client);
}
}
/* buffer_execute_lock: lock for in order execution */
@@ -267,13 +272,19 @@ bool VoiceClientConnection::next_reassembled_command(unique_lock<std::recursive_
/* handle commands before command low packets */
for(auto& buf : this->_command_fragment_buffers) {
unique_lock ring_lock(buf.buffer_lock, try_to_lock);
if(!ring_lock.owns_lock()) continue;
unique_lock ring_lock(buf.buffer_lock, try_to_lock); //Perm lock the buffer else, may command wount get handeled. Because we've more left, but say we waven't
if(!ring_lock.owns_lock()) {
this->should_reassembled_reschedule = true;
continue;
}
if(buf.front_set()) {
if(!buffer) { /* lets still test for reexecute */
buffer_execute_lock = unique_lock(buf.execute_lock, try_to_lock);
if(!buffer_execute_lock.owns_lock()) continue;
if(!buffer_execute_lock.owns_lock()) {
this->should_reassembled_reschedule = true;
continue;
}
buffer_lock = move(ring_lock);
buffer = &buf;
@@ -477,7 +488,11 @@ bool VoiceClientConnection::prepare_packet_for_write(vector<pipes::buffer> &resu
for(const auto& fragment : fragments) {
if(!fragment->memory_state.id_branded)
fragment->applyPacketId(this->packet_id_manager);
if(fragment->type().type() == protocol::PacketType::COMMAND_LOW || fragment->type().type() == protocol::PacketType::COMMAND)
this->packet_statistics().send_command(fragment->type().type(), fragment->packetId() | fragment->generationId() << 16U);
}
work_lock.unlock(); /* the rest could be unordered */
@@ -509,8 +524,10 @@ bool VoiceClientConnection::prepare_packet_for_write(vector<pipes::buffer> &resu
}
#ifndef CONNECTION_NO_STATISTICS
if(statistics)
statistics->logOutgoingPacket(*fragment);
if(statistics) {
auto category = stats::ConnectionStatistics::category::from_type(fragment->type());
statistics->logOutgoingPacket(category, fragment->length() + 96); /* 96 for the UDP packet overhead */
}
#endif
this->acknowledge_handler.process_packet(*fragment);
result.push_back(fragment->buffer());
@@ -576,7 +593,7 @@ int VoiceClientConnection::pop_write_buffer(pipes::buffer& target) {
if(this->client->state == DISCONNECTED)
return 2;
lock_guard write_queue_lock(this->write_queue_lock);
lock_guard wqlock{this->write_queue_lock};
size_t size = this->write_queue.size();
if(size == 0)
return 2;
@@ -15,6 +15,7 @@
#include "VoiceClient.h"
#include "protocol/AcknowledgeManager.h"
#include <protocol/generation.h>
#include "./PacketStatistics.h"
//#define LOG_ACK_SYSTEM
#ifdef LOG_ACK_SYSTEM
@@ -82,10 +83,14 @@ namespace ts {
bool wait_empty_write_and_prepare_queue(std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
protocol::PacketIdManager& getPacketIdManager() { return this->packet_id_manager; }
AcknowledgeManager& getAcknowledgeManager() { return this->acknowledge_handler; }
inline auto& get_incoming_generation_estimators() { return this->incoming_generation_estimators; }
void reset();
void force_insert_command(const pipes::buffer_view& /* payload */);
void register_initiv_packet();
[[nodiscard]] inline auto& packet_statistics() { return this->packet_statistics_; }
//buffer::SortedBufferQueue<protocol::ClientPacket>** getReadQueue() { return this->readTypedQueue; }
protected:
void handle_incoming_datagram(const pipes::buffer_view &buffer);
@@ -100,6 +105,8 @@ namespace ts {
CompressionHandler compress_handler;
AcknowledgeManager acknowledge_handler;
std::atomic_bool should_reassembled_reschedule; /* this get checked as soon the command handle lock has been released so trylock will succeed */
//Handle stuff
void execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */);
bool next_reassembled_command(std::unique_lock<std::recursive_timed_mutex> &buffer_execute_lock /* packet channel execute lock */, pipes::buffer & /* buffer*/, uint16_t& /* packet id */);
@@ -172,6 +179,7 @@ namespace ts {
return packet_index & 0x1U; /* use 0 for command and 1 for command low */
}
server::client::PacketStatistics packet_statistics_{};
};
}
}
@@ -70,6 +70,7 @@ ts::command_result VoiceClient::handleCommandClientInitIv(Command& command) {
this->connection->reset();
this->connection->register_initiv_packet();
this->connection->packet_statistics().reset_offsets();
this->crypto.protocol_encrypted = false;
bool use_teaspeak = command.hasParm("teaspeak");
@@ -81,7 +81,12 @@ void VoiceClient::handlePacketVoice(const protocol::ClientPacketParser& packet)
}
void VoiceClient::handlePacketAck(const protocol::ClientPacketParser& packet) {
if(packet.payload_length() < 2) return;
uint16_t target_id{be2le16(packet.payload().data_ptr<char>())};
this->connection->packet_statistics().received_acknowledge((protocol::PacketType) packet.type(), target_id | (packet.estimated_generation() << 16U));
string error{};
if(!this->connection->acknowledge_handler.process_acknowledge(packet.type(), packet.payload(), error))
if(!this->connection->acknowledge_handler.process_acknowledge(packet.type(), target_id, error))
debugMessage(this->getServerId(), "{} Failed to handle acknowledge: {}", CLIENT_STR_LOG_PREFIX, error);
}
+79 -72
View File
@@ -1,8 +1,7 @@
#include <misc/std_unique_ptr.h>
#include <log/LogUtils.h>
#include <pipes/rtc/PeerConnection.h>
#include <pipes/rtc/AudioStream.h>
#include <misc/endianness.h>
#include <dlfcn.h>
#include "WebClient.h"
#include "VoiceBridge.h"
@@ -11,7 +10,7 @@ using namespace ts;
using namespace ts::server;
using namespace ts::web;
void log(pipes::Logger::LogLevel level, const std::string& name, const std::string& message, ...) {
void VoiceBridge::callback_log(void* ptr, pipes::Logger::LogLevel level, const std::string& name, const std::string& message, ...) {
auto max_length = 1024 * 8;
char buffer[max_length];
@@ -20,7 +19,49 @@ void log(pipes::Logger::LogLevel level, const std::string& name, const std::stri
max_length = vsnprintf(buffer, max_length, message.c_str(), args);
va_end(args);
debugMessage(LOG_GENERAL, "[WebRTC][{}][{}] {}", level, name, string(buffer));
auto bridge = (VoiceBridge*) ptr;
debugMessage(LOG_GENERAL, "{}[WebRTC][{}][{}] {}", CLIENT_STR_LOG_PREFIX_(bridge->owner()), level, name, string(buffer));
}
namespace gioloop {
void* main_loop_;
void*(*g_main_loop_new)(void* /* context */, bool /* is true */);
void(*g_main_loop_run)(void* /* loop */);
void(*g_main_loop_unref)(void* /* loop */);
void*(*g_main_loop_ref)(void* /* loop */);
bool initialized{false};
void initialize() {
if(initialized) return;
initialized = true;
g_main_loop_new = (decltype(g_main_loop_new)) dlsym(nullptr, "g_main_loop_new");
g_main_loop_run = (decltype(g_main_loop_run)) dlsym(nullptr, "g_main_loop_run");
g_main_loop_ref = (decltype(g_main_loop_ref)) dlsym(nullptr, "g_main_loop_ref");
g_main_loop_unref = (decltype(g_main_loop_unref)) dlsym(nullptr, "g_main_loop_unref");
if(!g_main_loop_run || !g_main_loop_new || !g_main_loop_ref || !g_main_loop_unref) {
logWarning(LOG_INSTANCE, "Missing g_main_loop_new, g_main_loop_run, g_main_loop_ref or g_main_loop_unref functions. Could not spawn main loop.");
g_main_loop_run = nullptr;
g_main_loop_new = nullptr;
return;
}
main_loop_ = g_main_loop_new(nullptr, false);
if(!main_loop_) {
logError(LOG_INSTANCE, "Failed to spawn new event loop for the web client.");
return;
}
std::thread([]{
g_main_loop_run(main_loop_);
}).detach();
}
std::shared_ptr<GMainLoop> loop() {
return std::shared_ptr<GMainLoop>{(GMainLoop*) g_main_loop_ref(main_loop_), g_main_loop_unref};
}
}
VoiceBridge::VoiceBridge(const shared_ptr<WebClient>& owner) : _owner(owner) {
@@ -48,14 +89,16 @@ VoiceBridge::VoiceBridge(const shared_ptr<WebClient>& owner) : _owner(owner) {
config->nice_config->allow_ice_tcp = false;
config->nice_config->use_upnp = config::web::enable_upnp;
//FIXME Use the internal thread or a shared worker
/* Not creating the thread here because DataPipes has a better impl with a join
gioloop::initialize();
config->nice_config->main_loop = gioloop::loop();
/*
config->nice_config->main_loop = std::shared_ptr<GMainLoop>(g_main_loop_new(nullptr, false), g_main_loop_unref);
std::thread(g_main_loop_run, config->nice_config->main_loop.get()).detach();
*/
config->logger = make_shared<pipes::Logger>();
config->logger->callback_log = log;
config->logger->callback_log = VoiceBridge::callback_log;
config->logger->callback_argument = this;
//config->sctp.local_port = 5202; //Fire Fox don't support a different port :D
this->connection = make_unique<rtc::PeerConnection>(config);
@@ -77,19 +120,17 @@ std::shared_ptr<server::WebClient> VoiceBridge::owner() {
bool VoiceBridge::initialize(std::string &error) {
if(!this->connection->initialize(error)) return false;
this->connection->callback_ice_candidate = [&](const rtc::IceCandidate& candidate, bool last_candidate) {
auto function_callback = this->callback_ice_candidate;
if(function_callback)
function_callback(candidate);
if(last_candidate) {
auto function_callback_end = this->callback_ice_candidate_finished;
this->negotiation_possible = true;
if(function_callback_end)
function_callback_end();
this->connection->callback_ice_candidate = [&](const rtc::IceCandidate& candidate) {
if(!candidate.is_finished_candidate()) {
if(auto callback{this->callback_ice_candidate}; callback)
callback(candidate);
} else {
if(auto callback{this->callback_ice_candidate_finished}; callback)
callback();
}
};
this->connection->callback_new_stream = [&](const std::shared_ptr<rtc::Stream> &channel) { this->handle_media_stream(channel); }; //bind(&VoiceBridge::handle_media_stream, this, placeholders::_1); => crash
this->connection->callback_new_stream = [&](const std::shared_ptr<rtc::Channel> &channel) { this->handle_media_stream(channel); }; //bind(&VoiceBridge::handle_media_stream, this, placeholders::_1); => crash
this->connection->callback_setup_fail = [&](rtc::PeerConnection::ConnectionComponent comp, const std::string& reason) {
debugMessage(this->server_id(), "{} WebRTC setup failed! Component {} ({})", CLIENT_STR_LOG_PREFIX_(this->owner()), comp, reason);
if(this->callback_failed)
@@ -109,45 +150,45 @@ int VoiceBridge::apply_ice(const std::deque<std::shared_ptr<rtc::IceCandidate>>&
}
void VoiceBridge::remote_ice_finished() {
if(negotiation_possible) {
this->connection->execute_negotiation();
negotiation_required = false;
} else {
negotiation_required = true;
}
this->connection->remote_candidates_finished();
}
std::string VoiceBridge::generate_answer() {
return this->connection->generate_answer(true);
return this->connection->generate_answer(false);
}
void VoiceBridge::execute_tick() {
if(!this->_voice_channel) {
if(this->offer_timestamp.time_since_epoch().count() > 0 && this->offer_timestamp + chrono::seconds(10) < chrono::system_clock::now()) {
if(this->offer_timestamp.time_since_epoch().count() > 0 && this->offer_timestamp + chrono::seconds{20} < chrono::system_clock::now()) {
this->offer_timestamp = chrono::system_clock::time_point();
this->connection->callback_setup_fail(rtc::PeerConnection::ConnectionComponent::BASE, "setup timeout");
}
}
if(this->negotiation_required && this->negotiation_possible) {
this->connection->execute_negotiation();
negotiation_required = false;
}
}
void VoiceBridge::handle_media_stream(const std::shared_ptr<rtc::Stream> &undefined_stream) {
void VoiceBridge::handle_media_stream(const std::shared_ptr<rtc::Channel> &undefined_stream) {
if(undefined_stream->type() == rtc::CHANTYPE_APPLICATION) {
auto stream = dynamic_pointer_cast<rtc::ApplicationStream>(undefined_stream);
auto stream = dynamic_pointer_cast<rtc::ApplicationChannel>(undefined_stream);
if(!stream) return;
stream->callback_datachannel_new = [&](const std::shared_ptr<rtc::DataChannel> &channel) { this->handle_data_channel(channel); }; //bind(&VoiceBridge::handle_data_channel, this, placeholders::_1); => may crash?
} else if(undefined_stream->type() == rtc::CHANTYPE_AUDIO) {
auto stream = dynamic_pointer_cast<rtc::AudioStream>(undefined_stream);
auto stream = dynamic_pointer_cast<rtc::AudioChannel>(undefined_stream);
if(!stream) return;
this->_audio_channel = stream;
for(const auto& ex : stream->list_extensions()) {
debugMessage(0, "{} | {}", ex->name, ex->id);
}
stream->register_local_extension("urn:ietf:params:rtp-hdrext:ssrc-audio-level");
//bind(&VoiceBridge::handle_audio_data, this, placeholders::_1, placeholders::_2, placeholders::_3); => may crash?
stream->incoming_data_handler = [&](const std::shared_ptr<rtc::AudioChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) { this->handle_audio_data(channel, data, payload_offset); };
for(const auto& codec : stream->list_codecs()) {
if(codec->type == rtc::codec::Codec::OPUS) {
codec->accepted = true;
break;
}
}
stream->incoming_data_handler = [&](const std::shared_ptr<rtc::MediaChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) { this->handle_audio_data(channel, data, payload_offset); };
} else {
logError(this->server_id(), "Got offer for unknown channel of type {}", undefined_stream->type());
}
@@ -174,42 +215,8 @@ void VoiceBridge::handle_data_channel(const std::shared_ptr<rtc::DataChannel> &c
};
}
/*
ssize_t sample_count = 960;
ssize_t sample_length = sample_count * 2;
opus_int16 samples[sample_length];
if(!this->decoder) {
int error;
this->decoder = opus_decoder_create(48000, 2, &error);
assert(error == 0 && this->decoder);
}
if(!this->encoder) {
int error;
this->encoder = opus_encoder_create(48000, 2, OPUS_APPLICATION_AUDIO, &error);
assert(error == 0 && this->encoder);
}
{
sample_count = opus_decode(this->decoder, (u_char*) &data.data()[payload_offset], data.length() - payload_offset, samples, sample_count, 0);
if(sample_count < 0) {
logError(this->server_id(), "Could not decode opus!");
return;
}
}
string target_buffer(512, '\0'); //Way too big!
le2be16(this->voice.packet_id++, (char*) target_buffer.data());
target_buffer[2] = 5;
auto encoded = opus_encode(this->encoder, samples, sample_count, (u_char*) &target_buffer[3], 512 - 3);
if(encoded < 0 ){
logError(this->server_id(), "Could not encode opus!");
return;
}
*/
void VoiceBridge::handle_audio_data(const std::shared_ptr<rtc::AudioChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) {
if(channel->codec->type != rtc::codec::TypedAudio::OPUS) {
void VoiceBridge::handle_audio_data(const std::shared_ptr<rtc::MediaChannel> &channel, const pipes::buffer_view &data, size_t payload_offset) {
if(channel->codec->type != rtc::codec::Codec::OPUS) {
debugMessage(this->server_id(), "{} Got unknown codec ({})!", CLIENT_STR_LOG_PREFIX_(this->owner()), channel->codec->type);
return;
}
@@ -217,7 +224,7 @@ void VoiceBridge::handle_audio_data(const std::shared_ptr<rtc::AudioChannel> &ch
auto ac = _audio_channel.lock();
if(!ac) return;
for(const auto& ext : ac->list_extensions(0x02)) {
for(const auto& ext : ac->list_extensions(rtc::direction::incoming)) {
if(ext->name == "urn:ietf:params:rtp-hdrext:ssrc-audio-level") {
int level;
if(rtc::protocol::rtp_header_extension_parse_audio_level(data, ext->id, &level) == 0) {
+6 -8
View File
@@ -1,8 +1,8 @@
#pragma once
#include <pipes/rtc/PeerConnection.h>
#include <pipes/rtc/ApplicationStream.h>
#include <pipes/rtc/AudioStream.h>
#include <pipes/rtc/channels/ApplicationChannel.h>
#include <pipes/rtc/channels/AudioChannel.h>
namespace ts {
namespace server {
@@ -36,26 +36,24 @@ namespace ts {
void execute_tick();
private:
static void callback_log(void* ptr, pipes::Logger::LogLevel level, const std::string& name, const std::string& message, ...);
inline int server_id();
inline std::shared_ptr<server::WebClient> owner();
void handle_media_stream(const std::shared_ptr<rtc::Stream>& /* stream */);
void handle_media_stream(const std::shared_ptr<rtc::Channel>& /* stream */);
void handle_data_channel(const std::shared_ptr<rtc::DataChannel> & /* channel */);
void handle_audio_data(const std::shared_ptr<rtc::AudioChannel>& /* channel */, const pipes::buffer_view& /* buffer */, size_t /* payload offset */);
void handle_audio_data(const std::shared_ptr<rtc::MediaChannel>& /* channel */, const pipes::buffer_view& /* buffer */, size_t /* payload offset */);
std::weak_ptr<server::WebClient> _owner;
std::chrono::system_clock::time_point offer_timestamp;
std::unique_ptr<rtc::PeerConnection> connection;
std::shared_ptr<rtc::DataChannel> _voice_channel;
std::weak_ptr<rtc::AudioStream> _audio_channel;
std::weak_ptr<rtc::AudioChannel> _audio_channel;
struct {
uint16_t packet_id = 0;
bool muted = true;
} voice;
bool negotiation_possible = false;
bool negotiation_required = false;
};
}
}
+12 -6
View File
@@ -457,8 +457,11 @@ void WebClient::handleMessage(const std::string &message) {
unique_lock voice_bridge_lock(this->voice_bridge_lock);
if(this->voice_bridge) {
logError(this->server->getServerId(), "[{}] Tried to register a WebRTC channel twice!", CLIENT_STR_LOG_PREFIX_(this));
//return;
this->voice_bridge = nullptr;
std::thread([&, vb_ptr = std::move(this->voice_bridge), lock = this->ref()]() mutable {
vb_ptr = nullptr;
lock = nullptr;
}).detach();
}
//TODO test if bridge already exists!
this->voice_bridge = make_unique<web::VoiceBridge>(dynamic_pointer_cast<WebClient>(_this.lock())); //FIXME Add config
@@ -476,8 +479,11 @@ void WebClient::handleMessage(const std::string &message) {
auto vb_ptr = &*this->voice_bridge; /* read only no lock needed */
std::thread([&, vb_ptr, lock = this->ref()]{
unique_lock vbl{this->voice_bridge_lock};
if(&*this->voice_bridge == vb_ptr)
this->voice_bridge.release();
if(&*this->voice_bridge == vb_ptr) {
auto bridge = std::exchange(this->voice_bridge, nullptr);
vbl.unlock();
bridge.reset();
}
}).detach();
Json::Value response;
@@ -551,7 +557,7 @@ void WebClient::handleMessage(const std::string &message) {
this->sendJson(response);
}
} else if(subType == "ice") {
shared_lock read_voice_bridge_lock(this->voice_bridge_lock);
std::shared_lock read_voice_bridge_lock{this->voice_bridge_lock};
if(!this->voice_bridge) {
debugMessage(this->getServerId(), "[{}] Received remote ICE candidate without having a voice bridge! Dropping candidate.", CLIENT_STR_LOG_PREFIX);
return;
@@ -578,7 +584,7 @@ void WebClient::handleMessage(const std::string &message) {
}
}
} else if(subType == "ice_finish") {
shared_lock read_voice_bridge_lock(this->voice_bridge_lock);
std::shared_lock read_voice_bridge_lock{this->voice_bridge_lock};
if(!this->voice_bridge) {
debugMessage(this->getServerId(), "[{}] Received remote ICE candidate without having a voice bridge! Dropping candidate.", CLIENT_STR_LOG_PREFIX);
return;
+9 -5
View File
@@ -79,7 +79,7 @@ void LicenseService::abort_request(std::lock_guard<std::recursive_timed_mutex> &
this->current_client->close_connection();
}
this->current_client.release();
this->current_client.reset();
}
}
@@ -172,6 +172,9 @@ void LicenseService::handle_check_succeeded() {
} else {
logMessage(LOG_INSTANCE, strobf("Instance integrity has been validated.").string());
}
if(!config::server::check_server_version_with_license())
handle_check_fail(strobf("memory invalid").string());
}
{
@@ -191,9 +194,10 @@ void LicenseService::handle_check_fail(const std::string &error) {
this->timings.last_succeeded.time_since_epoch().count() == 0 ? this->timings.failed_count < 32 : /* About 12hours */
this->timings.failed_count < 82 /* about 36 hours */
);
if(config::license->isPremium() && !soft_license_check) {
const auto invalid_memory = !config::server::check_server_version_with_license();
if(invalid_memory || (config::license->isPremium() && !soft_license_check)) {
logCritical(LOG_INSTANCE, strobf("Failed to validate license:").string());
logCritical(LOG_INSTANCE, error);
logCritical(LOG_INSTANCE, invalid_memory ? strobf("invalid memory").string() : error);
logCritical(LOG_INSTANCE, strobf("Stopping server!").string());
ts::server::shutdownInstance();
} else {
@@ -225,7 +229,7 @@ void LicenseService::handle_dns_lookup_result(bool success, const std::variant<s
this->request_state_ = request_state::connecting;
assert(!this->current_client);
this->current_client = std::make_unique<::license::client::LicenseServerClient>(std::get<sockaddr_in>(result), 3);
this->current_client = std::make_shared<::license::client::LicenseServerClient>(std::get<sockaddr_in>(result), 3);
this->current_client->callback_connected = [&]{ this->handle_client_connected(); };
this->current_client->callback_disconnected = [&](bool expected, const std::string& error) {
this->handle_client_disconnected(error);
@@ -305,11 +309,11 @@ void LicenseService::send_license_validate_request() {
request.set_licensed(false);
request.set_license_info(false);
}
request.set_memory_valid(config::server::check_server_version_with_license());
request.mutable_info()->set_uname(this->license_request_data->info.uname);
request.mutable_info()->set_version(this->license_request_data->info.version);
request.mutable_info()->set_timestamp(this->license_request_data->info.timestamp.count());
request.mutable_info()->set_unique_id(this->license_request_data->info.unique_id);
this->client_send_message(::license::protocol::PACKET_CLIENT_SERVER_VALIDATION, request);
}
+1 -1
View File
@@ -75,7 +75,7 @@ namespace ts::server::license {
std::recursive_timed_mutex request_lock{};
request_state request_state_{request_state::empty};
std::unique_ptr<::license::client::LicenseServerClient> current_client{nullptr};
std::shared_ptr<::license::client::LicenseServerClient> current_client{nullptr};
std::shared_ptr<InstanceLicenseInfo> license_request_data{nullptr};
std::condition_variable sync_request_cv;
+1 -1
View File
@@ -222,7 +222,7 @@ namespace ts {
class ConversationManager {
public:
ConversationManager(const std::shared_ptr<VirtualServer>& /* server */);
explicit ConversationManager(const std::shared_ptr<VirtualServer>& /* server */);
virtual ~ConversationManager();
void initialize(const std::shared_ptr<ConversationManager>& _this);
+82 -5
View File
@@ -44,8 +44,8 @@ if(!result && result.msg().find(ignore) == string::npos){
#define RESIZE_COLUMN(tblName, rowName, size) up vote EXECUTE("Could not change column size", "ALTER TABLE " tblName " ALTER COLUMN " rowName " varchar(" size ")");
#define CURRENT_DATABASE_VERSION 11
#define CURRENT_PERMISSION_VERSION 2
#define CURRENT_DATABASE_VERSION 12
#define CURRENT_PERMISSION_VERSION 3
#define CLIENT_UID_LENGTH "64"
#define CLIENT_NAME_LENGTH "128"
@@ -144,7 +144,7 @@ bool SqlDataManager::initialize(std::string& error) {
//Advanced locked test
{
bool property_exists = false;
sql::command(this->sql(), "SELECT * FORM `general` WHERE `key` = :key", variable{":key", "lock_test"}).query([](bool& flag, int, string*, string*) { flag = true; }, property_exists);
sql::command(this->sql(), "SELECT * FORM `general` WHERE `key` = :key", variable{":key", "lock_test"}).query([&](int, string*, string*) { property_exists = true; });
sql::result res;
if(!property_exists) {
res = sql::command(this->sql(), "INSERT INTO `general` (`key`, `value`) VALUES (:key, :value);", variable{":key", "lock_test"}, variable{":value", "UPDATE ME!"}).execute();
@@ -411,6 +411,65 @@ ROLLBACK;
CREATE_INDEX("conversations", "server_id");
CREATE_INDEX2R("conversation_blocks", "server_id", "conversation_id");
db_version(11);
case 11:
/* update the group table */
{
result = sql::command(this->sql(), "CREATE TABLE `groups_v2` (`serverId` INT NOT NULL, `groupId` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, `target` INT, `type` INT, `displayName` VARCHAR(128));").execute();
if(!result) {
error = "failed to create new groups table (" + result.fmtStr() + ")";
return false;
}
result = sql::command(this->sql(), "INSERT INTO `groups_v2`(`serverId`, `groupId`, `target`, `type`, `displayName`) SELECT `serverId`, `groupId`, `target`, `type`, `displayName` FROM `groups`;").execute();
if(!result) {
sql::command(this->sql(), "DROP TABLE `groups_v2`;").execute();
error = "failed to insert data into the new groups table (" + result.fmtStr() + ")";
return false;
}
result = sql::command(this->sql(), "DROP TABLE `groups`;").execute();
if(!result) {
error = "failed to delete old groups table (" + result.fmtStr() + ")";
return false;
}
result = sql::command(this->sql(), "ALTER TABLE `groups_v2` RENAME TO groups;").execute();
if(!result) {
error = "failed to rename new groups table to the old groups table (" + result.fmtStr() + ")";
return false;
}
CREATE_INDEX2R("groups", "serverId", "groupId");
CREATE_INDEX("groups", "serverId");
result = sql::command(this->sql(), "ALTER TABLE `groups` ADD COLUMN `original_id` INTEGER DEFAULT 0;").execute();
if(!result) {
error = "Failed to alter groups table";
return false;
}
}
/* update the client table */
{
result = sql::command(this->sql(), "CREATE TABLE `clients_v2` (`serverId` INT NOT NULL, `cldbid` INTEGER, `original_client_id` INTEGER DEFAULT 0, `clientUid` VARCHAR(64) NOT NULL, `firstConnect` BIGINT DEFAULT 0, `lastConnect` BIGINT DEFAULT 0, `connections` INT DEFAULT 0, `lastName` VARCHAR(128) DEFAULT '', UNIQUE(`serverId`, `clientUid`));").execute();
if(!result) {
error = "failed to create new clients table (" + result.fmtStr() + ")";
return false;
}
result = sql::command(this->sql(), "INSERT INTO `clients_v2` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) SELECT `serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName` FROM `clients`;").execute();
if(!result) {
sql::command(this->sql(), "DROP TABLE `groups_v2`;").execute();
error = "failed to insert data into the new clients table (" + result.fmtStr() + ")";
return false;
}
result = sql::command(this->sql(), "DROP TABLE `clients`;").execute();
if(!result) {
error = "failed to delete old clients table (" + result.fmtStr() + ")";
return false;
}
result = sql::command(this->sql(), "ALTER TABLE `clients_v2` RENAME TO clients;").execute();
if(!result) {
error = "failed to rename new clients table to the old clients table (" + result.fmtStr() + ")";
return false;
}
}
db_version(12);
default:
break;
}
@@ -443,14 +502,14 @@ bool SqlDataManager::update_permissions(std::string &error) {
else
query += "OR IGNORE ";
query += "INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) ";
query += string() + "SELECT DISTINCT `permissions`.`serverId`, 0, `groupId`, 0, "
query += string() + "SELECT DISTINCT `permissions`.`serverId`, `permissions`.`type`, `groupId`, `permissions`.`channelId`, "
+ "'" + permission + "', "
+ to_string(value.has_value ? value.value : -2) + ", "
+ to_string(granted.has_value ? granted.value : -2) + ", "
+ to_string(skip) + ", "
+ to_string(negate) + " FROM groups ";
query += "INNER JOIN `permissions` ";
query += "ON permissions.permId = 'i_group_auto_update_type' AND permissions.channelId = 0 AND permissions.id = groups.groupId AND permissions.serverId = groups.serverId AND permissions.value = " + to_string(update_type);
query += "ON permissions.permId = 'i_group_auto_update_type' AND permissions.id = groups.groupId AND permissions.serverId = groups.serverId AND permissions.value = " + to_string(update_type);
logTrace(LOG_GENERAL, "Executing sql update: {}", query);
auto result = sql::command(this->sql(), query).execute();
@@ -547,6 +606,24 @@ bool SqlDataManager::update_permissions(std::string &error) {
if(!auto_update(permission::update::QUERY_ADMIN, "b_channel_conversation_message_delete", {1, true}, false, false, {100, true}))
return false;
perm_version(2);
case 2:
if(!auto_update(permission::update::SERVER_ADMIN, "b_client_query_create_own", {1, true}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "b_client_query_create_own", {1, true}, false, false, {100, true}))
return false;
/* for some reason some users haven't received these updates from last time */
if(!auto_update(permission::update::SERVER_ADMIN, "i_playlist_song_move_power", {75, true}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "i_playlist_song_move_power", {100, true}, false, false, {100, true}))
return false;
if(!auto_update(permission::update::SERVER_ADMIN, "i_playlist_song_needed_move_power", {0, false}, false, false, {75, true}))
return false;
if(!auto_update(permission::update::QUERY_ADMIN, "i_playlist_song_needed_move_power", {0, false}, false, false, {100, true}))
return false;
perm_version(3);
default:
break;
}
+6 -3
View File
@@ -81,19 +81,22 @@ std::shared_ptr<server::MusicClient> MusicBotManager::createBot(ClientDbId owner
musicBot->manager = this;
musicBot->server = handle;
DatabaseHelper::assignDatabaseId(handle->getSql(), handle->getServerId(), musicBot);
{
if(config::music::enabled) {
lock_guard lock(this->music_bots_lock);
this->music_bots.push_back(musicBot);
}
(LOG_SQL_CMD)(sql::command(handle->getSql(), "INSERT INTO `musicbots` (`serverId`, `botId`, `uniqueId`, `owner`) VALUES (:sid, :botId, :uid, :owner)",
variable{":sid", handle->getServerId()}, variable{":botId", musicBot->getClientDatabaseId()}, variable{":uid", musicBot->getUid()}, variable{":owner", owner}).execute());
musicBot->properties()[property::CLIENT_OWNER] = owner;
handle->groups->enableCache(musicBot->getClientDatabaseId());
musicBot->setDisplayName("Im a music bot!");
musicBot->properties()[property::CLIENT_LASTCONNECTED] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
musicBot->properties()[property::CLIENT_CREATED] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
musicBot->properties()[property::CLIENT_VERSION] = "TeaMusic";
musicBot->properties()[property::CLIENT_PLATFORM] = "internal";
if(!config::music::enabled) return nullptr;
handle->groups->enableCache(musicBot->getClientDatabaseId());
handle->registerClient(musicBot);
{
@@ -283,7 +286,7 @@ void MusicBotManager::disconnectBots() {
void MusicBotManager::load_playlists() {
if(!config::music::enabled) return;
lock_guard playlist_lock(this->playlists_lock);
auto sql_result = sql::command(this->ref_server()->getSql(), "SELECT `playlist_id` FROM `playlists` WHERE `serverId` = :server_id", variable{":server_id", this->ref_server()->getServerId()}).query([&](int length, string* values, string* names){
if(length != 1) return;
+1 -1
View File
@@ -351,7 +351,7 @@ void Playlist::destroy_tree() {
}
}
std::deque<std::shared_ptr<PlaylistEntryInfo> > Playlist::list_songs() {
std::deque<std::shared_ptr<PlaylistEntryInfo>> Playlist::list_songs() {
unique_lock list_lock(this->playlist_lock);
return this->_list_songs(list_lock);
}
+10 -9
View File
@@ -63,7 +63,8 @@ std::shared_ptr<ts::music::PlayableSong> PlayablePlaylist::current_song(bool& aw
if(this->properties()[property::PLAYLIST_CURRENT_SONG_ID].as<SongId>() != id) /* should not happen */
this->properties()[property::PLAYLIST_CURRENT_SONG_ID] = id;
if(!entry || entry->metadata.requires_load()) return nullptr;
if(!entry)
return nullptr;
if(entry->metadata.is_loading()) {
if(entry->metadata.load_begin + seconds(30) < system_clock::now()) {
@@ -113,10 +114,10 @@ std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::playlist_next_entry() {
auto replay_mode = this->properties()[property::PLAYLIST_REPLAY_MODE].as<ReplayMode::value>();
unique_lock playlist_lock(this->playlist_lock);
auto current_song = this->playlist_find(playlist_lock, this->currently_playing());
auto old_song = this->playlist_find(playlist_lock, this->currently_playing());
if(replay_mode == ReplayMode::SINGLE_LOOPED) {
if(current_song) return current_song->entry;
if(old_song) return old_song->entry;
if(this->playlist_head) return this->playlist_head->entry;
this->properties()[property::PLAYLIST_FLAG_FINISHED] = true;
@@ -125,9 +126,9 @@ std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::playlist_next_entry() {
std::shared_ptr<PlaylistEntryInfo> result;
if(replay_mode == ReplayMode::LINEAR || replay_mode == ReplayMode::LINEAR_LOOPED) {
if(current_song) {
if(current_song->next_song)
result = current_song->next_song->entry;
if(old_song) {
if(old_song->next_song)
result = old_song->next_song->entry;
else if(replay_mode == ReplayMode::LINEAR_LOOPED && this->playlist_head)
result = this->playlist_head->entry;
else {
@@ -148,14 +149,14 @@ std::shared_ptr<PlaylistEntryInfo> PlayablePlaylist::playlist_next_entry() {
result = songs.front();
} else {
size_t index;
while(songs[index = (rand() % songs.size())] == (!current_song ? nullptr : current_song->entry)) { }
while(songs[index = (rand() % songs.size())] == (!old_song ? nullptr : old_song->entry)) { }
result = songs[index];
}
}
playlist_lock.unlock();
if(current_song && this->properties()[property::PLAYLIST_FLAG_DELETE_PLAYED].as<bool>()) {
this->delete_song(current_song->entry->song_id);
if(old_song && this->properties()[property::PLAYLIST_FLAG_DELETE_PLAYED].as<bool>()) {
this->delete_song(old_song->entry->song_id);
}
return result;
}
+5 -12
View File
@@ -1,10 +1,8 @@
#include "POWHandler.h"
#include "src/InstanceHandler.h"
#include "src/VirtualServerManager.h"
#include "src/client/voice/VoiceClient.h"
#include <misc/endianness.h>
#include <log/LogUtils.h>
#include <src/client/voice/VoiceClient.h>
using namespace std;
using namespace std::chrono;
@@ -21,7 +19,7 @@ void POWHandler::execute_tick() {
lock_guard lock(this->pending_clients_lock);
this->pending_clients.erase(remove_if(this->pending_clients.begin(), this->pending_clients.end(), [&, now](const shared_ptr<Client>& client) {
if(now - client->last_packet > seconds(5)) {
if(now - client->last_packet > std::chrono::seconds{5}) {
#ifdef POW_ERROR
if(client->state != LowHandshakeState::COMPLETED) { /* handshake succeeded */
debugMessage(this->get_server_id(), "[POW] Dropping connection from {} (Timeout)", net::to_string(client->address));
@@ -155,12 +153,6 @@ inline void generate_random(uint8_t *destination, size_t length) {
*(destination++) = (uint8_t) rand();
}
inline void write_reversed(uint8_t* destination, uint8_t* source, size_t length) {
destination += length;
while(length-- > 0)
*(--destination) = *(source++);
}
void POWHandler::handle_cookie_get(const std::shared_ptr<ts::server::POWHandler::Client> &client, const pipes::buffer_view &buffer) {
if(buffer.length() != 21) {
#ifdef POW_ERROR
@@ -172,7 +164,7 @@ void POWHandler::handle_cookie_get(const std::shared_ptr<ts::server::POWHandler:
/* initialize data */
if(client->server_control_data[0] == 0) {
generate_random(client->server_control_data, 16);
client->server_control_data[0] |= 1;
client->server_control_data[0] |= 1U;
}
/* parse values */
@@ -183,7 +175,7 @@ void POWHandler::handle_cookie_get(const std::shared_ptr<ts::server::POWHandler:
uint8_t response_buffer[21];
response_buffer[0] = LowHandshakeState::COOKIE_SET;
memcpy(&response_buffer[1], client->server_control_data, 16);
write_reversed(&response_buffer[17], client->client_control_data, 4);
*(uint32_t*) &response_buffer[17] = htonl(*(uint32_t*) &client->client_control_data);
this->send_data(client, pipes::buffer_view{response_buffer, 21});
}
@@ -211,7 +203,7 @@ void POWHandler::handle_puzzle_get(const std::shared_ptr<ts::server::POWHandler:
}
if(!client->rsa_challenge)
client->rsa_challenge = serverInstance->getVoiceServerManager()->rsaPuzzles()->nextPuzzle();
client->rsa_challenge = serverInstance->getVoiceServerManager()->rsaPuzzles()->next_puzzle();
/* send response */
{
@@ -262,6 +254,7 @@ void POWHandler::handle_puzzle_solve(const std::shared_ptr<ts::server::POWHandle
#ifdef POW_ERROR
debugMessage(this->get_server_id(), "[POW][{}][Puzzle] Received an invalid puzzle solution! Resetting client", net::to_string(client->address));
#endif
client->rsa_challenge.reset(); /* get another RSA challenge */
this->reset_client(client);
return;
}
+4 -3
View File
@@ -8,6 +8,7 @@
#include "VoiceServer.h"
#include "src/VirtualServer.h"
namespace ts::server {
class POWHandler {
public:
@@ -32,13 +33,13 @@ namespace ts::server {
std::chrono::system_clock::time_point last_packet;
LowHandshakeState state = LowHandshakeState::COOKIE_GET;
uint8_t client_control_data[4] = {0,0,0,0};
uint8_t server_control_data[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
uint8_t client_control_data[4]{0};
uint8_t server_control_data[16]{0};
uint8_t server_data[100];
uint32_t client_version;
std::shared_ptr<protocol::Puzzle> rsa_challenge;
std::shared_ptr<udp::Puzzle> rsa_challenge;
};
explicit POWHandler(VoiceServer* /* server */);
+1
View File
@@ -6,6 +6,7 @@
#include <condition_variable>
#include <pipes/buffer.h>
#include <misc/spin_lock.h>
#include <ThreadPool/Mutex.h>
namespace ts {
namespace server {
+8 -4
View File
@@ -148,7 +148,7 @@ void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &no
lock_guard lock(this->connectionLock);
connections = this->activeConnections;
}
deque<pipes::buffer> buffers;
deque<std::shared_ptr<connection::AcknowledgeManager::Entry>> buffers;
string error;
for(const auto& client : connections) {
auto connection = client->getConnection();
@@ -165,12 +165,16 @@ void VoiceServer::execute_resend(const std::chrono::system_clock::time_point &no
} else if(!buffers.empty()) {
{
lock_guard client_write_lock(connection->write_queue_lock);
connection->write_queue.insert(connection->write_queue.end(), buffers.begin(), buffers.end());
for(auto& buf : buffers)
connection->write_queue.push_back(buf->buffer);
}
//logTrace(client->getServerId(), "{} Resending {} packets.", CLIENT_STR_LOG_PREFIX_(client), buffers.size());
buffers.clear();
for(auto& entry : buffers)
connection->packet_statistics().send_command((protocol::PacketType) entry->packet_type, entry->packet_id | entry->generation_id << 16U);
//if(buffers.size() > 0)
// logTrace(client->getServerId(), "{} Resending {} packets.", CLIENT_STR_LOG_PREFIX_(client), buffers.size());
connection->triggerWrite();
}
buffers.clear();
}
}
+60
View File
@@ -0,0 +1,60 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "channel.h"
using namespace ts::server::snapshots;
bool channel_parser::parse(std::string &error, channel_entry &channel, size_t &offset) {
auto data = this->command.bulk(offset++);
channel.properties.register_property_type<property::ChannelProperties>();
std::optional<ChannelId> channel_id{};
std::optional<ChannelId> parent_channel_id{};
size_t entry_index{0};
std::string_view key{};
std::string value{};
while(data.next_entry(entry_index, key, value)) {
if(key == "begin_channels")
continue;
else if(key == "channel_id") {
char* end_ptr{nullptr};
channel_id = strtoull(value.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "failed to parse channel id at character " + std::to_string(data.key_command_character_index(key) + key.length());
return false;
}
} else if(key == "channel_pid") {
char* end_ptr{nullptr};
parent_channel_id = strtoull(value.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "failed to parse channel parent id at character " + std::to_string(data.key_command_character_index(key) + key.length());
return false;
}
} else {
const auto& property = property::find<property::ChannelProperties>(key);
if(property.is_undefined()) {
//TODO: Issue a warning
continue;
}
//TODO: Validate value
channel.properties[property] = value;
}
}
if(!channel_id.has_value()) {
error = "channel entry at character index " + std::to_string(data.command_character_index()) + " misses a channel id";
return false;
}
if(!parent_channel_id.has_value()) {
error = "channel entry at character index " + std::to_string(data.command_character_index()) + " misses a channel parent id";
return false;
}
channel.properties[property::CHANNEL_ID] = *channel_id;
channel.properties[property::CHANNEL_PID] = *parent_channel_id;
return true;
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <Definitions.h>
#include <Properties.h>
#include <chrono>
#include <deque>
#include "./snapshot.h"
namespace ts::server::snapshots {
struct channel_entry {
Properties properties{};
};
class channel_parser : public parser<channel_entry> {
public:
channel_parser(type type_, version_t version, const command_parser& command) : parser{type_, version, command} {}
bool parse(
std::string& /* error */,
channel_entry& /* result */,
size_t& /* offset */) override;
};
}
+108
View File
@@ -0,0 +1,108 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "client.h"
using namespace ts::server::snapshots;
bool client_parser::parse(std::string &error, client_entry &client, size_t &offset) {
bool key_found;
auto data = this->command.bulk(offset++);
{
auto value_string = data.value("client_id", key_found);
if(!key_found) {
error = "missing id for client entry at character " + std::to_string(data.command_character_index());
return false;
}
char* end_ptr{nullptr};
client.database_id = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable id for client entry at character " + std::to_string(data.key_command_character_index("client_id") + 9);
return false;
}
}
{
auto value_string = data.value("client_created", key_found);
if(!key_found) {
error = "missing created timestamp for client entry at character " + std::to_string(data.command_character_index());
return false;
}
char* end_ptr{nullptr};
auto value = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable created timestamp for client entry at character " + std::to_string(data.key_command_character_index("client_created") + 14);
return false;
}
client.timestamp_created = std::chrono::system_clock::time_point{} + std::chrono::seconds{value};
}
/* optional */
{
auto value_string = data.value("client_lastconnected", key_found);
if(key_found) {
char* end_ptr{nullptr};
auto value = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable last connected timestamp for client entry at character " + std::to_string(data.key_command_character_index("client_lastconnected") + 20);
return false;
}
client.timestamp_last_connected = std::chrono::system_clock::time_point{} + std::chrono::seconds{value};
} else {
client.timestamp_last_connected = std::chrono::system_clock::time_point{};
}
}
/* optional */
{
auto value_string = data.value("client_totalconnections", key_found);
if(key_found) {
char* end_ptr{nullptr};
client.client_total_connections = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable total connection count for client entry at character " + std::to_string(data.key_command_character_index("client_totalconnections") + 23);
return false;
}
} else {
client.client_total_connections = 0;
}
}
client.unique_id = data.value("client_unique_id", key_found);
if(!key_found) {
error = "missing unique id for client entry at character " + std::to_string(data.command_character_index());
return false;
}
client.nickname = data.value("client_nickname", key_found);
if(!key_found) {
error = "missing nickname for client entry at character " + std::to_string(data.command_character_index());
return false;
}
client.description = data.value("client_description", key_found);
if(!key_found) {
error = "missing description for client entry at character " + std::to_string(data.command_character_index());
return false;
}
return true;
}
bool client_writer::write(std::string &error, size_t &offset, const client_entry &client) {
auto data = this->command.bulk(offset++);
data.put_unchecked("client_id", client.database_id);
data.put_unchecked("client_unique_id", client.unique_id);
data.put_unchecked("client_nickname", client.nickname);
data.put_unchecked("client_description", client.description);
data.put_unchecked("client_created", std::chrono::floor<std::chrono::seconds>(client.timestamp_created.time_since_epoch()).count());
data.put_unchecked("client_lastconnected", std::chrono::floor<std::chrono::seconds>(client.timestamp_last_connected.time_since_epoch()).count());
data.put_unchecked("client_totalconnections", client.client_total_connections);
if(this->type_ == type::TEAMSPEAK)
data.put_unchecked("client_unread_messages", "0");
return true;
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <Definitions.h>
#include <chrono>
#include <deque>
#include "./snapshot.h"
namespace ts::server::snapshots {
struct client_entry {
ClientDbId database_id;
std::string unique_id;
std::string nickname;
std::string description;
std::chrono::system_clock::time_point timestamp_created;
std::chrono::system_clock::time_point timestamp_last_connected;
size_t client_total_connections;
};
class client_parser : public parser<client_entry> {
public:
client_parser(type type_, version_t version, const command_parser& command) : parser{type_, version, command} {}
bool parse(
std::string& /* error */,
client_entry& /* result */,
size_t& /* offset */) override;
};
class client_writer : public writer<client_entry> {
public:
client_writer(type type_, version_t version, command_builder& command) : writer{type_, version, command} {}
bool write(std::string &, size_t &, const client_entry &) override;
};
}
+503
View File
@@ -0,0 +1,503 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "./snapshot.h"
#include "./server.h"
#include "./channel.h"
#include "./permission.h"
#include "./client.h"
#include "./groups.h"
#include "../VirtualServerManager.h"
#include "../InstanceHandler.h"
#include <sql/insert.h>
#include <log/LogUtils.h>
using namespace ts;
using namespace ts::server;
using SnapshotType = ts::server::snapshots::type;
using SnapshotVersion = ts::server::snapshots::version_t;
bool VirtualServerManager::deploy_snapshot(std::string &error, ServerId server_id, const command_parser &data) {
if(data.bulk(0).has_key("version")) {
return this->deploy_ts3_snapshot(error, server_id, data);
} else if(data.bulk(1).has_key("snapshot_version")) {
/* teaspeak snapshot */
return this->deploy_teaspeak_snapshot(error, server_id, data);
} else {
/* old TS3 snapshot format */
return this->deploy_ts3_snapshot(error, server_id, data);
}
}
bool VirtualServerManager::deploy_teaspeak_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
if(!data.bulk(1).has_key("snapshot_version")) {
error = "Missing snapshot version";
return false;
}
auto version = data.bulk(1).value_as<snapshots::version_t>("snapshot_version");
auto hash = data.bulk(0).value("hash");
if(version < 1) {
error = "snapshot version too old";
return false;
} else if(version > 2) {
error = "snapshot version is too new";
return false;
}
/* the actual snapshot begins at index 2 */
return this->deploy_raw_snapshot(error, server_id, data, hash, 2, SnapshotType::TEASPEAK, version);
}
bool VirtualServerManager::deploy_ts3_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &data) {
snapshots::version_t version{0};
if(data.bulk(0).has_key("version"))
version = data.bulk(0).value_as<snapshots::version_t>("version");
auto hash = data.bulk(0).value("hash");
if(data.bulk(0).has_key("salt")) {
error = "TeaSpeak dosn't support encrypted snapshots yet";
return false;
}
if(version == 0) {
return this->deploy_raw_snapshot(error, server_id, data, hash, 1, SnapshotType::TEAMSPEAK, version);
} else if(version == 1) {
error = "version 1 is an invalid version";
return false;
} else if(version == 2) {
/* compressed data */
error = "version 2 isn't currently supported";
return false;
} else if(version == 3) {
error = "version 3 isn't currently supported";
return false;
} else {
error = "snapshots with version 1-3 are currently supported";
return false;
}
}
struct parse_client_entry {
snapshots::client_entry parsed_data{};
};
struct parsed_group_entry {
snapshots::group_entry parsed_data{};
};
bool VirtualServerManager::deploy_raw_snapshot(std::string &error, ts::ServerId server_id, const ts::command_parser &command, const std::string& /* hash */, size_t command_offset,
snapshots::type type, snapshots::version_t version) {
snapshots::server_entry parsed_server{};
//TODO: Verify hash
/* all snapshots start with the virtual server properties */
{
snapshots::server_parser parser{type, version, command};
if(!parser.parse(error, parsed_server, command_offset))
return false;
}
std::vector<snapshots::channel_entry> parsed_channels{};
/* afterwards all channels */
{
snapshots::channel_parser parser{type, version, command};
auto data = command.bulk(command_offset);
if(!data.has_key("begin_channels")) {
error = "missing begin channels token at " + std::to_string(data.command_character_index());
return false;
}
auto end_bulk = command.next_bulk_containing("end_channels", command_offset);
if(!end_bulk.has_value()) {
error = "missing end channels token";
return false;
} else if(*end_bulk == command_offset) {
error = "snapshot contains no channels";
return false;
}
parsed_channels.reserve(*end_bulk - command_offset);
debugMessage(server_id, "Snapshot contains {} channels", *end_bulk - command_offset);
while(!command.bulk(command_offset).has_key("end_channels")) {
auto& entry = parsed_channels.emplace_back();
if(!parser.parse(error, entry, command_offset))
return false;
}
command_offset++; /* the "end_channels" token */
}
std::vector<parse_client_entry> parsed_clients{};
/* after channels all clients */
{
snapshots::client_parser parser{type, version, command};
auto data = command.bulk(command_offset);
if(!data.has_key("begin_clients")) {
error = "missing begin clients token at " + std::to_string(data.command_character_index());
return false;
}
auto end_bulk = command.next_bulk_containing("end_clients", command_offset);
if(!end_bulk.has_value()) {
error = "missing end clients token";
return false;
}
parsed_channels.reserve(*end_bulk - command_offset);
debugMessage(server_id, "Snapshot contains {} clients", *end_bulk - command_offset);
while(!command.bulk(command_offset).has_key("end_clients")) {
auto& entry = parsed_clients.emplace_back();
if(!parser.parse(error, entry.parsed_data, command_offset))
return false;
}
command_offset++; /* the "end_clients" token */
}
bool server_groups_parsed{false},
channel_groups_parsed{false},
client_permissions_parsed{false},
channel_permissions_parsed{false},
client_channel_permissions_parsed{false};
std::vector<parsed_group_entry> parsed_server_groups{};
snapshots::group_relations parsed_server_group_relations{};
std::vector<parsed_group_entry> parsed_channel_groups{};
snapshots::group_relations parsed_channel_group_relations{};
std::deque<snapshots::permissions_flat_entry> client_permissions{};
std::deque<snapshots::permissions_flat_entry> channel_permissions{};
std::deque<snapshots::permissions_flat_entry> client_channel_permissions{};
/* permissions */
{
if(!command.bulk(command_offset++).has_key("begin_permissions")) {
error = "missing begin permissions key";
return false;
}
snapshots::relation_parser relation_parser{type, version, command};
while(!command.bulk(command_offset).has_key("end_permissions")) {
if(command.bulk(command_offset).has_key("server_groups")) {
if(server_groups_parsed) {
error = "duplicated server group list";
return false;
} else server_groups_parsed = true;
snapshots::group_parser group_parser{type, version, command, "id", permission::teamspeak::GroupType::SERVER};
/* parse all groups */
while(!command.bulk(command_offset).has_key("end_groups")){
auto& group = parsed_server_groups.emplace_back();
if(!group_parser.parse(error, group.parsed_data, command_offset)) /* will consume the end group token */
return false;
command_offset++; /* for the "end_group" token */
}
command_offset++; /* for the "end_groups" token */
/* parse relations */
if(!relation_parser.parse(error, parsed_server_group_relations, command_offset))
return false;
command_offset++; /* for the "end_relations" token */
if(parsed_server_group_relations.size() > 1) {
error = "all group relations should be for channel id 0 but received more than one different channel.";
return false;
} else if(!parsed_server_group_relations.empty() && parsed_server_group_relations.begin()->first != 0) {
error = "all group relations should be for channel id 0 but received it for " + std::to_string(parsed_server_group_relations.begin()->first);
return false;
}
} else if(command.bulk(command_offset).has_key("channel_groups")) {
if(channel_groups_parsed) {
error = "duplicated channel group list";
return false;
} else channel_groups_parsed = true;
snapshots::group_parser group_parser{type, version, command, "id", permission::teamspeak::GroupType::CHANNEL};
/* parse all groups */
while(!command.bulk(command_offset).has_key("end_groups")){
auto& group = parsed_channel_groups.emplace_back();
if(!group_parser.parse(error, group.parsed_data, command_offset))
return false;
command_offset++; /* for the "end_group" token */
}
command_offset++; /* for the "end_groups" token */
/* parse relations */
if(!relation_parser.parse(error, parsed_channel_group_relations, command_offset))
return false;
command_offset++; /* for the "end_relations" token */
} else if(command.bulk(command_offset).has_key("client_flat")) {
/* client permissions */
if(client_permissions_parsed) {
error = "duplicated client permissions list";
return false;
} else client_permissions_parsed = true;
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CLIENT};
if(!flat_parser.parse(error, client_permissions, command_offset))
return false;
command_offset++; /* for the "end_flat" token */
} else if(command.bulk(command_offset).has_key("channel_flat")) {
/* channel permissions */
if(channel_permissions_parsed) {
error = "duplicated channel permissions list";
return false;
} else channel_permissions_parsed = true;
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CHANNEL};
if(!flat_parser.parse(error, channel_permissions, command_offset))
return false;
command_offset++; /* for the "end_flat" token */
} else if(command.bulk(command_offset).has_key("channel_client_flat")) {
/* channel client permissions */
if(client_channel_permissions_parsed) {
error = "duplicated client channel permissions list";
return false;
} else client_channel_permissions_parsed = true;
snapshots::flat_parser flat_parser{type, version, command, permission::teamspeak::GroupType::CLIENT};
if(!flat_parser.parse(error, client_channel_permissions, command_offset))
return false;
command_offset++; /* for the "end_flat" token */
} else {
command_offset++;
}
}
}
/* check if everything has been parsed */
{
/* basic stuff */
if(!server_groups_parsed) {
error = "missing server groups";
return false;
}
if(!channel_groups_parsed) {
error = "missing channel groups";
return false;
}
if(!client_permissions_parsed) {
error = "missing client permissions";
return false;
}
if(!channel_permissions_parsed) {
error = "missing channel permissions";
return false;
}
if(!client_channel_permissions_parsed) {
error = "missing client channel permissions";
return false;
}
}
std::map<ChannelId, ChannelId> channel_id_mapping{};
std::map<ChannelId, ChannelId> channel_group_id_mapping{};
std::map<ChannelId, ChannelId> server_group_id_mapping{};
/* lets start inserting data to the database */
{
/* cleanup all old data */
this->delete_server_in_db(server_id);
/* register clients */
{
//`original_client_id` INTEGER DEFAULT 0
//CREATE TABLE `clients_v2` (`serverId` INT NOT NULL, `cldbid` INTEGER, `clientUid` VARCHAR(64) NOT NULL, `firstConnect` BIGINT DEFAULT 0, `lastConnect` BIGINT DEFAULT 0, `connections` INT DEFAULT 0, `lastName` VARCHAR(128) DEFAULT '', UNIQUE(`serverId`, `clientUid`));
sql::InsertQuery insert_general_query{"clients",
sql::Column<ServerId>("serverId"),
sql::Column<ClientDbId>("original_client_id"),
sql::Column<std::string>("clientUid"),
sql::Column<int64_t>("firstConnect"),
sql::Column<int64_t>("lastConnect"),
sql::Column<uint64_t>("connections"),
sql::Column<std::string>("lastName")
};
sql::InsertQuery insert_server_query{"clients",
sql::Column<ServerId>("serverId"),
sql::Column<ClientDbId>("original_client_id"),
sql::Column<std::string>("clientUid"),
sql::Column<int64_t>("firstConnect"),
sql::Column<int64_t>("lastConnect"),
sql::Column<uint64_t>("connections"),
sql::Column<std::string>("lastName")
};
for(const auto& client : parsed_clients) {
/*
insert_general_query.add_entry(
server_id,
client.parsed_data.database_id,
client.parsed_data.unique_id,
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_created.time_since_epoch()).count(),
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_last_connected.time_since_epoch()).count(),
client.parsed_data.client_total_connections,
client.parsed_data.nickname
);
*/
insert_general_query.add_entry(
server_id,
client.parsed_data.database_id,
client.parsed_data.unique_id,
std::chrono::floor<std::chrono::seconds>(client.parsed_data.timestamp_created.time_since_epoch()).count()
);
}
auto result = insert_general_query.execute(this->handle->getSql(), true);
for(const auto& fail : result.failed_entries)
logWarning(server_id, "Failed to insert client {} into the database: {}", parsed_clients[std::get<0>(fail)], std::get<1>(fail).fmtStr());
sql::command{this->handle->getSql(), "SELECT `original_client_id`,`cldbid` FROM `clients` WHERE `serverId` = :sid;"}
.value(":serverId", server_id)
.query([&](int length, std::string* values, std::string* names) {
ClientDbId original_id{0}, new_id{0};
try {
original_id = std::stoull(values[0]);
new_id = std::stoull(values[1]);
} catch (std::exception& ex) {
logWarning(server_id, "Failed to parse client database entry mapping for group id {} (New ID: {})", values[1], values[0]);
return;
}
server_group_id_mapping[original_id] = new_id;
});
}
/* channels */
{
/* Assign each channel a new id */
ChannelId current_id{0};
for(auto& channel : parsed_channels) {
const auto new_id = current_id++;
channel_id_mapping[channel.properties[property::CHANNEL_ID]] = new_id;
channel.properties[property::CHANNEL_ID] = new_id;
}
/* Update channel parents */
for(auto& channel : parsed_channels) {
auto pid = channel.properties[property::CHANNEL_PID].as<ChannelId>();
if(pid > 0) {
auto new_id = channel_id_mapping.find(pid);
if(new_id == channel_id_mapping.end()) {
error = "failed to remap channel parent id for channel \"" + channel.properties[property::CHANNEL_NAME].value() + "\" (snapshot/channel tree broken?)";
return false;
}
channel.properties[property::CHANNEL_PID] = new_id->second;
}
}
//TODO: Insert them into the database
}
/* channel permissions */
{
for(auto& entry : channel_permissions) {
auto new_id = channel_id_mapping.find(entry.id1);
if(new_id == channel_id_mapping.end()) {
error = "missing channel id mapping for channel permission entry";
return false;
}
entry.id1 = new_id->second;
}
}
/* server groups */
{
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_id`) VALUES (:serverId, :target, :type, :name, :id)"};
insert_model.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL);
for(auto& group : parsed_server_groups) {
auto result = insert_model.command().value(":name", group.parsed_data.name).value(":id", group.parsed_data.group_id).execute();
if(!result)
logWarning(server_id, "Failed to insert server group \"{}\" into the database", group.parsed_data.name);
}
sql::command{this->handle->getSql(), "SELECT `original_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_SERVER).value(":type", GroupType::GROUP_TYPE_NORMAL)
.query([&](int length, std::string* values, std::string* names) {
GroupId original_id{0}, new_id{0};
try {
original_id = std::stoull(values[0]);
new_id = std::stoull(values[1]);
} catch (std::exception& ex) {
logWarning(server_id, "Failed to parse server group mapping for group id {} (New ID: {})", values[1], values[0]);
return;
}
server_group_id_mapping[original_id] = new_id;
});
}
/* channel groups */
{
sql::model insert_model{this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `target`, `type`, `displayName`, `original_id`) VALUES (:serverId, :target, :type, :name, :id)"};
insert_model.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL);
for(auto& group : parsed_server_groups) {
auto result = insert_model.command().value(":name", group.parsed_data.name).value(":id", group.parsed_data.group_id).execute();
if(!result)
logWarning(server_id, "Failed to insert channel group \"{}\" into the database", group.parsed_data.name);
}
sql::command{this->handle->getSql(), "SELECT `original_id`,`groupId` FROM `groups` WHERE `serverId` = :sid AND `target` = :target AND `type` = :type"}
.value(":serverId", server_id).value(":target", GroupTarget::GROUPTARGET_CHANNEL).value(":type", GroupType::GROUP_TYPE_NORMAL)
.query([&](int length, std::string* values, std::string* names) {
GroupId original_id{0}, new_id{0};
try {
original_id = std::stoull(values[0]);
new_id = std::stoull(values[1]);
} catch (std::exception& ex) {
logWarning(server_id, "Failed to parse channel group mapping for group id {} (New ID: {})", values[1], values[0]);
return;
}
channel_group_id_mapping[original_id] = new_id;
});
}
#define INSERT_PERMISSION_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)"
/* client permissions */
{
sql::InsertQuery insert_query{"permissions",
sql::Column<ServerId>("serverId"),
sql::Column<permission::PermissionSqlType>("type"),
sql::Column<uint64_t>("id"),
sql::Column<ChannelId>("channelId"),
sql::Column<std::string>("permId"),
sql::Column<permission::PermissionValue>("value"),
sql::Column<permission::PermissionValue>("grant"),
sql::Column<bool>("flag_skip"),
sql::Column<bool>("flag_negate"),
};
for(auto& permission : client_permissions) {
}
auto result = insert_query.execute(this->handle->getSql(), true);
}
/* register clients in the database */
{
}
/* client channel permissions */
{
for(auto& entry : client_channel_permissions) {
auto new_id = channel_id_mapping.find(entry.id1);
if(new_id == channel_id_mapping.end()) {
error = "missing channel id mapping for client channel permission entry";
return false;
}
entry.id1 = new_id->second;
}
}
}
error = "not implemented";
return false;
}
+97
View File
@@ -0,0 +1,97 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "groups.h"
using namespace ts::server::snapshots;
bool group_parser::parse(std::string &error, group_entry &group, size_t &offset) {
auto group_data = this->command.bulk(offset);
bool key_found;
{
auto value_string = group_data.value(this->id_key, key_found);
if(!key_found) {
error = "missing id for group entry at character " + std::to_string(group_data.command_character_index());
return false;
}
char* end_ptr{nullptr};
group.group_id = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable id for group entry at character " + std::to_string(group_data.key_command_character_index(this->id_key) + this->id_key.length());
return false;
}
}
group.name = group_data.value("name", key_found);
if(!key_found) {
error = "missing name for group entry at character " + std::to_string(group_data.command_character_index());
return false;
}
return this->pparser.parse(error, group.permissions, offset);
}
bool relation_parser::parse(std::string &error, group_relations &result, size_t &offset) {
auto relation_end = this->command.next_bulk_containing("end_relations", offset);
if(!relation_end.has_value()) {
error = "missing end relations token";
return false;
}
bool key_found;
while(offset < *relation_end) {
auto begin_bulk = this->command.bulk(offset);
if(!begin_bulk.has_key("iid")) {
error = "missing iid at character " + std::to_string(begin_bulk.command_character_index());
return false;
}
auto& relations = result[begin_bulk.value_as<ChannelId>("iid")];
auto next_iid = this->command.next_bulk_containing("iid", offset + 1);
if(next_iid.has_value() && *next_iid < relation_end)
relations.reserve(*next_iid - offset);
else
relations.reserve(*relation_end - offset);
while(offset < *next_iid) {
auto relation_data = this->command.bulk(offset++);
auto& relation = relations.emplace_back();
{
auto value_string = relation_data.value("cldbid", key_found);
if(!key_found) {
error = "missing client id for group relation entry at character " + std::to_string(relation_data.command_character_index());
return false;
}
char* end_ptr{nullptr};
relation.client_id = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable client id for group relation entry at character " + std::to_string(relation_data.key_command_character_index("cldbid") + 4);
return false;
}
}
{
auto value_string = relation_data.value("gid", key_found);
if(!key_found) {
error = "missing group id for group relation entry at character " + std::to_string(relation_data.command_character_index());
return false;
}
char* end_ptr{nullptr};
relation.group_id = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable group id for group relation entry at character " + std::to_string(relation_data.key_command_character_index("gid") + 3);
return false;
}
}
}
}
return true;
}
+54
View File
@@ -0,0 +1,54 @@
#pragma once
#include <Definitions.h>
#include <chrono>
#include <deque>
#include <utility>
#include "./snapshot.h"
#include "./permission.h"
namespace ts::server::snapshots {
struct group_entry {
GroupId group_id;
std::string name;
std::vector<permission_entry> permissions{};
};
struct group_relation {
ClientDbId client_id;
GroupId group_id;
};
typedef std::map<ChannelId, std::vector<group_relation>> group_relations;
class group_parser : public parser<group_entry> {
public:
group_parser(type type_, version_t version, const command_parser& command, std::string id_key, permission::teamspeak::GroupType target_permission_type)
: parser{type_, version, command}, id_key{std::move(id_key)}, pparser{type_, version, command, {
target_permission_type,
{"end_group"},
false
}} {}
bool parse(
std::string& /* error */,
group_entry& /* result */,
size_t& /* offset */) override;
private:
std::string id_key{};
permission_parser pparser;
};
class relation_parser : public parser<group_relations> {
public:
relation_parser(type type_, version_t version, const command_parser& command) : parser{type_, version, command} {}
bool parse(
std::string& /* error */,
group_relations& /* result */,
size_t& /* offset */) override;
};
}
+305
View File
@@ -0,0 +1,305 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "permission.h"
using namespace ts::server::snapshots;
permission_parser::permission_parser(ts::server::snapshots::type type_, ts::server::snapshots::version_t version,
const ts::command_parser &command, permission_parser_options options) : parser{type_, version, command}, options{std::move(options)} {
if(type_ == type::TEAMSPEAK) {
this->parser_impl = &permission_parser::parse_entry_teamspeak_v0;
} else if(type_ == type::TEASPEAK) {
if(version >= 1) {
this->parser_impl = &permission_parser::parse_entry_teaspeak_v1;
} else {
/* TeaSpeak has no snapshot version 0. 0 implies a TeamSpeak snapshot */
assert(false);
}
} else {
assert(false);
}
}
bool permission_parser::parse(
std::string &error,
std::vector<permission_entry> &result,
size_t &offset) {
size_t end_offset{(size_t) -1};
{
size_t end_begin_offset{offset + (this->options.ignore_delimiter_at_index_0 ? 1 : 0)};
for(const auto& token : this->options.delimiter) {
auto index = this->command.next_bulk_containing(token, end_begin_offset);
if(index.has_value() && *index < end_offset)
end_offset = *index;
}
if(end_offset == (size_t) -1) {
error = "missing end token";
return false;
}
}
if(end_offset == offset) {
/* no entries at all */
return true;
}
result.reserve((end_offset - offset) * 2); /* reserve some extra space because we might import permissions */
assert(this->type_ == type::TEAMSPEAK || this->type_ == type::TEASPEAK);
while(offset < end_offset) {
if(!(this->*(this->parser_impl))(error, result, this->command[offset]))
return false;
offset++;
}
return true;
}
bool permission_parser::parse_entry_teamspeak_v0(std::string &error, std::vector<permission_entry> &result,
const ts::command_bulk &data) {
bool key_found;
auto original_name = data.value("permid", key_found);
if(!key_found) {
error = "missing id for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
permission::PermissionValue value{};
{
auto value_string = data.value("permvalue", key_found);
if(!key_found) {
error = "missing value for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
char* end_ptr{nullptr};
value = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable permission value at index " + std::to_string(data.key_command_character_index("permvalue") + 9);
return false;
}
}
auto flag_skip = data.value("permskip", key_found) == "1";
if(!key_found) {
error = "missing skip flag for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
auto flag_negate = data.value("permnegated", key_found) == "1";
if(!key_found) {
error = "missing skip flag for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
for(const auto& mapped : permission::teamspeak::map_key(original_name, this->options.target_permission_type)) {
auto type = permission::resolvePermissionData(mapped);
if(type == permission::PermissionTypeEntry::unknown)
continue;
permission_entry* entry{nullptr};
for(auto& e : result)
if(e.type == type) {
entry = &e;
break;
}
if(!entry) {
entry = &result.emplace_back();
entry->type = type;
}
entry->value = {value, true};
if(mapped != type->grant_name) {
entry->flag_negate = flag_negate;
entry->flag_skip = flag_skip;
}
}
return true;
}
bool permission_parser::parse_entry_teaspeak_v1(std::string &error, std::vector<permission_entry> &result,
const ts::command_bulk &data) {
bool key_found;
auto permission_name = data.value("perm", key_found);
if(!key_found) {
error = "missing id for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
auto flag_skip = data.value("flag_skip", key_found) == "1";
if(!key_found) {
error = "missing skip flag for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
auto flag_negated = data.value("flag_negated", key_found) == "1";
if(!key_found) {
error = "missing negate flag for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
permission::PermissionValue value{};
{
auto value_string = data.value("value", key_found);
if(!key_found) {
error = "missing value for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
char* end_ptr{nullptr};
value = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable permission value at index " + std::to_string(data.key_command_character_index("value") + 5);
return false;
}
}
permission::PermissionValue granted{};
{
auto value_string = data.value("grant", key_found);
if(!key_found) {
error = "missing grant for permission entry at character " + std::to_string(data.command_character_index());
return false;
}
char* end_ptr{nullptr};
granted = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable permission granted value at index " + std::to_string(data.key_command_character_index("grant") + 5);
return false;
}
}
auto type = permission::resolvePermissionData(permission_name);
if(type == permission::PermissionTypeEntry::unknown)
return true; /* we just drop unknown permissions */ //TODO: Log this drop
auto& entry = result.emplace_back();
entry.type = type;
entry.flag_skip = flag_skip;
entry.flag_negate = flag_negated;
entry.value = {value, value != permNotGranted};
entry.granted = {granted, granted != permNotGranted};
return true;
}
permission_writer::permission_writer(type type_, version_t version,
ts::command_builder &command, permission::teamspeak::GroupType target_permission_type) : command{command}, type_{type_}, version_{version}, target_permission_type_{target_permission_type} {
if(type_ == type::TEAMSPEAK) {
this->write_impl = &permission_writer::write_entry_teamspeak_v0;
} else if(type_ == type::TEASPEAK) {
if(version >= 1) {
this->write_impl = &permission_writer::write_entry_teaspeak_v1;
} else {
/* TeaSpeak has no snapshot version 0. 0 implies a TeamSpeak snapshot */
assert(false);
}
} else {
assert(false);
}
}
bool permission_writer::write(std::string &error, size_t &offset, const std::deque<permission_entry> &entries) {
this->command.reserve_bulks(entries.size() * 2);
for(auto& entry : entries)
if(!this->write_entry(error, offset, entry))
return false;
return true;
}
bool permission_writer::write_entry(std::string &error, size_t &offset, const ts::server::snapshots::permission_entry &entry) {
return (this->*(this->write_impl))(error, offset, entry);
}
bool permission_writer::write_entry_teamspeak_v0(std::string &error, size_t& offset,
const ts::server::snapshots::permission_entry &entry) {
if(entry.value.has_value) {
for(const auto& name : permission::teamspeak::unmap_key(entry.type->name, this->target_permission_type_)) {
auto bulk = this->command.bulk(offset++);
bulk.put_unchecked("permid", name);
bulk.put_unchecked("permvalue", entry.value.value);
bulk.put_unchecked("permskip", entry.flag_skip);
bulk.put_unchecked("permnegated", entry.flag_negate);
}
}
if(entry.granted.has_value) {
for(const auto& name : permission::teamspeak::unmap_key(entry.type->grant_name, this->target_permission_type_)) {
auto bulk = this->command.bulk(offset++);
bulk.put_unchecked("permid", name);
bulk.put_unchecked("permvalue", entry.granted.value);
bulk.put_unchecked("permskip", "0");
bulk.put_unchecked("permnegated", "0");
}
}
return true;
}
bool permission_writer::write_entry_teaspeak_v1(std::string &error, size_t &offset,
const ts::server::snapshots::permission_entry &entry) {
if(!entry.value.has_value && !entry.granted.has_value)
return true; /* should not happen, but we skip that here */
auto bulk = this->command.bulk(offset++);
bulk.put_unchecked("perm", entry.type->name);
bulk.put_unchecked("value", entry.value.has_value ? entry.value.value : permNotGranted);
bulk.put_unchecked("grant", entry.granted.has_value ? entry.granted.value : permNotGranted);
bulk.put_unchecked("flag_skip", entry.flag_skip);
bulk.put_unchecked("flag_negated", entry.flag_negate);
return true;
}
bool flat_parser::parse(std::string &error, std::deque<permissions_flat_entry> &result, size_t &offset) {
auto flat_end = this->command.next_bulk_containing("end_flat", offset);
if(!flat_end.has_value()) {
error = "missing flat end for " + std::to_string(this->command.bulk(offset).command_character_index());
return false;
}
bool key_found;
while(offset < *flat_end) {
auto flat_data = this->command.bulk(offset);
auto& flat_entry = result.emplace_back();
/* id1 */
{
auto value_string = flat_data.value("id1", key_found);
if(!key_found) {
error = "missing id1 for flat entry at character " + std::to_string(flat_data.command_character_index());
return false;
}
char* end_ptr{nullptr};
flat_entry.id1 = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable id1 for flat entry at character " + std::to_string(flat_data.key_command_character_index("id1") + 3);
return false;
}
}
/* id2 */
{
auto value_string = flat_data.value("id2", key_found);
if(!key_found) {
error = "missing id2 for flat entry at character " + std::to_string(flat_data.command_character_index());
return false;
}
char* end_ptr{nullptr};
flat_entry.id2 = strtoll(value_string.c_str(), &end_ptr, 10);
if (*end_ptr) {
error = "unparsable id2 for flat entry at character " + std::to_string(flat_data.key_command_character_index("id2") + 3);
return false;
}
}
if(!this->pparser.parse(error, flat_entry.permissions, offset))
return false;
}
return true;
}
+110
View File
@@ -0,0 +1,110 @@
#pragma once
#include <PermissionManager.h>
#include <query/command3.h>
#include "./snapshot.h"
namespace ts::server::snapshots {
struct permission_entry {
std::shared_ptr<permission::PermissionTypeEntry> type{nullptr};
permission::v2::PermissionFlaggedValue value{0, false};
permission::v2::PermissionFlaggedValue granted{0, false};
bool flag_skip{false};
bool flag_negate{false};
};
struct permission_parser_options {
permission::teamspeak::GroupType target_permission_type;
std::vector<std::string> delimiter;
bool ignore_delimiter_at_index_0;
};
class permission_parser : public parser<std::vector<permission_entry>> {
public:
permission_parser(type type_, version_t version, const command_parser& command, permission_parser_options /* options */);
bool parse(
std::string& /* error */,
std::vector<permission_entry>& /* result */,
size_t& /* offset */) override;
private:
typedef bool(permission_parser::*parse_impl_t)(std::string &error, std::vector<permission_entry> &result, const ts::command_bulk &data);
const permission_parser_options options;
parse_impl_t parser_impl;
bool parse_entry_teamspeak_v0(
std::string& /* error */,
std::vector<permission_entry>& /* result */,
const command_bulk& /* entry */
);
bool parse_entry_teaspeak_v1(
std::string& /* error */,
std::vector<permission_entry>& /* result */,
const command_bulk& /* entry */
);
};
class permission_writer {
public:
permission_writer(type type_, version_t version, command_builder& command, permission::teamspeak::GroupType target_permission_type);
bool write(
std::string& /* error */,
size_t& /* offset */,
const std::deque<permission_entry>& /* permissions */);
bool write_entry(
std::string& /* error */,
size_t& /* offset */,
const permission_entry& /* permissions */);
private:
typedef bool(permission_writer::*write_impl_t)(std::string &error, size_t& offset,const permission_entry& /* permissions */);
command_builder& command;
const type type_;
const version_t version_;
const permission::teamspeak::GroupType target_permission_type_;
write_impl_t write_impl;
bool write_entry_teamspeak_v0(
std::string &error,
size_t& offset,
const permission_entry& /* permissions */
);
bool write_entry_teaspeak_v1(
std::string &error,
size_t& offset,
const permission_entry& /* permissions */
);
};
struct permissions_flat_entry {
uint64_t id1;
uint64_t id2;
std::vector<permission_entry> permissions;
};
class flat_parser : public parser<std::deque<permissions_flat_entry>> {
public:
flat_parser(type type_, version_t version, const command_parser& command, permission::teamspeak::GroupType target_permission_type)
: parser{type_, version, command}, pparser{type_, version, command, {
target_permission_type,
{"id1", "id2", "end_flat"}, /* only id1 should be enough, because if id2 changes id1 will be set as well but we just wan't to get sure */
true
}} {}
bool parse(
std::string& /* error */,
std::deque<permissions_flat_entry>& /* result */,
size_t& /* offset */) override;
private:
permission_parser pparser;
};
}
+42
View File
@@ -0,0 +1,42 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include "server.h"
using namespace ts::server::snapshots;
bool server_parser::parse(std::string &error, server_entry &result, size_t &offset) {
auto data = this->command.bulk(offset++);
if(!data.has_key("end_virtualserver")) {
error = "missing virtual server end token at character " + std::to_string(data.command_character_index());
return false;
}
result.properties.register_property_type<property::VirtualServerProperties>();
size_t entry_index{0};
std::string_view key{};
std::string value{};
while(data.next_entry(entry_index, key, value)) {
if(key == "end_virtualserver" ||
key == property::describe(property::VIRTUALSERVER_PORT).name ||
key == property::describe(property::VIRTUALSERVER_HOST).name ||
key == property::describe(property::VIRTUALSERVER_WEB_PORT).name ||
key == property::describe(property::VIRTUALSERVER_WEB_HOST).name ||
key == property::describe(property::VIRTUALSERVER_VERSION).name ||
key == property::describe(property::VIRTUALSERVER_PLATFORM).name)
continue;
const auto& property = property::find<property::VirtualServerProperties>(key);
if(property.is_undefined()) {
//TODO: Issue a warning
continue;
}
//TODO: Validate value?
result.properties[property] = value;
}
return true;
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include <Definitions.h>
#include <Properties.h>
#include <chrono>
#include <deque>
#include "./snapshot.h"
namespace ts::server::snapshots {
struct server_entry {
Properties properties{};
};
class server_parser : public parser<server_entry> {
public:
server_parser(type type_, version_t version, const command_parser& command) : parser{type_, version, command} {}
bool parse(
std::string & /* error */,
server_entry & /* result */,
size_t & /* offset */) override;
};
/*
class server_writer : public writer<server_entry> {
public:
server_writer(type type_, version_t version, command_builder& command) : writer{type_, version, command} {}
bool write(std::string &, size_t &, const server_entry &) override;
};
*/
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include <cstdint>
#include <query/command3.h>
namespace ts::server::snapshots {
enum struct type {
TEAMSPEAK,
TEASPEAK
};
typedef int32_t version_t;
constexpr version_t unknown_version{-1};
template <typename result_t>
class parser {
public:
parser(type type_, version_t version, const command_parser& command) :
command{command}, type_{type_}, version_{version} {}
virtual bool parse(
std::string& /* error */,
result_t& /* result */,
size_t& /* offset */) = 0;
protected:
const command_parser& command;
const type type_;
const version_t version_;
};
template <typename entry_t>
class writer {
public:
writer(type type_, version_t version, command_builder& command) :
command{command}, type_{type_}, version_{version} {}
virtual bool write(
std::string& /* error */,
size_t& /* offset */,
const entry_t& /* entry */) = 0;
protected:
command_builder& command;
const type type_;
const version_t version_;
};
}
+4 -2
View File
@@ -473,8 +473,10 @@ namespace terminal {
if(cmd.arguments.size() < 1) {
value = 1024;
rlimit limit{1024, 10000};
setrlimit(7, &limit);
rlimit rlimit{0, 0};
getrlimit(RLIMIT_NOFILE, &rlimit);
logMessage("RLimit: {}/{}", rlimit.rlim_cur, rlimit.rlim_max);
//setrlimit(7, &limit);
} else if(cmd.larguments[0] == "clear") {
logMessage("Clearup leaks");
for(auto& fd : fd_leaks)
+43
View File
@@ -0,0 +1,43 @@
//
// Created by WolverinDEV on 11/04/2020.
//
#include <snapshots/permission.h>
using namespace ts::server::snapshots;
void test_write() {
std::string error{};
size_t offset{0};
ts::command_builder result{""};
permission_writer writer{type::TEAMSPEAK, 0, result, ts::permission::teamspeak::GroupType::GENERAL};
std::deque<permission_entry> entries{};
{
{
auto& entry = entries.emplace_back();
entry.type = ts::permission::resolvePermissionData("b_virtualserver_modify_host");
entry.granted = {2, true};
entry.value = {4, true};
}
{
auto& entry = entries.emplace_back();
entry.type = ts::permission::resolvePermissionData("i_icon_id");
entry.granted = {0, false};
entry.value = {4, true};
entry.flag_skip = true;
}
}
if(!writer.write(error, offset, entries)) {
std::cerr << error << "\n";
assert(false);
return;
}
std::cout << "Offset: " << offset << ". Command: " << result.build() << "\n";
}
int main() {
test_write();
}
+1 -1
Submodule shared updated: ece70e4df4...707736d896