From 25c507c6996d49d876e4d17f70edc13293da93e9 Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Mon, 2 Mar 2020 13:34:35 +0100 Subject: [PATCH] Some more updates --- license/shared/include/license/client.h | 192 ++-- license/shared/src/client.cpp | 1052 +++++++++++----------- server/src/lincense/LicenseService.cpp | 1074 +++++++++++------------ server/src/lincense/LicenseService.h | 266 +++--- 4 files changed, 1292 insertions(+), 1292 deletions(-) diff --git a/license/shared/include/license/client.h b/license/shared/include/license/client.h index c9aa1ee..3b55a70 100644 --- a/license/shared/include/license/client.h +++ b/license/shared/include/license/client.h @@ -1,97 +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 */); - }; +#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/license/shared/src/client.cpp b/license/shared/src/client.cpp index 52c2f8d..238df2f 100644 --- a/license/shared/src/client.cpp +++ b/license/shared/src/client.cpp @@ -1,527 +1,527 @@ -// -// Created by WolverinDEV on 23/02/2020. -// - -#include -#include -#include -#include -#include -#include "shared/include/license/client.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{}); +// +// Created by WolverinDEV on 23/02/2020. +// + +#include +#include +#include +#include +#include +#include "shared/include/license/client.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/server/src/lincense/LicenseService.cpp b/server/src/lincense/LicenseService.cpp index 69d67b8..d03d0d0 100644 --- a/server/src/lincense/LicenseService.cpp +++ b/server/src/lincense/LicenseService.cpp @@ -1,538 +1,538 @@ -// -// Created by WolverinDEV on 27/02/2020. -// -#include -#include -#include -#include -#include -#include -#include -#include "src/InstanceHandler.h" -#include "LicenseRequest.pb.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() && (this->timings.last_succeeded.time_since_epoch().count() == 0 || this->timings.failed_count < 6)) { - 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(); +// +// Created by WolverinDEV on 27/02/2020. +// +#include +#include +#include +#include +#include +#include +#include +#include "src/InstanceHandler.h" +#include "LicenseRequest.pb.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() && (this->timings.last_succeeded.time_since_epoch().count() == 0 || this->timings.failed_count < 6)) { + 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 index 71ac378..c24c721 100644 --- a/server/src/lincense/LicenseService.h +++ b/server/src/lincense/LicenseService.h @@ -1,134 +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(); - }; +#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