1.4.10 updates
This commit is contained in:
parent
2b929fd3c0
commit
0d456eea5d
@ -1 +1 @@
|
||||
Subproject commit fec314cf2b73fc87b34bc68f26eb64e51982b26e
|
||||
Subproject commit ea81efb4130ad6c616b3b222a364fc8cd5ad9868
|
@ -17,6 +17,7 @@ set(LICENCE_SOURCE_FILES
|
||||
shared/LicenseRequest.cpp
|
||||
shared/LicenseRequestHandler.cpp
|
||||
shared/License.cpp
|
||||
shared/LicenseServerClient.cpp
|
||||
../shared/src/log/LogUtils.cpp
|
||||
)
|
||||
|
||||
@ -36,13 +37,14 @@ add_executable(TeaLicenseServer ${LICENCE_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HD
|
||||
server/KeyIdCache.cpp
|
||||
server/LicenseServer.cpp
|
||||
server/LicenseServerHandler.cpp
|
||||
server/LicenseManager.cpp
|
||||
server/DatabaseHandler.cpp
|
||||
LicenseServerMain.cpp
|
||||
server/WebAPI.cpp
|
||||
server/StatisticManager.cpp
|
||||
server/UserManager.cpp
|
||||
MySQLLibSSLFix.c
|
||||
)
|
||||
target_compile_options(TeaLicenseServer PRIVATE "-Wl,--unresolved-symbols=ignore-in-object-files")
|
||||
|
||||
target_link_libraries(TeaLicenseServer
|
||||
threadpool::static #Static
|
||||
@ -68,44 +70,43 @@ target_link_libraries(TeaLicenseServer
|
||||
mysqlclient.a
|
||||
jsoncpp_lib
|
||||
${DataPipes_LIBRARIES_SHARED} # Also includes glib2.0
|
||||
ffi
|
||||
openssl::ssl::shared
|
||||
openssl::crypto::shared
|
||||
pthread
|
||||
dl
|
||||
z
|
||||
)
|
||||
if (COMPILE_WEB_CLIENT)
|
||||
target_link_libraries(TeaLicenseServer ${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10 ffi)
|
||||
endif ()
|
||||
|
||||
include_directories(${LIBRARY_PATH}/boringssl/include/)
|
||||
|
||||
#The test license client
|
||||
add_executable(TeaLicenseClient
|
||||
LicenseClientMain.cpp
|
||||
${LICENCE_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HDRS}
|
||||
)
|
||||
${LICENCE_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HDRS})
|
||||
target_link_libraries(TeaLicenseClient
|
||||
${LIBRARY_PATH_DATA_PIPES}
|
||||
${LIBRARY_PATH_TERMINAL} #Static
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent.a
|
||||
${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent_pthreads.a
|
||||
pthread
|
||||
${LIBRARY_PATH_VARIBALES}
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
|
||||
${LIBRARY_TOM_MATH}
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
stdc++fs.a
|
||||
${LIBRARY_PATH_TERMINAL}
|
||||
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${TOM_LIBRARIES}
|
||||
${LIBRARY_PATH_JDBC}
|
||||
${LIBRARY_PATH_ED255}
|
||||
TeaSpeak #Static
|
||||
jsoncpp.a
|
||||
|
||||
protobuf::libprotobuf
|
||||
stdc++fs
|
||||
libevent::core libevent::pthreads
|
||||
${ed25519_LIBRARIES_STATIC}
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
|
||||
CXXTerminal::static #Static
|
||||
|
||||
tomcrypt::static
|
||||
tommath::static
|
||||
|
||||
${DataPipes_LIBRARIES_SHARED} # Also includes glib2.0
|
||||
${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10 ffi
|
||||
openssl::ssl::shared
|
||||
openssl::crypto::shared
|
||||
pthread
|
||||
dl
|
||||
z
|
||||
)
|
||||
|
||||
#The license manager
|
||||
@ -166,11 +167,12 @@ add_executable(LicenseCLI
|
||||
)
|
||||
|
||||
target_link_libraries(LicenseCLI
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent.a
|
||||
${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent_pthreads.a
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
${LIBRARY_TOM_MATH} #Static
|
||||
${LIBRARY_TOM_CRYPT} #Static
|
||||
TeaSpeak
|
||||
libevent::core libevent::pthreads
|
||||
threadpool::static #Static
|
||||
tomcrypt::static
|
||||
tommath::static
|
||||
protobuf::libprotobuf
|
||||
${ed25519_LIBRARIES_STATIC}
|
||||
pthread
|
||||
)
|
@ -2,6 +2,8 @@
|
||||
#include <shared/License.h>
|
||||
#include <shared/LicenseRequest.h>
|
||||
#include <event2/thread.h>
|
||||
#include "shared/LicenseServerClient.h"
|
||||
|
||||
#include <random>
|
||||
#include <ed25519/ed25519.h>
|
||||
#include <misc/base64.h>
|
||||
@ -49,8 +51,8 @@ int main(int ac, char** av){
|
||||
}
|
||||
cout << endl;
|
||||
cout << "Intermediate: " << base64::encode(error) << endl;
|
||||
|
||||
#endif
|
||||
#if 0
|
||||
auto license = v2::License::read(intermediate_key.data(), intermediate_key.size(), errc);
|
||||
|
||||
license->push_entry<v2::hierarchy::Server>(system_clock::now(), system_clock::now() + hours{24 * 365 * 1000}, "TeaSpeak Official server", "contact@teaspeak.de");
|
||||
@ -60,6 +62,7 @@ int main(int ac, char** av){
|
||||
__asm__("nop");
|
||||
cout << "Errc: " << errc << endl;
|
||||
cout << "Write: " << base64::encode(error) << endl;
|
||||
#endif
|
||||
#if 0
|
||||
std::array<uint8_t, 32> private_key, public_key;
|
||||
|
||||
@ -84,7 +87,6 @@ int main(int ac, char** av){
|
||||
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
srand(system_clock::now().time_since_epoch().count());
|
||||
cout << "Generating new license" << endl;
|
||||
@ -121,29 +123,56 @@ int main(int ac, char** av){
|
||||
auto data = make_shared<LicenseRequestData>();
|
||||
data->license = license;
|
||||
data->info = make_shared<ServerInfo>();
|
||||
while(true) {
|
||||
LicenceRequest request(data, serv_addr);
|
||||
try {
|
||||
cout << "Requesting license" << endl;
|
||||
auto info = request.requestInfo().waitAndGet(nullptr);
|
||||
if(!info) {
|
||||
cout << "Invalid result! Error: " << (request.exception() ? "yes => " + string(request.exception()->what()) : "no") << endl;
|
||||
throw *request.exception();
|
||||
}
|
||||
cout << "Got result!" << endl;
|
||||
cout << "Valid: " << info->license_valid << endl;
|
||||
if(info->license) {
|
||||
cout << "License:" << endl;
|
||||
cout << " Type: " << info->license->type << endl;
|
||||
cout << " User name: " << info->license->username << endl;
|
||||
cout << " First name: " << info->license->first_name << endl;
|
||||
cout << " Last name: " << info->license->last_name << endl;
|
||||
cout << " EMail: " << info->license->email << endl;
|
||||
} else cout << "License: none";
|
||||
} catch (const std::exception& ex){
|
||||
cerr << "Could not load info after throwing: " << endl << ex.what() << endl;
|
||||
}
|
||||
}
|
||||
|
||||
LicenceRequest request(data, serv_addr);
|
||||
try {
|
||||
cout << "Requesting license" << endl;
|
||||
auto info = request.requestInfo().waitAndGet(nullptr);
|
||||
if(!info) {
|
||||
cout << "Invalid result! Error: " << (request.exception() ? "yes => " + string(request.exception()->what()) : "no") << endl;
|
||||
throw *request.exception();
|
||||
}
|
||||
cout << "Got result!" << endl;
|
||||
cout << "Valid: " << info->license_valid << endl;
|
||||
if(info->license) {
|
||||
cout << "License:" << endl;
|
||||
cout << " Type: " << info->license->type << endl;
|
||||
cout << " User name: " << info->license->username << endl;
|
||||
cout << " First name: " << info->license->first_name << endl;
|
||||
cout << " Last name: " << info->license->last_name << endl;
|
||||
cout << " EMail: " << info->license->email << endl;
|
||||
} else cout << "License: none";
|
||||
} catch (const std::exception& ex){
|
||||
cerr << "Could not load info after throwing: " << endl << ex.what() << endl;
|
||||
}
|
||||
#endif
|
||||
#if 1
|
||||
sockaddr_in serv_addr{};
|
||||
serv_addr.sin_family = AF_INET;
|
||||
|
||||
serv_addr.sin_addr.s_addr = ((in_addr*) gethostbyname("localhost")->h_addr)->s_addr;
|
||||
serv_addr.sin_port = htons(27786);
|
||||
|
||||
client::LicenseServerClient client{serv_addr, 3};
|
||||
client.callback_connected = [&]{
|
||||
std::cout << "Connected" << std::endl;
|
||||
client.disconnect("client closed", std::chrono::system_clock::now() + std::chrono::seconds{5});
|
||||
};
|
||||
client.callback_message = [&](auto type, const void* buffer, size_t length) {
|
||||
std::cout << "Received an message" << std::endl;
|
||||
};
|
||||
client.callback_disconnected = [&](bool expected, const std::string& reason) {
|
||||
std::cout << "Received disconnect (expected: " << expected << "): " << reason << std::endl;
|
||||
};
|
||||
|
||||
if(!client.start_connection(error)) {
|
||||
std::cout << "Failed to start connection" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds{2});
|
||||
client.disconnect("client closed", std::chrono::system_clock::now() + std::chrono::seconds{5});
|
||||
std::cout << "Disconnect result: " << client.await_disconnect() << std::endl;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <event2/thread.h>
|
||||
#include <misc/base64.h>
|
||||
|
||||
#include "manager/ServerConnection.h"
|
||||
|
||||
@ -63,16 +64,18 @@ class CLIParser {
|
||||
std::vector<std::string> tokens;
|
||||
};
|
||||
|
||||
#define REQ_CMD(variable, message, ...) \
|
||||
if(!options.option_exists({__VA_ARGS__}, false)) { \
|
||||
cerr << message << endl; \
|
||||
return 1; \
|
||||
} \
|
||||
auto variable = url_decode(options.get_option({__VA_ARGS__})) \
|
||||
#define _str(x) #x
|
||||
|
||||
#define REQ_CMD(variable, message, skey, lkey, ...) \
|
||||
if(!options.option_exists({skey, lkey, #__VA_ARGS__}, false)) { \
|
||||
cerr << message << " (" << _str(skey) << " or " << _str(lkey) << ")" << endl; \
|
||||
return 1; \
|
||||
} \
|
||||
auto variable = url_decode(options.get_option({skey, lkey, #__VA_ARGS__})) \
|
||||
|
||||
#define NO_OPEN_SSL
|
||||
#include <misc/digest.h>
|
||||
#include <misc/base64.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
CLIParser options(argc, argv);
|
||||
|
||||
@ -88,7 +91,9 @@ int main(int argc, char **argv) {
|
||||
REQ_CMD(auth_pass, "missing authentication user", "-ap", "--auth-pass");
|
||||
|
||||
REQ_CMD(server_host, "missing server host", "-h", "--server-host");
|
||||
REQ_CMD(server_port, "missing server port", "-p", "--server-port");
|
||||
REQ_CMD(server_port, "missing server port", "-p", "--server-port");
|
||||
|
||||
REQ_CMD(old_key, "missing old license key", "-ol", "--old-license");
|
||||
|
||||
auto state = evthread_use_pthreads();
|
||||
if(state != 0) {
|
||||
@ -96,6 +101,7 @@ int main(int argc, char **argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string error{};
|
||||
ServerConnection connection;
|
||||
connection.verbose = false;
|
||||
try {
|
||||
@ -112,15 +118,24 @@ int main(int argc, char **argv) {
|
||||
{
|
||||
auto future = connection.login(auth_user, auth_pass);
|
||||
if(!future.waitAndGet(false, system_clock::now() + seconds(5))) {
|
||||
cerr << "failed to athentificate (" << future.errorMegssage() << ")" << endl;
|
||||
cerr << "failed to authenticate (" << future.errorMegssage() << ")" << endl;
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<License> old_license{nullptr};
|
||||
if(!old_key.empty() && old_key != "none") {
|
||||
old_license = license::readLocalLicence(old_key, error);
|
||||
if(!old_license) {
|
||||
cerr << "failed to decode old license: " << error << endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
system_clock::time_point timestamp_begin = system_clock::time_point() + seconds(stoll(begin));
|
||||
system_clock::time_point timestamp_end = system_clock::time_point() + seconds(stoll(end));
|
||||
auto future = connection.registerLicense(first_name, last_name, user, email, (LicenseType) stoll(license_type), timestamp_end, timestamp_begin);
|
||||
auto future = connection.registerLicense(first_name, last_name, user, email, (LicenseType) stoll(license_type), timestamp_end, timestamp_begin, old_license ? old_license->key() : "none");
|
||||
auto result = future.waitAndGet({nullptr, nullptr}, system_clock::now() + seconds(5));
|
||||
if(!result.first || !result.second) {
|
||||
cerr << "failed to create license! (" << future.errorMegssage() << ")" << endl;
|
||||
|
@ -34,7 +34,7 @@ using namespace license;
|
||||
*/
|
||||
bool handle_command(string& line);
|
||||
|
||||
shared_ptr<server::LicenseManager> license_manager;
|
||||
shared_ptr<server::database::DatabaseHandler> license_manager;
|
||||
shared_ptr<stats::StatisticManager> statistic_manager;
|
||||
shared_ptr<ts::ssl::SSLManager> ssl_manager;
|
||||
shared_ptr<license::web::WebStatistics> web_server;
|
||||
@ -102,14 +102,16 @@ int main(int argc, char** argv) {
|
||||
|
||||
evthread_use_pthreads();
|
||||
srand(system_clock::now().time_since_epoch().count());
|
||||
terminal::install();
|
||||
if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
|
||||
|
||||
//terminal::install();
|
||||
//if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
|
||||
|
||||
auto config = std::make_shared<logger::LoggerConfig>();
|
||||
config->vs_group_size = 0;
|
||||
config->logfileLevel = spdlog::level::trace;
|
||||
config->terminalLevel = spdlog::level::trace;
|
||||
config->logPath = "logs/log_${time}(%Y-%m-%d_%H:%M:%S).log";
|
||||
config->sync = !terminal::active();
|
||||
logger::setup(config);
|
||||
|
||||
string error;
|
||||
@ -138,7 +140,7 @@ int main(int argc, char** argv) {
|
||||
}, (void*) nullptr) << endl;
|
||||
#endif
|
||||
|
||||
license_manager = make_shared<server::LicenseManager>(database);
|
||||
license_manager = make_shared<server::database::DatabaseHandler>(database);
|
||||
if(!license_manager->setup(error)) {
|
||||
logError(LOG_GENERAL, "Could not start license manager! (" +error + ")");
|
||||
return 0;
|
||||
@ -147,6 +149,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
statistic_manager = make_shared<stats::StatisticManager>(license_manager);
|
||||
|
||||
#if false
|
||||
/*
|
||||
{
|
||||
auto _now = system_clock::now();
|
||||
@ -163,6 +166,9 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
return 0;
|
||||
*/
|
||||
#endif
|
||||
|
||||
#if false
|
||||
/*
|
||||
{
|
||||
ofstream _file_out("version_history.txt");
|
||||
@ -229,6 +235,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
return 0;
|
||||
*/
|
||||
#endif
|
||||
|
||||
ssl_manager = make_shared<ts::ssl::SSLManager>();
|
||||
{
|
||||
@ -268,17 +275,20 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
while(db_connected && web_server->running() && license_server->isRunning()) {
|
||||
if(!terminal::instance()) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds{10});
|
||||
continue;
|
||||
}
|
||||
auto line = terminal::instance()->readLine("§a> §f");
|
||||
if(line.empty()){
|
||||
usleep(500);
|
||||
continue;
|
||||
}
|
||||
if(!handle_command(line)) {
|
||||
terminal::instance()->writeMessage("§aStopping server...");
|
||||
if(!handle_command(line))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
terminal::instance()->writeMessage("§aStopping server...");
|
||||
web_server->stop();
|
||||
license_server->stop();
|
||||
if(database) database->disconnect();
|
||||
|
@ -50,8 +50,8 @@ threads::Future<bool> ServerConnection::connect(const std::string &host, uint16_
|
||||
if(this->network.file_descriptor < 0) CERR("Socket setup failed");
|
||||
if(::connect(this->network.file_descriptor, reinterpret_cast<const sockaddr *>(&this->network.address_remote), sizeof(this->network.address_remote)) < 0) CERR("connect() failed (" + to_string(errno) + " | " + strerror(errno) + ")");
|
||||
int enabled = 1, disabled = 0;
|
||||
if(setsockopt(this->network.file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0) CERR("could not set reuse addr");
|
||||
if(setsockopt(this->network.file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0) CERR("could not set no push");
|
||||
if(setsockopt(this->network.file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0); // CERR("could not set reuse addr");
|
||||
if(setsockopt(this->network.file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0); // CERR("could not set no push");
|
||||
|
||||
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, ServerConnection::handleEventRead, this);
|
||||
@ -64,7 +64,7 @@ threads::Future<bool> ServerConnection::connect(const std::string &host, uint16_
|
||||
cout << "ev ended!" << endl;
|
||||
}};
|
||||
this->network.state = ConnectionState::CONNECTED;
|
||||
this->protocol.state = protocol::HANDSCAKE;
|
||||
this->protocol.state = protocol::HANDSCHAKE;
|
||||
this->protocol.ping_thread = thread([&]{
|
||||
while(true) {
|
||||
{
|
||||
@ -83,7 +83,7 @@ threads::Future<bool> ServerConnection::connect(const std::string &host, uint16_
|
||||
handshakeBuffer[0] = 0xC0;
|
||||
handshakeBuffer[1] = 0xFF;
|
||||
handshakeBuffer[2] = 0xEE;
|
||||
handshakeBuffer[3] = LICENSE_PROT_VERSION;
|
||||
handshakeBuffer[3] = 2;
|
||||
handshakeBuffer[4] = 1; //Im a manager
|
||||
this->sendPacket(protocol::packet{protocol::PACKET_CLIENT_HANDSHAKE, string((const char*) handshakeBuffer, 5)}); //Initialise packet
|
||||
}).detach();
|
||||
@ -156,8 +156,8 @@ void ServerConnection::handleEventRead(int fd, short, void* _connection) {
|
||||
auto connection = (ServerConnection*) _connection;
|
||||
|
||||
char buffer[1024];
|
||||
auto read = recv(fd, buffer, 1024, SOCK_NONBLOCK);
|
||||
if(read < 0) {
|
||||
auto read = recv(fd, buffer, 1024, MSG_DONTWAIT);
|
||||
if(read <= 0) {
|
||||
if(connection->verbose)
|
||||
cout << "Invalid read: " << strerror(errno) << endl;
|
||||
connection->local_disconnect_message = "invalid read";
|
||||
|
@ -25,94 +25,93 @@ do { \
|
||||
} while(0)
|
||||
|
||||
|
||||
namespace license {
|
||||
namespace manager {
|
||||
enum ConnectionState {
|
||||
UNCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTING
|
||||
};
|
||||
class ServerConnection {
|
||||
public:
|
||||
ServerConnection();
|
||||
~ServerConnection();
|
||||
namespace license::manager {
|
||||
enum ConnectionState {
|
||||
UNCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTING
|
||||
};
|
||||
class ServerConnection {
|
||||
public:
|
||||
ServerConnection();
|
||||
~ServerConnection();
|
||||
|
||||
threads::Future<bool> connect(const std::string& host, uint16_t port);
|
||||
void disconnect(const std::string&);
|
||||
threads::Future<bool> connect(const std::string& host, uint16_t port);
|
||||
void disconnect(const std::string&);
|
||||
|
||||
void ping();
|
||||
void ping();
|
||||
|
||||
threads::Future<bool> login(const std::string&, const std::string&);
|
||||
threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>> registerLicense(
|
||||
const std::string& first_name,
|
||||
const std::string& last_name,
|
||||
const std::string& username,
|
||||
const std::string& email,
|
||||
license::LicenseType type,
|
||||
const std::chrono::system_clock::time_point& end,
|
||||
const std::chrono::system_clock::time_point& begin = std::chrono::system_clock::now()
|
||||
);
|
||||
threads::Future<bool> login(const std::string&, const std::string&);
|
||||
threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>> registerLicense(
|
||||
const std::string& first_name,
|
||||
const std::string& last_name,
|
||||
const std::string& username,
|
||||
const std::string& email,
|
||||
license::LicenseType type,
|
||||
const std::chrono::system_clock::time_point& end,
|
||||
const std::chrono::system_clock::time_point& begin,
|
||||
const std::string& old_license
|
||||
);
|
||||
|
||||
threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>> list(int offset, int count);
|
||||
threads::Future<bool> deleteLicense(const std::string& key, bool full = false);
|
||||
threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>> list(int offset, int count);
|
||||
threads::Future<bool> deleteLicense(const std::string& key, bool full = false);
|
||||
|
||||
bool verbose = true;
|
||||
private:
|
||||
struct {
|
||||
ConnectionState state = ConnectionState::UNCONNECTED;
|
||||
sockaddr_in address_remote;
|
||||
int file_descriptor = 0;
|
||||
bool verbose = true;
|
||||
private:
|
||||
struct {
|
||||
ConnectionState state = ConnectionState::UNCONNECTED;
|
||||
sockaddr_in address_remote;
|
||||
int file_descriptor = 0;
|
||||
|
||||
event* event_read = nullptr;
|
||||
event* event_write = nullptr;
|
||||
struct event_base* event_base = nullptr;
|
||||
std::thread event_base_dispatch;
|
||||
event* event_read = nullptr;
|
||||
event* event_write = nullptr;
|
||||
struct event_base* event_base = nullptr;
|
||||
std::thread event_base_dispatch;
|
||||
|
||||
threads::Thread* flush_thread = nullptr;
|
||||
threads::Thread* flush_thread = nullptr;
|
||||
|
||||
threads::Mutex queue_lock;
|
||||
std::deque<std::string> queue_write;
|
||||
threads::Mutex queue_lock;
|
||||
std::deque<std::string> queue_write;
|
||||
|
||||
std::unique_ptr<protocol::packet> current_packet;
|
||||
std::unique_ptr<protocol::packet> current_packet;
|
||||
|
||||
|
||||
std::string overhead;
|
||||
} network;
|
||||
std::string overhead;
|
||||
} network;
|
||||
|
||||
|
||||
struct {
|
||||
protocol::RequestState state;
|
||||
std::string crypt_key = "";
|
||||
struct {
|
||||
protocol::RequestState state;
|
||||
std::string crypt_key = "";
|
||||
|
||||
std::mutex ping_lock;
|
||||
std::condition_variable ping_notify;
|
||||
std::thread ping_thread;
|
||||
} protocol;
|
||||
std::mutex ping_lock;
|
||||
std::condition_variable ping_notify;
|
||||
std::thread ping_thread;
|
||||
} protocol;
|
||||
|
||||
struct {
|
||||
std::unique_ptr<threads::Future<bool>> future_connect;
|
||||
std::unique_ptr<threads::Future<bool>> future_login;
|
||||
std::unique_ptr<threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>>> future_register;
|
||||
std::unique_ptr<threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>>> future_list;
|
||||
std::unique_ptr<threads::Future<bool>> future_delete;
|
||||
} listener;
|
||||
struct {
|
||||
std::unique_ptr<threads::Future<bool>> future_connect;
|
||||
std::unique_ptr<threads::Future<bool>> future_login;
|
||||
std::unique_ptr<threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>>> future_register;
|
||||
std::unique_ptr<threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>>> future_list;
|
||||
std::unique_ptr<threads::Future<bool>> future_delete;
|
||||
} listener;
|
||||
|
||||
std::string local_disconnect_message;
|
||||
std::string local_disconnect_message;
|
||||
|
||||
static void handleEventRead(int, short, void*);
|
||||
static void handleEventWrite(int, short, void*);
|
||||
static void handleEventRead(int, short, void*);
|
||||
static void handleEventWrite(int, short, void*);
|
||||
|
||||
void closeConnection();
|
||||
void sendPacket(const protocol::packet&);
|
||||
void handleMessage(const std::string&);
|
||||
void closeConnection();
|
||||
void sendPacket(const protocol::packet&);
|
||||
void handleMessage(const std::string&);
|
||||
|
||||
void handlePacketDisconnect(const std::string&);
|
||||
void handlePacketHandshake(const std::string&);
|
||||
void handlePacketAuthResponse(const std::string&);
|
||||
void handlePacketCreateResponse(const std::string&);
|
||||
void handlePacketListResponse(const std::string&);
|
||||
void handlePacketDeleteResponse(const std::string&);
|
||||
};
|
||||
}
|
||||
void handlePacketDisconnect(const std::string&);
|
||||
void handlePacketHandshake(const std::string&);
|
||||
void handlePacketAuthResponse(const std::string&);
|
||||
void handlePacketCreateResponse(const std::string&);
|
||||
void handlePacketListResponse(const std::string&);
|
||||
void handlePacketDeleteResponse(const std::string&);
|
||||
};
|
||||
}
|
@ -35,7 +35,8 @@ threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<lic
|
||||
const std::string &email,
|
||||
license::LicenseType type,
|
||||
const std::chrono::system_clock::time_point& end,
|
||||
const std::chrono::system_clock::time_point& start
|
||||
const std::chrono::system_clock::time_point& start,
|
||||
const std::string& old_license
|
||||
) {
|
||||
|
||||
this->listener.future_register = std::make_unique<threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>>>();
|
||||
@ -48,6 +49,9 @@ threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<lic
|
||||
request.set_type(type);
|
||||
request.set_begin(duration_cast<milliseconds>(start.time_since_epoch()).count());
|
||||
request.set_end(duration_cast<milliseconds>(end.time_since_epoch()).count());
|
||||
if(!old_license.empty() && old_license != "none")
|
||||
request.set_old_key(old_license);
|
||||
|
||||
this->sendPacket({protocol::PACKET_CLIENT_LICENSE_CREATE_REQUEST, request});
|
||||
|
||||
if(this->network.state != ConnectionState::CONNECTED)
|
||||
|
@ -84,11 +84,11 @@ void ServerConnection::handlePacketDisconnect(const std::string& message) {
|
||||
}
|
||||
|
||||
void ServerConnection::handlePacketHandshake(const std::string& data) {
|
||||
if(this->protocol.state != protocol::HANDSCAKE) LERROR("Protocol state mismatch");
|
||||
if(this->protocol.state != protocol::HANDSCHAKE) LERROR("Protocol state mismatch");
|
||||
if(data.length() < 3) LERROR("Invalid packet size");
|
||||
|
||||
if((uint8_t) data[0] != 0xAF || (uint8_t) data[1] != 0xFE) LERROR("Invalid handshake");
|
||||
if((uint8_t) data[2] != LICENSE_PROT_VERSION) LERROR("Invalid license protocol version. Please update TeaSpeak!");
|
||||
if((uint8_t) data[2] != 2) LERROR("Invalid license protocol version. Please update this client!");
|
||||
|
||||
auto key_length = be2le16(data.data(), 3);
|
||||
if(data.length() < key_length + 3) LERROR("Invalid packet size");
|
||||
|
@ -32,6 +32,9 @@ message LicenseCreateRequest {
|
||||
required int64 type = 5;
|
||||
required int64 begin = 6;
|
||||
required int64 end = 7;
|
||||
|
||||
/* if set the license will upgrade automatically */
|
||||
optional bytes old_key = 8;
|
||||
}
|
||||
message LicenseCreateResponse {
|
||||
oneof result {
|
||||
|
@ -40,13 +40,16 @@ message ServerValidation {
|
||||
required bool licensed = 1;
|
||||
required bool license_info = 2;
|
||||
optional bytes license = 3;
|
||||
optional ServerInfo info = 4; //Change somewhen to required but its currently for lagacy support
|
||||
optional ServerInfo info = 4; //Change somewhere to required but its currently for legacy support
|
||||
}
|
||||
|
||||
message LicenseResponse {
|
||||
required bool valid = 1; //If set the license is valid. The blacklist field could still be active. If the flag is false then the blacklist will container the message
|
||||
required bool valid = 1;
|
||||
optional string invalid_reason = 5; /* in protocol version 2 the blacklist.reason field will contain the message */
|
||||
|
||||
required Blacklist blacklist = 2;
|
||||
optional LicenseInfo license_info = 3; //Only availible when ServerValidation::license_info = true
|
||||
optional LicenseInfo license_info = 3; //Only available when ServerValidation::license_info = true
|
||||
optional bool update_pending = 4; /* if an update is pending */
|
||||
}
|
||||
|
||||
message PropertyUpdateRequest {
|
||||
@ -64,6 +67,15 @@ message PropertyUpdateRequest {
|
||||
optional bytes web_cert_revision = 12;
|
||||
}
|
||||
|
||||
message RequestLicenseUpgrade { }
|
||||
message LicenseUpgradeResponse {
|
||||
required bool valid = 1;
|
||||
oneof result {
|
||||
bytes license_key = 2;
|
||||
string error_message = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message WebCertificate {
|
||||
required bytes revision = 1;
|
||||
required string key = 2;
|
||||
|
@ -1,16 +1,17 @@
|
||||
#include "LicenseManager.h"
|
||||
#include "DatabaseHandler.h"
|
||||
#include <misc/base64.h>
|
||||
#include <log/LogUtils.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace license;
|
||||
using namespace license::server;
|
||||
using namespace license::server::database;
|
||||
using namespace sql;
|
||||
|
||||
LicenseManager::LicenseManager(sql::SqlManager* database) : database(database){
|
||||
DatabaseHandler::DatabaseHandler(sql::SqlManager* database) : database(database){
|
||||
this->id_cache = make_shared<KeyIdCache>(this);
|
||||
}
|
||||
LicenseManager::~LicenseManager() { }
|
||||
DatabaseHandler::~DatabaseHandler() { }
|
||||
|
||||
/*
|
||||
LicenseType type;
|
||||
@ -40,7 +41,7 @@ res = command(this->database, cmd).execute(); \
|
||||
version = ver; \
|
||||
sql::command(this->database, "UPDATE `general` SET `value` = :version WHERE `key` = 'version'", variable{":version", version}).execute();
|
||||
|
||||
bool LicenseManager::setup(std::string& error) {
|
||||
bool DatabaseHandler::setup(std::string& error) {
|
||||
result res;
|
||||
int version = -1;
|
||||
sql::command(this->database, "SELECT `value` FROM `general` WHERE `key` = 'version'").query([](int* version, int lnegth, string* values, string* names) {
|
||||
@ -106,20 +107,33 @@ bool LicenseManager::setup(std::string& error) {
|
||||
|
||||
case 4:
|
||||
CTBL("CREATE TABLE IF NOT EXISTS `users` (`username` VARCHAR(64) NOT NULL PRIMARY KEY, `password_hash` VARCHAR(128), `status` INT, `owner` VARCHAR(64))");
|
||||
SET_VERSION(5);
|
||||
|
||||
case 5:
|
||||
CTBL("CREATE TABLE `license_upgrades` (`upgrade_id` INT PRIMARY KEY NOT NULL, `old_key_id` INT, `new_key_id` INT, `timestamp_begin` BIGINT, `timestamp_end` BIGINT, `valid` INT, `use_count` INT, `license` BLOB);");
|
||||
CTBL("ALTER TABLE `license_upgrades` ADD INDEX(`old_key_id`)");
|
||||
CTBL("ALTER TABLE `license` ADD INDEX(`keyId`);");
|
||||
CTBL("ALTER TABLE `license` ADD COLUMN `upgrade_id` INT DEFAULT 0;");
|
||||
SET_VERSION(6);
|
||||
|
||||
case 6:
|
||||
CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` INT, `succeeded` TINYINT);");
|
||||
CIDX("CREATE INDEX `upgrade_id_timestamp` ON `license_upgrade_log` (`upgrade_id`, `timestamp`)");
|
||||
SET_VERSION(7);
|
||||
|
||||
default:;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseManager::registerLicense(const std::string& key, const shared_ptr<LicenseInfo>& info, const std::string& issuer) {
|
||||
bool DatabaseHandler::register_license(const std::string& key, const shared_ptr<LicenseInfo>& info, const std::string& issuer) {
|
||||
result res;
|
||||
res = command(this->database, "INSERT INTO `license` (`key`, `type`, `deleted`, `issuer`) VALUES (:key, :type, :deleted, :issuer)", variable{":key", key}, variable{":type", (uint32_t) info->type}, variable{":deleted", false}, variable{":issuer", issuer}).execute();
|
||||
if(!res) {
|
||||
logError(LOG_GENERAL, "Could not register new license (" + res.fmtStr() + ")");
|
||||
return false;
|
||||
}
|
||||
auto keyId = this->id_cache->getKeyId(key);
|
||||
auto keyId = this->id_cache->get_key_id_from_key(key);
|
||||
if(keyId == 0) return false;
|
||||
|
||||
res = command(this->database, "INSERT INTO `license_info` (`keyId`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`) VALUES (:key, :username, :first_name, :last_name, :email, :begin, :end, :generated)",
|
||||
@ -139,9 +153,9 @@ bool LicenseManager::registerLicense(const std::string& key, const shared_ptr<Li
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseManager::deleteLicense(const std::string& key, bool full) {
|
||||
bool DatabaseHandler::delete_license(const std::string& key, bool full) {
|
||||
if(full) {
|
||||
auto keyId = this->id_cache->getKeyId(key);
|
||||
auto keyId = this->id_cache->get_key_id_from_key(key);
|
||||
if(keyId == 0) return false; //Never exists
|
||||
|
||||
auto res = command(this->database, "DELETE FROM `license` WHERE `key` = :key", variable{":key", key}).execute();
|
||||
@ -156,7 +170,7 @@ bool LicenseManager::deleteLicense(const std::string& key, bool full) {
|
||||
}
|
||||
}
|
||||
|
||||
bool LicenseManager::validLicenseKey(const std::string& key) {
|
||||
bool DatabaseHandler::validLicenseKey(const std::string& key) {
|
||||
bool valid = false;
|
||||
auto res = command(this->database, "SELECT * FROM `license` WHERE `key` = :key AND `deleted` = :false LIMIT 1", variable{":false", false}, variable{":key", key}).query([](bool* flag, int, char**, char**) {
|
||||
*flag = true;
|
||||
@ -166,10 +180,10 @@ bool LicenseManager::validLicenseKey(const std::string& key) {
|
||||
return !!res && valid;
|
||||
}
|
||||
|
||||
inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlManager* mgr, std::string key, int offset, int length) {
|
||||
inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlManager* mgr, const std::string& key, int offset, int length) {
|
||||
std::map<std::string, std::shared_ptr<LicenseInfo>> result;
|
||||
|
||||
auto query = string() + "SELECT `key`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`, `deleted` FROM `license_info` INNER JOIN `license` ON `license_info`.`keyId` = `license`.`keyId`";
|
||||
auto query = string() + "SELECT `key`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`, `deleted`, `upgrade_id` FROM `license_info` INNER JOIN `license` ON `license_info`.`keyId` = `license`.`keyId`";
|
||||
if(!key.empty())
|
||||
query += "WHERE `key` = :key";
|
||||
else
|
||||
@ -203,6 +217,8 @@ inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlMana
|
||||
info->creation = system_clock::time_point() + milliseconds(stoll(values[index]));
|
||||
else if(names[index] == "deleted")
|
||||
info->deleted = values[index] == "1" || values[index] == "true";
|
||||
else if(names[index] == "upgrade_id")
|
||||
info->upgrade_id = std::stol(values[index]);
|
||||
else
|
||||
logError(LOG_GENERAL, "Unknown field {}", names[index]);
|
||||
} catch (std::exception& ex) {
|
||||
@ -218,20 +234,20 @@ inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlMana
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<LicenseInfo> LicenseManager::licenseInfo(const std::string& key) {
|
||||
std::shared_ptr<LicenseInfo> DatabaseHandler::query_license_info(const std::string& key) {
|
||||
auto result = query_license(this->database, key, 0, 0);
|
||||
if(result.empty()) return nullptr;
|
||||
return result.begin()->second;
|
||||
}
|
||||
|
||||
std::map<std::string, std::shared_ptr<LicenseInfo>> LicenseManager::listLicenses(int offset, int limit) {
|
||||
std::map<std::string, std::shared_ptr<LicenseInfo>> DatabaseHandler::list_licenses(int offset, int limit) {
|
||||
return query_license(this->database, "", offset, limit);
|
||||
}
|
||||
|
||||
bool LicenseManager::logRequest(const std::string& key, const std::string& unique_id, const std::string& ip, const std::string& version, int state) {
|
||||
bool DatabaseHandler::logRequest(const std::string& key, const std::string& unique_id, const std::string& ip, const std::string& version, int state) {
|
||||
result res;
|
||||
|
||||
auto keyId = this->id_cache->getKeyId(key);
|
||||
auto keyId = this->id_cache->get_key_id_from_key(key);
|
||||
if(keyId == 0) {
|
||||
logError(LOG_GENERAL, "Failed to log license request (could not resolve key id)");
|
||||
return false;
|
||||
@ -264,11 +280,11 @@ bool LicenseManager::logRequest(const std::string& key, const std::string& uniqu
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseManager::logStatistic(const std::string &key, const std::string& unique_id, const std::string &ip,
|
||||
const ts::proto::license::PropertyUpdateRequest &data) {
|
||||
bool DatabaseHandler::logStatistic(const std::string &key, const std::string& unique_id, const std::string &ip,
|
||||
const ts::proto::license::PropertyUpdateRequest &data) {
|
||||
result res;
|
||||
|
||||
auto keyId = this->id_cache->getKeyId(key);
|
||||
auto keyId = this->id_cache->get_key_id_from_key(key);
|
||||
if(keyId == 0) return false;
|
||||
|
||||
auto time = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
@ -305,7 +321,7 @@ bool LicenseManager::logStatistic(const std::string &key, const std::string& uni
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<LicenseManager::UserHistory> LicenseManager::list_statistics_user(const system_clock::time_point &begin, const system_clock::time_point &end, const milliseconds &interval) {
|
||||
std::shared_ptr<DatabaseHandler::UserHistory> DatabaseHandler::list_statistics_user(const system_clock::time_point &begin, const system_clock::time_point &end, const milliseconds &interval) {
|
||||
auto timeout = hours(2) + minutes(10); //TODO timeout configurable?
|
||||
|
||||
/* initialize the result */
|
||||
@ -328,11 +344,11 @@ std::shared_ptr<LicenseManager::UserHistory> LicenseManager::list_statistics_use
|
||||
auto info = &_result->history[0];
|
||||
|
||||
/* temp db variables */
|
||||
map<std::string, LicenseManager::DatabaseUserStatistics> server_statistics;
|
||||
map<std::string, DatabaseHandler::DatabaseUserStatistics> server_statistics;
|
||||
|
||||
bool have_key, have_uid;
|
||||
LicenseManager::DatabaseUserStatistics temp_stats; /* temp struct for stats parsing */
|
||||
LicenseManager::DatabaseUserStatistics* stats_ptr; /* pointer to the target stats */
|
||||
DatabaseHandler::DatabaseUserStatistics temp_stats; /* temp struct for stats parsing */
|
||||
DatabaseHandler::DatabaseUserStatistics* stats_ptr; /* pointer to the target stats */
|
||||
std::chrono::system_clock::time_point current_timestamp = begin + interval; /* upper limit of the current interval */
|
||||
|
||||
auto state = command(this->database, "SELECT * FROM `history_online` WHERE `timestamp` >= :timestamp_start AND `timestamp` <= :timestamp_end ORDER BY `timestamp` ASC",
|
||||
@ -440,8 +456,8 @@ std::shared_ptr<LicenseManager::UserHistory> LicenseManager::list_statistics_use
|
||||
return result;
|
||||
}
|
||||
|
||||
std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseManager::list_statistics_version(const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point &end, const std::chrono::milliseconds &interval) {
|
||||
map<std::string, deque<unique_ptr<LicenseManager::GlobalVersionsStatistic>>> server_statistics;
|
||||
std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> DatabaseHandler::list_statistics_version(const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point &end, const std::chrono::milliseconds &interval) {
|
||||
map<std::string, deque<unique_ptr<DatabaseHandler::GlobalVersionsStatistic>>> server_statistics;
|
||||
auto timeout = hours(2) + minutes(10); //TODO timeout configurable?
|
||||
|
||||
auto state = command(this->database, "SELECT * FROM `history_version` WHERE `timestamp` >= :timestamp_start AND `timestamp` <= :timestamp_end ORDER BY `timestamp` ASC",
|
||||
@ -450,7 +466,7 @@ std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseMana
|
||||
).query([&server_statistics](int columns, std::string* values, std::string* names){
|
||||
size_t key_id = 0;
|
||||
std::string unique_id;
|
||||
auto info = make_unique<LicenseManager::GlobalVersionsStatistic>();
|
||||
auto info = make_unique<DatabaseHandler::GlobalVersionsStatistic>();
|
||||
for(int index = 0; index < columns; index++) {
|
||||
try {
|
||||
if(names[index] == "keyId")
|
||||
@ -477,10 +493,10 @@ std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseMana
|
||||
return {};
|
||||
}
|
||||
|
||||
std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> result;
|
||||
std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> result;
|
||||
system_clock::time_point current_timestamp = begin;
|
||||
while(current_timestamp <= end) {
|
||||
auto info = make_unique<LicenseManager::GlobalVersionsStatistic>();
|
||||
auto info = make_unique<DatabaseHandler::GlobalVersionsStatistic>();
|
||||
info->timestamp = current_timestamp;
|
||||
|
||||
for(auto& server : server_statistics) {
|
||||
@ -511,4 +527,87 @@ std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseMana
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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 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},
|
||||
variable{":old_key_id", old_key_id},
|
||||
variable{":new_key_id", new_key_id},
|
||||
variable{":timestamp_begin", std::chrono::duration_cast<std::chrono::milliseconds>(begin_timestamp.time_since_epoch()).count()},
|
||||
variable{":timestamp_end", std::chrono::duration_cast<std::chrono::milliseconds>(end_timestamp.time_since_epoch()).count()},
|
||||
variable{":license", base64::decode(license_key)}).execute();
|
||||
if(!sql_result) {
|
||||
logError(LOG_GENERAL, "Failed to insert license upgrade: {}", sql_result.fmtStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
sql_result = sql::command(this->sql(), "UPDATE `license` SET `upgrade_id` = :upgrade_id WHERE `keyId` = :key_id",
|
||||
variable{":upgrade_id", upgrade_id},
|
||||
variable{":key_id", old_key_id}).execute();
|
||||
if(!sql_result) {
|
||||
logError(LOG_GENERAL, "Failed to set license upgrade: {}", sql_result.fmtStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<LicenseUpgrade> DatabaseHandler::query_license_upgrade(upgrade_id_t id) {
|
||||
std::unique_ptr<LicenseUpgrade> result{};
|
||||
auto sql_result = sql::command(this->sql(), "SELECT `upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license` FROM `license_upgrades` WHERE `upgrade_id` = :upgrade_id LIMIT 1",
|
||||
variable{":upgrade_id", id}).query([&](int length, std::string* values, std::string* names) {
|
||||
result = std::make_unique<LicenseUpgrade>();
|
||||
for(size_t index = 0; index < length; index++) {
|
||||
try {
|
||||
if(names[index] == "upgrade_id")
|
||||
result->upgrade_id = std::stoull(values[index]);
|
||||
else if(names[index] == "old_key_id")
|
||||
result->old_license_key_id = std::stoull(values[index]);
|
||||
else if(names[index] == "new_key_id")
|
||||
result->new_license_key_id = std::stoull(values[index]);
|
||||
else if(names[index] == "timestamp_begin")
|
||||
result->begin_timestamp = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoull(values[index])};
|
||||
else if(names[index] == "timestamp_end")
|
||||
result->end_timestamp = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoull(values[index])};
|
||||
else if(names[index] == "use_count")
|
||||
result->update_count = std::stoull(values[index]);
|
||||
else if(names[index] == "valid")
|
||||
result->valid = std::stoull(values[index]) > 0;
|
||||
else if(names[index] == "license")
|
||||
result->license_key = base64::encode(values[index]);
|
||||
} catch(std::exception& ex) {
|
||||
result = nullptr;
|
||||
logWarning(LOG_GENERAL, "Failed to parse column {} for upgrade id {}. (Value: {})", names[index], id, values[index]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
if(!sql_result) {
|
||||
logWarning(LOG_GENERAL, "Failed to query license upgrade info for upgrade {}: {}", id, sql_result.fmtStr());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DatabaseHandler::log_license_upgrade_attempt(upgrade_id_t upgrade_id, bool succeeded, const std::string &unique_id, const std::string &ip) {
|
||||
auto result = sql::command(this->sql(), "INSERT INTO `license_upgrade_log` (`upgrade_id`, `timestamp`, `unique_id`, `server_ip`, `succeeded`) VALUES (:upgrade_id, :timestamp, :unique_id, :server_ip, :succeeded);",
|
||||
variable{":upgrade_id", upgrade_id},
|
||||
variable{":timestamp", std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count()},
|
||||
variable{":unique_id", unique_id},
|
||||
variable{":server_ip", ip},
|
||||
variable{":succeeded", succeeded}).execute();
|
||||
if(!result)
|
||||
logWarning(LOG_GENERAL, "Failed to insert upgrade log into database ({})", result.fmtStr());
|
||||
|
||||
if(succeeded) {
|
||||
result = sql::command(this->sql(), "UPDATE `license_upgrades` SET `use_count` = `use_count` + 1 WHERE `upgrade_id` = :upgrade_id",
|
||||
variable{":upgrade_id", upgrade_id}).execute();
|
||||
if(!result)
|
||||
logWarning(LOG_GENERAL, "Failed to increase upgrade use count MySQL ({})", result.fmtStr());
|
||||
}
|
||||
}
|
125
license/server/DatabaseHandler.h
Normal file
125
license/server/DatabaseHandler.h
Normal file
@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include <sql/SqlQuery.h>
|
||||
#include <shared/License.h>
|
||||
#include <LicenseRequest.pb.h>
|
||||
|
||||
namespace license::server::database {
|
||||
typedef size_t license_key_id_t;
|
||||
typedef size_t upgrade_id_t;
|
||||
|
||||
class DatabaseHandler;
|
||||
class KeyIdCache {
|
||||
public:
|
||||
explicit KeyIdCache(DatabaseHandler*);
|
||||
|
||||
std::string get_key_from_id(license_key_id_t keyId);
|
||||
size_t get_key_id_from_key(const std::string &key);
|
||||
|
||||
void clear_cache();
|
||||
private:
|
||||
struct CacheEntry {
|
||||
std::string key;
|
||||
size_t keyId;
|
||||
std::chrono::system_clock::time_point age;
|
||||
};
|
||||
|
||||
int insert_entry(int, std::string*, std::string*);
|
||||
|
||||
DatabaseHandler* handle;
|
||||
|
||||
std::mutex entry_lock{};
|
||||
std::deque<std::unique_ptr<CacheEntry>> entries;
|
||||
};
|
||||
|
||||
struct LicenseUpgrade {
|
||||
upgrade_id_t upgrade_id{0};
|
||||
|
||||
license_key_id_t old_license_key_id{0};
|
||||
license_key_id_t new_license_key_id{0};
|
||||
|
||||
std::chrono::system_clock::time_point begin_timestamp{};
|
||||
std::chrono::system_clock::time_point end_timestamp{};
|
||||
|
||||
bool valid{false};
|
||||
size_t update_count{0};
|
||||
|
||||
std::string license_key{};
|
||||
|
||||
[[nodiscard]] inline bool not_yet_available() const {
|
||||
return std::chrono::system_clock::now() < this->begin_timestamp && this->begin_timestamp.time_since_epoch().count() != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool is_expired() const {
|
||||
return std::chrono::system_clock::now() > this->end_timestamp && this->end_timestamp.time_since_epoch().count() != 0;
|
||||
}
|
||||
};
|
||||
|
||||
class DatabaseHandler {
|
||||
public:
|
||||
struct UserStatistics {
|
||||
uint64_t clients_online = 0;
|
||||
uint64_t web_clients_online = 0;
|
||||
uint64_t bots_online = 0;
|
||||
uint64_t queries_online = 0;
|
||||
uint64_t servers_online = 0;
|
||||
};
|
||||
|
||||
struct ExtendedUserStatistics : public UserStatistics {
|
||||
std::string ip_address;
|
||||
};
|
||||
|
||||
struct GlobalUserStatistics : public UserStatistics {
|
||||
uint64_t instance_online = 0;
|
||||
};
|
||||
|
||||
struct DatabaseUserStatistics : public UserStatistics {
|
||||
std::chrono::system_clock::time_point timestamp{};
|
||||
};
|
||||
|
||||
struct GlobalVersionsStatistic {
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
std::map<std::string, uint32_t> versions;
|
||||
};
|
||||
|
||||
struct UserHistory {
|
||||
std::chrono::system_clock::time_point begin{};
|
||||
std::chrono::system_clock::time_point end{};
|
||||
std::chrono::milliseconds interval{0};
|
||||
|
||||
size_t record_count = 0;
|
||||
GlobalUserStatistics history[0];
|
||||
};
|
||||
static_assert(sizeof(UserHistory) == 32);
|
||||
|
||||
public:
|
||||
explicit DatabaseHandler(sql::SqlManager* database);
|
||||
~DatabaseHandler();
|
||||
|
||||
bool setup(std::string&);
|
||||
|
||||
[[nodiscard]] inline std::shared_ptr<KeyIdCache> key_id_cache() { return this->id_cache; }
|
||||
|
||||
bool validLicenseKey(const std::string& /* key */);
|
||||
std::shared_ptr<LicenseInfo> query_license_info(const std::string&);
|
||||
std::map<std::string, std::shared_ptr<LicenseInfo>> list_licenses(int offset = 0, int limit = -1);
|
||||
|
||||
bool register_license_upgrade(license_key_id_t /* old key */, license_key_id_t /* new key */, const std::chrono::system_clock::time_point& /* begin */, const std::chrono::system_clock::time_point& /* end */, const std::string& /* key */);
|
||||
std::unique_ptr<LicenseUpgrade> query_license_upgrade(upgrade_id_t /* upgrade id */);
|
||||
void log_license_upgrade_attempt(upgrade_id_t /* upgrade id */, bool /* succeeded */, const std::string& /* server unique id */, const std::string& /* ip address */);
|
||||
|
||||
bool register_license(const std::string& /* key */, const std::shared_ptr<LicenseInfo>& /* info */, const std::string& /* issuer */);
|
||||
bool delete_license(const std::string& /* key */, bool /* full */ = false);
|
||||
|
||||
bool logRequest(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const std::string& /* version */, int /* result */);
|
||||
bool logStatistic(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const ts::proto::license::PropertyUpdateRequest&);
|
||||
//std::deque<std::unique_ptr<ExtendedUserStatistics>> list_statistics_user(const std::string& key, const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end);
|
||||
std::shared_ptr<UserHistory> list_statistics_user(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */);
|
||||
std::deque<std::unique_ptr<GlobalVersionsStatistic>> list_statistics_version(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */);
|
||||
|
||||
inline sql::SqlManager* sql() { return this->database; }
|
||||
private:
|
||||
std::shared_ptr<KeyIdCache> id_cache;
|
||||
sql::SqlManager* database;
|
||||
};
|
||||
}
|
@ -1,26 +1,30 @@
|
||||
#include "log/LogUtils.h"
|
||||
#include "LicenseManager.h"
|
||||
#include "DatabaseHandler.h"
|
||||
|
||||
using namespace license;
|
||||
using namespace license::server;
|
||||
using namespace license::server::database;
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using KeyIdCache = LicenseManager::KeyIdCache;
|
||||
|
||||
KeyIdCache::KeyIdCache(license::server::LicenseManager *handle) : handle(handle) {}
|
||||
KeyIdCache::KeyIdCache(DatabaseHandler *handle) : handle(handle) {}
|
||||
|
||||
std::string KeyIdCache::getKey(size_t keyId) {
|
||||
void KeyIdCache::clear_cache() {
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
this->entries.clear();
|
||||
}
|
||||
|
||||
std::string KeyIdCache::get_key_from_id(size_t keyId) {
|
||||
{
|
||||
threads::MutexLock lock(this->entry_lock);
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
|
||||
for(const auto& entry : this->entries)
|
||||
if(entry->keyId == keyId) return entry->key;
|
||||
}
|
||||
|
||||
sql::command(this->handle->database, "SELECT `key`, `keyId` FROM `license` WHERE `keyId` = :key", variable{":key", keyId})
|
||||
sql::command(this->handle->sql(), "SELECT `key`, `keyId` FROM `license` WHERE `keyId` = :key", variable{":key", keyId})
|
||||
.query(&KeyIdCache::insert_entry, this);
|
||||
{
|
||||
threads::MutexLock lock(this->entry_lock);
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
|
||||
for(const auto& entry : this->entries)
|
||||
if(entry->keyId == keyId) return entry->key;
|
||||
@ -28,20 +32,20 @@ std::string KeyIdCache::getKey(size_t keyId) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t KeyIdCache::getKeyId(const std::string &key) {
|
||||
size_t KeyIdCache::get_key_id_from_key(const std::string &key) {
|
||||
{
|
||||
threads::MutexLock lock(this->entry_lock);
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
|
||||
for(const auto& entry : this->entries)
|
||||
if(entry->key == key) return entry->keyId;
|
||||
}
|
||||
|
||||
auto result = sql::command(this->handle->database, "SELECT `key`, `keyId` FROM `license` WHERE `key` = :key", variable{":key", key})
|
||||
auto result = sql::command(this->handle->sql(), "SELECT `key`, `keyId` FROM `license` WHERE `key` = :key", variable{":key", key})
|
||||
.query(&KeyIdCache::insert_entry, this);
|
||||
if(!result)
|
||||
logError(LOG_GENERAL, "Failed to query key id for license. Query resulted in {}", result.fmtStr());
|
||||
{
|
||||
threads::MutexLock lock(this->entry_lock);
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
|
||||
for(const auto& entry : this->entries)
|
||||
if(entry->key == key)
|
||||
@ -52,16 +56,24 @@ size_t KeyIdCache::getKeyId(const std::string &key) {
|
||||
}
|
||||
|
||||
int KeyIdCache::insert_entry(int length, std::string *value, std::string *names) {
|
||||
string key;
|
||||
size_t keyId = 0;
|
||||
for(int index = 0; index < length; index++)
|
||||
string key{"unknown"};
|
||||
size_t keyId{0};
|
||||
|
||||
for(int index = 0; index < length; index++) {
|
||||
if(names[index] == "key")
|
||||
key = value[index];
|
||||
else if(names[index] == "keyId")
|
||||
keyId = stoll(value[index]);
|
||||
keyId = std::strtoll(value[index].c_str(), nullptr, 10);
|
||||
}
|
||||
if(!keyId) {
|
||||
logWarning(LOG_GENERAL, "Failed to parse key id for key {}", key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
threads::MutexLock lock(this->entry_lock);
|
||||
this->entries.push_back(new KeyIdCache::CacheEntry{key, keyId, system_clock::now()});
|
||||
auto entry = new KeyIdCache::CacheEntry{key, keyId, system_clock::now()};
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
this->entries.emplace_back(entry);
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <sql/SqlQuery.h>
|
||||
#include <shared/License.h>
|
||||
#include <LicenseRequest.pb.h>
|
||||
|
||||
namespace license {
|
||||
namespace server {
|
||||
class LicenseManager {
|
||||
public:
|
||||
class KeyIdCache {
|
||||
struct CacheEntry {
|
||||
std::string key;
|
||||
size_t keyId;
|
||||
std::chrono::system_clock::time_point age;
|
||||
};
|
||||
|
||||
public:
|
||||
KeyIdCache(LicenseManager*);
|
||||
|
||||
void cache_keys(const std::deque<std::string>& /* keys */);
|
||||
std::string getKey(size_t keyId);
|
||||
size_t getKeyId(const std::string&);
|
||||
private:
|
||||
int insert_entry(int, std::string*, std::string*);
|
||||
|
||||
LicenseManager* handle;
|
||||
threads::Mutex entry_lock;
|
||||
threads::Mutex entry_update_lock;
|
||||
std::deque<CacheEntry*> entries;
|
||||
};
|
||||
|
||||
struct UserStatistics {
|
||||
uint64_t clients_online = 0;
|
||||
uint64_t web_clients_online = 0;
|
||||
uint64_t bots_online = 0;
|
||||
uint64_t queries_online = 0;
|
||||
uint64_t servers_online = 0;
|
||||
};
|
||||
|
||||
struct ExtendedUserStatistics : public UserStatistics{
|
||||
std::string ip_address;
|
||||
};
|
||||
|
||||
struct GlobalUserStatistics : public UserStatistics {
|
||||
uint64_t instance_online = 0;
|
||||
};
|
||||
|
||||
struct DatabaseUserStatistics : public UserStatistics {
|
||||
std::chrono::system_clock::time_point timestamp{};
|
||||
};
|
||||
|
||||
struct GlobalVersionsStatistic {
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
std::map<std::string, uint32_t> versions;
|
||||
};
|
||||
|
||||
struct UserHistory {
|
||||
std::chrono::system_clock::time_point begin{};
|
||||
std::chrono::system_clock::time_point end{};
|
||||
std::chrono::milliseconds interval{0};
|
||||
|
||||
size_t record_count = 0;
|
||||
GlobalUserStatistics history[0];
|
||||
};
|
||||
static_assert(sizeof(UserHistory) == 32);
|
||||
|
||||
public:
|
||||
LicenseManager(sql::SqlManager* database);
|
||||
~LicenseManager();
|
||||
|
||||
bool setup(std::string&);
|
||||
|
||||
bool validLicenseKey(const std::string& /* key */);
|
||||
std::shared_ptr<LicenseInfo> licenseInfo(const std::string&);
|
||||
|
||||
std::map<std::string, std::shared_ptr<LicenseInfo>> listLicenses(int offset = 0, int limit = -1);
|
||||
|
||||
bool registerLicense(const std::string& /* key */, const std::shared_ptr<LicenseInfo>& /* info */, const std::string& /* issuer */);
|
||||
bool updateLicenseInfo(const std::string&, const std::shared_ptr<LicenseInfo>&);
|
||||
bool deleteLicense(const std::string& /* key */, bool /* full */ = false);
|
||||
|
||||
bool logRequest(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const std::string& /* version */, int /* result */);
|
||||
bool logStatistic(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const ts::proto::license::PropertyUpdateRequest&);
|
||||
//std::deque<std::unique_ptr<ExtendedUserStatistics>> list_statistics_user(const std::string& key, const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end);
|
||||
std::shared_ptr<UserHistory> list_statistics_user(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */);
|
||||
std::deque<std::unique_ptr<GlobalVersionsStatistic>> list_statistics_version(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */);
|
||||
|
||||
inline sql::SqlManager* sql() { return this->database; }
|
||||
private:
|
||||
std::shared_ptr<KeyIdCache> id_cache;
|
||||
sql::SqlManager* database;
|
||||
};
|
||||
}
|
||||
}
|
@ -9,10 +9,8 @@
|
||||
#include <LicenseRequest.pb.h>
|
||||
#include <shared/License.h>
|
||||
#include <shared/crypt.h>
|
||||
#include <misc/base64.h>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include "LicenseServer.h"
|
||||
#include "crypt.h"
|
||||
#include "UserManager.h"
|
||||
|
||||
using namespace std;
|
||||
@ -21,7 +19,7 @@ using namespace license;
|
||||
using namespace ts;
|
||||
|
||||
LicenseServer::LicenseServer(const sockaddr_in& addr,
|
||||
std::shared_ptr<server::LicenseManager> manager,
|
||||
std::shared_ptr<server::database::DatabaseHandler> manager,
|
||||
shared_ptr<license::stats::StatisticManager> stats,
|
||||
shared_ptr<license::web::WebStatistics> wstats,
|
||||
std::shared_ptr<UserManager> user_manager) : manager{std::move(manager)}, statistics{std::move(stats)}, web_statistics{std::move(wstats)}, user_manager{std::move(user_manager)} {
|
||||
@ -226,7 +224,7 @@ void LicenseServer::handleEventRead(int fd, short, void* ptrServer) {
|
||||
server->closeConnection(client);
|
||||
return;
|
||||
} else if(read == 0) {
|
||||
logError(LOG_LICENSE_CONTROLL, "Invalid read. Disconnecting remote client");
|
||||
logMessage(LOG_LICENSE_CONTROLL, "[CLIENT][" + client->address() + "] Received EOF for client. Removing client.");
|
||||
event_del_noblock(client->network.readEvent);
|
||||
server->closeConnection(client);
|
||||
return;
|
||||
@ -253,7 +251,7 @@ void LicenseServer::handleEventAccept(int fd, short, void* ptrServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
client->protocol.state = protocol::HANDSCAKE;
|
||||
client->protocol.state = protocol::HANDSCHAKE;
|
||||
{
|
||||
lock_guard lock(server->client_lock);
|
||||
server->clients.push_back(client);
|
||||
@ -378,6 +376,8 @@ void LicenseServer::handleMessage(shared_ptr<ConnectedClient>& client, const std
|
||||
success = this->handleServerValidation(client, packet, error);
|
||||
} else if(packet.header.packetId == protocol::PACKET_CLIENT_PROPERTY_ADJUSTMENT) {
|
||||
success = this->handlePacketPropertyUpdate(client, packet, error);
|
||||
} else if(packet.header.packetId == protocol::PACKET_CLIENT_LICENSE_UPGRADE) {
|
||||
success = this->handlePacketLicenseUpgrade(client, packet, error);
|
||||
} else if(packet.header.packetId == protocol::PACKET_CLIENT_AUTH_REQUEST) {
|
||||
success = this->handlePacketAuth(client, packet, error);
|
||||
} else if(packet.header.packetId == protocol::PACKET_CLIENT_LICENSE_CREATE_REQUEST) {
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include <shared/License.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "LicenseManager.h"
|
||||
#include "DatabaseHandler.h"
|
||||
|
||||
namespace license {
|
||||
namespace web {
|
||||
@ -44,11 +44,14 @@ namespace license {
|
||||
|
||||
std::chrono::system_clock::time_point last_read;
|
||||
std::string cryptKey = "";
|
||||
|
||||
int version{2}; /* current version is 3 */
|
||||
} protocol;
|
||||
|
||||
ClientType type = ClientType::SERVER;
|
||||
std::string username;
|
||||
std::string key;
|
||||
uint64_t key_pending_upgrade{0};
|
||||
std::string unique_identifier;
|
||||
|
||||
bool invalid_license = false;
|
||||
@ -68,7 +71,7 @@ namespace license {
|
||||
|
||||
class LicenseServer {
|
||||
public:
|
||||
explicit LicenseServer(const sockaddr_in&, std::shared_ptr<server::LicenseManager> , std::shared_ptr<stats::StatisticManager> /* stats */, std::shared_ptr<web::WebStatistics> /* web stats */, std::shared_ptr<UserManager> /* user manager */);
|
||||
explicit LicenseServer(const sockaddr_in&, std::shared_ptr<server::database::DatabaseHandler> , std::shared_ptr<stats::StatisticManager> /* stats */, std::shared_ptr<web::WebStatistics> /* web stats */, std::shared_ptr<UserManager> /* user manager */);
|
||||
~LicenseServer();
|
||||
|
||||
bool start();
|
||||
@ -91,7 +94,7 @@ namespace license {
|
||||
|
||||
std::shared_ptr<web::WebStatistics> web_statistics;
|
||||
std::shared_ptr<stats::StatisticManager> statistics;
|
||||
std::shared_ptr<server::LicenseManager> manager;
|
||||
std::shared_ptr<server::database::DatabaseHandler> manager;
|
||||
std::shared_ptr<UserManager> user_manager;
|
||||
|
||||
std::mutex client_lock;
|
||||
@ -117,6 +120,7 @@ namespace license {
|
||||
bool handleDisconnect(std::shared_ptr<ConnectedClient>&, protocol::packet&, std::string& error);
|
||||
bool handleHandshake(std::shared_ptr<ConnectedClient>&, protocol::packet&, std::string& error);
|
||||
bool handleServerValidation(std::shared_ptr<ConnectedClient> &, protocol::packet &, std::string &error);
|
||||
bool handlePacketLicenseUpgrade(std::shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error);
|
||||
bool handlePacketPropertyUpdate(std::shared_ptr<ConnectedClient> &, protocol::packet &, std::string &error);
|
||||
|
||||
bool handlePacketAuth(std::shared_ptr<ConnectedClient> &, protocol::packet &, std::string &error);
|
||||
|
@ -22,6 +22,8 @@ inline void generate(char* buffer, size_t length){
|
||||
buffer[index] = rand();
|
||||
}
|
||||
|
||||
#define _str(x) #x
|
||||
|
||||
#define TEST_PROTOCOL_STATE(expected) \
|
||||
if(client->protocol.state != protocol::expected) { \
|
||||
error = "invalid protocol state"; \
|
||||
@ -37,21 +39,23 @@ if(packet.data.length() < (expected)) { \
|
||||
#define PARSE_PROTO(class, var) \
|
||||
ts::proto::license::class var; \
|
||||
if(!var.ParseFromString(packet.data)) { \
|
||||
error = "invalid data!"; \
|
||||
error = "invalid data (" _str(class) ")!"; \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define CRYPT_KEY_LENGTH 32
|
||||
bool LicenseServer::handleHandshake(shared_ptr<ConnectedClient>& client, protocol::packet& packet, std::string &error) {
|
||||
TEST_PROTOCOL_STATE(HANDSCAKE);
|
||||
TEST_PROTOCOL_STATE(HANDSCHAKE);
|
||||
ENSURE_PACKET_SIZE(4);
|
||||
if((uint8_t) packet.data[0] != 0xC0 || (uint8_t) packet.data[1] != 0xFF || (uint8_t) packet.data[2] != 0xEE) {
|
||||
error = "invalid magic!";
|
||||
return false;
|
||||
}
|
||||
if((uint8_t) packet.data[3] != LICENSE_PROT_VERSION) {
|
||||
error = "invalid version!";
|
||||
return false;
|
||||
|
||||
client->protocol.version = (uint8_t) packet.data[3];
|
||||
if(client->protocol.version < 2 || client->protocol.version > 3) {
|
||||
error = "unsupported version";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool manager = false;
|
||||
@ -67,7 +71,7 @@ bool LicenseServer::handleHandshake(shared_ptr<ConnectedClient>& client, protoco
|
||||
size_t buffer_index = 0;
|
||||
buffer[buffer_index++] = 0xAF;
|
||||
buffer[buffer_index++] = 0xFE;
|
||||
buffer[buffer_index++] = LICENSE_PROT_VERSION;
|
||||
buffer[buffer_index++] = client->protocol.version;
|
||||
le2be16(CRYPT_KEY_LENGTH, buffer, buffer_index, &buffer_index);
|
||||
memcpy(&buffer[buffer_index], buffer_cryptkey, CRYPT_KEY_LENGTH);
|
||||
buffer_index += CRYPT_KEY_LENGTH;
|
||||
@ -134,94 +138,129 @@ std::string string_to_hex(const std::string& input)
|
||||
}
|
||||
|
||||
bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) {
|
||||
TEST_PROTOCOL_STATE(SERVER_VALIDATION);
|
||||
if(client->protocol.state != protocol::LICENSE_UPGRADE) /* server may wants to verify new license */
|
||||
TEST_PROTOCOL_STATE(SERVER_VALIDATION);
|
||||
|
||||
PARSE_PROTO(ServerValidation, pkt);
|
||||
|
||||
shared_ptr<License> remoteLicense = nullptr;
|
||||
if(pkt.licensed() && !pkt.has_license()) {
|
||||
//TODO shutdown server
|
||||
std::shared_ptr<License> remote_license{nullptr};
|
||||
if(pkt.licensed() && (!pkt.has_license() || !pkt.has_license_info())) {
|
||||
error = "invalid/missing license data";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!pkt.has_info()) {
|
||||
error = "invalid data or missing data";
|
||||
return false;
|
||||
}
|
||||
if(pkt.has_license()){ //Client has license
|
||||
remoteLicense = readLocalLicence(pkt.license(), error);
|
||||
if(!remoteLicense) {
|
||||
error = "Could not read remote key: " + error;
|
||||
|
||||
if(pkt.licensed()){ //Client has license
|
||||
remote_license = readLocalLicence(pkt.license(), error);
|
||||
if(!remote_license) {
|
||||
error = "could not parse license (" + error + ")";
|
||||
return false;
|
||||
};
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Got remote license. Registered to {}. Key: {} (0x{})", client->address(), remoteLicense->owner(), base64::encode(remoteLicense->key()), string_to_hex(remoteLicense->key()));
|
||||
client->key = remoteLicense->key();
|
||||
} else { }
|
||||
if(pkt.licensed() && !pkt.has_license_info()) {
|
||||
error = "Invalid content!";
|
||||
return false;
|
||||
}
|
||||
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Got remote license. Registered to {}. Key: {} (0x{})", client->address(), remote_license->owner(), base64::encode(remote_license->key()), string_to_hex(remote_license->key()));
|
||||
client->key = remote_license->key();
|
||||
}
|
||||
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Got some server information. TeaSpeak-Version: {} uname: {}", client->address(), pkt.info().version(), pkt.info().uname());
|
||||
ts::proto::license::LicenseResponse response;
|
||||
|
||||
//Forces
|
||||
|
||||
ts::proto::license::LicenseResponse response{};
|
||||
client->unique_identifier = pkt.info().has_unique_id() ? pkt.info().unique_id() : client->address();
|
||||
if(remoteLicense && pkt.licensed()) {
|
||||
auto info = this->manager->licenseInfo(remoteLicense->key());
|
||||
|
||||
if(remote_license) {
|
||||
auto info = this->manager->query_license_info(remote_license->key());
|
||||
|
||||
if(!info) {
|
||||
response.mutable_blacklist()->set_reason("License hasn't been found.");
|
||||
response.set_invalid_reason("license has not been found");
|
||||
response.set_valid(false);
|
||||
|
||||
/*
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Unknown license! Adding it to database!", client->address());
|
||||
auto db_info = make_shared<LicenseInfo>();
|
||||
db_info->start = system_clock::now();
|
||||
db_info->end = remoteLicense->end();
|
||||
db_info->last_name = "unknown";
|
||||
db_info->first_name = "unknown";
|
||||
db_info->username = remoteLicense->owner();
|
||||
db_info->email = "unknonw@unknown";
|
||||
db_info->creation = system_clock::now();
|
||||
db_info->type = remoteLicense->data.type;
|
||||
this->manager->registerLicense(remoteLicense->key(), db_info, "teaforo-fix");
|
||||
info = this->manager->licenseInfo(remoteLicense->key());
|
||||
if(!info) {
|
||||
error = "could not insert key!";
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license hasn't been found in database. Shutting down server!", client->address());
|
||||
} else {
|
||||
response.set_update_pending(info->upgrade_id > 0);
|
||||
client->key_pending_upgrade = info->upgrade_id;
|
||||
if(info->deleted) {
|
||||
response.mutable_blacklist()->set_reason("License has been deleted.");
|
||||
response.set_invalid_reason("license has been deleted");
|
||||
response.set_valid(false);
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license has been deleted! Shutting down server!", client->address());
|
||||
} else {
|
||||
fill_info(response.mutable_license_info(), info, remoteLicense->data.licenceKey);
|
||||
response.set_valid(info->isValid());
|
||||
fill_info(response.mutable_license_info(), info, remote_license->data.licenceKey);
|
||||
auto is_invalid = !info->isValid();
|
||||
if(is_invalid) {
|
||||
response.set_invalid_reason("license is invalid");
|
||||
response.set_valid(false);
|
||||
} else {
|
||||
response.set_valid(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
this->manager->logRequest(remoteLicense->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid());
|
||||
this->manager->logRequest(remote_license->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid());
|
||||
} else {
|
||||
response.set_valid(true);
|
||||
}
|
||||
|
||||
if(response.valid())
|
||||
response.mutable_blacklist()->set_state(ts::proto::license::VALID);
|
||||
else {
|
||||
if(!response.has_license_info())
|
||||
fill_info(response.mutable_license_info(), nullptr, ""); /* "Hack" for old clients which require a license. Else the server would not be stopped */
|
||||
response.mutable_blacklist()->set_state(ts::proto::license::BLACKLISTED); /* "Hack" for all old clients */
|
||||
client->invalid_license = true;
|
||||
if(client->protocol.version == 2) {
|
||||
if(response.valid())
|
||||
response.mutable_blacklist()->set_state(ts::proto::license::VALID);
|
||||
else {
|
||||
response.mutable_blacklist()->set_reason(response.invalid_reason());
|
||||
response.mutable_blacklist()->set_state(ts::proto::license::BLACKLISTED); /* "Hack" for all old clients */
|
||||
|
||||
if(!response.has_license_info()) fill_info(response.mutable_license_info(), nullptr, ""); /* "Hack" for old clients which require a license. Else the server would not be stopped */
|
||||
client->invalid_license = true;
|
||||
}
|
||||
} else {
|
||||
if(!response.has_blacklist())
|
||||
response.mutable_blacklist()->set_state(ts::proto::license::VALID);
|
||||
}
|
||||
client->invalid_license = !response.valid();
|
||||
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_VALIDATION_RESPONSE, response});
|
||||
client->protocol.state = protocol::PROPERTY_ADJUSTMENT;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseServer::handlePacketLicenseUpgrade(shared_ptr<ConnectedClient> &client, license::protocol::packet &packet,
|
||||
std::string &error) {
|
||||
TEST_PROTOCOL_STATE(PROPERTY_ADJUSTMENT);
|
||||
PARSE_PROTO(RequestLicenseUpgrade, pkt);
|
||||
|
||||
if(!client->key_pending_upgrade) {
|
||||
error = "no update pending";
|
||||
return false;
|
||||
}
|
||||
ts::proto::license::LicenseUpgradeResponse response;
|
||||
auto license_upgrade = this->manager->query_license_upgrade(client->key_pending_upgrade);
|
||||
response.set_valid(false);
|
||||
if(license_upgrade) {
|
||||
if(!license_upgrade->valid) {
|
||||
response.set_error_message("upgrade has been invalidated");
|
||||
} else if(license_upgrade->is_expired()) {
|
||||
response.set_error_message("upgrade has been expired");
|
||||
} else if(license_upgrade->not_yet_available()) {
|
||||
response.set_error_message("upgrade is not yet active.");
|
||||
} else {
|
||||
response.set_valid(true);
|
||||
response.set_license_key(license_upgrade->license_key);
|
||||
|
||||
}
|
||||
this->manager->log_license_upgrade_attempt(license_upgrade->upgrade_id, response.valid(), client->unique_identifier, client->address());
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Client requested license upgrade {}. Result: {}", client->key_pending_upgrade, response.valid() ? "granted" : "denied (" + response.error_message() + ")");
|
||||
} else {
|
||||
response.set_error_message("failed to find upgrade");
|
||||
}
|
||||
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_LICENSE_UPGRADE_RESPONSE, response});
|
||||
client->protocol.state = protocol::LICENSE_UPGRADE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) {
|
||||
TEST_PROTOCOL_STATE(PROPERTY_ADJUSTMENT);
|
||||
if(client->protocol.state != protocol::LICENSE_UPGRADE) /* LICENSE_UPGRADE could be skipped */
|
||||
TEST_PROTOCOL_STATE(PROPERTY_ADJUSTMENT);
|
||||
if(client->invalid_license) {
|
||||
ts::proto::license::PropertyUpdateResponse response;
|
||||
response.set_accepted(true);
|
||||
@ -330,33 +369,58 @@ bool LicenseServer::handlePacketLicenseCreate(shared_ptr<ConnectedClient> &clien
|
||||
TEST_PROTOCOL_STATE(MANAGER_CONNECTED);
|
||||
PARSE_PROTO(LicenseCreateRequest, pkt);
|
||||
|
||||
logMessage(LOG_GENERAL, "[MANAGER][" + client->address() + "] Register new license to {} {} ({}). E-Mail: {}", pkt.issuer_first_name(), pkt.issuer_last_name(), pkt.issuer_username(), pkt.issuer_email());
|
||||
auto old_license = pkt.has_old_key() ? hex::hex(pkt.old_key()) : "none";
|
||||
logMessage(LOG_GENERAL, "[MANAGER][" + client->address() + "] Register new license to {} {} ({}). E-Mail: {}. Old license: {}", pkt.issuer_first_name(), pkt.issuer_last_name(), pkt.issuer_username(), pkt.issuer_email(), old_license);
|
||||
|
||||
ts::proto::license::LicenseCreateResponse response;
|
||||
auto old_key_id{0};
|
||||
ts::proto::license::LicenseCreateResponse response{};
|
||||
if(pkt.has_old_key()) {
|
||||
old_key_id = this->manager->key_id_cache()->get_key_id_from_key(pkt.old_key());
|
||||
if(old_key_id == 0) {
|
||||
response.set_error("failed to find old license key in database");
|
||||
goto _send_response;
|
||||
}
|
||||
}
|
||||
|
||||
auto db_info = make_shared<LicenseInfo>();
|
||||
db_info->start = system_clock::time_point() + milliseconds(pkt.begin());
|
||||
db_info->end = system_clock::time_point() + milliseconds(pkt.end());
|
||||
db_info->last_name = pkt.issuer_last_name();
|
||||
db_info->first_name = pkt.issuer_first_name();
|
||||
db_info->username = pkt.issuer_username();
|
||||
db_info->email = pkt.issuer_email();
|
||||
db_info->creation = system_clock::now();
|
||||
db_info->type = static_cast<LicenseType>(pkt.type());
|
||||
{
|
||||
auto db_info = make_shared<LicenseInfo>();
|
||||
db_info->start = system_clock::time_point() + milliseconds(pkt.begin());
|
||||
db_info->end = system_clock::time_point() + milliseconds(pkt.end());
|
||||
db_info->last_name = pkt.issuer_last_name();
|
||||
db_info->first_name = pkt.issuer_first_name();
|
||||
db_info->username = pkt.issuer_username();
|
||||
db_info->email = pkt.issuer_email();
|
||||
db_info->creation = system_clock::now();
|
||||
db_info->type = static_cast<LicenseType>(pkt.type());
|
||||
|
||||
auto license = license::createLocalLicence(db_info->type, db_info->end, db_info->first_name + db_info->last_name);
|
||||
auto parsed_license = license::readLocalLicence(license, error);
|
||||
if(!parsed_license) {
|
||||
response.set_error("failed to register license (parse)");
|
||||
} else {
|
||||
auto license = license::createLocalLicence(db_info->type, db_info->end, db_info->first_name + db_info->last_name);
|
||||
auto parsed_license = license::readLocalLicence(license, error);
|
||||
if(!parsed_license) {
|
||||
response.set_error("failed to register license (parse)");
|
||||
} else {
|
||||
if(!this->manager->register_license(parsed_license->key(), db_info, client->username)) {
|
||||
response.set_error("failed to register license");
|
||||
goto _send_response;
|
||||
}
|
||||
|
||||
if(!this->manager->registerLicense(parsed_license->key(), db_info, client->username)) {
|
||||
response.set_error("failed to register license");
|
||||
} else {
|
||||
fill_info(response.mutable_license(), db_info, parsed_license->key());
|
||||
response.set_exported_key(license);
|
||||
}
|
||||
}
|
||||
if(old_key_id) {
|
||||
auto new_key_id = this->manager->key_id_cache()->get_key_id_from_key(parsed_license->key());
|
||||
if(!new_key_id) {
|
||||
response.set_error("failed to find new license in database");
|
||||
goto _send_response;
|
||||
}
|
||||
|
||||
if(!this->manager->register_license_upgrade(old_key_id, new_key_id, std::chrono::system_clock::now(), db_info->end, license)) {
|
||||
response.set_error("failed to register license upgrade");
|
||||
goto _send_response;
|
||||
}
|
||||
}
|
||||
fill_info(response.mutable_license(), db_info, parsed_license->key());
|
||||
response.set_exported_key(license);
|
||||
}
|
||||
}
|
||||
|
||||
_send_response:
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_LICENSE_CREATE_RESPONSE, response});
|
||||
return true;
|
||||
}
|
||||
@ -368,7 +432,7 @@ bool LicenseServer::handlePacketLicenseList(shared_ptr<ConnectedClient> &client,
|
||||
proto::license::LicenseListResponse response;
|
||||
response.set_end(false);
|
||||
|
||||
for(const auto& info : this->manager->listLicenses(pkt.offset(), pkt.count())) {
|
||||
for(const auto& info : this->manager->list_licenses(pkt.offset(), pkt.count())) {
|
||||
auto entry = response.add_entries();
|
||||
fill_info(entry, info.second, info.first);
|
||||
}
|
||||
@ -382,7 +446,7 @@ bool LicenseServer::handlePacketLicenseDelete(shared_ptr<ConnectedClient> &clien
|
||||
PARSE_PROTO(LicenseDeleteRequest, pkt);
|
||||
|
||||
proto::license::LicenseDeleteResponse response;
|
||||
response.set_succeed(this->manager->deleteLicense(pkt.key(), pkt.full()));
|
||||
response.set_succeed(this->manager->delete_license(pkt.key(), pkt.full()));
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_CLIENT_DELETE_RESPONSE, response});
|
||||
|
||||
return true;
|
||||
|
@ -4,8 +4,10 @@
|
||||
|
||||
#include <sql/SqlQuery.h>
|
||||
#include <misc/std_unique_ptr.h>
|
||||
|
||||
#include <utility>
|
||||
#include "StatisticManager.h"
|
||||
#include "LicenseManager.h"
|
||||
#include "DatabaseHandler.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@ -76,16 +78,16 @@ system_clock::time_point HistoryStatistics::align_type(license::stats::HistorySt
|
||||
}
|
||||
}
|
||||
|
||||
StatisticManager::StatisticManager(const std::shared_ptr<license::server::LicenseManager> &manager) : license_manager(manager) {}
|
||||
StatisticManager::~StatisticManager() {}
|
||||
StatisticManager::StatisticManager(std::shared_ptr<license::server::database::DatabaseHandler> manager) : license_manager{std::move(manager)} {}
|
||||
StatisticManager::~StatisticManager() = default;
|
||||
|
||||
struct GeneralStatisticEntry {
|
||||
std::chrono::system_clock::time_point age;
|
||||
string unique_id = "";
|
||||
uint64_t key_id = 0;
|
||||
uint64_t servers = 0;
|
||||
uint64_t clients = 0;
|
||||
uint64_t bots = 0;
|
||||
string unique_id{""};
|
||||
uint64_t key_id{0};
|
||||
uint64_t servers{0};
|
||||
uint64_t clients{0};
|
||||
uint64_t bots{0};
|
||||
};
|
||||
|
||||
void StatisticManager::reset_cache_general() {
|
||||
@ -93,7 +95,7 @@ void StatisticManager::reset_cache_general() {
|
||||
this->_general_statistics = nullptr;
|
||||
}
|
||||
|
||||
void parse_general_entry(deque<unique_ptr<GeneralStatisticEntry>>& entries, bool unique, int length, string* values, string* names) {
|
||||
void parse_general_entry(std::deque<std::unique_ptr<GeneralStatisticEntry>>& entries, bool unique, int length, string* values, string* names) {
|
||||
auto entry = make_unique<GeneralStatisticEntry>();
|
||||
for(int index = 0; index < length; index++) {
|
||||
if(names[index] == "keyId") {
|
||||
@ -139,7 +141,7 @@ std::shared_ptr<GeneralStatistics> StatisticManager::general_statistics() {
|
||||
|
||||
auto result = sql::command(this->license_manager->sql(), "SELECT `keyId`, `unique_id`, `timestamp`,`server`,`clients`,`music` FROM `history_online` WHERE `timestamp` > :time ORDER BY `timestamp` ASC",
|
||||
variable{":time", duration_cast<milliseconds>(system_clock::now().time_since_epoch() - hours(2) - minutes(10)).count()}) //10min as buffer
|
||||
.query(std::function<decltype(parse_general_entry)>(parse_general_entry), entries, true);
|
||||
.query(std::function<decltype(parse_general_entry)>{parse_general_entry}, entries, true);
|
||||
|
||||
auto stats = make_shared<GeneralStatistics>();
|
||||
for(auto& entry : entries) {
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include "LicenseManager.h"
|
||||
#include "DatabaseHandler.h"
|
||||
|
||||
namespace license {
|
||||
namespace stats {
|
||||
@ -49,19 +49,19 @@ namespace license {
|
||||
std::chrono::milliseconds period;
|
||||
HistoryType type;
|
||||
|
||||
std::shared_ptr<server::LicenseManager::UserHistory> statistics;
|
||||
std::shared_ptr<server::database::DatabaseHandler::UserHistory> statistics;
|
||||
};
|
||||
|
||||
class StatisticManager {
|
||||
public:
|
||||
explicit StatisticManager(const std::shared_ptr<server::LicenseManager>& /* manager */);
|
||||
explicit StatisticManager(std::shared_ptr<server::database::DatabaseHandler> /* manager */);
|
||||
virtual ~StatisticManager();
|
||||
|
||||
void reset_cache_general();
|
||||
std::shared_ptr<GeneralStatistics> general_statistics();
|
||||
std::shared_ptr<HistoryStatistics> history(HistoryStatistics::HistoryType);
|
||||
private:
|
||||
std::shared_ptr<server::LicenseManager> license_manager;
|
||||
std::shared_ptr<server::database::DatabaseHandler> license_manager;
|
||||
|
||||
std::recursive_mutex _general_statistics_lock;
|
||||
std::recursive_mutex _general_statistics_generate_lock;
|
||||
|
@ -17,7 +17,7 @@ using namespace ts::ssl;
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
WebStatistics::WebStatistics(const shared_ptr<LicenseManager> &manager, const std::shared_ptr<stats::StatisticManager>& stats) : license_manager(manager), statistics_manager(stats) {}
|
||||
WebStatistics::WebStatistics(const shared_ptr<database::DatabaseHandler> &manager, const std::shared_ptr<stats::StatisticManager>& stats) : license_manager(manager), statistics_manager(stats) {}
|
||||
WebStatistics::~WebStatistics() {}
|
||||
|
||||
#define SFAIL(message) \
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
namespace license {
|
||||
namespace server {
|
||||
class LicenseManager;
|
||||
class DatabaseHandler;
|
||||
}
|
||||
namespace stats {
|
||||
class StatisticManager;
|
||||
@ -47,7 +47,7 @@ namespace license {
|
||||
inline std::string client_prefix() { return peer_address ? net::to_string(peer_address->sin_addr) : "unconnected"; }
|
||||
};
|
||||
public:
|
||||
WebStatistics(const std::shared_ptr<server::LicenseManager>& /* license manager */, const std::shared_ptr<stats::StatisticManager>& /* stats manager */);
|
||||
WebStatistics(const std::shared_ptr<server::database::DatabaseHandler>& /* license manager */, const std::shared_ptr<stats::StatisticManager>& /* stats manager */);
|
||||
virtual ~WebStatistics();
|
||||
|
||||
bool start(std::string& /* error */, uint16_t /* port */, const std::shared_ptr<ts::ssl::SSLContext>& /* ssl */);
|
||||
@ -75,7 +75,7 @@ namespace license {
|
||||
bool _running = false;
|
||||
std::recursive_mutex running_lock;
|
||||
|
||||
std::shared_ptr<server::LicenseManager> license_manager;
|
||||
std::shared_ptr<server::database::DatabaseHandler> license_manager;
|
||||
std::shared_ptr<stats::StatisticManager> statistics_manager;
|
||||
|
||||
struct {
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <random>
|
||||
#include <ed25519/ed25519.h>
|
||||
|
||||
//#define NO_OPEN_SSL
|
||||
#define NO_OPEN_SSL
|
||||
#include <misc/digest.h>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
@ -9,7 +10,7 @@
|
||||
#include <Variable.h>
|
||||
|
||||
#define LICENSE_VERSION 1
|
||||
#define LICENSE_PROT_VERSION 2
|
||||
#define LICENSE_PROT_VERSION 3
|
||||
#define MAGIC_NUMER 0xBADC0DED
|
||||
|
||||
namespace license {
|
||||
@ -17,11 +18,11 @@ namespace license {
|
||||
class LicenseException : public std::exception {
|
||||
public:
|
||||
LicenseException() = delete;
|
||||
LicenseException(std::string message) : errorMessage(std::move(message)) {}
|
||||
explicit LicenseException(std::string message) : errorMessage(std::move(message)) {}
|
||||
LicenseException(const LicenseException& ref) : errorMessage(ref.errorMessage) {}
|
||||
LicenseException(LicenseException&& ref) : errorMessage(std::move(ref.errorMessage)) {}
|
||||
explicit LicenseException(LicenseException&& ref) : errorMessage(std::move(ref.errorMessage)) {}
|
||||
|
||||
const char* what() const noexcept override;
|
||||
[[nodiscard]] const char* what() const noexcept override;
|
||||
private:
|
||||
std::string errorMessage;
|
||||
};
|
||||
@ -36,7 +37,7 @@ namespace license {
|
||||
struct LicenseHeader {
|
||||
uint16_t version; /* first 16 bytes const version */
|
||||
uint64_t crypt_key_seed; /* the seed */
|
||||
uint8_t crypt_key_verify_offset; /* the first 8 bits determine how much generations (n * 3) and the rest what value is expeted. Due to the loss of 8 bits does the highest 8 bits get xored with the lowerst and 56 bits get compared */
|
||||
uint8_t crypt_key_verify_offset; /* the first 8 bits determine how much generations (n * 3) and the rest what value is expected. Due to the loss of 8 bits does the highest 8 bits get xored with the lowerst and 56 bits get compared */
|
||||
uint8_t crypt_key_verify[5];
|
||||
} __attribute__ ((__packed__));
|
||||
static_assert(sizeof(LicenseHeader) == 16);
|
||||
@ -62,7 +63,7 @@ namespace license {
|
||||
/* Note for the write method: Write "private_buffer" if we're not able to resign the private data. As well enfore (assert) that we have a private buffer (e.g. by the read method) */
|
||||
|
||||
~License();
|
||||
const std::vector<std::shared_ptr<const HierarchyEntry>> hierarchy() const { return this->_hierarchy; }
|
||||
[[nodiscard]] std::vector<std::shared_ptr<const HierarchyEntry>> hierarchy() const { return this->_hierarchy; }
|
||||
bool push_entry(const std::shared_ptr<const HierarchyEntry>& /* entry */, size_t* /* index */ = nullptr);
|
||||
|
||||
bool hierarchy_timestamps_valid();
|
||||
@ -86,7 +87,7 @@ namespace license {
|
||||
|
||||
std::string write(uint8_t& /* error */);
|
||||
|
||||
bool private_data_editable() const;
|
||||
[[nodiscard]] bool private_data_editable() const;
|
||||
bool write_private_data(const LicensePrivateWriteOptions& /* write options */);
|
||||
|
||||
[[nodiscard]] inline uint16_t version() const { return this->_version; }
|
||||
@ -118,17 +119,17 @@ namespace license {
|
||||
|
||||
bool private_key_chain_valid();
|
||||
|
||||
bool has_meta(const std::string& key) const { return this->meta_data.count(key) > 0; }
|
||||
std::string get_meta(const std::string& key) const { return this->meta_data.at(key); }
|
||||
[[nodiscard]] bool has_meta(const std::string& key) const { return this->meta_data.count(key) > 0; }
|
||||
[[nodiscard]] std::string get_meta(const std::string& key) const { return this->meta_data.at(key); }
|
||||
void set_meta(const std::string& key, const std::string& value) { this->meta_data[key] = value; }
|
||||
|
||||
/* if target is null just increase the offset! */
|
||||
bool write(uint8_t* /* target */, size_t& /* offset */, size_t /* length */, const LicensePrivateWriteOptions& /* options */);
|
||||
|
||||
void register_raw_private_key(uint8_t /* index */, const uint8_t* /* key */);
|
||||
bool has_raw_private_key(uint8_t /* index */) const;
|
||||
[[nodiscard]] bool has_raw_private_key(uint8_t /* index */) const;
|
||||
|
||||
bool private_key_calculable(int /* index */) const;
|
||||
[[nodiscard]] bool private_key_calculable(int /* index */) const;
|
||||
bool calculate_private_key(uint8_t* /* response */, uint8_t /* index */) const;
|
||||
private:
|
||||
std::weak_ptr<License> _handle;
|
||||
@ -168,7 +169,7 @@ namespace license {
|
||||
}
|
||||
|
||||
inline const uint8_t* body() const { return this->read_body; }
|
||||
inline const size_t body_length() const { return this->read_body_length; }
|
||||
inline size_t body_length() const { return this->read_body_length; }
|
||||
|
||||
template <typename I>
|
||||
inline I interpret_as() const {
|
||||
@ -240,7 +241,7 @@ namespace license {
|
||||
const std::chrono::system_clock::time_point &end,
|
||||
size_t buffer_size, uint8_t*& buffer_ptr
|
||||
) {
|
||||
auto result = std::shared_ptr<HierarchyEntry>(new HierarchyEntry{T::_type, pub_key, begin, end});
|
||||
auto result = std::make_shared<HierarchyEntry>(HierarchyEntry{T::_type, pub_key, begin, end});
|
||||
if(!result || !result->allocate_read_body(buffer_size)) return nullptr;
|
||||
result->read_body_length = buffer_size;
|
||||
buffer_ptr = result->read_body;
|
||||
@ -344,7 +345,8 @@ namespace license {
|
||||
std::chrono::system_clock::time_point end;
|
||||
std::chrono::system_clock::time_point creation;
|
||||
|
||||
bool deleted = false;
|
||||
bool deleted{false};
|
||||
uint32_t upgrade_id{0};
|
||||
|
||||
inline bool isValid() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); }
|
||||
};
|
||||
@ -358,10 +360,13 @@ namespace license {
|
||||
UNCONNECTED,
|
||||
CONNECTING,
|
||||
|
||||
HANDSCAKE,
|
||||
HANDSCHAKE,
|
||||
SERVER_VALIDATION,
|
||||
LICENSE_INFO,
|
||||
|
||||
PROPERTY_ADJUSTMENT,
|
||||
LICENSE_UPGRADE,
|
||||
|
||||
MANAGER_AUTHORIZATION,
|
||||
MANAGER_CONNECTED,
|
||||
|
||||
@ -371,8 +376,10 @@ namespace license {
|
||||
enum PacketType : uint8_t {
|
||||
PACKET_CLIENT_HANDSHAKE,
|
||||
PACKET_SERVER_HANDSHAKE,
|
||||
|
||||
PACKET_CLIENT_SERVER_VALIDATION,
|
||||
PACKET_SERVER_VALIDATION_RESPONSE,
|
||||
|
||||
PACKET_CLIENT_PROPERTY_ADJUSTMENT,
|
||||
PACKET_SERVER_PROPERTY_ADJUSTMENT,
|
||||
|
||||
@ -387,10 +394,18 @@ namespace license {
|
||||
PACKET_CLIENT_DELETE_REQUEST,
|
||||
PACKET_CLIENT_DELETE_RESPONSE,
|
||||
|
||||
PACKET_CLIENT_LICENSE_UPGRADE,
|
||||
PACKET_SERVER_LICENSE_UPGRADE_RESPONSE,
|
||||
|
||||
PACKET_PING = 0xF0,
|
||||
PACKET_DISCONNECT = 0xFF
|
||||
};
|
||||
|
||||
struct packet_header {
|
||||
PacketType packetId{0};
|
||||
uint16_t length{0};
|
||||
};
|
||||
|
||||
struct packet {
|
||||
struct {
|
||||
PacketType packetId{0};
|
||||
|
@ -1,11 +1,15 @@
|
||||
#include <netinet/tcp.h>
|
||||
#include <fcntl.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/memtracker.h>
|
||||
#include "crypt.h"
|
||||
|
||||
#define DEFINE_HELPER
|
||||
#include "LicenseRequest.h"
|
||||
#include "License.h"
|
||||
#include <csignal>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@ -16,7 +20,7 @@ using namespace license;
|
||||
#define CERR(message) LICENSE_FERR(this, CouldNotConnectException, message)
|
||||
|
||||
|
||||
LicenceRequest::LicenceRequest(const std::shared_ptr<LicenseRequestData> & license, const sockaddr_in& remoteAddr) : data(license) {
|
||||
LicenceRequest::LicenceRequest(const std::shared_ptr<LicenseRequestData> & license, const sockaddr_in& remoteAddr) : data{license} {
|
||||
#ifdef DEBUG_LICENSE_CLIENT
|
||||
memtrack::allocated<LicenceRequest>(this);
|
||||
#endif
|
||||
@ -31,13 +35,7 @@ LicenceRequest::~LicenceRequest() {
|
||||
#endif
|
||||
this->abortRequest();
|
||||
|
||||
if(this->closeThread) {
|
||||
this->closeThread->join();
|
||||
delete this->closeThread;
|
||||
this->closeThread = nullptr;
|
||||
}
|
||||
|
||||
|
||||
threads::save_join(this->closeThread);
|
||||
delete this->currentFuture;
|
||||
this->currentFuture = nullptr;
|
||||
}
|
||||
@ -179,7 +177,7 @@ void LicenceRequest::beginRequest() {
|
||||
}
|
||||
|
||||
void LicenceRequest::handleConnected() {
|
||||
this->state = protocol::HANDSCAKE;
|
||||
this->state = protocol::HANDSCHAKE;
|
||||
|
||||
uint8_t handshakeBuffer[4];
|
||||
handshakeBuffer[0] = 0xC0;
|
||||
@ -230,17 +228,16 @@ void LicenceRequest::disconnect(const std::string& message) {
|
||||
}
|
||||
|
||||
void LicenceRequest::closeConnection() {
|
||||
event *event_read, *event_write;
|
||||
event *event_read{nullptr}, *event_write{nullptr};
|
||||
{
|
||||
lock_guard lock(this->lock);
|
||||
lock_guard slock(this->lock);
|
||||
if(this->state == protocol::UNCONNECTED) return;
|
||||
|
||||
if(this->event_dispatch.get_id() == this_thread::get_id()) { //We could not close in the same thread as we read/write (we're joining it later)
|
||||
if(this->state == protocol::DISCONNECTING) return;
|
||||
|
||||
this->state = protocol::DISCONNECTING;
|
||||
this->closeThread = new threads::Thread(THREAD_SAVE_OPERATIONS, [&]() { this->closeConnection(); });
|
||||
|
||||
this->closeThread = std::thread(&LicenceRequest::closeConnection, this);
|
||||
#ifdef DEBUG_LICENSE_CLIENT
|
||||
if(this->verbose) {
|
||||
debugMessage(LOG_GENERAL,"Running close in a new thread");
|
||||
@ -251,11 +248,8 @@ void LicenceRequest::closeConnection() {
|
||||
}
|
||||
this->state = protocol::UNCONNECTED;
|
||||
|
||||
event_read = this->event_read;
|
||||
event_write = this->event_write;
|
||||
|
||||
this->event_write = nullptr;
|
||||
this->event_read = nullptr;
|
||||
std::swap(this->event_read, event_read);
|
||||
std::swap(this->event_write, event_write);
|
||||
}
|
||||
|
||||
if(event_read) {
|
||||
@ -271,7 +265,7 @@ void LicenceRequest::closeConnection() {
|
||||
/* close before base shutdown (else epoll hangup) */
|
||||
if(this->file_descriptor > 0) {
|
||||
shutdown(this->file_descriptor, SHUT_RDWR);
|
||||
close(this->file_descriptor);
|
||||
::close(this->file_descriptor);
|
||||
}
|
||||
this->file_descriptor = 0;
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <protocol/buffers.h>
|
||||
#include <ThreadPool/Mutex.h>
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <event.h>
|
||||
@ -96,7 +95,8 @@ namespace license {
|
||||
|
||||
void sendPacket(const protocol::packet&);
|
||||
|
||||
std::function<void(const WebCertificate&)> callback_update_certificate{nullptr};
|
||||
std::function<void(const WebCertificate& /* certificate */)> callback_update_certificate{nullptr};
|
||||
std::function<void(const std::string& /* new key */)> callback_update_license{nullptr};
|
||||
bool verbose = true;
|
||||
private:
|
||||
std::shared_ptr<LicenseRequestData> data;
|
||||
@ -111,12 +111,12 @@ namespace license {
|
||||
|
||||
std::string buffer{};
|
||||
|
||||
int file_descriptor = 0;
|
||||
std::thread event_dispatch;
|
||||
threads::Thread* closeThread = nullptr;
|
||||
struct event_base* event_base = nullptr;
|
||||
event* event_read = nullptr;
|
||||
event* event_write = nullptr;
|
||||
int file_descriptor{0};
|
||||
std::thread event_dispatch{};
|
||||
std::thread closeThread{};
|
||||
struct event_base* event_base{nullptr};
|
||||
struct event* event_read{nullptr};
|
||||
struct event* event_write{nullptr};
|
||||
|
||||
TAILQ_HEAD(, ts::buffer::RawBuffer) writeQueue;
|
||||
|
||||
|
@ -25,7 +25,7 @@ void LicenceRequest::handlePacketDisconnect(const std::string& message) {
|
||||
}
|
||||
|
||||
void LicenceRequest::handlePacketHandshake(const std::string& data) {
|
||||
if(this->state != protocol::HANDSCAKE) LICENSE_FERR(this, InvalidResponseException, "Protocol state mismatch");
|
||||
if(this->state != protocol::HANDSCHAKE) LICENSE_FERR(this, InvalidResponseException, "Protocol state mismatch");
|
||||
if(data.length() < 3) LICENSE_FERR(this, InvalidResponseException, "Invalid packet size");
|
||||
|
||||
if((uint8_t) data[0] != 0xAF || (uint8_t) data[1] != 0xFE) LICENSE_FERR(this, InvalidResponseException, "Invalid handshake");
|
||||
@ -54,14 +54,13 @@ void LicenceRequest::handlePacketHandshake(const std::string& data) {
|
||||
}
|
||||
|
||||
void LicenceRequest::handlePacketLicenseInfo(const std::string& message) {
|
||||
ts::proto::license::LicenseResponse response;
|
||||
ts::proto::license::LicenseResponse response{};
|
||||
if(!response.ParseFromString(message)) LICENSE_FERR(this, InvalidResponseException, "Could not parse response");
|
||||
|
||||
auto result = make_shared<LicenseRequestResponse>();
|
||||
auto licenseInfo = make_shared<LicenseInfo>();
|
||||
if(!response.has_license_info() && this->data->license && response.valid() && response.blacklist().state() == ts::proto::license::VALID) {
|
||||
if(!response.has_license_info() && this->data->license && response.valid() && response.blacklist().state() == ts::proto::license::VALID)
|
||||
LICENSE_FERR(this, InvalidResponseException, "Missing license info");
|
||||
}
|
||||
|
||||
if(this->data->license) {
|
||||
licenseInfo->type = (LicenseType) response.license_info().type();
|
||||
@ -88,7 +87,11 @@ void LicenceRequest::handlePacketLicenseInfo(const std::string& message) {
|
||||
result->license = licenseInfo;
|
||||
this->response = result;
|
||||
|
||||
ts::proto::license::PropertyUpdateRequest infos;
|
||||
if(response.has_update_pending()) {
|
||||
|
||||
}
|
||||
|
||||
ts::proto::license::PropertyUpdateRequest infos{};
|
||||
infos.set_speach_total(this->data->speach_total);
|
||||
infos.set_speach_dead(this->data->speach_dead);
|
||||
infos.set_speach_online(this->data->speach_online);
|
||||
|
527
license/shared/LicenseServerClient.cpp
Normal file
527
license/shared/LicenseServerClient.cpp
Normal file
@ -0,0 +1,527 @@
|
||||
//
|
||||
// Created by WolverinDEV on 23/02/2020.
|
||||
//
|
||||
|
||||
#include <csignal>
|
||||
#include <netinet/tcp.h>
|
||||
#include <event.h>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include <misc/endianness.h>
|
||||
#include "LicenseServerClient.h"
|
||||
#include "crypt.h"
|
||||
|
||||
using namespace license::client;
|
||||
|
||||
LicenseServerClient::Buffer* LicenseServerClient::Buffer::allocate(size_t capacity) {
|
||||
static_assert(std::is_trivially_constructible<Buffer>::value);
|
||||
|
||||
const auto allocated_bytes = sizeof(LicenseServerClient::Buffer) + capacity;
|
||||
auto result = malloc(allocated_bytes);
|
||||
if(!result) return nullptr;
|
||||
|
||||
auto buffer = reinterpret_cast<LicenseServerClient::Buffer*>(result);
|
||||
buffer->capacity = capacity;
|
||||
buffer->fill = 0;
|
||||
buffer->offset = 0;
|
||||
buffer->data = (char*) result + sizeof(LicenseServerClient::Buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void LicenseServerClient::Buffer::free(Buffer *ptr) {
|
||||
static_assert(std::is_trivially_destructible<Buffer>::value);
|
||||
|
||||
::free(ptr);
|
||||
}
|
||||
|
||||
LicenseServerClient::LicenseServerClient(const sockaddr_in &address, int pversion) : protocol_version{pversion} {
|
||||
memcpy(&this->network.address, &address, sizeof(address));
|
||||
TAILQ_INIT(&this->buffers.write);
|
||||
|
||||
if(!this->buffers.read)
|
||||
this->buffers.read = Buffer::allocate(1024 * 8);
|
||||
}
|
||||
|
||||
LicenseServerClient::~LicenseServerClient() {
|
||||
this->close_connection();
|
||||
|
||||
if(this->buffers.read)
|
||||
Buffer::free(this->buffers.read);
|
||||
threads::save_join(this->network.event_dispatch, false);
|
||||
}
|
||||
|
||||
bool LicenseServerClient::start_connection(std::string &error) {
|
||||
bool event_dispatch_spawned{false};
|
||||
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
if(this->connection_state != ConnectionState::UNCONNECTED) {
|
||||
error = "invalid connection state";
|
||||
return false;
|
||||
}
|
||||
|
||||
this->connection_state = ConnectionState::CONNECTING;
|
||||
this->communication.initialized = false;
|
||||
|
||||
this->network.file_descriptor = socket(this->network.address.sin_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
||||
if(this->network.file_descriptor < 0) {
|
||||
error = "failed to allocate socket";
|
||||
goto error_cleanup;
|
||||
}
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
{
|
||||
auto connect_state = ::connect(this->network.file_descriptor, reinterpret_cast<const sockaddr *>(&this->network.address), sizeof(this->network.address));
|
||||
if(connect_state < 0 && errno != EINPROGRESS) {
|
||||
error = "connect() failed (" + std::string{strerror(errno)} + ")";
|
||||
goto error_cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int enabled{1}, disabled{0};
|
||||
if(setsockopt(this->network.file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0); //CERR("could not set reuse addr");
|
||||
if(setsockopt(this->network.file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0); // CERR("could not set no push");
|
||||
|
||||
if(fcntl(this->network.file_descriptor, F_SETFD, fcntl(this->network.file_descriptor, F_GETFL, 0) | FD_CLOEXEC | O_NONBLOCK) < 0); // CERR("Failed to set FD_CLOEXEC and O_NONBLOCK (" + std::to_string(errno) + ")");
|
||||
}
|
||||
|
||||
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);
|
||||
client->callback_read(e);
|
||||
}, 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);
|
||||
client->callback_write(e);
|
||||
}, this);
|
||||
|
||||
event_dispatch_spawned = true;
|
||||
this->network.event_dispatch = std::thread([&] {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
event_add(this->network.event_read, nullptr);
|
||||
|
||||
timeval connect_timeout{5, 0};
|
||||
event_add(this->network.event_write, &connect_timeout);
|
||||
|
||||
auto event_base{this->network.event_base};
|
||||
event_base_loop(event_base, EVLOOP_NO_EXIT_ON_EMPTY);
|
||||
event_base_free(event_base);
|
||||
|
||||
//this ptr might be dangling
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void LicenseServerClient::close_connection() {
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
if(this->connection_state == ConnectionState::UNCONNECTED) return;
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
|
||||
this->cleanup_network_resources();
|
||||
}
|
||||
|
||||
void LicenseServerClient::cleanup_network_resources() {
|
||||
const auto is_event_loop = this->network.event_dispatch.get_id() == std::this_thread::get_id();
|
||||
|
||||
if(this->network.event_read) {
|
||||
if(is_event_loop) event_del_noblock(this->network.event_read);
|
||||
else event_del_block(this->network.event_read);
|
||||
event_free(this->network.event_read);
|
||||
this->network.event_read = nullptr;
|
||||
}
|
||||
|
||||
if(this->network.event_write) {
|
||||
if(is_event_loop) event_del_noblock(this->network.event_write);
|
||||
else event_del_block(this->network.event_write);
|
||||
event_free(this->network.event_write);
|
||||
this->network.event_write = nullptr;
|
||||
}
|
||||
|
||||
if(this->network.event_base) {
|
||||
event_base_loopexit(this->network.event_base, nullptr);
|
||||
if(!is_event_loop)
|
||||
threads::save_join(this->network.event_dispatch, false);
|
||||
this->network.event_base = nullptr; /* event base has been saved by the event dispatcher and will be freed there */
|
||||
}
|
||||
|
||||
if(this->network.file_descriptor) {
|
||||
::close(this->network.file_descriptor);
|
||||
this->network.file_descriptor = 0;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard block{this->buffers.lock};
|
||||
auto buffer = TAILQ_FIRST(&this->buffers.write);
|
||||
while(buffer) {
|
||||
auto next = TAILQ_NEXT(buffer, tail);
|
||||
Buffer::free(next);
|
||||
buffer = next;
|
||||
}
|
||||
TAILQ_INIT(&this->buffers.write);
|
||||
this->buffers.notify_empty.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseServerClient::callback_read(short events) {
|
||||
constexpr static auto buffer_size{1024};
|
||||
|
||||
ssize_t read_bytes{0};
|
||||
char buffer[buffer_size];
|
||||
|
||||
read_bytes = recv(this->network.file_descriptor, buffer, buffer_size, MSG_DONTWAIT);
|
||||
if(read_bytes <= 0) {
|
||||
if(errno == EAGAIN) return;
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
|
||||
std::string disconnect_reason{};
|
||||
bool disconnect_expected{false};
|
||||
switch (this->connection_state) {
|
||||
case ConnectionState::CONNECTING:
|
||||
disconnect_reason = "connect error (" + std::string{strerror(errno)} + ")";
|
||||
disconnect_expected = false;
|
||||
break;
|
||||
case ConnectionState::INITIALIZING:
|
||||
case ConnectionState::CONNECTED:
|
||||
disconnect_reason = "read error (" + std::string{strerror(errno)} + ")";
|
||||
disconnect_expected = false;
|
||||
break;
|
||||
case ConnectionState::DISCONNECTING:
|
||||
disconnect_expected = true;
|
||||
break;
|
||||
case ConnectionState::UNCONNECTED:
|
||||
return; /* we're obsolete */
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_disconnected}; callback) {
|
||||
slock.unlock();
|
||||
callback(disconnect_expected, disconnect_reason);
|
||||
slock.lock();
|
||||
}
|
||||
|
||||
if(this->connection_state != ConnectionState::UNCONNECTED) {
|
||||
this->cleanup_network_resources();
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this->handle_data(buffer, (size_t) read_bytes);
|
||||
}
|
||||
|
||||
void LicenseServerClient::callback_write(short events) {
|
||||
bool add_write_event{this->connection_state == ConnectionState::DISCONNECTING};
|
||||
if(events & EV_TIMEOUT) {
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
if(this->connection_state == ConnectionState::CONNECTING || this->connection_state == ConnectionState::INITIALIZING) {
|
||||
/* connect timeout */
|
||||
if(auto callback{this->callback_disconnected}; callback) {
|
||||
slock.unlock();
|
||||
callback(false, "connect timeout");
|
||||
slock.lock();
|
||||
}
|
||||
|
||||
if(this->connection_state != ConnectionState::UNCONNECTED) {
|
||||
this->cleanup_network_resources();
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
} else if(this->connection_state == ConnectionState::DISCONNECTING) {
|
||||
/* disconnect timeout */
|
||||
this->cleanup_network_resources();
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(events & EV_WRITE) {
|
||||
if(this->connection_state == ConnectionState::CONNECTING)
|
||||
this->callback_socket_connected();
|
||||
|
||||
ssize_t written_bytes{0};
|
||||
|
||||
std::unique_lock block{this->buffers.lock};
|
||||
auto buffer = TAILQ_FIRST(&this->buffers.write);
|
||||
if(!buffer) {
|
||||
this->buffers.notify_empty.notify_all();
|
||||
return;
|
||||
}
|
||||
block.unlock();
|
||||
written_bytes = send(this->network.file_descriptor, (char*) buffer->data + buffer->offset, buffer->fill - buffer->offset, MSG_DONTWAIT);
|
||||
|
||||
if(written_bytes <= 0) {
|
||||
if(errno == EAGAIN) goto readd_event;
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
|
||||
std::string disconnect_reason{};
|
||||
bool disconnect_expected{false};
|
||||
switch (this->connection_state) {
|
||||
case ConnectionState::CONNECTING:
|
||||
case ConnectionState::INITIALIZING:
|
||||
case ConnectionState::CONNECTED:
|
||||
disconnect_reason = "write error (" + std::string{strerror(errno)} + ")";
|
||||
disconnect_expected = false;
|
||||
break;
|
||||
case ConnectionState::DISCONNECTING:
|
||||
disconnect_expected = true;
|
||||
break;
|
||||
case ConnectionState::UNCONNECTED:
|
||||
return; /* we're obsolete */
|
||||
}
|
||||
if(auto callback{this->callback_disconnected}; callback) {
|
||||
slock.unlock();
|
||||
callback(disconnect_expected, disconnect_reason);
|
||||
slock.lock();
|
||||
}
|
||||
|
||||
if(this->connection_state != ConnectionState::UNCONNECTED) {
|
||||
this->cleanup_network_resources();
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
buffer->offset += (size_t) written_bytes;
|
||||
if(buffer->offset >= buffer->fill) {
|
||||
assert(buffer->offset == buffer->fill);
|
||||
block.lock();
|
||||
TAILQ_REMOVE(&this->buffers.write, buffer, tail);
|
||||
if(!TAILQ_FIRST(&this->buffers.write)) {
|
||||
this->buffers.notify_empty.notify_all();
|
||||
} else {
|
||||
add_write_event = true;
|
||||
}
|
||||
block.unlock();
|
||||
Buffer::free(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if(this->network.event_write && add_write_event) {
|
||||
readd_event:
|
||||
auto timeout = this->disconnect_timeout;
|
||||
if(timeout.time_since_epoch().count() == 0)
|
||||
event_add(this->network.event_write, nullptr);
|
||||
else {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
struct timeval t{0, 1};
|
||||
if(now > timeout) {
|
||||
this->callback_write(EV_TIMEOUT);
|
||||
return;
|
||||
} else {
|
||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(timeout - now);
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(microseconds);
|
||||
microseconds -= seconds;
|
||||
|
||||
t.tv_usec = microseconds.count();
|
||||
t.tv_sec = seconds.count();
|
||||
}
|
||||
event_add(this->network.event_write, &t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseServerClient::handle_data(void *recv_buffer, size_t length) {
|
||||
auto& buffer = this->buffers.read;
|
||||
assert(buffer);
|
||||
|
||||
if(buffer->capacity - buffer->offset - buffer->fill < length) {
|
||||
if(buffer->capacity - buffer->fill > length) {
|
||||
memcpy(buffer->data, (char*) buffer->data + buffer->offset, buffer->fill);
|
||||
buffer->offset = 0;
|
||||
} else {
|
||||
auto new_buffer = Buffer::allocate(buffer->fill + length);
|
||||
memcpy(new_buffer->data, (char*) buffer->data + buffer->offset, buffer->fill);
|
||||
new_buffer->fill = buffer->fill;
|
||||
Buffer::free(buffer);
|
||||
buffer = new_buffer;
|
||||
}
|
||||
}
|
||||
auto buffer_ptr = (char*) buffer->data;
|
||||
auto& buffer_offset = buffer->offset;
|
||||
auto& buffer_length = buffer->fill;
|
||||
|
||||
memcpy((char*) buffer_ptr + buffer_offset + buffer_length, recv_buffer, length);
|
||||
buffer_length += length;
|
||||
|
||||
while(true) {
|
||||
if(buffer_length < sizeof(protocol::packet_header)) return;
|
||||
|
||||
auto header = reinterpret_cast<protocol::packet_header*>(buffer_ptr + buffer_offset);
|
||||
if(header->length > 1024 * 8) {
|
||||
if(auto callback{this->callback_disconnected}; callback)
|
||||
callback(false, "received a too large message");
|
||||
this->disconnect("received too large message", std::chrono::system_clock::time_point{});
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
buffer_offset += header->length + sizeof(protocol::packet_header);
|
||||
buffer_length -= header->length + sizeof(protocol::packet_header);
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseServerClient::send_message(protocol::PacketType type, const void *payload, size_t size) {
|
||||
const auto packet_size = size + sizeof(protocol::packet_header);
|
||||
auto buffer = Buffer::allocate(packet_size);
|
||||
buffer->fill = packet_size;
|
||||
|
||||
auto header = (protocol::packet_header*) buffer->data;
|
||||
header->length = packet_size;
|
||||
header->packetId = type;
|
||||
memcpy((char*) buffer->data + sizeof(protocol::packet_header), payload, size);
|
||||
if(this->communication.initialized)
|
||||
xorBuffer((char*) buffer->data + sizeof(protocol::packet_header), size, this->communication.crypt_key.data(), this->communication.crypt_key.length());
|
||||
|
||||
std::lock_guard clock{this->connection_lock};
|
||||
if(this->connection_state == ConnectionState::UNCONNECTED || !this->network.event_write) {
|
||||
Buffer::free(buffer);
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard block{this->buffers.lock};
|
||||
TAILQ_INSERT_TAIL(&this->buffers.write, buffer, tail);
|
||||
}
|
||||
event_add(this->network.event_write, nullptr);
|
||||
}
|
||||
|
||||
void LicenseServerClient::disconnect(const std::string &message, std::chrono::system_clock::time_point timeout) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(now > timeout)
|
||||
timeout = now + std::chrono::seconds{timeout.time_since_epoch().count() ? 1 : 0};
|
||||
|
||||
std::unique_lock clock{this->connection_lock};
|
||||
if(this->connection_state == ConnectionState::DISCONNECTING) {
|
||||
this->disconnect_timeout = std::min(this->disconnect_timeout, timeout);
|
||||
if(this->network.event_write)
|
||||
event_add(this->network.event_write, nullptr); /* let the write update the timeout */
|
||||
return;
|
||||
}
|
||||
this->disconnect_timeout = timeout;
|
||||
|
||||
if(this->connection_state != ConnectionState::INITIALIZING && this->connection_state != ConnectionState::CONNECTED) {
|
||||
clock.unlock();
|
||||
this->close_connection();
|
||||
return;
|
||||
}
|
||||
|
||||
this->connection_state = ConnectionState::DISCONNECTING;
|
||||
if(this->network.event_read)
|
||||
event_del_noblock(this->network.event_read);
|
||||
clock.unlock();
|
||||
|
||||
this->send_message(protocol::PACKET_DISCONNECT, message.data(), message.length());
|
||||
}
|
||||
|
||||
bool LicenseServerClient::await_disconnect() {
|
||||
{
|
||||
std::lock_guard clock{this->connection_lock};
|
||||
if(this->connection_state != ConnectionState::DISCONNECTING)
|
||||
return this->connection_state == ConnectionState::UNCONNECTED;
|
||||
}
|
||||
/* state might change here, but when we're disconnected the write buffer will be empty */
|
||||
std::unique_lock block{this->buffers.lock};
|
||||
while(TAILQ_FIRST(&this->buffers.write))
|
||||
this->buffers.notify_empty.wait(block);
|
||||
|
||||
return std::chrono::system_clock::now() <= this->disconnect_timeout;
|
||||
}
|
||||
|
||||
void LicenseServerClient::callback_socket_connected() {
|
||||
{
|
||||
std::lock_guard clock{this->connection_lock};
|
||||
if(this->connection_state != ConnectionState::CONNECTING) return;
|
||||
this->connection_state = ConnectionState::INITIALIZING;
|
||||
}
|
||||
|
||||
uint8_t handshakeBuffer[4];
|
||||
handshakeBuffer[0] = 0xC0;
|
||||
handshakeBuffer[1] = 0xFF;
|
||||
handshakeBuffer[2] = 0xEE;
|
||||
handshakeBuffer[3] = this->protocol_version;
|
||||
|
||||
this->send_message(protocol::PACKET_CLIENT_HANDSHAKE, handshakeBuffer, 4);
|
||||
}
|
||||
|
||||
void LicenseServerClient::handle_raw_packet(license::protocol::PacketType type, void * buffer, size_t length) {
|
||||
/* decrypt packet */
|
||||
if(this->communication.initialized)
|
||||
xorBuffer((char*) buffer, length, this->communication.crypt_key.data(), this->communication.crypt_key.length());
|
||||
|
||||
if(type == protocol::PACKET_DISCONNECT) {
|
||||
if(auto callback{this->callback_disconnected}; callback)
|
||||
callback(false, std::string{(const char*) buffer, length});
|
||||
this->close_connection();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this->communication.initialized) {
|
||||
if(type != protocol::PACKET_SERVER_HANDSHAKE) {
|
||||
if(auto callback{this->callback_disconnected}; callback)
|
||||
callback(false, "expected handshake packet");
|
||||
this->disconnect("expected handshake packet", std::chrono::system_clock::time_point{});
|
||||
return;
|
||||
}
|
||||
|
||||
this->handle_handshake_packet(buffer, length);
|
||||
this->communication.initialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_message}; callback)
|
||||
callback(type, buffer, length);
|
||||
else
|
||||
; //TODO: Print error?
|
||||
}
|
||||
|
||||
void LicenseServerClient::handle_handshake_packet(void *buffer, size_t length) {
|
||||
const auto data_ptr = (const char*) buffer;
|
||||
|
||||
std::string error{};
|
||||
if(this->connection_state != ConnectionState::INITIALIZING) {
|
||||
error = "invalid protocol state";
|
||||
goto handle_error;
|
||||
}
|
||||
|
||||
if(length < 5) {
|
||||
error = "invalid packet size";
|
||||
goto handle_error;
|
||||
}
|
||||
|
||||
if((uint8_t) data_ptr[0] != 0xAF || (uint8_t) data_ptr[1] != 0xFE) {
|
||||
error = "invalid handshake signature";
|
||||
goto handle_error;
|
||||
}
|
||||
if((uint8_t) data_ptr[2] != this->protocol_version) {
|
||||
error = "Invalid license protocol version. Please update TeaSpeak!";
|
||||
goto handle_error;
|
||||
}
|
||||
|
||||
{
|
||||
auto key_length = be2le16(data_ptr, 3);
|
||||
if(length < key_length + 5) {
|
||||
error = "invalid packet size";
|
||||
goto handle_error;
|
||||
}
|
||||
this->communication.crypt_key = std::string(data_ptr + 5, key_length);
|
||||
this->communication.initialized = true;
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_connected}; callback)
|
||||
callback();
|
||||
return;
|
||||
|
||||
handle_error:
|
||||
if(auto callback{this->callback_disconnected}; callback)
|
||||
callback(false, error);
|
||||
this->disconnect(error, std::chrono::system_clock::time_point{});
|
||||
}
|
97
license/shared/LicenseServerClient.h
Normal file
97
license/shared/LicenseServerClient.h
Normal file
@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <protocol/buffers.h>
|
||||
#include <netinet/in.h>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
#include "./License.h"
|
||||
|
||||
namespace license::client {
|
||||
class LicenseServerClient {
|
||||
public:
|
||||
enum ConnectionState {
|
||||
CONNECTING,
|
||||
INITIALIZING,
|
||||
CONNECTED,
|
||||
DISCONNECTING,
|
||||
|
||||
UNCONNECTED
|
||||
};
|
||||
typedef std::function<void()> callback_connected_t;
|
||||
typedef std::function<void(protocol::PacketType /* type */, const void* /* payload */, size_t /* length */)> callback_message_t;
|
||||
typedef std::function<void(bool /* expected */, const std::string& /* reason */)> callback_disconnect_t;
|
||||
|
||||
explicit LicenseServerClient(const sockaddr_in&, int /* protocol version */);
|
||||
virtual ~LicenseServerClient();
|
||||
|
||||
bool start_connection(std::string& /* error */);
|
||||
void send_message(protocol::PacketType /* type */, const void* /* buffer */, size_t /* length */);
|
||||
void close_connection();
|
||||
|
||||
void disconnect(const std::string& /* reason */, std::chrono::system_clock::time_point /* timeout */);
|
||||
bool await_disconnect();
|
||||
|
||||
|
||||
/*
|
||||
* Events will be called within the event loop.
|
||||
* All methods are save to call.
|
||||
* When close_connection or await_disconnect has been called these methods will not be called anymore.
|
||||
*/
|
||||
callback_message_t callback_message{nullptr};
|
||||
callback_connected_t callback_connected{nullptr};
|
||||
callback_disconnect_t callback_disconnected{nullptr};
|
||||
|
||||
const int protocol_version;
|
||||
private:
|
||||
std::mutex connection_lock{};
|
||||
ConnectionState connection_state{ConnectionState::UNCONNECTED};
|
||||
std::chrono::system_clock::time_point disconnect_timeout{};
|
||||
|
||||
struct Buffer {
|
||||
static Buffer* allocate(size_t /* capacity */);
|
||||
static void free(Buffer* /* ptr */);
|
||||
|
||||
void* data;
|
||||
size_t capacity;
|
||||
size_t fill;
|
||||
size_t offset;
|
||||
|
||||
TAILQ_ENTRY(Buffer) tail;
|
||||
};
|
||||
|
||||
/* modify everything here only within the event base, or when exited when connection_lock is locked */
|
||||
struct {
|
||||
sockaddr_in address{};
|
||||
int file_descriptor{0};
|
||||
|
||||
std::thread event_dispatch{};
|
||||
struct event_base* event_base{nullptr}; /* will be cleaned up by the event loop! */
|
||||
struct event* event_read{nullptr};
|
||||
struct event* event_write{nullptr};
|
||||
} network;
|
||||
|
||||
struct {
|
||||
std::mutex lock{};
|
||||
std::condition_variable notify_empty{};
|
||||
|
||||
Buffer* read{nullptr}; /* must noch be accessed via lock because only the event loop uses it */
|
||||
TAILQ_HEAD(, Buffer) write;
|
||||
} buffers;
|
||||
|
||||
struct {
|
||||
bool initialized{false};
|
||||
std::string crypt_key{};
|
||||
} communication;
|
||||
|
||||
void callback_read(short /* events */);
|
||||
void callback_write(short /* events */);
|
||||
void callback_socket_connected();
|
||||
|
||||
void cleanup_network_resources();
|
||||
|
||||
void handle_data(void*, size_t);
|
||||
void handle_raw_packet(protocol::PacketType /* type */, void* /* payload */, size_t /* length */);
|
||||
void handle_handshake_packet(void* /* payload */, size_t /* length */);
|
||||
};
|
||||
}
|
@ -124,7 +124,6 @@ set(SERVER_SOURCE_FILES
|
||||
src/server/WebIoManager.cpp
|
||||
src/client/SpeakingClient.cpp
|
||||
|
||||
src/lincense/LicenseHelper.cpp
|
||||
../shared/src/ssl/SSLManager.cpp
|
||||
|
||||
src/manager/SqlDataManager.cpp
|
||||
@ -151,7 +150,7 @@ if (COMPILE_WEB_CLIENT)
|
||||
src/client/web/WSWebClient.cpp
|
||||
src/client/web/SampleHandler.cpp
|
||||
src/client/web/VoiceBridge.cpp
|
||||
src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h)
|
||||
src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h src/lincense/LicenseService.cpp src/lincense/LicenseService.h)
|
||||
endif ()
|
||||
|
||||
add_executable(PermHelper helpers/permgen.cpp)
|
||||
|
@ -3,3 +3,8 @@ Build opus:
|
||||
|
||||
Build libevent 2.1.8 (with -fPIC)
|
||||
|
||||
|
||||
|
||||
Local licenses test:
|
||||
Base: AQDm4ty6AAAAACnNuqvy++NGfMJU+Bvo5412Wi5jM5/JmmYyDbcxWKNaJV0FF1jpXtSrss3Gm7RUEQ6CdEEhPdyH5OLcugAAAAAp9cb+t1r6P5zGt9fi+QbAOroWM7LqY0B+iy055XrZxX+r8bfP50esuU9qypU9DUtnE9+qJ2gZmOgHcVkHJ4JhN4Db2ZmOkA4dpjGWfY2L6QJanPCy6HsVOm1/1LasZv9Om1/4uWHYLfab0n2+FNajxbcyDY3FAI6+rLBlQzGErtE=
|
||||
New: AQCJON26AAAAACnNuqvy++NGfMJU+Bvo5412Wi5jM5/JmmYyDbcxWKNaJV0FF1jpXtSrss3Gm7RUEQ6CdEEhPdyHizjdugAAAAAp6d+xUgRteE54jqv3OXoRS946xsWKUtJBFkB4pPMT1+pIrXhM3/CTWjpXG8KDLoa1DEjwguolTXJ+RCvySR7Bd4Db2ZmOkA4dpjGWfY2L6QJ3bxZH8OTPFlV/1lU9x1r5QPPOpFmZt68h6VZDz4y9AVNXO+JZPHu24Pre7hTnyhg=
|
@ -1,6 +1,5 @@
|
||||
#include <client/linux/handler/exception_handler.h>
|
||||
#include <iostream>
|
||||
#include <misc/endianness.h>
|
||||
#include <misc/strobf.h>
|
||||
#include <CXXTerminal/QuickTerminal.h>
|
||||
#include <event2/thread.h>
|
||||
@ -14,7 +13,6 @@
|
||||
#include "src/server/file/FileServer.h"
|
||||
#include "src/terminal/CommandHandler.h"
|
||||
#include "src/client/InternalClient.h"
|
||||
#include "src/music/MusicBotManager.h"
|
||||
#include "src/SignalHandler.h"
|
||||
#include "src/build.h"
|
||||
|
||||
@ -126,8 +124,11 @@ int main(int argc, char** argv) {
|
||||
assert(evthread_use_pthreads_result == 0);
|
||||
(void) evthread_use_pthreads_result;
|
||||
}
|
||||
terminal::install();
|
||||
if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
|
||||
|
||||
if(!arguments.cmdOptionExists("--no-terminal")) {
|
||||
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")) {
|
||||
@ -276,6 +277,8 @@ int main(int argc, char** argv) {
|
||||
logConfig->file_colored = ts::config::log::logfileColored;
|
||||
logConfig->logPath = ts::config::log::path;
|
||||
logConfig->vs_group_size = ts::config::log::vs_size;
|
||||
logConfig->sync = !terminal::instance();
|
||||
|
||||
logger::setup(logConfig);
|
||||
threads::timer::function_log = [](const std::string& message, bool debug) {
|
||||
auto msg = message.find('\n') == std::string::npos ? message : message.substr(0, message.find('\n'));
|
||||
@ -309,13 +312,13 @@ int main(int argc, char** argv) {
|
||||
logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true));
|
||||
logMessage(LOG_GENERAL, "Starting music providers");
|
||||
|
||||
terminal::instance()->setPrompt("§aStarting server. §7[§aloading music§7]");
|
||||
if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§aloading music§7]");
|
||||
if(ts::config::music::enabled && !arguments.cmdOptionExists("--no-providers")) {
|
||||
::music::manager::loadProviders("providers");
|
||||
::music::manager::register_provider(::music::provider::ChannelProvider::create_provider());
|
||||
}
|
||||
|
||||
terminal::instance()->setPrompt("§aStarting server. §7[§aloading geoloc§7]");
|
||||
if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§aloading geoloc§7]");
|
||||
|
||||
if(!ts::config::geo::staticFlag) {
|
||||
if(ts::config::geo::type == geoloc::PROVIDER_SOFTWARE77)
|
||||
@ -342,7 +345,7 @@ int main(int argc, char** argv) {
|
||||
errorMessage = "";
|
||||
}
|
||||
}
|
||||
terminal::instance()->setPrompt("§aStarting server. §7[§aloading sql§7]");
|
||||
if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§aloading sql§7]");
|
||||
|
||||
sql = new ts::server::SqlDataManager();
|
||||
if(!sql->initialize(errorMessage)) {
|
||||
@ -358,7 +361,7 @@ int main(int argc, char** argv) {
|
||||
goto stopApp;
|
||||
}
|
||||
|
||||
terminal::instance()->setPrompt("§aStarting server. §7[§astarting instance§7]");
|
||||
if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§astarting instance§7]");
|
||||
|
||||
serverInstance = new ts::server::InstanceHandler(sql); //if error than mainThreadActive = false
|
||||
if(!mainThreadActive || !serverInstance->startInstance())
|
||||
@ -384,13 +387,15 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
terminal::instance()->setPrompt("§7> §f");
|
||||
if(terminal::instance()) terminal::instance()->setPrompt("§7> §f");
|
||||
while(mainThreadActive) {
|
||||
usleep(5 * 1000);
|
||||
|
||||
if(terminal::instance()->linesAvailable() > 0){
|
||||
while(!(line = terminal::instance()->readLine("§7> §f")).empty())
|
||||
threads::Thread(THREAD_DETACHED, [line](){ terminal::chandler::handleCommand(line); });
|
||||
if(terminal::instance()) {
|
||||
if(terminal::instance()->linesAvailable() > 0){
|
||||
while(!(line = terminal::instance()->readLine("§7> §f")).empty())
|
||||
threads::Thread(THREAD_DETACHED, [line](){ terminal::chandler::handleCommand(line); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -411,7 +416,8 @@ int main(int argc, char** argv) {
|
||||
logMessageFmt(true, LOG_GENERAL, "Application suspend successful!");
|
||||
|
||||
logger::uninstall();
|
||||
terminal::uninstall();
|
||||
if(terminal::active())
|
||||
terminal::uninstall();
|
||||
mainThreadDone = true;
|
||||
return 0;
|
||||
}
|
@ -341,7 +341,7 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
}
|
||||
cfgStream.close();
|
||||
|
||||
map<string,deque<string>> comments;
|
||||
std::map<std::string, std::deque<std::string>> comments;
|
||||
try {
|
||||
int config_version;
|
||||
string teaspeak_license;
|
||||
@ -351,9 +351,9 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
build_comments(comments, bindings);
|
||||
}
|
||||
if(config_version > CURRENT_CONFIG_VERSION) {
|
||||
errors.push_back("Given config version is higher that currently supported config version!");
|
||||
errors.emplace_back("Given config version is higher that currently supported config version!");
|
||||
errors.push_back("Decrease the version by hand to " + to_string(CURRENT_CONFIG_VERSION));
|
||||
errors.push_back("Attention: Decreasing the version could may lead to data loss!");
|
||||
errors.emplace_back("Attention: Decreasing the version could may lead to data loss!");
|
||||
return errors;
|
||||
}
|
||||
{
|
||||
@ -462,15 +462,13 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
}
|
||||
|
||||
if(!config::license){
|
||||
logErrorFmt(true, LOG_GENERAL, strobf("The given license isn't valid!").string());
|
||||
logErrorFmt(true, LOG_GENERAL, strobf("The given license could not be parsed!").string());
|
||||
logErrorFmt(true, LOG_GENERAL, strobf("Falling back to the default license.").string());
|
||||
teaspeak_license = "none";
|
||||
goto license_parsing;
|
||||
}
|
||||
if(!config::license){
|
||||
errors.emplace_back(strobf("Invalid license code!").string());
|
||||
return errors;
|
||||
}
|
||||
|
||||
/*
|
||||
if(!config::license->isValid()) {
|
||||
if(config::license->data.type == license::LicenseType::INVALID) {
|
||||
errors.emplace_back(strobf("Give license isn't valid!").string());
|
||||
@ -482,6 +480,7 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
teaspeak_license = "none";
|
||||
goto license_parsing;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
{
|
||||
@ -548,8 +547,7 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
}
|
||||
|
||||
std::vector<std::string> config::reload() {
|
||||
|
||||
vector<string> errors;
|
||||
std::vector<std::string> errors;
|
||||
saveConfig = false;
|
||||
|
||||
ifstream cfgStream(_config_path);
|
||||
@ -589,6 +587,53 @@ std::vector<std::string> config::reload() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
bool config::update_license(std::string &error, const std::string &new_license) {
|
||||
std::vector<std::string> lines{};
|
||||
|
||||
{
|
||||
lines.reserve(1024);
|
||||
|
||||
std::ifstream icfg_stream{_config_path};
|
||||
if(!icfg_stream) {
|
||||
error = "failed to open config file";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string line{};
|
||||
while(std::getline(icfg_stream, line))
|
||||
lines.push_back(line);
|
||||
|
||||
icfg_stream.close();
|
||||
}
|
||||
|
||||
bool license_found{false};
|
||||
for(auto& line : lines) {
|
||||
if(!line.starts_with(" license:")) continue;
|
||||
|
||||
line = " license: \"" + new_license + "\"";
|
||||
license_found = true;
|
||||
break;
|
||||
}
|
||||
if(!license_found) {
|
||||
error = "missing license config key";
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::ofstream ocfg_stream{_config_path};
|
||||
if(!ocfg_stream) {
|
||||
error = "failed to write to config file";
|
||||
return false;
|
||||
}
|
||||
|
||||
for(const auto& line : lines)
|
||||
ocfg_stream << line << "\n";
|
||||
ocfg_stream << std::flush;
|
||||
ocfg_stream.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void bind_string_description(const shared_ptr<EntryBinding>& _entry, std::string& target, const std::string& default_value) {
|
||||
_entry->default_value = [default_value]() -> std::deque<std::string> { return { default_value }; };
|
||||
_entry->value_description = [] { return "The value must be a string"; };
|
||||
|
@ -29,6 +29,7 @@ namespace ts::config {
|
||||
std::function<void(const std::string&)> read_argument;
|
||||
};
|
||||
|
||||
extern bool update_license(std::string& /* error */, const std::string& /* new license */);
|
||||
extern std::vector<std::string> parseConfig(const std::string& /* path */);
|
||||
extern std::vector<std::string> reload();
|
||||
extern std::deque<std::shared_ptr<EntryBinding>> create_bindings();
|
||||
|
@ -44,7 +44,11 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr, true);
|
||||
this->statistics->measure_bandwidths(true);
|
||||
|
||||
this->licenseHelper = make_shared<license::LicenseHelper>();
|
||||
std::string error_message{};
|
||||
this->license_service_ = std::make_shared<license::LicenseService>();
|
||||
if(!this->license_service_->initialize(error_message)) {
|
||||
logCritical(LOG_INSTANCE, strobf("Failed to the license service: {}").string(), error_message);
|
||||
}
|
||||
this->dbHelper = new DatabaseHelper(this->getSql());
|
||||
|
||||
this->_properties = new Properties();
|
||||
@ -214,7 +218,6 @@ InstanceHandler::~InstanceHandler() {
|
||||
globalServerAdmin = nullptr;
|
||||
_musicRoot = nullptr;
|
||||
|
||||
licenseHelper = nullptr;
|
||||
statistics = nullptr;
|
||||
tick_manager = nullptr;
|
||||
}
|
||||
@ -453,6 +456,8 @@ void InstanceHandler::stopInstance() {
|
||||
this->sslMgr = nullptr;
|
||||
|
||||
this->web_event_loop = nullptr;
|
||||
|
||||
this->license_service_->shutdown();
|
||||
}
|
||||
|
||||
void InstanceHandler::tickInstance() {
|
||||
@ -470,7 +475,7 @@ void InstanceHandler::tickInstance() {
|
||||
}
|
||||
{
|
||||
ALARM_TIMER(t, strobf("InstanceHandler::tickInstance -> license tick").string(), milliseconds(5));
|
||||
this->licenseHelper->tick();
|
||||
this->license_service_->execute_tick();
|
||||
}
|
||||
}
|
||||
{
|
||||
@ -637,36 +642,35 @@ string get_mac_address() {
|
||||
}
|
||||
|
||||
#define SN_BUFFER 1024
|
||||
std::shared_ptr<license::LicenseRequestData> InstanceHandler::generateLicenseData() {
|
||||
auto request = make_shared<license::LicenseRequestData>();
|
||||
std::shared_ptr<ts::server::license::InstanceLicenseInfo> InstanceHandler::generateLicenseData() {
|
||||
auto request = std::make_shared<license::InstanceLicenseInfo>();
|
||||
request->license = config::license;
|
||||
request->servers_online = this->voiceServerManager->runningServers();
|
||||
request->metrics.servers_online = this->voiceServerManager->runningServers();
|
||||
auto report = this->voiceServerManager->clientReport();
|
||||
request->client_online = report.clients_ts;
|
||||
request->web_clients_online = report.clients_web;
|
||||
request->bots_online = report.bots;
|
||||
request->queries_online = report.queries;
|
||||
request->speach_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as<uint64_t>();
|
||||
request->speach_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
|
||||
request->speach_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>();
|
||||
request->speach_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>();
|
||||
request->metrics.client_online = report.clients_ts;
|
||||
request->metrics.web_clients_online = report.clients_web;
|
||||
request->metrics.bots_online = report.bots;
|
||||
request->metrics.queries_online = report.queries;
|
||||
request->metrics.speech_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as<uint64_t>();
|
||||
request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
|
||||
request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>();
|
||||
request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>();
|
||||
|
||||
static std::string null_str{"\0\0\0\0\0\0\0\0", 8}; /* we need at least some characters */
|
||||
request->web_certificate_revision = this->web_cert_revision.empty() ? null_str : this->web_cert_revision;
|
||||
|
||||
{
|
||||
auto info = make_shared<license::ServerInfo>();
|
||||
info->timestamp = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
info->version = build::version()->string(true);
|
||||
request->info.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
request->info.version = build::version()->string(true);
|
||||
|
||||
{ /* uname */
|
||||
utsname retval{};
|
||||
if(uname(&retval) < 0) {
|
||||
info->uname = "unknown (" + string(strerror(errno)) + ")";
|
||||
request->info.uname = "unknown (" + string(strerror(errno)) + ")";
|
||||
} else {
|
||||
char buffer[SN_BUFFER];
|
||||
snprintf(buffer, SN_BUFFER, "sys:%s version:%s release:%s", retval.sysname, retval.version, retval.release);
|
||||
info->uname = string(buffer);
|
||||
request->info.uname = string(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
@ -676,12 +680,10 @@ std::shared_ptr<license::LicenseRequestData> InstanceHandler::generateLicenseDat
|
||||
if(property_unique_id.as<string>().empty())
|
||||
property_unique_id = rnd_string(64);
|
||||
|
||||
auto hash = digest::sha256(info->uname);
|
||||
auto hash = digest::sha256(request->info.uname);
|
||||
hash = digest::sha256(hash + property_unique_id.as<string>() + get_mac_address());
|
||||
info->unique_identifier = base64::encode(hash);
|
||||
request->info.unique_id = base64::encode(hash);
|
||||
}
|
||||
|
||||
request->info = info;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
@ -3,9 +3,8 @@
|
||||
#include <sql/SqlQuery.h>
|
||||
#include <Properties.h>
|
||||
#include "VirtualServerManager.h"
|
||||
#include "../../license/shared/LicenseRequest.h"
|
||||
#include "lincense/LicenseHelper.h"
|
||||
#include <ssl/SSLManager.h>
|
||||
#include <src/lincense/LicenseService.h>
|
||||
#include "manager/SqlDataManager.h"
|
||||
#include "lincense/TeamSpeakLicense.h"
|
||||
#include "server/WebIoManager.h"
|
||||
@ -20,6 +19,10 @@ namespace ts {
|
||||
}
|
||||
|
||||
namespace server {
|
||||
namespace license {
|
||||
class LicenseService;
|
||||
}
|
||||
|
||||
class InstanceHandler {
|
||||
public:
|
||||
explicit InstanceHandler(SqlDataManager*);
|
||||
@ -63,7 +66,7 @@ namespace ts {
|
||||
|
||||
std::shared_ptr<stats::ConnectionStatistics> getStatistics(){ return statistics; }
|
||||
std::shared_ptr<threads::Scheduler> scheduler(){ return this->tick_manager; }
|
||||
std::shared_ptr<license::LicenseRequestData> generateLicenseData();
|
||||
std::shared_ptr<license::InstanceLicenseInfo> generateLicenseData();
|
||||
|
||||
std::shared_ptr<TeamSpeakLicense> getTeamSpeakLicense() { return this->teamspeak_license; }
|
||||
std::shared_ptr<ts::Properties> getDefaultServerProperties() { return this->default_server_properties; }
|
||||
@ -90,6 +93,8 @@ namespace ts {
|
||||
bool granted = false,
|
||||
std::shared_ptr<CalculateCache> cache = nullptr
|
||||
);
|
||||
|
||||
[[nodiscard]] inline std::shared_ptr<license::LicenseService> license_service() { return this->license_service_; }
|
||||
private:
|
||||
std::mutex activeLock;
|
||||
std::condition_variable activeCon;
|
||||
@ -126,7 +131,7 @@ namespace ts {
|
||||
std::shared_ptr<ts::server::InternalClient> globalServerAdmin = nullptr;
|
||||
std::shared_ptr<ConnectedClient> _musicRoot = nullptr;
|
||||
|
||||
std::shared_ptr<license::LicenseHelper> licenseHelper = nullptr;
|
||||
std::shared_ptr<license::LicenseService> license_service_{nullptr};
|
||||
std::shared_ptr<stats::ConnectionStatistics> statistics = nullptr;
|
||||
std::shared_ptr<threads::Scheduler> tick_manager = nullptr;
|
||||
|
||||
|
@ -39,7 +39,7 @@ bool SpeakingClient::shouldReceiveVoiceWhisper(const std::shared_ptr<ConnectedCl
|
||||
if(!this->shouldReceiveVoice(sender))
|
||||
return false;
|
||||
|
||||
return permission::v2::permission_granted(this->cpmerission_needed_whisper_power, sender->cpmerission_whisper_power);
|
||||
return permission::v2::permission_granted(this->cpmerission_needed_whisper_power, sender->cpmerission_whisper_power, false);
|
||||
}
|
||||
|
||||
void SpeakingClient::handlePacketVoice(const pipes::buffer_view& data, bool head, bool fragmented) {
|
||||
@ -127,6 +127,15 @@ enum WhisperTarget {
|
||||
CHANNEL_SUBCHANNELS = 6
|
||||
};
|
||||
|
||||
inline bool update_whisper_error(std::chrono::system_clock::time_point& last) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(last + std::chrono::milliseconds{500} < now) {
|
||||
last = now;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//All clients => type := SERVER_GROUP and target_id := 0
|
||||
//Server group => type := SERVER_GROUP and target_id := <server group id>
|
||||
//Channel group => type := CHANNEL_GROUP and target_id := <channel group id>
|
||||
@ -239,7 +248,27 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
|
||||
return target->currentChannel->parent() != current;
|
||||
}), available_clients.end());
|
||||
}
|
||||
if(available_clients.empty()) return;
|
||||
|
||||
auto self_lock = this->_this.lock();
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
return !speakingClient->shouldReceiveVoiceWhisper(self_lock);
|
||||
}), available_clients.end());
|
||||
|
||||
if(available_clients.empty()) {
|
||||
if(update_whisper_error(this->speak_last_no_whisper_target)) {
|
||||
command_result result{error::whisper_no_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(available_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
|
||||
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
|
||||
command_result result{error::whisper_too_many_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Create the packet data
|
||||
char packet_buffer[OUT_WHISPER_PKT_OFFSET + data_length];
|
||||
@ -253,7 +282,6 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
|
||||
VoicePacketFlags flags{};
|
||||
auto data = pipes::buffer_view(packet_buffer, OUT_WHISPER_PKT_OFFSET + data_length);
|
||||
for(const auto& cl : available_clients){
|
||||
if(cl->shouldReceiveVoiceWhisper(_this.lock()))
|
||||
cl->send_voice_whisper_packet(data, flags);
|
||||
}
|
||||
|
||||
@ -275,6 +303,44 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
|
||||
for(uint8_t index = 0; index < channelCount; index++)
|
||||
clientIds[index] = be2le16((char*) data.data_ptr(), offset, &offset);
|
||||
|
||||
auto available_clients = this->server->getClients();
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
if(!speakingClient || cl == this || !speakingClient->currentChannel) return true;
|
||||
|
||||
auto clientChannelId = cl->currentChannel->channelId();
|
||||
auto clientId = cl->getClientId();
|
||||
|
||||
for(uint8_t index = 0; index < clientCount; index++)
|
||||
if(channelIds[index] == clientChannelId) return false;
|
||||
|
||||
for(uint8_t index = 0; index < channelCount; index++)
|
||||
if(clientIds[index] == clientId) return false;
|
||||
|
||||
return true;
|
||||
}), available_clients.end());
|
||||
|
||||
auto self_lock = this->_this.lock();
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
return !speakingClient->shouldReceiveVoiceWhisper(self_lock);
|
||||
}), available_clients.end());
|
||||
|
||||
if(available_clients.empty()) {
|
||||
if(update_whisper_error(this->speak_last_no_whisper_target)) {
|
||||
command_result result{error::whisper_no_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(available_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
|
||||
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
|
||||
command_result result{error::whisper_too_many_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t dataLength = data.length() - offset;
|
||||
#ifdef PKT_LOG_WHISPER
|
||||
logTrace(this->getServerId(), "{} Whisper data length: {}. Client count: {}. Channel count: {}.", CLIENT_STR_LOG_PREFIX, dataLength, clientCount, channelCount);
|
||||
@ -291,21 +357,9 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
|
||||
VoicePacketFlags flags{};
|
||||
auto data = pipes::buffer_view(packetBuffer, OUT_WHISPER_PKT_OFFSET + dataLength);
|
||||
|
||||
for(const auto& cl : this->server->getClients()){ //Faster?
|
||||
for(const auto& cl : available_clients){ //Faster?
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
if(!speakingClient || cl == this) continue;
|
||||
if(!cl->currentChannel) continue;
|
||||
|
||||
auto clientChannelId = cl->currentChannel->channelId();
|
||||
auto clientId = cl->getClientId();
|
||||
|
||||
for(uint8_t index = 0; index < clientCount; index++)
|
||||
if(channelIds[index] == clientChannelId) goto handleSend;
|
||||
for(uint8_t index = 0; index < channelCount; index++)
|
||||
if(clientIds[index] == clientId) goto handleSend;
|
||||
continue;
|
||||
|
||||
handleSend:
|
||||
assert(speakingClient);
|
||||
if(speakingClient->shouldReceiveVoiceWhisper(_this.lock()))
|
||||
speakingClient->send_voice_whisper_packet(data, flags);
|
||||
}
|
||||
|
@ -81,6 +81,9 @@ namespace ts {
|
||||
std::chrono::system_clock::time_point speak_begin;
|
||||
std::chrono::system_clock::time_point speak_last_packet;
|
||||
|
||||
std::chrono::system_clock::time_point speak_last_no_whisper_target;
|
||||
std::chrono::system_clock::time_point speak_last_too_many_whisper_targets;
|
||||
|
||||
permission::v2::PermissionFlaggedValue max_idle_time{permission::v2::empty_permission_flagged_value};
|
||||
struct {
|
||||
HandshakeState state{HandshakeState::BEGIN};
|
||||
|
@ -1,11 +1,11 @@
|
||||
#include "SpeakingClient.h"
|
||||
#include <misc/endianness.h>
|
||||
#include <src/VirtualServerManager.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <src/InstanceHandler.h>
|
||||
#include <misc/base64.h>
|
||||
#include <misc/digest.h>
|
||||
#include <misc/rnd.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "../VirtualServerManager.h"
|
||||
#include "../InstanceHandler.h"
|
||||
|
||||
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
|
||||
#define TCP_NOPUSH TCP_CORK
|
||||
|
@ -1247,6 +1247,8 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
if(conversation)
|
||||
conversation->set_history_length(cmd[key->name]);
|
||||
}
|
||||
} else if(*key == property::CHANNEL_NEEDED_TALK_POWER) {
|
||||
channel->permissions()->set_permission(permission::i_client_needed_talk_power, {cmd[key->name].as<int>(), 0}, permission::v2::set_value, permission::v2::do_nothing);
|
||||
}
|
||||
|
||||
channel->properties()[key] = cmd[key->name].string();
|
||||
|
@ -1906,7 +1906,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
}
|
||||
string command = "cat \"" + log_path + "\"";
|
||||
command += " | grep -E ";
|
||||
command += "\"\\] \\[.*\\]( ){0,6}?" + server_identifier + " \\|\"";
|
||||
command += R"("\] \[.*\]( ){0,6}?)" + server_identifier + " \\|\"";
|
||||
|
||||
size_t beginpos = cmd[0].has("begin_pos") ? cmd["begin_pos"].as<size_t>() : 0ULL; //TODO test it?
|
||||
size_t file_index = 0;
|
||||
@ -1927,7 +1927,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
if(beginpos != 0 && file_index + read > beginpos) { //We're done we just want to get the size later
|
||||
line_buffer += string(buffer.data(), beginpos - file_index);
|
||||
|
||||
lines.push_back({file_index, line_buffer});
|
||||
lines.emplace_back(file_index, line_buffer);
|
||||
if(lines.size() > max_lines) lines.pop_front();
|
||||
//debugMessage(LOG_GENERAL, "Final line {}", line_buffer);
|
||||
line_buffer = "";
|
||||
@ -1955,7 +1955,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
if(length == 0) length = 1;
|
||||
|
||||
//debugMessage(LOG_GENERAL, "Got line {}", line_buffer.substr(0, index));
|
||||
lines.push_back({file_index + cut_offset, line_buffer.substr(0, index)});
|
||||
lines.emplace_back(file_index + cut_offset, line_buffer.substr(0, index));
|
||||
if(lines.size() > max_lines) lines.pop_front();
|
||||
|
||||
cut_offset += index + length;
|
||||
@ -1968,7 +1968,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
}
|
||||
|
||||
if(!line_buffer.empty()) {
|
||||
lines.push_back({file_index - line_buffer.length(), line_buffer});
|
||||
lines.emplace_back(file_index - line_buffer.length(), line_buffer);
|
||||
if(lines.size() > max_lines) lines.pop_front();
|
||||
}
|
||||
}
|
||||
@ -2004,8 +2004,13 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
|
||||
ts += type + " | | |" + line.substr(line.find('|') + 1);
|
||||
}
|
||||
|
||||
if(ts.length() > 1024)
|
||||
ts = ts.substr(0, 1024) + "...";
|
||||
result[index++]["l"] = ts;
|
||||
} else {
|
||||
if(line.length() > 1024)
|
||||
line = line.substr(0, 1024) + "...";
|
||||
result[index++]["l"] = line;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <misc/memtracker.h>
|
||||
#include <misc/base64.h>
|
||||
#include "src/client/ConnectedClient.h"
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
@ -1,200 +0,0 @@
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/strobf.h>
|
||||
#include <misc/hex.h>
|
||||
#include <src/Configuration.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <src/ShutdownHelper.h>
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "LicenseHelper.h"
|
||||
|
||||
using namespace license;
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts;
|
||||
using namespace ts::server;
|
||||
|
||||
LicenseHelper::LicenseHelper() {
|
||||
this->scheduled_request = system_clock::now() + seconds(rand() % 30); //Check in one minute
|
||||
}
|
||||
|
||||
LicenseHelper::~LicenseHelper() {
|
||||
if(this->request.prepare_thread.joinable())
|
||||
this->request.prepare_thread.join();
|
||||
|
||||
{
|
||||
unique_lock lock(this->request.current_lock);
|
||||
if(this->request.current) {
|
||||
auto request = move(this->request.current);
|
||||
lock.unlock();
|
||||
|
||||
request->abortRequest();
|
||||
request->callback_update_certificate = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline string format_time(const system_clock::time_point& time) {
|
||||
std::time_t now = system_clock::to_time_t(time);
|
||||
std::tm * ptm = std::localtime(&now);
|
||||
char buffer[128];
|
||||
const auto length = std::strftime(buffer, 128, "%a, %d.%m.%Y %H:%M:%S", ptm);
|
||||
return string(buffer, length);
|
||||
}
|
||||
|
||||
void LicenseHelper::tick() {
|
||||
lock_guard tick_lock(this->license_tick_lock);
|
||||
|
||||
bool verbose = config::license->isPremium();
|
||||
{
|
||||
lock_guard request_lock(this->request.current_lock);
|
||||
if(this->request.current) {
|
||||
auto promise = this->request.current->requestInfo();
|
||||
if(promise.state() != threads::FutureState::WORKING){
|
||||
auto exception = this->request.current->exception();
|
||||
if(promise.state() == threads::FutureState::FAILED) {
|
||||
this->handle_request_failed(verbose, exception ? exception->what() : strobf("unknown").c_str());
|
||||
this->request.current = nullptr; /* connection should be already closed */
|
||||
return;
|
||||
} else {
|
||||
auto response = promise.waitAndGet(nullptr);
|
||||
this->request.current = nullptr; /* connection should be already closed */
|
||||
|
||||
if(!response){
|
||||
this->handle_request_failed(verbose, exception ? exception->what() : strobf("invalid result (null)").c_str());
|
||||
return;
|
||||
}
|
||||
if(!response->license_valid || !response->properties_valid){
|
||||
if(!response->license_valid) {
|
||||
if(config::license->isPremium()) logCritical(LOG_INSTANCE, strobf("Could not validate license.").c_str());
|
||||
else logCritical(LOG_INSTANCE, strobf("Your server has been shutdown remotely!").c_str());
|
||||
} else if(!response->properties_valid) {
|
||||
logCritical(LOG_INSTANCE, strobf("Property adjustment failed!").c_str());
|
||||
} else
|
||||
logCritical(LOG_INSTANCE, strobf("Your license expired!").c_str());
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping application!").c_str());
|
||||
ts::server::shutdownInstance();
|
||||
return;
|
||||
} else {
|
||||
this->scheduled_request = this->last_request + hours(2);
|
||||
logMessage(LOG_INSTANCE, strobf("License successfully validated! Scheduling next check at {}").c_str(), format_time(this->scheduled_request));
|
||||
if(response->speach_reset)
|
||||
serverInstance->resetSpeechTime();
|
||||
serverInstance->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ] = response->speach_varianz_adjustment;
|
||||
|
||||
{
|
||||
lock_guard lock(this->request.info_lock);
|
||||
this->request.info = response->license;
|
||||
}
|
||||
|
||||
this->request_fail_count = 0;
|
||||
this->last_successful_request = system_clock::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(system_clock::now() > scheduled_request){
|
||||
this->do_request(verbose);
|
||||
}
|
||||
}
|
||||
void LicenseHelper::do_request(bool verbose) {
|
||||
if(config::license && config::license->isPremium())
|
||||
logMessage(LOG_INSTANCE, strobf("Validating license").c_str());
|
||||
else
|
||||
logMessage(LOG_INSTANCE, strobf("Validating instance integrity").c_str());
|
||||
|
||||
this->last_request = system_clock::now();
|
||||
this->scheduled_request = this->last_request + minutes(10); /* some kind of timeout */
|
||||
|
||||
{
|
||||
unique_lock lock(this->request.current_lock);
|
||||
if(this->request.current) {
|
||||
auto request = move(this->request.current);
|
||||
lock.unlock();
|
||||
|
||||
if(verbose) {
|
||||
auto promise = request->requestInfo();
|
||||
if(promise.state() == threads::FutureState::WORKING) {
|
||||
logMessage(LOG_INSTANCE, strobf("Old check timed out (10 min). Running new one!").c_str());
|
||||
}
|
||||
}
|
||||
request->abortRequest();
|
||||
}
|
||||
}
|
||||
|
||||
if(this->request.prepare_thread.joinable()) /* usually the preparation should not take too long */
|
||||
this->request.prepare_thread.join();
|
||||
|
||||
this->request.prepare_thread = std::thread([&]{
|
||||
sockaddr_in server_addr{};
|
||||
server_addr.sin_family = AF_INET;
|
||||
|
||||
#ifdef DO_LOCAL_REQUEST
|
||||
auto license_host = gethostbyname(strobf("localhost").c_str());
|
||||
server_addr.sin_addr.s_addr = ((in_addr*) license_host->h_addr)->s_addr;
|
||||
#else
|
||||
auto license_host = gethostbyname(strobf("license.teaspeak.de").c_str());
|
||||
if(!license_host){
|
||||
if(verbose) logError(LOG_INSTANCE, strobf("Could not valid license! (1)").c_str());
|
||||
return;
|
||||
}
|
||||
if(!license_host->h_addr){
|
||||
if(verbose) logError(LOG_INSTANCE, strobf("Could not valid license! (2)").c_str());
|
||||
return;
|
||||
}
|
||||
server_addr.sin_addr.s_addr = ((in_addr*) license_host->h_addr)->s_addr;
|
||||
int first = server_addr.sin_addr.s_addr >> 24;
|
||||
if(first == 0 || first == 127 || first == 255) {
|
||||
if(config::license->isPremium()) {
|
||||
logError(LOG_INSTANCE, strobf("You tried to nullroot 'license.teaspeak.de'!").c_str());
|
||||
logCritical(LOG_INSTANCE, strobf("Could not validate license!").c_str());
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping server!").c_str());
|
||||
ts::server::shutdownInstance();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
server_addr.sin_port = htons(27786);
|
||||
|
||||
|
||||
auto license_data = serverInstance->generateLicenseData();
|
||||
auto request = make_shared<license::LicenceRequest>(license_data, server_addr);
|
||||
request->verbose = false;
|
||||
request->callback_update_certificate = [&](const auto& update) { this->callback_certificate_update(update); };
|
||||
{
|
||||
lock_guard lock(this->request.current_lock);
|
||||
this->request.current = request;
|
||||
request->requestInfo();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void LicenseHelper::handle_request_failed(bool verbose, const std::string& error) {
|
||||
if(config::license && config::license->isPremium())
|
||||
logError(LOG_INSTANCE, strobf("License validation failed: {}").c_str(), error);
|
||||
else
|
||||
logError(LOG_INSTANCE, strobf("Instance integrity check failed: {}").c_str(), error);
|
||||
|
||||
this->request_fail_count++;
|
||||
milliseconds next_request;
|
||||
|
||||
if(this->request_fail_count <= 1)
|
||||
next_request = minutes(1);
|
||||
else if(this->request_fail_count <= 5)
|
||||
next_request = minutes(5);
|
||||
else if(this->request_fail_count <= 10)
|
||||
next_request = minutes(10);
|
||||
else
|
||||
next_request = minutes(30);
|
||||
|
||||
this->scheduled_request = this->last_request + next_request;
|
||||
if(verbose)
|
||||
logMessage(LOG_INSTANCE, strobf("Scheduling next check at {}").c_str(), format_time(this->scheduled_request));
|
||||
}
|
||||
|
||||
void LicenseHelper::callback_certificate_update(const license::WebCertificate &certificate) {
|
||||
serverInstance->setWebCertRoot(certificate.key, certificate.certificate, certificate.revision);
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <ThreadPool/Mutex.h>
|
||||
#include "../../../license/shared/License.h"
|
||||
#include "../../../license/shared/LicenseRequest.h"
|
||||
|
||||
namespace license {
|
||||
class LicenseHelper {
|
||||
public:
|
||||
LicenseHelper();
|
||||
~LicenseHelper();
|
||||
|
||||
void tick();
|
||||
std::shared_ptr<license::LicenseInfo> getLicenseInfo() {
|
||||
std::lock_guard lock(this->request.info_lock);
|
||||
return this->request.info;
|
||||
}
|
||||
private:
|
||||
std::mutex license_tick_lock;
|
||||
|
||||
std::chrono::system_clock::time_point scheduled_request;
|
||||
std::chrono::system_clock::time_point last_request;
|
||||
std::chrono::system_clock::time_point last_successful_request;
|
||||
size_t request_fail_count = 0;
|
||||
|
||||
struct {
|
||||
std::shared_ptr<license::LicenceRequest> current = nullptr;
|
||||
std::mutex current_lock;
|
||||
|
||||
std::shared_ptr<license::LicenseInfo> info;
|
||||
std::mutex info_lock;
|
||||
|
||||
std::thread prepare_thread;
|
||||
} request;
|
||||
|
||||
void do_request(bool /* verbose */);
|
||||
void handle_request_failed(bool /* verbose */, const std::string& /* error */);
|
||||
void callback_certificate_update(const license::WebCertificate&);
|
||||
};
|
||||
}
|
540
server/src/lincense/LicenseService.cpp
Normal file
540
server/src/lincense/LicenseService.cpp
Normal file
@ -0,0 +1,540 @@
|
||||
//
|
||||
// Created by WolverinDEV on 27/02/2020.
|
||||
//
|
||||
#include <netdb.h>
|
||||
#include <cassert>
|
||||
#include <misc/strobf.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <src/Configuration.h>
|
||||
#include <src/ShutdownHelper.h>
|
||||
#include <misc/base64.h>
|
||||
#include "../../../license/shared/LicenseServerClient.h"
|
||||
#include "../../../cmake-build-debug-wsl/license/LicenseRequest.pb.h"
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "../../../license/shared/License.h"
|
||||
|
||||
#define DO_LOCAL_REQUEST
|
||||
using namespace ts::server::license;
|
||||
|
||||
LicenseService::LicenseService() {
|
||||
this->dns.lock = std::make_shared<std::recursive_mutex>();
|
||||
}
|
||||
|
||||
LicenseService::~LicenseService() {
|
||||
{
|
||||
std::lock_guard lock{this->request_lock};
|
||||
this->abort_request(lock, "");
|
||||
}
|
||||
}
|
||||
|
||||
bool LicenseService::initialize(std::string &error) {
|
||||
//this->verbose_ = true;
|
||||
this->startup_timepoint_ = std::chrono::steady_clock::now();
|
||||
this->timings.next_request = std::chrono::system_clock::now() + std::chrono::seconds(rand() % 20);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseService::execute_request_sync(const std::chrono::milliseconds& timeout) {
|
||||
std::unique_lock slock{this->sync_request_lock};
|
||||
this->begin_request();
|
||||
|
||||
if(this->sync_request_cv.wait_for(slock, timeout) == std::cv_status::timeout)
|
||||
return false;
|
||||
|
||||
return this->timings.failed_count == 0;
|
||||
}
|
||||
|
||||
void LicenseService::shutdown() {
|
||||
std::lock_guard lock{this->request_lock};
|
||||
if(this->request_state_ == request_state::empty) return;
|
||||
|
||||
this->abort_request(lock, "shutdown");
|
||||
}
|
||||
|
||||
void LicenseService::begin_request() {
|
||||
std::lock_guard lock{this->request_lock};
|
||||
if(this->request_state_ != request_state::empty)
|
||||
this->abort_request(lock, "last request has been aborted");
|
||||
|
||||
if(this->verbose_)
|
||||
debugMessage(LOG_INSTANCE, strobf("Executing license request.").string());
|
||||
this->timings.last_request = std::chrono::system_clock::now();
|
||||
this->request_state_ = request_state::dns_lookup;
|
||||
this->execute_dns_request();
|
||||
}
|
||||
|
||||
void LicenseService::abort_request(std::lock_guard<std::recursive_timed_mutex> &, const std::string& reason) {
|
||||
if(this->request_state_ == request_state::dns_lookup) {
|
||||
this->abort_dns_request();
|
||||
return;
|
||||
} else if(this->current_client) {
|
||||
this->current_client->callback_connected = nullptr;
|
||||
this->current_client->callback_message = nullptr;
|
||||
this->current_client->callback_disconnected = nullptr;
|
||||
|
||||
if(!reason.empty()) {
|
||||
this->current_client->disconnect(reason, std::chrono::system_clock::now() + std::chrono::seconds{1});
|
||||
/* Lets not wait here because we might be within the event loop. */
|
||||
//if(!this->current_client->await_disconnect())
|
||||
// this->current_client->close_connection();
|
||||
} else {
|
||||
this->current_client->close_connection();
|
||||
}
|
||||
|
||||
this->current_client.release();
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseService::abort_dns_request() {
|
||||
std::unique_lock llock{*this->dns.lock};
|
||||
if(!this->dns.current_lookup) return;
|
||||
|
||||
this->dns.current_lookup->handle = nullptr;
|
||||
this->dns.current_lookup = nullptr;
|
||||
}
|
||||
|
||||
void LicenseService::execute_dns_request() {
|
||||
std::unique_lock llock{*this->dns.lock};
|
||||
assert(!this->dns.current_lookup);
|
||||
|
||||
auto lookup = new _dns::_lookup{};
|
||||
|
||||
lookup->lock = this->dns.lock;
|
||||
lookup->handle = this;
|
||||
lookup->thread = std::thread([lookup] {
|
||||
bool success{false};
|
||||
std::string error{};
|
||||
sockaddr_in server_addr{};
|
||||
|
||||
{
|
||||
server_addr.sin_family = AF_INET;
|
||||
#ifdef DO_LOCAL_REQUEST
|
||||
auto license_host = gethostbyname(strobf("localhost").c_str());
|
||||
#else
|
||||
auto license_host = gethostbyname(strobf("license.teaspeak.de").c_str());
|
||||
#endif
|
||||
if(!license_host) {
|
||||
error = strobf("result is null").string();
|
||||
goto handle_result;
|
||||
}
|
||||
if(!license_host->h_addr){
|
||||
error = strobf("missing h_addr in result").string();
|
||||
goto handle_result;
|
||||
}
|
||||
|
||||
server_addr.sin_addr.s_addr = ((in_addr*) license_host->h_addr)->s_addr;
|
||||
|
||||
#ifndef DO_LOCAL_REQUEST
|
||||
int first = server_addr.sin_addr.s_addr >> 24;
|
||||
if(first == 0 || first == 127 || first == 255) {
|
||||
error = strobf("local response address").string();
|
||||
goto handle_result;
|
||||
}
|
||||
#endif
|
||||
server_addr.sin_port = htons(27786);
|
||||
success = true;
|
||||
}
|
||||
|
||||
handle_result:
|
||||
{
|
||||
std::unique_lock llock{*lookup->lock};
|
||||
if(lookup->handle) {
|
||||
lookup->handle->dns.current_lookup = nullptr;
|
||||
|
||||
if(success) {
|
||||
debugMessage(LOG_INSTANCE, strobf("Successfully resolved the hostname to {}").string(), net::to_string(server_addr.sin_addr));
|
||||
lookup->handle->handle_dns_lookup_result(true, server_addr);
|
||||
} else {
|
||||
debugMessage(LOG_INSTANCE, strobf("Failed to resolve hostname for license server: {}").string(), error);
|
||||
lookup->handle->handle_dns_lookup_result(false, error);
|
||||
}
|
||||
}
|
||||
|
||||
assert(lookup->thread.get_id() == std::this_thread::get_id());
|
||||
if(lookup->thread.joinable())
|
||||
lookup->thread.detach();
|
||||
delete lookup;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this->dns.current_lookup = lookup;
|
||||
}
|
||||
|
||||
void LicenseService::handle_check_succeeded() {
|
||||
{
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
this->abort_request(rlock, strobf("request succeeded").string());
|
||||
this->schedule_next_request(true);
|
||||
this->request_state_ = request_state::empty;
|
||||
|
||||
if(config::license->isPremium()) {
|
||||
logMessage(LOG_INSTANCE, strobf("License has been validated.").string());
|
||||
} else {
|
||||
logMessage(LOG_INSTANCE, strobf("Instance integrity has been validated.").string());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock slock{this->sync_request_lock};
|
||||
this->sync_request_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseService::handle_check_fail(const std::string &error) {
|
||||
{
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
this->abort_request(rlock, "request failed");
|
||||
|
||||
if(config::license->isPremium()) {
|
||||
logCritical(LOG_INSTANCE, strobf("Failed to validate license:").string());
|
||||
logCritical(LOG_INSTANCE, error);
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping server!").string());
|
||||
ts::server::shutdownInstance();
|
||||
} else {
|
||||
logError(LOG_INSTANCE, strobf("Failed to validate instance integrity:").string());
|
||||
logError(LOG_INSTANCE, error);
|
||||
}
|
||||
|
||||
this->schedule_next_request(false);
|
||||
this->request_state_ = request_state::empty;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock slock{this->sync_request_lock};
|
||||
this->sync_request_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseService::handle_dns_lookup_result(bool success, const std::variant<std::string, sockaddr_in> &result) {
|
||||
if(!success) {
|
||||
this->handle_check_fail(std::get<std::string>(result));
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::dns_lookup) {
|
||||
logError(LOG_INSTANCE, strobf("Request state isn't dns lookup anymore. Aborting dns lookup result callback.").string());
|
||||
return;
|
||||
}
|
||||
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->callback_connected = [&]{ this->handle_client_connected(); };
|
||||
this->current_client->callback_disconnected = [&](bool expected, const std::string& error) {
|
||||
this->handle_client_disconnected(error);
|
||||
};
|
||||
this->current_client->callback_message = [&](auto a, auto b, auto c) {
|
||||
this->handle_message(a, b, c);
|
||||
};
|
||||
|
||||
std::string error{};
|
||||
if(!this->current_client->start_connection(error))
|
||||
this->handle_check_fail(strobf("connect failed: ").string() + error);
|
||||
}
|
||||
|
||||
void LicenseService::client_send_message(::license::protocol::PacketType type, ::google::protobuf::Message &message) {
|
||||
auto buffer = message.SerializeAsString();
|
||||
|
||||
assert(this->current_client);
|
||||
this->current_client->send_message(type, buffer.data(), buffer.length());
|
||||
}
|
||||
|
||||
void LicenseService::handle_client_connected() {
|
||||
{
|
||||
if(this->verbose_)
|
||||
debugMessage(LOG_INSTANCE, strobf("License client connected").string());
|
||||
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::connecting) {
|
||||
logError(LOG_INSTANCE, strobf("Request state isn't connecting anymore. Aborting client connect callback.").string());
|
||||
return;
|
||||
}
|
||||
|
||||
this->request_state_ = request_state::license_validate;
|
||||
}
|
||||
|
||||
this->send_license_validate_request();
|
||||
}
|
||||
|
||||
void LicenseService::handle_message(::license::protocol::PacketType type, const void *buffer, size_t size) {
|
||||
switch (type) {
|
||||
case ::license::protocol::PACKET_SERVER_VALIDATION_RESPONSE:
|
||||
this->handle_message_license_info(buffer, size);
|
||||
return;
|
||||
|
||||
case ::license::protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT:
|
||||
this->handle_message_property_adjustment(buffer, size);
|
||||
return;
|
||||
|
||||
case ::license::protocol::PACKET_SERVER_LICENSE_UPGRADE_RESPONSE:
|
||||
this->handle_message_license_update(buffer, size);
|
||||
return;
|
||||
|
||||
default:
|
||||
this->handle_check_fail(strobf("received unknown packet").string());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseService::handle_client_disconnected(const std::string& message) {
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::finishing) {
|
||||
this->handle_check_fail(strobf("unexpected disconnect: ").string() + message);
|
||||
return;
|
||||
}
|
||||
|
||||
this->abort_request(rlock, "");
|
||||
}
|
||||
|
||||
void LicenseService::send_license_validate_request() {
|
||||
this->license_request_data = serverInstance->generateLicenseData();
|
||||
|
||||
ts::proto::license::ServerValidation request{};
|
||||
if(this->license_request_data->license) {
|
||||
request.set_licensed(true);
|
||||
request.set_license_info(true);
|
||||
request.set_license(exportLocalLicense(this->license_request_data->license));
|
||||
} else {
|
||||
request.set_licensed(false);
|
||||
request.set_license_info(false);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
void LicenseService::handle_message_license_info(const void *buffer, size_t buffer_length) {
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::license_validate) {
|
||||
this->handle_check_fail(strobf("finvalid request state for license response packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
ts::proto::license::LicenseResponse response{};
|
||||
if(!response.ParseFromArray(buffer, buffer_length)) {
|
||||
this->handle_check_fail(strobf("failed to parse license response packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
if(response.has_blacklist()) {
|
||||
auto blacklist_state = response.blacklist().state();
|
||||
if(blacklist_state == ::ts::proto::license::BLACKLISTED) {
|
||||
this->abort_request(rlock, strobf("blacklist action").string());
|
||||
|
||||
logCritical(LOG_INSTANCE, strobf("This TeaSpeak-Server instance has been blacklisted by TeaSpeak.").string());
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping server!").string());
|
||||
ts::server::shutdownInstance();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!response.valid()) {
|
||||
std::string reason{};
|
||||
if(response.has_invalid_reason())
|
||||
reason = response.invalid_reason();
|
||||
else
|
||||
reason = strobf("no reason given").string();
|
||||
|
||||
license_invalid_reason = reason;
|
||||
} else {
|
||||
license_invalid_reason.reset();
|
||||
}
|
||||
|
||||
if(response.has_update_pending() && response.update_pending()) {
|
||||
if(this->send_license_update_request()) {
|
||||
this->request_state_ = request_state::license_upgrade;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(this->license_invalid_reason.has_value()) {
|
||||
this->handle_check_fail(strobf("Failed to verify license (").string() + *this->license_invalid_reason + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
this->send_property_update_request();
|
||||
this->request_state_ = request_state::property_update;
|
||||
}
|
||||
|
||||
void LicenseService::send_property_update_request() {
|
||||
auto data = this->license_request_data;
|
||||
if(!data) {
|
||||
this->handle_check_fail(strobf("missing property data").string());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ts::proto::license::PropertyUpdateRequest infos{};
|
||||
infos.set_speach_total(this->license_request_data->metrics.speech_total);
|
||||
infos.set_speach_dead(this->license_request_data->metrics.speech_dead);
|
||||
infos.set_speach_online(this->license_request_data->metrics.speech_online);
|
||||
infos.set_speach_varianz(this->license_request_data->metrics.speech_varianz);
|
||||
|
||||
infos.set_clients_online(this->license_request_data->metrics.client_online);
|
||||
infos.set_bots_online(this->license_request_data->metrics.bots_online);
|
||||
infos.set_queries_online(this->license_request_data->metrics.queries_online);
|
||||
infos.set_servers_online(this->license_request_data->metrics.servers_online);
|
||||
infos.set_web_clients_online(this->license_request_data->metrics.web_clients_online);
|
||||
|
||||
infos.set_web_cert_revision(this->license_request_data->web_certificate_revision);
|
||||
|
||||
this->client_send_message(::license::protocol::PACKET_CLIENT_PROPERTY_ADJUSTMENT, infos);
|
||||
}
|
||||
|
||||
void LicenseService::handle_message_property_adjustment(const void *buffer, size_t buffer_length) {
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::property_update) {
|
||||
this->handle_check_fail(strobf("invalid request state for property update packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
ts::proto::license::PropertyUpdateResponse response{};
|
||||
if(!response.ParseFromArray(buffer, buffer_length)) {
|
||||
this->handle_check_fail(strobf("failed to parse property update packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
if(response.has_web_certificate()) {
|
||||
auto& certificate = response.web_certificate();
|
||||
serverInstance->setWebCertRoot(certificate.key(), certificate.certificate(), certificate.revision());
|
||||
}
|
||||
|
||||
if(response.has_reset_speach())
|
||||
serverInstance->resetSpeechTime();
|
||||
serverInstance->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ] = response.speach_varianz_corrector();
|
||||
|
||||
this->request_state_ = request_state::finishing;
|
||||
this->handle_check_succeeded();
|
||||
}
|
||||
|
||||
bool LicenseService::send_license_update_request() {
|
||||
ts::proto::license::RequestLicenseUpgrade request{};
|
||||
this->client_send_message(::license::protocol::PACKET_CLIENT_LICENSE_UPGRADE, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::string format_time(const std::chrono::system_clock::time_point& time);
|
||||
void LicenseService::handle_message_license_update(const void *buffer, size_t buffer_length) {
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::license_upgrade) {
|
||||
this->handle_check_fail(strobf("invalid request state for license upgrade packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
ts::proto::license::LicenseUpgradeResponse response{};
|
||||
if(!response.ParseFromArray(buffer, buffer_length)) {
|
||||
this->handle_check_fail(strobf("failed to parse license upgrade packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
if(!response.valid()) {
|
||||
logError(LOG_INSTANCE, strobf("Failed to upgrade license: {}").string(), response.error_message());
|
||||
goto error_exit;
|
||||
} else {
|
||||
std::string error{};
|
||||
auto license_data = response.license_key();
|
||||
auto license = ::license::readLocalLicence(license_data, error);
|
||||
if(!license) {
|
||||
logError(LOG_INSTANCE, strobf("Failed to parse received upgraded license key: {}").string(), error);
|
||||
goto error_exit;
|
||||
}
|
||||
if(!license->isValid()) {
|
||||
logError(LOG_INSTANCE, strobf("Received license seems to be invalid.").string());
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
auto end = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{license->data.endTimestamp};
|
||||
logMessage(LOG_INSTANCE, strobf("Received new license registered to {}, valid until {}").string(), license->data.licenceOwner, format_time(end));
|
||||
if(!config::update_license(error, license_data))
|
||||
logError(LOG_INSTANCE, strobf("Failed to write new license key to config file: {}").string(), error);
|
||||
|
||||
config::license = license;
|
||||
|
||||
this->send_license_validate_request();
|
||||
this->request_state_ = request_state::license_validate;
|
||||
}
|
||||
|
||||
return;
|
||||
error_exit:
|
||||
logError(LOG_INSTANCE, strobf("License upgrade failed. Using old key.").string());
|
||||
if(this->license_invalid_reason.has_value()) {
|
||||
this->handle_check_fail(strobf("Failed to verify license (").string() + *this->license_invalid_reason + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
this->send_property_update_request();
|
||||
this->request_state_ = request_state::property_update;
|
||||
}
|
||||
|
||||
/* request scheduler */
|
||||
inline std::string format_time(const std::chrono::system_clock::time_point& time) {
|
||||
std::time_t now = std::chrono::system_clock::to_time_t(time);
|
||||
std::tm * ptm = std::localtime(&now);
|
||||
char buffer[128];
|
||||
const auto length = std::strftime(buffer, 128, "%a, %d.%m.%Y %H:%M:%S", ptm);
|
||||
return std::string{buffer, length};
|
||||
}
|
||||
|
||||
void LicenseService::schedule_next_request(bool request_success) {
|
||||
auto& fail_count = this->timings.failed_count;
|
||||
if(request_success)
|
||||
fail_count = 0;
|
||||
else
|
||||
fail_count++;
|
||||
|
||||
std::chrono::milliseconds next_request;
|
||||
if(fail_count == 0)
|
||||
next_request = std::chrono::hours{2};
|
||||
if(fail_count <= 1)
|
||||
next_request = std::chrono::minutes(1);
|
||||
else if(fail_count <= 5)
|
||||
next_request = std::chrono::minutes(5);
|
||||
else if(fail_count <= 10)
|
||||
next_request = std::chrono::minutes(10);
|
||||
else
|
||||
next_request = std::chrono::minutes(30);
|
||||
#ifdef DO_LOCAL_REQUEST
|
||||
next_request = std::chrono::seconds(30);
|
||||
#endif
|
||||
|
||||
this->timings.next_request = this->timings.last_request + next_request;
|
||||
if(this->verbose_)
|
||||
logMessage(LOG_INSTANCE, strobf("Scheduling next check at {}").c_str(), format_time(this->timings.next_request));
|
||||
}
|
||||
|
||||
void LicenseService::execute_tick() {
|
||||
std::unique_lock rlock{this->request_lock, std::try_to_lock}; /* It will be slightly blocking when its within the message hendeling */
|
||||
if(!rlock) return;
|
||||
|
||||
/* do it not above because if we might have a deadlock here we don't want to punish the user */
|
||||
if(this->timings.last_succeeded.time_since_epoch().count() == 0) {
|
||||
auto difference = config::license->isPremium() ? std::chrono::hours{24 * 4} : std::chrono::hours{24 * 7};
|
||||
if(std::chrono::steady_clock::now() - difference > this->startup_timepoint_) {
|
||||
this->startup_timepoint_ = std::chrono::steady_clock::now(); /* shut down only once */
|
||||
|
||||
if(config::license->isPremium())
|
||||
logCritical(LOG_INSTANCE, strobf("Failed to validate license within 4 days.").string());
|
||||
else
|
||||
logCritical(LOG_INSTANCE, strobf("Failed to validate instance integrity within 7 days.").string());
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping server!").string());
|
||||
ts::server::shutdownInstance();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(this->request_state_ != request_state::empty) {
|
||||
if(this->timings.last_request + std::chrono::minutes{5} < now) {
|
||||
this->handle_check_fail(strobf("Scheduling next check at {}").string());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(std::chrono::system_clock::now() > this->timings.next_request)
|
||||
this->begin_request();
|
||||
}
|
134
server/src/lincense/LicenseService.h
Normal file
134
server/src/lincense/LicenseService.h
Normal file
@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
namespace license::client {
|
||||
class LicenseServerClient;
|
||||
}
|
||||
|
||||
namespace google::protobuf {
|
||||
class Message;
|
||||
}
|
||||
|
||||
namespace ts::server::license {
|
||||
struct InstanceLicenseInfo {
|
||||
std::shared_ptr<::license::License> license{nullptr};
|
||||
std::string web_certificate_revision{};
|
||||
|
||||
struct metrics_ {
|
||||
size_t servers_online{0};
|
||||
size_t client_online{0};
|
||||
size_t web_clients_online{0};
|
||||
size_t bots_online{0};
|
||||
size_t queries_online{0};
|
||||
|
||||
size_t speech_total{0};
|
||||
size_t speech_varianz{0};
|
||||
size_t speech_online{0};
|
||||
size_t speech_dead{0};
|
||||
} metrics;
|
||||
|
||||
struct info_ {
|
||||
std::chrono::milliseconds timestamp{};
|
||||
std::string version{};
|
||||
std::string uname{};
|
||||
std::string unique_id{};
|
||||
} info;
|
||||
};
|
||||
|
||||
|
||||
class LicenseService {
|
||||
public:
|
||||
LicenseService();
|
||||
~LicenseService();
|
||||
|
||||
[[nodiscard]] bool initialize(std::string& /* error */);
|
||||
void shutdown();
|
||||
|
||||
/* whatever it failed/succeeded */
|
||||
bool execute_request_sync(const std::chrono::milliseconds& /* timeout */);
|
||||
|
||||
[[nodiscard]] inline bool verbose() const { return this->verbose_; }
|
||||
void execute_tick(); /* should not be essential to the core functionality! */
|
||||
private:
|
||||
std::chrono::steady_clock::time_point startup_timepoint_;
|
||||
|
||||
enum struct request_state {
|
||||
empty,
|
||||
|
||||
/* initializing */
|
||||
dns_lookup,
|
||||
connecting,
|
||||
|
||||
/* connected states */
|
||||
license_validate,
|
||||
license_upgrade,
|
||||
property_update,
|
||||
|
||||
/* disconnecting */
|
||||
finishing
|
||||
};
|
||||
bool verbose_{false};
|
||||
|
||||
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<InstanceLicenseInfo> license_request_data{nullptr};
|
||||
|
||||
std::condition_variable sync_request_cv;
|
||||
std::mutex sync_request_lock;
|
||||
|
||||
struct _timings {
|
||||
std::chrono::system_clock::time_point last_request{};
|
||||
std::chrono::system_clock::time_point next_request{};
|
||||
|
||||
std::chrono::system_clock::time_point last_succeeded{};
|
||||
size_t failed_count{0};
|
||||
} timings;
|
||||
|
||||
struct _dns {
|
||||
std::shared_ptr<std::recursive_mutex> lock{nullptr};
|
||||
|
||||
struct _lookup {
|
||||
std::shared_ptr<std::recursive_mutex> lock{nullptr};
|
||||
std::thread thread{};
|
||||
|
||||
LicenseService* handle{nullptr}; /* may be null, locked via lock */
|
||||
}* current_lookup{nullptr};
|
||||
} dns;
|
||||
|
||||
std::optional<std::string> license_invalid_reason{}; /* set if the last license is invalid */
|
||||
|
||||
void schedule_next_request(bool /* last request succeeded */);
|
||||
|
||||
void begin_request();
|
||||
void client_send_message(::license::protocol::PacketType /* type */, ::google::protobuf::Message& /* message */);
|
||||
void handle_check_fail(const std::string& /* error */); /* might be called form the DNS loop */
|
||||
void handle_check_succeeded();
|
||||
|
||||
/* if not disconnect message has been set it will just close the connection */
|
||||
void abort_request(std::lock_guard<std::recursive_timed_mutex>& /* request lock */, const std::string& /* disconnect message */);
|
||||
|
||||
void abort_dns_request();
|
||||
void execute_dns_request();
|
||||
|
||||
/* will be called while dns lock has been locked! */
|
||||
void handle_dns_lookup_result(bool /* success */, const std::variant<std::string, sockaddr_in>& /* data */);
|
||||
|
||||
/* all callbacks bellow are called from the current_client. It will not be null while being within the callback. */
|
||||
void handle_client_connected();
|
||||
void handle_client_disconnected(const std::string& /* error */);
|
||||
|
||||
void handle_message(::license::protocol::PacketType /* type */, const void* /* buffer */, size_t /* length */);
|
||||
void handle_message_license_info(const void* /* buffer */, size_t /* length */);
|
||||
void handle_message_license_update(const void* /* buffer */, size_t /* length */);
|
||||
void handle_message_property_adjustment(const void* /* buffer */, size_t /* length */);
|
||||
|
||||
void send_license_validate_request();
|
||||
bool send_license_update_request();
|
||||
void send_property_update_request();
|
||||
};
|
||||
}
|
@ -1,17 +1,20 @@
|
||||
#include "./CommandHandler.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <src/SignalHandler.h>
|
||||
#include <src/VirtualServer.h>
|
||||
#include <src/client/ConnectedClient.h>
|
||||
#include <src/VirtualServerManager.h>
|
||||
#include <src/InstanceHandler.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <src/ShutdownHelper.h>
|
||||
#include <misc/time.h>
|
||||
#include <misc/memtracker.h>
|
||||
#include <sql/sqlite/SqliteSQL.h>
|
||||
#include <sys/resource.h>
|
||||
#include "CommandHandler.h"
|
||||
#include "src/server/QueryServer.h"
|
||||
#include <protocol/buffers.h>
|
||||
|
||||
#include "../SignalHandler.h"
|
||||
#include "../client/ConnectedClient.h"
|
||||
#include "../InstanceHandler.h"
|
||||
#include "../VirtualServerManager.h"
|
||||
#include "../VirtualServer.h"
|
||||
#include "../ShutdownHelper.h"
|
||||
#include "../server/QueryServer.h"
|
||||
|
||||
#ifdef HAVE_JEMALLOC
|
||||
#include <jemalloc/jemalloc.h>
|
||||
|
2
shared
2
shared
@ -1 +1 @@
|
||||
Subproject commit 51d9a002e3311a2079b25253b7946a4b03a9baf4
|
||||
Subproject commit fd8aba2ede8675f4feb9fcfc01ada3822f2d4780
|
Loading…
Reference in New Issue
Block a user