#include #include #include #include #include #include #include "server/WebAPI.h" #include "server/StatisticManager.h" #include #include "server/UserManager.h" #include #include using namespace std; using namespace std::chrono; using namespace license; /* * Requests/license: SELECT `tmp`.`keyId`, `tmp`.`key`, `tmp`.`ip`, `tmp`.`count`, `license_info`.`username`, `tmp`.`type` FROM (SELECT DISTINCT `license_request`.`keyId`, `key`, `license_request`.`ip`, COUNT(`license_request`.`keyId`) AS `count`, `license`.`type` FROM `license_request` INNER JOIN `license` ON `license`.`keyId` = `license_request`.`keyId` GROUP BY (`license_request`.`ip`)) AS `tmp` INNER JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId` * Different IP's: SELECT `tmp`.`keyId`, `license_info`.`username`, COUNT(`ip`) FROM (SELECT DISTINCT `ip`, `keyId` FROM `license_request`) AS `tmp` LEFT JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId` GROUP BY (`tmp`.`keyId`) ORDER BY COUNT(`ip`) DESC * * Requests (license) / ip: SELECT DISTINCT `ip`, COUNT(`ip`) FROM `license_request` WHERE `keyId` = ? GROUP BY `ip` * * SELECT DISTINCT(`ip`), `keyId` FROM `license_request` WHERE `timestamp` > (UNIX_TIMESTAMP() - 2 * 60 * 60) * 1000 * //462 * * SELECT DISTINCT(`ip`), `license_request`.`keyId`, `license_info`.`username` FROM `license_request` LEFT JOIN `license_info` ON `license_info`.`keyId` = `license_request`.`keyId` WHERE `timestamp` > (UNIX_TIMESTAMP() - 2 * 60 * 60) * 1000 * License too many request looking: SELECT `keyId`, `username`, COUNT(`ip`) FROM `unique_license_requests` GROUP BY `keyId` ORDER BY COUNT(`ip`) DESC * * * * Online clients: SELECT SUM(`clients`) FROM (SELECT DISTINCT(`ip`), `clients` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a` * Online bots: SELECT SUM(`clients`) FROM (SELECT DISTINCT(`ip`), `clients` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a` * Online VS Server: SELECT SUM(`music`) FROM (SELECT DISTINCT(`ip`), `music` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a` * * Empty instances: curl -ik "https://stats.teaspeak.de:27788?type=request&request_type=general" -X GET 2>&1 | grep "data:" */ //SELECT SUM(`clients`) FROM `history_online` WHERE `keyId` = 701 OR `keyId` = 795 OR `keyId` = 792 OR `keyId` = 582 OR `keyId` = 753 OR `keyId` = 764 OR `keyId` = 761 OR `keyId` = 796 WHERE `timestamp` > (UNIX_TIMESTAMP() - 2.1 * 60 * 60) * 1000 /* * Extra users: * - ServerSponsoring * - Davide550 * - xDeyego? * - latters * - Pamonha * - vova3639 (5 licenses) */ bool handle_command(string& line); shared_ptr license_manager; shared_ptr statistic_manager; shared_ptr ssl_manager; shared_ptr web_server; shared_ptr license_server; shared_ptr user_manager; inline std::shared_ptr load_web_certificate() { std::string certificate_file{"web_certificate.txt"}, certificate{}; std::string key_file{"web_key.txt"}, key{}; std::string error{}; auto context = ssl_manager->initializeContext("web_shared_cert", key_file, certificate_file, error); if(!context) { logError(0, "Failed to load web certificated: {}", error); return nullptr; } std::shared_ptr bio{nullptr}; const uint8_t* mem_ptr{nullptr}; size_t length{0}; { bio = shared_ptr(BIO_new(BIO_s_mem()), ::BIO_free); if(PEM_write_bio_PrivateKey(&*bio, &*context->privateKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) { logError(0, "Failed to export certificate"); return nullptr; } #ifdef CRYPTO_BORINGSSL if(!BIO_mem_contents(&*bio_private_key, &mem_ptr, &length)) { logError(0, "Failed to receive memptr to private key"); return nullptr; } #else BUF_MEM* memory{nullptr}; if(!BIO_get_mem_ptr(&*bio, &memory) || !memory) { logError(0, "Failed to receive memptr to private key"); return nullptr; } mem_ptr = (uint8_t*) memory->data; length = memory->length; #endif key.resize(length); memcpy(key.data(), mem_ptr, length); } { bio = shared_ptr(BIO_new(BIO_s_mem()), ::BIO_free); if(PEM_write_bio_X509(&*bio, &*context->certificate) != 1) { logError(0, "Failed to export certificate"); return nullptr; } #ifdef CRYPTO_BORINGSSL if(!BIO_mem_contents(&*bio_private_key, &mem_ptr, &length)) { logError(0, "Failed to receive memptr to public key"); return nullptr; } #else BUF_MEM* memory{nullptr}; if(!BIO_get_mem_ptr(&*bio, &memory) || !memory) { logError(0, "Failed to receive memptr to public key"); return nullptr; } mem_ptr = (uint8_t*) memory->data; length = memory->length; #endif certificate.resize(length); memcpy(certificate.data(), mem_ptr, length); } auto response = std::make_shared(); response->key = key; response->certificate = certificate; response->revision = digest::sha512(response->key + response->certificate); logMessage(0, "Web certificate revision: {}", hex::hex(response->revision)); return response; } int main(int argc, char** argv) { if(argc < 2) { cerr << "Invalid arguments! Need MySQL connection" << endl; return 0; } evthread_use_pthreads(); srand(system_clock::now().time_since_epoch().count()); //terminal::install(); //if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; } auto config = std::make_shared(); config->vs_group_size = 0; config->logfileLevel = spdlog::level::trace; config->terminalLevel = spdlog::level::trace; config->logPath = "logs/log_${time}(%Y-%m-%d_%H:%M:%S).log"; config->sync = !terminal::active(); logger::setup(config); string error; sql::SqlManager* database = new sql::mysql::MySQLManager(); bool db_connected = true; ((sql::mysql::MySQLManager*) database)->listener_disconnected = [&](bool wanted){ if(wanted) return; logCritical(LOG_GENERAL, "Lost connection to MySQL server!"); logCritical(LOG_GENERAL, "Stopping server!"); db_connected = false; }; //mysql://localhost:3306/license?userName=root&password=markus logMessage(LOG_GENERAL, "Connecting to {}", argv[1]); auto connect_result = database->connect(string(argv[1])); if(!connect_result) { logError(LOG_GENERAL, "Could not connect to mysql server! (" + connect_result.fmtStr() + ")"); return 0; } #if false sql::command(database, "INSERT INTO license (`key`, type, deleted, issuer) VALUES ('0020', 1, 1, 'Test'); ").execute(); cout << sql::command(database, "SELECT LAST_INSERT_ID();").query([](void*, int length, string* values, string* names) { for(int i = 0; i < length; i++) cout << names[i] << " -> " << values[i] << endl; return 0; }, (void*) nullptr) << endl; #endif license_manager = make_shared(database); if(!license_manager->setup(error)) { logError(LOG_GENERAL, "Could not start license manager! (" +error + ")"); return 0; } statistic_manager = make_shared(license_manager); #if false /* { auto _now = system_clock::now(); auto statistics = license_manager->list_statistics_user(_now - hours(24) * 32 * 4, _now, duration_cast(hours(2))); cout << "Date,Instances,Servers,Clients,Web Clients,Queries,Music Bots" << endl; for(const auto& entry : statistics) { auto time = system_clock::to_time_t(entry->timestamp); tm* localtm = localtime(&time); string string_time = asctime(localtm); string_time = string_time.substr(0, string_time.length() - 1); //cout << "[" << string_time << "] Users online: " << entry->clients_online << " | Instances: " << entry->instance_online << endl; cout << string_time << "," << entry->instance_online << "," << entry->servers_online << "," << entry->clients_online << "," << entry->web_clients_online << "," << entry->queries_online << "," << entry->bots_online << endl; } } return 0; */ #endif #if false /* { ofstream _file_out("version_history.txt"); auto _now = system_clock::now(); cout << "Getting statistics" << endl; auto statistics = license_manager->list_statistics_version(_now - hours(24) * 32 * 12, _now, duration_cast(hours(1))); cout << "Grouping statistics" << endl; std::deque versions; const auto version_name = [](const std::string& key) { auto space = key.find(' '); return key.substr(0, space); }; for(const auto& entry : statistics) { for(const auto& version : entry->versions) { const auto name = version_name(version.first); if(name.empty()) { continue; } auto it = find(versions.begin(), versions.end(), name); if(it == versions.end()) versions.push_back(name); } } cout << "Sorting statistics" << endl; sort(versions.begin(), versions.end(), [](const std::string& a, const std::string& b) { const auto index_a = a.find_last_of('.'); const auto index_b = b.find_last_of('.'); const auto length_a = a.find('-', index_a) - index_a - 1; const auto length_b = b.find('-', index_b) - index_b - 1; const auto patch_a = stoll(a.substr(index_a + 1, length_a)); const auto patch_b = stoll(b.substr(index_b + 1, length_b)); return patch_a > patch_b; }); cout << "Writing statistics" << endl; _file_out << "Date"; for(auto & version : versions) _file_out << "," << version; _file_out << endl; for(const auto& entry : statistics) { auto time = system_clock::to_time_t(entry->timestamp); tm* localtm = localtime(&time); string string_time = asctime(localtm); string_time = string_time.substr(0, string_time.length() - 1); map version_count; for(const auto& version : entry->versions) { const auto name = version_name(version.first); version_count[name] += version.second; } _file_out << string_time; for(const auto& name : versions) { _file_out << "," << version_count[name]; } _file_out << endl; }s cout << "Done statistics" << endl; _file_out.flush(); } return 0; */ #endif ssl_manager = make_shared(); { string key_file = "certificates/web_stats_prv.pem"; string cert_file = "certificates/web_stats_crt.pem"; if(!ssl_manager->initializeContext("web_stats", key_file, cert_file, error, false, make_shared(ts::ssl::SSLGenerator{ .subjects = {}, .issues = {{"O", "TeaSpeak"}, {"OU", "License server"}, {"creator", "WolverinDEV"}} }))) { logCritical(LOG_LICENSE_WEB, "Failed to initialize ssl certificate! Stopping server."); } } { web_server = make_shared(license_manager, statistic_manager); logMessage(LOG_GENERAL, "Starting web server on [:::]:27788"); if(!web_server->start(error, 27788, ssl_manager->getContext("web_stats"))) { logError(LOG_GENERAL, "Failed to start web statistics server!"); return 0; } } { user_manager = make_shared(database); } { logMessage(LOG_GENERAL, "Starting license server on [:::]:27786"); struct sockaddr_in listen_addr{}; memset(&listen_addr, 0, sizeof(listen_addr)); listen_addr.sin_family = AF_INET; listen_addr.sin_addr.s_addr = INADDR_ANY; listen_addr.sin_port = htons(27786); license_server = make_shared(listen_addr, license_manager, statistic_manager, web_server, user_manager); license_server->web_certificate = load_web_certificate(); license_server->start(); } while(db_connected && web_server->running() && license_server->isRunning()) { if(!terminal::instance()) { std::this_thread::sleep_for(std::chrono::seconds{10}); continue; } auto line = terminal::instance()->readLine("§a> §f"); if(line.empty()){ usleep(500); continue; } if(!handle_command(line)) break; } terminal::instance()->writeMessage("§aStopping server..."); web_server->stop(); license_server->stop(); if(database) database->disconnect(); logger::uninstall(); terminal::uninstall(); return 0; } bool handle_command(string& line) { if(line == "end" || line == "stop") return false; //Exit loop if(line == "info web") { logMessage(LOG_LICENSE_WEB, "Currently online clients:"); auto clients = web_server->get_clients(); for(const auto& client : clients) logMessage(LOG_LICENSE_WEB, " - {}", client->client_prefix()); logMessage(LOG_LICENSE_WEB, " {} clients are currently connected!", clients.size()); return true; } logError(LOG_GENERAL, "Invalid command: " + line); return true; }