1.4.10 updates

This commit is contained in:
WolverinDEV 2020-02-28 11:24:07 +01:00
parent 2b929fd3c0
commit 0d456eea5d
48 changed files with 2267 additions and 781 deletions

@ -1 +1 @@
Subproject commit fec314cf2b73fc87b34bc68f26eb64e51982b26e
Subproject commit ea81efb4130ad6c616b3b222a364fc8cd5ad9868

View File

@ -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
)

View File

@ -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;
}

View File

@ -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;

View File

@ -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();

View File

@ -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";

View File

@ -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&);
};
}

View File

@ -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)

View File

@ -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");

View File

@ -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 {

View File

@ -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;

View File

@ -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) {
@ -512,3 +528,86 @@ 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());
}
}

View 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;
};
}

View File

@ -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;
}

View File

@ -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;
};
}
}

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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) \

View File

@ -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 {

View File

@ -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>

View File

@ -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};

View File

@ -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;

View File

@ -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;

View File

@ -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);

View 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{});
}

View 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 */);
};
}

View File

@ -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)

View File

@ -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=

View File

@ -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;
}

View File

@ -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"; };

View File

@ -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();

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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};

View File

@ -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

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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&);
};
}

View 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();
}

View 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();
};
}

View File

@ -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

@ -1 +1 @@
Subproject commit 51d9a002e3311a2079b25253b7946a4b03a9baf4
Subproject commit fd8aba2ede8675f4feb9fcfc01ada3822f2d4780