1.4.10 updates
This commit is contained in:
@@ -341,7 +341,7 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
}
|
||||
cfgStream.close();
|
||||
|
||||
map<string,deque<string>> comments;
|
||||
std::map<std::string, std::deque<std::string>> comments;
|
||||
try {
|
||||
int config_version;
|
||||
string teaspeak_license;
|
||||
@@ -351,9 +351,9 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
build_comments(comments, bindings);
|
||||
}
|
||||
if(config_version > CURRENT_CONFIG_VERSION) {
|
||||
errors.push_back("Given config version is higher that currently supported config version!");
|
||||
errors.emplace_back("Given config version is higher that currently supported config version!");
|
||||
errors.push_back("Decrease the version by hand to " + to_string(CURRENT_CONFIG_VERSION));
|
||||
errors.push_back("Attention: Decreasing the version could may lead to data loss!");
|
||||
errors.emplace_back("Attention: Decreasing the version could may lead to data loss!");
|
||||
return errors;
|
||||
}
|
||||
{
|
||||
@@ -462,15 +462,13 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
}
|
||||
|
||||
if(!config::license){
|
||||
logErrorFmt(true, LOG_GENERAL, strobf("The given license isn't valid!").string());
|
||||
logErrorFmt(true, LOG_GENERAL, strobf("The given license could not be parsed!").string());
|
||||
logErrorFmt(true, LOG_GENERAL, strobf("Falling back to the default license.").string());
|
||||
teaspeak_license = "none";
|
||||
goto license_parsing;
|
||||
}
|
||||
if(!config::license){
|
||||
errors.emplace_back(strobf("Invalid license code!").string());
|
||||
return errors;
|
||||
}
|
||||
|
||||
/*
|
||||
if(!config::license->isValid()) {
|
||||
if(config::license->data.type == license::LicenseType::INVALID) {
|
||||
errors.emplace_back(strobf("Give license isn't valid!").string());
|
||||
@@ -482,6 +480,7 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
teaspeak_license = "none";
|
||||
goto license_parsing;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
{
|
||||
@@ -548,8 +547,7 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
}
|
||||
|
||||
std::vector<std::string> config::reload() {
|
||||
|
||||
vector<string> errors;
|
||||
std::vector<std::string> errors;
|
||||
saveConfig = false;
|
||||
|
||||
ifstream cfgStream(_config_path);
|
||||
@@ -589,6 +587,53 @@ std::vector<std::string> config::reload() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
bool config::update_license(std::string &error, const std::string &new_license) {
|
||||
std::vector<std::string> lines{};
|
||||
|
||||
{
|
||||
lines.reserve(1024);
|
||||
|
||||
std::ifstream icfg_stream{_config_path};
|
||||
if(!icfg_stream) {
|
||||
error = "failed to open config file";
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string line{};
|
||||
while(std::getline(icfg_stream, line))
|
||||
lines.push_back(line);
|
||||
|
||||
icfg_stream.close();
|
||||
}
|
||||
|
||||
bool license_found{false};
|
||||
for(auto& line : lines) {
|
||||
if(!line.starts_with(" license:")) continue;
|
||||
|
||||
line = " license: \"" + new_license + "\"";
|
||||
license_found = true;
|
||||
break;
|
||||
}
|
||||
if(!license_found) {
|
||||
error = "missing license config key";
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::ofstream ocfg_stream{_config_path};
|
||||
if(!ocfg_stream) {
|
||||
error = "failed to write to config file";
|
||||
return false;
|
||||
}
|
||||
|
||||
for(const auto& line : lines)
|
||||
ocfg_stream << line << "\n";
|
||||
ocfg_stream << std::flush;
|
||||
ocfg_stream.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void bind_string_description(const shared_ptr<EntryBinding>& _entry, std::string& target, const std::string& default_value) {
|
||||
_entry->default_value = [default_value]() -> std::deque<std::string> { return { default_value }; };
|
||||
_entry->value_description = [] { return "The value must be a string"; };
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace ts::config {
|
||||
std::function<void(const std::string&)> read_argument;
|
||||
};
|
||||
|
||||
extern bool update_license(std::string& /* error */, const std::string& /* new license */);
|
||||
extern std::vector<std::string> parseConfig(const std::string& /* path */);
|
||||
extern std::vector<std::string> reload();
|
||||
extern std::deque<std::shared_ptr<EntryBinding>> create_bindings();
|
||||
|
||||
@@ -44,7 +44,11 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr, true);
|
||||
this->statistics->measure_bandwidths(true);
|
||||
|
||||
this->licenseHelper = make_shared<license::LicenseHelper>();
|
||||
std::string error_message{};
|
||||
this->license_service_ = std::make_shared<license::LicenseService>();
|
||||
if(!this->license_service_->initialize(error_message)) {
|
||||
logCritical(LOG_INSTANCE, strobf("Failed to the license service: {}").string(), error_message);
|
||||
}
|
||||
this->dbHelper = new DatabaseHelper(this->getSql());
|
||||
|
||||
this->_properties = new Properties();
|
||||
@@ -214,7 +218,6 @@ InstanceHandler::~InstanceHandler() {
|
||||
globalServerAdmin = nullptr;
|
||||
_musicRoot = nullptr;
|
||||
|
||||
licenseHelper = nullptr;
|
||||
statistics = nullptr;
|
||||
tick_manager = nullptr;
|
||||
}
|
||||
@@ -453,6 +456,8 @@ void InstanceHandler::stopInstance() {
|
||||
this->sslMgr = nullptr;
|
||||
|
||||
this->web_event_loop = nullptr;
|
||||
|
||||
this->license_service_->shutdown();
|
||||
}
|
||||
|
||||
void InstanceHandler::tickInstance() {
|
||||
@@ -470,7 +475,7 @@ void InstanceHandler::tickInstance() {
|
||||
}
|
||||
{
|
||||
ALARM_TIMER(t, strobf("InstanceHandler::tickInstance -> license tick").string(), milliseconds(5));
|
||||
this->licenseHelper->tick();
|
||||
this->license_service_->execute_tick();
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -637,36 +642,35 @@ string get_mac_address() {
|
||||
}
|
||||
|
||||
#define SN_BUFFER 1024
|
||||
std::shared_ptr<license::LicenseRequestData> InstanceHandler::generateLicenseData() {
|
||||
auto request = make_shared<license::LicenseRequestData>();
|
||||
std::shared_ptr<ts::server::license::InstanceLicenseInfo> InstanceHandler::generateLicenseData() {
|
||||
auto request = std::make_shared<license::InstanceLicenseInfo>();
|
||||
request->license = config::license;
|
||||
request->servers_online = this->voiceServerManager->runningServers();
|
||||
request->metrics.servers_online = this->voiceServerManager->runningServers();
|
||||
auto report = this->voiceServerManager->clientReport();
|
||||
request->client_online = report.clients_ts;
|
||||
request->web_clients_online = report.clients_web;
|
||||
request->bots_online = report.bots;
|
||||
request->queries_online = report.queries;
|
||||
request->speach_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as<uint64_t>();
|
||||
request->speach_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
|
||||
request->speach_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>();
|
||||
request->speach_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>();
|
||||
request->metrics.client_online = report.clients_ts;
|
||||
request->metrics.web_clients_online = report.clients_web;
|
||||
request->metrics.bots_online = report.bots;
|
||||
request->metrics.queries_online = report.queries;
|
||||
request->metrics.speech_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as<uint64_t>();
|
||||
request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
|
||||
request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>();
|
||||
request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>();
|
||||
|
||||
static std::string null_str{"\0\0\0\0\0\0\0\0", 8}; /* we need at least some characters */
|
||||
request->web_certificate_revision = this->web_cert_revision.empty() ? null_str : this->web_cert_revision;
|
||||
|
||||
{
|
||||
auto info = make_shared<license::ServerInfo>();
|
||||
info->timestamp = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
info->version = build::version()->string(true);
|
||||
request->info.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
request->info.version = build::version()->string(true);
|
||||
|
||||
{ /* uname */
|
||||
utsname retval{};
|
||||
if(uname(&retval) < 0) {
|
||||
info->uname = "unknown (" + string(strerror(errno)) + ")";
|
||||
request->info.uname = "unknown (" + string(strerror(errno)) + ")";
|
||||
} else {
|
||||
char buffer[SN_BUFFER];
|
||||
snprintf(buffer, SN_BUFFER, "sys:%s version:%s release:%s", retval.sysname, retval.version, retval.release);
|
||||
info->uname = string(buffer);
|
||||
request->info.uname = string(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -676,12 +680,10 @@ std::shared_ptr<license::LicenseRequestData> InstanceHandler::generateLicenseDat
|
||||
if(property_unique_id.as<string>().empty())
|
||||
property_unique_id = rnd_string(64);
|
||||
|
||||
auto hash = digest::sha256(info->uname);
|
||||
auto hash = digest::sha256(request->info.uname);
|
||||
hash = digest::sha256(hash + property_unique_id.as<string>() + get_mac_address());
|
||||
info->unique_identifier = base64::encode(hash);
|
||||
request->info.unique_id = base64::encode(hash);
|
||||
}
|
||||
|
||||
request->info = info;
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
#include <sql/SqlQuery.h>
|
||||
#include <Properties.h>
|
||||
#include "VirtualServerManager.h"
|
||||
#include "../../license/shared/LicenseRequest.h"
|
||||
#include "lincense/LicenseHelper.h"
|
||||
#include <ssl/SSLManager.h>
|
||||
#include <src/lincense/LicenseService.h>
|
||||
#include "manager/SqlDataManager.h"
|
||||
#include "lincense/TeamSpeakLicense.h"
|
||||
#include "server/WebIoManager.h"
|
||||
@@ -20,6 +19,10 @@ namespace ts {
|
||||
}
|
||||
|
||||
namespace server {
|
||||
namespace license {
|
||||
class LicenseService;
|
||||
}
|
||||
|
||||
class InstanceHandler {
|
||||
public:
|
||||
explicit InstanceHandler(SqlDataManager*);
|
||||
@@ -63,7 +66,7 @@ namespace ts {
|
||||
|
||||
std::shared_ptr<stats::ConnectionStatistics> getStatistics(){ return statistics; }
|
||||
std::shared_ptr<threads::Scheduler> scheduler(){ return this->tick_manager; }
|
||||
std::shared_ptr<license::LicenseRequestData> generateLicenseData();
|
||||
std::shared_ptr<license::InstanceLicenseInfo> generateLicenseData();
|
||||
|
||||
std::shared_ptr<TeamSpeakLicense> getTeamSpeakLicense() { return this->teamspeak_license; }
|
||||
std::shared_ptr<ts::Properties> getDefaultServerProperties() { return this->default_server_properties; }
|
||||
@@ -90,6 +93,8 @@ namespace ts {
|
||||
bool granted = false,
|
||||
std::shared_ptr<CalculateCache> cache = nullptr
|
||||
);
|
||||
|
||||
[[nodiscard]] inline std::shared_ptr<license::LicenseService> license_service() { return this->license_service_; }
|
||||
private:
|
||||
std::mutex activeLock;
|
||||
std::condition_variable activeCon;
|
||||
@@ -126,7 +131,7 @@ namespace ts {
|
||||
std::shared_ptr<ts::server::InternalClient> globalServerAdmin = nullptr;
|
||||
std::shared_ptr<ConnectedClient> _musicRoot = nullptr;
|
||||
|
||||
std::shared_ptr<license::LicenseHelper> licenseHelper = nullptr;
|
||||
std::shared_ptr<license::LicenseService> license_service_{nullptr};
|
||||
std::shared_ptr<stats::ConnectionStatistics> statistics = nullptr;
|
||||
std::shared_ptr<threads::Scheduler> tick_manager = nullptr;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ bool SpeakingClient::shouldReceiveVoiceWhisper(const std::shared_ptr<ConnectedCl
|
||||
if(!this->shouldReceiveVoice(sender))
|
||||
return false;
|
||||
|
||||
return permission::v2::permission_granted(this->cpmerission_needed_whisper_power, sender->cpmerission_whisper_power);
|
||||
return permission::v2::permission_granted(this->cpmerission_needed_whisper_power, sender->cpmerission_whisper_power, false);
|
||||
}
|
||||
|
||||
void SpeakingClient::handlePacketVoice(const pipes::buffer_view& data, bool head, bool fragmented) {
|
||||
@@ -127,6 +127,15 @@ enum WhisperTarget {
|
||||
CHANNEL_SUBCHANNELS = 6
|
||||
};
|
||||
|
||||
inline bool update_whisper_error(std::chrono::system_clock::time_point& last) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(last + std::chrono::milliseconds{500} < now) {
|
||||
last = now;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//All clients => type := SERVER_GROUP and target_id := 0
|
||||
//Server group => type := SERVER_GROUP and target_id := <server group id>
|
||||
//Channel group => type := CHANNEL_GROUP and target_id := <channel group id>
|
||||
@@ -239,7 +248,27 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
|
||||
return target->currentChannel->parent() != current;
|
||||
}), available_clients.end());
|
||||
}
|
||||
if(available_clients.empty()) return;
|
||||
|
||||
auto self_lock = this->_this.lock();
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
return !speakingClient->shouldReceiveVoiceWhisper(self_lock);
|
||||
}), available_clients.end());
|
||||
|
||||
if(available_clients.empty()) {
|
||||
if(update_whisper_error(this->speak_last_no_whisper_target)) {
|
||||
command_result result{error::whisper_no_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(available_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
|
||||
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
|
||||
command_result result{error::whisper_too_many_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Create the packet data
|
||||
char packet_buffer[OUT_WHISPER_PKT_OFFSET + data_length];
|
||||
@@ -253,7 +282,6 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
|
||||
VoicePacketFlags flags{};
|
||||
auto data = pipes::buffer_view(packet_buffer, OUT_WHISPER_PKT_OFFSET + data_length);
|
||||
for(const auto& cl : available_clients){
|
||||
if(cl->shouldReceiveVoiceWhisper(_this.lock()))
|
||||
cl->send_voice_whisper_packet(data, flags);
|
||||
}
|
||||
|
||||
@@ -275,6 +303,44 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
|
||||
for(uint8_t index = 0; index < channelCount; index++)
|
||||
clientIds[index] = be2le16((char*) data.data_ptr(), offset, &offset);
|
||||
|
||||
auto available_clients = this->server->getClients();
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
if(!speakingClient || cl == this || !speakingClient->currentChannel) return true;
|
||||
|
||||
auto clientChannelId = cl->currentChannel->channelId();
|
||||
auto clientId = cl->getClientId();
|
||||
|
||||
for(uint8_t index = 0; index < clientCount; index++)
|
||||
if(channelIds[index] == clientChannelId) return false;
|
||||
|
||||
for(uint8_t index = 0; index < channelCount; index++)
|
||||
if(clientIds[index] == clientId) return false;
|
||||
|
||||
return true;
|
||||
}), available_clients.end());
|
||||
|
||||
auto self_lock = this->_this.lock();
|
||||
available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
return !speakingClient->shouldReceiveVoiceWhisper(self_lock);
|
||||
}), available_clients.end());
|
||||
|
||||
if(available_clients.empty()) {
|
||||
if(update_whisper_error(this->speak_last_no_whisper_target)) {
|
||||
command_result result{error::whisper_no_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(available_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) {
|
||||
if(update_whisper_error(this->speak_last_too_many_whisper_targets)) {
|
||||
command_result result{error::whisper_too_many_targets};
|
||||
this->notifyError(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t dataLength = data.length() - offset;
|
||||
#ifdef PKT_LOG_WHISPER
|
||||
logTrace(this->getServerId(), "{} Whisper data length: {}. Client count: {}. Channel count: {}.", CLIENT_STR_LOG_PREFIX, dataLength, clientCount, channelCount);
|
||||
@@ -291,21 +357,9 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo
|
||||
VoicePacketFlags flags{};
|
||||
auto data = pipes::buffer_view(packetBuffer, OUT_WHISPER_PKT_OFFSET + dataLength);
|
||||
|
||||
for(const auto& cl : this->server->getClients()){ //Faster?
|
||||
for(const auto& cl : available_clients){ //Faster?
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
if(!speakingClient || cl == this) continue;
|
||||
if(!cl->currentChannel) continue;
|
||||
|
||||
auto clientChannelId = cl->currentChannel->channelId();
|
||||
auto clientId = cl->getClientId();
|
||||
|
||||
for(uint8_t index = 0; index < clientCount; index++)
|
||||
if(channelIds[index] == clientChannelId) goto handleSend;
|
||||
for(uint8_t index = 0; index < channelCount; index++)
|
||||
if(clientIds[index] == clientId) goto handleSend;
|
||||
continue;
|
||||
|
||||
handleSend:
|
||||
assert(speakingClient);
|
||||
if(speakingClient->shouldReceiveVoiceWhisper(_this.lock()))
|
||||
speakingClient->send_voice_whisper_packet(data, flags);
|
||||
}
|
||||
|
||||
@@ -81,6 +81,9 @@ namespace ts {
|
||||
std::chrono::system_clock::time_point speak_begin;
|
||||
std::chrono::system_clock::time_point speak_last_packet;
|
||||
|
||||
std::chrono::system_clock::time_point speak_last_no_whisper_target;
|
||||
std::chrono::system_clock::time_point speak_last_too_many_whisper_targets;
|
||||
|
||||
permission::v2::PermissionFlaggedValue max_idle_time{permission::v2::empty_permission_flagged_value};
|
||||
struct {
|
||||
HandshakeState state{HandshakeState::BEGIN};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "SpeakingClient.h"
|
||||
#include <misc/endianness.h>
|
||||
#include <src/VirtualServerManager.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <src/InstanceHandler.h>
|
||||
#include <misc/base64.h>
|
||||
#include <misc/digest.h>
|
||||
#include <misc/rnd.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "../VirtualServerManager.h"
|
||||
#include "../InstanceHandler.h"
|
||||
|
||||
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
|
||||
#define TCP_NOPUSH TCP_CORK
|
||||
|
||||
@@ -1247,6 +1247,8 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) {
|
||||
if(conversation)
|
||||
conversation->set_history_length(cmd[key->name]);
|
||||
}
|
||||
} else if(*key == property::CHANNEL_NEEDED_TALK_POWER) {
|
||||
channel->permissions()->set_permission(permission::i_client_needed_talk_power, {cmd[key->name].as<int>(), 0}, permission::v2::set_value, permission::v2::do_nothing);
|
||||
}
|
||||
|
||||
channel->properties()[key] = cmd[key->name].string();
|
||||
|
||||
@@ -1906,7 +1906,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
}
|
||||
string command = "cat \"" + log_path + "\"";
|
||||
command += " | grep -E ";
|
||||
command += "\"\\] \\[.*\\]( ){0,6}?" + server_identifier + " \\|\"";
|
||||
command += R"("\] \[.*\]( ){0,6}?)" + server_identifier + " \\|\"";
|
||||
|
||||
size_t beginpos = cmd[0].has("begin_pos") ? cmd["begin_pos"].as<size_t>() : 0ULL; //TODO test it?
|
||||
size_t file_index = 0;
|
||||
@@ -1927,7 +1927,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
if(beginpos != 0 && file_index + read > beginpos) { //We're done we just want to get the size later
|
||||
line_buffer += string(buffer.data(), beginpos - file_index);
|
||||
|
||||
lines.push_back({file_index, line_buffer});
|
||||
lines.emplace_back(file_index, line_buffer);
|
||||
if(lines.size() > max_lines) lines.pop_front();
|
||||
//debugMessage(LOG_GENERAL, "Final line {}", line_buffer);
|
||||
line_buffer = "";
|
||||
@@ -1955,7 +1955,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
if(length == 0) length = 1;
|
||||
|
||||
//debugMessage(LOG_GENERAL, "Got line {}", line_buffer.substr(0, index));
|
||||
lines.push_back({file_index + cut_offset, line_buffer.substr(0, index)});
|
||||
lines.emplace_back(file_index + cut_offset, line_buffer.substr(0, index));
|
||||
if(lines.size() > max_lines) lines.pop_front();
|
||||
|
||||
cut_offset += index + length;
|
||||
@@ -1968,7 +1968,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
}
|
||||
|
||||
if(!line_buffer.empty()) {
|
||||
lines.push_back({file_index - line_buffer.length(), line_buffer});
|
||||
lines.emplace_back(file_index - line_buffer.length(), line_buffer);
|
||||
if(lines.size() > max_lines) lines.pop_front();
|
||||
}
|
||||
}
|
||||
@@ -2004,8 +2004,13 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) {
|
||||
|
||||
ts += type + " | | |" + line.substr(line.find('|') + 1);
|
||||
}
|
||||
|
||||
if(ts.length() > 1024)
|
||||
ts = ts.substr(0, 1024) + "...";
|
||||
result[index++]["l"] = ts;
|
||||
} else {
|
||||
if(line.length() > 1024)
|
||||
line = line.substr(0, 1024) + "...";
|
||||
result[index++]["l"] = line;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <misc/memtracker.h>
|
||||
#include <misc/base64.h>
|
||||
#include "src/client/ConnectedClient.h"
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/strobf.h>
|
||||
#include <misc/hex.h>
|
||||
#include <src/Configuration.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <src/ShutdownHelper.h>
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "LicenseHelper.h"
|
||||
|
||||
using namespace license;
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts;
|
||||
using namespace ts::server;
|
||||
|
||||
LicenseHelper::LicenseHelper() {
|
||||
this->scheduled_request = system_clock::now() + seconds(rand() % 30); //Check in one minute
|
||||
}
|
||||
|
||||
LicenseHelper::~LicenseHelper() {
|
||||
if(this->request.prepare_thread.joinable())
|
||||
this->request.prepare_thread.join();
|
||||
|
||||
{
|
||||
unique_lock lock(this->request.current_lock);
|
||||
if(this->request.current) {
|
||||
auto request = move(this->request.current);
|
||||
lock.unlock();
|
||||
|
||||
request->abortRequest();
|
||||
request->callback_update_certificate = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline string format_time(const system_clock::time_point& time) {
|
||||
std::time_t now = system_clock::to_time_t(time);
|
||||
std::tm * ptm = std::localtime(&now);
|
||||
char buffer[128];
|
||||
const auto length = std::strftime(buffer, 128, "%a, %d.%m.%Y %H:%M:%S", ptm);
|
||||
return string(buffer, length);
|
||||
}
|
||||
|
||||
void LicenseHelper::tick() {
|
||||
lock_guard tick_lock(this->license_tick_lock);
|
||||
|
||||
bool verbose = config::license->isPremium();
|
||||
{
|
||||
lock_guard request_lock(this->request.current_lock);
|
||||
if(this->request.current) {
|
||||
auto promise = this->request.current->requestInfo();
|
||||
if(promise.state() != threads::FutureState::WORKING){
|
||||
auto exception = this->request.current->exception();
|
||||
if(promise.state() == threads::FutureState::FAILED) {
|
||||
this->handle_request_failed(verbose, exception ? exception->what() : strobf("unknown").c_str());
|
||||
this->request.current = nullptr; /* connection should be already closed */
|
||||
return;
|
||||
} else {
|
||||
auto response = promise.waitAndGet(nullptr);
|
||||
this->request.current = nullptr; /* connection should be already closed */
|
||||
|
||||
if(!response){
|
||||
this->handle_request_failed(verbose, exception ? exception->what() : strobf("invalid result (null)").c_str());
|
||||
return;
|
||||
}
|
||||
if(!response->license_valid || !response->properties_valid){
|
||||
if(!response->license_valid) {
|
||||
if(config::license->isPremium()) logCritical(LOG_INSTANCE, strobf("Could not validate license.").c_str());
|
||||
else logCritical(LOG_INSTANCE, strobf("Your server has been shutdown remotely!").c_str());
|
||||
} else if(!response->properties_valid) {
|
||||
logCritical(LOG_INSTANCE, strobf("Property adjustment failed!").c_str());
|
||||
} else
|
||||
logCritical(LOG_INSTANCE, strobf("Your license expired!").c_str());
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping application!").c_str());
|
||||
ts::server::shutdownInstance();
|
||||
return;
|
||||
} else {
|
||||
this->scheduled_request = this->last_request + hours(2);
|
||||
logMessage(LOG_INSTANCE, strobf("License successfully validated! Scheduling next check at {}").c_str(), format_time(this->scheduled_request));
|
||||
if(response->speach_reset)
|
||||
serverInstance->resetSpeechTime();
|
||||
serverInstance->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ] = response->speach_varianz_adjustment;
|
||||
|
||||
{
|
||||
lock_guard lock(this->request.info_lock);
|
||||
this->request.info = response->license;
|
||||
}
|
||||
|
||||
this->request_fail_count = 0;
|
||||
this->last_successful_request = system_clock::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(system_clock::now() > scheduled_request){
|
||||
this->do_request(verbose);
|
||||
}
|
||||
}
|
||||
void LicenseHelper::do_request(bool verbose) {
|
||||
if(config::license && config::license->isPremium())
|
||||
logMessage(LOG_INSTANCE, strobf("Validating license").c_str());
|
||||
else
|
||||
logMessage(LOG_INSTANCE, strobf("Validating instance integrity").c_str());
|
||||
|
||||
this->last_request = system_clock::now();
|
||||
this->scheduled_request = this->last_request + minutes(10); /* some kind of timeout */
|
||||
|
||||
{
|
||||
unique_lock lock(this->request.current_lock);
|
||||
if(this->request.current) {
|
||||
auto request = move(this->request.current);
|
||||
lock.unlock();
|
||||
|
||||
if(verbose) {
|
||||
auto promise = request->requestInfo();
|
||||
if(promise.state() == threads::FutureState::WORKING) {
|
||||
logMessage(LOG_INSTANCE, strobf("Old check timed out (10 min). Running new one!").c_str());
|
||||
}
|
||||
}
|
||||
request->abortRequest();
|
||||
}
|
||||
}
|
||||
|
||||
if(this->request.prepare_thread.joinable()) /* usually the preparation should not take too long */
|
||||
this->request.prepare_thread.join();
|
||||
|
||||
this->request.prepare_thread = std::thread([&]{
|
||||
sockaddr_in server_addr{};
|
||||
server_addr.sin_family = AF_INET;
|
||||
|
||||
#ifdef DO_LOCAL_REQUEST
|
||||
auto license_host = gethostbyname(strobf("localhost").c_str());
|
||||
server_addr.sin_addr.s_addr = ((in_addr*) license_host->h_addr)->s_addr;
|
||||
#else
|
||||
auto license_host = gethostbyname(strobf("license.teaspeak.de").c_str());
|
||||
if(!license_host){
|
||||
if(verbose) logError(LOG_INSTANCE, strobf("Could not valid license! (1)").c_str());
|
||||
return;
|
||||
}
|
||||
if(!license_host->h_addr){
|
||||
if(verbose) logError(LOG_INSTANCE, strobf("Could not valid license! (2)").c_str());
|
||||
return;
|
||||
}
|
||||
server_addr.sin_addr.s_addr = ((in_addr*) license_host->h_addr)->s_addr;
|
||||
int first = server_addr.sin_addr.s_addr >> 24;
|
||||
if(first == 0 || first == 127 || first == 255) {
|
||||
if(config::license->isPremium()) {
|
||||
logError(LOG_INSTANCE, strobf("You tried to nullroot 'license.teaspeak.de'!").c_str());
|
||||
logCritical(LOG_INSTANCE, strobf("Could not validate license!").c_str());
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping server!").c_str());
|
||||
ts::server::shutdownInstance();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
server_addr.sin_port = htons(27786);
|
||||
|
||||
|
||||
auto license_data = serverInstance->generateLicenseData();
|
||||
auto request = make_shared<license::LicenceRequest>(license_data, server_addr);
|
||||
request->verbose = false;
|
||||
request->callback_update_certificate = [&](const auto& update) { this->callback_certificate_update(update); };
|
||||
{
|
||||
lock_guard lock(this->request.current_lock);
|
||||
this->request.current = request;
|
||||
request->requestInfo();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void LicenseHelper::handle_request_failed(bool verbose, const std::string& error) {
|
||||
if(config::license && config::license->isPremium())
|
||||
logError(LOG_INSTANCE, strobf("License validation failed: {}").c_str(), error);
|
||||
else
|
||||
logError(LOG_INSTANCE, strobf("Instance integrity check failed: {}").c_str(), error);
|
||||
|
||||
this->request_fail_count++;
|
||||
milliseconds next_request;
|
||||
|
||||
if(this->request_fail_count <= 1)
|
||||
next_request = minutes(1);
|
||||
else if(this->request_fail_count <= 5)
|
||||
next_request = minutes(5);
|
||||
else if(this->request_fail_count <= 10)
|
||||
next_request = minutes(10);
|
||||
else
|
||||
next_request = minutes(30);
|
||||
|
||||
this->scheduled_request = this->last_request + next_request;
|
||||
if(verbose)
|
||||
logMessage(LOG_INSTANCE, strobf("Scheduling next check at {}").c_str(), format_time(this->scheduled_request));
|
||||
}
|
||||
|
||||
void LicenseHelper::callback_certificate_update(const license::WebCertificate &certificate) {
|
||||
serverInstance->setWebCertRoot(certificate.key, certificate.certificate, certificate.revision);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <ThreadPool/Mutex.h>
|
||||
#include "../../../license/shared/License.h"
|
||||
#include "../../../license/shared/LicenseRequest.h"
|
||||
|
||||
namespace license {
|
||||
class LicenseHelper {
|
||||
public:
|
||||
LicenseHelper();
|
||||
~LicenseHelper();
|
||||
|
||||
void tick();
|
||||
std::shared_ptr<license::LicenseInfo> getLicenseInfo() {
|
||||
std::lock_guard lock(this->request.info_lock);
|
||||
return this->request.info;
|
||||
}
|
||||
private:
|
||||
std::mutex license_tick_lock;
|
||||
|
||||
std::chrono::system_clock::time_point scheduled_request;
|
||||
std::chrono::system_clock::time_point last_request;
|
||||
std::chrono::system_clock::time_point last_successful_request;
|
||||
size_t request_fail_count = 0;
|
||||
|
||||
struct {
|
||||
std::shared_ptr<license::LicenceRequest> current = nullptr;
|
||||
std::mutex current_lock;
|
||||
|
||||
std::shared_ptr<license::LicenseInfo> info;
|
||||
std::mutex info_lock;
|
||||
|
||||
std::thread prepare_thread;
|
||||
} request;
|
||||
|
||||
void do_request(bool /* verbose */);
|
||||
void handle_request_failed(bool /* verbose */, const std::string& /* error */);
|
||||
void callback_certificate_update(const license::WebCertificate&);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,540 @@
|
||||
//
|
||||
// Created by WolverinDEV on 27/02/2020.
|
||||
//
|
||||
#include <netdb.h>
|
||||
#include <cassert>
|
||||
#include <misc/strobf.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <src/Configuration.h>
|
||||
#include <src/ShutdownHelper.h>
|
||||
#include <misc/base64.h>
|
||||
#include "../../../license/shared/LicenseServerClient.h"
|
||||
#include "../../../cmake-build-debug-wsl/license/LicenseRequest.pb.h"
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "../../../license/shared/License.h"
|
||||
|
||||
#define DO_LOCAL_REQUEST
|
||||
using namespace ts::server::license;
|
||||
|
||||
LicenseService::LicenseService() {
|
||||
this->dns.lock = std::make_shared<std::recursive_mutex>();
|
||||
}
|
||||
|
||||
LicenseService::~LicenseService() {
|
||||
{
|
||||
std::lock_guard lock{this->request_lock};
|
||||
this->abort_request(lock, "");
|
||||
}
|
||||
}
|
||||
|
||||
bool LicenseService::initialize(std::string &error) {
|
||||
//this->verbose_ = true;
|
||||
this->startup_timepoint_ = std::chrono::steady_clock::now();
|
||||
this->timings.next_request = std::chrono::system_clock::now() + std::chrono::seconds(rand() % 20);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseService::execute_request_sync(const std::chrono::milliseconds& timeout) {
|
||||
std::unique_lock slock{this->sync_request_lock};
|
||||
this->begin_request();
|
||||
|
||||
if(this->sync_request_cv.wait_for(slock, timeout) == std::cv_status::timeout)
|
||||
return false;
|
||||
|
||||
return this->timings.failed_count == 0;
|
||||
}
|
||||
|
||||
void LicenseService::shutdown() {
|
||||
std::lock_guard lock{this->request_lock};
|
||||
if(this->request_state_ == request_state::empty) return;
|
||||
|
||||
this->abort_request(lock, "shutdown");
|
||||
}
|
||||
|
||||
void LicenseService::begin_request() {
|
||||
std::lock_guard lock{this->request_lock};
|
||||
if(this->request_state_ != request_state::empty)
|
||||
this->abort_request(lock, "last request has been aborted");
|
||||
|
||||
if(this->verbose_)
|
||||
debugMessage(LOG_INSTANCE, strobf("Executing license request.").string());
|
||||
this->timings.last_request = std::chrono::system_clock::now();
|
||||
this->request_state_ = request_state::dns_lookup;
|
||||
this->execute_dns_request();
|
||||
}
|
||||
|
||||
void LicenseService::abort_request(std::lock_guard<std::recursive_timed_mutex> &, const std::string& reason) {
|
||||
if(this->request_state_ == request_state::dns_lookup) {
|
||||
this->abort_dns_request();
|
||||
return;
|
||||
} else if(this->current_client) {
|
||||
this->current_client->callback_connected = nullptr;
|
||||
this->current_client->callback_message = nullptr;
|
||||
this->current_client->callback_disconnected = nullptr;
|
||||
|
||||
if(!reason.empty()) {
|
||||
this->current_client->disconnect(reason, std::chrono::system_clock::now() + std::chrono::seconds{1});
|
||||
/* Lets not wait here because we might be within the event loop. */
|
||||
//if(!this->current_client->await_disconnect())
|
||||
// this->current_client->close_connection();
|
||||
} else {
|
||||
this->current_client->close_connection();
|
||||
}
|
||||
|
||||
this->current_client.release();
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseService::abort_dns_request() {
|
||||
std::unique_lock llock{*this->dns.lock};
|
||||
if(!this->dns.current_lookup) return;
|
||||
|
||||
this->dns.current_lookup->handle = nullptr;
|
||||
this->dns.current_lookup = nullptr;
|
||||
}
|
||||
|
||||
void LicenseService::execute_dns_request() {
|
||||
std::unique_lock llock{*this->dns.lock};
|
||||
assert(!this->dns.current_lookup);
|
||||
|
||||
auto lookup = new _dns::_lookup{};
|
||||
|
||||
lookup->lock = this->dns.lock;
|
||||
lookup->handle = this;
|
||||
lookup->thread = std::thread([lookup] {
|
||||
bool success{false};
|
||||
std::string error{};
|
||||
sockaddr_in server_addr{};
|
||||
|
||||
{
|
||||
server_addr.sin_family = AF_INET;
|
||||
#ifdef DO_LOCAL_REQUEST
|
||||
auto license_host = gethostbyname(strobf("localhost").c_str());
|
||||
#else
|
||||
auto license_host = gethostbyname(strobf("license.teaspeak.de").c_str());
|
||||
#endif
|
||||
if(!license_host) {
|
||||
error = strobf("result is null").string();
|
||||
goto handle_result;
|
||||
}
|
||||
if(!license_host->h_addr){
|
||||
error = strobf("missing h_addr in result").string();
|
||||
goto handle_result;
|
||||
}
|
||||
|
||||
server_addr.sin_addr.s_addr = ((in_addr*) license_host->h_addr)->s_addr;
|
||||
|
||||
#ifndef DO_LOCAL_REQUEST
|
||||
int first = server_addr.sin_addr.s_addr >> 24;
|
||||
if(first == 0 || first == 127 || first == 255) {
|
||||
error = strobf("local response address").string();
|
||||
goto handle_result;
|
||||
}
|
||||
#endif
|
||||
server_addr.sin_port = htons(27786);
|
||||
success = true;
|
||||
}
|
||||
|
||||
handle_result:
|
||||
{
|
||||
std::unique_lock llock{*lookup->lock};
|
||||
if(lookup->handle) {
|
||||
lookup->handle->dns.current_lookup = nullptr;
|
||||
|
||||
if(success) {
|
||||
debugMessage(LOG_INSTANCE, strobf("Successfully resolved the hostname to {}").string(), net::to_string(server_addr.sin_addr));
|
||||
lookup->handle->handle_dns_lookup_result(true, server_addr);
|
||||
} else {
|
||||
debugMessage(LOG_INSTANCE, strobf("Failed to resolve hostname for license server: {}").string(), error);
|
||||
lookup->handle->handle_dns_lookup_result(false, error);
|
||||
}
|
||||
}
|
||||
|
||||
assert(lookup->thread.get_id() == std::this_thread::get_id());
|
||||
if(lookup->thread.joinable())
|
||||
lookup->thread.detach();
|
||||
delete lookup;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this->dns.current_lookup = lookup;
|
||||
}
|
||||
|
||||
void LicenseService::handle_check_succeeded() {
|
||||
{
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
this->abort_request(rlock, strobf("request succeeded").string());
|
||||
this->schedule_next_request(true);
|
||||
this->request_state_ = request_state::empty;
|
||||
|
||||
if(config::license->isPremium()) {
|
||||
logMessage(LOG_INSTANCE, strobf("License has been validated.").string());
|
||||
} else {
|
||||
logMessage(LOG_INSTANCE, strobf("Instance integrity has been validated.").string());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock slock{this->sync_request_lock};
|
||||
this->sync_request_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseService::handle_check_fail(const std::string &error) {
|
||||
{
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
this->abort_request(rlock, "request failed");
|
||||
|
||||
if(config::license->isPremium()) {
|
||||
logCritical(LOG_INSTANCE, strobf("Failed to validate license:").string());
|
||||
logCritical(LOG_INSTANCE, error);
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping server!").string());
|
||||
ts::server::shutdownInstance();
|
||||
} else {
|
||||
logError(LOG_INSTANCE, strobf("Failed to validate instance integrity:").string());
|
||||
logError(LOG_INSTANCE, error);
|
||||
}
|
||||
|
||||
this->schedule_next_request(false);
|
||||
this->request_state_ = request_state::empty;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock slock{this->sync_request_lock};
|
||||
this->sync_request_cv.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseService::handle_dns_lookup_result(bool success, const std::variant<std::string, sockaddr_in> &result) {
|
||||
if(!success) {
|
||||
this->handle_check_fail(std::get<std::string>(result));
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::dns_lookup) {
|
||||
logError(LOG_INSTANCE, strobf("Request state isn't dns lookup anymore. Aborting dns lookup result callback.").string());
|
||||
return;
|
||||
}
|
||||
this->request_state_ = request_state::connecting;
|
||||
|
||||
assert(!this->current_client);
|
||||
this->current_client = std::make_unique<::license::client::LicenseServerClient>(std::get<sockaddr_in>(result), 3);
|
||||
this->current_client->callback_connected = [&]{ this->handle_client_connected(); };
|
||||
this->current_client->callback_disconnected = [&](bool expected, const std::string& error) {
|
||||
this->handle_client_disconnected(error);
|
||||
};
|
||||
this->current_client->callback_message = [&](auto a, auto b, auto c) {
|
||||
this->handle_message(a, b, c);
|
||||
};
|
||||
|
||||
std::string error{};
|
||||
if(!this->current_client->start_connection(error))
|
||||
this->handle_check_fail(strobf("connect failed: ").string() + error);
|
||||
}
|
||||
|
||||
void LicenseService::client_send_message(::license::protocol::PacketType type, ::google::protobuf::Message &message) {
|
||||
auto buffer = message.SerializeAsString();
|
||||
|
||||
assert(this->current_client);
|
||||
this->current_client->send_message(type, buffer.data(), buffer.length());
|
||||
}
|
||||
|
||||
void LicenseService::handle_client_connected() {
|
||||
{
|
||||
if(this->verbose_)
|
||||
debugMessage(LOG_INSTANCE, strobf("License client connected").string());
|
||||
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::connecting) {
|
||||
logError(LOG_INSTANCE, strobf("Request state isn't connecting anymore. Aborting client connect callback.").string());
|
||||
return;
|
||||
}
|
||||
|
||||
this->request_state_ = request_state::license_validate;
|
||||
}
|
||||
|
||||
this->send_license_validate_request();
|
||||
}
|
||||
|
||||
void LicenseService::handle_message(::license::protocol::PacketType type, const void *buffer, size_t size) {
|
||||
switch (type) {
|
||||
case ::license::protocol::PACKET_SERVER_VALIDATION_RESPONSE:
|
||||
this->handle_message_license_info(buffer, size);
|
||||
return;
|
||||
|
||||
case ::license::protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT:
|
||||
this->handle_message_property_adjustment(buffer, size);
|
||||
return;
|
||||
|
||||
case ::license::protocol::PACKET_SERVER_LICENSE_UPGRADE_RESPONSE:
|
||||
this->handle_message_license_update(buffer, size);
|
||||
return;
|
||||
|
||||
default:
|
||||
this->handle_check_fail(strobf("received unknown packet").string());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseService::handle_client_disconnected(const std::string& message) {
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::finishing) {
|
||||
this->handle_check_fail(strobf("unexpected disconnect: ").string() + message);
|
||||
return;
|
||||
}
|
||||
|
||||
this->abort_request(rlock, "");
|
||||
}
|
||||
|
||||
void LicenseService::send_license_validate_request() {
|
||||
this->license_request_data = serverInstance->generateLicenseData();
|
||||
|
||||
ts::proto::license::ServerValidation request{};
|
||||
if(this->license_request_data->license) {
|
||||
request.set_licensed(true);
|
||||
request.set_license_info(true);
|
||||
request.set_license(exportLocalLicense(this->license_request_data->license));
|
||||
} else {
|
||||
request.set_licensed(false);
|
||||
request.set_license_info(false);
|
||||
}
|
||||
request.mutable_info()->set_uname(this->license_request_data->info.uname);
|
||||
request.mutable_info()->set_version(this->license_request_data->info.version);
|
||||
request.mutable_info()->set_timestamp(this->license_request_data->info.timestamp.count());
|
||||
request.mutable_info()->set_unique_id(this->license_request_data->info.unique_id);
|
||||
|
||||
this->client_send_message(::license::protocol::PACKET_CLIENT_SERVER_VALIDATION, request);
|
||||
}
|
||||
|
||||
void LicenseService::handle_message_license_info(const void *buffer, size_t buffer_length) {
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::license_validate) {
|
||||
this->handle_check_fail(strobf("finvalid request state for license response packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
ts::proto::license::LicenseResponse response{};
|
||||
if(!response.ParseFromArray(buffer, buffer_length)) {
|
||||
this->handle_check_fail(strobf("failed to parse license response packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
if(response.has_blacklist()) {
|
||||
auto blacklist_state = response.blacklist().state();
|
||||
if(blacklist_state == ::ts::proto::license::BLACKLISTED) {
|
||||
this->abort_request(rlock, strobf("blacklist action").string());
|
||||
|
||||
logCritical(LOG_INSTANCE, strobf("This TeaSpeak-Server instance has been blacklisted by TeaSpeak.").string());
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping server!").string());
|
||||
ts::server::shutdownInstance();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!response.valid()) {
|
||||
std::string reason{};
|
||||
if(response.has_invalid_reason())
|
||||
reason = response.invalid_reason();
|
||||
else
|
||||
reason = strobf("no reason given").string();
|
||||
|
||||
license_invalid_reason = reason;
|
||||
} else {
|
||||
license_invalid_reason.reset();
|
||||
}
|
||||
|
||||
if(response.has_update_pending() && response.update_pending()) {
|
||||
if(this->send_license_update_request()) {
|
||||
this->request_state_ = request_state::license_upgrade;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(this->license_invalid_reason.has_value()) {
|
||||
this->handle_check_fail(strobf("Failed to verify license (").string() + *this->license_invalid_reason + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
this->send_property_update_request();
|
||||
this->request_state_ = request_state::property_update;
|
||||
}
|
||||
|
||||
void LicenseService::send_property_update_request() {
|
||||
auto data = this->license_request_data;
|
||||
if(!data) {
|
||||
this->handle_check_fail(strobf("missing property data").string());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ts::proto::license::PropertyUpdateRequest infos{};
|
||||
infos.set_speach_total(this->license_request_data->metrics.speech_total);
|
||||
infos.set_speach_dead(this->license_request_data->metrics.speech_dead);
|
||||
infos.set_speach_online(this->license_request_data->metrics.speech_online);
|
||||
infos.set_speach_varianz(this->license_request_data->metrics.speech_varianz);
|
||||
|
||||
infos.set_clients_online(this->license_request_data->metrics.client_online);
|
||||
infos.set_bots_online(this->license_request_data->metrics.bots_online);
|
||||
infos.set_queries_online(this->license_request_data->metrics.queries_online);
|
||||
infos.set_servers_online(this->license_request_data->metrics.servers_online);
|
||||
infos.set_web_clients_online(this->license_request_data->metrics.web_clients_online);
|
||||
|
||||
infos.set_web_cert_revision(this->license_request_data->web_certificate_revision);
|
||||
|
||||
this->client_send_message(::license::protocol::PACKET_CLIENT_PROPERTY_ADJUSTMENT, infos);
|
||||
}
|
||||
|
||||
void LicenseService::handle_message_property_adjustment(const void *buffer, size_t buffer_length) {
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::property_update) {
|
||||
this->handle_check_fail(strobf("invalid request state for property update packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
ts::proto::license::PropertyUpdateResponse response{};
|
||||
if(!response.ParseFromArray(buffer, buffer_length)) {
|
||||
this->handle_check_fail(strobf("failed to parse property update packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
if(response.has_web_certificate()) {
|
||||
auto& certificate = response.web_certificate();
|
||||
serverInstance->setWebCertRoot(certificate.key(), certificate.certificate(), certificate.revision());
|
||||
}
|
||||
|
||||
if(response.has_reset_speach())
|
||||
serverInstance->resetSpeechTime();
|
||||
serverInstance->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ] = response.speach_varianz_corrector();
|
||||
|
||||
this->request_state_ = request_state::finishing;
|
||||
this->handle_check_succeeded();
|
||||
}
|
||||
|
||||
bool LicenseService::send_license_update_request() {
|
||||
ts::proto::license::RequestLicenseUpgrade request{};
|
||||
this->client_send_message(::license::protocol::PACKET_CLIENT_LICENSE_UPGRADE, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::string format_time(const std::chrono::system_clock::time_point& time);
|
||||
void LicenseService::handle_message_license_update(const void *buffer, size_t buffer_length) {
|
||||
std::lock_guard rlock{this->request_lock};
|
||||
if(this->request_state_ != request_state::license_upgrade) {
|
||||
this->handle_check_fail(strobf("invalid request state for license upgrade packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
ts::proto::license::LicenseUpgradeResponse response{};
|
||||
if(!response.ParseFromArray(buffer, buffer_length)) {
|
||||
this->handle_check_fail(strobf("failed to parse license upgrade packet").string());
|
||||
return;
|
||||
}
|
||||
|
||||
if(!response.valid()) {
|
||||
logError(LOG_INSTANCE, strobf("Failed to upgrade license: {}").string(), response.error_message());
|
||||
goto error_exit;
|
||||
} else {
|
||||
std::string error{};
|
||||
auto license_data = response.license_key();
|
||||
auto license = ::license::readLocalLicence(license_data, error);
|
||||
if(!license) {
|
||||
logError(LOG_INSTANCE, strobf("Failed to parse received upgraded license key: {}").string(), error);
|
||||
goto error_exit;
|
||||
}
|
||||
if(!license->isValid()) {
|
||||
logError(LOG_INSTANCE, strobf("Received license seems to be invalid.").string());
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
auto end = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{license->data.endTimestamp};
|
||||
logMessage(LOG_INSTANCE, strobf("Received new license registered to {}, valid until {}").string(), license->data.licenceOwner, format_time(end));
|
||||
if(!config::update_license(error, license_data))
|
||||
logError(LOG_INSTANCE, strobf("Failed to write new license key to config file: {}").string(), error);
|
||||
|
||||
config::license = license;
|
||||
|
||||
this->send_license_validate_request();
|
||||
this->request_state_ = request_state::license_validate;
|
||||
}
|
||||
|
||||
return;
|
||||
error_exit:
|
||||
logError(LOG_INSTANCE, strobf("License upgrade failed. Using old key.").string());
|
||||
if(this->license_invalid_reason.has_value()) {
|
||||
this->handle_check_fail(strobf("Failed to verify license (").string() + *this->license_invalid_reason + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
this->send_property_update_request();
|
||||
this->request_state_ = request_state::property_update;
|
||||
}
|
||||
|
||||
/* request scheduler */
|
||||
inline std::string format_time(const std::chrono::system_clock::time_point& time) {
|
||||
std::time_t now = std::chrono::system_clock::to_time_t(time);
|
||||
std::tm * ptm = std::localtime(&now);
|
||||
char buffer[128];
|
||||
const auto length = std::strftime(buffer, 128, "%a, %d.%m.%Y %H:%M:%S", ptm);
|
||||
return std::string{buffer, length};
|
||||
}
|
||||
|
||||
void LicenseService::schedule_next_request(bool request_success) {
|
||||
auto& fail_count = this->timings.failed_count;
|
||||
if(request_success)
|
||||
fail_count = 0;
|
||||
else
|
||||
fail_count++;
|
||||
|
||||
std::chrono::milliseconds next_request;
|
||||
if(fail_count == 0)
|
||||
next_request = std::chrono::hours{2};
|
||||
if(fail_count <= 1)
|
||||
next_request = std::chrono::minutes(1);
|
||||
else if(fail_count <= 5)
|
||||
next_request = std::chrono::minutes(5);
|
||||
else if(fail_count <= 10)
|
||||
next_request = std::chrono::minutes(10);
|
||||
else
|
||||
next_request = std::chrono::minutes(30);
|
||||
#ifdef DO_LOCAL_REQUEST
|
||||
next_request = std::chrono::seconds(30);
|
||||
#endif
|
||||
|
||||
this->timings.next_request = this->timings.last_request + next_request;
|
||||
if(this->verbose_)
|
||||
logMessage(LOG_INSTANCE, strobf("Scheduling next check at {}").c_str(), format_time(this->timings.next_request));
|
||||
}
|
||||
|
||||
void LicenseService::execute_tick() {
|
||||
std::unique_lock rlock{this->request_lock, std::try_to_lock}; /* It will be slightly blocking when its within the message hendeling */
|
||||
if(!rlock) return;
|
||||
|
||||
/* do it not above because if we might have a deadlock here we don't want to punish the user */
|
||||
if(this->timings.last_succeeded.time_since_epoch().count() == 0) {
|
||||
auto difference = config::license->isPremium() ? std::chrono::hours{24 * 4} : std::chrono::hours{24 * 7};
|
||||
if(std::chrono::steady_clock::now() - difference > this->startup_timepoint_) {
|
||||
this->startup_timepoint_ = std::chrono::steady_clock::now(); /* shut down only once */
|
||||
|
||||
if(config::license->isPremium())
|
||||
logCritical(LOG_INSTANCE, strobf("Failed to validate license within 4 days.").string());
|
||||
else
|
||||
logCritical(LOG_INSTANCE, strobf("Failed to validate instance integrity within 7 days.").string());
|
||||
logCritical(LOG_INSTANCE, strobf("Stopping server!").string());
|
||||
ts::server::shutdownInstance();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(this->request_state_ != request_state::empty) {
|
||||
if(this->timings.last_request + std::chrono::minutes{5} < now) {
|
||||
this->handle_check_fail(strobf("Scheduling next check at {}").string());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(std::chrono::system_clock::now() > this->timings.next_request)
|
||||
this->begin_request();
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
namespace license::client {
|
||||
class LicenseServerClient;
|
||||
}
|
||||
|
||||
namespace google::protobuf {
|
||||
class Message;
|
||||
}
|
||||
|
||||
namespace ts::server::license {
|
||||
struct InstanceLicenseInfo {
|
||||
std::shared_ptr<::license::License> license{nullptr};
|
||||
std::string web_certificate_revision{};
|
||||
|
||||
struct metrics_ {
|
||||
size_t servers_online{0};
|
||||
size_t client_online{0};
|
||||
size_t web_clients_online{0};
|
||||
size_t bots_online{0};
|
||||
size_t queries_online{0};
|
||||
|
||||
size_t speech_total{0};
|
||||
size_t speech_varianz{0};
|
||||
size_t speech_online{0};
|
||||
size_t speech_dead{0};
|
||||
} metrics;
|
||||
|
||||
struct info_ {
|
||||
std::chrono::milliseconds timestamp{};
|
||||
std::string version{};
|
||||
std::string uname{};
|
||||
std::string unique_id{};
|
||||
} info;
|
||||
};
|
||||
|
||||
|
||||
class LicenseService {
|
||||
public:
|
||||
LicenseService();
|
||||
~LicenseService();
|
||||
|
||||
[[nodiscard]] bool initialize(std::string& /* error */);
|
||||
void shutdown();
|
||||
|
||||
/* whatever it failed/succeeded */
|
||||
bool execute_request_sync(const std::chrono::milliseconds& /* timeout */);
|
||||
|
||||
[[nodiscard]] inline bool verbose() const { return this->verbose_; }
|
||||
void execute_tick(); /* should not be essential to the core functionality! */
|
||||
private:
|
||||
std::chrono::steady_clock::time_point startup_timepoint_;
|
||||
|
||||
enum struct request_state {
|
||||
empty,
|
||||
|
||||
/* initializing */
|
||||
dns_lookup,
|
||||
connecting,
|
||||
|
||||
/* connected states */
|
||||
license_validate,
|
||||
license_upgrade,
|
||||
property_update,
|
||||
|
||||
/* disconnecting */
|
||||
finishing
|
||||
};
|
||||
bool verbose_{false};
|
||||
|
||||
std::recursive_timed_mutex request_lock{};
|
||||
request_state request_state_{request_state::empty};
|
||||
std::unique_ptr<::license::client::LicenseServerClient> current_client{nullptr};
|
||||
std::shared_ptr<InstanceLicenseInfo> license_request_data{nullptr};
|
||||
|
||||
std::condition_variable sync_request_cv;
|
||||
std::mutex sync_request_lock;
|
||||
|
||||
struct _timings {
|
||||
std::chrono::system_clock::time_point last_request{};
|
||||
std::chrono::system_clock::time_point next_request{};
|
||||
|
||||
std::chrono::system_clock::time_point last_succeeded{};
|
||||
size_t failed_count{0};
|
||||
} timings;
|
||||
|
||||
struct _dns {
|
||||
std::shared_ptr<std::recursive_mutex> lock{nullptr};
|
||||
|
||||
struct _lookup {
|
||||
std::shared_ptr<std::recursive_mutex> lock{nullptr};
|
||||
std::thread thread{};
|
||||
|
||||
LicenseService* handle{nullptr}; /* may be null, locked via lock */
|
||||
}* current_lookup{nullptr};
|
||||
} dns;
|
||||
|
||||
std::optional<std::string> license_invalid_reason{}; /* set if the last license is invalid */
|
||||
|
||||
void schedule_next_request(bool /* last request succeeded */);
|
||||
|
||||
void begin_request();
|
||||
void client_send_message(::license::protocol::PacketType /* type */, ::google::protobuf::Message& /* message */);
|
||||
void handle_check_fail(const std::string& /* error */); /* might be called form the DNS loop */
|
||||
void handle_check_succeeded();
|
||||
|
||||
/* if not disconnect message has been set it will just close the connection */
|
||||
void abort_request(std::lock_guard<std::recursive_timed_mutex>& /* request lock */, const std::string& /* disconnect message */);
|
||||
|
||||
void abort_dns_request();
|
||||
void execute_dns_request();
|
||||
|
||||
/* will be called while dns lock has been locked! */
|
||||
void handle_dns_lookup_result(bool /* success */, const std::variant<std::string, sockaddr_in>& /* data */);
|
||||
|
||||
/* all callbacks bellow are called from the current_client. It will not be null while being within the callback. */
|
||||
void handle_client_connected();
|
||||
void handle_client_disconnected(const std::string& /* error */);
|
||||
|
||||
void handle_message(::license::protocol::PacketType /* type */, const void* /* buffer */, size_t /* length */);
|
||||
void handle_message_license_info(const void* /* buffer */, size_t /* length */);
|
||||
void handle_message_license_update(const void* /* buffer */, size_t /* length */);
|
||||
void handle_message_property_adjustment(const void* /* buffer */, size_t /* length */);
|
||||
|
||||
void send_license_validate_request();
|
||||
bool send_license_update_request();
|
||||
void send_property_update_request();
|
||||
};
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
#include "./CommandHandler.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <src/SignalHandler.h>
|
||||
#include <src/VirtualServer.h>
|
||||
#include <src/client/ConnectedClient.h>
|
||||
#include <src/VirtualServerManager.h>
|
||||
#include <src/InstanceHandler.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <src/ShutdownHelper.h>
|
||||
#include <misc/time.h>
|
||||
#include <misc/memtracker.h>
|
||||
#include <sql/sqlite/SqliteSQL.h>
|
||||
#include <sys/resource.h>
|
||||
#include "CommandHandler.h"
|
||||
#include "src/server/QueryServer.h"
|
||||
#include <protocol/buffers.h>
|
||||
|
||||
#include "../SignalHandler.h"
|
||||
#include "../client/ConnectedClient.h"
|
||||
#include "../InstanceHandler.h"
|
||||
#include "../VirtualServerManager.h"
|
||||
#include "../VirtualServer.h"
|
||||
#include "../ShutdownHelper.h"
|
||||
#include "../server/QueryServer.h"
|
||||
|
||||
#ifdef HAVE_JEMALLOC
|
||||
#include <jemalloc/jemalloc.h>
|
||||
|
||||
Reference in New Issue
Block a user