diff --git a/git-teaspeak b/git-teaspeak index fec314c..ea81efb 160000 --- a/git-teaspeak +++ b/git-teaspeak @@ -1 +1 @@ -Subproject commit fec314cf2b73fc87b34bc68f26eb64e51982b26e +Subproject commit ea81efb4130ad6c616b3b222a364fc8cd5ad9868 diff --git a/license/CMakeLists.txt b/license/CMakeLists.txt index 5f3eeda..5c563c7 100644 --- a/license/CMakeLists.txt +++ b/license/CMakeLists.txt @@ -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 ) \ No newline at end of file diff --git a/license/LicenseClientMain.cpp b/license/LicenseClientMain.cpp index 9ac3914..5c6c494 100644 --- a/license/LicenseClientMain.cpp +++ b/license/LicenseClientMain.cpp @@ -2,6 +2,8 @@ #include #include #include +#include "shared/LicenseServerClient.h" + #include #include #include @@ -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(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 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(); data->license = license; data->info = make_shared(); - 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; } \ No newline at end of file diff --git a/license/LicenseCreatorCLI.cpp b/license/LicenseCreatorCLI.cpp index 4225e88..0c85e24 100644 --- a/license/LicenseCreatorCLI.cpp +++ b/license/LicenseCreatorCLI.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "manager/ServerConnection.h" @@ -63,16 +64,18 @@ class CLIParser { std::vector 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 -#include + 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 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; diff --git a/license/LicenseServerMain.cpp b/license/LicenseServerMain.cpp index 0dee29e..93f7be8 100644 --- a/license/LicenseServerMain.cpp +++ b/license/LicenseServerMain.cpp @@ -34,7 +34,7 @@ using namespace license; */ bool handle_command(string& line); -shared_ptr license_manager; +shared_ptr license_manager; shared_ptr statistic_manager; shared_ptr ssl_manager; shared_ptr 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(); 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(database); + license_manager = make_shared(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(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(); { @@ -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(); diff --git a/license/manager/ServerConnection.cpp b/license/manager/ServerConnection.cpp index 6cfdd3a..18f98b5 100644 --- a/license/manager/ServerConnection.cpp +++ b/license/manager/ServerConnection.cpp @@ -50,8 +50,8 @@ threads::Future 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(&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 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 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"; diff --git a/license/manager/ServerConnection.h b/license/manager/ServerConnection.h index 35e8e95..a5d7a9f 100644 --- a/license/manager/ServerConnection.h +++ b/license/manager/ServerConnection.h @@ -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 connect(const std::string& host, uint16_t port); - void disconnect(const std::string&); + threads::Future connect(const std::string& host, uint16_t port); + void disconnect(const std::string&); - void ping(); + void ping(); - threads::Future login(const std::string&, const std::string&); - threads::Future, std::shared_ptr>> 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 login(const std::string&, const std::string&); + threads::Future, std::shared_ptr>> 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>> list(int offset, int count); - threads::Future deleteLicense(const std::string& key, bool full = false); + threads::Future>> list(int offset, int count); + threads::Future 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 queue_write; + threads::Mutex queue_lock; + std::deque queue_write; - std::unique_ptr current_packet; + std::unique_ptr 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> future_connect; - std::unique_ptr> future_login; - std::unique_ptr, std::shared_ptr>>> future_register; - std::unique_ptr>>> future_list; - std::unique_ptr> future_delete; - } listener; + struct { + std::unique_ptr> future_connect; + std::unique_ptr> future_login; + std::unique_ptr, std::shared_ptr>>> future_register; + std::unique_ptr>>> future_list; + std::unique_ptr> 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&); + }; } \ No newline at end of file diff --git a/license/manager/ServerConnectionExecutor.cpp b/license/manager/ServerConnectionExecutor.cpp index 067e194..e85fc41 100644 --- a/license/manager/ServerConnectionExecutor.cpp +++ b/license/manager/ServerConnectionExecutor.cpp @@ -35,7 +35,8 @@ threads::Future, std::shared_ptrlistener.future_register = std::make_unique, std::shared_ptr>>>(); @@ -48,6 +49,9 @@ threads::Future, std::shared_ptr(start.time_since_epoch()).count()); request.set_end(duration_cast(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) diff --git a/license/manager/ServerConnectionHandler.cpp b/license/manager/ServerConnectionHandler.cpp index faffd0f..6398d6d 100644 --- a/license/manager/ServerConnectionHandler.cpp +++ b/license/manager/ServerConnectionHandler.cpp @@ -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"); diff --git a/license/packets/LicenseManager.proto b/license/packets/LicenseManager.proto index 83a91fb..114e820 100644 --- a/license/packets/LicenseManager.proto +++ b/license/packets/LicenseManager.proto @@ -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 { diff --git a/license/packets/LicenseRequest.proto b/license/packets/LicenseRequest.proto index 7060b5e..4776ac5 100644 --- a/license/packets/LicenseRequest.proto +++ b/license/packets/LicenseRequest.proto @@ -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; diff --git a/license/server/LicenseManager.cpp b/license/server/DatabaseHandler.cpp similarity index 70% rename from license/server/LicenseManager.cpp rename to license/server/DatabaseHandler.cpp index 9a0ef64..60ddbc5 100644 --- a/license/server/LicenseManager.cpp +++ b/license/server/DatabaseHandler.cpp @@ -1,16 +1,17 @@ -#include "LicenseManager.h" +#include "DatabaseHandler.h" +#include #include 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(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& info, const std::string& issuer) { +bool DatabaseHandler::register_license(const std::string& key, const shared_ptr& 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
  • 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> query_license(SqlManager* mgr, std::string key, int offset, int length) { +inline std::map> query_license(SqlManager* mgr, const std::string& key, int offset, int length) { std::map> 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> 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> query_license(SqlMana return result; } -std::shared_ptr LicenseManager::licenseInfo(const std::string& key) { +std::shared_ptr 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> LicenseManager::listLicenses(int offset, int limit) { +std::map> 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(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::list_statistics_user(const system_clock::time_point &begin, const system_clock::time_point &end, const milliseconds &interval) { +std::shared_ptr 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::list_statistics_use auto info = &_result->history[0]; /* temp db variables */ - map server_statistics; + map 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::list_statistics_use return result; } -std::deque> 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>> server_statistics; +std::deque> 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>> 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> 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(); + auto info = make_unique(); for(int index = 0; index < columns; index++) { try { if(names[index] == "keyId") @@ -477,10 +493,10 @@ std::deque> LicenseMana return {}; } - std::deque> result; + std::deque> result; system_clock::time_point current_timestamp = begin; while(current_timestamp <= end) { - auto info = make_unique(); + auto info = make_unique(); info->timestamp = current_timestamp; for(auto& server : server_statistics) { @@ -511,4 +527,87 @@ std::deque> 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(begin_timestamp.time_since_epoch()).count()}, + variable{":timestamp_end", std::chrono::duration_cast(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 DatabaseHandler::query_license_upgrade(upgrade_id_t id) { + std::unique_ptr 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(); + 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::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()); + } } \ No newline at end of file diff --git a/license/server/DatabaseHandler.h b/license/server/DatabaseHandler.h new file mode 100644 index 0000000..a1d907d --- /dev/null +++ b/license/server/DatabaseHandler.h @@ -0,0 +1,125 @@ +#pragma once + +#include +#include +#include + +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> 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 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 key_id_cache() { return this->id_cache; } + + bool validLicenseKey(const std::string& /* key */); + std::shared_ptr query_license_info(const std::string&); + std::map> 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 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& /* 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> 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 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> 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 id_cache; + sql::SqlManager* database; + }; +} \ No newline at end of file diff --git a/license/server/KeyIdCache.cpp b/license/server/KeyIdCache.cpp index f148af9..442205b 100644 --- a/license/server/KeyIdCache.cpp +++ b/license/server/KeyIdCache.cpp @@ -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; } \ No newline at end of file diff --git a/license/server/LicenseManager.h b/license/server/LicenseManager.h deleted file mode 100644 index 713fea7..0000000 --- a/license/server/LicenseManager.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include -#include -#include - -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& /* 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 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 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(const std::string&); - - std::map> listLicenses(int offset = 0, int limit = -1); - - bool registerLicense(const std::string& /* key */, const std::shared_ptr& /* info */, const std::string& /* issuer */); - bool updateLicenseInfo(const std::string&, const std::shared_ptr&); - 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> 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 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> 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 id_cache; - sql::SqlManager* database; - }; - } -} \ No newline at end of file diff --git a/license/server/LicenseServer.cpp b/license/server/LicenseServer.cpp index 95f68d5..7ce0a73 100644 --- a/license/server/LicenseServer.cpp +++ b/license/server/LicenseServer.cpp @@ -9,10 +9,8 @@ #include #include #include -#include #include #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 manager, + std::shared_ptr manager, shared_ptr stats, shared_ptr wstats, std::shared_ptr 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& 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) { diff --git a/license/server/LicenseServer.h b/license/server/LicenseServer.h index 063d974..d1837f8 100644 --- a/license/server/LicenseServer.h +++ b/license/server/LicenseServer.h @@ -11,7 +11,7 @@ #include #include #include -#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 , std::shared_ptr /* stats */, std::shared_ptr /* web stats */, std::shared_ptr /* user manager */); + explicit LicenseServer(const sockaddr_in&, std::shared_ptr , std::shared_ptr /* stats */, std::shared_ptr /* web stats */, std::shared_ptr /* user manager */); ~LicenseServer(); bool start(); @@ -91,7 +94,7 @@ namespace license { std::shared_ptr web_statistics; std::shared_ptr statistics; - std::shared_ptr manager; + std::shared_ptr manager; std::shared_ptr user_manager; std::mutex client_lock; @@ -117,6 +120,7 @@ namespace license { bool handleDisconnect(std::shared_ptr&, protocol::packet&, std::string& error); bool handleHandshake(std::shared_ptr&, protocol::packet&, std::string& error); bool handleServerValidation(std::shared_ptr &, protocol::packet &, std::string &error); + bool handlePacketLicenseUpgrade(std::shared_ptr &client, protocol::packet &packet, std::string &error); bool handlePacketPropertyUpdate(std::shared_ptr &, protocol::packet &, std::string &error); bool handlePacketAuth(std::shared_ptr &, protocol::packet &, std::string &error); diff --git a/license/server/LicenseServerHandler.cpp b/license/server/LicenseServerHandler.cpp index 813787a..da21193 100644 --- a/license/server/LicenseServerHandler.cpp +++ b/license/server/LicenseServerHandler.cpp @@ -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& 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& 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 &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 remoteLicense = nullptr; - if(pkt.licensed() && !pkt.has_license()) { - //TODO shutdown server + std::shared_ptr 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(); - 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 &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 &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 &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(); - 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(pkt.type()); + { + auto db_info = make_shared(); + 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(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 &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 &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; diff --git a/license/server/StatisticManager.cpp b/license/server/StatisticManager.cpp index 5e813e5..375ab34 100644 --- a/license/server/StatisticManager.cpp +++ b/license/server/StatisticManager.cpp @@ -4,8 +4,10 @@ #include #include + +#include #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 &manager) : license_manager(manager) {} -StatisticManager::~StatisticManager() {} +StatisticManager::StatisticManager(std::shared_ptr 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>& entries, bool unique, int length, string* values, string* names) { +void parse_general_entry(std::deque>& entries, bool unique, int length, string* values, string* names) { auto entry = make_unique(); for(int index = 0; index < length; index++) { if(names[index] == "keyId") { @@ -139,7 +141,7 @@ std::shared_ptr 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(system_clock::now().time_since_epoch() - hours(2) - minutes(10)).count()}) //10min as buffer - .query(std::function(parse_general_entry), entries, true); + .query(std::function{parse_general_entry}, entries, true); auto stats = make_shared(); for(auto& entry : entries) { diff --git a/license/server/StatisticManager.h b/license/server/StatisticManager.h index ea6ff43..d41c5d7 100644 --- a/license/server/StatisticManager.h +++ b/license/server/StatisticManager.h @@ -3,7 +3,7 @@ #include #include #include -#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 statistics; + std::shared_ptr statistics; }; class StatisticManager { public: - explicit StatisticManager(const std::shared_ptr& /* manager */); + explicit StatisticManager(std::shared_ptr /* manager */); virtual ~StatisticManager(); void reset_cache_general(); std::shared_ptr general_statistics(); std::shared_ptr history(HistoryStatistics::HistoryType); private: - std::shared_ptr license_manager; + std::shared_ptr license_manager; std::recursive_mutex _general_statistics_lock; std::recursive_mutex _general_statistics_generate_lock; diff --git a/license/server/WebAPI.cpp b/license/server/WebAPI.cpp index 4e3f2a5..7bf8d85 100644 --- a/license/server/WebAPI.cpp +++ b/license/server/WebAPI.cpp @@ -17,7 +17,7 @@ using namespace ts::ssl; using namespace std; using namespace std::chrono; -WebStatistics::WebStatistics(const shared_ptr &manager, const std::shared_ptr& stats) : license_manager(manager), statistics_manager(stats) {} +WebStatistics::WebStatistics(const shared_ptr &manager, const std::shared_ptr& stats) : license_manager(manager), statistics_manager(stats) {} WebStatistics::~WebStatistics() {} #define SFAIL(message) \ diff --git a/license/server/WebAPI.h b/license/server/WebAPI.h index 58a61b9..4b14bb2 100644 --- a/license/server/WebAPI.h +++ b/license/server/WebAPI.h @@ -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& /* license manager */, const std::shared_ptr& /* stats manager */); + WebStatistics(const std::shared_ptr& /* license manager */, const std::shared_ptr& /* stats manager */); virtual ~WebStatistics(); bool start(std::string& /* error */, uint16_t /* port */, const std::shared_ptr& /* ssl */); @@ -75,7 +75,7 @@ namespace license { bool _running = false; std::recursive_mutex running_lock; - std::shared_ptr license_manager; + std::shared_ptr license_manager; std::shared_ptr statistics_manager; struct { diff --git a/license/shared/License.cpp b/license/shared/License.cpp index 9656b6a..d4d15f7 100644 --- a/license/shared/License.cpp +++ b/license/shared/License.cpp @@ -3,7 +3,7 @@ #include #include -//#define NO_OPEN_SSL +#define NO_OPEN_SSL #include #include #include diff --git a/license/shared/License.h b/license/shared/License.h index 4340ee5..2368af7 100644 --- a/license/shared/License.h +++ b/license/shared/License.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -9,7 +10,7 @@ #include #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> hierarchy() const { return this->_hierarchy; } + [[nodiscard]] std::vector> hierarchy() const { return this->_hierarchy; } bool push_entry(const std::shared_ptr& /* 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 _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 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(new HierarchyEntry{T::_type, pub_key, begin, end}); + auto result = std::make_shared(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}; diff --git a/license/shared/LicenseRequest.cpp b/license/shared/LicenseRequest.cpp index fc317ab..5fdef57 100644 --- a/license/shared/LicenseRequest.cpp +++ b/license/shared/LicenseRequest.cpp @@ -1,11 +1,15 @@ #include +#include #include #include #include "crypt.h" + #define DEFINE_HELPER #include "LicenseRequest.h" #include "License.h" #include +#include +#include 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 & license, const sockaddr_in& remoteAddr) : data(license) { +LicenceRequest::LicenceRequest(const std::shared_ptr & license, const sockaddr_in& remoteAddr) : data{license} { #ifdef DEBUG_LICENSE_CLIENT memtrack::allocated(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; diff --git a/license/shared/LicenseRequest.h b/license/shared/LicenseRequest.h index 6f4b7bd..f1c49a5 100644 --- a/license/shared/LicenseRequest.h +++ b/license/shared/LicenseRequest.h @@ -1,8 +1,7 @@ #pragma once +#include #include -#include -#include #include #include #include @@ -96,7 +95,8 @@ namespace license { void sendPacket(const protocol::packet&); - std::function callback_update_certificate{nullptr}; + std::function callback_update_certificate{nullptr}; + std::function callback_update_license{nullptr}; bool verbose = true; private: std::shared_ptr 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; diff --git a/license/shared/LicenseRequestHandler.cpp b/license/shared/LicenseRequestHandler.cpp index 0366e29..e96ff5e 100644 --- a/license/shared/LicenseRequestHandler.cpp +++ b/license/shared/LicenseRequestHandler.cpp @@ -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(); auto licenseInfo = make_shared(); - 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); diff --git a/license/shared/LicenseServerClient.cpp b/license/shared/LicenseServerClient.cpp new file mode 100644 index 0000000..b2d31e8 --- /dev/null +++ b/license/shared/LicenseServerClient.cpp @@ -0,0 +1,527 @@ +// +// Created by WolverinDEV on 23/02/2020. +// + +#include +#include +#include +#include +#include +#include "LicenseServerClient.h" +#include "crypt.h" + +using namespace license::client; + +LicenseServerClient::Buffer* LicenseServerClient::Buffer::allocate(size_t capacity) { + static_assert(std::is_trivially_constructible::value); + + const auto allocated_bytes = sizeof(LicenseServerClient::Buffer) + capacity; + auto result = malloc(allocated_bytes); + if(!result) return nullptr; + + auto buffer = reinterpret_cast(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::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(&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(_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(_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(timeout - now); + auto seconds = std::chrono::duration_cast(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(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{}); +} \ No newline at end of file diff --git a/license/shared/LicenseServerClient.h b/license/shared/LicenseServerClient.h new file mode 100644 index 0000000..7719825 --- /dev/null +++ b/license/shared/LicenseServerClient.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include + +#include "./License.h" + +namespace license::client { + class LicenseServerClient { + public: + enum ConnectionState { + CONNECTING, + INITIALIZING, + CONNECTED, + DISCONNECTING, + + UNCONNECTED + }; + typedef std::function callback_connected_t; + typedef std::function callback_message_t; + typedef std::function 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 */); + }; +} \ No newline at end of file diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 3a8e8ef..b58c4cf 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -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) diff --git a/server/CreateEnviroment.txt b/server/CreateEnviroment.txt index b816ada..527cba5 100644 --- a/server/CreateEnviroment.txt +++ b/server/CreateEnviroment.txt @@ -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= \ No newline at end of file diff --git a/server/main.cpp b/server/main.cpp index 174b142..75fb9ce 100644 --- a/server/main.cpp +++ b/server/main.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -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; } \ No newline at end of file diff --git a/server/src/Configuration.cpp b/server/src/Configuration.cpp index 1255efc..22b6b0a 100644 --- a/server/src/Configuration.cpp +++ b/server/src/Configuration.cpp @@ -341,7 +341,7 @@ vector config::parseConfig(const std::string& path) { } cfgStream.close(); - map> comments; + std::map> comments; try { int config_version; string teaspeak_license; @@ -351,9 +351,9 @@ vector 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 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 config::parseConfig(const std::string& path) { teaspeak_license = "none"; goto license_parsing; } + */ } { @@ -548,8 +547,7 @@ vector config::parseConfig(const std::string& path) { } std::vector config::reload() { - - vector errors; + std::vector errors; saveConfig = false; ifstream cfgStream(_config_path); @@ -589,6 +587,53 @@ std::vector config::reload() { return errors; } +bool config::update_license(std::string &error, const std::string &new_license) { + std::vector 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& _entry, std::string& target, const std::string& default_value) { _entry->default_value = [default_value]() -> std::deque { return { default_value }; }; _entry->value_description = [] { return "The value must be a string"; }; diff --git a/server/src/Configuration.h b/server/src/Configuration.h index 01644a0..91181b8 100644 --- a/server/src/Configuration.h +++ b/server/src/Configuration.h @@ -29,6 +29,7 @@ namespace ts::config { std::function read_argument; }; + extern bool update_license(std::string& /* error */, const std::string& /* new license */); extern std::vector parseConfig(const std::string& /* path */); extern std::vector reload(); extern std::deque> create_bindings(); diff --git a/server/src/InstanceHandler.cpp b/server/src/InstanceHandler.cpp index f6ea0eb..b80f8bd 100644 --- a/server/src/InstanceHandler.cpp +++ b/server/src/InstanceHandler.cpp @@ -44,7 +44,11 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) { this->statistics = make_shared(nullptr, true); this->statistics->measure_bandwidths(true); - this->licenseHelper = make_shared(); + std::string error_message{}; + this->license_service_ = std::make_shared(); + 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 InstanceHandler::generateLicenseData() { - auto request = make_shared(); +std::shared_ptr InstanceHandler::generateLicenseData() { + auto request = std::make_shared(); 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(); - request->speach_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as(); - request->speach_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as(); - request->speach_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as(); + 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(); + request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as(); + request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as(); + request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as(); 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(); - info->timestamp = duration_cast(system_clock::now().time_since_epoch()).count(); - info->version = build::version()->string(true); + request->info.timestamp = std::chrono::duration_cast(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 InstanceHandler::generateLicenseDat if(property_unique_id.as().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() + get_mac_address()); - info->unique_identifier = base64::encode(hash); + request->info.unique_id = base64::encode(hash); } - - request->info = info; } return request; } diff --git a/server/src/InstanceHandler.h b/server/src/InstanceHandler.h index 21a901d..8f3569d 100644 --- a/server/src/InstanceHandler.h +++ b/server/src/InstanceHandler.h @@ -3,9 +3,8 @@ #include #include #include "VirtualServerManager.h" -#include "../../license/shared/LicenseRequest.h" -#include "lincense/LicenseHelper.h" #include +#include #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 getStatistics(){ return statistics; } std::shared_ptr scheduler(){ return this->tick_manager; } - std::shared_ptr generateLicenseData(); + std::shared_ptr generateLicenseData(); std::shared_ptr getTeamSpeakLicense() { return this->teamspeak_license; } std::shared_ptr getDefaultServerProperties() { return this->default_server_properties; } @@ -90,6 +93,8 @@ namespace ts { bool granted = false, std::shared_ptr cache = nullptr ); + + [[nodiscard]] inline std::shared_ptr license_service() { return this->license_service_; } private: std::mutex activeLock; std::condition_variable activeCon; @@ -126,7 +131,7 @@ namespace ts { std::shared_ptr globalServerAdmin = nullptr; std::shared_ptr _musicRoot = nullptr; - std::shared_ptr licenseHelper = nullptr; + std::shared_ptr license_service_{nullptr}; std::shared_ptr statistics = nullptr; std::shared_ptr tick_manager = nullptr; diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index 42ba4ba..9949045 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -39,7 +39,7 @@ bool SpeakingClient::shouldReceiveVoiceWhisper(const std::shared_ptrshouldReceiveVoice(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 := //Channel group => type := CHANNEL_GROUP and target_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& cl) { + auto speakingClient = dynamic_pointer_cast(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()) { + 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& cl) { + auto speakingClient = dynamic_pointer_cast(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& cl) { + auto speakingClient = dynamic_pointer_cast(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()) { + 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(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); } diff --git a/server/src/client/SpeakingClient.h b/server/src/client/SpeakingClient.h index d3888a8..643d5c7 100644 --- a/server/src/client/SpeakingClient.h +++ b/server/src/client/SpeakingClient.h @@ -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}; diff --git a/server/src/client/SpeakingClientHandshake.cpp b/server/src/client/SpeakingClientHandshake.cpp index 3b4f43e..b224647 100644 --- a/server/src/client/SpeakingClientHandshake.cpp +++ b/server/src/client/SpeakingClientHandshake.cpp @@ -1,11 +1,11 @@ #include "SpeakingClient.h" -#include -#include #include -#include #include #include #include +#include +#include "../VirtualServerManager.h" +#include "../InstanceHandler.h" #if defined(TCP_CORK) && !defined(TCP_NOPUSH) #define TCP_NOPUSH TCP_CORK diff --git a/server/src/client/command_handler/channel.cpp b/server/src/client/command_handler/channel.cpp index b75b86b..3c430ee 100644 --- a/server/src/client/command_handler/channel.cpp +++ b/server/src/client/command_handler/channel.cpp @@ -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(), 0}, permission::v2::set_value, permission::v2::do_nothing); } channel->properties()[key] = cmd[key->name].string(); diff --git a/server/src/client/command_handler/misc.cpp b/server/src/client/command_handler/misc.cpp index 3b06490..ecca6e2 100644 --- a/server/src/client/command_handler/misc.cpp +++ b/server/src/client/command_handler/misc.cpp @@ -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() : 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; } } diff --git a/server/src/client/file/FileClient.cpp b/server/src/client/file/FileClient.cpp index 550f945..6d7eb6d 100644 --- a/server/src/client/file/FileClient.cpp +++ b/server/src/client/file/FileClient.cpp @@ -7,6 +7,7 @@ #include #include #include "src/client/ConnectedClient.h" +#include using namespace std; using namespace std::chrono; diff --git a/server/src/lincense/LicenseHelper.cpp b/server/src/lincense/LicenseHelper.cpp deleted file mode 100644 index 05b85c1..0000000 --- a/server/src/lincense/LicenseHelper.cpp +++ /dev/null @@ -1,200 +0,0 @@ -#include -#include -#include -#include -#include -#include -#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_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); -} \ No newline at end of file diff --git a/server/src/lincense/LicenseHelper.h b/server/src/lincense/LicenseHelper.h deleted file mode 100644 index acc84dc..0000000 --- a/server/src/lincense/LicenseHelper.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include "../../../license/shared/License.h" -#include "../../../license/shared/LicenseRequest.h" - -namespace license { - class LicenseHelper { - public: - LicenseHelper(); - ~LicenseHelper(); - - void tick(); - std::shared_ptr 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 current = nullptr; - std::mutex current_lock; - - std::shared_ptr 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&); - }; -} \ No newline at end of file diff --git a/server/src/lincense/LicenseService.cpp b/server/src/lincense/LicenseService.cpp new file mode 100644 index 0000000..31aed73 --- /dev/null +++ b/server/src/lincense/LicenseService.cpp @@ -0,0 +1,540 @@ +// +// Created by WolverinDEV on 27/02/2020. +// +#include +#include +#include +#include +#include +#include +#include +#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(); +} + +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 &, 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 &result) { + if(!success) { + this->handle_check_fail(std::get(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(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(); +} \ No newline at end of file diff --git a/server/src/lincense/LicenseService.h b/server/src/lincense/LicenseService.h new file mode 100644 index 0000000..71ac378 --- /dev/null +++ b/server/src/lincense/LicenseService.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include +#include +#include + +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 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 lock{nullptr}; + + struct _lookup { + std::shared_ptr lock{nullptr}; + std::thread thread{}; + + LicenseService* handle{nullptr}; /* may be null, locked via lock */ + }* current_lookup{nullptr}; + } dns; + + std::optional 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& /* 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& /* 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(); + }; +} \ No newline at end of file diff --git a/server/src/terminal/CommandHandler.cpp b/server/src/terminal/CommandHandler.cpp index 170b938..3f2708c 100644 --- a/server/src/terminal/CommandHandler.cpp +++ b/server/src/terminal/CommandHandler.cpp @@ -1,17 +1,20 @@ +#include "./CommandHandler.h" + #include -#include -#include -#include -#include -#include #include -#include #include #include #include #include -#include "CommandHandler.h" -#include "src/server/QueryServer.h" +#include + +#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 diff --git a/shared b/shared index 51d9a00..fd8aba2 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit 51d9a002e3311a2079b25253b7946a4b03a9baf4 +Subproject commit fd8aba2ede8675f4feb9fcfc01ada3822f2d4780