352 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			352 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include <iostream>
 | 
						|
#include <openssl/bio.h>
 | 
						|
#include <server/LicenseServer.h>
 | 
						|
#include <log/LogUtils.h>
 | 
						|
#include <CXXTerminal/Terminal.h>
 | 
						|
#include <sql/mysql/MySQL.h>
 | 
						|
#include "server/WebAPI.h"
 | 
						|
#include "server/StatisticManager.h"
 | 
						|
#include <event2/thread.h>
 | 
						|
#include "server/UserManager.h"
 | 
						|
 | 
						|
#include <misc/digest.h>
 | 
						|
#include <misc/hex.h>
 | 
						|
 | 
						|
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<server::database::DatabaseHandler> license_manager;
 | 
						|
shared_ptr<stats::StatisticManager> statistic_manager;
 | 
						|
shared_ptr<ts::ssl::SSLManager> ssl_manager;
 | 
						|
shared_ptr<license::web::WebStatistics> web_server;
 | 
						|
shared_ptr<LicenseServer> license_server;
 | 
						|
shared_ptr<UserManager> user_manager;
 | 
						|
 | 
						|
inline std::shared_ptr<WebCertificate> 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> bio{nullptr};
 | 
						|
	const uint8_t* mem_ptr{nullptr};
 | 
						|
	size_t length{0};
 | 
						|
 | 
						|
	{
 | 
						|
		bio = shared_ptr<BIO>(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>(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<WebCertificate>();
 | 
						|
	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<logger::LoggerConfig>();
 | 
						|
    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<server::database::DatabaseHandler>(database);
 | 
						|
    if(!license_manager->setup(error)) {
 | 
						|
        logError(LOG_GENERAL, "Could not start license manager! (" +error + ")");
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    statistic_manager = make_shared<stats::StatisticManager>(license_manager);
 | 
						|
 | 
						|
#if false
 | 
						|
    /*
 | 
						|
	{
 | 
						|
		auto _now = system_clock::now();
 | 
						|
		auto statistics = license_manager->list_statistics_user(_now - hours(24) * 32 * 4, _now, duration_cast<milliseconds>(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<milliseconds>(hours(1)));
 | 
						|
	    cout << "Grouping statistics" << endl;
 | 
						|
		std::deque<std::string> 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<string, int> 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<ts::ssl::SSLManager>();
 | 
						|
	{
 | 
						|
		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>(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::web::WebStatistics>(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<UserManager>(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<LicenseServer>(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;
 | 
						|
} |