552 lines
21 KiB
C++
552 lines
21 KiB
C++
//
|
|
// Created by WolverinDEV on 27/02/2020.
|
|
//
|
|
#include <netdb.h>
|
|
#include <cassert>
|
|
#include <misc/strobf.h>
|
|
#include <log/LogUtils.h>
|
|
#include <src/Configuration.h>
|
|
#include <src/ShutdownHelper.h>
|
|
#include <license/client.h>
|
|
#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<std::recursive_mutex>();
|
|
}
|
|
|
|
LicenseService::~LicenseService() {
|
|
{
|
|
std::lock_guard lock{this->request_lock};
|
|
this->abort_request(lock, "");
|
|
}
|
|
}
|
|
|
|
bool LicenseService::initialize(std::string &error) {
|
|
//this->verbose_ = true;
|
|
this->startup_timepoint_ = std::chrono::steady_clock::now();
|
|
this->timings.next_request = std::chrono::system_clock::now() + std::chrono::seconds(rand() % 20);
|
|
return true;
|
|
}
|
|
|
|
bool LicenseService::execute_request_sync(const std::chrono::milliseconds& timeout) {
|
|
std::unique_lock slock{this->sync_request_lock};
|
|
this->begin_request();
|
|
|
|
if(this->sync_request_cv.wait_for(slock, timeout) == std::cv_status::timeout)
|
|
return false;
|
|
|
|
return this->timings.failed_count == 0;
|
|
}
|
|
|
|
void LicenseService::shutdown() {
|
|
std::lock_guard lock{this->request_lock};
|
|
if(this->request_state_ == request_state::empty) return;
|
|
|
|
this->abort_request(lock, "shutdown");
|
|
}
|
|
|
|
void LicenseService::begin_request() {
|
|
std::lock_guard lock{this->request_lock};
|
|
if(this->request_state_ != request_state::empty)
|
|
this->abort_request(lock, "last request has been aborted");
|
|
|
|
if(this->verbose_)
|
|
debugMessage(LOG_INSTANCE, strobf("Executing license request.").string());
|
|
this->timings.last_request = std::chrono::system_clock::now();
|
|
this->request_state_ = request_state::dns_lookup;
|
|
this->execute_dns_request();
|
|
}
|
|
|
|
void LicenseService::abort_request(std::lock_guard<std::recursive_timed_mutex> &, const std::string& reason) {
|
|
if(this->request_state_ == request_state::dns_lookup) {
|
|
this->abort_dns_request();
|
|
return;
|
|
} else if(this->current_client) {
|
|
this->current_client->callback_connected = nullptr;
|
|
this->current_client->callback_message = nullptr;
|
|
this->current_client->callback_disconnected = nullptr;
|
|
|
|
if(!reason.empty()) {
|
|
this->current_client->disconnect(reason, std::chrono::system_clock::now() + std::chrono::seconds{1});
|
|
/* Lets not wait here because we might be within the event loop. */
|
|
//if(!this->current_client->await_disconnect())
|
|
// this->current_client->close_connection();
|
|
} else {
|
|
this->current_client->close_connection();
|
|
}
|
|
|
|
this->current_client.reset();
|
|
}
|
|
}
|
|
|
|
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() {
|
|
this->timings.last_succeeded = std::chrono::system_clock::now();
|
|
{
|
|
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());
|
|
}
|
|
|
|
if(!config::server::check_server_version_with_license())
|
|
handle_check_fail(strobf("memory invalid").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");
|
|
|
|
//1 + 5*4 + 5*10+20*30
|
|
//1 + 5*4 + 5*10+70*30
|
|
auto soft_license_check = config::license->isValid() && (
|
|
this->timings.last_succeeded.time_since_epoch().count() == 0 ? this->timings.failed_count < 32 : /* About 12hours */
|
|
this->timings.failed_count < 82 /* about 36 hours */
|
|
);
|
|
const auto invalid_memory = !config::server::check_server_version_with_license();
|
|
if(invalid_memory || (config::license->isPremium() && !soft_license_check)) {
|
|
logCritical(LOG_INSTANCE, strobf("Failed to validate license:").string());
|
|
logCritical(LOG_INSTANCE, invalid_memory ? strobf("invalid memory").string() : error);
|
|
logCritical(LOG_INSTANCE, strobf("Stopping server!").string());
|
|
ts::server::shutdownInstance();
|
|
} else {
|
|
logError(LOG_INSTANCE, strobf("Failed to validate instance integrity:").string());
|
|
logError(LOG_INSTANCE, error);
|
|
}
|
|
|
|
this->schedule_next_request(false);
|
|
this->request_state_ = request_state::empty;
|
|
}
|
|
|
|
{
|
|
std::unique_lock slock{this->sync_request_lock};
|
|
this->sync_request_cv.notify_all();
|
|
}
|
|
}
|
|
|
|
void LicenseService::handle_dns_lookup_result(bool success, const std::variant<std::string, sockaddr_in> &result) {
|
|
if(!success) {
|
|
this->handle_check_fail(std::get<std::string>(result));
|
|
return;
|
|
}
|
|
|
|
std::lock_guard rlock{this->request_lock};
|
|
if(this->request_state_ != request_state::dns_lookup) {
|
|
logError(LOG_INSTANCE, strobf("Request state isn't dns lookup anymore. Aborting dns lookup result callback.").string());
|
|
return;
|
|
}
|
|
this->request_state_ = request_state::connecting;
|
|
|
|
assert(!this->current_client);
|
|
this->current_client = std::make_shared<::license::client::LicenseServerClient>(std::get<sockaddr_in>(result), 3);
|
|
this->current_client->callback_connected = [&]{ this->handle_client_connected(); };
|
|
this->current_client->callback_disconnected = [&](bool expected, const std::string& error) {
|
|
this->handle_client_disconnected(error);
|
|
};
|
|
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();
|
|
}
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
|
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;
|
|
}
|
|
}
|
|
#pragma GCC diagnostic pop
|
|
|
|
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.set_memory_valid(config::server::check_server_version_with_license());
|
|
request.mutable_info()->set_uname(this->license_request_data->info.uname);
|
|
request.mutable_info()->set_version(this->license_request_data->info.version);
|
|
request.mutable_info()->set_timestamp(this->license_request_data->info.timestamp.count());
|
|
request.mutable_info()->set_unique_id(this->license_request_data->info.unique_id);
|
|
this->client_send_message(::license::protocol::PACKET_CLIENT_SERVER_VALIDATION, request);
|
|
}
|
|
|
|
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("invalid 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};
|
|
else 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("check timeout").string());
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
if(std::chrono::system_clock::now() > this->timings.next_request)
|
|
this->begin_request();
|
|
} |