669 lines
27 KiB
C++
669 lines
27 KiB
C++
#define XMALLOC undefined_malloc /* fix jemalloc and tomcrypt */
|
|
#define XCALLOC undefined_calloc
|
|
#define XFREE undefined_free
|
|
#define XREALLOC undefined_realloc
|
|
|
|
#include <netdb.h>
|
|
#include "../../license/shared/LicenseRequest.h"
|
|
#include "src/weblist/WebListManager.h"
|
|
#include <log/LogUtils.h>
|
|
#include "InstanceHandler.h"
|
|
#include "src/client/InternalClient.h"
|
|
#include "src/server/QueryServer.h"
|
|
#include "src/server/file/FileServer.h"
|
|
#include "SignalHandler.h"
|
|
#include "src/manager/PermissionNameMapper.h"
|
|
#include <ThreadPool/Timer.h>
|
|
#include "ShutdownHelper.h"
|
|
#include <sys/utsname.h>
|
|
#include "build.h"
|
|
#include <misc/digest.h>
|
|
#include <misc/base64.h>
|
|
#include <misc/rnd.h>
|
|
#include <jemalloc/jemalloc.h>
|
|
|
|
#ifndef _POSIX_SOURCE
|
|
#define _POSIX_SOURCE
|
|
#endif
|
|
#include <unistd.h>
|
|
#undef _POSIX_SOURCE
|
|
#include <stdio.h>
|
|
|
|
using namespace std;
|
|
using namespace std::chrono;
|
|
using namespace ts;
|
|
using namespace ts::server;
|
|
|
|
#define INSTANCE_TICK_NAME "instance"
|
|
|
|
#define _STRINGIFY(x) #x
|
|
#define STRINGIFY(x) _STRINGIFY(x)
|
|
|
|
extern bool mainThreadActive;
|
|
extern InstanceHandler* serverInstance;
|
|
InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
|
serverInstance = this;
|
|
this->tick_manager = make_shared<threads::Scheduler>(config::threads::ticking, "tick task ");
|
|
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr, true);
|
|
this->statistics->measure_bandwidths(true);
|
|
|
|
this->licenseHelper = make_shared<license::LicenseHelper>();
|
|
this->dbHelper = new DatabaseHelper(this->getSql());
|
|
|
|
this->_properties = new Properties();
|
|
this->_properties->register_property_type<property::InstanceProperties>();
|
|
this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT] = ts::config::binding::DefaultFilePort;
|
|
this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST] = ts::config::binding::DefaultFileHost;
|
|
this->properties()[property::SERVERINSTANCE_QUERY_PORT] = ts::config::binding::DefaultQueryPort;
|
|
this->properties()[property::SERVERINSTANCE_QUERY_HOST] = ts::config::binding::DefaultQueryHost;
|
|
|
|
|
|
auto result = sql::command(this->getSql(), "SELECT * FROM `properties` WHERE `id` = :id AND `type` = :type AND `serverId` = :serverId", variable{":id", 0}, variable{":serverId", 0}, variable{":type", property::PropertyType::PROP_TYPE_INSTANCE})
|
|
.query([](InstanceHandler *instance, int length, char **values, char **columns) {
|
|
string key, value;
|
|
for (int index = 0; index < length; index++) {
|
|
if (strcmp(columns[index], "key") == 0) {
|
|
key = values[index];
|
|
} else if (strcmp(columns[index], "value") == 0) {
|
|
value = values[index] == nullptr ? "" : values[index];
|
|
}
|
|
}
|
|
|
|
const auto &info = property::impl::info<property::InstanceProperties>(key);
|
|
if(*info == property::SERVERINSTANCE_UNDEFINED) {
|
|
logError(0, "Got an unknown instance property " + key);
|
|
return 0;
|
|
}
|
|
auto prop = instance->properties()[info];
|
|
prop = value;
|
|
prop.setDbReference(true);
|
|
prop.setModified(false);
|
|
return 0;
|
|
}, this);
|
|
if (!result) cerr << result << endl;
|
|
this->_properties->registerNotifyHandler([&](Property &prop) {
|
|
if ((prop.type().flags & property::FLAG_SAVE) == 0) {
|
|
prop.setModified(false);
|
|
return;
|
|
}
|
|
|
|
string sqlQuery;
|
|
if (prop.hasDbReference())
|
|
sqlQuery = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key";
|
|
else {
|
|
prop.setDbReference(true);
|
|
sqlQuery = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
|
|
}
|
|
|
|
sql::command(this->getSql(), sqlQuery, variable{":sid", 0}, variable{":type", property::PropertyType::PROP_TYPE_INSTANCE}, variable{":id", 0}, variable{":key", prop.type().name}, variable{":value", prop.value()})
|
|
.executeLater().waitAndGetLater(LOG_SQL_CMD, sql::result{1, "future failed"});
|
|
});
|
|
this->properties()[property::SERVERINSTANCE_DATABASE_VERSION] = this->sql->getVersion();
|
|
|
|
|
|
globalServerAdmin = std::make_shared<ts::server::InternalClient>(this->getSql(), nullptr, "serveradmin", true);
|
|
static_pointer_cast<ts::server::InternalClient>(globalServerAdmin)->setSharedLock(globalServerAdmin);
|
|
ts::server::DatabaseHelper::assignDatabaseId(this->getSql(), 0, globalServerAdmin);
|
|
|
|
this->_musicRoot = std::make_shared<InternalClient>(this->getSql(), nullptr, "Music Manager", false);
|
|
static_pointer_cast<InternalClient>(this->_musicRoot)->setSharedLock(this->_musicRoot);
|
|
|
|
{
|
|
this->groupManager = std::make_shared<GroupManager>(nullptr, this->getSql());
|
|
this->groupManager->loadGroupFormDatabase();
|
|
|
|
if (this->groupManager->availableServerGroups(false).empty()) {
|
|
if(!this->setupDefaultGroups()){
|
|
logCritical(LOG_INSTANCE, "Could not setup server instance! Stopping...");
|
|
mainThreadActive = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
debugMessage(LOG_INSTANCE, "Instance admin group id " + to_string(this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP].as<GroupId>()));
|
|
auto instance_server_admin = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP].as<GroupId>());
|
|
if (!instance_server_admin) {
|
|
instance_server_admin = this->groupManager->availableServerGroups(false).front();
|
|
logCritical(LOG_INSTANCE, "Missing instance server admin group! Using first available (" + (instance_server_admin ? instance_server_admin->name() : "nil") + ")");
|
|
}
|
|
if(this->groupManager->listGroupAssignments(this->globalServerAdmin->getClientDatabaseId()).empty())
|
|
this->groupManager->addServerGroup(this->globalServerAdmin->getClientDatabaseId(), instance_server_admin);
|
|
|
|
debugMessage(LOG_INSTANCE, "Server guest group id " + to_string(this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as<GroupId>()));
|
|
auto instance_server_guest = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save<GroupId>());
|
|
if (!instance_server_guest) {
|
|
instance_server_guest = this->groupManager->availableServerGroups(false).front();
|
|
logCritical(LOG_INSTANCE, "Missing instance server guest group! Using first available (" + (instance_server_guest ? instance_server_guest->name() : "nil") + ")");
|
|
}
|
|
this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] = instance_server_guest->groupId();
|
|
|
|
debugMessage(LOG_INSTANCE, "Server music group id " + to_string(this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as<GroupId>()));
|
|
auto instance_server_music = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_save<GroupId>());
|
|
if (!instance_server_music) {
|
|
instance_server_music = instance_server_guest;
|
|
logCritical(LOG_INSTANCE, "Missing instance server music group! Using serverguest (" + (instance_server_music ? instance_server_music->name() : "nil") + ")");
|
|
}
|
|
this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP] = instance_server_music->groupId();
|
|
}
|
|
|
|
{
|
|
this->default_tree = make_shared<ServerChannelTree>(nullptr, this->getSql());
|
|
this->default_tree->loadChannelsFromDatabase();
|
|
|
|
this->default_tree->deleteSemiPermanentChannels();
|
|
if(this->default_tree->channel_count() == 0){
|
|
logMessage(LOG_GENERAL, "Generating default tree");
|
|
|
|
std::shared_ptr<BasicChannel> ch;
|
|
ch = this->default_tree->createChannel(0, 0, "[cspacer01]┏╋━━━━━━◥◣◆◢◤━━━━━━╋┓");
|
|
ch = this->default_tree->createChannel(0, ch->channelId(), "[cspacer02] TeaSpeak Server");
|
|
ch = this->default_tree->createChannel(0, ch->channelId(), "[cspacer03]┗╋━━━━━━◥◣◆◢◤━━━━━━╋┛");
|
|
ch = this->default_tree->createChannel(0, ch->channelId(), "[cspacer04]Default Channel");
|
|
this->default_tree->setDefaultChannel(ch);
|
|
|
|
this->properties()[property::SERVERINSTANCE_UNIQUE_ID] = ""; /* we def got a new instance */
|
|
}
|
|
if(!this->default_tree->getDefaultChannel()) this->default_tree->setDefaultChannel(this->default_tree->findChannel("[cspacer04]Default Channel", nullptr));
|
|
if(!this->default_tree->getDefaultChannel()) this->default_tree->setDefaultChannel(*this->default_tree->channels().begin());
|
|
assert(this->default_tree->getDefaultChannel());
|
|
}
|
|
|
|
{
|
|
this->default_server_properties = serverInstance->databaseHelper()->loadServerProperties(nullptr);
|
|
}
|
|
|
|
|
|
if(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as<int64_t>() == 0) {
|
|
debugMessage(LOG_INSTANCE, "Setting up monthly reset timestamp!");
|
|
this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
this->banMgr = new BanManager(this->getSql());
|
|
this->banMgr->loadBans();
|
|
|
|
this->web_list = make_shared<weblist::WebListManager>();
|
|
}
|
|
|
|
void InstanceHandler::executeTick(TSServer* server) {
|
|
auto str = "server_" + to_string(server->getServerId());
|
|
if(!this->tick_manager->schedule(str, std::bind(&TSServer::executeServerTick, server), milliseconds(500))) {
|
|
logCritical(LOG_INSTANCE, "Could not schedule server ticking task!");
|
|
}
|
|
}
|
|
|
|
void InstanceHandler::cancelExecute(TSServer* server) {
|
|
auto str = "server_" + to_string(server->getServerId());
|
|
if(!this->tick_manager->cancelTask(str)){
|
|
logError(LOG_INSTANCE, "Could not stop server tick task!");
|
|
}
|
|
}
|
|
|
|
|
|
InstanceHandler::~InstanceHandler() {
|
|
delete this->_properties;
|
|
delete this->banMgr;
|
|
delete this->dbHelper;
|
|
|
|
groupManager = nullptr;
|
|
globalServerAdmin = nullptr;
|
|
_musicRoot = nullptr;
|
|
|
|
licenseHelper = nullptr;
|
|
statistics = nullptr;
|
|
tick_manager = nullptr;
|
|
}
|
|
|
|
inline sockaddr_in* resolveAddress(const string& host, uint16_t port) {
|
|
hostent* record = gethostbyname(host.c_str());
|
|
if (!record) {
|
|
cerr << "Cant resolve bind host! (" << host << ")" << endl;
|
|
return nullptr;
|
|
}
|
|
auto addr = new sockaddr_in{};
|
|
addr->sin_addr.s_addr = ((in_addr *) record->h_addr)->s_addr;
|
|
addr->sin_family = AF_INET;
|
|
addr->sin_port = htons(port);
|
|
return addr;
|
|
}
|
|
|
|
bool InstanceHandler::startInstance() {
|
|
if (this->active) return false;
|
|
active = true;
|
|
string errorMessage;
|
|
|
|
this->web_list->enabled = ts::config::server::enable_teamspeak_weblist;
|
|
|
|
this->permission_mapper = make_shared<permission::PermissionNameMapper>();
|
|
if(!this->permission_mapper->initialize(config::permission_mapping_file, errorMessage)) {
|
|
logCritical(LOG_INSTANCE, "Failed to initialize permission name mapping from file {}: {}", config::permission_mapping_file, errorMessage);
|
|
return false;
|
|
}
|
|
this->sslMgr = new ssl::SSLManager();
|
|
if(!this->sslMgr->initialize()) {
|
|
logCritical("Failed to initialize ssl manager.");
|
|
return false;
|
|
}
|
|
|
|
//Startup file server
|
|
sockaddr_in *fAddr = resolveAddress(this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].as<string>(), this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as<uint16_t>());
|
|
if (!fAddr) {
|
|
logCritical(LOG_FT, "Could not resolve file server host");
|
|
return false;
|
|
}
|
|
logMessage(LOG_FT, "Starting server on {}:{}", inet_ntoa(fAddr->sin_addr), ntohs(fAddr->sin_port));
|
|
|
|
fileServer = new ts::server::FileServer();
|
|
if (!fileServer->start(*fAddr)) {
|
|
logCritical(LOG_FT, "Failed to start file server.");
|
|
delete fAddr;
|
|
return false;
|
|
}
|
|
delete fAddr;
|
|
|
|
if(config::query::sslMode > 0) {
|
|
string error;
|
|
auto result = this->sslMgr->initializeContext("query", config::query::ssl::keyFile, config::query::ssl::certFile, error, false, make_shared<ssl::SSLGenerator>(ssl::SSLGenerator{
|
|
.subjects = {},
|
|
.issues = {{"O", "TeaSpeak"}, {"OU", "Query server"}, {"creator", "WolverinDEV"}}
|
|
}));
|
|
if(!result) {
|
|
logCritical(LOG_QUERY, "Failed to initialize query certificate! (" + error + ")");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
queryServer = new ts::server::QueryServer(this->getSql());
|
|
{
|
|
auto server_query = queryServer->find_query_account_by_name("serveradmin");
|
|
if(!server_query) {
|
|
string queryPassword = rnd_string(12);
|
|
if((server_query = queryServer->create_query_account("serveradmin", 0, "serveradmin", queryPassword))) {
|
|
logMessageFmt(true, LOG_GENERAL, "------------------ [Server Query] ------------------");
|
|
logMessageFmt(true, LOG_GENERAL, " New Admin Server Query login credentials generated");
|
|
logMessageFmt(true, LOG_GENERAL, " Username: serveradmin");
|
|
logMessageFmt(true, LOG_GENERAL, " Password: " + queryPassword);
|
|
logMessageFmt(true, LOG_GENERAL, "------------------ [Server Query] ------------------");
|
|
} else {
|
|
logCriticalFmt(true,LOG_GENERAL,"Failed to create a new server admin query account!");
|
|
}
|
|
}
|
|
}
|
|
sockaddr_in *qAddr = resolveAddress(this->properties()[property::SERVERINSTANCE_QUERY_HOST].as<string>(), this->properties()[property::SERVERINSTANCE_QUERY_PORT].as<uint16_t>());
|
|
if (!qAddr) {
|
|
logCritical(LOG_QUERY, "Could not resolve query server host");
|
|
return false;
|
|
}
|
|
logMessage(LOG_QUERY, "Starting server on {}:{}", inet_ntoa(qAddr->sin_addr), ntohs(qAddr->sin_port));
|
|
if (!queryServer->start(*qAddr, errorMessage)) {
|
|
logCritical(LOG_QUERY, "Could not start Query server.\nMessage: " + errorMessage);
|
|
delete qAddr;
|
|
return false;
|
|
}
|
|
delete qAddr;
|
|
|
|
#ifdef COMPILE_WEB_CLIENT
|
|
if(config::web::activated) {
|
|
string error;
|
|
for(auto& certificate : config::web::ssl::certificates) {
|
|
auto result = this->sslMgr->initializeContext("web_" + get<0>(certificate), get<1>(certificate), get<2>(certificate), error, false, make_shared<ts::ssl::SSLGenerator>(ts::ssl::SSLGenerator{
|
|
.subjects = {},
|
|
.issues = {{"O", "TeaSpeak"}, {"OU", "Web server"}, {"creator", "WolverinDEV"}}
|
|
}));
|
|
if(!result) {
|
|
logError(LOG_INSTANCE, "Failed to initialize web certificate for servername {}! (Private key: {}, Certificate: {})", get<0>(certificate), get<1>(certificate), get<2>(certificate));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
auto rsa = this->sslMgr->initializeSSLKey("teaforo_sign", R"(
|
|
-----BEGIN PUBLIC KEY-----
|
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsfsTByPTE0aIqi6pJl4f
|
|
Xr4UqsIZkU5wYtktKIFpoDGHCHspCTMXF0fOXJkSGaTBtvTUEraRZz0+zshU+aiy
|
|
92qZ9DlC6Px3A94WW6mS48q2wEqZuj2q6Is4vf+DdjiqTzcZsqVJQj6WcqPg24pZ
|
|
cC9Yg9mys1IoBEoHmUXYVMFC5ibzRwjxfcAan0qSa+h983pL+4hva/+nHK1kaR2w
|
|
feTyUopv10ndkg9jxvAt5+roV3ID2fuHZBsEknWwFTTTjzPsf2Y+B6YYh4CW7haw
|
|
vf11A3V+xDFIrSbS9pix1jWgztrQbUcHDczQozArcyflE5+rUMuPPRp3IyRuSq/6
|
|
FwIDAQAB
|
|
-----END PUBLIC KEY-----
|
|
)", error, true);
|
|
if(!rsa) { //TODO just disable the forum verification
|
|
logCritical("Failed to initialize WebClient TeaForum key! (" + error + ")");
|
|
return false;
|
|
}
|
|
this->web_event_loop = make_shared<webio::LoopManager>();
|
|
}
|
|
#endif
|
|
|
|
if(config::experimental_31) {
|
|
this->teamspeak_license.reset(new TeamSpeakLicense("protocol_key.txt"));
|
|
if(!this->teamspeak_license->load(errorMessage)) {
|
|
logCritical(LOG_INSTANCE, "§cFailed to load the protocol key chain! ({})", errorMessage);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
this->voiceServerManager = new ServerManager(this);
|
|
if (!this->voiceServerManager->initialize(true)) {
|
|
logCritical(LOG_INSTANCE, "Could not load servers!");
|
|
delete this->voiceServerManager;
|
|
this->voiceServerManager = nullptr;
|
|
return false;
|
|
}
|
|
|
|
if (voiceServerManager->serverInstances().empty()) {
|
|
logMessage(LOG_INSTANCE, "§aCreating new TeaSpeak server...");
|
|
auto server = voiceServerManager->createServer(config::binding::DefaultVoiceHost, config::voice::default_voice_port);
|
|
if (!server)
|
|
logCritical(LOG_INSTANCE, "§cCould not create a new server!");
|
|
else {
|
|
string error;
|
|
if (!server->start(error)) {
|
|
logCritical(LOG_INSTANCE, "Could not start new server. Message: \n" + error);
|
|
}
|
|
}
|
|
}
|
|
|
|
startTimestamp = system_clock::now();
|
|
this->voiceServerManager->executeAutostart();
|
|
|
|
this->scheduler()->schedule(INSTANCE_TICK_NAME, bind(&InstanceHandler::tickInstance, this), milliseconds(100));
|
|
return true;
|
|
}
|
|
|
|
void InstanceHandler::stopInstance() {
|
|
{
|
|
lock_guard<mutex> lock(this->activeLock);
|
|
if(!this->active) return;
|
|
this->active = false;
|
|
this->activeCon.notify_all();
|
|
}
|
|
this->web_list->enabled = false;
|
|
|
|
threads::MutexLock lock_tick(this->lock_tick);
|
|
this->scheduler()->cancelTask(INSTANCE_TICK_NAME);
|
|
|
|
this->save_channel_permissions();
|
|
this->save_group_permissions();
|
|
|
|
debugMessage(LOG_INSTANCE, "Stopping all virtual servers");
|
|
if (this->voiceServerManager)
|
|
this->voiceServerManager->shutdownAll(ts::config::messages::applicationStopped);
|
|
delete this->voiceServerManager;
|
|
this->voiceServerManager = nullptr;
|
|
debugMessage(LOG_INSTANCE, "All virtual server stopped");
|
|
|
|
debugMessage(LOG_QUERY, "Stopping query server");
|
|
if (this->queryServer) this->queryServer->stop();
|
|
delete this->queryServer;
|
|
this->queryServer = nullptr;
|
|
debugMessage(LOG_QUERY, "Query server stopped");
|
|
|
|
debugMessage(LOG_FT, "Stopping file server");
|
|
if (this->fileServer) this->fileServer->stop();
|
|
delete this->fileServer;
|
|
this->fileServer = nullptr;
|
|
debugMessage(LOG_FT, "File server stopped");
|
|
|
|
delete this->sslMgr;
|
|
this->sslMgr = nullptr;
|
|
|
|
this->web_event_loop = nullptr;
|
|
}
|
|
|
|
void InstanceHandler::tickInstance() {
|
|
threads::MutexLock lock(this->lock_tick);
|
|
if(!this->active) return;
|
|
|
|
auto now = system_clock::now();
|
|
|
|
if(generalUpdateTimestamp + seconds(5) < now) {
|
|
generalUpdateTimestamp = now;
|
|
|
|
{
|
|
ALARM_TIMER(t, "InstanceHandler::tickInstance -> db helper tick", milliseconds(5));
|
|
this->dbHelper->tick();
|
|
}
|
|
{
|
|
ALARM_TIMER(t, "InstanceHandler::tickInstance -> license tick", milliseconds(5));
|
|
this->licenseHelper->tick();
|
|
}
|
|
}
|
|
{
|
|
ALARM_TIMER(t, "InstanceHandler::tickInstance -> flush", milliseconds(5));
|
|
//logger::flush();
|
|
}
|
|
if(statisticsUpdateTimestamp + seconds(5) < now) {
|
|
statisticsUpdateTimestamp = now;
|
|
{
|
|
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
|
|
this->statistics->tick();
|
|
}
|
|
|
|
{
|
|
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick [monthly]", milliseconds(2));
|
|
auto month_timestamp = system_clock::time_point() + seconds(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as<int64_t>());
|
|
auto time_t_old = system_clock::to_time_t(month_timestamp);
|
|
auto time_t_new = system_clock::to_time_t(system_clock::now());
|
|
|
|
tm tm_old{}, tm_new{};
|
|
gmtime_r(&time_t_old, &tm_old);
|
|
gmtime_r(&time_t_new, &tm_new);
|
|
|
|
if(tm_old.tm_mon != tm_new.tm_mon) {
|
|
logMessage(LOG_INSTANCE, "We entered a new month! Resetting monthly stats!");
|
|
if(!this->resetMonthlyStats()) logError(LOG_INSTANCE, "Monthly stats reset failed!");
|
|
else {
|
|
logMessage(LOG_INSTANCE, "Monthly stats reset done!");
|
|
this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(memcleanTimestamp + minutes(10) < now) {
|
|
memcleanTimestamp = now;
|
|
{
|
|
{
|
|
ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5));
|
|
auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS);
|
|
if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0)
|
|
logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal);
|
|
}
|
|
}
|
|
}
|
|
{
|
|
ALARM_TIMER(t, "InstanceHandler::tickInstance -> fileserver tick", milliseconds(5));
|
|
if(this->fileServer) this->fileServer->instanceTick();
|
|
}
|
|
{
|
|
ALARM_TIMER(t, "InstanceHandler::tickInstance -> sql_test tick", milliseconds(5));
|
|
if(this->sql && this->active) {
|
|
if(sqlTestTimestamp + seconds(10) < now) {
|
|
sqlTestTimestamp = now;
|
|
threads::Thread(THREAD_SAVE_OPERATIONS | THREAD_DETACHED, [&](){
|
|
auto command = this->sql->sql()->getType() == sql::TYPE_SQLITE ? "SELECT * FROM `sqlite_master`" : "SHOW TABLES";
|
|
auto result = sql::command(this->getSql(), command).query([command](int, string*, string*){ return 0; });
|
|
if(!result) {
|
|
logCritical(LOG_INSTANCE, "Dummy sql connection test faild! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr());
|
|
logCritical(LOG_INSTANCE, "Stopping instance!");
|
|
ts::server::shutdownInstance("invalid sql connection!");
|
|
}
|
|
//debugMessage(0, "SQL connection still alive!");
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if(groupSaveTimestamp + minutes(1) < now) {
|
|
speachUpdateTimestamp = now;
|
|
this->save_group_permissions();
|
|
}
|
|
if(channelSaveTimestamp + minutes(1) < now) {
|
|
speachUpdateTimestamp = now;
|
|
this->save_channel_permissions();
|
|
}
|
|
if(speachUpdateTimestamp + seconds(5) < now) {
|
|
speachUpdateTimestamp = now;
|
|
|
|
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE] = this->calculateSpokenTime().count();
|
|
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL] =
|
|
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>() +
|
|
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>() +
|
|
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
|
|
}
|
|
|
|
this->web_list->tick();
|
|
}
|
|
|
|
void InstanceHandler::save_group_permissions() {
|
|
auto groups = this->getGroupManager()->availableGroups(false);
|
|
for(auto& group : groups) {
|
|
auto permissions = group->permissions();
|
|
if(permissions->require_db_updates()) {
|
|
auto begin = system_clock::now();
|
|
serverInstance->databaseHelper()->saveGroupPermissions(nullptr, group->groupId(), permissions);
|
|
auto end = system_clock::now();
|
|
debugMessage(0, "Saved instance group permissions for group {} ({}) in {}ms", group->groupId(), group->name(), duration_cast<milliseconds>(end - begin).count());
|
|
}
|
|
}
|
|
}
|
|
|
|
void InstanceHandler::save_channel_permissions() {
|
|
shared_lock tree_lock(this->getChannelTreeLock());
|
|
auto channels = this->getChannelTree()->channels();
|
|
tree_lock.unlock();
|
|
|
|
for(auto& channel : channels) {
|
|
auto permissions = channel->permissions();
|
|
if(permissions->require_db_updates()) {
|
|
auto begin = system_clock::now();
|
|
serverInstance->databaseHelper()->saveChannelPermissions(nullptr, channel->channelId(), permissions);
|
|
auto end = system_clock::now();
|
|
debugMessage(0, "Saved instance channel permissions for channel {} ({}) in {}ms", channel->channelId(), channel->name(), duration_cast<milliseconds>(end - begin).count());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::chrono::milliseconds InstanceHandler::calculateSpokenTime() {
|
|
std::chrono::milliseconds result{};
|
|
for(const auto& server : this->voiceServerManager->serverInstances())
|
|
result += server->spoken_time;
|
|
return result;
|
|
}
|
|
|
|
void InstanceHandler::resetSpeechTime() {
|
|
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] = 0;
|
|
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ] = 0;
|
|
for(const auto& server : this->voiceServerManager->serverInstances())
|
|
server->properties()[property::VIRTUALSERVER_SPOKEN_TIME] = 0;
|
|
}
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <net/if.h>
|
|
#include <unistd.h>
|
|
#include <netinet/in.h>
|
|
#include <string.h>
|
|
|
|
string get_mac_address() {
|
|
struct ifreq ifr;
|
|
struct ifconf ifc;
|
|
char buf[1024];
|
|
int success = 0;
|
|
|
|
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
|
if (sock == -1) { return "undefined"; };
|
|
|
|
ifc.ifc_len = sizeof(buf);
|
|
ifc.ifc_buf = buf;
|
|
if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) { /* handle error */ }
|
|
|
|
struct ifreq* it = ifc.ifc_req;
|
|
const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));
|
|
|
|
for (; it != end; ++it) {
|
|
strcpy(ifr.ifr_name, it->ifr_name);
|
|
if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
|
|
if (! (ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback
|
|
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
|
|
success = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else { return "undefined"; }
|
|
}
|
|
|
|
return success ? base64::encode(ifr.ifr_hwaddr.sa_data, 6) : "undefined";
|
|
}
|
|
|
|
#define SN_BUFFER 1024
|
|
std::shared_ptr<license::LicenseRequestData> InstanceHandler::generateLicenseData() {
|
|
auto response = make_shared<license::LicenseRequestData>();
|
|
response->license = config::license;
|
|
response->servers_online = this->voiceServerManager->runningServers();
|
|
auto report = this->voiceServerManager->clientReport();
|
|
response->client_online = report.clients_ts;
|
|
response->web_clients_online = report.clients_web;
|
|
response->bots_online = report.bots;
|
|
response->queries_online = report.queries;
|
|
response->speach_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as<uint64_t>();
|
|
response->speach_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
|
|
response->speach_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>();
|
|
response->speach_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>();
|
|
|
|
{
|
|
auto info = make_shared<license::ServerInfo>();
|
|
info->timestamp = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
|
info->version = build::version()->string(true);
|
|
|
|
{ /* uname */
|
|
utsname retval{};
|
|
if(uname(&retval) < 0) { // <----
|
|
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);
|
|
}
|
|
|
|
}
|
|
|
|
{ /* unique id */
|
|
auto property_unique_id = this->properties()[property::SERVERINSTANCE_UNIQUE_ID];
|
|
if(property_unique_id.as<string>().empty())
|
|
property_unique_id = rnd_string(64);
|
|
|
|
auto hash = digest::sha256(info->uname);
|
|
hash = digest::sha256(hash + property_unique_id.as<string>() + get_mac_address());
|
|
info->unique_identifier = base64::encode(hash);
|
|
}
|
|
|
|
response->info = info;
|
|
}
|
|
return response;
|
|
}
|
|
|
|
bool InstanceHandler::resetMonthlyStats() {
|
|
//serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT
|
|
auto result = sql::command(this->getSql(), "UPDATE `properties` SET `value` = 0 WHERE "
|
|
"`key` = 'serverinstance_monthly_timestamp' OR "
|
|
"`key` = 'virtualserver_month_bytes_downloaded' OR "
|
|
"`key` = 'virtualserver_month_bytes_uploaded' OR "
|
|
"`key` = 'client_month_bytes_downloaded' OR "
|
|
"`key` = 'client_month_bytes_uploaded' OR "
|
|
"`key` = 'client_month_online_time'").execute();
|
|
if(!result) {
|
|
logError(LOG_INSTANCE, "Failed to reset monthly stats ({})", result.fmtStr());
|
|
return false;
|
|
}
|
|
|
|
for(const auto& server : this->getVoiceServerManager()->serverInstances()) {
|
|
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED] = 0;
|
|
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED] = 0;
|
|
|
|
for(const auto& client : server->getClients()) {
|
|
client->properties()[property::CLIENT_MONTH_ONLINE_TIME] = 0;
|
|
client->properties()[property::CLIENT_MONTH_BYTES_UPLOADED] = 0;
|
|
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED] = 0;
|
|
}
|
|
}
|
|
return true;
|
|
} |