1.4.10 updates
This commit is contained in:
		
							parent
							
								
									2b929fd3c0
								
							
						
					
					
						commit
						0d456eea5d
					
				| @ -1 +1 @@ | ||||
| Subproject commit fec314cf2b73fc87b34bc68f26eb64e51982b26e | ||||
| Subproject commit ea81efb4130ad6c616b3b222a364fc8cd5ad9868 | ||||
| @ -17,6 +17,7 @@ set(LICENCE_SOURCE_FILES | ||||
| 		shared/LicenseRequest.cpp | ||||
| 		shared/LicenseRequestHandler.cpp | ||||
| 		shared/License.cpp | ||||
| 		shared/LicenseServerClient.cpp | ||||
| 		../shared/src/log/LogUtils.cpp | ||||
| ) | ||||
| 
 | ||||
| @ -36,13 +37,14 @@ add_executable(TeaLicenseServer ${LICENCE_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HD | ||||
| 		server/KeyIdCache.cpp | ||||
| 		server/LicenseServer.cpp | ||||
| 		server/LicenseServerHandler.cpp | ||||
| 		server/LicenseManager.cpp | ||||
|         server/DatabaseHandler.cpp | ||||
| 		LicenseServerMain.cpp | ||||
| 		server/WebAPI.cpp | ||||
| 		server/StatisticManager.cpp | ||||
| 		server/UserManager.cpp | ||||
| 		MySQLLibSSLFix.c | ||||
| ) | ||||
| target_compile_options(TeaLicenseServer PRIVATE "-Wl,--unresolved-symbols=ignore-in-object-files") | ||||
| 
 | ||||
| target_link_libraries(TeaLicenseServer | ||||
|         threadpool::static    #Static | ||||
| @ -68,44 +70,43 @@ target_link_libraries(TeaLicenseServer | ||||
|         mysqlclient.a | ||||
|         jsoncpp_lib | ||||
|         ${DataPipes_LIBRARIES_SHARED} # Also includes glib2.0 | ||||
|         ffi | ||||
|         openssl::ssl::shared | ||||
|         openssl::crypto::shared | ||||
|         pthread | ||||
|         dl | ||||
|         z | ||||
| ) | ||||
| if (COMPILE_WEB_CLIENT) | ||||
| 	target_link_libraries(TeaLicenseServer ${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10 ffi) | ||||
| endif () | ||||
| 
 | ||||
| include_directories(${LIBRARY_PATH}/boringssl/include/) | ||||
| 
 | ||||
| #The test license client | ||||
| add_executable(TeaLicenseClient | ||||
| 		LicenseClientMain.cpp | ||||
| 		${LICENCE_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HDRS} | ||||
| ) | ||||
| 		${LICENCE_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HDRS}) | ||||
| target_link_libraries(TeaLicenseClient | ||||
| 		${LIBRARY_PATH_DATA_PIPES} | ||||
| 		${LIBRARY_PATH_TERMINAL}   #Static | ||||
| 		${LIBRARY_PATH_THREAD_POOL}    #Static | ||||
| 		${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent.a | ||||
| 		${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent_pthreads.a | ||||
| 		pthread | ||||
| 		${LIBRARY_PATH_VARIBALES} | ||||
| 		${LIBRARY_PATH_BREAKPAD} | ||||
| 		${LIBRARY_PATH_PROTOBUF} | ||||
| 
 | ||||
| 		${LIBRARY_TOM_MATH} | ||||
| 		${LIBRARY_TOM_CRYPT} | ||||
| 		stdc++fs.a | ||||
| 		${LIBRARY_PATH_TERMINAL} | ||||
| 
 | ||||
| 		${LIBRARY_PATH_BORINGSSL_SSL} | ||||
| 		${LIBRARY_PATH_BORINGSSL_CRYPTO} | ||||
| 		${LIBRARY_PATH_BREAKPAD} | ||||
| 		${TOM_LIBRARIES} | ||||
| 		${LIBRARY_PATH_JDBC} | ||||
| 		${LIBRARY_PATH_ED255} | ||||
| 		TeaSpeak            #Static | ||||
| 		jsoncpp.a | ||||
| 
 | ||||
| 		protobuf::libprotobuf | ||||
| 		stdc++fs | ||||
| 		libevent::core libevent::pthreads | ||||
| 		${ed25519_LIBRARIES_STATIC} | ||||
| 		${StringVariable_LIBRARIES_STATIC} | ||||
| 
 | ||||
| 		CXXTerminal::static  #Static | ||||
| 
 | ||||
| 		tomcrypt::static | ||||
| 		tommath::static | ||||
| 
 | ||||
| 		${DataPipes_LIBRARIES_SHARED} # Also includes glib2.0 | ||||
| 		${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10 ffi | ||||
| 		openssl::ssl::shared | ||||
| 		openssl::crypto::shared | ||||
| 		pthread | ||||
| 		dl | ||||
| 		z | ||||
| ) | ||||
| 
 | ||||
| #The license manager | ||||
| @ -166,11 +167,12 @@ add_executable(LicenseCLI | ||||
| ) | ||||
| 
 | ||||
| target_link_libraries(LicenseCLI | ||||
| 		${LIBRARY_PATH_THREAD_POOL} #Static | ||||
| 		${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent.a | ||||
| 		${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent_pthreads.a | ||||
| 		${LIBRARY_PATH_PROTOBUF} | ||||
| 		${LIBRARY_TOM_MATH} #Static | ||||
| 		${LIBRARY_TOM_CRYPT} #Static | ||||
| 		TeaSpeak | ||||
| 		libevent::core libevent::pthreads | ||||
| 		threadpool::static    #Static | ||||
| 		tomcrypt::static | ||||
| 		tommath::static | ||||
| 		protobuf::libprotobuf | ||||
| 		${ed25519_LIBRARIES_STATIC} | ||||
| 		pthread | ||||
| ) | ||||
| @ -2,6 +2,8 @@ | ||||
| #include <shared/License.h> | ||||
| #include <shared/LicenseRequest.h> | ||||
| #include <event2/thread.h> | ||||
| #include "shared/LicenseServerClient.h" | ||||
| 
 | ||||
| #include <random> | ||||
| #include <ed25519/ed25519.h> | ||||
| #include <misc/base64.h> | ||||
| @ -49,8 +51,8 @@ int main(int ac, char** av){ | ||||
| 	} | ||||
| 	cout << endl; | ||||
| 	cout << "Intermediate: " << base64::encode(error) << endl; | ||||
| 
 | ||||
| #endif | ||||
| #if 0 | ||||
| 	auto license = v2::License::read(intermediate_key.data(), intermediate_key.size(), errc); | ||||
| 
 | ||||
| 	license->push_entry<v2::hierarchy::Server>(system_clock::now(), system_clock::now() + hours{24 * 365 * 1000}, "TeaSpeak Official server", "contact@teaspeak.de"); | ||||
| @ -60,6 +62,7 @@ int main(int ac, char** av){ | ||||
| 	__asm__("nop"); | ||||
| 	cout << "Errc: " << errc << endl; | ||||
| 	cout << "Write: " << base64::encode(error) << endl; | ||||
| #endif | ||||
| #if 0 | ||||
| 	std::array<uint8_t, 32> private_key, public_key; | ||||
| 
 | ||||
| @ -84,7 +87,6 @@ int main(int ac, char** av){ | ||||
| 
 | ||||
| 	return true; | ||||
| #endif | ||||
| 
 | ||||
| #if 0 | ||||
| 	srand(system_clock::now().time_since_epoch().count()); | ||||
| 	cout << "Generating new license" << endl; | ||||
| @ -121,29 +123,56 @@ int main(int ac, char** av){ | ||||
| 	auto data = make_shared<LicenseRequestData>(); | ||||
| 	data->license = license; | ||||
| 	data->info = make_shared<ServerInfo>(); | ||||
| 	while(true) { | ||||
| 		LicenceRequest request(data, serv_addr); | ||||
| 		try { | ||||
| 			cout << "Requesting license" << endl; | ||||
| 			auto info = request.requestInfo().waitAndGet(nullptr); | ||||
| 			if(!info) { | ||||
| 				cout << "Invalid result! Error: " << (request.exception() ? "yes => " + string(request.exception()->what()) : "no")  << endl; | ||||
| 				throw *request.exception(); | ||||
| 			} | ||||
| 			cout << "Got result!" << endl; | ||||
| 			cout << "Valid: " << info->license_valid << endl; | ||||
| 			if(info->license) { | ||||
| 				cout << "License:" << endl; | ||||
| 				cout << "  Type: " << info->license->type << endl; | ||||
| 				cout << "  User name: " << info->license->username << endl; | ||||
| 				cout << "  First name: " << info->license->first_name << endl; | ||||
| 				cout << "  Last name: " << info->license->last_name << endl; | ||||
| 				cout << "  EMail: " << info->license->email << endl; | ||||
| 			} else cout << "License: none"; | ||||
| 		} catch (const std::exception& ex){ | ||||
| 			cerr << "Could not load info after throwing: " << endl << ex.what() << endl; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|     LicenceRequest request(data, serv_addr); | ||||
|     try { | ||||
|         cout << "Requesting license" << endl; | ||||
|         auto info = request.requestInfo().waitAndGet(nullptr); | ||||
|         if(!info) { | ||||
|             cout << "Invalid result! Error: " << (request.exception() ? "yes => " + string(request.exception()->what()) : "no")  << endl; | ||||
|             throw *request.exception(); | ||||
|         } | ||||
|         cout << "Got result!" << endl; | ||||
|         cout << "Valid: " << info->license_valid << endl; | ||||
|         if(info->license) { | ||||
|             cout << "License:" << endl; | ||||
|             cout << "  Type: " << info->license->type << endl; | ||||
|             cout << "  User name: " << info->license->username << endl; | ||||
|             cout << "  First name: " << info->license->first_name << endl; | ||||
|             cout << "  Last name: " << info->license->last_name << endl; | ||||
|             cout << "  EMail: " << info->license->email << endl; | ||||
|         } else cout << "License: none"; | ||||
|     } catch (const std::exception& ex){ | ||||
|         cerr << "Could not load info after throwing: " << endl << ex.what() << endl; | ||||
|     } | ||||
| #endif | ||||
| #if 1 | ||||
|     sockaddr_in serv_addr{}; | ||||
|     serv_addr.sin_family = AF_INET; | ||||
| 
 | ||||
|     serv_addr.sin_addr.s_addr = ((in_addr*) gethostbyname("localhost")->h_addr)->s_addr; | ||||
|     serv_addr.sin_port = htons(27786); | ||||
| 
 | ||||
|     client::LicenseServerClient client{serv_addr, 3}; | ||||
|     client.callback_connected = [&]{ | ||||
|         std::cout << "Connected" << std::endl; | ||||
|         client.disconnect("client closed", std::chrono::system_clock::now() + std::chrono::seconds{5}); | ||||
|     }; | ||||
|     client.callback_message = [&](auto type, const void* buffer, size_t length) { | ||||
|         std::cout << "Received an message" << std::endl; | ||||
|     }; | ||||
|     client.callback_disconnected = [&](bool expected, const std::string& reason) { | ||||
|         std::cout << "Received disconnect (expected: " << expected << "): " << reason << std::endl; | ||||
|     }; | ||||
| 
 | ||||
|     if(!client.start_connection(error)) { | ||||
|         std::cout << "Failed to start connection" << std::endl; | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     std::this_thread::sleep_for(std::chrono::seconds{2}); | ||||
|     client.disconnect("client closed", std::chrono::system_clock::now() + std::chrono::seconds{5}); | ||||
|     std::cout << "Disconnect result: " << client.await_disconnect() << std::endl; | ||||
| #endif | ||||
| 	return 0; | ||||
| } | ||||
| @ -2,6 +2,7 @@ | ||||
| #include <algorithm> | ||||
| #include <vector> | ||||
| #include <event2/thread.h> | ||||
| #include <misc/base64.h> | ||||
| 
 | ||||
| #include "manager/ServerConnection.h" | ||||
| 
 | ||||
| @ -63,16 +64,18 @@ class CLIParser { | ||||
| 		std::vector<std::string> tokens; | ||||
| }; | ||||
| 
 | ||||
| #define REQ_CMD(variable, message, ...)                             \ | ||||
| if(!options.option_exists({__VA_ARGS__}, false)) {                  \ | ||||
|     cerr << message << endl;                                        \ | ||||
|     return 1;                                                       \ | ||||
| }                                                                   \ | ||||
| auto variable = url_decode(options.get_option({__VA_ARGS__}))       \ | ||||
| #define _str(x) #x | ||||
| 
 | ||||
| #define REQ_CMD(variable, message, skey, lkey, ...)                                  \ | ||||
| if(!options.option_exists({skey, lkey, #__VA_ARGS__}, false)) {                      \ | ||||
|     cerr << message << " (" << _str(skey) << " or " << _str(lkey) << ")" << endl;    \ | ||||
|     return 1;                                                                        \ | ||||
| }                                                                                    \ | ||||
| auto variable = url_decode(options.get_option({skey, lkey, #__VA_ARGS__}))           \ | ||||
| 
 | ||||
| #define NO_OPEN_SSL | ||||
| #include <misc/digest.h> | ||||
| #include <misc/base64.h> | ||||
| 
 | ||||
| int main(int argc, char **argv) { | ||||
| 	CLIParser options(argc, argv); | ||||
| 
 | ||||
| @ -88,7 +91,9 @@ int main(int argc, char **argv) { | ||||
| 	REQ_CMD(auth_pass, "missing authentication user", "-ap", "--auth-pass"); | ||||
| 
 | ||||
| 	REQ_CMD(server_host, "missing server host", "-h", "--server-host"); | ||||
| 	REQ_CMD(server_port, "missing server port", "-p", "--server-port"); | ||||
|     REQ_CMD(server_port, "missing server port", "-p", "--server-port"); | ||||
| 
 | ||||
|     REQ_CMD(old_key, "missing old license key", "-ol", "--old-license"); | ||||
| 
 | ||||
| 	auto state = evthread_use_pthreads(); | ||||
| 	if(state != 0) { | ||||
| @ -96,6 +101,7 @@ int main(int argc, char **argv) { | ||||
| 		return 1; | ||||
| 	} | ||||
| 
 | ||||
| 	std::string error{}; | ||||
| 	ServerConnection connection; | ||||
| 	connection.verbose = false; | ||||
| 	try { | ||||
| @ -112,15 +118,24 @@ int main(int argc, char **argv) { | ||||
| 	{ | ||||
| 		auto future = connection.login(auth_user, auth_pass); | ||||
| 		if(!future.waitAndGet(false, system_clock::now() + seconds(5))) { | ||||
| 			cerr << "failed to athentificate (" << future.errorMegssage() << ")" << endl; | ||||
| 			cerr << "failed to authenticate (" << future.errorMegssage() << ")" << endl; | ||||
| 			return 1; | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
|     std::shared_ptr<License> old_license{nullptr}; | ||||
| 	if(!old_key.empty() && old_key != "none") { | ||||
| 	    old_license = license::readLocalLicence(old_key, error); | ||||
| 	    if(!old_license) { | ||||
| 	        cerr << "failed to decode old license: " << error << endl; | ||||
| 	        return 1; | ||||
| 	    } | ||||
| 	} | ||||
| 
 | ||||
| 	try { | ||||
| 		system_clock::time_point timestamp_begin = system_clock::time_point() + seconds(stoll(begin)); | ||||
| 		system_clock::time_point timestamp_end = system_clock::time_point() + seconds(stoll(end)); | ||||
| 		auto future = connection.registerLicense(first_name, last_name, user, email, (LicenseType) stoll(license_type), timestamp_end, timestamp_begin); | ||||
| 		auto future = connection.registerLicense(first_name, last_name, user, email, (LicenseType) stoll(license_type), timestamp_end, timestamp_begin, old_license ? old_license->key() : "none"); | ||||
| 		auto result = future.waitAndGet({nullptr, nullptr}, system_clock::now() + seconds(5)); | ||||
| 		if(!result.first || !result.second) { | ||||
| 			cerr << "failed to create license! (" << future.errorMegssage() << ")" << endl; | ||||
|  | ||||
| @ -34,7 +34,7 @@ using namespace license; | ||||
|  */ | ||||
| bool handle_command(string& line); | ||||
| 
 | ||||
| shared_ptr<server::LicenseManager> license_manager; | ||||
| 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; | ||||
| @ -102,14 +102,16 @@ int main(int argc, char** argv) { | ||||
| 
 | ||||
| 	evthread_use_pthreads(); | ||||
|     srand(system_clock::now().time_since_epoch().count()); | ||||
|     terminal::install(); | ||||
|     if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; } | ||||
| 
 | ||||
|     //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; | ||||
| @ -138,7 +140,7 @@ int main(int argc, char** argv) { | ||||
|     }, (void*) nullptr) << endl; | ||||
| #endif | ||||
| 
 | ||||
| 	license_manager = make_shared<server::LicenseManager>(database); | ||||
| 	license_manager = make_shared<server::database::DatabaseHandler>(database); | ||||
|     if(!license_manager->setup(error)) { | ||||
|         logError(LOG_GENERAL, "Could not start license manager! (" +error + ")"); | ||||
|         return 0; | ||||
| @ -147,6 +149,7 @@ int main(int argc, char** argv) { | ||||
| 
 | ||||
|     statistic_manager = make_shared<stats::StatisticManager>(license_manager); | ||||
| 
 | ||||
| #if false | ||||
|     /*
 | ||||
| 	{ | ||||
| 		auto _now = system_clock::now(); | ||||
| @ -163,6 +166,9 @@ int main(int argc, char** argv) { | ||||
|  	} | ||||
| 	return 0; | ||||
| 	*/ | ||||
| #endif | ||||
| 
 | ||||
| #if false | ||||
|     /*
 | ||||
|     { | ||||
|     	ofstream _file_out("version_history.txt"); | ||||
| @ -229,6 +235,7 @@ int main(int argc, char** argv) { | ||||
|     } | ||||
| 	return 0; | ||||
|      */ | ||||
| #endif | ||||
| 
 | ||||
| 	ssl_manager = make_shared<ts::ssl::SSLManager>(); | ||||
| 	{ | ||||
| @ -268,17 +275,20 @@ int main(int argc, char** argv) { | ||||
| 	} | ||||
| 
 | ||||
| 	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)) { | ||||
| 			terminal::instance()->writeMessage("§aStopping server..."); | ||||
| 		if(!handle_command(line)) | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|     terminal::instance()->writeMessage("§aStopping server..."); | ||||
| 	web_server->stop(); | ||||
|     license_server->stop(); | ||||
|     if(database) database->disconnect(); | ||||
|  | ||||
| @ -50,8 +50,8 @@ threads::Future<bool> ServerConnection::connect(const std::string &host, uint16_ | ||||
| 		if(this->network.file_descriptor < 0) CERR("Socket setup failed"); | ||||
| 		if(::connect(this->network.file_descriptor, reinterpret_cast<const sockaddr *>(&this->network.address_remote), sizeof(this->network.address_remote)) < 0) CERR("connect() failed (" + to_string(errno) + " | " + strerror(errno) + ")"); | ||||
| 		int enabled = 1, disabled = 0; | ||||
| 		if(setsockopt(this->network.file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0) CERR("could not set reuse addr"); | ||||
| 		if(setsockopt(this->network.file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0) CERR("could not set no push"); | ||||
| 		if(setsockopt(this->network.file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0); // CERR("could not set reuse addr");
 | ||||
| 		if(setsockopt(this->network.file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0); // CERR("could not set no push");
 | ||||
| 
 | ||||
| 		this->network.event_base = event_base_new(); | ||||
| 		this->network.event_read = event_new(this->network.event_base, this->network.file_descriptor, EV_READ | EV_PERSIST, ServerConnection::handleEventRead, this); | ||||
| @ -64,7 +64,7 @@ threads::Future<bool> ServerConnection::connect(const std::string &host, uint16_ | ||||
| 				cout << "ev ended!" << endl; | ||||
| 		}}; | ||||
| 		this->network.state = ConnectionState::CONNECTED; | ||||
| 		this->protocol.state = protocol::HANDSCAKE; | ||||
| 		this->protocol.state = protocol::HANDSCHAKE; | ||||
| 		this->protocol.ping_thread = thread([&]{ | ||||
| 			while(true) { | ||||
| 				{ | ||||
| @ -83,7 +83,7 @@ threads::Future<bool> ServerConnection::connect(const std::string &host, uint16_ | ||||
| 		handshakeBuffer[0] = 0xC0; | ||||
| 		handshakeBuffer[1] = 0xFF; | ||||
| 		handshakeBuffer[2] = 0xEE; | ||||
| 		handshakeBuffer[3] = LICENSE_PROT_VERSION; | ||||
| 		handshakeBuffer[3] = 2; | ||||
| 		handshakeBuffer[4] = 1; //Im a manager
 | ||||
| 		this->sendPacket(protocol::packet{protocol::PACKET_CLIENT_HANDSHAKE, string((const char*) handshakeBuffer, 5)}); //Initialise packet
 | ||||
| 	}).detach(); | ||||
| @ -156,8 +156,8 @@ void ServerConnection::handleEventRead(int fd, short, void* _connection) { | ||||
| 	auto connection = (ServerConnection*) _connection; | ||||
| 
 | ||||
| 	char buffer[1024]; | ||||
| 	auto read = recv(fd, buffer, 1024, SOCK_NONBLOCK); | ||||
| 	if(read < 0) { | ||||
| 	auto read = recv(fd, buffer, 1024, MSG_DONTWAIT); | ||||
| 	if(read <= 0) { | ||||
| 		if(connection->verbose) | ||||
| 			cout << "Invalid read: " << strerror(errno) << endl; | ||||
| 		connection->local_disconnect_message = "invalid read"; | ||||
|  | ||||
| @ -25,94 +25,93 @@ do {                                    \ | ||||
| } while(0) | ||||
| 
 | ||||
| 
 | ||||
| namespace license { | ||||
| 	namespace manager { | ||||
| 		enum ConnectionState { | ||||
| 			UNCONNECTED, | ||||
| 			CONNECTING, | ||||
| 			CONNECTED, | ||||
| 			DISCONNECTING | ||||
| 		}; | ||||
| 		class ServerConnection { | ||||
| 			public: | ||||
| 				ServerConnection(); | ||||
| 				~ServerConnection(); | ||||
| namespace license::manager { | ||||
|     enum ConnectionState { | ||||
|         UNCONNECTED, | ||||
|         CONNECTING, | ||||
|         CONNECTED, | ||||
|         DISCONNECTING | ||||
|     }; | ||||
|     class ServerConnection { | ||||
|         public: | ||||
|             ServerConnection(); | ||||
|             ~ServerConnection(); | ||||
| 
 | ||||
| 				threads::Future<bool> connect(const std::string& host, uint16_t port); | ||||
| 				void disconnect(const std::string&); | ||||
|             threads::Future<bool> connect(const std::string& host, uint16_t port); | ||||
|             void disconnect(const std::string&); | ||||
| 
 | ||||
| 				void ping(); | ||||
|             void ping(); | ||||
| 
 | ||||
| 				threads::Future<bool> login(const std::string&, const std::string&); | ||||
| 				threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>> registerLicense( | ||||
| 						const std::string& first_name, | ||||
| 						const std::string& last_name, | ||||
| 						const std::string& username, | ||||
|                         const std::string& email, | ||||
| 						license::LicenseType type, | ||||
|                         const std::chrono::system_clock::time_point& end, | ||||
| 						const std::chrono::system_clock::time_point& begin = std::chrono::system_clock::now() | ||||
| 				); | ||||
|             threads::Future<bool> login(const std::string&, const std::string&); | ||||
|             threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>> registerLicense( | ||||
|                     const std::string& first_name, | ||||
|                     const std::string& last_name, | ||||
|                     const std::string& username, | ||||
|                     const std::string& email, | ||||
|                     license::LicenseType type, | ||||
|                     const std::chrono::system_clock::time_point& end, | ||||
|                     const std::chrono::system_clock::time_point& begin, | ||||
|                     const std::string& old_license | ||||
|             ); | ||||
| 
 | ||||
|                 threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>> list(int offset, int count); | ||||
| 				threads::Future<bool> deleteLicense(const std::string& key, bool full = false); | ||||
|             threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>> list(int offset, int count); | ||||
|             threads::Future<bool> deleteLicense(const std::string& key, bool full = false); | ||||
| 
 | ||||
| 				bool verbose = true; | ||||
| 			private: | ||||
| 				struct { | ||||
| 					ConnectionState state = ConnectionState::UNCONNECTED; | ||||
| 					sockaddr_in address_remote; | ||||
| 					int file_descriptor = 0; | ||||
|             bool verbose = true; | ||||
|         private: | ||||
|             struct { | ||||
|                 ConnectionState state = ConnectionState::UNCONNECTED; | ||||
|                 sockaddr_in address_remote; | ||||
|                 int file_descriptor = 0; | ||||
| 
 | ||||
| 					event* event_read = nullptr; | ||||
| 					event* event_write = nullptr; | ||||
| 					struct event_base* event_base = nullptr; | ||||
| 					std::thread event_base_dispatch; | ||||
|                 event* event_read = nullptr; | ||||
|                 event* event_write = nullptr; | ||||
|                 struct event_base* event_base = nullptr; | ||||
|                 std::thread event_base_dispatch; | ||||
| 
 | ||||
| 					threads::Thread* flush_thread = nullptr; | ||||
|                 threads::Thread* flush_thread = nullptr; | ||||
| 
 | ||||
| 					threads::Mutex queue_lock; | ||||
| 					std::deque<std::string> queue_write; | ||||
|                 threads::Mutex queue_lock; | ||||
|                 std::deque<std::string> queue_write; | ||||
| 
 | ||||
|                     std::unique_ptr<protocol::packet> current_packet; | ||||
|                 std::unique_ptr<protocol::packet> current_packet; | ||||
| 
 | ||||
| 
 | ||||
| 					std::string overhead; | ||||
| 				} network; | ||||
|                 std::string overhead; | ||||
|             } network; | ||||
| 
 | ||||
| 
 | ||||
| 				struct { | ||||
| 					protocol::RequestState state; | ||||
| 					std::string crypt_key = ""; | ||||
|             struct { | ||||
|                 protocol::RequestState state; | ||||
|                 std::string crypt_key = ""; | ||||
| 
 | ||||
| 					std::mutex ping_lock; | ||||
| 					std::condition_variable ping_notify; | ||||
| 					std::thread ping_thread; | ||||
| 				} protocol; | ||||
|                 std::mutex ping_lock; | ||||
|                 std::condition_variable ping_notify; | ||||
|                 std::thread ping_thread; | ||||
|             } protocol; | ||||
| 
 | ||||
| 				struct { | ||||
| 					std::unique_ptr<threads::Future<bool>> future_connect; | ||||
| 					std::unique_ptr<threads::Future<bool>> future_login; | ||||
| 					std::unique_ptr<threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>>> future_register; | ||||
| 					std::unique_ptr<threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>>> future_list; | ||||
| 					std::unique_ptr<threads::Future<bool>> future_delete; | ||||
| 				} listener; | ||||
|             struct { | ||||
|                 std::unique_ptr<threads::Future<bool>> future_connect; | ||||
|                 std::unique_ptr<threads::Future<bool>> future_login; | ||||
|                 std::unique_ptr<threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>>> future_register; | ||||
|                 std::unique_ptr<threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>>> future_list; | ||||
|                 std::unique_ptr<threads::Future<bool>> future_delete; | ||||
|             } listener; | ||||
| 
 | ||||
| 				std::string local_disconnect_message; | ||||
|             std::string local_disconnect_message; | ||||
| 
 | ||||
| 				static void handleEventRead(int, short, void*); | ||||
| 				static void handleEventWrite(int, short, void*); | ||||
|             static void handleEventRead(int, short, void*); | ||||
|             static void handleEventWrite(int, short, void*); | ||||
| 
 | ||||
| 				void closeConnection(); | ||||
| 				void sendPacket(const protocol::packet&); | ||||
| 				void handleMessage(const std::string&); | ||||
|             void closeConnection(); | ||||
|             void sendPacket(const protocol::packet&); | ||||
|             void handleMessage(const std::string&); | ||||
| 
 | ||||
| 				void handlePacketDisconnect(const std::string&); | ||||
| 				void handlePacketHandshake(const std::string&); | ||||
| 				void handlePacketAuthResponse(const std::string&); | ||||
|                 void handlePacketCreateResponse(const std::string&); | ||||
| 				void handlePacketListResponse(const std::string&); | ||||
|                 void handlePacketDeleteResponse(const std::string&); | ||||
| 		}; | ||||
| 	} | ||||
|             void handlePacketDisconnect(const std::string&); | ||||
|             void handlePacketHandshake(const std::string&); | ||||
|             void handlePacketAuthResponse(const std::string&); | ||||
|             void handlePacketCreateResponse(const std::string&); | ||||
|             void handlePacketListResponse(const std::string&); | ||||
|             void handlePacketDeleteResponse(const std::string&); | ||||
|     }; | ||||
| } | ||||
| @ -35,7 +35,8 @@ threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<lic | ||||
| 		const std::string &email, | ||||
| 		license::LicenseType type, | ||||
| 		const std::chrono::system_clock::time_point& end, | ||||
| 		const std::chrono::system_clock::time_point& start | ||||
| 		const std::chrono::system_clock::time_point& start, | ||||
|         const std::string& old_license | ||||
| ) { | ||||
| 
 | ||||
| 	this->listener.future_register = std::make_unique<threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>>>(); | ||||
| @ -48,6 +49,9 @@ threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<lic | ||||
| 	request.set_type(type); | ||||
| 	request.set_begin(duration_cast<milliseconds>(start.time_since_epoch()).count()); | ||||
|     request.set_end(duration_cast<milliseconds>(end.time_since_epoch()).count()); | ||||
|     if(!old_license.empty() && old_license != "none") | ||||
|         request.set_old_key(old_license); | ||||
| 
 | ||||
| 	this->sendPacket({protocol::PACKET_CLIENT_LICENSE_CREATE_REQUEST, request}); | ||||
| 
 | ||||
| 	if(this->network.state != ConnectionState::CONNECTED) | ||||
|  | ||||
| @ -84,11 +84,11 @@ void ServerConnection::handlePacketDisconnect(const std::string& message) { | ||||
| } | ||||
| 
 | ||||
| void ServerConnection::handlePacketHandshake(const std::string& data) { | ||||
| 	if(this->protocol.state != protocol::HANDSCAKE) LERROR("Protocol state mismatch"); | ||||
| 	if(this->protocol.state != protocol::HANDSCHAKE) LERROR("Protocol state mismatch"); | ||||
| 	if(data.length() < 3) LERROR("Invalid packet size"); | ||||
| 
 | ||||
| 	if((uint8_t) data[0] != 0xAF || (uint8_t) data[1] != 0xFE) LERROR("Invalid handshake"); | ||||
| 	if((uint8_t) data[2] != LICENSE_PROT_VERSION) LERROR("Invalid license protocol version. Please update TeaSpeak!"); | ||||
| 	if((uint8_t) data[2] != 2) LERROR("Invalid license protocol version. Please update this client!"); | ||||
| 
 | ||||
| 	auto key_length = be2le16(data.data(), 3); | ||||
| 	if(data.length() < key_length + 3) LERROR("Invalid packet size"); | ||||
|  | ||||
| @ -32,6 +32,9 @@ message LicenseCreateRequest { | ||||
|     required int64 type = 5; | ||||
|     required int64 begin = 6; | ||||
|     required int64 end = 7; | ||||
| 
 | ||||
|     /* if set the license will upgrade automatically */ | ||||
|     optional bytes old_key = 8; | ||||
| } | ||||
| message LicenseCreateResponse { | ||||
|     oneof result { | ||||
|  | ||||
| @ -40,13 +40,16 @@ message ServerValidation { | ||||
|     required bool licensed          = 1; | ||||
|     required bool license_info      = 2; | ||||
|     optional bytes license          = 3; | ||||
|     optional ServerInfo info        = 4; //Change somewhen to required but its currently for lagacy support | ||||
|     optional ServerInfo info        = 4; //Change somewhere to required but its currently for legacy support | ||||
| } | ||||
| 
 | ||||
| message LicenseResponse { | ||||
|     required bool valid                 = 1; //If set the license is valid. The blacklist field could still be active. If the flag is false then the blacklist will container the message | ||||
|     required bool valid                 = 1; | ||||
|     optional string invalid_reason      = 5; /* in protocol version 2 the blacklist.reason field will contain the message */ | ||||
| 
 | ||||
|     required Blacklist blacklist        = 2; | ||||
|     optional LicenseInfo license_info   = 3; //Only availible when ServerValidation::license_info = true | ||||
|     optional LicenseInfo license_info   = 3; //Only available when ServerValidation::license_info = true | ||||
|     optional bool update_pending        = 4; /* if an update is pending */ | ||||
| } | ||||
| 
 | ||||
| message PropertyUpdateRequest { | ||||
| @ -64,6 +67,15 @@ message PropertyUpdateRequest { | ||||
|     optional bytes web_cert_revision    = 12; | ||||
| } | ||||
| 
 | ||||
| message RequestLicenseUpgrade { } | ||||
| message LicenseUpgradeResponse { | ||||
|     required bool valid                 = 1; | ||||
|     oneof result { | ||||
|         bytes license_key               = 2; | ||||
|         string error_message            = 3; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| message WebCertificate { | ||||
|     required bytes revision             = 1; | ||||
|     required string key                 = 2; | ||||
|  | ||||
| @ -1,16 +1,17 @@ | ||||
| #include "LicenseManager.h" | ||||
| #include "DatabaseHandler.h" | ||||
| #include <misc/base64.h> | ||||
| #include <log/LogUtils.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
| using namespace license; | ||||
| using namespace license::server; | ||||
| using namespace license::server::database; | ||||
| using namespace sql; | ||||
| 
 | ||||
| LicenseManager::LicenseManager(sql::SqlManager* database) : database(database){ | ||||
| DatabaseHandler::DatabaseHandler(sql::SqlManager* database) : database(database){ | ||||
|     this->id_cache = make_shared<KeyIdCache>(this); | ||||
| } | ||||
| LicenseManager::~LicenseManager() { } | ||||
| DatabaseHandler::~DatabaseHandler() { } | ||||
| 
 | ||||
| /*
 | ||||
|         LicenseType type; | ||||
| @ -40,7 +41,7 @@ res = command(this->database, cmd).execute();                       \ | ||||
| version = ver; \ | ||||
| sql::command(this->database, "UPDATE `general` SET `value` = :version WHERE `key` = 'version'", variable{":version", version}).execute(); | ||||
| 
 | ||||
| bool LicenseManager::setup(std::string& error) { | ||||
| bool DatabaseHandler::setup(std::string& error) { | ||||
| 	result res; | ||||
| 	int version = -1; | ||||
| 	sql::command(this->database, "SELECT `value` FROM `general` WHERE `key` = 'version'").query([](int* version, int lnegth, string* values, string* names) { | ||||
| @ -106,20 +107,33 @@ bool LicenseManager::setup(std::string& error) { | ||||
| 
 | ||||
| 		case 4: | ||||
| 			CTBL("CREATE TABLE IF NOT EXISTS `users` (`username` VARCHAR(64) NOT NULL PRIMARY KEY, `password_hash` VARCHAR(128), `status` INT, `owner` VARCHAR(64))"); | ||||
|             SET_VERSION(5); | ||||
| 
 | ||||
| 	    case 5: | ||||
|             CTBL("CREATE TABLE `license_upgrades` (`upgrade_id` INT PRIMARY KEY NOT NULL, `old_key_id` INT, `new_key_id` INT, `timestamp_begin` BIGINT, `timestamp_end` BIGINT, `valid` INT, `use_count` INT, `license` BLOB);"); | ||||
|             CTBL("ALTER TABLE `license_upgrades` ADD INDEX(`old_key_id`)"); | ||||
|             CTBL("ALTER TABLE `license` ADD INDEX(`keyId`);"); | ||||
|             CTBL("ALTER TABLE `license` ADD COLUMN `upgrade_id` INT DEFAULT 0;"); | ||||
|             SET_VERSION(6); | ||||
| 
 | ||||
| 	    case 6: | ||||
| 	        CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` INT, `succeeded` TINYINT);"); | ||||
|             CIDX("CREATE INDEX `upgrade_id_timestamp` ON `license_upgrade_log` (`upgrade_id`, `timestamp`)"); | ||||
|             SET_VERSION(7); | ||||
| 
 | ||||
|         default:; | ||||
|         } | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool LicenseManager::registerLicense(const std::string& key, const shared_ptr<LicenseInfo>& info, const std::string& issuer) { | ||||
| bool DatabaseHandler::register_license(const std::string& key, const shared_ptr<LicenseInfo>& info, const std::string& issuer) { | ||||
| 	result res; | ||||
| 	res = command(this->database, "INSERT INTO `license` (`key`, `type`, `deleted`, `issuer`) VALUES (:key, :type, :deleted, :issuer)", variable{":key", key}, variable{":type", (uint32_t) info->type}, variable{":deleted", false}, variable{":issuer", issuer}).execute(); | ||||
| 	if(!res) { | ||||
| 		logError(LOG_GENERAL, "Could not register new license (" + res.fmtStr() + ")"); | ||||
| 		return false; | ||||
| 	} | ||||
| 	auto keyId = this->id_cache->getKeyId(key); | ||||
| 	auto keyId = this->id_cache->get_key_id_from_key(key); | ||||
| 	if(keyId == 0) return false; | ||||
| 
 | ||||
| 	res = command(this->database, "INSERT INTO `license_info` (`keyId`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`) VALUES (:key, :username, :first_name, :last_name, :email, :begin, :end, :generated)", | ||||
| @ -139,9 +153,9 @@ bool LicenseManager::registerLicense(const std::string& key, const shared_ptr<Li | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool LicenseManager::deleteLicense(const std::string& key, bool full) { | ||||
| bool DatabaseHandler::delete_license(const std::string& key, bool full) { | ||||
|     if(full) { | ||||
|         auto keyId = this->id_cache->getKeyId(key); | ||||
|         auto keyId = this->id_cache->get_key_id_from_key(key); | ||||
|         if(keyId == 0) return false; //Never exists
 | ||||
| 
 | ||||
|         auto res = command(this->database, "DELETE FROM `license` WHERE `key` = :key", variable{":key", key}).execute(); | ||||
| @ -156,7 +170,7 @@ bool LicenseManager::deleteLicense(const std::string& key, bool full) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool LicenseManager::validLicenseKey(const std::string& key) { | ||||
| bool DatabaseHandler::validLicenseKey(const std::string& key) { | ||||
| 	bool valid = false; | ||||
| 	auto res = command(this->database, "SELECT * FROM `license` WHERE `key` = :key AND `deleted` = :false LIMIT 1", variable{":false", false}, variable{":key", key}).query([](bool* flag, int, char**, char**) { | ||||
| 		*flag = true; | ||||
| @ -166,10 +180,10 @@ bool LicenseManager::validLicenseKey(const std::string& key) { | ||||
| 	return !!res && valid; | ||||
| } | ||||
| 
 | ||||
| inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlManager* mgr, std::string key, int offset, int length) { | ||||
| inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlManager* mgr, const std::string& key, int offset, int length) { | ||||
|     std::map<std::string, std::shared_ptr<LicenseInfo>> result; | ||||
| 
 | ||||
|     auto query = string() + "SELECT `key`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`, `deleted` FROM `license_info` INNER JOIN `license` ON `license_info`.`keyId` = `license`.`keyId`"; | ||||
|     auto query = string() + "SELECT `key`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`, `deleted`, `upgrade_id` FROM `license_info` INNER JOIN `license` ON `license_info`.`keyId` = `license`.`keyId`"; | ||||
|     if(!key.empty()) | ||||
| 	    query += "WHERE `key` = :key"; | ||||
|     else | ||||
| @ -203,6 +217,8 @@ inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlMana | ||||
|                     info->creation = system_clock::time_point() + milliseconds(stoll(values[index])); | ||||
|                 else if(names[index] == "deleted") | ||||
| 	                info->deleted = values[index] == "1" || values[index] == "true"; | ||||
|                 else if(names[index] == "upgrade_id") | ||||
|                     info->upgrade_id = std::stol(values[index]); | ||||
|                 else | ||||
|                     logError(LOG_GENERAL, "Unknown field {}", names[index]); | ||||
|             } catch (std::exception& ex) { | ||||
| @ -218,20 +234,20 @@ inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlMana | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<LicenseInfo> LicenseManager::licenseInfo(const std::string& key) { | ||||
| std::shared_ptr<LicenseInfo> DatabaseHandler::query_license_info(const std::string& key) { | ||||
|     auto result = query_license(this->database, key, 0, 0); | ||||
|     if(result.empty()) return nullptr; | ||||
|     return result.begin()->second; | ||||
| } | ||||
| 
 | ||||
| std::map<std::string, std::shared_ptr<LicenseInfo>> LicenseManager::listLicenses(int offset, int limit) { | ||||
| std::map<std::string, std::shared_ptr<LicenseInfo>> DatabaseHandler::list_licenses(int offset, int limit) { | ||||
| 	return query_license(this->database, "", offset, limit); | ||||
| } | ||||
| 
 | ||||
| bool LicenseManager::logRequest(const std::string& key, const std::string& unique_id, const std::string& ip, const std::string& version, int state) { | ||||
| bool DatabaseHandler::logRequest(const std::string& key, const std::string& unique_id, const std::string& ip, const std::string& version, int state) { | ||||
|     result res; | ||||
| 
 | ||||
|     auto keyId = this->id_cache->getKeyId(key); | ||||
|     auto keyId = this->id_cache->get_key_id_from_key(key); | ||||
|     if(keyId == 0) { | ||||
| 		logError(LOG_GENERAL, "Failed to log license request (could not resolve key id)"); | ||||
| 	    return false; | ||||
| @ -264,11 +280,11 @@ bool LicenseManager::logRequest(const std::string& key, const std::string& uniqu | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool LicenseManager::logStatistic(const std::string &key, const std::string& unique_id, const std::string &ip, | ||||
|                                   const ts::proto::license::PropertyUpdateRequest &data) { | ||||
| bool DatabaseHandler::logStatistic(const std::string &key, const std::string& unique_id, const std::string &ip, | ||||
|                                    const ts::proto::license::PropertyUpdateRequest &data) { | ||||
| 	result res; | ||||
| 
 | ||||
| 	auto keyId = this->id_cache->getKeyId(key); | ||||
| 	auto keyId = this->id_cache->get_key_id_from_key(key); | ||||
| 	if(keyId == 0) return false; | ||||
| 
 | ||||
| 	auto time = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count(); | ||||
| @ -305,7 +321,7 @@ bool LicenseManager::logStatistic(const std::string &key, const std::string& uni | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<LicenseManager::UserHistory> LicenseManager::list_statistics_user(const system_clock::time_point &begin, const system_clock::time_point &end, const milliseconds &interval) { | ||||
| std::shared_ptr<DatabaseHandler::UserHistory> DatabaseHandler::list_statistics_user(const system_clock::time_point &begin, const system_clock::time_point &end, const milliseconds &interval) { | ||||
| 	auto timeout = hours(2) + minutes(10); //TODO timeout configurable?
 | ||||
| 
 | ||||
| 	/* initialize the result */ | ||||
| @ -328,11 +344,11 @@ std::shared_ptr<LicenseManager::UserHistory> LicenseManager::list_statistics_use | ||||
| 	auto info = &_result->history[0]; | ||||
| 
 | ||||
| 	/* temp db variables */ | ||||
| 	map<std::string, LicenseManager::DatabaseUserStatistics> server_statistics; | ||||
| 	map<std::string, DatabaseHandler::DatabaseUserStatistics> server_statistics; | ||||
| 
 | ||||
| 	bool have_key, have_uid; | ||||
| 	LicenseManager::DatabaseUserStatistics temp_stats; /* temp struct for stats parsing */ | ||||
| 	LicenseManager::DatabaseUserStatistics* stats_ptr; /* pointer to the target stats */ | ||||
| 	DatabaseHandler::DatabaseUserStatistics temp_stats; /* temp struct for stats parsing */ | ||||
| 	DatabaseHandler::DatabaseUserStatistics* stats_ptr; /* pointer to the target stats */ | ||||
| 	std::chrono::system_clock::time_point current_timestamp = begin + interval; /* upper limit of the current interval */ | ||||
| 
 | ||||
| 	auto state = command(this->database, "SELECT * FROM `history_online` WHERE `timestamp` >= :timestamp_start AND `timestamp` <= :timestamp_end ORDER BY `timestamp` ASC", | ||||
| @ -440,8 +456,8 @@ std::shared_ptr<LicenseManager::UserHistory> LicenseManager::list_statistics_use | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseManager::list_statistics_version(const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point &end, const std::chrono::milliseconds &interval) { | ||||
| 	map<std::string, deque<unique_ptr<LicenseManager::GlobalVersionsStatistic>>> server_statistics; | ||||
| std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> DatabaseHandler::list_statistics_version(const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point &end, const std::chrono::milliseconds &interval) { | ||||
| 	map<std::string, deque<unique_ptr<DatabaseHandler::GlobalVersionsStatistic>>> server_statistics; | ||||
| 	auto timeout = hours(2) + minutes(10); //TODO timeout configurable?
 | ||||
| 
 | ||||
| 	auto state = command(this->database, "SELECT * FROM `history_version` WHERE `timestamp` >= :timestamp_start AND `timestamp` <= :timestamp_end ORDER BY `timestamp` ASC", | ||||
| @ -450,7 +466,7 @@ std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseMana | ||||
| 	).query([&server_statistics](int columns, std::string* values, std::string* names){ | ||||
| 		size_t key_id = 0; | ||||
| 		std::string unique_id; | ||||
| 		auto info = make_unique<LicenseManager::GlobalVersionsStatistic>(); | ||||
| 		auto info = make_unique<DatabaseHandler::GlobalVersionsStatistic>(); | ||||
| 		for(int index = 0; index < columns; index++) { | ||||
| 			try { | ||||
| 				if(names[index] == "keyId") | ||||
| @ -477,10 +493,10 @@ std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseMana | ||||
| 		return {}; | ||||
| 	} | ||||
| 
 | ||||
| 	std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> result; | ||||
| 	std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> result; | ||||
| 	system_clock::time_point current_timestamp = begin; | ||||
| 	while(current_timestamp <= end) { | ||||
| 		auto info = make_unique<LicenseManager::GlobalVersionsStatistic>(); | ||||
| 		auto info = make_unique<DatabaseHandler::GlobalVersionsStatistic>(); | ||||
| 		info->timestamp = current_timestamp; | ||||
| 
 | ||||
| 		for(auto& server : server_statistics) { | ||||
| @ -511,4 +527,87 @@ std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseMana | ||||
| 	} | ||||
| 
 | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| bool DatabaseHandler::register_license_upgrade(license_key_id_t old_key_id, license_key_id_t new_key_id, | ||||
|         const std::chrono::system_clock::time_point &begin_timestamp, const std::chrono::system_clock::time_point &end_timestamp, const std::string &license_key) { | ||||
|     auto upgrade_id = std::chrono::system_clock::now().time_since_epoch().count(); | ||||
|     auto sql_result = sql::command(this->sql(), "INSERT INTO `license_upgrades` (`upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license`) VALUES" | ||||
|                                                 "(:upgrade_id, :old_key_id, :new_key_id, :timestamp_begin, :timestamp_end, 1, 0, :license)", | ||||
|                                                 variable{":upgrade_id", upgrade_id}, | ||||
|                                                 variable{":old_key_id", old_key_id}, | ||||
|                                                 variable{":new_key_id", new_key_id}, | ||||
|                                                 variable{":timestamp_begin", std::chrono::duration_cast<std::chrono::milliseconds>(begin_timestamp.time_since_epoch()).count()}, | ||||
|                                                 variable{":timestamp_end", std::chrono::duration_cast<std::chrono::milliseconds>(end_timestamp.time_since_epoch()).count()}, | ||||
|                                                 variable{":license", base64::decode(license_key)}).execute(); | ||||
|     if(!sql_result) { | ||||
|         logError(LOG_GENERAL, "Failed to insert license upgrade: {}", sql_result.fmtStr()); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     sql_result = sql::command(this->sql(), "UPDATE `license` SET `upgrade_id` = :upgrade_id WHERE `keyId` = :key_id", | ||||
|                               variable{":upgrade_id", upgrade_id}, | ||||
|                               variable{":key_id", old_key_id}).execute(); | ||||
|     if(!sql_result) { | ||||
|         logError(LOG_GENERAL, "Failed to set license upgrade: {}", sql_result.fmtStr()); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<LicenseUpgrade> DatabaseHandler::query_license_upgrade(upgrade_id_t id) { | ||||
|     std::unique_ptr<LicenseUpgrade> result{}; | ||||
|     auto sql_result = sql::command(this->sql(), "SELECT `upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license` FROM `license_upgrades` WHERE `upgrade_id` = :upgrade_id LIMIT 1", | ||||
|             variable{":upgrade_id", id}).query([&](int length, std::string* values, std::string* names) { | ||||
|         result = std::make_unique<LicenseUpgrade>(); | ||||
|         for(size_t index = 0; index < length; index++) { | ||||
|             try { | ||||
|                 if(names[index] == "upgrade_id") | ||||
|                     result->upgrade_id = std::stoull(values[index]); | ||||
|                 else if(names[index] == "old_key_id") | ||||
|                     result->old_license_key_id = std::stoull(values[index]); | ||||
|                 else if(names[index] == "new_key_id") | ||||
|                     result->new_license_key_id = std::stoull(values[index]); | ||||
|                 else if(names[index] == "timestamp_begin") | ||||
|                     result->begin_timestamp = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoull(values[index])}; | ||||
|                 else if(names[index] == "timestamp_end") | ||||
|                     result->end_timestamp = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoull(values[index])}; | ||||
|                 else if(names[index] == "use_count") | ||||
|                     result->update_count = std::stoull(values[index]); | ||||
|                 else if(names[index] == "valid") | ||||
|                     result->valid = std::stoull(values[index]) > 0; | ||||
|                 else if(names[index] == "license") | ||||
|                     result->license_key = base64::encode(values[index]); | ||||
|             } catch(std::exception& ex) { | ||||
|                 result = nullptr; | ||||
|                 logWarning(LOG_GENERAL, "Failed to parse column {} for upgrade id {}. (Value: {})", names[index], id, values[index]); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|     if(!sql_result) { | ||||
|         logWarning(LOG_GENERAL, "Failed to query license upgrade info for upgrade {}: {}", id, sql_result.fmtStr()); | ||||
|         return nullptr; | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void DatabaseHandler::log_license_upgrade_attempt(upgrade_id_t upgrade_id, bool succeeded, const std::string &unique_id, const std::string &ip) { | ||||
|     auto result = sql::command(this->sql(), "INSERT INTO `license_upgrade_log` (`upgrade_id`, `timestamp`, `unique_id`, `server_ip`, `succeeded`) VALUES (:upgrade_id, :timestamp, :unique_id, :server_ip, :succeeded);", | ||||
|             variable{":upgrade_id", upgrade_id}, | ||||
|             variable{":timestamp", std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count()}, | ||||
|             variable{":unique_id", unique_id}, | ||||
|             variable{":server_ip", ip}, | ||||
|             variable{":succeeded", succeeded}).execute(); | ||||
|     if(!result) | ||||
|         logWarning(LOG_GENERAL, "Failed to insert upgrade log into database ({})", result.fmtStr()); | ||||
| 
 | ||||
|     if(succeeded) { | ||||
|         result = sql::command(this->sql(), "UPDATE `license_upgrades` SET `use_count` = `use_count` + 1 WHERE `upgrade_id` = :upgrade_id", | ||||
|                               variable{":upgrade_id", upgrade_id}).execute(); | ||||
|         if(!result) | ||||
|             logWarning(LOG_GENERAL, "Failed to increase upgrade use count MySQL ({})", result.fmtStr()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										125
									
								
								license/server/DatabaseHandler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								license/server/DatabaseHandler.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <sql/SqlQuery.h> | ||||
| #include <shared/License.h> | ||||
| #include <LicenseRequest.pb.h> | ||||
| 
 | ||||
| namespace license::server::database { | ||||
|     typedef size_t license_key_id_t; | ||||
|     typedef size_t upgrade_id_t; | ||||
| 
 | ||||
|     class DatabaseHandler; | ||||
|     class KeyIdCache { | ||||
|         public: | ||||
|             explicit KeyIdCache(DatabaseHandler*); | ||||
| 
 | ||||
|             std::string get_key_from_id(license_key_id_t keyId); | ||||
|             size_t  get_key_id_from_key(const std::string &key); | ||||
| 
 | ||||
|             void clear_cache(); | ||||
|         private: | ||||
|             struct CacheEntry { | ||||
|                 std::string key; | ||||
|                 size_t keyId; | ||||
|                 std::chrono::system_clock::time_point age; | ||||
|             }; | ||||
| 
 | ||||
|             int insert_entry(int, std::string*, std::string*); | ||||
| 
 | ||||
|             DatabaseHandler* handle; | ||||
| 
 | ||||
|             std::mutex entry_lock{}; | ||||
|             std::deque<std::unique_ptr<CacheEntry>> entries; | ||||
|     }; | ||||
| 
 | ||||
|     struct LicenseUpgrade { | ||||
|         upgrade_id_t upgrade_id{0}; | ||||
| 
 | ||||
|         license_key_id_t old_license_key_id{0}; | ||||
|         license_key_id_t new_license_key_id{0}; | ||||
| 
 | ||||
|         std::chrono::system_clock::time_point begin_timestamp{}; | ||||
|         std::chrono::system_clock::time_point end_timestamp{}; | ||||
| 
 | ||||
|         bool valid{false}; | ||||
|         size_t update_count{0}; | ||||
| 
 | ||||
|         std::string license_key{}; | ||||
| 
 | ||||
|         [[nodiscard]] inline bool not_yet_available() const { | ||||
|             return std::chrono::system_clock::now() < this->begin_timestamp && this->begin_timestamp.time_since_epoch().count() != 0; | ||||
|         } | ||||
| 
 | ||||
|         [[nodiscard]] inline bool is_expired() const { | ||||
|             return std::chrono::system_clock::now() > this->end_timestamp && this->end_timestamp.time_since_epoch().count() != 0; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     class DatabaseHandler { | ||||
|         public: | ||||
|             struct UserStatistics { | ||||
|                 uint64_t clients_online = 0; | ||||
|                 uint64_t web_clients_online = 0; | ||||
|                 uint64_t bots_online = 0; | ||||
|                 uint64_t queries_online = 0; | ||||
|                 uint64_t servers_online = 0; | ||||
|             }; | ||||
| 
 | ||||
|             struct ExtendedUserStatistics : public UserStatistics { | ||||
|                 std::string ip_address; | ||||
|             }; | ||||
| 
 | ||||
|             struct GlobalUserStatistics : public UserStatistics { | ||||
|                 uint64_t instance_online = 0; | ||||
|             }; | ||||
| 
 | ||||
|             struct DatabaseUserStatistics : public UserStatistics { | ||||
|                 std::chrono::system_clock::time_point timestamp{}; | ||||
|             }; | ||||
| 
 | ||||
|             struct GlobalVersionsStatistic { | ||||
|                 std::chrono::system_clock::time_point timestamp; | ||||
|                 std::map<std::string, uint32_t> versions; | ||||
|             }; | ||||
| 
 | ||||
|             struct UserHistory { | ||||
|                 std::chrono::system_clock::time_point begin{}; | ||||
|                 std::chrono::system_clock::time_point end{}; | ||||
|                 std::chrono::milliseconds interval{0}; | ||||
| 
 | ||||
|                 size_t record_count = 0; | ||||
|                 GlobalUserStatistics history[0]; | ||||
|             }; | ||||
|             static_assert(sizeof(UserHistory) == 32); | ||||
| 
 | ||||
|         public: | ||||
|             explicit DatabaseHandler(sql::SqlManager* database); | ||||
|             ~DatabaseHandler(); | ||||
| 
 | ||||
|             bool setup(std::string&); | ||||
| 
 | ||||
|             [[nodiscard]] inline std::shared_ptr<KeyIdCache> key_id_cache() { return this->id_cache; } | ||||
| 
 | ||||
|             bool validLicenseKey(const std::string& /* key */); | ||||
|             std::shared_ptr<LicenseInfo> query_license_info(const std::string&); | ||||
|             std::map<std::string, std::shared_ptr<LicenseInfo>> list_licenses(int offset = 0, int limit = -1); | ||||
| 
 | ||||
|             bool register_license_upgrade(license_key_id_t /* old key */, license_key_id_t /* new key */, const std::chrono::system_clock::time_point& /* begin */, const std::chrono::system_clock::time_point& /* end */, const std::string& /* key */); | ||||
|             std::unique_ptr<LicenseUpgrade> query_license_upgrade(upgrade_id_t /* upgrade id */); | ||||
|             void log_license_upgrade_attempt(upgrade_id_t /* upgrade id */, bool /* succeeded */, const std::string& /* server unique id */, const std::string& /* ip address */); | ||||
| 
 | ||||
|             bool register_license(const std::string& /* key */, const std::shared_ptr<LicenseInfo>& /* info */, const std::string& /* issuer */); | ||||
|             bool delete_license(const std::string& /* key */, bool /* full */ = false); | ||||
| 
 | ||||
|             bool logRequest(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const std::string& /* version */, int /* result */); | ||||
|             bool logStatistic(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const ts::proto::license::PropertyUpdateRequest&); | ||||
|             //std::deque<std::unique_ptr<ExtendedUserStatistics>> list_statistics_user(const std::string& key, const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end);
 | ||||
|             std::shared_ptr<UserHistory> list_statistics_user(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */); | ||||
|             std::deque<std::unique_ptr<GlobalVersionsStatistic>> list_statistics_version(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */); | ||||
| 
 | ||||
|             inline sql::SqlManager* sql() { return this->database; } | ||||
|         private: | ||||
|             std::shared_ptr<KeyIdCache> id_cache; | ||||
|             sql::SqlManager* database; | ||||
|     }; | ||||
| } | ||||
| @ -1,26 +1,30 @@ | ||||
| #include "log/LogUtils.h" | ||||
| #include "LicenseManager.h" | ||||
| #include "DatabaseHandler.h" | ||||
| 
 | ||||
| using namespace license; | ||||
| using namespace license::server; | ||||
| using namespace license::server::database; | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
| using KeyIdCache = LicenseManager::KeyIdCache; | ||||
| 
 | ||||
| KeyIdCache::KeyIdCache(license::server::LicenseManager *handle) : handle(handle) {} | ||||
| KeyIdCache::KeyIdCache(DatabaseHandler *handle) : handle(handle) {} | ||||
| 
 | ||||
| std::string KeyIdCache::getKey(size_t keyId) { | ||||
| void KeyIdCache::clear_cache() { | ||||
|     std::lock_guard elock{this->entry_lock}; | ||||
|     this->entries.clear(); | ||||
| } | ||||
| 
 | ||||
| std::string KeyIdCache::get_key_from_id(size_t keyId) { | ||||
|     { | ||||
|         threads::MutexLock lock(this->entry_lock); | ||||
|         std::lock_guard elock{this->entry_lock}; | ||||
| 
 | ||||
|         for(const auto& entry : this->entries) | ||||
|             if(entry->keyId == keyId) return entry->key; | ||||
|     } | ||||
| 
 | ||||
|     sql::command(this->handle->database, "SELECT `key`, `keyId` FROM `license` WHERE `keyId` = :key", variable{":key", keyId}) | ||||
|     sql::command(this->handle->sql(), "SELECT `key`, `keyId` FROM `license` WHERE `keyId` = :key", variable{":key", keyId}) | ||||
|             .query(&KeyIdCache::insert_entry, this); | ||||
|     { | ||||
|         threads::MutexLock lock(this->entry_lock); | ||||
|         std::lock_guard elock{this->entry_lock}; | ||||
| 
 | ||||
|         for(const auto& entry : this->entries) | ||||
|             if(entry->keyId == keyId) return entry->key; | ||||
| @ -28,20 +32,20 @@ std::string KeyIdCache::getKey(size_t keyId) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| size_t KeyIdCache::getKeyId(const std::string &key) { | ||||
| size_t KeyIdCache::get_key_id_from_key(const std::string &key) { | ||||
|     { | ||||
|         threads::MutexLock lock(this->entry_lock); | ||||
|         std::lock_guard elock{this->entry_lock}; | ||||
| 
 | ||||
|         for(const auto& entry : this->entries) | ||||
|             if(entry->key == key) return entry->keyId; | ||||
|     } | ||||
| 
 | ||||
|     auto result = sql::command(this->handle->database, "SELECT `key`, `keyId` FROM `license` WHERE `key` = :key", variable{":key", key}) | ||||
|     auto result = sql::command(this->handle->sql(), "SELECT `key`, `keyId` FROM `license` WHERE `key` = :key", variable{":key", key}) | ||||
|             .query(&KeyIdCache::insert_entry, this); | ||||
|     if(!result) | ||||
|         logError(LOG_GENERAL, "Failed to query key id for license. Query resulted in {}", result.fmtStr()); | ||||
|     { | ||||
|         threads::MutexLock lock(this->entry_lock); | ||||
|         std::lock_guard elock{this->entry_lock}; | ||||
| 
 | ||||
|         for(const auto& entry : this->entries) | ||||
|             if(entry->key == key) | ||||
| @ -52,16 +56,24 @@ size_t KeyIdCache::getKeyId(const std::string &key) { | ||||
| } | ||||
| 
 | ||||
| int KeyIdCache::insert_entry(int length, std::string *value, std::string *names) { | ||||
|     string key; | ||||
|     size_t keyId = 0; | ||||
|     for(int index = 0; index < length; index++) | ||||
|     string key{"unknown"}; | ||||
|     size_t keyId{0}; | ||||
| 
 | ||||
|     for(int index = 0; index < length; index++) { | ||||
|         if(names[index] == "key") | ||||
|             key = value[index]; | ||||
|         else if(names[index] == "keyId") | ||||
|             keyId = stoll(value[index]); | ||||
|             keyId = std::strtoll(value[index].c_str(), nullptr, 10); | ||||
|     } | ||||
|     if(!keyId) { | ||||
|         logWarning(LOG_GENERAL, "Failed to parse key id for key {}", key); | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         threads::MutexLock lock(this->entry_lock); | ||||
|         this->entries.push_back(new KeyIdCache::CacheEntry{key, keyId, system_clock::now()}); | ||||
|         auto entry = new KeyIdCache::CacheEntry{key, keyId, system_clock::now()}; | ||||
|         std::lock_guard elock{this->entry_lock}; | ||||
|         this->entries.emplace_back(entry); | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| @ -1,95 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <sql/SqlQuery.h> | ||||
| #include <shared/License.h> | ||||
| #include <LicenseRequest.pb.h> | ||||
| 
 | ||||
| namespace license { | ||||
| 	namespace server { | ||||
| 		class LicenseManager { | ||||
| 			public: | ||||
| 				class KeyIdCache { | ||||
| 					struct CacheEntry { | ||||
| 						std::string key; | ||||
| 						size_t keyId; | ||||
| 						std::chrono::system_clock::time_point age; | ||||
| 					}; | ||||
| 
 | ||||
| 					public: | ||||
| 						KeyIdCache(LicenseManager*); | ||||
| 
 | ||||
| 						void cache_keys(const std::deque<std::string>& /* keys */); | ||||
| 						std::string getKey(size_t keyId); | ||||
| 						size_t  getKeyId(const std::string&); | ||||
| 					private: | ||||
| 				        int insert_entry(int, std::string*, std::string*); | ||||
| 
 | ||||
| 						LicenseManager* handle; | ||||
| 						threads::Mutex entry_lock; | ||||
|                         threads::Mutex entry_update_lock; | ||||
| 						std::deque<CacheEntry*> entries; | ||||
| 				}; | ||||
| 
 | ||||
| 				struct UserStatistics { | ||||
| 					uint64_t clients_online = 0; | ||||
| 					uint64_t web_clients_online = 0; | ||||
| 					uint64_t bots_online = 0; | ||||
| 					uint64_t queries_online = 0; | ||||
| 					uint64_t servers_online = 0; | ||||
| 				}; | ||||
| 
 | ||||
| 				struct ExtendedUserStatistics : public UserStatistics{ | ||||
| 					std::string ip_address; | ||||
| 				}; | ||||
| 
 | ||||
| 				struct GlobalUserStatistics : public UserStatistics { | ||||
| 					uint64_t instance_online = 0; | ||||
| 				}; | ||||
| 
 | ||||
| 				struct DatabaseUserStatistics : public UserStatistics { | ||||
| 					std::chrono::system_clock::time_point timestamp{}; | ||||
| 				}; | ||||
| 
 | ||||
| 				struct GlobalVersionsStatistic { | ||||
| 					std::chrono::system_clock::time_point timestamp; | ||||
| 					std::map<std::string, uint32_t> versions; | ||||
| 				}; | ||||
| 
 | ||||
| 				struct UserHistory { | ||||
| 					std::chrono::system_clock::time_point begin{}; | ||||
| 					std::chrono::system_clock::time_point end{}; | ||||
| 					std::chrono::milliseconds interval{0}; | ||||
| 
 | ||||
| 					size_t record_count = 0; | ||||
| 					GlobalUserStatistics history[0]; | ||||
| 				}; | ||||
| 				static_assert(sizeof(UserHistory) == 32); | ||||
| 
 | ||||
| 			public: | ||||
| 				LicenseManager(sql::SqlManager* database); | ||||
| 				~LicenseManager(); | ||||
| 
 | ||||
| 				bool setup(std::string&); | ||||
| 
 | ||||
|                 bool validLicenseKey(const std::string& /* key */); | ||||
| 				std::shared_ptr<LicenseInfo> licenseInfo(const std::string&); | ||||
| 
 | ||||
| 				std::map<std::string, std::shared_ptr<LicenseInfo>> listLicenses(int offset = 0, int limit = -1); | ||||
| 
 | ||||
| 				bool registerLicense(const std::string& /* key */, const std::shared_ptr<LicenseInfo>& /* info */, const std::string& /* issuer */); | ||||
| 				bool updateLicenseInfo(const std::string&, const std::shared_ptr<LicenseInfo>&); | ||||
| 				bool deleteLicense(const std::string& /* key */, bool /* full */ = false); | ||||
| 
 | ||||
| 				bool logRequest(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const std::string& /* version */, int /* result */); | ||||
| 				bool logStatistic(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const ts::proto::license::PropertyUpdateRequest&); | ||||
| 				//std::deque<std::unique_ptr<ExtendedUserStatistics>> list_statistics_user(const std::string& key, const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end);
 | ||||
| 				std::shared_ptr<UserHistory> list_statistics_user(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */); | ||||
| 				std::deque<std::unique_ptr<GlobalVersionsStatistic>> list_statistics_version(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */); | ||||
| 
 | ||||
| 				inline sql::SqlManager* sql() { return this->database; } | ||||
| 			private: | ||||
| 				std::shared_ptr<KeyIdCache> id_cache; | ||||
| 				sql::SqlManager* database; | ||||
| 		}; | ||||
| 	} | ||||
| } | ||||
| @ -9,10 +9,8 @@ | ||||
| #include <LicenseRequest.pb.h> | ||||
| #include <shared/License.h> | ||||
| #include <shared/crypt.h> | ||||
| #include <misc/base64.h> | ||||
| #include <ThreadPool/ThreadHelper.h> | ||||
| #include "LicenseServer.h" | ||||
| #include "crypt.h" | ||||
| #include "UserManager.h" | ||||
| 
 | ||||
| using namespace std; | ||||
| @ -21,7 +19,7 @@ using namespace license; | ||||
| using namespace ts; | ||||
| 
 | ||||
| LicenseServer::LicenseServer(const sockaddr_in& addr, | ||||
|         std::shared_ptr<server::LicenseManager>  manager, | ||||
|         std::shared_ptr<server::database::DatabaseHandler>  manager, | ||||
|         shared_ptr<license::stats::StatisticManager> stats, | ||||
|         shared_ptr<license::web::WebStatistics> wstats, | ||||
|         std::shared_ptr<UserManager>  user_manager) : manager{std::move(manager)}, statistics{std::move(stats)}, web_statistics{std::move(wstats)}, user_manager{std::move(user_manager)} { | ||||
| @ -226,7 +224,7 @@ void LicenseServer::handleEventRead(int fd, short, void* ptrServer) { | ||||
| 	    server->closeConnection(client); | ||||
|         return; | ||||
|     } else if(read == 0) { | ||||
|         logError(LOG_LICENSE_CONTROLL, "Invalid read. Disconnecting remote client"); | ||||
|         logMessage(LOG_LICENSE_CONTROLL, "[CLIENT][" + client->address() + "] Received EOF for client. Removing client."); | ||||
|         event_del_noblock(client->network.readEvent); | ||||
| 	    server->closeConnection(client); | ||||
|         return; | ||||
| @ -253,7 +251,7 @@ void LicenseServer::handleEventAccept(int fd, short, void* ptrServer) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     client->protocol.state = protocol::HANDSCAKE; | ||||
|     client->protocol.state = protocol::HANDSCHAKE; | ||||
|     { | ||||
|         lock_guard lock(server->client_lock); | ||||
|         server->clients.push_back(client); | ||||
| @ -378,6 +376,8 @@ void LicenseServer::handleMessage(shared_ptr<ConnectedClient>& client, const std | ||||
| 			success = this->handleServerValidation(client, packet, error); | ||||
| 		} else if(packet.header.packetId == protocol::PACKET_CLIENT_PROPERTY_ADJUSTMENT) { | ||||
| 			success = this->handlePacketPropertyUpdate(client, packet, error); | ||||
| 		} else if(packet.header.packetId == protocol::PACKET_CLIENT_LICENSE_UPGRADE) { | ||||
| 		    success = this->handlePacketLicenseUpgrade(client, packet, error); | ||||
| 		} else if(packet.header.packetId == protocol::PACKET_CLIENT_AUTH_REQUEST) { | ||||
| 			success = this->handlePacketAuth(client, packet, error); | ||||
| 		} else if(packet.header.packetId == protocol::PACKET_CLIENT_LICENSE_CREATE_REQUEST) { | ||||
|  | ||||
| @ -11,7 +11,7 @@ | ||||
| #include <ThreadPool/Thread.h> | ||||
| #include <shared/License.h> | ||||
| #include <arpa/inet.h> | ||||
| #include "LicenseManager.h" | ||||
| #include "DatabaseHandler.h" | ||||
| 
 | ||||
| namespace license { | ||||
| 	namespace web { | ||||
| @ -44,11 +44,14 @@ namespace license { | ||||
| 
 | ||||
| 			    std::chrono::system_clock::time_point last_read; | ||||
| 			    std::string cryptKey = ""; | ||||
| 
 | ||||
| 			    int version{2}; /* current version is 3 */ | ||||
| 		    } protocol; | ||||
| 
 | ||||
| 		    ClientType type = ClientType::SERVER; | ||||
| 		    std::string username; | ||||
| 		    std::string key; | ||||
| 		    uint64_t key_pending_upgrade{0}; | ||||
| 		    std::string unique_identifier; | ||||
| 
 | ||||
| 		    bool invalid_license = false; | ||||
| @ -68,7 +71,7 @@ namespace license { | ||||
| 
 | ||||
|     class LicenseServer { | ||||
|         public: | ||||
|             explicit LicenseServer(const sockaddr_in&, std::shared_ptr<server::LicenseManager> , std::shared_ptr<stats::StatisticManager>  /* stats */, std::shared_ptr<web::WebStatistics>  /* web stats */, std::shared_ptr<UserManager>  /* user manager */); | ||||
|             explicit LicenseServer(const sockaddr_in&, std::shared_ptr<server::database::DatabaseHandler> , std::shared_ptr<stats::StatisticManager>  /* stats */, std::shared_ptr<web::WebStatistics>  /* web stats */, std::shared_ptr<UserManager>  /* user manager */); | ||||
|             ~LicenseServer(); | ||||
| 
 | ||||
|             bool start(); | ||||
| @ -91,7 +94,7 @@ namespace license { | ||||
| 
 | ||||
| 		    std::shared_ptr<web::WebStatistics> web_statistics; | ||||
| 		    std::shared_ptr<stats::StatisticManager> statistics; | ||||
| 		    std::shared_ptr<server::LicenseManager> manager; | ||||
| 		    std::shared_ptr<server::database::DatabaseHandler> manager; | ||||
| 		    std::shared_ptr<UserManager> user_manager; | ||||
| 
 | ||||
|             std::mutex client_lock; | ||||
| @ -117,6 +120,7 @@ namespace license { | ||||
| 		    bool handleDisconnect(std::shared_ptr<ConnectedClient>&, protocol::packet&, std::string& error); | ||||
| 		    bool handleHandshake(std::shared_ptr<ConnectedClient>&, protocol::packet&, std::string& error); | ||||
| 		    bool handleServerValidation(std::shared_ptr<ConnectedClient> &, protocol::packet &, std::string &error); | ||||
|             bool handlePacketLicenseUpgrade(std::shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error); | ||||
| 		    bool handlePacketPropertyUpdate(std::shared_ptr<ConnectedClient> &, protocol::packet &, std::string &error); | ||||
| 
 | ||||
| 		    bool handlePacketAuth(std::shared_ptr<ConnectedClient> &, protocol::packet &, std::string &error); | ||||
|  | ||||
| @ -22,6 +22,8 @@ inline void generate(char* buffer, size_t length){ | ||||
| 		buffer[index] = rand(); | ||||
| } | ||||
| 
 | ||||
| #define _str(x) #x | ||||
| 
 | ||||
| #define TEST_PROTOCOL_STATE(expected)               \ | ||||
| if(client->protocol.state != protocol::expected) {  \ | ||||
| 	error = "invalid protocol state";               \ | ||||
| @ -37,21 +39,23 @@ if(packet.data.length() < (expected)) {             \ | ||||
| #define PARSE_PROTO(class, var)                     \ | ||||
| ts::proto::license::class var;                      \ | ||||
| if(!var.ParseFromString(packet.data)) {             \ | ||||
| 	error = "invalid data!";                        \ | ||||
| 	error = "invalid data (" _str(class) ")!";      \ | ||||
| 	return false;                                   \ | ||||
| } | ||||
| 
 | ||||
| #define CRYPT_KEY_LENGTH 32 | ||||
| bool LicenseServer::handleHandshake(shared_ptr<ConnectedClient>& client, protocol::packet& packet, std::string &error) { | ||||
| 	TEST_PROTOCOL_STATE(HANDSCAKE); | ||||
| 	TEST_PROTOCOL_STATE(HANDSCHAKE); | ||||
| 	ENSURE_PACKET_SIZE(4); | ||||
| 	if((uint8_t) packet.data[0] != 0xC0 || (uint8_t) packet.data[1] != 0xFF || (uint8_t) packet.data[2] != 0xEE) { | ||||
| 		error = "invalid magic!"; | ||||
| 		return false; | ||||
| 	} | ||||
| 	if((uint8_t) packet.data[3] != LICENSE_PROT_VERSION) { | ||||
| 		error = "invalid version!"; | ||||
| 		return false; | ||||
| 
 | ||||
| 	client->protocol.version = (uint8_t) packet.data[3]; | ||||
| 	if(client->protocol.version < 2 || client->protocol.version > 3) { | ||||
| 	    error = "unsupported version"; | ||||
| 	    return false; | ||||
| 	} | ||||
| 
 | ||||
| 	bool manager = false; | ||||
| @ -67,7 +71,7 @@ bool LicenseServer::handleHandshake(shared_ptr<ConnectedClient>& client, protoco | ||||
| 	size_t buffer_index = 0; | ||||
| 	buffer[buffer_index++] = 0xAF; | ||||
| 	buffer[buffer_index++] = 0xFE; | ||||
| 	buffer[buffer_index++] = LICENSE_PROT_VERSION; | ||||
| 	buffer[buffer_index++] = client->protocol.version; | ||||
| 	le2be16(CRYPT_KEY_LENGTH, buffer, buffer_index, &buffer_index); | ||||
| 	memcpy(&buffer[buffer_index], buffer_cryptkey, CRYPT_KEY_LENGTH); | ||||
| 	buffer_index += CRYPT_KEY_LENGTH; | ||||
| @ -134,94 +138,129 @@ std::string string_to_hex(const std::string& input) | ||||
| } | ||||
| 
 | ||||
| bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) { | ||||
| 	TEST_PROTOCOL_STATE(SERVER_VALIDATION); | ||||
|     if(client->protocol.state != protocol::LICENSE_UPGRADE) /* server may wants to verify new license */ | ||||
|         TEST_PROTOCOL_STATE(SERVER_VALIDATION); | ||||
| 
 | ||||
| 	PARSE_PROTO(ServerValidation, pkt); | ||||
| 
 | ||||
| 	shared_ptr<License> remoteLicense = nullptr; | ||||
| 	if(pkt.licensed() && !pkt.has_license()) { | ||||
| 		//TODO shutdown server
 | ||||
| 	std::shared_ptr<License> remote_license{nullptr}; | ||||
| 	if(pkt.licensed() && (!pkt.has_license() || !pkt.has_license_info())) { | ||||
| 	    error = "invalid/missing license data"; | ||||
| 	    return false; | ||||
| 	} | ||||
| 
 | ||||
| 	if(!pkt.has_info()) { | ||||
| 		error = "invalid data or missing data"; | ||||
| 		return false; | ||||
| 	} | ||||
| 	if(pkt.has_license()){ //Client has license
 | ||||
| 		remoteLicense = readLocalLicence(pkt.license(), error); | ||||
| 		if(!remoteLicense) { | ||||
| 			error = "Could not read remote key: " + error; | ||||
| 
 | ||||
| 	if(pkt.licensed()){ //Client has license
 | ||||
| 		remote_license = readLocalLicence(pkt.license(), error); | ||||
| 		if(!remote_license) { | ||||
| 			error = "could not parse license (" + error + ")"; | ||||
| 			return false; | ||||
| 		}; | ||||
| 		logMessage(LOG_GENERAL, "[CLIENT][{}] Got remote license. Registered to {}. Key: {} (0x{})", client->address(), remoteLicense->owner(), base64::encode(remoteLicense->key()), string_to_hex(remoteLicense->key())); | ||||
| 		client->key = remoteLicense->key(); | ||||
| 	} else { } | ||||
|     if(pkt.licensed() && !pkt.has_license_info()) { | ||||
| 	    error = "Invalid content!"; | ||||
| 	    return false; | ||||
| 		} | ||||
| 
 | ||||
| 		logMessage(LOG_GENERAL, "[CLIENT][{}] Got remote license. Registered to {}. Key: {} (0x{})", client->address(), remote_license->owner(), base64::encode(remote_license->key()), string_to_hex(remote_license->key())); | ||||
| 		client->key = remote_license->key(); | ||||
| 	} | ||||
| 
 | ||||
|     logMessage(LOG_GENERAL, "[CLIENT][{}] Got some server information. TeaSpeak-Version: {} uname: {}", client->address(), pkt.info().version(), pkt.info().uname()); | ||||
| 	ts::proto::license::LicenseResponse response; | ||||
| 
 | ||||
| 	//Forces
 | ||||
| 
 | ||||
| 	ts::proto::license::LicenseResponse response{}; | ||||
| 	client->unique_identifier = pkt.info().has_unique_id() ? pkt.info().unique_id() : client->address(); | ||||
| 	if(remoteLicense && pkt.licensed()) { | ||||
|         auto info = this->manager->licenseInfo(remoteLicense->key()); | ||||
| 
 | ||||
| 	if(remote_license) { | ||||
|         auto info = this->manager->query_license_info(remote_license->key()); | ||||
| 
 | ||||
|         if(!info) { | ||||
| 	        response.mutable_blacklist()->set_reason("License hasn't been found."); | ||||
|             response.set_invalid_reason("license has not been found"); | ||||
| 	        response.set_valid(false); | ||||
| 
 | ||||
| 	        /*
 | ||||
|             logMessage(LOG_GENERAL, "[CLIENT][{}] Unknown license! Adding it to database!", client->address()); | ||||
|             auto db_info = make_shared<LicenseInfo>(); | ||||
|             db_info->start = system_clock::now(); | ||||
|             db_info->end = remoteLicense->end(); | ||||
|             db_info->last_name = "unknown"; | ||||
|             db_info->first_name = "unknown"; | ||||
|             db_info->username = remoteLicense->owner(); | ||||
|             db_info->email = "unknonw@unknown"; | ||||
|             db_info->creation = system_clock::now(); | ||||
|             db_info->type = remoteLicense->data.type; | ||||
|             this->manager->registerLicense(remoteLicense->key(), db_info, "teaforo-fix"); | ||||
|             info = this->manager->licenseInfo(remoteLicense->key()); | ||||
|             if(!info) { | ||||
|                 error = "could not insert key!"; | ||||
|                 return false; | ||||
|             } | ||||
|             */ | ||||
| 
 | ||||
| 	        logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license hasn't been found in database. Shutting down server!", client->address()); | ||||
|         } else { | ||||
|             response.set_update_pending(info->upgrade_id > 0); | ||||
|             client->key_pending_upgrade = info->upgrade_id; | ||||
| 	        if(info->deleted) { | ||||
| 		        response.mutable_blacklist()->set_reason("License has been deleted."); | ||||
|                 response.set_invalid_reason("license has been deleted"); | ||||
| 		        response.set_valid(false); | ||||
| 		        logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license has been deleted! Shutting down server!", client->address()); | ||||
| 	        } else { | ||||
| 		        fill_info(response.mutable_license_info(), info, remoteLicense->data.licenceKey); | ||||
| 		        response.set_valid(info->isValid()); | ||||
| 		        fill_info(response.mutable_license_info(), info, remote_license->data.licenceKey); | ||||
| 		        auto is_invalid = !info->isValid(); | ||||
| 		        if(is_invalid) { | ||||
|                     response.set_invalid_reason("license is invalid"); | ||||
|                     response.set_valid(false); | ||||
| 		        } else { | ||||
| 		            response.set_valid(true); | ||||
| 		        } | ||||
| 	        } | ||||
|         } | ||||
| 		this->manager->logRequest(remoteLicense->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid()); | ||||
| 		this->manager->logRequest(remote_license->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid()); | ||||
| 	} else { | ||||
| 	    response.set_valid(true); | ||||
| 	} | ||||
| 
 | ||||
| 	if(response.valid()) | ||||
| 		response.mutable_blacklist()->set_state(ts::proto::license::VALID); | ||||
| 	else { | ||||
| 		if(!response.has_license_info()) | ||||
| 			fill_info(response.mutable_license_info(), nullptr, ""); /* "Hack" for old clients which require a license. Else the server would not be stopped */ | ||||
| 		response.mutable_blacklist()->set_state(ts::proto::license::BLACKLISTED); /* "Hack" for all old clients */ | ||||
| 		client->invalid_license = true; | ||||
| 	if(client->protocol.version == 2) { | ||||
|         if(response.valid()) | ||||
|             response.mutable_blacklist()->set_state(ts::proto::license::VALID); | ||||
|         else { | ||||
|             response.mutable_blacklist()->set_reason(response.invalid_reason()); | ||||
|             response.mutable_blacklist()->set_state(ts::proto::license::BLACKLISTED); /* "Hack" for all old clients */ | ||||
| 
 | ||||
|             if(!response.has_license_info()) fill_info(response.mutable_license_info(), nullptr, ""); /* "Hack" for old clients which require a license. Else the server would not be stopped */ | ||||
|             client->invalid_license = true; | ||||
|         } | ||||
| 	} else { | ||||
| 	    if(!response.has_blacklist()) | ||||
| 	        response.mutable_blacklist()->set_state(ts::proto::license::VALID); | ||||
| 	} | ||||
| 	client->invalid_license = !response.valid(); | ||||
| 
 | ||||
| 	client->sendPacket(protocol::packet{protocol::PACKET_SERVER_VALIDATION_RESPONSE, response}); | ||||
| 	client->protocol.state = protocol::PROPERTY_ADJUSTMENT; | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool LicenseServer::handlePacketLicenseUpgrade(shared_ptr<ConnectedClient> &client, license::protocol::packet &packet, | ||||
|                                                std::string &error) { | ||||
|     TEST_PROTOCOL_STATE(PROPERTY_ADJUSTMENT); | ||||
|     PARSE_PROTO(RequestLicenseUpgrade, pkt); | ||||
| 
 | ||||
|     if(!client->key_pending_upgrade) { | ||||
|         error = "no update pending"; | ||||
|         return false; | ||||
|     } | ||||
|     ts::proto::license::LicenseUpgradeResponse response; | ||||
|     auto license_upgrade = this->manager->query_license_upgrade(client->key_pending_upgrade); | ||||
|     response.set_valid(false); | ||||
|     if(license_upgrade) { | ||||
|         if(!license_upgrade->valid) { | ||||
|             response.set_error_message("upgrade has been invalidated"); | ||||
|         } else if(license_upgrade->is_expired()) { | ||||
|             response.set_error_message("upgrade has been expired"); | ||||
|         } else if(license_upgrade->not_yet_available()) { | ||||
|             response.set_error_message("upgrade is not yet active."); | ||||
|         } else { | ||||
|             response.set_valid(true); | ||||
|             response.set_license_key(license_upgrade->license_key); | ||||
| 
 | ||||
|         } | ||||
|         this->manager->log_license_upgrade_attempt(license_upgrade->upgrade_id, response.valid(), client->unique_identifier, client->address()); | ||||
|         logMessage(LOG_GENERAL, "[CLIENT][{}] Client requested license upgrade {}. Result: {}", client->key_pending_upgrade, response.valid() ? "granted" : "denied (" + response.error_message() + ")"); | ||||
|     } else { | ||||
|         response.set_error_message("failed to find upgrade"); | ||||
|     } | ||||
| 
 | ||||
|     client->sendPacket(protocol::packet{protocol::PACKET_SERVER_LICENSE_UPGRADE_RESPONSE, response}); | ||||
|     client->protocol.state = protocol::LICENSE_UPGRADE; | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) { | ||||
| 	TEST_PROTOCOL_STATE(PROPERTY_ADJUSTMENT); | ||||
|     if(client->protocol.state != protocol::LICENSE_UPGRADE) /* LICENSE_UPGRADE could be skipped */ | ||||
|         TEST_PROTOCOL_STATE(PROPERTY_ADJUSTMENT); | ||||
| 	if(client->invalid_license) { | ||||
| 		ts::proto::license::PropertyUpdateResponse response; | ||||
| 		response.set_accepted(true); | ||||
| @ -330,33 +369,58 @@ bool LicenseServer::handlePacketLicenseCreate(shared_ptr<ConnectedClient> &clien | ||||
| 	TEST_PROTOCOL_STATE(MANAGER_CONNECTED); | ||||
| 	PARSE_PROTO(LicenseCreateRequest, pkt); | ||||
| 
 | ||||
|     logMessage(LOG_GENERAL, "[MANAGER][" + client->address() + "] Register new license to {} {} ({}). E-Mail: {}", pkt.issuer_first_name(), pkt.issuer_last_name(), pkt.issuer_username(), pkt.issuer_email()); | ||||
| 	auto old_license = pkt.has_old_key() ? hex::hex(pkt.old_key()) : "none"; | ||||
|     logMessage(LOG_GENERAL, "[MANAGER][" + client->address() + "] Register new license to {} {} ({}). E-Mail: {}. Old license: {}", pkt.issuer_first_name(), pkt.issuer_last_name(), pkt.issuer_username(), pkt.issuer_email(), old_license); | ||||
| 
 | ||||
|     ts::proto::license::LicenseCreateResponse response; | ||||
|     auto old_key_id{0}; | ||||
|     ts::proto::license::LicenseCreateResponse response{}; | ||||
|     if(pkt.has_old_key()) { | ||||
|         old_key_id = this->manager->key_id_cache()->get_key_id_from_key(pkt.old_key()); | ||||
|         if(old_key_id == 0) { | ||||
|             response.set_error("failed to find old license key in database"); | ||||
|             goto _send_response; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     auto db_info = make_shared<LicenseInfo>(); | ||||
|     db_info->start = system_clock::time_point() + milliseconds(pkt.begin()); | ||||
|     db_info->end = system_clock::time_point() + milliseconds(pkt.end()); | ||||
|     db_info->last_name = pkt.issuer_last_name(); | ||||
|     db_info->first_name = pkt.issuer_first_name(); | ||||
|     db_info->username = pkt.issuer_username(); | ||||
|     db_info->email = pkt.issuer_email(); | ||||
|     db_info->creation = system_clock::now(); | ||||
|     db_info->type = static_cast<LicenseType>(pkt.type()); | ||||
|     { | ||||
|         auto db_info = make_shared<LicenseInfo>(); | ||||
|         db_info->start = system_clock::time_point() + milliseconds(pkt.begin()); | ||||
|         db_info->end = system_clock::time_point() + milliseconds(pkt.end()); | ||||
|         db_info->last_name = pkt.issuer_last_name(); | ||||
|         db_info->first_name = pkt.issuer_first_name(); | ||||
|         db_info->username = pkt.issuer_username(); | ||||
|         db_info->email = pkt.issuer_email(); | ||||
|         db_info->creation = system_clock::now(); | ||||
|         db_info->type = static_cast<LicenseType>(pkt.type()); | ||||
| 
 | ||||
| 	auto license = license::createLocalLicence(db_info->type, db_info->end, db_info->first_name + db_info->last_name); | ||||
| 	auto parsed_license = license::readLocalLicence(license, error); | ||||
| 	if(!parsed_license) { | ||||
| 		response.set_error("failed to register license (parse)"); | ||||
| 	} else { | ||||
|         auto license = license::createLocalLicence(db_info->type, db_info->end, db_info->first_name + db_info->last_name); | ||||
|         auto parsed_license = license::readLocalLicence(license, error); | ||||
|         if(!parsed_license) { | ||||
|             response.set_error("failed to register license (parse)"); | ||||
|         } else { | ||||
|             if(!this->manager->register_license(parsed_license->key(), db_info, client->username)) { | ||||
|                 response.set_error("failed to register license"); | ||||
|                 goto _send_response; | ||||
|             } | ||||
| 
 | ||||
| 		if(!this->manager->registerLicense(parsed_license->key(), db_info, client->username)) { | ||||
| 			response.set_error("failed to register license"); | ||||
| 		} else { | ||||
| 			fill_info(response.mutable_license(), db_info, parsed_license->key()); | ||||
| 			response.set_exported_key(license); | ||||
| 		} | ||||
| 	} | ||||
|             if(old_key_id) { | ||||
|                 auto new_key_id = this->manager->key_id_cache()->get_key_id_from_key(parsed_license->key()); | ||||
|                 if(!new_key_id)  { | ||||
|                     response.set_error("failed to find new license in database"); | ||||
|                     goto _send_response; | ||||
|                 } | ||||
| 
 | ||||
|                 if(!this->manager->register_license_upgrade(old_key_id, new_key_id, std::chrono::system_clock::now(), db_info->end, license)) { | ||||
|                     response.set_error("failed to register license upgrade"); | ||||
|                     goto _send_response; | ||||
|                 } | ||||
|             } | ||||
|             fill_info(response.mutable_license(), db_info, parsed_license->key()); | ||||
|             response.set_exported_key(license); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 	_send_response: | ||||
|     client->sendPacket(protocol::packet{protocol::PACKET_SERVER_LICENSE_CREATE_RESPONSE, response}); | ||||
| 	return true; | ||||
| } | ||||
| @ -368,7 +432,7 @@ bool LicenseServer::handlePacketLicenseList(shared_ptr<ConnectedClient> &client, | ||||
| 	proto::license::LicenseListResponse response; | ||||
|     response.set_end(false); | ||||
| 
 | ||||
| 	for(const auto& info : this->manager->listLicenses(pkt.offset(), pkt.count())) { | ||||
| 	for(const auto& info : this->manager->list_licenses(pkt.offset(), pkt.count())) { | ||||
| 		auto entry = response.add_entries(); | ||||
| 		fill_info(entry, info.second, info.first); | ||||
| 	} | ||||
| @ -382,7 +446,7 @@ bool LicenseServer::handlePacketLicenseDelete(shared_ptr<ConnectedClient> &clien | ||||
| 	PARSE_PROTO(LicenseDeleteRequest, pkt); | ||||
| 
 | ||||
| 	proto::license::LicenseDeleteResponse response; | ||||
| 	response.set_succeed(this->manager->deleteLicense(pkt.key(), pkt.full())); | ||||
| 	response.set_succeed(this->manager->delete_license(pkt.key(), pkt.full())); | ||||
| 	client->sendPacket(protocol::packet{protocol::PACKET_CLIENT_DELETE_RESPONSE, response}); | ||||
| 
 | ||||
| 	return true; | ||||
|  | ||||
| @ -4,8 +4,10 @@ | ||||
| 
 | ||||
| #include <sql/SqlQuery.h> | ||||
| #include <misc/std_unique_ptr.h> | ||||
| 
 | ||||
| #include <utility> | ||||
| #include "StatisticManager.h" | ||||
| #include "LicenseManager.h" | ||||
| #include "DatabaseHandler.h" | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
| @ -76,16 +78,16 @@ system_clock::time_point HistoryStatistics::align_type(license::stats::HistorySt | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| StatisticManager::StatisticManager(const std::shared_ptr<license::server::LicenseManager> &manager) : license_manager(manager) {} | ||||
| StatisticManager::~StatisticManager() {} | ||||
| StatisticManager::StatisticManager(std::shared_ptr<license::server::database::DatabaseHandler> manager) : license_manager{std::move(manager)} {} | ||||
| StatisticManager::~StatisticManager() = default; | ||||
| 
 | ||||
| struct GeneralStatisticEntry { | ||||
| 	std::chrono::system_clock::time_point age; | ||||
| 	string unique_id = ""; | ||||
| 	uint64_t key_id = 0; | ||||
| 	uint64_t servers = 0; | ||||
| 	uint64_t clients = 0; | ||||
| 	uint64_t bots = 0; | ||||
| 	string unique_id{""}; | ||||
| 	uint64_t key_id{0}; | ||||
| 	uint64_t servers{0}; | ||||
| 	uint64_t clients{0}; | ||||
| 	uint64_t bots{0}; | ||||
| }; | ||||
| 
 | ||||
| void StatisticManager::reset_cache_general() { | ||||
| @ -93,7 +95,7 @@ void StatisticManager::reset_cache_general() { | ||||
| 	this->_general_statistics = nullptr; | ||||
| } | ||||
| 
 | ||||
| void parse_general_entry(deque<unique_ptr<GeneralStatisticEntry>>& entries, bool unique, int length, string* values, string* names) { | ||||
| void parse_general_entry(std::deque<std::unique_ptr<GeneralStatisticEntry>>& entries, bool unique, int length, string* values, string* names) { | ||||
| 	auto entry = make_unique<GeneralStatisticEntry>(); | ||||
| 	for(int index = 0; index < length; index++) { | ||||
| 		if(names[index] == "keyId") { | ||||
| @ -139,7 +141,7 @@ std::shared_ptr<GeneralStatistics> StatisticManager::general_statistics() { | ||||
| 
 | ||||
| 	auto result = sql::command(this->license_manager->sql(), "SELECT `keyId`, `unique_id`, `timestamp`,`server`,`clients`,`music` FROM `history_online` WHERE `timestamp` > :time ORDER BY `timestamp` ASC", | ||||
| 				variable{":time", duration_cast<milliseconds>(system_clock::now().time_since_epoch() - hours(2) - minutes(10)).count()}) //10min as buffer
 | ||||
| 			.query(std::function<decltype(parse_general_entry)>(parse_general_entry), entries, true); | ||||
| 			.query(std::function<decltype(parse_general_entry)>{parse_general_entry}, entries, true); | ||||
| 
 | ||||
| 	auto stats = make_shared<GeneralStatistics>(); | ||||
| 	for(auto& entry : entries) { | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| #include <mutex> | ||||
| #include <memory> | ||||
| #include <chrono> | ||||
| #include "LicenseManager.h" | ||||
| #include "DatabaseHandler.h" | ||||
| 
 | ||||
| namespace license { | ||||
| 	namespace stats { | ||||
| @ -49,19 +49,19 @@ namespace license { | ||||
| 			std::chrono::milliseconds period; | ||||
| 			HistoryType type; | ||||
| 
 | ||||
| 			std::shared_ptr<server::LicenseManager::UserHistory> statistics; | ||||
| 			std::shared_ptr<server::database::DatabaseHandler::UserHistory> statistics; | ||||
| 		}; | ||||
| 
 | ||||
| 		class StatisticManager { | ||||
| 			public: | ||||
| 				explicit StatisticManager(const std::shared_ptr<server::LicenseManager>& /* manager */); | ||||
| 				explicit StatisticManager(std::shared_ptr<server::database::DatabaseHandler>  /* manager */); | ||||
| 				virtual ~StatisticManager(); | ||||
| 
 | ||||
| 				void reset_cache_general(); | ||||
| 				std::shared_ptr<GeneralStatistics> general_statistics(); | ||||
| 				std::shared_ptr<HistoryStatistics> history(HistoryStatistics::HistoryType); | ||||
| 			private: | ||||
| 				std::shared_ptr<server::LicenseManager> license_manager; | ||||
| 				std::shared_ptr<server::database::DatabaseHandler> license_manager; | ||||
| 
 | ||||
| 				std::recursive_mutex _general_statistics_lock; | ||||
| 				std::recursive_mutex _general_statistics_generate_lock; | ||||
|  | ||||
| @ -17,7 +17,7 @@ using namespace ts::ssl; | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
| 
 | ||||
| WebStatistics::WebStatistics(const shared_ptr<LicenseManager> &manager, const std::shared_ptr<stats::StatisticManager>& stats) : license_manager(manager), statistics_manager(stats) {} | ||||
| WebStatistics::WebStatistics(const shared_ptr<database::DatabaseHandler> &manager, const std::shared_ptr<stats::StatisticManager>& stats) : license_manager(manager), statistics_manager(stats) {} | ||||
| WebStatistics::~WebStatistics() {} | ||||
| 
 | ||||
| #define SFAIL(message)                                                                          \ | ||||
|  | ||||
| @ -20,7 +20,7 @@ | ||||
| 
 | ||||
| namespace license { | ||||
| 	namespace server { | ||||
| 		class LicenseManager; | ||||
| 		class DatabaseHandler; | ||||
| 	} | ||||
| 	namespace stats { | ||||
| 		class StatisticManager; | ||||
| @ -47,7 +47,7 @@ namespace license { | ||||
| 					inline std::string client_prefix() { return peer_address ? net::to_string(peer_address->sin_addr) : "unconnected"; } | ||||
| 				}; | ||||
| 			public: | ||||
| 				WebStatistics(const std::shared_ptr<server::LicenseManager>& /* license manager */, const std::shared_ptr<stats::StatisticManager>& /* stats manager */); | ||||
| 				WebStatistics(const std::shared_ptr<server::database::DatabaseHandler>& /* license manager */, const std::shared_ptr<stats::StatisticManager>& /* stats manager */); | ||||
| 				virtual ~WebStatistics(); | ||||
| 
 | ||||
| 				bool start(std::string& /* error */, uint16_t /* port */, const std::shared_ptr<ts::ssl::SSLContext>& /* ssl */); | ||||
| @ -75,7 +75,7 @@ namespace license { | ||||
| 				bool _running = false; | ||||
| 				std::recursive_mutex running_lock; | ||||
| 
 | ||||
| 				std::shared_ptr<server::LicenseManager> license_manager; | ||||
| 				std::shared_ptr<server::database::DatabaseHandler> license_manager; | ||||
| 				std::shared_ptr<stats::StatisticManager> statistics_manager; | ||||
| 
 | ||||
| 				struct { | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
| #include <random> | ||||
| #include <ed25519/ed25519.h> | ||||
| 
 | ||||
| //#define NO_OPEN_SSL
 | ||||
| #define NO_OPEN_SSL | ||||
| #include <misc/digest.h> | ||||
| #include <cstring> | ||||
| #include <cassert> | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <chrono> | ||||
| #include <memory> | ||||
| @ -9,7 +10,7 @@ | ||||
| #include <Variable.h> | ||||
| 
 | ||||
| #define LICENSE_VERSION 1 | ||||
| #define LICENSE_PROT_VERSION 2 | ||||
| #define LICENSE_PROT_VERSION 3 | ||||
| #define MAGIC_NUMER 0xBADC0DED | ||||
| 
 | ||||
| namespace license { | ||||
| @ -17,11 +18,11 @@ namespace license { | ||||
|         class LicenseException : public std::exception { | ||||
|             public: | ||||
|                 LicenseException() = delete; | ||||
|                 LicenseException(std::string message) : errorMessage(std::move(message)) {} | ||||
|                 explicit LicenseException(std::string message) : errorMessage(std::move(message)) {} | ||||
|                 LicenseException(const LicenseException& ref) : errorMessage(ref.errorMessage) {} | ||||
|                 LicenseException(LicenseException&& ref) : errorMessage(std::move(ref.errorMessage)) {} | ||||
|                 explicit LicenseException(LicenseException&& ref) : errorMessage(std::move(ref.errorMessage)) {} | ||||
| 
 | ||||
|                 const char* what() const noexcept override; | ||||
|                 [[nodiscard]] const char* what() const noexcept override; | ||||
|             private: | ||||
|                 std::string errorMessage; | ||||
|         }; | ||||
| @ -36,7 +37,7 @@ namespace license { | ||||
|     	struct LicenseHeader { | ||||
| 		    uint16_t version; /* first 16 bytes const version */ | ||||
| 		    uint64_t crypt_key_seed; /* the seed */ | ||||
| 		    uint8_t crypt_key_verify_offset; /*  the first 8 bits determine how much generations (n * 3) and the rest what value is expeted. Due to the loss of 8 bits does the highest 8 bits get xored with the lowerst and 56 bits get compared */ | ||||
| 		    uint8_t crypt_key_verify_offset; /*  the first 8 bits determine how much generations (n * 3) and the rest what value is expected. Due to the loss of 8 bits does the highest 8 bits get xored with the lowerst and 56 bits get compared */ | ||||
| 		    uint8_t crypt_key_verify[5]; | ||||
|     	} __attribute__ ((__packed__)); | ||||
|     	static_assert(sizeof(LicenseHeader) == 16); | ||||
| @ -62,7 +63,7 @@ namespace license { | ||||
|     			/* Note for the write method: Write "private_buffer" if we're not able to resign the private data. As well enfore (assert) that we have a private buffer (e.g. by the read method) */ | ||||
| 
 | ||||
| 			    ~License(); | ||||
| 			    const std::vector<std::shared_ptr<const HierarchyEntry>> hierarchy() const { return this->_hierarchy; } | ||||
| 			    [[nodiscard]] std::vector<std::shared_ptr<const HierarchyEntry>> hierarchy() const { return this->_hierarchy; } | ||||
| 				bool push_entry(const std::shared_ptr<const HierarchyEntry>& /* entry */, size_t* /* index */ = nullptr); | ||||
| 
 | ||||
| 			    bool hierarchy_timestamps_valid(); | ||||
| @ -86,7 +87,7 @@ namespace license { | ||||
| 
 | ||||
| 			    std::string write(uint8_t& /* error */); | ||||
| 
 | ||||
| 			    bool private_data_editable() const; | ||||
| 			    [[nodiscard]] bool private_data_editable() const; | ||||
| 			    bool write_private_data(const LicensePrivateWriteOptions& /* write options */); | ||||
| 
 | ||||
| 			    [[nodiscard]] inline uint16_t version() const { return this->_version; } | ||||
| @ -118,17 +119,17 @@ namespace license { | ||||
| 
 | ||||
| 			    bool private_key_chain_valid(); | ||||
| 
 | ||||
| 			    bool has_meta(const std::string& key) const { return this->meta_data.count(key) > 0; } | ||||
| 			    std::string get_meta(const std::string& key) const { return this->meta_data.at(key); } | ||||
| 			    [[nodiscard]] bool has_meta(const std::string& key) const { return this->meta_data.count(key) > 0; } | ||||
| 			    [[nodiscard]] std::string get_meta(const std::string& key) const { return this->meta_data.at(key); } | ||||
| 			    void set_meta(const std::string& key, const std::string& value) { this->meta_data[key] = value; } | ||||
| 
 | ||||
| 			    /* if target is null just increase the offset! */ | ||||
| 			    bool write(uint8_t* /* target */, size_t& /* offset */, size_t /* length */, const LicensePrivateWriteOptions& /* options */); | ||||
| 
 | ||||
| 			    void register_raw_private_key(uint8_t /* index */, const uint8_t* /* key */); | ||||
|     			bool has_raw_private_key(uint8_t /* index */) const; | ||||
|     			[[nodiscard]] bool has_raw_private_key(uint8_t /* index */) const; | ||||
| 
 | ||||
|     			bool private_key_calculable(int /* index */) const; | ||||
|     			[[nodiscard]] bool private_key_calculable(int /* index */) const; | ||||
|     			bool calculate_private_key(uint8_t* /* response */, uint8_t /* index */) const; | ||||
| 		    private: | ||||
|     			std::weak_ptr<License> _handle; | ||||
| @ -168,7 +169,7 @@ namespace license { | ||||
| 			    } | ||||
| 
 | ||||
| 			    inline const uint8_t* body() const { return this->read_body; } | ||||
| 			    inline const size_t body_length() const { return this->read_body_length; } | ||||
| 			    inline size_t body_length() const { return this->read_body_length; } | ||||
| 
 | ||||
| 			    template <typename I> | ||||
| 			    inline I interpret_as() const { | ||||
| @ -240,7 +241,7 @@ namespace license { | ||||
|     						const std::chrono::system_clock::time_point &end, | ||||
|     						size_t buffer_size, uint8_t*& buffer_ptr | ||||
|                     ) { | ||||
| 					    auto result = std::shared_ptr<HierarchyEntry>(new HierarchyEntry{T::_type, pub_key, begin, end}); | ||||
| 					    auto result = std::make_shared<HierarchyEntry>(HierarchyEntry{T::_type, pub_key, begin, end}); | ||||
| 					    if(!result || !result->allocate_read_body(buffer_size)) return nullptr; | ||||
| 					    result->read_body_length = buffer_size; | ||||
| 					    buffer_ptr = result->read_body; | ||||
| @ -344,7 +345,8 @@ namespace license { | ||||
| 	    std::chrono::system_clock::time_point end; | ||||
| 	    std::chrono::system_clock::time_point creation; | ||||
| 
 | ||||
| 	    bool deleted = false; | ||||
| 	    bool deleted{false}; | ||||
| 	    uint32_t upgrade_id{0}; | ||||
| 
 | ||||
|         inline bool isValid() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); } | ||||
|     }; | ||||
| @ -358,10 +360,13 @@ namespace license { | ||||
| 			UNCONNECTED, | ||||
| 			CONNECTING, | ||||
| 			 | ||||
| 			HANDSCAKE, | ||||
| 			HANDSCHAKE, | ||||
| 			SERVER_VALIDATION, | ||||
| 			LICENSE_INFO, | ||||
| 
 | ||||
| 			PROPERTY_ADJUSTMENT, | ||||
| 			LICENSE_UPGRADE, | ||||
| 
 | ||||
| 			MANAGER_AUTHORIZATION, | ||||
| 			MANAGER_CONNECTED, | ||||
| 
 | ||||
| @ -371,8 +376,10 @@ namespace license { | ||||
| 		enum PacketType : uint8_t { | ||||
| 			PACKET_CLIENT_HANDSHAKE, | ||||
| 			PACKET_SERVER_HANDSHAKE, | ||||
| 
 | ||||
| 			PACKET_CLIENT_SERVER_VALIDATION, | ||||
| 			PACKET_SERVER_VALIDATION_RESPONSE, | ||||
| 
 | ||||
| 			PACKET_CLIENT_PROPERTY_ADJUSTMENT, | ||||
| 			PACKET_SERVER_PROPERTY_ADJUSTMENT, | ||||
| 
 | ||||
| @ -387,10 +394,18 @@ namespace license { | ||||
| 			PACKET_CLIENT_DELETE_REQUEST, | ||||
| 			PACKET_CLIENT_DELETE_RESPONSE, | ||||
| 
 | ||||
| 			PACKET_CLIENT_LICENSE_UPGRADE, | ||||
| 			PACKET_SERVER_LICENSE_UPGRADE_RESPONSE, | ||||
| 
 | ||||
| 			PACKET_PING = 0xF0, | ||||
| 			PACKET_DISCONNECT = 0xFF | ||||
| 		}; | ||||
| 
 | ||||
| 		struct packet_header { | ||||
| 			PacketType packetId{0}; | ||||
| 			uint16_t length{0}; | ||||
| 		}; | ||||
| 
 | ||||
| 		struct packet { | ||||
| 			struct { | ||||
| 				PacketType packetId{0}; | ||||
|  | ||||
| @ -1,11 +1,15 @@ | ||||
| #include <netinet/tcp.h> | ||||
| #include <fcntl.h> | ||||
| #include <log/LogUtils.h> | ||||
| #include <misc/memtracker.h> | ||||
| #include "crypt.h" | ||||
| 
 | ||||
| #define DEFINE_HELPER | ||||
| #include "LicenseRequest.h" | ||||
| #include "License.h" | ||||
| #include <csignal> | ||||
| #include <ThreadPool/ThreadHelper.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
| @ -16,7 +20,7 @@ using namespace license; | ||||
| #define CERR(message) LICENSE_FERR(this, CouldNotConnectException, message) | ||||
| 
 | ||||
| 
 | ||||
| LicenceRequest::LicenceRequest(const std::shared_ptr<LicenseRequestData> & license, const sockaddr_in& remoteAddr) : data(license) { | ||||
| LicenceRequest::LicenceRequest(const std::shared_ptr<LicenseRequestData> & license, const sockaddr_in& remoteAddr) : data{license} { | ||||
| #ifdef DEBUG_LICENSE_CLIENT | ||||
|     memtrack::allocated<LicenceRequest>(this); | ||||
| #endif | ||||
| @ -31,13 +35,7 @@ LicenceRequest::~LicenceRequest() { | ||||
| #endif | ||||
|     this->abortRequest(); | ||||
| 
 | ||||
|     if(this->closeThread) { | ||||
|         this->closeThread->join(); | ||||
|         delete this->closeThread; | ||||
|         this->closeThread = nullptr; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     threads::save_join(this->closeThread); | ||||
| 	delete this->currentFuture; | ||||
| 	this->currentFuture = nullptr; | ||||
| } | ||||
| @ -179,7 +177,7 @@ void LicenceRequest::beginRequest() { | ||||
| } | ||||
| 
 | ||||
| void LicenceRequest::handleConnected() { | ||||
| 	this->state = protocol::HANDSCAKE; | ||||
| 	this->state = protocol::HANDSCHAKE; | ||||
| 
 | ||||
| 	uint8_t handshakeBuffer[4]; | ||||
| 	handshakeBuffer[0] = 0xC0; | ||||
| @ -230,17 +228,16 @@ void LicenceRequest::disconnect(const std::string& message) { | ||||
| } | ||||
| 
 | ||||
| void LicenceRequest::closeConnection() { | ||||
| 	event *event_read, *event_write; | ||||
| 	event *event_read{nullptr}, *event_write{nullptr}; | ||||
|     { | ||||
|         lock_guard lock(this->lock); | ||||
|         lock_guard slock(this->lock); | ||||
|         if(this->state == protocol::UNCONNECTED) return; | ||||
| 
 | ||||
|         if(this->event_dispatch.get_id() == this_thread::get_id()) { //We could not close in the same thread as we read/write (we're joining it later)
 | ||||
|             if(this->state == protocol::DISCONNECTING) return; | ||||
| 
 | ||||
|             this->state = protocol::DISCONNECTING; | ||||
|             this->closeThread = new threads::Thread(THREAD_SAVE_OPERATIONS, [&]() { this->closeConnection(); }); | ||||
| 
 | ||||
|             this->closeThread = std::thread(&LicenceRequest::closeConnection, this); | ||||
| #ifdef DEBUG_LICENSE_CLIENT | ||||
| 	        if(this->verbose) { | ||||
| 		        debugMessage(LOG_GENERAL,"Running close in a new thread"); | ||||
| @ -251,11 +248,8 @@ void LicenceRequest::closeConnection() { | ||||
|         } | ||||
|         this->state = protocol::UNCONNECTED; | ||||
| 
 | ||||
| 	    event_read = this->event_read; | ||||
| 	    event_write = this->event_write; | ||||
| 
 | ||||
| 	    this->event_write = nullptr; | ||||
| 	    this->event_read = nullptr; | ||||
|         std::swap(this->event_read, event_read); | ||||
|         std::swap(this->event_write, event_write); | ||||
|     } | ||||
| 
 | ||||
|     if(event_read) { | ||||
| @ -271,7 +265,7 @@ void LicenceRequest::closeConnection() { | ||||
|     /* close before base shutdown (else epoll hangup) */ | ||||
| 	if(this->file_descriptor > 0) { | ||||
| 		shutdown(this->file_descriptor, SHUT_RDWR); | ||||
| 		close(this->file_descriptor); | ||||
| 		::close(this->file_descriptor); | ||||
| 	} | ||||
| 	this->file_descriptor = 0; | ||||
| 
 | ||||
|  | ||||
| @ -1,8 +1,7 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <thread> | ||||
| #include <protocol/buffers.h> | ||||
| #include <ThreadPool/Mutex.h> | ||||
| #include <ThreadPool/Thread.h> | ||||
| #include <netinet/in.h> | ||||
| #include <netinet/tcp.h> | ||||
| #include <event.h> | ||||
| @ -96,7 +95,8 @@ namespace license { | ||||
| 
 | ||||
|             void sendPacket(const protocol::packet&); | ||||
| 
 | ||||
|             std::function<void(const WebCertificate&)> callback_update_certificate{nullptr}; | ||||
|             std::function<void(const WebCertificate& /* certificate */)> callback_update_certificate{nullptr}; | ||||
|             std::function<void(const std::string& /* new key */)> callback_update_license{nullptr}; | ||||
|             bool verbose = true; | ||||
|         private: | ||||
|             std::shared_ptr<LicenseRequestData> data; | ||||
| @ -111,12 +111,12 @@ namespace license { | ||||
| 
 | ||||
| 		    std::string buffer{}; | ||||
| 
 | ||||
|             int file_descriptor = 0; | ||||
|             std::thread event_dispatch; | ||||
|             threads::Thread* closeThread = nullptr; | ||||
|             struct event_base* event_base = nullptr; | ||||
|             event* event_read = nullptr; | ||||
|             event* event_write = nullptr; | ||||
|             int file_descriptor{0}; | ||||
|             std::thread event_dispatch{}; | ||||
|             std::thread closeThread{}; | ||||
|             struct event_base* event_base{nullptr}; | ||||
|             struct event* event_read{nullptr}; | ||||
|             struct event* event_write{nullptr}; | ||||
| 
 | ||||
|             TAILQ_HEAD(, ts::buffer::RawBuffer) writeQueue; | ||||
| 
 | ||||
|  | ||||
| @ -25,7 +25,7 @@ void LicenceRequest::handlePacketDisconnect(const std::string& message) { | ||||
| } | ||||
| 
 | ||||
| void LicenceRequest::handlePacketHandshake(const std::string& data) { | ||||
| 	if(this->state != protocol::HANDSCAKE) LICENSE_FERR(this, InvalidResponseException, "Protocol state mismatch"); | ||||
| 	if(this->state != protocol::HANDSCHAKE) LICENSE_FERR(this, InvalidResponseException, "Protocol state mismatch"); | ||||
| 	if(data.length() < 3) LICENSE_FERR(this, InvalidResponseException, "Invalid packet size"); | ||||
| 
 | ||||
| 	if((uint8_t) data[0] != 0xAF || (uint8_t) data[1] != 0xFE) LICENSE_FERR(this, InvalidResponseException, "Invalid handshake"); | ||||
| @ -54,14 +54,13 @@ void LicenceRequest::handlePacketHandshake(const std::string& data) { | ||||
| } | ||||
| 
 | ||||
| void LicenceRequest::handlePacketLicenseInfo(const std::string& message) { | ||||
| 	ts::proto::license::LicenseResponse response; | ||||
| 	ts::proto::license::LicenseResponse response{}; | ||||
| 	if(!response.ParseFromString(message)) LICENSE_FERR(this, InvalidResponseException, "Could not parse response"); | ||||
| 
 | ||||
| 	auto result = make_shared<LicenseRequestResponse>(); | ||||
| 	auto licenseInfo = make_shared<LicenseInfo>(); | ||||
| 	if(!response.has_license_info() && this->data->license && response.valid() && response.blacklist().state() == ts::proto::license::VALID) { | ||||
| 	if(!response.has_license_info() && this->data->license && response.valid() && response.blacklist().state() == ts::proto::license::VALID) | ||||
| 		LICENSE_FERR(this, InvalidResponseException, "Missing license info"); | ||||
| 	} | ||||
| 
 | ||||
| 	if(this->data->license) { | ||||
| 		licenseInfo->type = (LicenseType) response.license_info().type(); | ||||
| @ -88,7 +87,11 @@ void LicenceRequest::handlePacketLicenseInfo(const std::string& message) { | ||||
| 	result->license = licenseInfo; | ||||
| 	this->response = result; | ||||
| 
 | ||||
| 	ts::proto::license::PropertyUpdateRequest infos; | ||||
| 	if(response.has_update_pending()) { | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	ts::proto::license::PropertyUpdateRequest infos{}; | ||||
| 	infos.set_speach_total(this->data->speach_total); | ||||
| 	infos.set_speach_dead(this->data->speach_dead); | ||||
| 	infos.set_speach_online(this->data->speach_online); | ||||
|  | ||||
							
								
								
									
										527
									
								
								license/shared/LicenseServerClient.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										527
									
								
								license/shared/LicenseServerClient.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,527 @@ | ||||
| //
 | ||||
| // Created by WolverinDEV on 23/02/2020.
 | ||||
| //
 | ||||
| 
 | ||||
| #include <csignal> | ||||
| #include <netinet/tcp.h> | ||||
| #include <event.h> | ||||
| #include <ThreadPool/ThreadHelper.h> | ||||
| #include <misc/endianness.h> | ||||
| #include "LicenseServerClient.h" | ||||
| #include "crypt.h" | ||||
| 
 | ||||
| using namespace license::client; | ||||
| 
 | ||||
| LicenseServerClient::Buffer* LicenseServerClient::Buffer::allocate(size_t capacity) { | ||||
|     static_assert(std::is_trivially_constructible<Buffer>::value); | ||||
| 
 | ||||
|     const auto allocated_bytes = sizeof(LicenseServerClient::Buffer) + capacity; | ||||
|     auto result = malloc(allocated_bytes); | ||||
|     if(!result) return nullptr; | ||||
| 
 | ||||
|     auto buffer = reinterpret_cast<LicenseServerClient::Buffer*>(result); | ||||
|     buffer->capacity = capacity; | ||||
|     buffer->fill = 0; | ||||
|     buffer->offset = 0; | ||||
|     buffer->data = (char*) result + sizeof(LicenseServerClient::Buffer); | ||||
|     return buffer; | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::Buffer::free(Buffer *ptr) { | ||||
|     static_assert(std::is_trivially_destructible<Buffer>::value); | ||||
| 
 | ||||
|     ::free(ptr); | ||||
| } | ||||
| 
 | ||||
| LicenseServerClient::LicenseServerClient(const sockaddr_in &address, int pversion) : protocol_version{pversion} { | ||||
|     memcpy(&this->network.address, &address, sizeof(address)); | ||||
|     TAILQ_INIT(&this->buffers.write); | ||||
| 
 | ||||
|     if(!this->buffers.read) | ||||
|         this->buffers.read = Buffer::allocate(1024 * 8); | ||||
| } | ||||
| 
 | ||||
| LicenseServerClient::~LicenseServerClient() { | ||||
|     this->close_connection(); | ||||
| 
 | ||||
|     if(this->buffers.read) | ||||
|         Buffer::free(this->buffers.read); | ||||
|     threads::save_join(this->network.event_dispatch, false); | ||||
| } | ||||
| 
 | ||||
| bool LicenseServerClient::start_connection(std::string &error) { | ||||
|     bool event_dispatch_spawned{false}; | ||||
| 
 | ||||
|     std::unique_lock slock{this->connection_lock}; | ||||
|     if(this->connection_state != ConnectionState::UNCONNECTED) { | ||||
|         error = "invalid connection state"; | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     this->connection_state = ConnectionState::CONNECTING; | ||||
|     this->communication.initialized = false; | ||||
| 
 | ||||
|     this->network.file_descriptor = socket(this->network.address.sin_family, SOCK_STREAM | SOCK_NONBLOCK, 0); | ||||
|     if(this->network.file_descriptor < 0) { | ||||
|         error = "failed to allocate socket"; | ||||
|         goto error_cleanup; | ||||
|     } | ||||
| 
 | ||||
|     signal(SIGPIPE, SIG_IGN); | ||||
| 
 | ||||
|     { | ||||
|         auto connect_state = ::connect(this->network.file_descriptor, reinterpret_cast<const sockaddr *>(&this->network.address), sizeof(this->network.address)); | ||||
|         if(connect_state < 0 && errno != EINPROGRESS) { | ||||
|             error = "connect() failed (" + std::string{strerror(errno)} + ")"; | ||||
|             goto error_cleanup; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         int enabled{1}, disabled{0}; | ||||
|         if(setsockopt(this->network.file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0); //CERR("could not set reuse addr");
 | ||||
|         if(setsockopt(this->network.file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0); // CERR("could not set no push");
 | ||||
| 
 | ||||
|         if(fcntl(this->network.file_descriptor, F_SETFD, fcntl(this->network.file_descriptor, F_GETFL, 0)  | FD_CLOEXEC | O_NONBLOCK) < 0); // CERR("Failed to set FD_CLOEXEC and O_NONBLOCK (" + std::to_string(errno) + ")");
 | ||||
|     } | ||||
| 
 | ||||
|     this->network.event_base = event_base_new(); | ||||
|     this->network.event_read = event_new(this->network.event_base, this->network.file_descriptor, EV_READ | EV_PERSIST, [](int, short e, void* _this) { | ||||
|         auto client = reinterpret_cast<LicenseServerClient*>(_this); | ||||
|         client->callback_read(e); | ||||
|     }, this); | ||||
|     this->network.event_write = event_new(this->network.event_base, this->network.file_descriptor, EV_WRITE, [](int, short e, void* _this) { | ||||
|         auto client = reinterpret_cast<LicenseServerClient*>(_this); | ||||
|         client->callback_write(e); | ||||
|     }, this); | ||||
| 
 | ||||
|     event_dispatch_spawned = true; | ||||
|     this->network.event_dispatch = std::thread([&] { | ||||
|         signal(SIGPIPE, SIG_IGN); | ||||
| 
 | ||||
|         event_add(this->network.event_read, nullptr); | ||||
| 
 | ||||
|         timeval connect_timeout{5, 0}; | ||||
|         event_add(this->network.event_write, &connect_timeout); | ||||
| 
 | ||||
|         auto event_base{this->network.event_base}; | ||||
|         event_base_loop(event_base, EVLOOP_NO_EXIT_ON_EMPTY); | ||||
|         event_base_free(event_base); | ||||
| 
 | ||||
|         //this ptr might be dangling
 | ||||
|     }); | ||||
| 
 | ||||
|     return true; | ||||
|     error_cleanup: | ||||
|     this->cleanup_network_resources(); | ||||
|     if(!event_dispatch_spawned) { | ||||
|         event_base_free(this->network.event_base); | ||||
|         this->network.event_base = nullptr; | ||||
|     } | ||||
|     this->connection_state = ConnectionState::UNCONNECTED; | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::close_connection() { | ||||
|     std::unique_lock slock{this->connection_lock}; | ||||
|     if(this->connection_state == ConnectionState::UNCONNECTED) return; | ||||
|     this->connection_state = ConnectionState::UNCONNECTED; | ||||
| 
 | ||||
|     this->cleanup_network_resources(); | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::cleanup_network_resources() { | ||||
|     const auto is_event_loop = this->network.event_dispatch.get_id() == std::this_thread::get_id(); | ||||
| 
 | ||||
|     if(this->network.event_read) { | ||||
|         if(is_event_loop) event_del_noblock(this->network.event_read); | ||||
|         else event_del_block(this->network.event_read); | ||||
|         event_free(this->network.event_read); | ||||
|         this->network.event_read = nullptr; | ||||
|     } | ||||
| 
 | ||||
|     if(this->network.event_write) { | ||||
|         if(is_event_loop) event_del_noblock(this->network.event_write); | ||||
|         else event_del_block(this->network.event_write); | ||||
|         event_free(this->network.event_write); | ||||
|         this->network.event_write = nullptr; | ||||
|     } | ||||
| 
 | ||||
|     if(this->network.event_base) { | ||||
|         event_base_loopexit(this->network.event_base, nullptr); | ||||
|         if(!is_event_loop) | ||||
|             threads::save_join(this->network.event_dispatch, false); | ||||
|         this->network.event_base = nullptr; /* event base has been saved by the event dispatcher and will be freed there */ | ||||
|     } | ||||
| 
 | ||||
|     if(this->network.file_descriptor) { | ||||
|         ::close(this->network.file_descriptor); | ||||
|         this->network.file_descriptor = 0; | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         std::lock_guard block{this->buffers.lock}; | ||||
|         auto buffer = TAILQ_FIRST(&this->buffers.write); | ||||
|         while(buffer) { | ||||
|             auto next = TAILQ_NEXT(buffer, tail); | ||||
|             Buffer::free(next); | ||||
|             buffer = next; | ||||
|         } | ||||
|         TAILQ_INIT(&this->buffers.write); | ||||
|         this->buffers.notify_empty.notify_all(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::callback_read(short events) { | ||||
|     constexpr static auto buffer_size{1024}; | ||||
| 
 | ||||
|     ssize_t read_bytes{0}; | ||||
|     char buffer[buffer_size]; | ||||
| 
 | ||||
|     read_bytes = recv(this->network.file_descriptor, buffer, buffer_size, MSG_DONTWAIT); | ||||
|     if(read_bytes <= 0) { | ||||
|         if(errno == EAGAIN) return; | ||||
|         std::unique_lock slock{this->connection_lock}; | ||||
| 
 | ||||
|         std::string disconnect_reason{}; | ||||
|         bool disconnect_expected{false}; | ||||
|         switch (this->connection_state) { | ||||
|             case ConnectionState::CONNECTING: | ||||
|                 disconnect_reason = "connect error (" + std::string{strerror(errno)} + ")"; | ||||
|                 disconnect_expected = false; | ||||
|                 break; | ||||
|             case ConnectionState::INITIALIZING: | ||||
|             case ConnectionState::CONNECTED: | ||||
|                 disconnect_reason = "read error (" + std::string{strerror(errno)} + ")"; | ||||
|                 disconnect_expected = false; | ||||
|                 break; | ||||
|             case ConnectionState::DISCONNECTING: | ||||
|                 disconnect_expected = true; | ||||
|                 break; | ||||
|             case ConnectionState::UNCONNECTED: | ||||
|                 return; /* we're obsolete */ | ||||
|         } | ||||
| 
 | ||||
|         if(auto callback{this->callback_disconnected}; callback) { | ||||
|             slock.unlock(); | ||||
|             callback(disconnect_expected, disconnect_reason); | ||||
|             slock.lock(); | ||||
|         } | ||||
| 
 | ||||
|         if(this->connection_state != ConnectionState::UNCONNECTED) { | ||||
|             this->cleanup_network_resources(); | ||||
|             this->connection_state = ConnectionState::UNCONNECTED; | ||||
|         } | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     this->handle_data(buffer, (size_t) read_bytes); | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::callback_write(short events) { | ||||
|     bool add_write_event{this->connection_state == ConnectionState::DISCONNECTING}; | ||||
|     if(events & EV_TIMEOUT) { | ||||
|         std::unique_lock slock{this->connection_lock}; | ||||
|         if(this->connection_state == ConnectionState::CONNECTING || this->connection_state == ConnectionState::INITIALIZING) { | ||||
|             /* connect timeout */ | ||||
|             if(auto callback{this->callback_disconnected}; callback) { | ||||
|                 slock.unlock(); | ||||
|                 callback(false, "connect timeout"); | ||||
|                 slock.lock(); | ||||
|             } | ||||
| 
 | ||||
|             if(this->connection_state != ConnectionState::UNCONNECTED) { | ||||
|                 this->cleanup_network_resources(); | ||||
|                 this->connection_state = ConnectionState::UNCONNECTED; | ||||
|             } | ||||
|         } else if(this->connection_state == ConnectionState::DISCONNECTING) { | ||||
|             /* disconnect timeout */ | ||||
|             this->cleanup_network_resources(); | ||||
|             this->connection_state = ConnectionState::UNCONNECTED; | ||||
|         } | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if(events & EV_WRITE) { | ||||
|         if(this->connection_state == ConnectionState::CONNECTING) | ||||
|             this->callback_socket_connected(); | ||||
| 
 | ||||
|         ssize_t written_bytes{0}; | ||||
| 
 | ||||
|         std::unique_lock block{this->buffers.lock}; | ||||
|         auto buffer = TAILQ_FIRST(&this->buffers.write); | ||||
|         if(!buffer) { | ||||
|             this->buffers.notify_empty.notify_all(); | ||||
|             return; | ||||
|         } | ||||
|         block.unlock(); | ||||
|         written_bytes = send(this->network.file_descriptor, (char*) buffer->data + buffer->offset, buffer->fill - buffer->offset, MSG_DONTWAIT); | ||||
| 
 | ||||
|         if(written_bytes <= 0) { | ||||
|             if(errno == EAGAIN) goto readd_event; | ||||
|             std::unique_lock slock{this->connection_lock}; | ||||
| 
 | ||||
|             std::string disconnect_reason{}; | ||||
|             bool disconnect_expected{false}; | ||||
|             switch (this->connection_state) { | ||||
|                 case ConnectionState::CONNECTING: | ||||
|                 case ConnectionState::INITIALIZING: | ||||
|                 case ConnectionState::CONNECTED: | ||||
|                     disconnect_reason = "write error (" + std::string{strerror(errno)} + ")"; | ||||
|                     disconnect_expected = false; | ||||
|                     break; | ||||
|                 case ConnectionState::DISCONNECTING: | ||||
|                     disconnect_expected = true; | ||||
|                     break; | ||||
|                 case ConnectionState::UNCONNECTED: | ||||
|                     return; /* we're obsolete */ | ||||
|             } | ||||
|             if(auto callback{this->callback_disconnected}; callback) { | ||||
|                 slock.unlock(); | ||||
|                 callback(disconnect_expected, disconnect_reason); | ||||
|                 slock.lock(); | ||||
|             } | ||||
| 
 | ||||
|             if(this->connection_state != ConnectionState::UNCONNECTED) { | ||||
|                 this->cleanup_network_resources(); | ||||
|                 this->connection_state = ConnectionState::UNCONNECTED; | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         buffer->offset += (size_t) written_bytes; | ||||
|         if(buffer->offset >= buffer->fill) { | ||||
|             assert(buffer->offset == buffer->fill); | ||||
|             block.lock(); | ||||
|             TAILQ_REMOVE(&this->buffers.write, buffer, tail); | ||||
|             if(!TAILQ_FIRST(&this->buffers.write)) { | ||||
|                 this->buffers.notify_empty.notify_all(); | ||||
|             } else { | ||||
|                 add_write_event = true; | ||||
|             } | ||||
|             block.unlock(); | ||||
|             Buffer::free(buffer); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(this->network.event_write && add_write_event) { | ||||
|         readd_event: | ||||
|         auto timeout = this->disconnect_timeout; | ||||
|         if(timeout.time_since_epoch().count() == 0) | ||||
|             event_add(this->network.event_write, nullptr); | ||||
|         else { | ||||
|             auto now = std::chrono::system_clock::now(); | ||||
|             struct timeval t{0, 1}; | ||||
|             if(now > timeout) { | ||||
|                 this->callback_write(EV_TIMEOUT); | ||||
|                 return; | ||||
|             } else { | ||||
|                 auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(timeout - now); | ||||
|                 auto seconds = std::chrono::duration_cast<std::chrono::seconds>(microseconds); | ||||
|                 microseconds -= seconds; | ||||
| 
 | ||||
|                 t.tv_usec = microseconds.count(); | ||||
|                 t.tv_sec = seconds.count(); | ||||
|             } | ||||
|             event_add(this->network.event_write, &t); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::handle_data(void *recv_buffer, size_t length) { | ||||
|     auto& buffer = this->buffers.read; | ||||
|     assert(buffer); | ||||
| 
 | ||||
|     if(buffer->capacity - buffer->offset - buffer->fill < length) { | ||||
|         if(buffer->capacity - buffer->fill > length) { | ||||
|             memcpy(buffer->data, (char*) buffer->data + buffer->offset, buffer->fill); | ||||
|             buffer->offset = 0; | ||||
|         } else { | ||||
|             auto new_buffer = Buffer::allocate(buffer->fill + length); | ||||
|             memcpy(new_buffer->data, (char*) buffer->data + buffer->offset, buffer->fill); | ||||
|             new_buffer->fill = buffer->fill; | ||||
|             Buffer::free(buffer); | ||||
|             buffer = new_buffer; | ||||
|         } | ||||
|     } | ||||
|     auto buffer_ptr = (char*) buffer->data; | ||||
|     auto& buffer_offset = buffer->offset; | ||||
|     auto& buffer_length = buffer->fill; | ||||
| 
 | ||||
|     memcpy((char*) buffer_ptr + buffer_offset + buffer_length, recv_buffer, length); | ||||
|     buffer_length += length; | ||||
| 
 | ||||
|     while(true) { | ||||
|         if(buffer_length < sizeof(protocol::packet_header)) return; | ||||
| 
 | ||||
|         auto header = reinterpret_cast<protocol::packet_header*>(buffer_ptr + buffer_offset); | ||||
|         if(header->length > 1024 * 8) { | ||||
|             if(auto callback{this->callback_disconnected}; callback) | ||||
|                 callback(false, "received a too large message"); | ||||
|             this->disconnect("received too large message", std::chrono::system_clock::time_point{}); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if(buffer_length < header->length + sizeof(protocol::packet_header)) return; | ||||
| 
 | ||||
|         this->handle_raw_packet(header->packetId, buffer_ptr + buffer_offset + sizeof(protocol::packet_header), header->length); | ||||
|         buffer_offset += header->length + sizeof(protocol::packet_header); | ||||
|         buffer_length -= header->length + sizeof(protocol::packet_header); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::send_message(protocol::PacketType type, const void *payload, size_t size) { | ||||
|     const auto packet_size = size + sizeof(protocol::packet_header); | ||||
|     auto buffer = Buffer::allocate(packet_size); | ||||
|     buffer->fill = packet_size; | ||||
| 
 | ||||
|     auto header = (protocol::packet_header*) buffer->data; | ||||
|     header->length = packet_size; | ||||
|     header->packetId = type; | ||||
|     memcpy((char*) buffer->data + sizeof(protocol::packet_header), payload, size); | ||||
|     if(this->communication.initialized) | ||||
|         xorBuffer((char*) buffer->data + sizeof(protocol::packet_header), size, this->communication.crypt_key.data(), this->communication.crypt_key.length()); | ||||
| 
 | ||||
|     std::lock_guard clock{this->connection_lock}; | ||||
|     if(this->connection_state == ConnectionState::UNCONNECTED || !this->network.event_write) { | ||||
|         Buffer::free(buffer); | ||||
|         return; | ||||
|     } | ||||
|     { | ||||
|         std::lock_guard block{this->buffers.lock}; | ||||
|         TAILQ_INSERT_TAIL(&this->buffers.write, buffer, tail); | ||||
|     } | ||||
|     event_add(this->network.event_write, nullptr); | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::disconnect(const std::string &message, std::chrono::system_clock::time_point timeout) { | ||||
|     auto now = std::chrono::system_clock::now(); | ||||
|     if(now > timeout) | ||||
|         timeout = now + std::chrono::seconds{timeout.time_since_epoch().count() ? 1 : 0}; | ||||
| 
 | ||||
|     std::unique_lock clock{this->connection_lock}; | ||||
|     if(this->connection_state == ConnectionState::DISCONNECTING) { | ||||
|         this->disconnect_timeout = std::min(this->disconnect_timeout, timeout); | ||||
|         if(this->network.event_write) | ||||
|             event_add(this->network.event_write, nullptr); /* let the write update the timeout */ | ||||
|         return; | ||||
|     } | ||||
|     this->disconnect_timeout = timeout; | ||||
| 
 | ||||
|     if(this->connection_state != ConnectionState::INITIALIZING && this->connection_state != ConnectionState::CONNECTED) { | ||||
|         clock.unlock(); | ||||
|         this->close_connection(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     this->connection_state = ConnectionState::DISCONNECTING; | ||||
|     if(this->network.event_read) | ||||
|         event_del_noblock(this->network.event_read); | ||||
|     clock.unlock(); | ||||
| 
 | ||||
|     this->send_message(protocol::PACKET_DISCONNECT, message.data(), message.length()); | ||||
| } | ||||
| 
 | ||||
| bool LicenseServerClient::await_disconnect() { | ||||
|     { | ||||
|         std::lock_guard clock{this->connection_lock}; | ||||
|         if(this->connection_state != ConnectionState::DISCONNECTING) | ||||
|             return this->connection_state == ConnectionState::UNCONNECTED; | ||||
|     } | ||||
|     /* state might change here, but when we're disconnected the write buffer will be empty */ | ||||
|     std::unique_lock block{this->buffers.lock}; | ||||
|     while(TAILQ_FIRST(&this->buffers.write)) | ||||
|         this->buffers.notify_empty.wait(block); | ||||
| 
 | ||||
|     return std::chrono::system_clock::now() <= this->disconnect_timeout; | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::callback_socket_connected() { | ||||
|     { | ||||
|         std::lock_guard clock{this->connection_lock}; | ||||
|         if(this->connection_state != ConnectionState::CONNECTING) return; | ||||
|         this->connection_state = ConnectionState::INITIALIZING; | ||||
|     } | ||||
| 
 | ||||
|     uint8_t handshakeBuffer[4]; | ||||
|     handshakeBuffer[0] = 0xC0; | ||||
|     handshakeBuffer[1] = 0xFF; | ||||
|     handshakeBuffer[2] = 0xEE; | ||||
|     handshakeBuffer[3] = this->protocol_version; | ||||
| 
 | ||||
|     this->send_message(protocol::PACKET_CLIENT_HANDSHAKE, handshakeBuffer, 4); | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::handle_raw_packet(license::protocol::PacketType type, void * buffer, size_t length) { | ||||
|     /* decrypt packet */ | ||||
|     if(this->communication.initialized) | ||||
|         xorBuffer((char*) buffer, length, this->communication.crypt_key.data(), this->communication.crypt_key.length()); | ||||
| 
 | ||||
|     if(type == protocol::PACKET_DISCONNECT) { | ||||
|         if(auto callback{this->callback_disconnected}; callback) | ||||
|             callback(false, std::string{(const char*) buffer, length}); | ||||
|         this->close_connection(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if(!this->communication.initialized) { | ||||
|         if(type != protocol::PACKET_SERVER_HANDSHAKE) { | ||||
|             if(auto callback{this->callback_disconnected}; callback) | ||||
|                 callback(false, "expected handshake packet"); | ||||
|             this->disconnect("expected handshake packet", std::chrono::system_clock::time_point{}); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this->handle_handshake_packet(buffer, length); | ||||
|         this->communication.initialized = true; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if(auto callback{this->callback_message}; callback) | ||||
|         callback(type, buffer, length); | ||||
|     else | ||||
|         ; //TODO: Print error?
 | ||||
| } | ||||
| 
 | ||||
| void LicenseServerClient::handle_handshake_packet(void *buffer, size_t length) { | ||||
|     const auto data_ptr = (const char*) buffer; | ||||
| 
 | ||||
|     std::string error{}; | ||||
|     if(this->connection_state != ConnectionState::INITIALIZING) { | ||||
|         error = "invalid protocol state"; | ||||
|         goto handle_error; | ||||
|     } | ||||
| 
 | ||||
|     if(length < 5) { | ||||
|         error = "invalid packet size"; | ||||
|         goto handle_error; | ||||
|     } | ||||
| 
 | ||||
|     if((uint8_t) data_ptr[0] != 0xAF || (uint8_t) data_ptr[1] != 0xFE) { | ||||
|         error = "invalid handshake signature"; | ||||
|         goto handle_error; | ||||
|     } | ||||
|     if((uint8_t) data_ptr[2] != this->protocol_version) { | ||||
|         error = "Invalid license protocol version. Please update TeaSpeak!"; | ||||
|         goto handle_error; | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         auto key_length = be2le16(data_ptr, 3); | ||||
|         if(length < key_length + 5) { | ||||
|             error = "invalid packet size"; | ||||
|             goto handle_error; | ||||
|         } | ||||
|         this->communication.crypt_key = std::string(data_ptr + 5, key_length); | ||||
|         this->communication.initialized = true; | ||||
|     } | ||||
| 
 | ||||
|     if(auto callback{this->callback_connected}; callback) | ||||
|         callback(); | ||||
|     return; | ||||
| 
 | ||||
|     handle_error: | ||||
|     if(auto callback{this->callback_disconnected}; callback) | ||||
|         callback(false, error); | ||||
|     this->disconnect(error, std::chrono::system_clock::time_point{}); | ||||
| } | ||||
							
								
								
									
										97
									
								
								license/shared/LicenseServerClient.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								license/shared/LicenseServerClient.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <protocol/buffers.h> | ||||
| #include <netinet/in.h> | ||||
| #include <functional> | ||||
| #include <mutex> | ||||
| 
 | ||||
| #include "./License.h" | ||||
| 
 | ||||
| namespace license::client { | ||||
|     class LicenseServerClient { | ||||
|         public: | ||||
|             enum ConnectionState { | ||||
|                 CONNECTING, | ||||
|                 INITIALIZING, | ||||
|                 CONNECTED, | ||||
|                 DISCONNECTING, | ||||
| 
 | ||||
|                 UNCONNECTED | ||||
|             }; | ||||
|             typedef std::function<void()> callback_connected_t; | ||||
|             typedef std::function<void(protocol::PacketType /* type */, const void* /* payload */, size_t /* length */)> callback_message_t; | ||||
|             typedef std::function<void(bool /* expected */, const std::string& /* reason */)> callback_disconnect_t; | ||||
| 
 | ||||
|             explicit LicenseServerClient(const sockaddr_in&, int /* protocol version */); | ||||
|             virtual ~LicenseServerClient(); | ||||
| 
 | ||||
|             bool start_connection(std::string& /* error */); | ||||
|             void send_message(protocol::PacketType /* type */, const void* /* buffer */, size_t /* length */); | ||||
|             void close_connection(); | ||||
| 
 | ||||
|             void disconnect(const std::string& /* reason */, std::chrono::system_clock::time_point /* timeout */); | ||||
|             bool await_disconnect(); | ||||
| 
 | ||||
| 
 | ||||
|             /*
 | ||||
|              * Events will be called within the event loop. | ||||
|              * All methods are save to call. | ||||
|              * When close_connection or await_disconnect has been called these methods will not be called anymore. | ||||
|              */ | ||||
|             callback_message_t callback_message{nullptr}; | ||||
|             callback_connected_t callback_connected{nullptr}; | ||||
|             callback_disconnect_t callback_disconnected{nullptr}; | ||||
| 
 | ||||
|             const int protocol_version; | ||||
|         private: | ||||
|             std::mutex connection_lock{}; | ||||
|             ConnectionState connection_state{ConnectionState::UNCONNECTED}; | ||||
|             std::chrono::system_clock::time_point disconnect_timeout{}; | ||||
| 
 | ||||
|             struct Buffer { | ||||
|                 static Buffer* allocate(size_t /* capacity */); | ||||
|                 static void free(Buffer* /* ptr */); | ||||
| 
 | ||||
|                 void* data; | ||||
|                 size_t capacity; | ||||
|                 size_t fill; | ||||
|                 size_t offset; | ||||
| 
 | ||||
|                 TAILQ_ENTRY(Buffer) tail; | ||||
|             }; | ||||
| 
 | ||||
|             /* modify everything here only within the event base, or when exited when connection_lock is locked */ | ||||
|             struct { | ||||
|                 sockaddr_in address{}; | ||||
|                 int file_descriptor{0}; | ||||
| 
 | ||||
|                 std::thread event_dispatch{}; | ||||
|                 struct event_base* event_base{nullptr}; /* will be cleaned up by the event loop! */ | ||||
|                 struct event* event_read{nullptr}; | ||||
|                 struct event* event_write{nullptr}; | ||||
|             } network; | ||||
| 
 | ||||
|             struct { | ||||
|                 std::mutex lock{}; | ||||
|                 std::condition_variable notify_empty{}; | ||||
| 
 | ||||
|                 Buffer* read{nullptr}; /* must noch be accessed via lock because only the event loop uses it */ | ||||
|                 TAILQ_HEAD(, Buffer) write; | ||||
|             } buffers; | ||||
| 
 | ||||
|             struct { | ||||
|                 bool initialized{false}; | ||||
|                 std::string crypt_key{}; | ||||
|             } communication; | ||||
| 
 | ||||
|             void callback_read(short /* events */); | ||||
|             void callback_write(short /* events */); | ||||
|             void callback_socket_connected(); | ||||
| 
 | ||||
|             void cleanup_network_resources(); | ||||
| 
 | ||||
|             void handle_data(void*, size_t); | ||||
|             void handle_raw_packet(protocol::PacketType /* type */, void* /* payload */, size_t /* length */); | ||||
|             void handle_handshake_packet(void* /* payload */, size_t /* length */); | ||||
|     }; | ||||
| } | ||||
| @ -124,7 +124,6 @@ set(SERVER_SOURCE_FILES | ||||
|         src/server/WebIoManager.cpp | ||||
|         src/client/SpeakingClient.cpp | ||||
| 
 | ||||
|         src/lincense/LicenseHelper.cpp | ||||
|         ../shared/src/ssl/SSLManager.cpp | ||||
| 
 | ||||
|         src/manager/SqlDataManager.cpp | ||||
| @ -151,7 +150,7 @@ if (COMPILE_WEB_CLIENT) | ||||
|             src/client/web/WSWebClient.cpp | ||||
|             src/client/web/SampleHandler.cpp | ||||
|             src/client/web/VoiceBridge.cpp | ||||
|             src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h) | ||||
|             src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h src/lincense/LicenseService.cpp src/lincense/LicenseService.h) | ||||
| endif () | ||||
| 
 | ||||
| add_executable(PermHelper helpers/permgen.cpp) | ||||
|  | ||||
| @ -3,3 +3,8 @@ Build opus: | ||||
| 
 | ||||
| Build libevent 2.1.8 (with -fPIC) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| Local licenses test: | ||||
| Base: AQDm4ty6AAAAACnNuqvy++NGfMJU+Bvo5412Wi5jM5/JmmYyDbcxWKNaJV0FF1jpXtSrss3Gm7RUEQ6CdEEhPdyH5OLcugAAAAAp9cb+t1r6P5zGt9fi+QbAOroWM7LqY0B+iy055XrZxX+r8bfP50esuU9qypU9DUtnE9+qJ2gZmOgHcVkHJ4JhN4Db2ZmOkA4dpjGWfY2L6QJanPCy6HsVOm1/1LasZv9Om1/4uWHYLfab0n2+FNajxbcyDY3FAI6+rLBlQzGErtE= | ||||
| New: AQCJON26AAAAACnNuqvy++NGfMJU+Bvo5412Wi5jM5/JmmYyDbcxWKNaJV0FF1jpXtSrss3Gm7RUEQ6CdEEhPdyHizjdugAAAAAp6d+xUgRteE54jqv3OXoRS946xsWKUtJBFkB4pPMT1+pIrXhM3/CTWjpXG8KDLoa1DEjwguolTXJ+RCvySR7Bd4Db2ZmOkA4dpjGWfY2L6QJ3bxZH8OTPFlV/1lU9x1r5QPPOpFmZt68h6VZDz4y9AVNXO+JZPHu24Pre7hTnyhg= | ||||
| @ -1,6 +1,5 @@ | ||||
| #include <client/linux/handler/exception_handler.h> | ||||
| #include <iostream> | ||||
| #include <misc/endianness.h> | ||||
| #include <misc/strobf.h> | ||||
| #include <CXXTerminal/QuickTerminal.h> | ||||
| #include <event2/thread.h> | ||||
| @ -14,7 +13,6 @@ | ||||
| #include "src/server/file/FileServer.h" | ||||
| #include "src/terminal/CommandHandler.h" | ||||
| #include "src/client/InternalClient.h" | ||||
| #include "src/music/MusicBotManager.h" | ||||
| #include "src/SignalHandler.h" | ||||
| #include "src/build.h" | ||||
| 
 | ||||
| @ -126,8 +124,11 @@ int main(int argc, char** argv) { | ||||
|         assert(evthread_use_pthreads_result == 0); | ||||
|         (void) evthread_use_pthreads_result; | ||||
|     } | ||||
|     terminal::install(); | ||||
|     if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; } | ||||
| 
 | ||||
|     if(!arguments.cmdOptionExists("--no-terminal")) { | ||||
|         terminal::install(); | ||||
|         if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; } | ||||
|     } | ||||
|     assert(ts::property::impl::validateUnique()); | ||||
| 
 | ||||
|     if(arguments.cmdOptionExists("--help") || arguments.cmdOptionExists("-h")) { | ||||
| @ -276,6 +277,8 @@ int main(int argc, char** argv) { | ||||
|     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')); | ||||
| @ -309,13 +312,13 @@ int main(int argc, char** argv) { | ||||
|     logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true)); | ||||
|     logMessage(LOG_GENERAL, "Starting music providers"); | ||||
| 
 | ||||
|     terminal::instance()->setPrompt("§aStarting server. §7[§aloading music§7]"); | ||||
|     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()); | ||||
|     } | ||||
| 
 | ||||
|     terminal::instance()->setPrompt("§aStarting server. §7[§aloading geoloc§7]"); | ||||
|     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) | ||||
| @ -342,7 +345,7 @@ int main(int argc, char** argv) { | ||||
|             errorMessage = ""; | ||||
|         } | ||||
|     } | ||||
|     terminal::instance()->setPrompt("§aStarting server. §7[§aloading sql§7]"); | ||||
|     if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§aloading sql§7]"); | ||||
| 
 | ||||
|     sql = new ts::server::SqlDataManager(); | ||||
|     if(!sql->initialize(errorMessage)) { | ||||
| @ -358,7 +361,7 @@ int main(int argc, char** argv) { | ||||
|         goto stopApp; | ||||
|     } | ||||
| 
 | ||||
|     terminal::instance()->setPrompt("§aStarting server. §7[§astarting instance§7]"); | ||||
|     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()) | ||||
| @ -384,13 +387,15 @@ int main(int argc, char** argv) { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     terminal::instance()->setPrompt("§7> §f"); | ||||
|     if(terminal::instance()) terminal::instance()->setPrompt("§7> §f"); | ||||
|     while(mainThreadActive) { | ||||
|         usleep(5 * 1000); | ||||
| 
 | ||||
|         if(terminal::instance()->linesAvailable() > 0){ | ||||
|             while(!(line = terminal::instance()->readLine("§7> §f")).empty()) | ||||
|                 threads::Thread(THREAD_DETACHED, [line](){ terminal::chandler::handleCommand(line); }); | ||||
|         if(terminal::instance()) { | ||||
|             if(terminal::instance()->linesAvailable() > 0){ | ||||
|                 while(!(line = terminal::instance()->readLine("§7> §f")).empty()) | ||||
|                     threads::Thread(THREAD_DETACHED, [line](){ terminal::chandler::handleCommand(line); }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -411,7 +416,8 @@ int main(int argc, char** argv) { | ||||
|     logMessageFmt(true, LOG_GENERAL, "Application suspend successful!"); | ||||
| 
 | ||||
|     logger::uninstall(); | ||||
|     terminal::uninstall(); | ||||
|     if(terminal::active()) | ||||
|         terminal::uninstall(); | ||||
|     mainThreadDone = true; | ||||
|     return 0; | ||||
| } | ||||
| @ -341,7 +341,7 @@ vector<string> config::parseConfig(const std::string& path) { | ||||
|     } | ||||
|     cfgStream.close(); | ||||
| 
 | ||||
|     map<string,deque<string>> comments; | ||||
|     std::map<std::string, std::deque<std::string>> comments; | ||||
|     try { | ||||
|         int config_version; | ||||
|         string teaspeak_license; | ||||
| @ -351,9 +351,9 @@ vector<string> config::parseConfig(const std::string& path) { | ||||
|             build_comments(comments, bindings); | ||||
|         } | ||||
|         if(config_version > CURRENT_CONFIG_VERSION) { | ||||
|             errors.push_back("Given config version is higher that currently supported 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.push_back("Attention: Decreasing the version could may lead to data loss!"); | ||||
|             errors.emplace_back("Attention: Decreasing the version could may lead to data loss!"); | ||||
|             return errors; | ||||
|         } | ||||
|         { | ||||
| @ -462,15 +462,13 @@ vector<string> config::parseConfig(const std::string& path) { | ||||
|             } | ||||
| 
 | ||||
|             if(!config::license){ | ||||
|                 logErrorFmt(true, LOG_GENERAL, strobf("The given license isn't valid!").string()); | ||||
|                 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){ | ||||
|                 errors.emplace_back(strobf("Invalid license code!").string()); | ||||
|                 return errors; | ||||
|             } | ||||
| 
 | ||||
|             /*
 | ||||
|             if(!config::license->isValid()) { | ||||
|                 if(config::license->data.type == license::LicenseType::INVALID) { | ||||
|                     errors.emplace_back(strobf("Give license isn't valid!").string()); | ||||
| @ -482,6 +480,7 @@ vector<string> config::parseConfig(const std::string& path) { | ||||
|                 teaspeak_license = "none"; | ||||
|                 goto license_parsing; | ||||
|             } | ||||
|              */ | ||||
|         } | ||||
| 
 | ||||
|         { | ||||
| @ -548,8 +547,7 @@ vector<string> config::parseConfig(const std::string& path) { | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> config::reload() { | ||||
| 
 | ||||
|     vector<string> errors; | ||||
|     std::vector<std::string> errors; | ||||
|     saveConfig = false; | ||||
| 
 | ||||
|     ifstream cfgStream(_config_path); | ||||
| @ -589,6 +587,53 @@ std::vector<std::string> config::reload() { | ||||
|     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"; }; | ||||
|  | ||||
| @ -29,6 +29,7 @@ namespace ts::config { | ||||
|         std::function<void(const std::string&)> read_argument; | ||||
|     }; | ||||
| 
 | ||||
|     extern bool update_license(std::string& /* error */, const std::string& /* new license */); | ||||
|     extern std::vector<std::string> parseConfig(const std::string& /* path */); | ||||
|     extern std::vector<std::string> reload(); | ||||
|     extern std::deque<std::shared_ptr<EntryBinding>> create_bindings(); | ||||
|  | ||||
| @ -44,7 +44,11 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) { | ||||
|     this->statistics = make_shared<stats::ConnectionStatistics>(nullptr, true); | ||||
|     this->statistics->measure_bandwidths(true); | ||||
| 
 | ||||
|     this->licenseHelper = make_shared<license::LicenseHelper>(); | ||||
|     std::string error_message{}; | ||||
|     this->license_service_ = std::make_shared<license::LicenseService>(); | ||||
|     if(!this->license_service_->initialize(error_message)) { | ||||
|         logCritical(LOG_INSTANCE, strobf("Failed to the license service: {}").string(), error_message); | ||||
|     } | ||||
|     this->dbHelper = new DatabaseHelper(this->getSql()); | ||||
| 
 | ||||
|     this->_properties = new Properties(); | ||||
| @ -214,7 +218,6 @@ InstanceHandler::~InstanceHandler() { | ||||
|     globalServerAdmin = nullptr; | ||||
|     _musicRoot = nullptr; | ||||
| 
 | ||||
|     licenseHelper = nullptr; | ||||
|     statistics = nullptr; | ||||
|     tick_manager = nullptr; | ||||
| } | ||||
| @ -453,6 +456,8 @@ void InstanceHandler::stopInstance() { | ||||
|     this->sslMgr = nullptr; | ||||
| 
 | ||||
|     this->web_event_loop = nullptr; | ||||
| 
 | ||||
|     this->license_service_->shutdown(); | ||||
| } | ||||
| 
 | ||||
| void InstanceHandler::tickInstance() { | ||||
| @ -470,7 +475,7 @@ void InstanceHandler::tickInstance() { | ||||
|         } | ||||
|         { | ||||
|             ALARM_TIMER(t, strobf("InstanceHandler::tickInstance -> license tick").string(), milliseconds(5)); | ||||
|             this->licenseHelper->tick(); | ||||
|             this->license_service_->execute_tick(); | ||||
|         } | ||||
|     } | ||||
|     { | ||||
| @ -637,36 +642,35 @@ string get_mac_address() { | ||||
| } | ||||
| 
 | ||||
| #define SN_BUFFER 1024 | ||||
| std::shared_ptr<license::LicenseRequestData> InstanceHandler::generateLicenseData() { | ||||
|     auto request = make_shared<license::LicenseRequestData>(); | ||||
| std::shared_ptr<ts::server::license::InstanceLicenseInfo> InstanceHandler::generateLicenseData() { | ||||
|     auto request = std::make_shared<license::InstanceLicenseInfo>(); | ||||
|     request->license = config::license; | ||||
|     request->servers_online = this->voiceServerManager->runningServers(); | ||||
|     request->metrics.servers_online = this->voiceServerManager->runningServers(); | ||||
|     auto report = this->voiceServerManager->clientReport(); | ||||
|     request->client_online = report.clients_ts; | ||||
|     request->web_clients_online = report.clients_web; | ||||
|     request->bots_online = report.bots; | ||||
|     request->queries_online = report.queries; | ||||
|     request->speach_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as<uint64_t>(); | ||||
|     request->speach_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>(); | ||||
|     request->speach_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>(); | ||||
|     request->speach_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>(); | ||||
|     request->metrics.client_online = report.clients_ts; | ||||
|     request->metrics.web_clients_online = report.clients_web; | ||||
|     request->metrics.bots_online = report.bots; | ||||
|     request->metrics.queries_online = report.queries; | ||||
|     request->metrics.speech_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as<uint64_t>(); | ||||
|     request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>(); | ||||
|     request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>(); | ||||
|     request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>(); | ||||
| 
 | ||||
|     static std::string null_str{"\0\0\0\0\0\0\0\0", 8}; /* we need at least some characters */ | ||||
|     request->web_certificate_revision = this->web_cert_revision.empty() ? null_str : this->web_cert_revision; | ||||
| 
 | ||||
|     { | ||||
|         auto info = make_shared<license::ServerInfo>(); | ||||
|         info->timestamp = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count(); | ||||
|         info->version = build::version()->string(true); | ||||
|         request->info.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()); | ||||
|         request->info.version = build::version()->string(true); | ||||
| 
 | ||||
|         { /* uname */ | ||||
|             utsname retval{}; | ||||
|             if(uname(&retval) < 0) { | ||||
|                 info->uname = "unknown (" + string(strerror(errno)) + ")"; | ||||
|                 request->info.uname = "unknown (" + string(strerror(errno)) + ")"; | ||||
|             } else { | ||||
|                 char buffer[SN_BUFFER]; | ||||
|                 snprintf(buffer, SN_BUFFER, "sys:%s version:%s release:%s", retval.sysname, retval.version, retval.release); | ||||
|                 info->uname = string(buffer); | ||||
|                 request->info.uname = string(buffer); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| @ -676,12 +680,10 @@ std::shared_ptr<license::LicenseRequestData> InstanceHandler::generateLicenseDat | ||||
|             if(property_unique_id.as<string>().empty()) | ||||
|                 property_unique_id = rnd_string(64); | ||||
| 
 | ||||
|             auto hash = digest::sha256(info->uname); | ||||
|             auto hash = digest::sha256(request->info.uname); | ||||
|             hash = digest::sha256(hash + property_unique_id.as<string>() + get_mac_address()); | ||||
|             info->unique_identifier = base64::encode(hash); | ||||
|             request->info.unique_id = base64::encode(hash); | ||||
|         } | ||||
| 
 | ||||
|         request->info = info; | ||||
|     } | ||||
|     return request; | ||||
| } | ||||
|  | ||||
| @ -3,9 +3,8 @@ | ||||
| #include <sql/SqlQuery.h> | ||||
| #include <Properties.h> | ||||
| #include "VirtualServerManager.h" | ||||
| #include "../../license/shared/LicenseRequest.h" | ||||
| #include "lincense/LicenseHelper.h" | ||||
| #include <ssl/SSLManager.h> | ||||
| #include <src/lincense/LicenseService.h> | ||||
| #include "manager/SqlDataManager.h" | ||||
| #include "lincense/TeamSpeakLicense.h" | ||||
| #include "server/WebIoManager.h" | ||||
| @ -20,6 +19,10 @@ namespace ts { | ||||
|     } | ||||
| 
 | ||||
|     namespace server { | ||||
|         namespace license { | ||||
|             class LicenseService; | ||||
|         } | ||||
| 
 | ||||
|         class InstanceHandler { | ||||
|             public: | ||||
|                 explicit InstanceHandler(SqlDataManager*); | ||||
| @ -63,7 +66,7 @@ namespace ts { | ||||
| 
 | ||||
|                 std::shared_ptr<stats::ConnectionStatistics> getStatistics(){ return statistics; } | ||||
|                 std::shared_ptr<threads::Scheduler> scheduler(){ return this->tick_manager; } | ||||
|                 std::shared_ptr<license::LicenseRequestData> generateLicenseData(); | ||||
|                 std::shared_ptr<license::InstanceLicenseInfo> generateLicenseData(); | ||||
| 
 | ||||
|                 std::shared_ptr<TeamSpeakLicense> getTeamSpeakLicense() { return this->teamspeak_license; } | ||||
|                 std::shared_ptr<ts::Properties> getDefaultServerProperties() { return this->default_server_properties; } | ||||
| @ -90,6 +93,8 @@ namespace ts { | ||||
|                         bool granted = false, | ||||
|                         std::shared_ptr<CalculateCache> cache = nullptr | ||||
|                 ); | ||||
| 
 | ||||
|                 [[nodiscard]] inline std::shared_ptr<license::LicenseService> license_service() { return this->license_service_; } | ||||
|             private: | ||||
|                 std::mutex activeLock; | ||||
|                 std::condition_variable activeCon; | ||||
| @ -126,7 +131,7 @@ namespace ts { | ||||
|                 std::shared_ptr<ts::server::InternalClient> globalServerAdmin = nullptr; | ||||
|                 std::shared_ptr<ConnectedClient> _musicRoot = nullptr; | ||||
| 
 | ||||
|                 std::shared_ptr<license::LicenseHelper> licenseHelper = nullptr; | ||||
|                 std::shared_ptr<license::LicenseService> license_service_{nullptr}; | ||||
|                 std::shared_ptr<stats::ConnectionStatistics> statistics = nullptr; | ||||
|                 std::shared_ptr<threads::Scheduler> tick_manager = nullptr; | ||||
| 
 | ||||
|  | ||||
| @ -39,7 +39,7 @@ bool SpeakingClient::shouldReceiveVoiceWhisper(const std::shared_ptr<ConnectedCl | ||||
|     if(!this->shouldReceiveVoice(sender)) | ||||
|         return false; | ||||
| 
 | ||||
|     return permission::v2::permission_granted(this->cpmerission_needed_whisper_power, sender->cpmerission_whisper_power); | ||||
|     return permission::v2::permission_granted(this->cpmerission_needed_whisper_power, sender->cpmerission_whisper_power, false); | ||||
| } | ||||
| 
 | ||||
| void SpeakingClient::handlePacketVoice(const pipes::buffer_view& data, bool head, bool fragmented) { | ||||
| @ -127,6 +127,15 @@ enum WhisperTarget { | ||||
|     CHANNEL_SUBCHANNELS = 6 | ||||
| }; | ||||
| 
 | ||||
| inline bool update_whisper_error(std::chrono::system_clock::time_point& last) { | ||||
|     auto now = std::chrono::system_clock::now(); | ||||
|     if(last + std::chrono::milliseconds{500} < now) { | ||||
|         last = now; | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
| //All clients => type := SERVER_GROUP and target_id := 0
 | ||||
| //Server group => type := SERVER_GROUP and target_id := <server group id>
 | ||||
| //Channel group => type := CHANNEL_GROUP and target_id := <channel group id>
 | ||||
| @ -239,7 +248,27 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo | ||||
|                 return target->currentChannel->parent() != current; | ||||
|             }), available_clients.end()); | ||||
|         } | ||||
|         if(available_clients.empty()) return; | ||||
| 
 | ||||
|         auto self_lock = this->_this.lock(); | ||||
|         available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) { | ||||
|             auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl); | ||||
|             return !speakingClient->shouldReceiveVoiceWhisper(self_lock); | ||||
|         }), available_clients.end()); | ||||
| 
 | ||||
|         if(available_clients.empty()) { | ||||
|             if(update_whisper_error(this->speak_last_no_whisper_target)) { | ||||
|                 command_result result{error::whisper_no_targets}; | ||||
|                 this->notifyError(result); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|         if(available_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) { | ||||
|             if(update_whisper_error(this->speak_last_too_many_whisper_targets)) { | ||||
|                 command_result result{error::whisper_too_many_targets}; | ||||
|                 this->notifyError(result); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         //Create the packet data
 | ||||
|         char packet_buffer[OUT_WHISPER_PKT_OFFSET + data_length]; | ||||
| @ -253,7 +282,6 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo | ||||
|         VoicePacketFlags flags{}; | ||||
|         auto data = pipes::buffer_view(packet_buffer, OUT_WHISPER_PKT_OFFSET + data_length); | ||||
|         for(const auto& cl : available_clients){ | ||||
|             if(cl->shouldReceiveVoiceWhisper(_this.lock())) | ||||
|                 cl->send_voice_whisper_packet(data, flags); | ||||
|         } | ||||
| 
 | ||||
| @ -275,6 +303,44 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo | ||||
|         for(uint8_t index = 0; index < channelCount; index++) | ||||
|             clientIds[index] = be2le16((char*) data.data_ptr(), offset, &offset); | ||||
| 
 | ||||
|         auto available_clients = this->server->getClients(); | ||||
|         available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) { | ||||
|             auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl); | ||||
|             if(!speakingClient || cl == this || !speakingClient->currentChannel) return true; | ||||
| 
 | ||||
|             auto clientChannelId = cl->currentChannel->channelId(); | ||||
|             auto clientId = cl->getClientId(); | ||||
| 
 | ||||
|             for(uint8_t index = 0; index < clientCount; index++) | ||||
|                 if(channelIds[index] == clientChannelId) return false; | ||||
| 
 | ||||
|             for(uint8_t index = 0; index < channelCount; index++) | ||||
|                 if(clientIds[index] == clientId) return false; | ||||
| 
 | ||||
|             return true; | ||||
|         }), available_clients.end()); | ||||
| 
 | ||||
|         auto self_lock = this->_this.lock(); | ||||
|         available_clients.erase(std::remove_if(available_clients.begin(), available_clients.end(), [&](const std::shared_ptr<ConnectedClient>& cl) { | ||||
|             auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl); | ||||
|             return !speakingClient->shouldReceiveVoiceWhisper(self_lock); | ||||
|         }), available_clients.end()); | ||||
| 
 | ||||
|         if(available_clients.empty()) { | ||||
|             if(update_whisper_error(this->speak_last_no_whisper_target)) { | ||||
|                 command_result result{error::whisper_no_targets}; | ||||
|                 this->notifyError(result); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|         if(available_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save<size_t>()) { | ||||
|             if(update_whisper_error(this->speak_last_too_many_whisper_targets)) { | ||||
|                 command_result result{error::whisper_too_many_targets}; | ||||
|                 this->notifyError(result); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         size_t dataLength = data.length() - offset; | ||||
| #ifdef PKT_LOG_WHISPER | ||||
|         logTrace(this->getServerId(), "{} Whisper data length: {}. Client count: {}. Channel count: {}.", CLIENT_STR_LOG_PREFIX, dataLength, clientCount, channelCount); | ||||
| @ -291,21 +357,9 @@ void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& data, bo | ||||
|         VoicePacketFlags flags{}; | ||||
|         auto data = pipes::buffer_view(packetBuffer, OUT_WHISPER_PKT_OFFSET + dataLength); | ||||
| 
 | ||||
|         for(const auto& cl : this->server->getClients()){ //Faster?
 | ||||
|         for(const auto& cl : available_clients){ //Faster?
 | ||||
|             auto speakingClient = dynamic_pointer_cast<SpeakingClient>(cl); | ||||
|             if(!speakingClient || cl == this) continue; | ||||
|             if(!cl->currentChannel) continue; | ||||
| 
 | ||||
|             auto clientChannelId = cl->currentChannel->channelId(); | ||||
|             auto clientId = cl->getClientId(); | ||||
| 
 | ||||
|             for(uint8_t index = 0; index < clientCount; index++) | ||||
|                 if(channelIds[index] == clientChannelId) goto handleSend; | ||||
|             for(uint8_t index = 0; index < channelCount; index++) | ||||
|                 if(clientIds[index] == clientId) goto handleSend; | ||||
|             continue; | ||||
| 
 | ||||
|             handleSend: | ||||
|             assert(speakingClient); | ||||
|             if(speakingClient->shouldReceiveVoiceWhisper(_this.lock())) | ||||
|                 speakingClient->send_voice_whisper_packet(data, flags); | ||||
|         } | ||||
|  | ||||
| @ -81,6 +81,9 @@ namespace ts { | ||||
|                 std::chrono::system_clock::time_point speak_begin; | ||||
|                 std::chrono::system_clock::time_point speak_last_packet; | ||||
| 
 | ||||
|                 std::chrono::system_clock::time_point speak_last_no_whisper_target; | ||||
|                 std::chrono::system_clock::time_point speak_last_too_many_whisper_targets; | ||||
| 
 | ||||
|                 permission::v2::PermissionFlaggedValue max_idle_time{permission::v2::empty_permission_flagged_value}; | ||||
|                 struct { | ||||
|                     HandshakeState state{HandshakeState::BEGIN}; | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| #include "SpeakingClient.h" | ||||
| #include <misc/endianness.h> | ||||
| #include <src/VirtualServerManager.h> | ||||
| #include <netinet/tcp.h> | ||||
| #include <src/InstanceHandler.h> | ||||
| #include <misc/base64.h> | ||||
| #include <misc/digest.h> | ||||
| #include <misc/rnd.h> | ||||
| #include <log/LogUtils.h> | ||||
| #include "../VirtualServerManager.h" | ||||
| #include "../InstanceHandler.h" | ||||
| 
 | ||||
| #if defined(TCP_CORK) && !defined(TCP_NOPUSH) | ||||
|     #define TCP_NOPUSH TCP_CORK | ||||
|  | ||||
| @ -1247,6 +1247,8 @@ command_result ConnectedClient::handleCommandChannelEdit(Command &cmd) { | ||||
|                 if(conversation) | ||||
|                     conversation->set_history_length(cmd[key->name]); | ||||
|             } | ||||
|         } else if(*key == property::CHANNEL_NEEDED_TALK_POWER) { | ||||
|             channel->permissions()->set_permission(permission::i_client_needed_talk_power, {cmd[key->name].as<int>(), 0}, permission::v2::set_value, permission::v2::do_nothing); | ||||
|         } | ||||
| 
 | ||||
|         channel->properties()[key] = cmd[key->name].string(); | ||||
|  | ||||
| @ -1906,7 +1906,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) { | ||||
|     } | ||||
|     string command = "cat \"" + log_path + "\""; | ||||
|     command += " | grep -E "; | ||||
|     command += "\"\\] \\[.*\\]( ){0,6}?" + server_identifier + " \\|\""; | ||||
|     command += R"("\] \[.*\]( ){0,6}?)" + server_identifier + " \\|\""; | ||||
| 
 | ||||
|     size_t beginpos = cmd[0].has("begin_pos") ? cmd["begin_pos"].as<size_t>() : 0ULL; //TODO test it?
 | ||||
|     size_t file_index = 0; | ||||
| @ -1927,7 +1927,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) { | ||||
|                     if(beginpos != 0 && file_index + read > beginpos) { //We're done we just want to get the size later
 | ||||
|                         line_buffer += string(buffer.data(), beginpos - file_index); | ||||
| 
 | ||||
|                         lines.push_back({file_index, line_buffer}); | ||||
|                         lines.emplace_back(file_index, line_buffer); | ||||
|                         if(lines.size() > max_lines) lines.pop_front(); | ||||
|                         //debugMessage(LOG_GENERAL, "Final line {}", line_buffer);
 | ||||
|                         line_buffer = ""; | ||||
| @ -1955,7 +1955,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) { | ||||
|                             if(length == 0) length = 1; | ||||
| 
 | ||||
|                             //debugMessage(LOG_GENERAL, "Got line {}",  line_buffer.substr(0, index));
 | ||||
|                             lines.push_back({file_index + cut_offset, line_buffer.substr(0, index)}); | ||||
|                             lines.emplace_back(file_index + cut_offset, line_buffer.substr(0, index)); | ||||
|                             if(lines.size() > max_lines) lines.pop_front(); | ||||
| 
 | ||||
|                             cut_offset += index + length; | ||||
| @ -1968,7 +1968,7 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) { | ||||
|         } | ||||
| 
 | ||||
|         if(!line_buffer.empty()) { | ||||
|             lines.push_back({file_index - line_buffer.length(), line_buffer}); | ||||
|             lines.emplace_back(file_index - line_buffer.length(), line_buffer); | ||||
|             if(lines.size() > max_lines) lines.pop_front(); | ||||
|         } | ||||
|     } | ||||
| @ -2004,8 +2004,13 @@ command_result ConnectedClient::handleCommandLogView(ts::Command& cmd) { | ||||
| 
 | ||||
|                 ts += type + " |    | |" + line.substr(line.find('|') + 1); | ||||
|             } | ||||
| 
 | ||||
|             if(ts.length() > 1024) | ||||
|                 ts = ts.substr(0, 1024) + "..."; | ||||
|             result[index++]["l"] = ts; | ||||
|         } else { | ||||
|             if(line.length() > 1024) | ||||
|                 line = line.substr(0, 1024) + "..."; | ||||
|             result[index++]["l"] = line; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -7,6 +7,7 @@ | ||||
| #include <misc/memtracker.h> | ||||
| #include <misc/base64.h> | ||||
| #include "src/client/ConnectedClient.h" | ||||
| #include <netinet/tcp.h> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
|  | ||||
| @ -1,200 +0,0 @@ | ||||
| #include <log/LogUtils.h> | ||||
| #include <misc/strobf.h> | ||||
| #include <misc/hex.h> | ||||
| #include <src/Configuration.h> | ||||
| #include <arpa/inet.h> | ||||
| #include <src/ShutdownHelper.h> | ||||
| #include "src/InstanceHandler.h" | ||||
| #include "LicenseHelper.h" | ||||
| 
 | ||||
| using namespace license; | ||||
| using namespace std; | ||||
| using namespace std::chrono; | ||||
| using namespace ts; | ||||
| using namespace ts::server; | ||||
| 
 | ||||
| LicenseHelper::LicenseHelper() { | ||||
|     this->scheduled_request = system_clock::now() + seconds(rand() % 30); //Check in one minute
 | ||||
| } | ||||
| 
 | ||||
| LicenseHelper::~LicenseHelper() { | ||||
|     if(this->request.prepare_thread.joinable()) | ||||
|         this->request.prepare_thread.join(); | ||||
| 
 | ||||
|     { | ||||
|         unique_lock lock(this->request.current_lock); | ||||
|         if(this->request.current) { | ||||
|             auto request = move(this->request.current); | ||||
|             lock.unlock(); | ||||
| 
 | ||||
|             request->abortRequest(); | ||||
|             request->callback_update_certificate = nullptr; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| inline string format_time(const system_clock::time_point& time) { | ||||
|     std::time_t now = system_clock::to_time_t(time); | ||||
|     std::tm * ptm = std::localtime(&now); | ||||
|     char buffer[128]; | ||||
|     const auto length = std::strftime(buffer, 128, "%a, %d.%m.%Y %H:%M:%S", ptm); | ||||
|     return string(buffer, length); | ||||
| } | ||||
| 
 | ||||
| void LicenseHelper::tick() { | ||||
|     lock_guard tick_lock(this->license_tick_lock); | ||||
| 
 | ||||
|     bool verbose = config::license->isPremium(); | ||||
|     { | ||||
|         lock_guard request_lock(this->request.current_lock); | ||||
|         if(this->request.current) { | ||||
|             auto promise = this->request.current->requestInfo(); | ||||
|             if(promise.state() != threads::FutureState::WORKING){ | ||||
|                 auto exception = this->request.current->exception(); | ||||
|                 if(promise.state() == threads::FutureState::FAILED) { | ||||
|                     this->handle_request_failed(verbose, exception ? exception->what() : strobf("unknown").c_str()); | ||||
|                     this->request.current = nullptr; /* connection should be already closed */ | ||||
|                     return; | ||||
|                 } else { | ||||
|                     auto response = promise.waitAndGet(nullptr); | ||||
|                     this->request.current = nullptr; /* connection should be already closed */ | ||||
| 
 | ||||
|                     if(!response){ | ||||
|                         this->handle_request_failed(verbose, exception ? exception->what() : strobf("invalid result (null)").c_str()); | ||||
|                         return; | ||||
|                     } | ||||
|                     if(!response->license_valid || !response->properties_valid){ | ||||
|                         if(!response->license_valid) { | ||||
|                             if(config::license->isPremium()) logCritical(LOG_INSTANCE, strobf("Could not validate license.").c_str()); | ||||
|                             else logCritical(LOG_INSTANCE, strobf("Your server has been shutdown remotely!").c_str()); | ||||
|                         } else if(!response->properties_valid) { | ||||
|                             logCritical(LOG_INSTANCE, strobf("Property adjustment failed!").c_str()); | ||||
|                         } else | ||||
|                             logCritical(LOG_INSTANCE, strobf("Your license expired!").c_str()); | ||||
|                         logCritical(LOG_INSTANCE, strobf("Stopping application!").c_str()); | ||||
|                         ts::server::shutdownInstance(); | ||||
|                         return; | ||||
|                     } else { | ||||
|                         this->scheduled_request = this->last_request + hours(2); | ||||
|                         logMessage(LOG_INSTANCE, strobf("License successfully validated! Scheduling next check at {}").c_str(), format_time(this->scheduled_request)); | ||||
|                         if(response->speach_reset) | ||||
|                             serverInstance->resetSpeechTime(); | ||||
|                         serverInstance->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ] = response->speach_varianz_adjustment; | ||||
| 
 | ||||
|                         { | ||||
|                             lock_guard lock(this->request.info_lock); | ||||
|                             this->request.info = response->license; | ||||
|                         } | ||||
| 
 | ||||
|                         this->request_fail_count = 0; | ||||
|                         this->last_successful_request = system_clock::now(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(system_clock::now() > scheduled_request){ | ||||
|         this->do_request(verbose); | ||||
|     } | ||||
| } | ||||
| void LicenseHelper::do_request(bool verbose) { | ||||
|     if(config::license && config::license->isPremium()) | ||||
|         logMessage(LOG_INSTANCE, strobf("Validating license").c_str()); | ||||
|     else | ||||
|         logMessage(LOG_INSTANCE, strobf("Validating instance integrity").c_str()); | ||||
| 
 | ||||
|     this->last_request = system_clock::now(); | ||||
|     this->scheduled_request = this->last_request + minutes(10); /* some kind of timeout */ | ||||
| 
 | ||||
|     { | ||||
|         unique_lock lock(this->request.current_lock); | ||||
|         if(this->request.current) { | ||||
|             auto request = move(this->request.current); | ||||
|             lock.unlock(); | ||||
| 
 | ||||
|             if(verbose) { | ||||
|                 auto promise = request->requestInfo(); | ||||
|                 if(promise.state() == threads::FutureState::WORKING) { | ||||
|                     logMessage(LOG_INSTANCE, strobf("Old check timed out (10 min). Running new one!").c_str()); | ||||
|                 } | ||||
|             } | ||||
|             request->abortRequest(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(this->request.prepare_thread.joinable()) /* usually the preparation should not take too long */ | ||||
|         this->request.prepare_thread.join(); | ||||
| 
 | ||||
|     this->request.prepare_thread = std::thread([&]{ | ||||
|         sockaddr_in server_addr{}; | ||||
|         server_addr.sin_family = AF_INET; | ||||
| 
 | ||||
| #ifdef DO_LOCAL_REQUEST | ||||
|         auto license_host = gethostbyname(strobf("localhost").c_str()); | ||||
|         server_addr.sin_addr.s_addr = ((in_addr*) license_host->h_addr)->s_addr; | ||||
| #else | ||||
|         auto license_host = gethostbyname(strobf("license.teaspeak.de").c_str()); | ||||
|         if(!license_host){ | ||||
|             if(verbose) logError(LOG_INSTANCE, strobf("Could not valid license! (1)").c_str()); | ||||
|             return; | ||||
|         } | ||||
|         if(!license_host->h_addr){ | ||||
|             if(verbose) logError(LOG_INSTANCE, strobf("Could not valid license! (2)").c_str()); | ||||
|             return; | ||||
|         } | ||||
|         server_addr.sin_addr.s_addr = ((in_addr*) license_host->h_addr)->s_addr; | ||||
|         int first = server_addr.sin_addr.s_addr >> 24; | ||||
|         if(first == 0 || first == 127 || first == 255) { | ||||
|             if(config::license->isPremium()) { | ||||
|                 logError(LOG_INSTANCE, strobf("You tried to nullroot 'license.teaspeak.de'!").c_str()); | ||||
|                 logCritical(LOG_INSTANCE, strobf("Could not validate license!").c_str()); | ||||
|                 logCritical(LOG_INSTANCE, strobf("Stopping server!").c_str()); | ||||
|                 ts::server::shutdownInstance(); | ||||
|                 return; | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| #endif | ||||
|         server_addr.sin_port = htons(27786); | ||||
| 
 | ||||
| 
 | ||||
|         auto license_data = serverInstance->generateLicenseData(); | ||||
|         auto request = make_shared<license::LicenceRequest>(license_data, server_addr); | ||||
|         request->verbose = false; | ||||
|         request->callback_update_certificate = [&](const auto& update) { this->callback_certificate_update(update); }; | ||||
|         { | ||||
|             lock_guard lock(this->request.current_lock); | ||||
|             this->request.current = request; | ||||
|             request->requestInfo(); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| void LicenseHelper::handle_request_failed(bool verbose, const std::string& error) { | ||||
|     if(config::license && config::license->isPremium()) | ||||
|         logError(LOG_INSTANCE, strobf("License validation failed: {}").c_str(), error); | ||||
|     else | ||||
|         logError(LOG_INSTANCE, strobf("Instance integrity check failed: {}").c_str(), error); | ||||
| 
 | ||||
|     this->request_fail_count++; | ||||
|     milliseconds next_request; | ||||
| 
 | ||||
|     if(this->request_fail_count <= 1) | ||||
|         next_request = minutes(1); | ||||
|     else if(this->request_fail_count <= 5) | ||||
|         next_request = minutes(5); | ||||
|     else if(this->request_fail_count <= 10) | ||||
|         next_request = minutes(10); | ||||
|     else | ||||
|         next_request = minutes(30); | ||||
| 
 | ||||
|     this->scheduled_request = this->last_request + next_request; | ||||
|     if(verbose) | ||||
|         logMessage(LOG_INSTANCE, strobf("Scheduling next check at {}").c_str(), format_time(this->scheduled_request)); | ||||
| } | ||||
| 
 | ||||
| void LicenseHelper::callback_certificate_update(const license::WebCertificate &certificate) { | ||||
|     serverInstance->setWebCertRoot(certificate.key, certificate.certificate, certificate.revision); | ||||
| } | ||||
| @ -1,40 +0,0 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <ThreadPool/Mutex.h> | ||||
| #include "../../../license/shared/License.h" | ||||
| #include "../../../license/shared/LicenseRequest.h" | ||||
| 
 | ||||
| namespace license { | ||||
|     class LicenseHelper { | ||||
|         public: | ||||
|             LicenseHelper(); | ||||
|             ~LicenseHelper(); | ||||
| 
 | ||||
|             void tick(); | ||||
|             std::shared_ptr<license::LicenseInfo> getLicenseInfo() { | ||||
|                 std::lock_guard lock(this->request.info_lock); | ||||
|                 return this->request.info; | ||||
|             } | ||||
|         private: | ||||
|             std::mutex license_tick_lock; | ||||
| 
 | ||||
|             std::chrono::system_clock::time_point scheduled_request; | ||||
|             std::chrono::system_clock::time_point last_request; | ||||
|             std::chrono::system_clock::time_point last_successful_request; | ||||
|             size_t request_fail_count = 0; | ||||
| 
 | ||||
|             struct { | ||||
|                 std::shared_ptr<license::LicenceRequest> current = nullptr; | ||||
|                 std::mutex current_lock; | ||||
| 
 | ||||
|                 std::shared_ptr<license::LicenseInfo> info; | ||||
|                 std::mutex info_lock; | ||||
| 
 | ||||
|                 std::thread prepare_thread; | ||||
|             } request; | ||||
| 
 | ||||
|             void do_request(bool /* verbose */); | ||||
|             void handle_request_failed(bool /* verbose */, const std::string& /* error */); | ||||
|             void callback_certificate_update(const license::WebCertificate&); | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										540
									
								
								server/src/lincense/LicenseService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										540
									
								
								server/src/lincense/LicenseService.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,540 @@ | ||||
| //
 | ||||
| // Created by WolverinDEV on 27/02/2020.
 | ||||
| //
 | ||||
| #include <netdb.h> | ||||
| #include <cassert> | ||||
| #include <misc/strobf.h> | ||||
| #include <log/LogUtils.h> | ||||
| #include <src/Configuration.h> | ||||
| #include <src/ShutdownHelper.h> | ||||
| #include <misc/base64.h> | ||||
| #include "../../../license/shared/LicenseServerClient.h" | ||||
| #include "../../../cmake-build-debug-wsl/license/LicenseRequest.pb.h" | ||||
| #include "src/InstanceHandler.h" | ||||
| #include "../../../license/shared/License.h" | ||||
| 
 | ||||
| #define DO_LOCAL_REQUEST | ||||
| using namespace ts::server::license; | ||||
| 
 | ||||
| LicenseService::LicenseService() { | ||||
|     this->dns.lock = std::make_shared<std::recursive_mutex>(); | ||||
| } | ||||
| 
 | ||||
| LicenseService::~LicenseService() { | ||||
|     { | ||||
|         std::lock_guard lock{this->request_lock}; | ||||
|         this->abort_request(lock, ""); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool LicenseService::initialize(std::string &error) { | ||||
|     //this->verbose_ = true;
 | ||||
|     this->startup_timepoint_ = std::chrono::steady_clock::now(); | ||||
|     this->timings.next_request = std::chrono::system_clock::now() + std::chrono::seconds(rand() % 20); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool LicenseService::execute_request_sync(const std::chrono::milliseconds& timeout) { | ||||
|     std::unique_lock slock{this->sync_request_lock}; | ||||
|     this->begin_request(); | ||||
| 
 | ||||
|     if(this->sync_request_cv.wait_for(slock, timeout) == std::cv_status::timeout) | ||||
|         return false; | ||||
| 
 | ||||
|     return this->timings.failed_count == 0; | ||||
| } | ||||
| 
 | ||||
| void LicenseService::shutdown() { | ||||
|     std::lock_guard lock{this->request_lock}; | ||||
|     if(this->request_state_ == request_state::empty) return; | ||||
| 
 | ||||
|     this->abort_request(lock, "shutdown"); | ||||
| } | ||||
| 
 | ||||
| void LicenseService::begin_request() { | ||||
|     std::lock_guard lock{this->request_lock}; | ||||
|     if(this->request_state_ != request_state::empty) | ||||
|         this->abort_request(lock, "last request has been aborted"); | ||||
| 
 | ||||
|     if(this->verbose_) | ||||
|         debugMessage(LOG_INSTANCE, strobf("Executing license request.").string()); | ||||
|     this->timings.last_request = std::chrono::system_clock::now(); | ||||
|     this->request_state_ = request_state::dns_lookup; | ||||
|     this->execute_dns_request(); | ||||
| } | ||||
| 
 | ||||
| void LicenseService::abort_request(std::lock_guard<std::recursive_timed_mutex> &, const std::string& reason) { | ||||
|     if(this->request_state_ == request_state::dns_lookup) { | ||||
|         this->abort_dns_request(); | ||||
|         return; | ||||
|     } else if(this->current_client) { | ||||
|         this->current_client->callback_connected = nullptr; | ||||
|         this->current_client->callback_message = nullptr; | ||||
|         this->current_client->callback_disconnected = nullptr; | ||||
| 
 | ||||
|         if(!reason.empty()) { | ||||
|             this->current_client->disconnect(reason, std::chrono::system_clock::now() + std::chrono::seconds{1}); | ||||
|             /* Lets not wait here because we might be within the event loop. */ | ||||
|             //if(!this->current_client->await_disconnect())
 | ||||
|             //    this->current_client->close_connection();
 | ||||
|         } else { | ||||
|             this->current_client->close_connection(); | ||||
|         } | ||||
| 
 | ||||
|         this->current_client.release(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void LicenseService::abort_dns_request() { | ||||
|     std::unique_lock llock{*this->dns.lock}; | ||||
|     if(!this->dns.current_lookup) return; | ||||
| 
 | ||||
|     this->dns.current_lookup->handle = nullptr; | ||||
|     this->dns.current_lookup = nullptr; | ||||
| } | ||||
| 
 | ||||
| void LicenseService::execute_dns_request() { | ||||
|     std::unique_lock llock{*this->dns.lock}; | ||||
|     assert(!this->dns.current_lookup); | ||||
| 
 | ||||
|     auto lookup = new _dns::_lookup{}; | ||||
| 
 | ||||
|     lookup->lock = this->dns.lock; | ||||
|     lookup->handle = this; | ||||
|     lookup->thread = std::thread([lookup] { | ||||
|         bool success{false}; | ||||
|         std::string error{}; | ||||
|         sockaddr_in server_addr{}; | ||||
| 
 | ||||
|         { | ||||
|             server_addr.sin_family = AF_INET; | ||||
| #ifdef DO_LOCAL_REQUEST | ||||
|             auto license_host = gethostbyname(strobf("localhost").c_str()); | ||||
| #else | ||||
|             auto license_host = gethostbyname(strobf("license.teaspeak.de").c_str()); | ||||
| #endif | ||||
|             if(!license_host) { | ||||
|                 error = strobf("result is null").string(); | ||||
|                 goto handle_result; | ||||
|             } | ||||
|             if(!license_host->h_addr){ | ||||
|                 error = strobf("missing h_addr in result").string(); | ||||
|                 goto handle_result; | ||||
|             } | ||||
| 
 | ||||
|             server_addr.sin_addr.s_addr = ((in_addr*) license_host->h_addr)->s_addr; | ||||
| 
 | ||||
| #ifndef DO_LOCAL_REQUEST | ||||
|             int first = server_addr.sin_addr.s_addr >> 24; | ||||
|             if(first == 0 || first == 127 || first == 255) { | ||||
|                 error = strobf("local response address").string(); | ||||
|                 goto handle_result; | ||||
|             } | ||||
| #endif | ||||
|             server_addr.sin_port = htons(27786); | ||||
|             success = true; | ||||
|         } | ||||
| 
 | ||||
|         handle_result: | ||||
|         { | ||||
|             std::unique_lock llock{*lookup->lock}; | ||||
|             if(lookup->handle) { | ||||
|                 lookup->handle->dns.current_lookup = nullptr; | ||||
| 
 | ||||
|                 if(success) { | ||||
|                     debugMessage(LOG_INSTANCE, strobf("Successfully resolved the hostname to {}").string(), net::to_string(server_addr.sin_addr)); | ||||
|                     lookup->handle->handle_dns_lookup_result(true, server_addr); | ||||
|                 } else { | ||||
|                     debugMessage(LOG_INSTANCE, strobf("Failed to resolve hostname for license server: {}").string(), error); | ||||
|                     lookup->handle->handle_dns_lookup_result(false, error); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             assert(lookup->thread.get_id() == std::this_thread::get_id()); | ||||
|             if(lookup->thread.joinable()) | ||||
|                 lookup->thread.detach(); | ||||
|             delete lookup; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
| 
 | ||||
|     this->dns.current_lookup = lookup; | ||||
| } | ||||
| 
 | ||||
| void LicenseService::handle_check_succeeded() { | ||||
|     { | ||||
|         std::lock_guard rlock{this->request_lock}; | ||||
|         this->abort_request(rlock, strobf("request succeeded").string()); | ||||
|         this->schedule_next_request(true); | ||||
|         this->request_state_ = request_state::empty; | ||||
| 
 | ||||
|         if(config::license->isPremium()) { | ||||
|             logMessage(LOG_INSTANCE, strobf("License has been validated.").string()); | ||||
|         } else { | ||||
|             logMessage(LOG_INSTANCE, strobf("Instance integrity has been validated.").string()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         std::unique_lock slock{this->sync_request_lock}; | ||||
|         this->sync_request_cv.notify_all(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void LicenseService::handle_check_fail(const std::string &error) { | ||||
|     { | ||||
|         std::lock_guard rlock{this->request_lock}; | ||||
|         this->abort_request(rlock, "request failed"); | ||||
| 
 | ||||
|         if(config::license->isPremium()) { | ||||
|             logCritical(LOG_INSTANCE, strobf("Failed to validate license:").string()); | ||||
|             logCritical(LOG_INSTANCE, error); | ||||
|             logCritical(LOG_INSTANCE, strobf("Stopping server!").string()); | ||||
|             ts::server::shutdownInstance(); | ||||
|         } else { | ||||
|             logError(LOG_INSTANCE, strobf("Failed to validate instance integrity:").string()); | ||||
|             logError(LOG_INSTANCE, error); | ||||
|         } | ||||
| 
 | ||||
|         this->schedule_next_request(false); | ||||
|         this->request_state_ = request_state::empty; | ||||
|     } | ||||
| 
 | ||||
|     { | ||||
|         std::unique_lock slock{this->sync_request_lock}; | ||||
|         this->sync_request_cv.notify_all(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void LicenseService::handle_dns_lookup_result(bool success, const std::variant<std::string, sockaddr_in> &result) { | ||||
|     if(!success) { | ||||
|         this->handle_check_fail(std::get<std::string>(result)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     std::lock_guard rlock{this->request_lock}; | ||||
|     if(this->request_state_ != request_state::dns_lookup) { | ||||
|         logError(LOG_INSTANCE, strobf("Request state isn't dns lookup anymore. Aborting dns lookup result callback.").string()); | ||||
|         return; | ||||
|     } | ||||
|     this->request_state_ = request_state::connecting; | ||||
| 
 | ||||
|     assert(!this->current_client); | ||||
|     this->current_client = std::make_unique<::license::client::LicenseServerClient>(std::get<sockaddr_in>(result), 3); | ||||
|     this->current_client->callback_connected = [&]{ this->handle_client_connected(); }; | ||||
|     this->current_client->callback_disconnected = [&](bool expected, const std::string& error) { | ||||
|         this->handle_client_disconnected(error); | ||||
|     }; | ||||
|     this->current_client->callback_message = [&](auto a, auto b, auto c) { | ||||
|         this->handle_message(a, b, c); | ||||
|     }; | ||||
| 
 | ||||
|     std::string error{}; | ||||
|     if(!this->current_client->start_connection(error)) | ||||
|         this->handle_check_fail(strobf("connect failed: ").string() + error); | ||||
| } | ||||
| 
 | ||||
| void LicenseService::client_send_message(::license::protocol::PacketType type, ::google::protobuf::Message &message) { | ||||
|     auto buffer = message.SerializeAsString(); | ||||
| 
 | ||||
|     assert(this->current_client); | ||||
|     this->current_client->send_message(type, buffer.data(), buffer.length()); | ||||
| } | ||||
| 
 | ||||
| void LicenseService::handle_client_connected() { | ||||
|     { | ||||
|         if(this->verbose_) | ||||
|             debugMessage(LOG_INSTANCE, strobf("License client connected").string()); | ||||
| 
 | ||||
|         std::lock_guard rlock{this->request_lock}; | ||||
|         if(this->request_state_ != request_state::connecting) { | ||||
|             logError(LOG_INSTANCE, strobf("Request state isn't connecting anymore. Aborting client connect callback.").string()); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this->request_state_ = request_state::license_validate; | ||||
|     } | ||||
| 
 | ||||
|     this->send_license_validate_request(); | ||||
| } | ||||
| 
 | ||||
| void LicenseService::handle_message(::license::protocol::PacketType type, const void *buffer, size_t size) { | ||||
|     switch (type) { | ||||
|         case ::license::protocol::PACKET_SERVER_VALIDATION_RESPONSE: | ||||
|             this->handle_message_license_info(buffer, size); | ||||
|             return; | ||||
| 
 | ||||
|         case ::license::protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT: | ||||
|             this->handle_message_property_adjustment(buffer, size); | ||||
|             return; | ||||
| 
 | ||||
|         case ::license::protocol::PACKET_SERVER_LICENSE_UPGRADE_RESPONSE: | ||||
|             this->handle_message_license_update(buffer, size); | ||||
|             return; | ||||
| 
 | ||||
|         default: | ||||
|             this->handle_check_fail(strobf("received unknown packet").string()); | ||||
|             return; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void LicenseService::handle_client_disconnected(const std::string& message) { | ||||
|     std::lock_guard rlock{this->request_lock}; | ||||
|     if(this->request_state_ != request_state::finishing) { | ||||
|         this->handle_check_fail(strobf("unexpected disconnect: ").string() + message); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     this->abort_request(rlock, ""); | ||||
| } | ||||
| 
 | ||||
| void LicenseService::send_license_validate_request() { | ||||
|     this->license_request_data = serverInstance->generateLicenseData(); | ||||
| 
 | ||||
|     ts::proto::license::ServerValidation request{}; | ||||
|     if(this->license_request_data->license) { | ||||
|         request.set_licensed(true); | ||||
|         request.set_license_info(true); | ||||
|         request.set_license(exportLocalLicense(this->license_request_data->license)); | ||||
|     } else { | ||||
|         request.set_licensed(false); | ||||
|         request.set_license_info(false); | ||||
|     } | ||||
|     request.mutable_info()->set_uname(this->license_request_data->info.uname); | ||||
|     request.mutable_info()->set_version(this->license_request_data->info.version); | ||||
|     request.mutable_info()->set_timestamp(this->license_request_data->info.timestamp.count()); | ||||
|     request.mutable_info()->set_unique_id(this->license_request_data->info.unique_id); | ||||
| 
 | ||||
|     this->client_send_message(::license::protocol::PACKET_CLIENT_SERVER_VALIDATION, request); | ||||
| } | ||||
| 
 | ||||
| void LicenseService::handle_message_license_info(const void *buffer, size_t buffer_length) { | ||||
|     std::lock_guard rlock{this->request_lock}; | ||||
|     if(this->request_state_ != request_state::license_validate) { | ||||
|         this->handle_check_fail(strobf("finvalid request state for license response packet").string()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ts::proto::license::LicenseResponse response{}; | ||||
|     if(!response.ParseFromArray(buffer, buffer_length)) { | ||||
|         this->handle_check_fail(strobf("failed to parse license response packet").string()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if(response.has_blacklist()) { | ||||
|         auto blacklist_state = response.blacklist().state(); | ||||
|         if(blacklist_state == ::ts::proto::license::BLACKLISTED) { | ||||
|             this->abort_request(rlock, strobf("blacklist action").string()); | ||||
| 
 | ||||
|             logCritical(LOG_INSTANCE, strobf("This TeaSpeak-Server instance has been blacklisted by TeaSpeak.").string()); | ||||
|             logCritical(LOG_INSTANCE, strobf("Stopping server!").string()); | ||||
|             ts::server::shutdownInstance(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(!response.valid()) { | ||||
|         std::string reason{}; | ||||
|         if(response.has_invalid_reason()) | ||||
|             reason = response.invalid_reason(); | ||||
|         else | ||||
|             reason = strobf("no reason given").string(); | ||||
| 
 | ||||
|         license_invalid_reason = reason; | ||||
|     } else { | ||||
|         license_invalid_reason.reset(); | ||||
|     } | ||||
| 
 | ||||
|     if(response.has_update_pending() && response.update_pending()) { | ||||
|         if(this->send_license_update_request()) { | ||||
|             this->request_state_ = request_state::license_upgrade; | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(this->license_invalid_reason.has_value()) { | ||||
|         this->handle_check_fail(strobf("Failed to verify license (").string() + *this->license_invalid_reason + ")"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     this->send_property_update_request(); | ||||
|     this->request_state_ = request_state::property_update; | ||||
| } | ||||
| 
 | ||||
| void LicenseService::send_property_update_request() { | ||||
|     auto data = this->license_request_data; | ||||
|     if(!data) { | ||||
|         this->handle_check_fail(strobf("missing property data").string()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     ts::proto::license::PropertyUpdateRequest infos{}; | ||||
|     infos.set_speach_total(this->license_request_data->metrics.speech_total); | ||||
|     infos.set_speach_dead(this->license_request_data->metrics.speech_dead); | ||||
|     infos.set_speach_online(this->license_request_data->metrics.speech_online); | ||||
|     infos.set_speach_varianz(this->license_request_data->metrics.speech_varianz); | ||||
| 
 | ||||
|     infos.set_clients_online(this->license_request_data->metrics.client_online); | ||||
|     infos.set_bots_online(this->license_request_data->metrics.bots_online); | ||||
|     infos.set_queries_online(this->license_request_data->metrics.queries_online); | ||||
|     infos.set_servers_online(this->license_request_data->metrics.servers_online); | ||||
|     infos.set_web_clients_online(this->license_request_data->metrics.web_clients_online); | ||||
| 
 | ||||
|     infos.set_web_cert_revision(this->license_request_data->web_certificate_revision); | ||||
| 
 | ||||
|     this->client_send_message(::license::protocol::PACKET_CLIENT_PROPERTY_ADJUSTMENT, infos); | ||||
| } | ||||
| 
 | ||||
| void LicenseService::handle_message_property_adjustment(const void *buffer, size_t buffer_length) { | ||||
|     std::lock_guard rlock{this->request_lock}; | ||||
|     if(this->request_state_ != request_state::property_update) { | ||||
|         this->handle_check_fail(strobf("invalid request state for property update packet").string()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ts::proto::license::PropertyUpdateResponse response{}; | ||||
|     if(!response.ParseFromArray(buffer, buffer_length)) { | ||||
|         this->handle_check_fail(strobf("failed to parse property update packet").string()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if(response.has_web_certificate()) { | ||||
|         auto& certificate = response.web_certificate(); | ||||
|         serverInstance->setWebCertRoot(certificate.key(), certificate.certificate(), certificate.revision()); | ||||
|     } | ||||
| 
 | ||||
|     if(response.has_reset_speach()) | ||||
|         serverInstance->resetSpeechTime(); | ||||
|     serverInstance->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ] = response.speach_varianz_corrector(); | ||||
| 
 | ||||
|     this->request_state_ = request_state::finishing; | ||||
|     this->handle_check_succeeded(); | ||||
| } | ||||
| 
 | ||||
| bool LicenseService::send_license_update_request() { | ||||
|     ts::proto::license::RequestLicenseUpgrade request{}; | ||||
|     this->client_send_message(::license::protocol::PACKET_CLIENT_LICENSE_UPGRADE, request); | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| inline std::string format_time(const std::chrono::system_clock::time_point& time); | ||||
| void LicenseService::handle_message_license_update(const void *buffer, size_t buffer_length) { | ||||
|     std::lock_guard rlock{this->request_lock}; | ||||
|     if(this->request_state_ != request_state::license_upgrade) { | ||||
|         this->handle_check_fail(strobf("invalid request state for license upgrade packet").string()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ts::proto::license::LicenseUpgradeResponse response{}; | ||||
|     if(!response.ParseFromArray(buffer, buffer_length)) { | ||||
|         this->handle_check_fail(strobf("failed to parse license upgrade packet").string()); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if(!response.valid()) { | ||||
|         logError(LOG_INSTANCE, strobf("Failed to upgrade license: {}").string(), response.error_message()); | ||||
|         goto error_exit; | ||||
|     } else { | ||||
|         std::string error{}; | ||||
|         auto license_data = response.license_key(); | ||||
|         auto license = ::license::readLocalLicence(license_data, error); | ||||
|         if(!license) { | ||||
|             logError(LOG_INSTANCE, strobf("Failed to parse received upgraded license key: {}").string(), error); | ||||
|             goto error_exit; | ||||
|         } | ||||
|         if(!license->isValid()) { | ||||
|             logError(LOG_INSTANCE, strobf("Received license seems to be invalid.").string()); | ||||
|             goto error_exit; | ||||
|         } | ||||
| 
 | ||||
|         auto end = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{license->data.endTimestamp}; | ||||
|         logMessage(LOG_INSTANCE, strobf("Received new license registered to {}, valid until {}").string(), license->data.licenceOwner, format_time(end)); | ||||
|         if(!config::update_license(error, license_data)) | ||||
|             logError(LOG_INSTANCE, strobf("Failed to write new license key to config file: {}").string(), error); | ||||
| 
 | ||||
|         config::license = license; | ||||
| 
 | ||||
|         this->send_license_validate_request(); | ||||
|         this->request_state_ = request_state::license_validate; | ||||
|     } | ||||
| 
 | ||||
|     return; | ||||
|     error_exit: | ||||
|     logError(LOG_INSTANCE, strobf("License upgrade failed. Using old key.").string()); | ||||
|     if(this->license_invalid_reason.has_value()) { | ||||
|         this->handle_check_fail(strobf("Failed to verify license (").string() + *this->license_invalid_reason + ")"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     this->send_property_update_request(); | ||||
|     this->request_state_ = request_state::property_update; | ||||
| } | ||||
| 
 | ||||
| /* request scheduler */ | ||||
| inline std::string format_time(const std::chrono::system_clock::time_point& time) { | ||||
|     std::time_t now = std::chrono::system_clock::to_time_t(time); | ||||
|     std::tm * ptm = std::localtime(&now); | ||||
|     char buffer[128]; | ||||
|     const auto length = std::strftime(buffer, 128, "%a, %d.%m.%Y %H:%M:%S", ptm); | ||||
|     return std::string{buffer, length}; | ||||
| } | ||||
| 
 | ||||
| void LicenseService::schedule_next_request(bool request_success) { | ||||
|     auto& fail_count = this->timings.failed_count; | ||||
|     if(request_success) | ||||
|         fail_count = 0; | ||||
|     else | ||||
|         fail_count++; | ||||
| 
 | ||||
|     std::chrono::milliseconds next_request; | ||||
|     if(fail_count == 0) | ||||
|         next_request = std::chrono::hours{2}; | ||||
|     if(fail_count <= 1) | ||||
|         next_request = std::chrono::minutes(1); | ||||
|     else if(fail_count <= 5) | ||||
|         next_request = std::chrono::minutes(5); | ||||
|     else if(fail_count <= 10) | ||||
|         next_request = std::chrono::minutes(10); | ||||
|     else | ||||
|         next_request = std::chrono::minutes(30); | ||||
| #ifdef DO_LOCAL_REQUEST | ||||
|     next_request = std::chrono::seconds(30); | ||||
| #endif | ||||
| 
 | ||||
|     this->timings.next_request = this->timings.last_request + next_request; | ||||
|     if(this->verbose_) | ||||
|         logMessage(LOG_INSTANCE, strobf("Scheduling next check at {}").c_str(), format_time(this->timings.next_request)); | ||||
| } | ||||
| 
 | ||||
| void LicenseService::execute_tick() { | ||||
|     std::unique_lock rlock{this->request_lock, std::try_to_lock}; /* It will be slightly blocking when its within the message hendeling */ | ||||
|     if(!rlock) return; | ||||
| 
 | ||||
|     /* do it not above because if we might have a deadlock here we don't want to punish the user */ | ||||
|     if(this->timings.last_succeeded.time_since_epoch().count() == 0) { | ||||
|         auto difference = config::license->isPremium() ? std::chrono::hours{24 * 4} : std::chrono::hours{24 * 7}; | ||||
|         if(std::chrono::steady_clock::now() - difference > this->startup_timepoint_) { | ||||
|             this->startup_timepoint_ = std::chrono::steady_clock::now(); /* shut down only once */ | ||||
| 
 | ||||
|             if(config::license->isPremium()) | ||||
|                 logCritical(LOG_INSTANCE, strobf("Failed to validate license within 4 days.").string()); | ||||
|             else | ||||
|                 logCritical(LOG_INSTANCE, strobf("Failed to validate instance integrity within 7 days.").string()); | ||||
|             logCritical(LOG_INSTANCE, strobf("Stopping server!").string()); | ||||
|             ts::server::shutdownInstance(); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     auto now = std::chrono::system_clock::now(); | ||||
|     if(this->request_state_ != request_state::empty) { | ||||
|         if(this->timings.last_request + std::chrono::minutes{5} < now) { | ||||
|             this->handle_check_fail(strobf("Scheduling next check at {}").string()); | ||||
|         } else { | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     if(std::chrono::system_clock::now() > this->timings.next_request) | ||||
|         this->begin_request(); | ||||
| } | ||||
							
								
								
									
										134
									
								
								server/src/lincense/LicenseService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								server/src/lincense/LicenseService.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,134 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <variant> | ||||
| #include <thread> | ||||
| #include <mutex> | ||||
| #include <memory> | ||||
| 
 | ||||
| namespace license::client { | ||||
|     class LicenseServerClient; | ||||
| } | ||||
| 
 | ||||
| namespace google::protobuf { | ||||
|     class Message; | ||||
| } | ||||
| 
 | ||||
| namespace ts::server::license { | ||||
|     struct InstanceLicenseInfo { | ||||
|         std::shared_ptr<::license::License> license{nullptr}; | ||||
|         std::string web_certificate_revision{}; | ||||
| 
 | ||||
|         struct metrics_ { | ||||
|             size_t servers_online{0}; | ||||
|             size_t client_online{0}; | ||||
|             size_t web_clients_online{0}; | ||||
|             size_t bots_online{0}; | ||||
|             size_t queries_online{0}; | ||||
| 
 | ||||
|             size_t speech_total{0}; | ||||
|             size_t speech_varianz{0}; | ||||
|             size_t speech_online{0}; | ||||
|             size_t speech_dead{0}; | ||||
|         } metrics; | ||||
| 
 | ||||
|         struct info_ { | ||||
|             std::chrono::milliseconds timestamp{}; | ||||
|             std::string version{}; | ||||
|             std::string uname{}; | ||||
|             std::string unique_id{}; | ||||
|         } info; | ||||
|     }; | ||||
| 
 | ||||
| 
 | ||||
|     class LicenseService { | ||||
|         public: | ||||
|             LicenseService(); | ||||
|             ~LicenseService(); | ||||
| 
 | ||||
|             [[nodiscard]] bool initialize(std::string& /* error */); | ||||
|             void shutdown(); | ||||
| 
 | ||||
|             /* whatever it failed/succeeded */ | ||||
|             bool execute_request_sync(const std::chrono::milliseconds& /* timeout */); | ||||
| 
 | ||||
|             [[nodiscard]] inline bool verbose() const { return this->verbose_; } | ||||
|             void execute_tick(); /* should not be essential to the core functionality! */ | ||||
|         private: | ||||
|             std::chrono::steady_clock::time_point startup_timepoint_; | ||||
| 
 | ||||
|             enum struct request_state { | ||||
|                 empty, | ||||
| 
 | ||||
|                 /* initializing */ | ||||
|                 dns_lookup, | ||||
|                 connecting, | ||||
| 
 | ||||
|                 /* connected states */ | ||||
|                 license_validate, | ||||
|                 license_upgrade, | ||||
|                 property_update, | ||||
| 
 | ||||
|                 /* disconnecting */ | ||||
|                 finishing | ||||
|             }; | ||||
|             bool verbose_{false}; | ||||
| 
 | ||||
|             std::recursive_timed_mutex request_lock{}; | ||||
|             request_state request_state_{request_state::empty}; | ||||
|             std::unique_ptr<::license::client::LicenseServerClient> current_client{nullptr}; | ||||
|             std::shared_ptr<InstanceLicenseInfo> license_request_data{nullptr}; | ||||
| 
 | ||||
|             std::condition_variable sync_request_cv; | ||||
|             std::mutex sync_request_lock; | ||||
| 
 | ||||
|             struct _timings { | ||||
|                 std::chrono::system_clock::time_point last_request{}; | ||||
|                 std::chrono::system_clock::time_point next_request{}; | ||||
| 
 | ||||
|                 std::chrono::system_clock::time_point last_succeeded{}; | ||||
|                 size_t failed_count{0}; | ||||
|             } timings; | ||||
| 
 | ||||
|             struct _dns { | ||||
|                 std::shared_ptr<std::recursive_mutex> lock{nullptr}; | ||||
| 
 | ||||
|                 struct _lookup { | ||||
|                     std::shared_ptr<std::recursive_mutex> lock{nullptr}; | ||||
|                     std::thread thread{}; | ||||
| 
 | ||||
|                     LicenseService* handle{nullptr}; /* may be null, locked via lock */ | ||||
|                 }* current_lookup{nullptr}; | ||||
|             } dns; | ||||
| 
 | ||||
|             std::optional<std::string> license_invalid_reason{}; /* set if the last license is invalid */ | ||||
| 
 | ||||
|             void schedule_next_request(bool /* last request succeeded */); | ||||
| 
 | ||||
|             void begin_request(); | ||||
|             void client_send_message(::license::protocol::PacketType /* type */, ::google::protobuf::Message& /* message */); | ||||
|             void handle_check_fail(const std::string& /* error */); /* might be called form the DNS loop */ | ||||
|             void handle_check_succeeded(); | ||||
| 
 | ||||
|             /* if not disconnect message has been set it will just close the connection */ | ||||
|             void abort_request(std::lock_guard<std::recursive_timed_mutex>& /* request lock */, const std::string& /* disconnect message */); | ||||
| 
 | ||||
|             void abort_dns_request(); | ||||
|             void execute_dns_request(); | ||||
| 
 | ||||
|             /* will be called while dns lock has been locked! */ | ||||
|             void handle_dns_lookup_result(bool /* success */, const std::variant<std::string, sockaddr_in>& /* data */); | ||||
| 
 | ||||
|             /* all callbacks bellow are called from the current_client. It will not be null while being within the callback. */ | ||||
|             void handle_client_connected(); | ||||
|             void handle_client_disconnected(const std::string& /* error */); | ||||
| 
 | ||||
|             void handle_message(::license::protocol::PacketType /* type */, const void* /* buffer */, size_t /* length */); | ||||
|             void handle_message_license_info(const void* /* buffer */, size_t /* length */); | ||||
|             void handle_message_license_update(const void* /* buffer */, size_t /* length */); | ||||
|             void handle_message_property_adjustment(const void* /* buffer */, size_t /* length */); | ||||
| 
 | ||||
|             void send_license_validate_request(); | ||||
|             bool send_license_update_request(); | ||||
|             void send_property_update_request(); | ||||
|     }; | ||||
| } | ||||
| @ -1,17 +1,20 @@ | ||||
| #include "./CommandHandler.h" | ||||
| 
 | ||||
| #include <csignal> | ||||
| #include <src/SignalHandler.h> | ||||
| #include <src/VirtualServer.h> | ||||
| #include <src/client/ConnectedClient.h> | ||||
| #include <src/VirtualServerManager.h> | ||||
| #include <src/InstanceHandler.h> | ||||
| #include <log/LogUtils.h> | ||||
| #include <src/ShutdownHelper.h> | ||||
| #include <misc/time.h> | ||||
| #include <misc/memtracker.h> | ||||
| #include <sql/sqlite/SqliteSQL.h> | ||||
| #include <sys/resource.h> | ||||
| #include "CommandHandler.h" | ||||
| #include "src/server/QueryServer.h" | ||||
| #include <protocol/buffers.h> | ||||
| 
 | ||||
| #include "../SignalHandler.h" | ||||
| #include "../client/ConnectedClient.h" | ||||
| #include "../InstanceHandler.h" | ||||
| #include "../VirtualServerManager.h" | ||||
| #include "../VirtualServer.h" | ||||
| #include "../ShutdownHelper.h" | ||||
| #include "../server/QueryServer.h" | ||||
| 
 | ||||
| #ifdef HAVE_JEMALLOC | ||||
|     #include <jemalloc/jemalloc.h> | ||||
|  | ||||
							
								
								
									
										2
									
								
								shared
									
									
									
									
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								shared
									
									
									
									
									
								
							| @ -1 +1 @@ | ||||
| Subproject commit 51d9a002e3311a2079b25253b7946a4b03a9baf4 | ||||
| Subproject commit fd8aba2ede8675f4feb9fcfc01ada3822f2d4780 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user