#define XMALLOC undefined_malloc /* fix jemalloc and tomcrypt */ #define XCALLOC undefined_calloc #define XFREE undefined_free #define XREALLOC undefined_realloc #include #include "InstanceHandler.h" #include "src/client/InternalClient.h" #include "src/server/QueryServer.h" #include "src/manager/PermissionNameMapper.h" #include "./FileServerHandler.h" #include #include "ShutdownHelper.h" #include #include #include #include #include #include #include "./groups/GroupManager.h" #ifndef _POSIX_SOURCE #define _POSIX_SOURCE #endif #include #include "./manager/ActionLogger.h" #include "./client/shared/ServerCommandExecutor.h" #undef _POSIX_SOURCE using namespace std; using namespace std::chrono; using namespace ts; using namespace ts::server; extern bool mainThreadActive; InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql), permission_helper_{this} { serverInstance = this; this->general_task_executor_ = std::make_shared(ts::config::threads::ticking, "instance tick "); this->general_task_executor_->set_exception_handler([](const std::string& task_name, const std::exception_ptr& exception) { std::string message{}; try { std::rethrow_exception(exception); } catch (const std::exception& ex) { message = "std::exception::what() -> " + std::string{ex.what()}; } catch(...) { message = "unknown exception"; } logCritical(LOG_INSTANCE, "Instance task executor received exception: {}", message); }); this->statistics = make_shared(nullptr); std::string error_message{}; this->license_service_ = std::make_shared(); if(!this->license_service_->initialize(error_message)) { logCritical(LOG_INSTANCE, strobf("Failed to the license service: {}").string(), error_message); } this->dbHelper = new DatabaseHelper(this->getSql()); this->action_logger_ = std::make_unique(); if(!this->action_logger_->initialize(error_message)) { logCritical(LOG_INSTANCE, "Failed to initialize instance action logs: {}", error_message); logCritical(LOG_INSTANCE, "Action log has been disabled."); this->action_logger_->finalize(); } this->_properties = new Properties(); this->_properties->register_property_type(); 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::find(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->get_database_version(); this->properties()[property::SERVERINSTANCE_PERMISSIONS_VERSION] = this->sql->get_permissions_version(); this->globalServerAdmin = std::make_shared(this->getSql(), nullptr, "serveradmin", true); this->globalServerAdmin->initialize_weak_reference(this->globalServerAdmin); ts::server::DatabaseHelper::assignDatabaseId(this->getSql(), 0, globalServerAdmin); this->_musicRoot = std::make_shared(this->getSql(), nullptr, "Music Manager", false); dynamic_pointer_cast(this->_musicRoot)->initialize_weak_reference(this->_musicRoot); { using GroupLoadResult = groups::GroupLoadResult; this->group_manager_ = std::make_shared(this->getSql(), 0, nullptr); if(!this->group_manager_->initialize(this->group_manager_, error_message)) { logCritical(LOG_INSTANCE, "Failed to initialize instance group manager: {}", error_message); mainThreadActive = false; return; } bool initialize_groups{false}; switch(this->group_manager_->server_groups()->load_data(true)) { case GroupLoadResult::SUCCESS: break; case GroupLoadResult::NO_GROUPS: initialize_groups = true; break; case GroupLoadResult::DATABASE_ERROR: logCritical(LOG_INSTANCE, "Failed to load instance server groups (Database error)"); mainThreadActive = false; return; } switch(this->group_manager_->channel_groups()->load_data(true)) { case GroupLoadResult::SUCCESS: break; case GroupLoadResult::NO_GROUPS: initialize_groups = true; break; case GroupLoadResult::DATABASE_ERROR: logCritical(LOG_INSTANCE, "Failed to load instance channel groups (Database error)"); mainThreadActive = false; return; } if(!this->group_manager_->assignments().load_data(error_message)) { logCritical(LOG_INSTANCE, "Failed to load instance group assignments: {}", error_message); mainThreadActive = false; return; } if (initialize_groups) { if(!this->setupDefaultGroups()){ logCritical(LOG_INSTANCE, "Could not setup server instance! Stopping..."); mainThreadActive = false; return; } } this->validate_default_groups(); } { this->default_tree = make_shared(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 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->channel_count() == 4) { auto default_channel = this->default_tree->findChannel("[cspacer04]Default Channel", nullptr); if(default_channel) { auto ch = this->default_tree->createChannel(0, default_channel->channelId(), "[cspacer05]Administrator Room"); ch->permissions()->set_permission(permission::i_channel_needed_view_power, {75, 0}, permission::v2::set_value, permission::v2::do_nothing, false, false); this->save_channel_permissions(); } } 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() == 0) { debugMessage(LOG_INSTANCE, "Setting up monthly reset timestamp!"); this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = duration_cast(system_clock::now().time_since_epoch()).count(); } this->banMgr = new BanManager(this->getSql()); this->banMgr->loadBans(); } InstanceHandler::~InstanceHandler() { delete this->_properties; delete this->banMgr; delete this->dbHelper; group_manager_ = nullptr; globalServerAdmin = nullptr; _musicRoot = nullptr; statistics = nullptr; } inline string strip(std::string message) { while(!message.empty()) { if(message[0] == ' ') message = message.substr(1); else if(message[message.length() - 1] == ' ') message = message.substr(0, message.length() - 1); else break; } return message; } inline vector split_hosts(const std::string& message, char delimiter) { vector result; size_t found, index = 0; do { found = message.find(delimiter, index); result.push_back(strip(message.substr(index, found - index))); index = found + 1; } while(index != 0); return result; } bool InstanceHandler::startInstance() { if (this->active) { return false; } active = true; string errorMessage; this->server_command_executor_ = std::make_shared(ts::config::threads::command_execute); this->permission_mapper = make_shared(); 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(LOG_GENERAL, "Failed to initialize ssl manager."); return false; } this->conversation_io = make_shared("conv io #"); if(!this->conversation_io->initialize(1)) { //TODO: Make the conversation IO loop thread size configurable logCritical(LOG_GENERAL, "Failed to initialize conversation io write loop"); return false; } { vector errors; if(!this->reloadConfig(errors, false)) { logCritical(LOG_GENERAL, "Failed to initialize config:"); for(auto& error : errors) logCritical(LOG_GENERAL, "{}", error); return false; } for(auto& error : errors) logError(LOG_GENERAL, "{}", error); } this->loadWebCertificate(); this->file_server_handler_ = new file::FileServerHandler{this}; if(!this->file_server_handler_->initialize(errorMessage)) { logCritical(LOG_FT, "Failed to initialize server: {}", errorMessage); return false; } if(config::query::sslMode > 0) { if(!this->sslMgr->getContext("query")) { logCritical(LOG_QUERY, "Missing query SSL certificate."); 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!"); } } } { auto query_bindings_string = this->properties()[property::SERVERINSTANCE_QUERY_HOST].as(); auto query_port = this->properties()[property::SERVERINSTANCE_QUERY_PORT].as(); auto query_bindings = net::resolve_bindings(query_bindings_string, query_port); deque> bindings; for(auto& binding : query_bindings) { if(!get<2>(binding).empty()) { logError(LOG_QUERY, "Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding)); continue; } auto entry = make_shared(); memcpy(&entry->address, &get<1>(binding), sizeof(sockaddr_storage)); entry->file_descriptor = -1; entry->event_accept = nullptr; bindings.push_back(entry); } logMessage(LOG_QUERY, "Starting server on {}:{}", query_bindings_string, query_port); if(!queryServer->start(bindings, errorMessage)) { logCritical(LOG_QUERY, "Failed to start query server: {}", errorMessage); return false; } } #ifdef COMPILE_WEB_CLIENT if(config::web::activated) { string error; 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(LOG_GENERAL, "Failed to initialize WebClient TeaForum key! ({})", error); return false; } this->web_event_loop = make_shared(); } #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 VirtualServerManager(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->create_server(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->general_task_executor()->schedule_repeating( this->tick_task_id, "instance ticker", std::chrono::milliseconds{500}, [&](const auto&) { this->tickInstance(); } ); return true; } void InstanceHandler::stopInstance() { { lock_guard lock(this->activeLock); if(!this->active) return; this->active = false; this->activeCon.notify_all(); } this->server_command_executor_->shutdown(); /* TODO: Block on canceling. */ this->general_task_executor()->cancel_task(this->tick_task_id); this->tick_task_id = 0; threads::MutexLock lock_tick(this->lock_tick); 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"); file::finalize(); debugMessage(LOG_FT, "File server stopped"); this->save_channel_permissions(); this->save_group_permissions(); if(this->file_server_handler_) { this->file_server_handler_->finalize(); delete std::exchange(this->file_server_handler_, nullptr); } delete this->sslMgr; this->sslMgr = nullptr; this->web_event_loop = nullptr; this->license_service_->shutdown(); this->server_command_executor_ = 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, strobf("InstanceHandler::tickInstance -> license tick").string(), milliseconds(5)); this->license_service_->execute_tick(); } } { ALARM_TIMER(t, "InstanceHandler::tickInstance -> flush", milliseconds(5)); //logger::flush(); } { ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5)); this->statistics->tick(); } if(statisticsUpdateTimestamp + seconds(1) < now) { statisticsUpdateTimestamp = now; { 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()); 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(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 -> 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 failed! (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_save() + this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as_save() + this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as_save(); } } void InstanceHandler::save_group_permissions() { this->group_manager()->save_permissions(); } 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(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 #include #include 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 InstanceHandler::generateLicenseData() { auto request = std::make_shared(); request->license = config::license; request->metrics.servers_online = this->voiceServerManager->runningServers(); auto report = this->voiceServerManager->clientReport(); request->metrics.client_online = report.clients_ts; request->metrics.web_clients_online = report.clients_web; request->metrics.bots_online = report.bots; request->metrics.queries_online = report.queries; request->metrics.speech_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as(); request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as(); request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as(); request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as(); static std::string null_str{"\0\0\0\0\0\0\0\0", 8}; /* we need at least some characters */ request->web_certificate_revision = this->web_cert_revision.empty() ? null_str : this->web_cert_revision; { request->info.timestamp = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); request->info.version = build::version()->string(true); { /* uname */ utsname retval{}; if(uname(&retval) < 0) { 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); request->info.uname = string(buffer); } } { /* unique id */ auto property_unique_id = this->properties()[property::SERVERINSTANCE_UNIQUE_ID]; if(property_unique_id.as().empty()) property_unique_id = rnd_string(64); auto hash = digest::sha256(request->info.uname); hash = digest::sha256(hash + property_unique_id.as() + get_mac_address()); request->info.unique_id = base64::encode(hash); } } return request; } 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; } bool InstanceHandler::reloadConfig(std::vector& errors, bool reload_file) { if(reload_file) { auto cfg_errors = config::reload(); if(!cfg_errors.empty()) { errors.emplace_back("Failed to load config:"); errors.insert(errors.begin(), cfg_errors.begin(), cfg_errors.end()); return false; } } string error; #ifdef COMPILE_WEB_CLIENT if(config::web::activated) { this->sslMgr->unregister_web_contexts(false); string error; for (auto &certificate : config::web::ssl::certificates) { if(get<0>(certificate) == "default") { logWarning(LOG_GENERAL, "Default Web certificate will be ignored. Using internal one!"); continue; } auto result = this->sslMgr->initializeContext( "web_" + get<0>(certificate), get<1>(certificate), get<2>(certificate), error, false, make_shared( ts::ssl::SSLGenerator{ .subjects = {}, .issues = {{"O", "TeaSpeak"}, {"OU", "Web server"}, {"creator", "WolverinDEV"}} } )); if (!result) { errors.push_back("Failed to initialize web certificate for servername " + get<0>(certificate) + "! (Key: " + get<1>(certificate) + ", Certificate: " + get<2>(certificate) + ")"); continue; } } } #endif auto result = this->sslMgr->initializeContext("query_new", config::query::ssl::keyFile, config::query::ssl::certFile, error, false, make_shared(ssl::SSLGenerator{ .subjects = {}, .issues = {{"O", "TeaSpeak"}, {"OU", "Query server"}, {"creator", "WolverinDEV"}} })); if(!result) errors.push_back("Failed to initialize query certificate! (" + error + ")"); this->sslMgr->rename_context("query_new", "query"); //Will not succeed if the query_new context failed return true; } void InstanceHandler::setWebCertRoot(const std::string &key, const std::string &certificate, const std::string &revision) { std::string error{}; logMessage(LOG_INSTANCE, strobf("Received new web default certificate. Revision {}").string(), hex::hex(revision)); std::string _key{key}, _cert{certificate}, _revision{revision}; auto result = this->sslMgr->initializeContext(strobf("web_default_new").string(), _key, _cert, error, true); if(!result) { logError(LOG_INSTANCE, strobf("Failed to use web default certificate: {}").string(), error); return; } this->sslMgr->rename_context(strobf("web_default_new").string(), strobf("web_default").string()); //https://127-0-0-1.con-gate.work:9987 { /* "Crypt" */ auto& xor_short = _key.length() < _cert.length() ? _key : _cert; auto& xor_long = _key.length() < _cert.length() ? _cert : _key; for(size_t index = 0; index < xor_short.length(); index++) xor_short[index] ^= xor_long[index]; for(size_t index = 0; index < xor_long.length(); index++) xor_long[index] ^= ((index << 4) & 0xFF) | ((index >> 4) & 0xFF); } for(auto& e : _cert) e ^= 0x8A; for(auto& e : _key) e ^= 0x8A; _key = base64::encode(_key); _cert = base64::encode(_cert); _revision = base64::encode(_revision); auto response = sql::command(this->sql->sql(), strobf("DELETE FROM `general` WHERE `key` = 'webcert-revision' or `key` = 'webcert-cert' or `key` = 'webcert-key'").string()).execute(); if(!response) { logError(LOG_INSTANCE, strobf("Failed to delete old default web certificate in database: {}").string(), response.fmtStr()); return; } response = sql::command(this->sql->sql(), strobf("INSERT INTO `general` (`key`, `value`) VALUES ('webcert-revision', :rev), ('webcert-cert', :cert), ('webcert-key', :key)").string(), variable{":rev", _revision}, variable{":cert", _cert}, variable{":key", _key} ).execute(); if(!response) { logError(LOG_INSTANCE, strobf("Failed to insert new default web certificate in database: {}").string(), response.fmtStr()); return; } } void InstanceHandler::loadWebCertificate() { std::string error{}; /* TMP */ { std::string key{}, cert{}; auto result = this->sslMgr->initializeContext("web_default", key, cert, error, true, make_shared( ts::ssl::SSLGenerator{ .subjects = {}, .issues = {{"O", "TeaSpeak"}, {"OU", "Web server"}, {"creator", "WolverinDEV"}} } )); if(!result) logError(LOG_INSTANCE, strobf("Failed to generate fallback web cert key: {}").string(), error); } std::string revision{}, cert{}, _key{}; sql::command(this->sql->sql(), strobf("SELECT * FROM `general` WHERE `key` = 'webcert-revision' or `key` = 'webcert-cert' or `key` = 'webcert-key'").string()) .query([&](int count, std::string* values, std::string* names) { std::string key{}, value{}; for(int index = 0; index < count; index++) { if(names[index] == "key") key = values[index]; else if(names[index] == "value") value = values[index]; } if(!value.empty() && !key.empty()) { if(key == strobf("webcert-revision").string()) revision = value; else if(key == strobf("webcert-cert").string()) cert = value; else if(key == strobf("webcert-key").string()) _key = value; } }); _key = base64::decode(_key); cert = base64::decode(cert); revision = base64::decode(revision); if(revision.empty() || cert.empty() || _key.empty()) { if(!revision.empty() || !cert.empty() || !_key.empty()) logWarning(LOG_INSTANCE, strobf("Failed to load default web certificate from database.").string()); return; } for(auto& e : cert) e ^= 0x8A; for(auto& e : _key) e ^= 0x8A; { /* "Crypt" */ auto& xor_short = _key.length() < cert.length() ? _key : cert; auto& xor_long = _key.length() < cert.length() ? cert : _key; for(size_t index = 0; index < xor_long.length(); index++) xor_long[index] ^= ((index << 4) & 0xFF) | ((index >> 4) & 0xFF); for(size_t index = 0; index < xor_short.length(); index++) xor_short[index] ^= xor_long[index]; } auto result = this->sslMgr->initializeContext(strobf("web_default_new").string(), _key, cert, error, true); if(!result) { logError(LOG_INSTANCE, strobf("Failed to use web default certificate from db: {}").string(), error); return; } this->sslMgr->rename_context(strobf("web_default_new").string(), strobf("web_default").string()); this->web_cert_revision = revision; } bool InstanceHandler::validate_default_groups() { using groups::GroupCalculateMode; using groups::GroupAssignmentCalculateMode; { auto property = this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP]; auto group_id = property.as_save(); debugMessage(LOG_INSTANCE, "Instance admin query group id {}", group_id); auto group_instance = this->group_manager_->server_groups()->find_group(GroupCalculateMode::GLOBAL, group_id); if(!group_instance) { auto available_groups = this->group_manager_->server_groups()->available_groups(GroupCalculateMode::GLOBAL); if(available_groups.empty()) { logCritical(LOG_INSTANCE, "Missing instance server groups."); return false; } group_instance = available_groups.front(); logCritical(LOG_INSTANCE, "Missing instance server admin query group. Using first available ({})", group_instance->group_id()); } property.update_value(group_instance->group_id()); { auto& assignments = this->group_manager_->assignments(); auto client_assignments = assignments.server_groups_of_client(GroupAssignmentCalculateMode::GLOBAL, this->globalServerAdmin->getClientDatabaseId()); if(client_assignments.empty()) { assignments.add_server_group(this->globalServerAdmin->getClientDatabaseId(), group_instance->group_id()); } } } { auto property = this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP]; auto group_id = property.as_save(); debugMessage(LOG_INSTANCE, "Instance guest query group id {}", group_id); auto group_instance = this->group_manager_->server_groups()->find_group(GroupCalculateMode::GLOBAL, group_id); if(!group_instance) { auto available_groups = this->group_manager_->server_groups()->available_groups(GroupCalculateMode::GLOBAL); if(available_groups.empty()) { logCritical(LOG_INSTANCE, "Missing instance server groups."); return false; } group_instance = available_groups.front(); logCritical(LOG_INSTANCE, "Missing instance server guest query group. Using first available ({})", group_instance->group_id()); } property.update_value(group_instance->group_id()); } return true; } InstancePermissionHelper::InstancePermissionHelper(InstanceHandler *instance) : instance{instance} {}