#include #include #include #include #include #include #include #include #include "src/Configuration.h" #include "src/VirtualServer.h" #include "src/InstanceHandler.h" #include "src/server/QueryServer.h" #include "src/terminal/CommandHandler.h" #include "src/client/InternalClient.h" #include "src/SignalHandler.h" #include "src/build.h" using namespace std; using namespace std::chrono; #define BUILD_CREATE_TABLE(tblName, types) "CREATE TABLE IF NOT EXISTS `" tblName "` (" types ")" #define CREATE_TABLE(table, types) \ result = sql::command(sqlData, BUILD_CREATE_TABLE(table, types)).execute();\ if(!result){\ logger::logger(0)->critical("Could not setup sql tables. Command '{}' returns {}", BUILD_CREATE_TABLE(table, types), result.fmtStr());\ goto stopApp;\ } bool mainThreadActive = true; bool mainThreadDone = false; ts::server::InstanceHandler* serverInstance = nullptr; extern void testTomMath(); #ifndef FUCK_CLION #define DB_NAME_BEG "TeaData" #define DB_NAME_END ".sqlite" #define DB_NAME DB_NAME_BEG DB_NAME_END #else #define DB_NAME "TeaData.sqlite" #endif #include #include #include "src/client/music/internal_provider/channel_replay/ChannelProvider.h" class CLIParser { public: CLIParser (int &argc, char **argv){ for (int i = 1; i < argc; i++) this->tokens.emplace_back(argv[i]); } std::deque getCmdOptions(const std::string &option) const { std::deque result; auto itr = this->tokens.begin(); while(true) { itr = std::find(itr, this->tokens.end(), option); if (itr != this->tokens.end() && ++itr != this->tokens.end()){ result.push_back(*itr); itr++; } else break; } return result; } std::deque getCmdOptionsBegins(const std::string &option) const { std::deque result; for(const auto& token : this->tokens) if(token.find(option) == 0) result.push_back(token); return result; } const std::string& get_option(const std::string &option) const { auto itr = std::find(this->tokens.begin(), this->tokens.end(), option); if (itr != this->tokens.end() && ++itr != this->tokens.end()){ return *itr; } static const std::string empty_string; return empty_string; } bool cmdOptionExists(const std::string &option) const{ return std::find(this->tokens.begin(), this->tokens.end(), option) != this->tokens.end(); } private: std::vector tokens; }; /* addr is where the exception identifier is stored id is the exception identifier. */ void __raise_exception (void **addr, void *id); #define T(address) \ std::cout << "Testing: " << address << " => "; \ {\ sockaddr_storage storage;\ net::resolve_address(address, storage);\ std::cout << manager.contains(storage) << std::endl;\ } #define CONFIG_NAME "config.yml" const char *malloc_conf = ""; //retain:false"; //,dirty_decay_ms:0"; int main(int argc, char** argv) { #ifdef HAVE_JEMALLOC (void*) malloc_conf; #endif #if 0 { //ts::property::list() std::cout << "| Name | Type | Flags | Default Value | Description | \n"; std::cout << "|:-- | -- | -- | -- |:-- | \n"; for(const auto& property : ts::property::list()) { std::cout << "| `" << property->name << "` | "; switch(property->type_value) { case ts::property::TYPE_STRING: std::cout << "String | "; break; case ts::property::TYPE_BOOL: std::cout << "Boolean | "; break; case ts::property::TYPE_SIGNED_NUMBER: std::cout << "Signed number | "; break; case ts::property::TYPE_UNSIGNED_NUMBER: std::cout << "Unsigned number | "; break; case ts::property::TYPE_FLOAT: std::cout << "Float | "; break; default: std::cout << "Unknown | "; break; } std::string flags{}; if(property->flags & ts::property::FLAG_INTERNAL) { flags += ", internal"; } if(property->flags & ts::property::FLAG_GLOBAL) { flags += ", global"; } if(property->flags & ts::property::FLAG_SNAPSHOT) { flags += ", snapshot"; } if(property->flags & ts::property::FLAG_SAVE) { flags += ", saved"; } if(property->flags & ts::property::FLAG_SAVE_MUSIC) { flags += ", saved (music)"; } if(property->flags & ts::property::FLAG_NEW) { flags += ", new"; } if(property->flags & ts::property::FLAG_SERVER_VARIABLE) { flags += ", server variable"; } if(property->flags & ts::property::FLAG_SERVER_VIEW) { flags += ", server view variable"; } if(property->flags & ts::property::FLAG_CLIENT_VARIABLE) { flags += ", client variable"; } if(property->flags & ts::property::FLAG_CLIENT_VIEW) { flags += ", client view variable"; } if(property->flags & ts::property::FLAG_CLIENT_INFO) { flags += ", client info variable"; } if(property->flags & ts::property::FLAG_CHANNEL_VARIABLE) { flags += ", channel variable"; } if(property->flags & ts::property::FLAG_CHANNEL_VIEW) { flags += ", channel view variable"; } // FLAG_GROUP_VIEW = FLAG_CHANNEL_VIEW << 1UL, if(property->flags & ts::property::FLAG_INSTANCE_VARIABLE) { flags += ", instance variable"; } if(property->flags & ts::property::FLAG_PLAYLIST_VARIABLE) { flags += ", playlist variable"; } if(property->flags & ts::property::FLAG_USER_EDITABLE) { flags += ", editable"; } if(!flags.empty()) { std::cout << flags.substr(2); } std::cout << "| "; if(property->default_value.empty()) { std::cout << "empty"; } else { std::cout << "`" << property->default_value << "` "; } std::cout << "| "; std::cout << "No description "; std::cout << "| "; std::cout << " \n"; } return 0; } #endif CLIParser arguments(argc, argv); SSL_load_error_strings(); OpenSSL_add_ssl_algorithms(); ts::permission::setup_permission_resolve(); { auto evthread_use_pthreads_result = evthread_use_pthreads(); assert(evthread_use_pthreads_result == 0); (void) evthread_use_pthreads_result; } if(!arguments.cmdOptionExists("--no-terminal")) { terminal::install(); if(!terminal::active()) { cerr << "could not setup terminal!" << endl; return -1; } } if(arguments.cmdOptionExists("--help") || arguments.cmdOptionExists("-h")) { #define HELP_FMT " {} {} | {}" logMessageFmt(true, LOG_GENERAL, "Available command line parameters:"); logMessageFmt(true, LOG_GENERAL, HELP_FMT, "-h", "--help", "Shows this page"); logMessageFmt(true, LOG_GENERAL, HELP_FMT, "-q", "--set_query_password", "Changed the server admin query password"); logMessageFmt(true, LOG_GENERAL, HELP_FMT, "-P=", "--property:=", "Override a config value manual"); logMessageFmt(true, LOG_GENERAL, HELP_FMT, "-l", "--property-list", "List all available properties"); terminal::uninstall(); return 0; } if(arguments.cmdOptionExists("--property-list") || arguments.cmdOptionExists("-l")) { logMessageFmt(true, LOG_GENERAL, "Available properties:"); auto properties = ts::config::create_bindings(); for(const auto& property : properties) { logMessageFmt(true, LOG_GENERAL, " " + property->key); for(const auto& entry : property->description) { if(entry.first.empty()) { for(const auto& line : entry.second) logMessageFmt(true, LOG_GENERAL, " " + line); } else { logMessageFmt(true, LOG_GENERAL, " " + entry.first + ":"); for(const auto& line : entry.second) logMessageFmt(true, LOG_GENERAL, " " + line); } } logMessageFmt(true, LOG_GENERAL, " " + property->value_description()); } return 0; } if(!arguments.cmdOptionExists("--valgrind")) { ts::syssignal::setup(); } ts::syssignal::setup_threads(); map override_settings; { auto short_override = arguments.getCmdOptionsBegins("-P"); for(const auto& entry : short_override) { if(entry.length() < 2) continue; auto ei = entry.find('='); if(ei == string::npos || ei == 2) { logErrorFmt(true, LOG_GENERAL, "Invalid command line parameter. (\"" + entry + "\")"); return 1; } auto key = entry.substr(2, ei - 2); auto value = entry.substr(ei + 1); override_settings[key] = value; } } { auto short_override = arguments.getCmdOptionsBegins("--property:"); for(const auto& entry : short_override) { if(entry.length() < 11) continue; auto ei = entry.find('='); if(ei == string::npos || ei == 11) { logErrorFmt(true, LOG_GENERAL, "Invalid command line parameter. (\"" + entry + "\")"); return 1; } auto key = entry.substr(11, ei - 11); auto value = entry.substr(ei + 1); override_settings[key] = value; } } { auto bindings = ts::config::create_bindings(); for(const auto& setting : bindings) { for(auto it = override_settings.begin(); it != override_settings.end(); it++) { if(it->first == setting->key) { try { setting->read_argument(it->second); } catch(const std::exception& ex) { logErrorFmt(true, LOG_GENERAL, "Failed to apply value for given property '" + it->first + "': " + ex.what()); } override_settings.erase(it); break; } } } for(const auto& entry : override_settings) { logMessageFmt(true, LOG_GENERAL, "Missing property " + entry.first + ". Value unused!"); } } /* std::string error; if(!interaction::waitForAttach(error)){ cerr << "Rsult: " << error << endl; } while(interaction::memoryInfo()){ usleep(1 * 1000 * 1000); logMessage("Current instances: " + to_string(interaction::memoryInfo()->instanceCount) + "/" + to_string(interaction::memoryInfo()->maxInstances)); } interaction::removeMemoryHook(); if(true) return 0; */ { //http://git.mcgalaxy.de/WolverinDEV/tomcrypt/blob/develop/src/misc/crypt/crypt_inits.c#L40-86 std::string descriptors = "LTGE"; bool crypt_init = false; for(const auto& c : descriptors) if((crypt_init |= crypt_mp_init(&c))) break; if(!crypt_init) { logCritical(LOG_GENERAL, "Could not initialise libtomcrypt mp descriptors!"); return 1; } if(register_prng(&sprng_desc) == -1) { logCritical(LOG_GENERAL, "could not setup prng"); return EXIT_FAILURE; } if (register_cipher(&rijndael_desc) == -1) { logCritical(LOG_GENERAL, "could not setup rijndael"); return EXIT_FAILURE; } testTomMath(); } ts::server::SqlDataManager* sql = nullptr; std::string errorMessage; shared_ptr logConfig = nullptr; std::string line; logMessageFmt(true, LOG_GENERAL, "Loading configuration"); auto cfgErrors = ts::config::parseConfig(CONFIG_NAME); if(!cfgErrors.empty()){ logErrorFmt(true, LOG_GENERAL, "Could not load configuration. Errors: (" + to_string(cfgErrors.size()) + ")"); for(const auto& entry : cfgErrors) logErrorFmt(true, LOG_GENERAL, " - {}", entry); logErrorFmt(true, LOG_GENERAL, "Stopping server..."); goto stopApp; } logMessage(LOG_GENERAL, "Setting up logging"); logConfig = make_shared(); logConfig->logfileLevel = (spdlog::level::level_enum) ts::config::log::logfileLevel; logConfig->terminalLevel = (spdlog::level::level_enum) ts::config::log::terminalLevel; logConfig->file_colored = ts::config::log::logfileColored; logConfig->logPath = ts::config::log::path; logConfig->vs_group_size = ts::config::log::vs_size; logConfig->sync = !terminal::instance(); logger::setup(logConfig); threads::timer::function_log = [](const std::string& message, bool debug) { auto msg = message.find('\n') == std::string::npos ? message : message.substr(0, message.find('\n')); if(debug) debugMessage(LOG_GENERAL, msg); else logWarning(LOG_GENERAL, msg); }; logger::updateLogLevels(); if(ts::config::license_original && ts::config::license_original->data.type != license::LicenseType::DEMO){ logMessageFmt(true, LOG_GENERAL, strobf("[]---------------------------------------------------------[]").string()); logMessageFmt(true, LOG_GENERAL, strobf(" §aThank you for buying the TeaSpeak-§lPremium-§aSoftware! ").string()); logMessageFmt(true, LOG_GENERAL, strobf(" §aLicense information:").string()); logMessageFmt(true, LOG_GENERAL, strobf(" §aLicense owner : §e").string() + ts::config::license_original->owner()); logMessageFmt(true, LOG_GENERAL, strobf(" §aLicense type : §e").string() + license::LicenseTypeNames[ts::config::license_original->data.type]); if(ts::config::license_original->end().time_since_epoch().count() == 0){ logMessageFmt(true, LOG_GENERAL, strobf(" §aLicense expires: §enever").string()); } else { char timeBuffer[32]; time_t t = duration_cast(ts::config::license_original->end().time_since_epoch()).count(); tm* stime = localtime(&t); strftime(timeBuffer, 32, "%c", stime); logMessageFmt(true, LOG_GENERAL, strobf(" §aLicense expires: §e").string() + string(timeBuffer)); } logMessageFmt(true, LOG_GENERAL, string() + strobf(" §aLicense valid : ").string() + (ts::config::license_original->isValid() ? strobf("§ayes").string() : strobf("§cno").string())); logMessageFmt(true, LOG_GENERAL, strobf("[]---------------------------------------------------------[]").string()); } { rlimit rlimit{0, 0}; //forum.teaspeak.de/index.php?threads/2570/ constexpr auto seek_help_message = "Fore more help visit the forum and read this thread (https://forum.teaspeak.de/index.php?threads/2570/)."; if(getrlimit(RLIMIT_NOFILE, &rlimit) != 0) { //prlimit -n4096 -p pid_of_process logWarningFmt(true, LOG_INSTANCE, "Failed to get open file rlimit ({}). Please ensure its over 16384.", strerror(errno)); logWarningFmt(true, LOG_INSTANCE, seek_help_message); } else { const auto original = rlimit.rlim_cur; rlimit.rlim_cur = std::max(rlimit.rlim_cur, std::min(rlimit.rlim_max, (rlim_t) 16384)); if(original != rlimit.rlim_cur) { if(setrlimit(RLIMIT_NOFILE, &rlimit) != 0) { logErrorFmt(true, LOG_INSTANCE, "Failed to set open file rlimit to {} ({}). Please ensure its over 16384.", rlimit.rlim_cur, strerror(errno)); logWarningFmt(true, LOG_INSTANCE, seek_help_message); goto rlimit_updates; } } if(rlimit.rlim_cur < 16384) { logWarningFmt(true, LOG_INSTANCE, "Open file rlimit is bellow 16384 ({}). Please increase the system file descriptor limits.", rlimit.rlim_cur); logWarningFmt(true, LOG_INSTANCE, seek_help_message); } } rlimit_updates:; } logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true)); logMessage(LOG_GENERAL, "Starting music providers"); if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§aloading music§7]"); if(ts::config::music::enabled && !arguments.cmdOptionExists("--no-providers")) { ::music::manager::loadProviders("providers"); ::music::manager::register_provider(::music::provider::ChannelProvider::create_provider()); } if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§aloading geoloc§7]"); if(!ts::config::geo::staticFlag) { if(ts::config::geo::type == geoloc::PROVIDER_SOFTWARE77) geoloc::provider = new geoloc::Software77Provider(ts::config::geo::mappingFile); else if(ts::config::geo::type == geoloc::PROVIDER_IP2LOCATION) geoloc::provider = new geoloc::IP2LocationProvider(ts::config::geo::mappingFile); else { logCritical(LOG_GENERAL,"Invalid geo resolver type!"); } if(geoloc::provider && !geoloc::provider->load(errorMessage)) { logCritical(LOG_GENERAL,"Could not setup geoloc! Fallback to default flag!"); logCritical(LOG_GENERAL,"Message: {}", errorMessage); geoloc::provider = nullptr; errorMessage = ""; } } if(ts::config::geo::vpn_block) { geoloc::provider_vpn = new geoloc::IPCatBlocker(ts::config::geo::vpn_file); if(geoloc::provider_vpn && !geoloc::provider_vpn->load(errorMessage)) { logCritical(LOG_GENERAL,"Could not setup vpn detector!"); logCritical(LOG_GENERAL,"Message: {}", errorMessage); geoloc::provider_vpn = nullptr; errorMessage = ""; } } if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§aloading sql§7]"); sql = new ts::server::SqlDataManager(); if(!sql->initialize(errorMessage)) { logCriticalFmt(true, LOG_GENERAL, "Could not initialize SQL!"); if(errorMessage.find("database is locked") != string::npos) { logCriticalFmt(true, LOG_GENERAL, "----------------------------[ ATTENTION ]----------------------------"); logCriticalFmt(true, LOG_GENERAL, "{:^69}", "You're database is already in use!"); logCriticalFmt(true, LOG_GENERAL, "{:^69}", "Stop the other instance first!"); logCriticalFmt(true, LOG_GENERAL, "----------------------------[ ATTENTION ]----------------------------"); } else { logCriticalFmt(true, LOG_GENERAL, errorMessage); } goto stopApp; } if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§astarting instance§7]"); serverInstance = new ts::server::InstanceHandler(sql); //if error than mainThreadActive = false if(!mainThreadActive || !serverInstance->startInstance()) goto stopApp; if(arguments.cmdOptionExists("-q") || arguments.cmdOptionExists("--set_query_password")) { auto password = arguments.cmdOptionExists("-q") ? arguments.get_option("-q") : arguments.get_option("--set_query_password"); if(!password.empty()) { logMessageFmt(true, LOG_GENERAL, "Updating server admin query password to \"{}\"", password); auto account = serverInstance->getQueryServer()->find_query_account_by_name("serveradmin"); if(!account) { logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!"); } else { if(!serverInstance->getQueryServer()->change_query_password(account, password)) { logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! (Internal error)"); } } } } terminal::initialize_pipe(arguments.get_option("--pipe-path")); if(terminal::instance()) terminal::instance()->setPrompt("§7> §f"); while(mainThreadActive) { usleep(5 * 1000); if(terminal::instance()) { if(terminal::instance()->linesAvailable() > 0){ while(!(line = terminal::instance()->readLine("§7> §f")).empty()) threads::Thread(THREAD_DETACHED, [line]{ terminal::chandler::CommandHandle handle{}; handle.command = line; if(!terminal::chandler::handleCommand(handle)) { for(const auto& response : handle.response) logErrorFmt(true, LOG_GENERAL, "{}", response); } else { for(const auto& response : handle.response) logMessageFmt(true, LOG_GENERAL, "{}", response); } }); } } } terminal::finalize_pipe(); stopApp: logMessageFmt(true, LOG_GENERAL, "Stopping application"); ::music::manager::finalizeProviders(); if(serverInstance) serverInstance->stopInstance(); delete serverInstance; serverInstance = nullptr; ts::music::MusicBotManager::shutdown(); if(sql) sql->finalize(); delete sql; logMessageFmt(true, LOG_GENERAL, "Application suspend successful!"); logger::uninstall(); if(terminal::active()) terminal::uninstall(); mainThreadDone = true; return 0; } /* Fix for Virtuzzo 7 where sometimes the pthread create fails! */ typedef int (*pthread_create_t)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); pthread_create_t original_pthread_create{nullptr}; int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) { if(!original_pthread_create) { original_pthread_create = (pthread_create_t) dlsym(RTLD_NEXT, "pthread_create"); if(!original_pthread_create) { std::cerr << "[CRITICAL] Missing original pthread_create function. Aborting execution!" << std::endl; std::abort(); } } int result, attempt{0}, sleep{5}; while((result = original_pthread_create(thread, attr, start_routine, arg)) != 0 && errno == EAGAIN) { if(attempt > 55) { std::cerr << "[CRITICAL] pthread_create(...) cause EAGAIN for the last 50 attempts (~4.7seconds)! Aborting application execution!" << std::endl; std::abort(); } else if(attempt > 5) { /* let some other threads do work */ pthread_yield(); } else if(attempt == 0) { std::string message{"[CRITICAL] Failed to spawn thread (Resource temporarily unavailable). Trying to recover."}; std::cerr << message << std::endl; } //std::string message{"[CRITICAL] pthread_create(...) cause EAGAIN! Trying again in " + std::to_string(sleep) + "usec (Attempt: " + std::to_string(attempt) + ")"}; //std::cerr << message << std::endl; usleep(sleep); attempt++; sleep = (int) (sleep * 1.25); } if(attempt > 0) { std::string message{"[CRITICAL] Successfully recovered from pthread_create() EAGAIN error. Took " + std::to_string(attempt) + " attempts."}; std::cerr << message << std::endl; } return result; }