1950 lines
83 KiB
C++
1950 lines
83 KiB
C++
#include <utility>
|
|
|
|
#include <log/LogUtils.h>
|
|
#include "Configuration.h"
|
|
#include "build.h"
|
|
#include "../../license/shared/include/license/license.h"
|
|
#include <yaml-cpp/yaml.h>
|
|
#include <fstream>
|
|
#include <deque>
|
|
#include <log/LogUtils.h>
|
|
#include <regex>
|
|
#include <src/geo/GeoLocation.h>
|
|
#include <misc/strobf.h>
|
|
|
|
#define _stringify(x) #x
|
|
#define stringify(x) _stringify(x)
|
|
|
|
using namespace std;
|
|
using namespace std::chrono;
|
|
using namespace ts;
|
|
using namespace ts::config;
|
|
|
|
//Variable define
|
|
std::string config::database::url;
|
|
std::string config::database::sqlite::locking_mode;
|
|
std::string config::database::sqlite::journal_mode;
|
|
std::string config::database::sqlite::sync_mode;
|
|
|
|
std::shared_ptr<license::License> config::license;
|
|
std::shared_ptr<license::License> config::license_original;
|
|
bool config::experimental_31 = false;
|
|
std::string config::permission_mapping_file;
|
|
|
|
bool ts::config::binding::enforce_default_voice_host = false;
|
|
std::string ts::config::binding::DefaultVoiceHost;
|
|
std::string ts::config::binding::DefaultWebHost;
|
|
std::string ts::config::binding::DefaultQueryHost;
|
|
std::string ts::config::binding::DefaultFileHost;
|
|
uint16_t ts::config::binding::DefaultQueryPort;
|
|
uint16_t ts::config::binding::DefaultFilePort;
|
|
|
|
std::string config::server::DefaultServerVersion;
|
|
std::string config::server::DefaultServerPlatform;
|
|
LicenseType config::server::DefaultServerLicense;
|
|
bool config::server::enable_teamspeak_weblist;
|
|
bool config::server::strict_ut8_mode;
|
|
bool config::server::show_invisible_clients_as_online;
|
|
bool config::server::disable_ip_saving;
|
|
bool config::server::default_music_bot;
|
|
/*
|
|
* namespace limits {
|
|
extern size_t poke_message_length;
|
|
extern size_t talk_power_request_message_length;
|
|
}
|
|
*/
|
|
size_t config::server::limits::poke_message_length;
|
|
size_t config::server::limits::talk_power_request_message_length;
|
|
size_t config::server::limits::afk_message_length;
|
|
|
|
ssize_t config::server::max_virtual_server;
|
|
bool config::server::badges::allow_badges;
|
|
bool config::server::badges::allow_overwolf;
|
|
bool config::server::authentication::name;
|
|
|
|
bool config::server::clients::teamspeak;
|
|
std::string config::server::clients::extra_welcome_message_teamspeak;
|
|
std::string config::server::clients::teamspeak_not_allowed_message;
|
|
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teamspeak;
|
|
|
|
bool config::server::clients::teaweb;
|
|
std::string config::server::clients::extra_welcome_message_teaweb;
|
|
std::string config::server::clients::teaweb_not_allowed_message;
|
|
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaweb;
|
|
|
|
std::string config::server::clients::extra_welcome_message_teaspeak;
|
|
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaspeak;
|
|
|
|
bool config::server::clients::ignore_max_clone_permissions;
|
|
|
|
uint16_t config::voice::default_voice_port;
|
|
size_t config::voice::DefaultPuzzlePrecomputeSize;
|
|
bool config::server::delete_missing_icon_permissions;
|
|
bool config::server::delete_old_bans;
|
|
int config::voice::RsaPuzzleLevel;
|
|
bool config::voice::warn_on_permission_editor;
|
|
bool config::voice::enforce_coocie_handshake;
|
|
int config::voice::connectLimit;
|
|
int config::voice::clientConnectLimit;
|
|
bool config::voice::notifyMuted;
|
|
bool config::voice::suppress_myts_warnings;
|
|
bool config::voice::allow_session_reinitialize;
|
|
|
|
std::string config::query::motd;
|
|
std::string config::query::newlineCharacter;
|
|
int config::query::sslMode;
|
|
std::string config::query::ssl::certFile;
|
|
std::string config::query::ssl::keyFile;
|
|
|
|
std::string config::messages::applicationCrashed;
|
|
std::string config::messages::applicationStopped;
|
|
std::string config::messages::serverStopped;
|
|
|
|
std::string config::messages::idle_time_exceeded;
|
|
std::string config::messages::mute_notify_message;
|
|
std::string config::messages::unmute_notify_message;
|
|
|
|
std::string config::messages::kick_invalid_badges;
|
|
std::string config::messages::kick_invalid_command;
|
|
std::string config::messages::kick_invalid_hardware_id;
|
|
std::string config::messages::kick_vpn;
|
|
|
|
std::string config::messages::shutdown::scheduled;
|
|
std::string config::messages::shutdown::interval;
|
|
std::string config::messages::shutdown::now;
|
|
std::string config::messages::shutdown::canceled;
|
|
std::vector<std::pair<std::chrono::seconds, std::string>> config::messages::shutdown::intervals;
|
|
|
|
std::string config::messages::music::song_announcement;
|
|
|
|
std::string config::messages::timeout::packet_resend_failed;
|
|
std::string config::messages::timeout::connection_reinitialized;
|
|
|
|
size_t config::threads::ticking;
|
|
size_t config::threads::voice::execute_limit;
|
|
size_t config::threads::voice::execute_per_server;
|
|
size_t config::threads::voice::events_per_server;
|
|
size_t config::threads::voice::io_min;
|
|
size_t config::threads::voice::io_per_server;
|
|
size_t config::threads::voice::io_limit;
|
|
bool config::threads::voice::bind_io_thread_to_kernel_thread;
|
|
size_t config::threads::music::execute_limit;
|
|
size_t config::threads::music::execute_per_bot;
|
|
size_t config::threads::web::io_loops;
|
|
std::string config::messages::teamspeak_permission_editor;
|
|
|
|
bool config::web::activated;
|
|
std::deque<std::tuple<std::string, std::string, std::string>> config::web::ssl::certificates;
|
|
uint16_t config::web::webrtc_port_max;
|
|
uint16_t config::web::webrtc_port_min;
|
|
bool config::web::stun_enabled;
|
|
std::string config::web::stun_host;
|
|
uint16_t config::web::stun_port;
|
|
bool config::web::enable_upnp;
|
|
bool config::web::udp_enabled;
|
|
bool config::web::tcp_enabled;
|
|
|
|
size_t config::log::vs_size;
|
|
std::string config::log::path;
|
|
spdlog::level::level_enum config::log::terminalLevel;
|
|
spdlog::level::level_enum config::log::logfileLevel;
|
|
bool config::log::logfileColored;
|
|
|
|
std::string config::geo::countryFlag;
|
|
std::string config::geo::mappingFile;
|
|
bool config::geo::staticFlag;
|
|
geoloc::ProviderType config::geo::type;
|
|
|
|
bool config::geo::vpn_block;
|
|
std::string config::geo::vpn_file;
|
|
|
|
std::string config::crash_path = ".";
|
|
|
|
bool config::music::enabled;
|
|
std::string config::music::command_prefix;
|
|
|
|
//Parse stuff
|
|
#define CREATE_IF_NOT_EXISTS 0b00000001
|
|
#define PREMIUM_ONLY 0b00000010
|
|
#define FLAG_REQUIRE 0b00000100
|
|
#define FLAG_RELOADABLE 0b00001000
|
|
|
|
#define COMMENT(path, comment) commentMapping[path].emplace_back(comment)
|
|
#define WARN_SENSITIVE(path) COMMENT(path, "Do NOT TOUCH unless you're 100% sure!")
|
|
|
|
class ConfigParseError : public exception {
|
|
public:
|
|
ConfigParseError(shared_ptr<EntryBinding> entry, std::string message) : binding(move(entry)), message(std::move(message)) { }
|
|
|
|
const char *what() const noexcept {
|
|
return message.c_str();
|
|
}
|
|
|
|
shared_ptr<EntryBinding> entry() const { return this->binding; }
|
|
private:
|
|
shared_ptr<EntryBinding> binding;
|
|
std::string message;
|
|
};
|
|
|
|
class PathNodeError : public exception {
|
|
public:
|
|
PathNodeError(std::string path, std::string message) : _message(std::move(message)), _path(std::move(path)) { }
|
|
|
|
const char *what() const noexcept {
|
|
return _message.c_str();
|
|
}
|
|
|
|
const std::string path() const { return this->_path; }
|
|
const std::string message() const { return this->_message; }
|
|
private:
|
|
std::string _path;
|
|
std::string _message;
|
|
};
|
|
|
|
std::string escapeJsonString(const std::string& input) {
|
|
std::ostringstream ss;
|
|
for (auto iter = input.cbegin(); iter != input.cend(); iter++) {
|
|
//C++98/03:
|
|
//for (std::string::const_iterator iter = input.begin(); iter != input.end(); iter++) {
|
|
switch (*iter) {
|
|
case '\\': ss << "\\\\"; break;
|
|
case '"': ss << "\\\""; break;
|
|
case '/': ss << "\\/"; break;
|
|
case '\b': ss << "\\b"; break;
|
|
case '\f': ss << "\\f"; break;
|
|
case '\n': ss << "\\n"; break;
|
|
case '\r': ss << "\\r"; break;
|
|
case '\t': ss << "\\t"; break;
|
|
default: ss << *iter; break;
|
|
}
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
std::string escapeHeaderString(const std::string& input) {
|
|
std::ostringstream ss;
|
|
for (auto iter = input.cbegin(); iter != input.cend(); iter++) {
|
|
switch (*iter) {
|
|
case '\b': ss << "\\b"; break;
|
|
case '\f': ss << "\\f"; break;
|
|
case '\n': ss << "\\n"; break;
|
|
case '\r': ss << "\\r"; break;
|
|
case '\t': ss << "\\t"; break;
|
|
case '\"': ss << "\\\""; break;
|
|
default: ss << *iter; break;
|
|
}
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
std::string deescapeJsonString(const std::string& input) {
|
|
std::ostringstream ss;
|
|
for (auto iter = input.cbegin(); iter != input.cend(); iter++) {
|
|
if(*iter == '\\'){
|
|
if(iter++ != input.cend()) return ss.str();
|
|
|
|
switch (*++iter) {
|
|
case 't': ss << "\t"; break;
|
|
case 'n': ss << "\n"; break;
|
|
case 'r': ss << "\r"; break;
|
|
case 'b': ss << "\b"; break;
|
|
case 'f': ss << "\f"; break;
|
|
case '/': ss << "/"; break;
|
|
case '\\': ss << "\\\\"; break;
|
|
default: ss << *iter; break;
|
|
}
|
|
} else ss << *iter;
|
|
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
static bool saveConfig = false;
|
|
|
|
std::vector<std::string> split(const std::string &text, char sep) {
|
|
std::vector<std::string> tokens;
|
|
std::size_t start = 0, end = 0;
|
|
while ((end = text.find(sep, start)) != std::string::npos) {
|
|
tokens.push_back(text.substr(start, end - start));
|
|
start = end + 1;
|
|
}
|
|
if(!text.substr(start).empty())
|
|
tokens.push_back(text.substr(start));
|
|
return tokens;
|
|
}
|
|
|
|
//We need to keep the root nodes in memory
|
|
vector<YAML::Node> resolveNode(const YAML::Node &root,const string& path, bool override_old = false){
|
|
vector<YAML::Node> result;
|
|
result.push_back(root);
|
|
|
|
auto entries = split(path, '.');
|
|
for(auto it = entries.begin(); it != entries.end(); it++) {
|
|
auto& back = result.back();
|
|
if(back.IsMap() || it == entries.end() || back.IsNull())
|
|
result.push_back(back[*it]);
|
|
else if(!back.IsDefined() || override_old)
|
|
result.push_back((back = YAML::Node(YAML::NodeType::Map))[*it]);
|
|
else
|
|
throw PathNodeError(path, "Node '" + *it + "' isnt a sequence");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void remapValue(YAML::Node& node, const string &oldPath, const string &newPath){
|
|
auto old = resolveNode(node, oldPath);
|
|
if(!old.back()) return;
|
|
|
|
auto _new = resolveNode(node, newPath, true);
|
|
_new.back() = YAML::Clone(old.back());
|
|
|
|
if(old.size() > 1) {
|
|
auto oldNode = old[old.size() - 1];
|
|
oldNode = YAML::Null;
|
|
if(old[old.size() - 2].remove(oldNode)) logError(LOG_GENERAL, "Could not remove old config entry");
|
|
}
|
|
}
|
|
|
|
void build_comments(map<string,deque<string>>& map, const std::deque<std::shared_ptr<EntryBinding>>& bindings) {
|
|
for(const auto& entry : bindings) {
|
|
for(const auto& message : entry->description) {
|
|
if(message.first.empty()) {
|
|
for(const auto& m : message.second)
|
|
map[entry->key].push_back(m);
|
|
} else {
|
|
map[entry->key].push_back(message.first + ":");
|
|
for(const auto& m : message.second)
|
|
map[entry->key].push_back(" " + m);
|
|
}
|
|
}
|
|
|
|
if(entry->value_description)
|
|
map[entry->key].push_back(entry->value_description());
|
|
}
|
|
}
|
|
|
|
void read_bindings(YAML::Node& root, const std::deque<std::shared_ptr<EntryBinding>>& bindings, uint8_t required_flags = 0) {
|
|
for(const auto& entry : bindings) {
|
|
if(entry->bounded_by == 2) continue;
|
|
if(required_flags > 0 && (entry->flags & required_flags) == 0) continue;
|
|
|
|
auto nodes = resolveNode(root, entry->key);
|
|
assert(!nodes.empty());
|
|
assert(entry->read_config);
|
|
|
|
if((entry->flags & PREMIUM_ONLY) > 0 && !config::license->isPremium()) {
|
|
const auto default_value = entry->default_value();
|
|
if(nodes.back().IsNull() || !nodes.back().IsDefined())
|
|
entry->set_default(nodes.back());
|
|
|
|
for(const auto& e : entry->default_value())
|
|
entry->read_argument(e);
|
|
continue;
|
|
}
|
|
|
|
entry->read_config(nodes.back());
|
|
}
|
|
}
|
|
|
|
inline string apply_comments(stringstream &in, map<string, deque<string>>& comments);
|
|
std::deque<std::shared_ptr<EntryBinding>> create_local_bindings(int& version, std::string& license);
|
|
|
|
#define CURRENT_CONFIG_VERSION 15
|
|
static std::string _config_path;
|
|
vector<string> config::parseConfig(const std::string& path) {
|
|
_config_path = path;
|
|
|
|
vector<string> errors;
|
|
saveConfig = false;
|
|
|
|
ifstream cfgStream(path);
|
|
YAML::Node config;
|
|
try {
|
|
config = YAML::Load(cfgStream);
|
|
} catch (const YAML::ParserException& ex){
|
|
errors.push_back("Could not load config file: " + ex.msg + " @" + to_string(ex.mark.line) + ":" + to_string(ex.mark.column));
|
|
return errors;
|
|
}
|
|
cfgStream.close();
|
|
|
|
std::map<std::string, std::deque<std::string>> comments;
|
|
try {
|
|
int config_version;
|
|
string teaspeak_license;
|
|
{
|
|
auto bindings = create_local_bindings(config_version, teaspeak_license);
|
|
read_bindings(config, bindings);
|
|
build_comments(comments, bindings);
|
|
}
|
|
if(config_version > CURRENT_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.emplace_back("Attention: Decreasing the version could may lead to data loss!");
|
|
return errors;
|
|
}
|
|
{
|
|
if(config_version != CURRENT_CONFIG_VERSION) {
|
|
logMessage(LOG_GENERAL, "You're using an outdated config.");
|
|
logMessage(LOG_GENERAL, "Updating config");
|
|
|
|
switch (config_version){
|
|
case 1:
|
|
remapValue(config, "voice.rsa_puzzle_precompute_size", "voice.rsa.puzzle_pool_size");
|
|
case 2:
|
|
remapValue(config, "messages.voice.app_stopped", "messages.application.stop");
|
|
remapValue(config, "messages.voice.app_crashed", "messages.application.crash");
|
|
remapValue(config, "messages.voice.server_stopped", "messages.voice.server_stop");
|
|
case 3:
|
|
remapValue(config, "voice.kick_invalid_packet", "voice.protocol.kick_invalid_packet");
|
|
case 4:
|
|
remapValue(config, "messages.voice.default_country", "voice.fallback_country");
|
|
case 6:
|
|
config["geolocation"] = YAML::Node(YAML::NodeType::Map);
|
|
remapValue(config, "voice.fallback_country", "geolocation.fallback_country");
|
|
remapValue(config, "voice.force_fallback_country.", "geolocation.force_fallback_country");
|
|
case 7:
|
|
remapValue(config, "web.ssh.certificate", "web.ssl.certificate");
|
|
remapValue(config, "web.ssh.privatekey", "web.ssl.privatekey");
|
|
case 8:
|
|
remapValue(config, "voice.rsa.puzzle_level", "voice.handshake.puzzle_level");
|
|
case 9:
|
|
if(config["general"]["dbFile"].IsDefined())
|
|
config["general"]["dbFile"] = "sqlite://" + config["general"]["dbFile"].as<string>();
|
|
remapValue(config, "general.dbFile", "general.database_url");
|
|
case 10:
|
|
remapValue(config, "messages.mute.kick_invalid.hardware_id", "messages.kick_invalid.hardware_id");
|
|
remapValue(config, "messages.mute.kick_invalid.command", "messages.kick_invalid.command");
|
|
remapValue(config, "messages.mute.kick_invalid.badges", "messages.kick_invalid.badges");
|
|
remapValue(config, "messages.level", "log.terminal_level");
|
|
case 11:
|
|
remapValue(config, "general.database_url", "general.database.url");
|
|
case 12:
|
|
{
|
|
auto nodes_certificate = YAML::Clone(resolveNode(config, "web.ssl.certificate").back()); //We'll clone here because we're overriding it later
|
|
auto nodes_key = resolveNode(config, "web.ssl.privatekey").back();
|
|
|
|
if(nodes_certificate.IsDefined() && nodes_key.IsDefined()) {
|
|
auto node_certificates_default = resolveNode(config, "web.ssl.certificate.default", true);
|
|
node_certificates_default.back() = YAML::Node(YAML::NodeType::Map);
|
|
node_certificates_default.back()["certificate"] = nodes_certificate;
|
|
node_certificates_default.back()["private_key"] = nodes_key;
|
|
}
|
|
|
|
nodes_key = YAML::Node(YAML::NodeType::Undefined);
|
|
}
|
|
case 13:
|
|
{
|
|
auto nodes_key = resolveNode(config, "binding.query.host").back();
|
|
if(nodes_key.IsDefined() && nodes_key.as<string>() == "0.0.0.0")
|
|
nodes_key = nodes_key.as<string>() + ",[::]";
|
|
auto node_ft = resolveNode(config, "binding.file.host").back();
|
|
if(node_ft.IsDefined() && node_ft.as<string>() == "0.0.0.0")
|
|
node_ft = node_ft.as<string>() + ",[::]";
|
|
}
|
|
case 14:
|
|
{
|
|
{
|
|
auto nodes_key = resolveNode(config, "threads.voice.execute_limit").back();
|
|
if(nodes_key.IsDefined() && nodes_key.as<uint32_t>() == 10)
|
|
nodes_key = "5";
|
|
}
|
|
{
|
|
auto nodes_key = resolveNode(config, "threads.voice.io_limit").back();
|
|
if(nodes_key.IsDefined() && nodes_key.as<uint32_t>() == 10)
|
|
nodes_key = "5";
|
|
}
|
|
{
|
|
auto nodes_key = resolveNode(config, "threads.web.io_loops").back();
|
|
if(nodes_key.IsDefined() && nodes_key.as<uint32_t>() == 4)
|
|
nodes_key = "2";
|
|
}
|
|
{
|
|
auto nodes_key = resolveNode(config, "threads.music.execute_limit").back();
|
|
if(nodes_key.IsDefined() && nodes_key.as<uint32_t>() == 15)
|
|
nodes_key = "2";
|
|
}
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
config["version"] = CURRENT_CONFIG_VERSION;
|
|
config_version = CURRENT_CONFIG_VERSION;
|
|
}
|
|
}
|
|
|
|
//License parsing
|
|
license_parsing:
|
|
{
|
|
string err;
|
|
if(teaspeak_license.empty() || teaspeak_license == "none") {
|
|
//Due to an implementation mistake every default license looks like this:
|
|
//AQA7CN26AAAAAMoLy9BIR2S9HyMeqBx7ZMUUc1rFXkt5YztwZCQRngncqtSs8hsQrzszzeNQSEcVXLtvIhm6m331OgjdugAAAADKbA25Oxab9/MK0xK3iZ8mUg+YkaZQkYS2Bj4Kcq2WFTCynv+sIfeYaei+VV8f/AJvxJDUfADJoSoGX85BIT3cTV+usRs3Adx0Ix/Wi5G4roL8Ypl0p8lYwELLGEVyEQf21rYMWOtVkQk2yoGuQikgLxr6p9sShKmAoES1lbHr8Xk=
|
|
//teaspeak_license = license::createLocalLicence(license::LicenseType::DEMO, system_clock::time_point(), "TeaSpeak");
|
|
teaspeak_license = strobf("AQA7CN26AAAAAMoLy9BIR2S9HyMeqBx7ZMUUc1rFXkt5YztwZCQRngncqtSs8hsQrzszzeNQSEcVXLtvIhm6m331OgjdugAAAADKbA25Oxab9/MK0xK3iZ8mUg+YkaZQkYS2Bj4Kcq2WFTCynv+sIfeYaei+VV8f/AJvxJDUfADJoSoGX85BIT3cTV+usRs3Adx0Ix/Wi5G4roL8Ypl0p8lYwELLGEVyEQf21rYMWOtVkQk2yoGuQikgLxr6p9sShKmAoES1lbHr8Xk=").string();
|
|
config::license = license::readLocalLicence(teaspeak_license, err);
|
|
} else {
|
|
config::license_original = license::readLocalLicence(teaspeak_license, err);
|
|
config::license = config::license_original;
|
|
}
|
|
|
|
if(!config::license){
|
|
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->isValid()) {
|
|
if(config::license->data.type == license::LicenseType::INVALID) {
|
|
errors.emplace_back(strobf("Give license isn't valid!").string());
|
|
return errors;
|
|
}
|
|
|
|
logErrorFmt(true, LOG_GENERAL, strobf("The given license isn't valid!").string());
|
|
logErrorFmt(true, LOG_GENERAL, strobf("Falling back to the default license.").string());
|
|
teaspeak_license = "none";
|
|
|
|
goto license_parsing;
|
|
}
|
|
}
|
|
|
|
{
|
|
auto bindings = create_bindings();
|
|
read_bindings(config, bindings);
|
|
build_comments(comments, bindings);
|
|
}
|
|
|
|
auto currentVersion = config::server::default_version();
|
|
if(currentVersion != config::server::DefaultServerVersion) {
|
|
auto ref = config::server::DefaultServerVersion;
|
|
try {
|
|
auto pattern = strobf("TeaSpeak\\s+").string() + build::pattern();
|
|
static std::regex const matcher(pattern);
|
|
if (std::regex_match(ref, matcher)) {
|
|
logMessage(LOG_GENERAL, "Updating displayed TeaSpeak server version in config from {} to {}", ref, currentVersion);
|
|
config["server"]["version"] = currentVersion;
|
|
config::server::DefaultServerVersion = currentVersion;
|
|
} else {
|
|
debugMessage(LOG_GENERAL, "Config version string does not matches default pattern. Do no updating it (Pattern: {}, Value: {})", pattern, ref);
|
|
}
|
|
} catch(std::exception& e){
|
|
logError(LOG_GENERAL, "Could not update displayed version ({})", e.what());
|
|
}
|
|
}
|
|
|
|
stringstream off;
|
|
off << config;
|
|
|
|
ofstream foff(path);
|
|
foff << apply_comments(off, comments) << endl;
|
|
foff.close();
|
|
} catch(const YAML::Exception& ex) {
|
|
errors.push_back("Could not read config: " + ex.msg + " @" + to_string(ex.mark.line) + ":" + to_string(ex.mark.column));
|
|
return errors;
|
|
} catch(const ConfigParseError& ex) {
|
|
errors.push_back("Failed to parse config entry \"" + ex.entry()->key + "\": " + ex.what());
|
|
return errors;
|
|
} catch(const PathNodeError& ex) {
|
|
errors.push_back("Expected sequence for path " + ex.path() + ": " + ex.message());
|
|
return errors;
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
std::vector<std::string> config::reload() {
|
|
std::vector<std::string> errors;
|
|
saveConfig = false;
|
|
|
|
ifstream cfgStream(_config_path);
|
|
YAML::Node config;
|
|
try {
|
|
config = YAML::Load(cfgStream);
|
|
} catch (const YAML::ParserException& ex){
|
|
errors.emplace_back("Could not load config file: " + ex.msg + " @" + to_string(ex.mark.line) + ":" + to_string(ex.mark.column));
|
|
return errors;
|
|
}
|
|
|
|
try {
|
|
int config_version;
|
|
string teaspeak_license;
|
|
{
|
|
auto bindings = create_local_bindings(config_version, teaspeak_license);
|
|
read_bindings(config, bindings, 0);
|
|
}
|
|
if(config_version != CURRENT_CONFIG_VERSION) {
|
|
errors.emplace_back("Given config version is no equal to the initial one!");
|
|
return errors;
|
|
}
|
|
|
|
auto bindings = create_bindings();
|
|
read_bindings(config, bindings, FLAG_RELOADABLE);
|
|
|
|
|
|
const auto& logConfig = logger::currentConfig();
|
|
if(logConfig) {
|
|
logConfig->logfileLevel = (spdlog::level::level_enum) ts::config::log::logfileLevel;
|
|
logConfig->terminalLevel = (spdlog::level::level_enum) ts::config::log::terminalLevel;
|
|
logger::updateLogLevels();
|
|
}
|
|
} catch(const YAML::Exception& ex) {
|
|
errors.emplace_back("Could not read config: " + ex.msg + " @" + to_string(ex.mark.line) + ":" + to_string(ex.mark.column));
|
|
return errors;
|
|
} catch(const ConfigParseError& ex) {
|
|
errors.emplace_back("Failed to parse config entry \"" + ex.entry()->key + "\": " + ex.what());
|
|
return errors;
|
|
} catch(const PathNodeError& ex) {
|
|
errors.emplace_back("Expected sequence for path " + ex.path() + ": " + ex.message());
|
|
return errors;
|
|
}
|
|
|
|
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"; };
|
|
}
|
|
|
|
void bind_string_parse(const shared_ptr<EntryBinding>& _entry, std::string& target, const std::string& default_value) {
|
|
weak_ptr weak_entry = _entry;
|
|
|
|
_entry->set_default = [weak_entry, default_value](YAML::Node& node) {
|
|
auto entry = weak_entry.lock();
|
|
if(!entry) throw ConfigParseError(nullptr, "Entry handle got deleted!");
|
|
|
|
node = default_value;
|
|
};
|
|
|
|
_entry->read_config = [weak_entry, default_value, &target](YAML::Node& node) {
|
|
auto entry = weak_entry.lock();
|
|
if(!entry) throw ConfigParseError(nullptr, "Entry handle got deleted!");
|
|
|
|
if(!node.IsDefined() || node.IsNull()) {
|
|
if((entry->flags & FLAG_REQUIRE) > 0)
|
|
throw ConfigParseError(entry, "missing required setting");
|
|
else
|
|
entry->set_default(node);
|
|
}
|
|
try {
|
|
target = node.as<string>();
|
|
entry->bounded_by = 1;
|
|
} catch (const YAML::BadConversion& e) {
|
|
throw ConfigParseError(entry, "Invalid node content. Requested was a string!");
|
|
}
|
|
};
|
|
|
|
_entry->read_argument = [weak_entry, &target](const std::string& value) {
|
|
auto entry = weak_entry.lock();
|
|
if(!entry) throw ConfigParseError(nullptr, "Entry handle got deleted!");
|
|
entry->bounded_by = 2;
|
|
|
|
target = value;
|
|
};
|
|
}
|
|
|
|
template<typename T>
|
|
inline std::string type_name() {
|
|
int status;
|
|
std::string tname = typeid(T).name();
|
|
char *demangled_name = abi::__cxa_demangle(tname.c_str(), NULL, NULL, &status);
|
|
if(status == 0) {
|
|
tname = demangled_name;
|
|
std::free(demangled_name);
|
|
}
|
|
return tname;
|
|
}
|
|
|
|
template <typename type_t>
|
|
static typename std::enable_if<std::is_unsigned<type_t>::value, type_t>::type integral_parse(const std::string& value) {
|
|
try {
|
|
auto result = std::stoull(value);
|
|
if(result < numeric_limits<type_t>::min()) throw std::out_of_range("");
|
|
if(result > numeric_limits<type_t>::max()) throw std::out_of_range("");
|
|
return (type_t) result;
|
|
} catch(std::out_of_range& ex) {
|
|
throw YAML::BadConversion(YAML::Mark::null_mark());
|
|
} catch(std::invalid_argument& ex) {
|
|
throw YAML::BadConversion(YAML::Mark::null_mark());
|
|
}
|
|
}
|
|
|
|
template <typename type_t>
|
|
static typename std::enable_if<!std::is_unsigned<type_t>::value, type_t>::type integral_parse(const std::string& value) {
|
|
try {
|
|
auto result = std::stoll(value);
|
|
if(result < numeric_limits<type_t>::min()) throw std::out_of_range("");
|
|
if(result > numeric_limits<type_t>::max()) throw std::out_of_range("");
|
|
return (type_t) result;
|
|
} catch(std::out_of_range& ex) {
|
|
throw YAML::BadConversion(YAML::Mark::null_mark());
|
|
} catch(std::invalid_argument& ex) {
|
|
throw YAML::BadConversion(YAML::Mark::null_mark());
|
|
}
|
|
}
|
|
|
|
template <typename type_t>
|
|
static typename std::enable_if<sizeof(type_t) == 1, uint16_t>::type enum_number_cast(type_t value) { return (uint16_t) value; }
|
|
template <typename type_t>
|
|
static typename std::enable_if<sizeof(type_t) == 2, uint16_t>::type enum_number_cast(type_t value) { return (uint16_t) value; }
|
|
template <typename type_t>
|
|
static typename std::enable_if<sizeof(type_t) == 4, uint32_t>::type enum_number_cast(type_t value) { return (uint32_t) value; }
|
|
template <typename type_t>
|
|
static typename std::enable_if<sizeof(type_t) == 8, uint64_t>::type enum_number_cast(type_t value) { return (uint64_t) value; }
|
|
|
|
static map<string, string> integral_mapping = {
|
|
{"bool", "boolean"},
|
|
{"unsigned char", "positive numeric value"},
|
|
{"unsigned short", "positive numeric value"},
|
|
{"unsigned int", "positive numeric value"},
|
|
{"unsigned long short", "positive numeric value"},
|
|
{"char", "numeric value"},
|
|
{"short", "numeric value"},
|
|
{"int", "numeric value"},
|
|
{"long short", "numeric value"},
|
|
};
|
|
|
|
template <typename type_t, typename std::enable_if<std::is_integral<type_t>::value || std::is_enum<type_t>::value, int>::type = 0>
|
|
void bind_integral_description(const shared_ptr<EntryBinding>& _entry, type_t& target, type_t default_value, type_t min_value, type_t max_value) {
|
|
_entry->default_value = [default_value]() -> std::deque<std::string> { return { to_string(default_value) }; };
|
|
_entry->value_description = [min_value, max_value] {
|
|
auto type_name = ::type_name<decltype(enum_number_cast<type_t>((type_t) 0))>();
|
|
return "The value must be a " + (integral_mapping.count(type_name) > 0 ? integral_mapping[type_name] : type_name) + " between " + to_string(min_value) + " and " + to_string(max_value);
|
|
};
|
|
}
|
|
|
|
template <typename type_t, typename std::enable_if<std::is_integral<type_t>::value || std::is_enum<type_t>::value, int>::type = 0>
|
|
void bind_integral_parse(const shared_ptr<EntryBinding>& _entry, type_t& target, type_t default_value, type_t min_value, type_t max_value) {
|
|
weak_ptr weak_entry = _entry;
|
|
|
|
_entry->set_default = [weak_entry, default_value](YAML::Node& node) {
|
|
auto entry = weak_entry.lock();
|
|
if(!entry) throw ConfigParseError(nullptr, "Entry handle got deleted!");
|
|
|
|
node = enum_number_cast(default_value);
|
|
};
|
|
|
|
_entry->read_config = [weak_entry, default_value, &target, min_value, max_value](YAML::Node& node) {
|
|
auto entry = weak_entry.lock();
|
|
if(!entry) throw ConfigParseError(nullptr, "Entry handle got deleted!");
|
|
|
|
if(!node.IsDefined() || node.IsNull()) {
|
|
if((entry->flags & FLAG_REQUIRE) > 0)
|
|
throw ConfigParseError(entry, "missing required setting");
|
|
else
|
|
entry->set_default(node);
|
|
}
|
|
try {
|
|
auto str = node.as<string>();
|
|
auto value = (type_t) node.as<decltype(enum_number_cast<type_t>((type_t) 0))>();
|
|
if(value < min_value) throw ConfigParseError(entry, "Invalid value (" + to_string(value) + "). Received value substeps boundary (" + to_string(min_value) + ")");
|
|
if(value > max_value) throw ConfigParseError(entry, "Invalid value (" + to_string(value) + "). Received value exceeds boundary (" + to_string(max_value) + ")");
|
|
|
|
target = value;
|
|
entry->bounded_by = 1;
|
|
} catch (const YAML::BadConversion& e) {
|
|
throw ConfigParseError(entry, "Invalid node content. Requested was a integral of type " + type_name<type_t>() + "!");
|
|
}
|
|
};
|
|
|
|
_entry->read_argument = [weak_entry, &target, min_value, max_value](const std::string& string) {
|
|
auto entry = weak_entry.lock();
|
|
if(!entry) throw ConfigParseError(nullptr, "Entry handle got deleted!");
|
|
entry->bounded_by = 2;
|
|
|
|
try {
|
|
auto value = (type_t) integral_parse<decltype(enum_number_cast<type_t>((type_t) 0))>(string);
|
|
if(value < min_value) throw ConfigParseError(entry, "Invalid value (" + to_string(value) + "). Received value substeps boundary (" + to_string(min_value) + ")");
|
|
if(value > max_value) throw ConfigParseError(entry, "Invalid value (" + to_string(value) + "). Received value exceeds boundary (" + to_string(max_value) + ")");
|
|
|
|
target = value;
|
|
entry->bounded_by = 2;
|
|
} catch (const YAML::BadConversion& e) {
|
|
throw ConfigParseError(entry, "Invalid node content. Requested was a integral of type " + type_name<type_t>() + "!");
|
|
}
|
|
};
|
|
}
|
|
|
|
void bind_vector_description(const shared_ptr<EntryBinding>& _entry, deque<string>&, const deque<string>& default_value) {
|
|
_entry->default_value = [default_value]() -> std::deque<std::string> { return default_value; };
|
|
_entry->value_description = [] { return "The value must be a sequence"; };
|
|
}
|
|
|
|
void bind_vector_parse(const shared_ptr<EntryBinding>& _entry, deque<string>& target, const deque<string>& default_value) {
|
|
weak_ptr weak_entry = _entry;
|
|
|
|
|
|
_entry->set_default = [weak_entry, default_value](YAML::Node& node) {
|
|
auto entry = weak_entry.lock();
|
|
if(!entry) throw ConfigParseError(nullptr, "Entry handle got deleted!");
|
|
|
|
node = YAML::Node(YAML::NodeType::Sequence);
|
|
for(const auto& entry : default_value)
|
|
node.push_back(entry);
|
|
};
|
|
|
|
_entry->read_config = [weak_entry, default_value, &target](YAML::Node& node) {
|
|
auto entry = weak_entry.lock();
|
|
if(!entry) throw ConfigParseError(nullptr, "Entry handle got deleted!");
|
|
|
|
if(!node.IsDefined() || node.IsNull()) {
|
|
if((entry->flags & FLAG_REQUIRE) > 0)
|
|
throw ConfigParseError(entry, "missing required setting");
|
|
else {
|
|
entry->set_default(node);
|
|
}
|
|
}
|
|
if(!node.IsSequence())
|
|
throw ConfigParseError(entry, "node requires to be a sequence");
|
|
|
|
try {
|
|
target.clear();
|
|
for(const auto& element : node) {
|
|
target.push_back(element.as<string>());
|
|
}
|
|
} catch (const YAML::BadConversion& e) {
|
|
throw ConfigParseError(entry, "Invalid node sequence content");
|
|
}
|
|
};
|
|
|
|
_entry->read_argument = [weak_entry, &target](const std::string& string) {
|
|
auto entry = weak_entry.lock();
|
|
if(!entry) throw ConfigParseError(nullptr, "Entry handle got deleted!");
|
|
if(entry->bounded_by != 2)
|
|
target.clear();
|
|
entry->bounded_by = 2;
|
|
|
|
target.push_back(string);
|
|
};
|
|
}
|
|
|
|
struct GroupStackEntry {
|
|
GroupStackEntry(deque<string>& list, string path) : list(list), path(move(path)) {
|
|
list.push_back(this->path);
|
|
}
|
|
~GroupStackEntry() {
|
|
assert(list.back() == this->path);
|
|
list.pop_back();
|
|
}
|
|
|
|
string path;
|
|
deque<string>& list;
|
|
};
|
|
|
|
inline std::string join_path(const deque<string>& stack, const std::string& entry) {
|
|
stringstream ss;
|
|
for(const auto& e : stack)
|
|
ss << e << ".";
|
|
ss << entry;
|
|
return ss.str();
|
|
}
|
|
|
|
#define STR(x) #x
|
|
#define BIND_GROUP(name) GroupStackEntry group_ ##name(group_stack, STR(name));
|
|
|
|
#define CREATE_BINDING(name, _flags) \
|
|
auto binding = make_shared<EntryBinding>(); \
|
|
binding->key = join_path(group_stack, name); \
|
|
binding->flags = (_flags); \
|
|
result.push_back(binding)
|
|
|
|
#define BIND_STRING(target, default) \
|
|
bind_string_parse(binding, target, default); \
|
|
bind_string_description(binding, target, default)
|
|
|
|
#define BIND_VECTOR(target, default) \
|
|
bind_vector_parse(binding, target, default); \
|
|
bind_vector_description(binding, target, default)
|
|
|
|
#define BIND_INTEGRAL(target, default, min, max) \
|
|
bind_integral_parse<typename std::remove_reference<decltype(target)>::type>( \
|
|
binding, \
|
|
target, \
|
|
(typename std::remove_reference<decltype(target)>::type) default, \
|
|
(typename std::remove_reference<decltype(target)>::type) min, \
|
|
(typename std::remove_reference<decltype(target)>::type) max \
|
|
); \
|
|
bind_integral_description<typename std::remove_reference<decltype(target)>::type>( \
|
|
binding, \
|
|
target, \
|
|
(typename std::remove_reference<decltype(target)>::type) default, \
|
|
(typename std::remove_reference<decltype(target)>::type) min, \
|
|
(typename std::remove_reference<decltype(target)>::type) max \
|
|
)
|
|
|
|
#define BIND_BOOL(target, default) BIND_INTEGRAL(target, default, false, true)
|
|
|
|
#define ADD_DESCRIPTION(desc, ...) \
|
|
for(const auto& entry : {desc, ##__VA_ARGS__}) \
|
|
binding->description["Description"].emplace_back(entry)
|
|
|
|
#define ADD_NOTE(desc, ...) \
|
|
for(const auto& entry : {desc, ##__VA_ARGS__}) \
|
|
binding->description["Notes"].emplace_back(entry)
|
|
|
|
#define ADD_NOTE_RELOADABLE() \
|
|
binding->description["Notes"].emplace_back("This option could be reloaded while the instance is running.")
|
|
|
|
#define ADD_WARN(desc, ...) \
|
|
for(const auto& entry : {desc, ##__VA_ARGS__}) \
|
|
binding->description["Warning"].emplace_back(entry)
|
|
|
|
#define ADD_SENSITIVE() ADD_WARN("Do NOT TOUCH unless you're 100% sure!")
|
|
|
|
std::deque<std::shared_ptr<EntryBinding>> create_local_bindings(int& version, std::string& license) {
|
|
deque<shared_ptr<EntryBinding>> result;
|
|
deque<string> group_stack;
|
|
|
|
{
|
|
CREATE_BINDING("version", 0);
|
|
BIND_INTEGRAL(version, CURRENT_CONFIG_VERSION, 0, 10000000000);
|
|
ADD_DESCRIPTION("The current config version");
|
|
ADD_WARN("This is an auto-generated id!", "Modification could cause data loss!");
|
|
}
|
|
{
|
|
CREATE_BINDING("general.license", 0);
|
|
BIND_STRING(license, "none");
|
|
ADD_DESCRIPTION("Insert here your TeaSpeak license code (if you have one)");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static std::deque<std::shared_ptr<EntryBinding>> _create_bindings;
|
|
std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
|
if(!_create_bindings.empty()) return _create_bindings;
|
|
|
|
deque<shared_ptr<EntryBinding>> result;
|
|
deque<string> group_stack;
|
|
|
|
{
|
|
BIND_GROUP(general);
|
|
|
|
//CREATE_BINDING("database_url", 0); old
|
|
{
|
|
BIND_GROUP(database);
|
|
{
|
|
CREATE_BINDING("url", 0);
|
|
BIND_STRING(config::database::url, "sqlite://TeaData.sqlite");
|
|
ADD_DESCRIPTION("Available urls:");
|
|
ADD_DESCRIPTION(" sqlite://[file]");
|
|
ADD_DESCRIPTION(" mysql://[host][:port]/[database][?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]");
|
|
ADD_DESCRIPTION("");
|
|
ADD_DESCRIPTION("More info about about the mysql url could be found here: https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-configuration-properties.html");
|
|
ADD_DESCRIPTION("There's also a new property called 'connections', which describes how many connections and queries could be executed synchronously");
|
|
ADD_DESCRIPTION("MySQL example: mysql://localhost:3306/teaspeak?userName=root&password=mysecretpassword&connections=4");
|
|
ADD_DESCRIPTION("Attention: If you're using MySQL you need at least 3 connections!");
|
|
}
|
|
|
|
{
|
|
BIND_GROUP(sqlite);
|
|
{
|
|
CREATE_BINDING("locking_mode", 0);
|
|
BIND_STRING(config::database::sqlite::locking_mode, "EXCLUSIVE");
|
|
ADD_DESCRIPTION("Sqlite database locking mode.");
|
|
ADD_DESCRIPTION("Set it to nothing (\"\") to use the default driver setting");
|
|
ADD_DESCRIPTION("More information could be found here: https://www.sqlite.org/lockingv3.html");
|
|
}
|
|
{
|
|
CREATE_BINDING("sync_mode", 0);
|
|
BIND_STRING(config::database::sqlite::sync_mode, "NORMAL");
|
|
ADD_DESCRIPTION("Sqlite database synchronous mode.");
|
|
ADD_DESCRIPTION("Set it to nothing (\"\") to use the default driver setting");
|
|
ADD_DESCRIPTION("More information could be found here: https://www.sqlite.org/pragma.html#pragma_synchronous");
|
|
}
|
|
{
|
|
CREATE_BINDING("journal_mode", 0);
|
|
BIND_STRING(config::database::sqlite::journal_mode, "WAL");
|
|
ADD_DESCRIPTION("Sqlite database journal mode.");
|
|
ADD_DESCRIPTION("Set it to nothing (\"\") to use the default driver setting");
|
|
ADD_DESCRIPTION("More information could be found here: https://www.sqlite.org/pragma.html#pragma_journal_mode");
|
|
}
|
|
}
|
|
}
|
|
{
|
|
CREATE_BINDING("crash_path", 0);
|
|
BIND_STRING(config::crash_path, "crash_dumps/");
|
|
ADD_DESCRIPTION("Define the folder where the crash dump files will be moved, when the server crashes");
|
|
}
|
|
{
|
|
CREATE_BINDING("command_prefix", 0);
|
|
BIND_STRING(config::music::command_prefix, ".");
|
|
ADD_DESCRIPTION("The default channel chat command prefix");
|
|
}
|
|
{
|
|
CREATE_BINDING("permission_mapping", 0);
|
|
BIND_STRING(config::permission_mapping_file, "resources/permission_mapping.txt");
|
|
ADD_DESCRIPTION("Mapping for the permission names");
|
|
ADD_SENSITIVE();
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(log)
|
|
{
|
|
CREATE_BINDING("level", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::log::logfileLevel, spdlog::level::debug, spdlog::level::trace, spdlog::level::off);
|
|
ADD_NOTE_RELOADABLE();
|
|
ADD_DESCRIPTION("The log level within the log files");
|
|
ADD_DESCRIPTION("Available types:");
|
|
ADD_DESCRIPTION(" 0: Trace");
|
|
ADD_DESCRIPTION(" 1: Debug");
|
|
ADD_DESCRIPTION(" 2: Info");
|
|
ADD_DESCRIPTION(" 3: Warn");
|
|
ADD_DESCRIPTION(" 4: Error");
|
|
ADD_DESCRIPTION(" 5: Critical");
|
|
ADD_DESCRIPTION(" 6: Off");
|
|
}
|
|
{
|
|
CREATE_BINDING("terminal_level", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::log::terminalLevel, spdlog::level::info, spdlog::level::trace, spdlog::level::off);
|
|
ADD_NOTE_RELOADABLE();
|
|
ADD_DESCRIPTION("The log level within the TeaSpeak server terminal");
|
|
ADD_DESCRIPTION("Available types:");
|
|
ADD_DESCRIPTION(" 0: Trace");
|
|
ADD_DESCRIPTION(" 1: Debug");
|
|
ADD_DESCRIPTION(" 2: Info");
|
|
ADD_DESCRIPTION(" 3: Warn");
|
|
ADD_DESCRIPTION(" 4: Error");
|
|
ADD_DESCRIPTION(" 5: Critical");
|
|
ADD_DESCRIPTION(" 6: Off");
|
|
}
|
|
{
|
|
CREATE_BINDING("colored", 0);
|
|
BIND_BOOL(config::log::logfileColored, false);
|
|
ADD_DESCRIPTION("Disable/enable ascii codes within the log file");
|
|
}
|
|
{
|
|
CREATE_BINDING("vs_size", 0);
|
|
BIND_INTEGRAL(config::log::vs_size, 0, 0, numeric_limits<ServerId>::max());
|
|
ADD_DESCRIPTION("Virtual server log chunk size");
|
|
}
|
|
{
|
|
CREATE_BINDING("path", 0);
|
|
BIND_STRING(config::log::path, "logs/log_${time}(%Y-%m-%d_%H:%M:%S)_${group}.log");
|
|
ADD_DESCRIPTION("The log file path");
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(binding);
|
|
{
|
|
BIND_GROUP(voice);
|
|
{
|
|
CREATE_BINDING("default_host", 0);
|
|
BIND_STRING(config::binding::DefaultVoiceHost, "0.0.0.0,::");
|
|
ADD_NOTE("Multibinding supported here! Host delimiter is \",\"");
|
|
}
|
|
{
|
|
CREATE_BINDING("enforce", 0);
|
|
BIND_BOOL(config::binding::enforce_default_voice_host, false);
|
|
ADD_NOTE("Enforce the default host for every virtual server. Ignoring the server specific host");
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(web);
|
|
{
|
|
CREATE_BINDING("default_host", 0);
|
|
BIND_STRING(config::binding::DefaultWebHost, "0.0.0.0,[::]");
|
|
ADD_NOTE("Multibinding supported here! Host delimiter is \",\"");
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(query);
|
|
{
|
|
CREATE_BINDING("port", 0);
|
|
BIND_INTEGRAL(config::binding::DefaultQueryPort, 10101, 1, 65535);
|
|
}
|
|
{
|
|
CREATE_BINDING("host", 0);
|
|
BIND_STRING(config::binding::DefaultQueryHost, "0.0.0.0,[::]");
|
|
ADD_NOTE("Multibinding supported here! Host delimiter is \",\"");
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(file);
|
|
{
|
|
CREATE_BINDING("port", 0);
|
|
BIND_INTEGRAL(config::binding::DefaultFilePort, 30303, 1, 65535);
|
|
}
|
|
{
|
|
CREATE_BINDING("host", 0);
|
|
BIND_STRING(config::binding::DefaultFileHost, "0.0.0.0,[::]");
|
|
ADD_NOTE("Multibinding supported here! Host delimiter is \",\"");
|
|
}
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(query);
|
|
{
|
|
CREATE_BINDING("nl_char", 0);
|
|
BIND_STRING(config::query::newlineCharacter, "\n");
|
|
ADD_DESCRIPTION("Change the query newline character");
|
|
ADD_NOTE("NOTE: TS3 - Compatible bots may require \"\n\r\"");
|
|
}
|
|
{
|
|
CREATE_BINDING("motd", 0);
|
|
BIND_STRING(config::query::motd, "TeaSpeak\nWelcome on the TeaSpeak ServerQuery interface.\n");
|
|
ADD_DESCRIPTION("The query welcome message");
|
|
|
|
ADD_NOTE("If not like TeamSpeak then some applications may not recognize the Query");
|
|
ADD_NOTE("Default TeamSpeak 3 MOTD:");
|
|
ADD_NOTE("TS3\n\rWelcome to the TeamSpeak 3 ServerQuery interface, type \"help\" for a list of commands and \"help <command>\" for information on a specific command.\n\r");
|
|
ADD_NOTE("NOTE: Sometimes you have to append one \r\n more!");
|
|
}
|
|
{
|
|
CREATE_BINDING("enableSSL", 0);
|
|
BIND_INTEGRAL(config::query::sslMode, 2, 0, 2);
|
|
ADD_DESCRIPTION("Enable/disable SSL for query");
|
|
ADD_DESCRIPTION("Available modes:");
|
|
ADD_DESCRIPTION(" 0: Disabled");
|
|
ADD_DESCRIPTION(" 1: Enabled (Enforced encryption)");
|
|
ADD_DESCRIPTION(" 2: Hybrid (Prefer encryption but fallback when it isnt available)");
|
|
}
|
|
{
|
|
BIND_GROUP(ssl);
|
|
{
|
|
CREATE_BINDING("certificate", FLAG_RELOADABLE);
|
|
BIND_STRING(config::query::ssl::certFile, "certs/query_certificate.pem");
|
|
ADD_DESCRIPTION("The SSL certificate for the query client");
|
|
}
|
|
{
|
|
CREATE_BINDING("privatekey", FLAG_RELOADABLE);
|
|
BIND_STRING(config::query::ssl::keyFile, "certs/query_privatekey.pem");
|
|
ADD_DESCRIPTION("The SSL private key for the query client (You have to export the key without a password!)");
|
|
}
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(voice)
|
|
{
|
|
CREATE_BINDING("default_port", 0);
|
|
BIND_INTEGRAL(config::voice::default_voice_port, 9987, 1, 65535);
|
|
ADD_DESCRIPTION("Change the default voice server port", "This also defines the start where the instance search for free server ports on a new server creation");
|
|
ADD_NOTE("This setting only apply once, when you create a new instance.",
|
|
"Once applied the default server port would not be changed!",
|
|
"The start point for the server creation still apply.");
|
|
}
|
|
{
|
|
CREATE_BINDING("notifymute", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::voice::notifyMuted, false);
|
|
ADD_DESCRIPTION("Enable/disable the mute notify");
|
|
}
|
|
{
|
|
CREATE_BINDING("suppress_myts_warnings", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::voice::suppress_myts_warnings, true);
|
|
ADD_DESCRIPTION("Suppress the MyTS integration warnings");
|
|
}
|
|
{
|
|
CREATE_BINDING("allow_session_reinitialize", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::voice::allow_session_reinitialize, true);
|
|
ADD_DESCRIPTION("Enable/disable fast session reinitialisation.");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
CREATE_BINDING("rsa.puzzle_pool_size", 0);
|
|
BIND_INTEGRAL(config::voice::DefaultPuzzlePrecomputeSize, 128, 1, 65536);
|
|
ADD_DESCRIPTION("The amount of precomputed puzzles");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
BIND_GROUP(handshake);
|
|
{
|
|
CREATE_BINDING("puzzle_level", 0);
|
|
BIND_INTEGRAL(config::voice::RsaPuzzleLevel, 1000, 512, 1048576);
|
|
ADD_DESCRIPTION("The puzzle level. (A higher number will result a longer calculation time for the manager RSA puzzle)");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
CREATE_BINDING("enforce_cookie", 0);
|
|
BIND_BOOL(config::voice::enforce_coocie_handshake, true);
|
|
ADD_DESCRIPTION("Enforces the cookie exchange (Low level protection against distributed denial of service attacks (DDOS attacks))");
|
|
ADD_NOTE("This option is highly recommended!");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
CREATE_BINDING("warn_on_permission_editor", 0);
|
|
BIND_BOOL(config::voice::warn_on_permission_editor, true);
|
|
ADD_DESCRIPTION("Enables/disabled the warning popup for the TeamSpeak 3 permission editor.");
|
|
ADD_NOTE("This option is highly recommended!");
|
|
}
|
|
}
|
|
{
|
|
CREATE_BINDING("connect_limit", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::voice::connectLimit, 10, 0, 1024);
|
|
ADD_DESCRIPTION("Maximum amount of join attempts per second.");
|
|
ADD_NOTE("A value of zero means unlimited");
|
|
}
|
|
{
|
|
CREATE_BINDING("client_connect_limit", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::voice::clientConnectLimit, 3, 0, 1024);
|
|
ADD_DESCRIPTION("Maximum amount of join attempts per second per ip.");
|
|
ADD_NOTE("A value of zero means unlimited");
|
|
}
|
|
{
|
|
CREATE_BINDING("protocol.experimental_31", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::experimental_31, false);
|
|
ADD_DESCRIPTION("Enables the newer and safer protocol based on TeamSpeak's documented standard");
|
|
ADD_NOTE("An invalid protocol chain could lead clients to calculate a wrong shared secret result");
|
|
ADD_NOTE("This may cause a connection setup fail and the client will be unable to connect!");
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(server)
|
|
{
|
|
CREATE_BINDING("platform", PREMIUM_ONLY | FLAG_RELOADABLE);
|
|
BIND_STRING(config::server::DefaultServerPlatform, build::platform());
|
|
ADD_DESCRIPTION("The displayed platform to the client");
|
|
ADD_NOTE("This option is only for the premium version.");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("version", PREMIUM_ONLY | FLAG_RELOADABLE);
|
|
BIND_STRING(config::server::DefaultServerVersion, strobf("TeaSpeak ").string() + build::version()->string(true));
|
|
ADD_DESCRIPTION("The displayed version to the client");
|
|
ADD_NOTE("This option is only for the premium version.");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("licence", PREMIUM_ONLY | FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::server::DefaultServerLicense, LicenseType::LICENSE_AUTOMATIC_SERVER, LicenseType::_LicenseType_MIN, LicenseType::_LicenseType_MAX);
|
|
ADD_DESCRIPTION("The displayed licence type to every TeaSpeak 3 Client");
|
|
ADD_DESCRIPTION("Available types:");
|
|
ADD_DESCRIPTION(" 0: No Licence");
|
|
ADD_DESCRIPTION(" 1: Authorised TeaSpeak Host Provider License (ATHP)");
|
|
ADD_DESCRIPTION(" 2: Offline/Lan Licence");
|
|
ADD_DESCRIPTION(" 3: Non-Profit License (NPL)");
|
|
ADD_DESCRIPTION(" 4: Unknown Licence");
|
|
ADD_DESCRIPTION(" 5: ~placeholder~");
|
|
ADD_DESCRIPTION(" 6: Auto-License (Server based)");
|
|
ADD_DESCRIPTION(" 7: Auto-License (Instance based)");
|
|
ADD_NOTE("This option just work for non 3.2 clients!");
|
|
ADD_NOTE("This option is only for the premium version.");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("delete_old_bans", 0);
|
|
BIND_BOOL(config::server::delete_old_bans, true);
|
|
ADD_DESCRIPTION("Enable/disable the deletion of old bans within the database");
|
|
}
|
|
#if 0
|
|
{
|
|
CREATE_BINDING("delete_missing_icon_permissions", 0);
|
|
BIND_BOOL(config::server::delete_missing_icon_permissions, true);
|
|
ADD_DESCRIPTION("Enable/disable the deletion of invalid icon id permissions");
|
|
}
|
|
#endif
|
|
{
|
|
CREATE_BINDING("allow_weblist", 0);
|
|
BIND_BOOL(config::server::enable_teamspeak_weblist, true);
|
|
ADD_DESCRIPTION("Enable/disable weblist reports globally! (Server setting wount be disabled, they will be just not send)");
|
|
}
|
|
{
|
|
CREATE_BINDING("strict_ut8_mode", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::server::strict_ut8_mode, false);
|
|
ADD_DESCRIPTION("If enabled an error will be throws on invalid UTF-8 characters within the protocol (Query & Client).");
|
|
ADD_DESCRIPTION("Else the property pair will be dropped silently!");
|
|
}
|
|
{
|
|
CREATE_BINDING("show_invisible_clients", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::server::show_invisible_clients_as_online, true);
|
|
ADD_DESCRIPTION("Allow anybody to send text messages to clients which are in invisible channels");
|
|
}
|
|
{
|
|
CREATE_BINDING("disable_ip_saving", 0);
|
|
BIND_BOOL(config::server::disable_ip_saving, false);
|
|
ADD_DESCRIPTION("Disable the saving of IP addresses within the database.");
|
|
}
|
|
{
|
|
CREATE_BINDING("default_music_bot", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::server::default_music_bot, true);
|
|
ADD_DESCRIPTION("Add by default a new music bot to each created virtual server.");
|
|
}
|
|
{
|
|
CREATE_BINDING("max_virtual_servers", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::server::max_virtual_server, 16, -1, 999999);
|
|
ADD_DESCRIPTION("Set the limit for maximal virtual servers. -1 means unlimited.");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
BIND_GROUP(limits);
|
|
|
|
{
|
|
CREATE_BINDING("poke_message_length", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::server::limits::poke_message_length, 1024, 1, 262144);
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
|
|
{
|
|
CREATE_BINDING("talk_power_request_message_length", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::server::limits::talk_power_request_message_length, 50, 1, 262144);
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
|
|
{
|
|
CREATE_BINDING("afk_message_length", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::server::limits::afk_message_length, 50, 1, 262144);
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
}
|
|
{
|
|
/*
|
|
BIND_GROUP(badges);
|
|
{
|
|
CREATE_BINDING("badges", 0);
|
|
BIND_BOOL(config::server::badges::allow_badges, true);
|
|
ADD_DESCRIPTION("Allow or disallow TeamSpeak badges");
|
|
}
|
|
{
|
|
CREATE_BINDING("overwolf", 0);
|
|
BIND_BOOL(config::server::badges::allow_overwolf, true);
|
|
ADD_DESCRIPTION("Allow or disallow the Overwolf badge");
|
|
}
|
|
*/
|
|
}
|
|
{
|
|
BIND_GROUP(authentication);
|
|
{
|
|
CREATE_BINDING("name", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::server::authentication::name, false);
|
|
ADD_DESCRIPTION("Allow or disallow client authentication just by their name");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
}
|
|
{
|
|
using WelcomeMessageType = config::server::clients::WelcomeMessageType;
|
|
BIND_GROUP(clients);
|
|
|
|
/* TeamSpeak */
|
|
{
|
|
CREATE_BINDING("teamspeak", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::server::clients::teamspeak, true);
|
|
ADD_DESCRIPTION("Allow/disallow the TeamSpeak 3 client to join the server.");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("not_allowed_message", FLAG_RELOADABLE);
|
|
BIND_STRING(config::server::clients::teamspeak_not_allowed_message, "");
|
|
ADD_DESCRIPTION("Chaneg the message, displayed when denying the access to the server");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("teamspeak_message", FLAG_RELOADABLE);
|
|
BIND_STRING(config::server::clients::extra_welcome_message_teamspeak, "");
|
|
ADD_DESCRIPTION("Add an extra welcome message for TeamSpeak client users");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("teamspeak_message_type", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teamspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
|
|
ADD_DESCRIPTION("The welcome message type modes");
|
|
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
|
|
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
|
|
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
|
|
/* TeaSpeak */
|
|
/*
|
|
{
|
|
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::server::clients::teaspeak, true);
|
|
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
*/
|
|
{
|
|
CREATE_BINDING("teaspeak_message", FLAG_RELOADABLE);
|
|
BIND_STRING(config::server::clients::extra_welcome_message_teaspeak, "");
|
|
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Client users");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("teaspeak_message_type", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
|
|
ADD_DESCRIPTION("The welcome message type modes");
|
|
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
|
|
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
|
|
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
|
|
|
|
/* TeaWeb */
|
|
{
|
|
CREATE_BINDING("teaweb", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::server::clients::teaweb, true);
|
|
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Web client to join the server.");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("not_allowed_message", FLAG_RELOADABLE);
|
|
BIND_STRING(config::server::clients::teaweb_not_allowed_message, "");
|
|
ADD_DESCRIPTION("Chaneg the message, displayed when denying the access to the server");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("teaweb_message", FLAG_RELOADABLE);
|
|
BIND_STRING(config::server::clients::extra_welcome_message_teaweb, "");
|
|
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Web client users");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("teaweb_message_type", FLAG_RELOADABLE);
|
|
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaweb, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
|
|
ADD_DESCRIPTION("The welcome message type modes");
|
|
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
|
|
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
|
|
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
|
|
{
|
|
CREATE_BINDING("ignore_max_clone_permissions", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::server::clients::ignore_max_clone_permissions, false);
|
|
ADD_DESCRIPTION("Allows you to disable the permission checks for i_client_max_clones_uid, i_client_max_clones_ip and i_client_max_clones_hwid");
|
|
ADD_SENSITIVE();
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(web);
|
|
{
|
|
CREATE_BINDING("enabled", 0);
|
|
BIND_BOOL(config::web::activated, true);
|
|
ADD_DESCRIPTION("Disable/enable the possibility to connect via the TeaSpeak web client");
|
|
ADD_NOTE("If you've disabled this feature the TeaClient wound be able to join too.");
|
|
}
|
|
{
|
|
CREATE_BINDING("upnp", 0);
|
|
BIND_BOOL(config::web::enable_upnp, false);
|
|
ADD_DESCRIPTION("Disable/enable UPNP support");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
BIND_GROUP(ssl)
|
|
{
|
|
CREATE_BINDING("certificate", FLAG_RELOADABLE);
|
|
ADD_NOTE_RELOADABLE();
|
|
binding->type = 4;
|
|
|
|
/* no terminal handling */
|
|
binding->read_argument = [](const std::string&) {
|
|
logError(LOG_GENERAL, "Failed to parse ssl certificate. Its only possible to configure them via config!");
|
|
};
|
|
binding->default_value = []() -> deque<string> { return {}; };
|
|
|
|
//Unused :)
|
|
binding->set_default = [](YAML::Node& node) {
|
|
auto default_node = node["default"];
|
|
default_node["certificate"] = "default_certificate.pem";
|
|
default_node["private_key"] = "default_privatekey.pem";
|
|
};
|
|
|
|
weak_ptr<EntryBinding> _binding = binding;
|
|
binding->read_config = [_binding](YAML::Node& node) {
|
|
auto b = _binding.lock();
|
|
if(!b) return;
|
|
config::web::ssl::certificates.clear();
|
|
|
|
if(!node.IsDefined() || node.IsNull())
|
|
return;
|
|
|
|
for(auto it = node.begin(); it != node.end(); it++) {
|
|
auto node_cert = it->second["certificate"];
|
|
auto node_key = it->second["private_key"];
|
|
if(!node_cert.IsDefined()) {
|
|
logError(LOG_GENERAL, "Failed to parse web certificate. Missing \"certificate\" key.");
|
|
continue;
|
|
}
|
|
if(!node_key.IsDefined()) {
|
|
logError(LOG_GENERAL, "Failed to parse web certificate. Missing \"private_key\" key.");
|
|
continue;
|
|
}
|
|
|
|
config::web::ssl::certificates.emplace_back(it->first.as<string>(), node_key.as<string>(), node_cert.as<string>());
|
|
}
|
|
};
|
|
}
|
|
/*
|
|
{
|
|
CREATE_BINDING("certificate", 0);
|
|
BIND_STRING(config::web::ssl::certFile, "certs/default_certificate.pem");
|
|
ADD_DESCRIPTION("The SSL certificate for the web client");
|
|
}
|
|
{
|
|
CREATE_BINDING("privatekey", 0);
|
|
BIND_STRING(config::web::ssl::keyFile, "certs/default_privatekey.pem");
|
|
ADD_DESCRIPTION("The SSL private key for the web client (You have to export the key without a password!)");
|
|
}
|
|
*/
|
|
}
|
|
|
|
{
|
|
CREATE_BINDING("webrtc.port_min", 0);
|
|
BIND_INTEGRAL(config::web::webrtc_port_min, 50000, 0, 65535);
|
|
ADD_DESCRIPTION("Define the port range within the web client and TeaClient operates in");
|
|
ADD_DESCRIPTION("A port of zero stands for no limit");
|
|
ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)");
|
|
}
|
|
{
|
|
CREATE_BINDING("webrtc.port_max", 0);
|
|
BIND_INTEGRAL(config::web::webrtc_port_max, 56000, 0, 65535);
|
|
ADD_DESCRIPTION("Define the port range within the web client and TeaClient operates in");
|
|
ADD_DESCRIPTION("A port of zero stands for no limit");
|
|
ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)");
|
|
}
|
|
{
|
|
CREATE_BINDING("webrtc.stun.enabled", 0);
|
|
BIND_INTEGRAL(config::web::stun_enabled, false, false, true);
|
|
ADD_DESCRIPTION("Whatever to use a STUN server");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("webrtc.stun.host", 0);
|
|
BIND_STRING(config::web::stun_host, "stun.l.google.com");
|
|
ADD_DESCRIPTION("Stun hostname");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("webrtc.stun.port", 0);
|
|
BIND_INTEGRAL(config::web::stun_port, 19302, 1, 0xFFFF);
|
|
ADD_DESCRIPTION("Port of the stun server");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("webrtc.udp", 0);
|
|
BIND_INTEGRAL(config::web::udp_enabled, true, false, true);
|
|
ADD_DESCRIPTION("Enable UDP for theweb client");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("webrtc.tcp", 0);
|
|
BIND_INTEGRAL(config::web::tcp_enabled, true, false, true);
|
|
ADD_DESCRIPTION("Enable TCP for theweb client");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(geolocation);
|
|
{
|
|
CREATE_BINDING("fallback_country", FLAG_RELOADABLE);
|
|
BIND_STRING(config::geo::countryFlag, "DE");
|
|
ADD_DESCRIPTION("The fallback country if lookup fails");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("force_fallback_country", FLAG_RELOADABLE);
|
|
BIND_BOOL(config::geo::staticFlag, false);
|
|
ADD_DESCRIPTION("Enforce the default country and disable resolve");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
|
|
{
|
|
CREATE_BINDING("mapping.file", 0);
|
|
BIND_STRING(config::geo::mappingFile, "geoloc/IP2Location.CSV");
|
|
ADD_DESCRIPTION("The mapping file for the given provider");
|
|
ADD_DESCRIPTION("Default for IP2Location: geoloc/IP2Location.CSV");
|
|
ADD_DESCRIPTION("Default for Software77: geoloc/IpToCountry.csv");
|
|
}
|
|
{
|
|
CREATE_BINDING("mapping.type", 0);
|
|
BIND_INTEGRAL(config::geo::type, geoloc::PROVIDER_IP2LOCATION, geoloc::PROVIDER_MIN, geoloc::PROVIDER_MAX);
|
|
ADD_DESCRIPTION("The IP 2 location resolver");
|
|
ADD_DESCRIPTION("0 = IP2Location");
|
|
ADD_DESCRIPTION("1 = Software77");
|
|
}
|
|
{
|
|
BIND_GROUP(vpn);
|
|
{
|
|
CREATE_BINDING("file", 0);
|
|
BIND_STRING(config::geo::vpn_file, "geoloc/ipcat.csv");
|
|
ADD_DESCRIPTION("The mapping file for vpn checker (https://github.com/client9/ipcat/blob/master/datacenters.csv)");
|
|
}
|
|
{
|
|
CREATE_BINDING("enabled", 0);
|
|
BIND_BOOL(config::geo::vpn_block,false);
|
|
ADD_DESCRIPTION("Disable/enable the vpn detection");
|
|
}
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(music)
|
|
{
|
|
CREATE_BINDING("enabled", 0);
|
|
BIND_BOOL(config::music::enabled, true);
|
|
ADD_DESCRIPTION("Enable/disable the music bots");
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(messages);
|
|
{
|
|
CREATE_BINDING("voice.server_stop", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::serverStopped, "Server stopped");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("application.stop", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::applicationStopped, "Application stopped");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("application.crash", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::applicationCrashed, "Application crashed");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("idle_time", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::idle_time_exceeded, "Idle time exceeded");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("teamspeak_permission_editor", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::teamspeak_permission_editor, "\n[b][COLOR=#aa0000]ATTENTION[/COLOR][/b]:\nIt seems like you're trying to edit the TeaSpeak permissions with the TeamSpeak 3 client!\nThis is [b]really[/b] buggy due a bug within the client you're using.\n\nWe recommand to [b]use the [url=https://web.teaspeak.de/]TeaSpeak-Web[/url][/b] client or the [b][url=https://teaspeak.de/]TeaSpeak client[/url][/b].\nYatQA is a good option as well.\n\nTo disable/edit this message please edit the config.yml\nPlease note: Permission bugs, which will be reported wound be accepted.");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
BIND_GROUP(mute);
|
|
{
|
|
CREATE_BINDING("mute_message", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::mute_notify_message, "Hey!\nI muted you!");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("unmute_message", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::unmute_notify_message, "Hey!\nI unmuted you!");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(kick_invalid);
|
|
{
|
|
CREATE_BINDING("hardware_id", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::kick_invalid_hardware_id, "Invalid hardware id. Protocol hacked?");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("command", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::kick_invalid_hardware_id, "Invalid command. Protocol hacked?");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("badges", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::kick_invalid_hardware_id, "Invalid badges. Protocol hacked?");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
}
|
|
{
|
|
CREATE_BINDING("vpn.kick", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::kick_vpn, "Please disable your VPN! (Provider: ${provider.name})");
|
|
ADD_DESCRIPTION("This is the kick/ban message when a client tries to connect with a vpn");
|
|
|
|
ADD_DESCRIPTION("Variables are enabled. Available:");
|
|
ADD_DESCRIPTION(" - provider.name => Contains the provider of the ip which has been flaged as vps");
|
|
ADD_DESCRIPTION(" - provider.website => Contains the website provider of the ip which has been flaged as vps");
|
|
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
BIND_GROUP(shutdown);
|
|
{
|
|
CREATE_BINDING("scheduled", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::shutdown::scheduled, "[b][color=#DA9100]Scheduled shutdown at ${time}(%Y-%m-%d %H:%M:%S)[/color][/b]");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("interval", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::shutdown::interval, "[b][color=red]Server instance shutting down in ${interval}[/color][/b]");
|
|
ADD_DESCRIPTION("${interval} is defined via map in 'intervals'");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("intervals", 0);
|
|
binding->default_value = []() { return deque<string>{}; };
|
|
binding->type = 4;
|
|
|
|
weak_ptr weak_binding = binding;
|
|
binding->read_config = [weak_binding](YAML::Node& node) {
|
|
auto bind = weak_binding.lock();
|
|
if(!bind) return;
|
|
|
|
if(node.IsNull() || !node.IsDefined() || !node.IsMap())
|
|
bind->set_default(node);
|
|
|
|
try {
|
|
auto intervals = node.as<map<size_t, string>>();
|
|
for(const auto& pair : intervals)
|
|
config::messages::shutdown::intervals.push_back({seconds(pair.first), pair.second});
|
|
} catch(YAML::Exception& ex) {
|
|
logError(LOG_GENERAL, "Failed to parse shutdown intervals! Exception: {}", ex.what());
|
|
}
|
|
};
|
|
|
|
binding->set_default = [weak_binding](YAML::Node& node) {
|
|
auto bind = weak_binding.lock();
|
|
if(!bind) return;
|
|
|
|
node = YAML::Node();
|
|
|
|
const static vector<pair<size_t, string>> intervals = {
|
|
{1, "1 second"},
|
|
{2, "2 seconds"},
|
|
{3, "3 seconds"},
|
|
{4, "4 seconds"},
|
|
{5, "5 seconds"},
|
|
{10, "10 seconds"},
|
|
{20, "20 seconds"},
|
|
{30, "30 seconds"},
|
|
{60, "1 minute"},
|
|
{2 * 60, "2 minutes"},
|
|
{3 * 60, "3 minutes"},
|
|
{5 * 60, "5 minutes"},
|
|
{10 * 60, "10 minutes"},
|
|
{20 * 60, "20 minutes"},
|
|
{30 * 60, "30 minutes"},
|
|
{60 * 60, "1 hour"},
|
|
{2 * 60 * 60, "2 hours"},
|
|
};
|
|
|
|
for(const auto& pair : intervals)
|
|
node[to_string(pair.first)] = pair.second;
|
|
};
|
|
ADD_DESCRIPTION("Add or delete intervals as you want");
|
|
}
|
|
{
|
|
CREATE_BINDING("now", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::shutdown::now, "[b][color=red]Server instance shutting down in now[/color][/b]");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("canceled", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::shutdown::canceled, "[b][color=green]Scheduled instance shutdown canceled![/color][/b]");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(music);
|
|
{
|
|
CREATE_BINDING("song_announcement", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::music::song_announcement, "Now replaying ${title} (${url}) added by ${invoker}");
|
|
ADD_DESCRIPTION("${title} title of the song");
|
|
ADD_DESCRIPTION("${description} description of the song");
|
|
ADD_DESCRIPTION("${url} url of the song");
|
|
ADD_DESCRIPTION("${invoker} link to the song adder");
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(timeout);
|
|
{
|
|
CREATE_BINDING("connection_reinitialized", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::timeout::connection_reinitialized, "Connection lost");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
{
|
|
CREATE_BINDING("packet_resend_failed", FLAG_RELOADABLE);
|
|
BIND_STRING(config::messages::timeout::packet_resend_failed, "Packet resend failed");
|
|
ADD_NOTE_RELOADABLE();
|
|
}
|
|
}
|
|
}
|
|
{
|
|
BIND_GROUP(threads);
|
|
{
|
|
CREATE_BINDING("ticking", 0);
|
|
BIND_INTEGRAL(config::threads::ticking, 2, 1, 128);
|
|
ADD_DESCRIPTION("Thread pool size for the ticking task of a VirtualServer");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
BIND_GROUP(music);
|
|
{
|
|
CREATE_BINDING("execute_limit", 0);
|
|
BIND_INTEGRAL(config::threads::music::execute_limit, 5, 1, 1024);
|
|
ADD_DESCRIPTION("Max number of threads for command handling on the instance");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
CREATE_BINDING("execute_per_bot", 0);
|
|
BIND_INTEGRAL(config::threads::music::execute_per_bot, 1, 1, 128);
|
|
ADD_DESCRIPTION("Threads per server for command executing");
|
|
ADD_SENSITIVE();
|
|
}
|
|
}
|
|
{
|
|
CREATE_BINDING("web.io_loops", 0);
|
|
BIND_INTEGRAL(config::threads::web::io_loops, 2, 1, 128);
|
|
ADD_DESCRIPTION("Thread pool size for the ticking task of a VirtualServer");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
BIND_GROUP(voice)
|
|
{
|
|
CREATE_BINDING("events_per_server", 0);
|
|
BIND_INTEGRAL(config::threads::voice::events_per_server, 2, 1, 16);
|
|
ADD_DESCRIPTION("Kernel events per server");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
CREATE_BINDING("execute_per_server", 0);
|
|
BIND_INTEGRAL(config::threads::voice::execute_per_server, 2, 1, 128);
|
|
ADD_DESCRIPTION("Threads per server for command executing");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
CREATE_BINDING("execute_limit", 0);
|
|
BIND_INTEGRAL(config::threads::voice::execute_limit, 5, 1, 1024);
|
|
ADD_DESCRIPTION("Max number of threads for command handling threads within the instance");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
CREATE_BINDING("io_min", 0);
|
|
BIND_INTEGRAL(config::threads::voice::io_min, 2, 1, 1024);
|
|
ADD_DESCRIPTION("Minimum IO threads");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
CREATE_BINDING("io_per_server", 0);
|
|
BIND_INTEGRAL(config::threads::voice::io_per_server, 2, 1, 64);
|
|
ADD_DESCRIPTION("IO Thread increase per server");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
CREATE_BINDING("io_limit", 0);
|
|
BIND_INTEGRAL(config::threads::voice::io_limit, 5, 1, 1024);
|
|
ADD_DESCRIPTION("Max IO threads");
|
|
ADD_SENSITIVE();
|
|
}
|
|
{
|
|
CREATE_BINDING("bind_io_thread_to_kernel_thread", 0);
|
|
BIND_BOOL(config::threads::voice::bind_io_thread_to_kernel_thread, false);
|
|
ADD_DESCRIPTION("Bind each IO thread to one kernel thread to improve socket IO performance");
|
|
ADD_SENSITIVE();
|
|
}
|
|
}
|
|
}
|
|
return _create_bindings = result;
|
|
}
|
|
|
|
|
|
inline string apply_comments(stringstream &in, map<string, deque<string>>& comments) {
|
|
stringstream out;
|
|
stringstream lineBuffer;
|
|
|
|
vector<string> tree;
|
|
vector<int> deepness;
|
|
|
|
char read;
|
|
|
|
//The header
|
|
for(const auto& comment : comments["header"])
|
|
out << "#" << escapeHeaderString(comment) << endl;
|
|
|
|
while(in){
|
|
int deep = 0;
|
|
|
|
do {
|
|
read = static_cast<char>(in.get());
|
|
if(read != ' ' && read != '\t') break;
|
|
lineBuffer << read;
|
|
deep++;
|
|
} while(in);
|
|
assert(read != ' ' && read != '\t');
|
|
|
|
stringstream keyStream;
|
|
stringstream pathStream;
|
|
std::string key;
|
|
std::string path;
|
|
|
|
if(read == '-') { //We have a list entry
|
|
lineBuffer << read;
|
|
goto writeUntilNewLine;
|
|
}
|
|
|
|
do {
|
|
lineBuffer << read;
|
|
if(read == ':') break;
|
|
keyStream << read;
|
|
read = static_cast<char>(in.get());
|
|
} while(in);
|
|
assert(read == ':');
|
|
|
|
key = keyStream.str();
|
|
while (!deepness.empty() && deep <= deepness.back()) {
|
|
deepness.pop_back();
|
|
tree.pop_back();
|
|
}
|
|
deepness.push_back(deep);
|
|
tree.push_back(key);
|
|
|
|
for(const auto& entry : tree)
|
|
pathStream << "." << entry;
|
|
path = pathStream.str().substr(1);
|
|
//cout << "having key " << key << " at deep " << deep << " - " << deepness.size() << " - " << path << endl;
|
|
for(const auto& comment : comments[path]){
|
|
for(int index = 0; index < deepness.back(); index++)
|
|
out << " ";
|
|
out << "#" << escapeHeaderString(comment) << endl;
|
|
}
|
|
writeUntilNewLine:
|
|
out << lineBuffer.str();
|
|
lineBuffer = stringstream();
|
|
|
|
do {
|
|
read = static_cast<char>(in.get());
|
|
if(!in) break; //End of lstream reached :D
|
|
out << read;
|
|
} while(in && read != '\n');
|
|
}
|
|
assert(lineBuffer.str().empty());
|
|
|
|
//The footer
|
|
for(const auto& comment : comments["footer"])
|
|
out << "#" << escapeJsonString(comment) << endl;
|
|
|
|
return out.str();
|
|
} |