351 lines
13 KiB
C++
351 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
|
|
*/
|
|
|
|
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;
|
|
} |