diff options
author | Sami Littow <sami@Alten-MacBook.local> | 2022-12-19 10:31:49 +0200 |
---|---|---|
committer | Sami <sami.littow@qt.io> | 2022-12-30 08:14:17 +0200 |
commit | ef4ba13bff5d2d0499e8af9f405ecf506aaa85d6 (patch) | |
tree | 615c90d95662b09a89790328292019818158bc18 | |
parent | ac5b2c916e9234947df8c240227b260330f97150 (diff) |
Refactoring (prepare for QA tools support)
31 files changed, 1057 insertions, 539 deletions
@@ -33,3 +33,9 @@ Changes in 2.0.3: Changes in 2.0.4: - Changed daemon response message in case there is no licenses left in the pool + +Changes in 2.1.0: +- Floating license support added (Squish) +- Daemon name 'licd' changed to 'qtlicd' +- Concept of "long-term" changed to "permanent" +- Daemon architecture changed towards more modular approach diff --git a/CMakeLists.txt b/CMakeLists.txt index bb71ed5..585a7cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,15 +16,17 @@ ADD_DEFINITIONS(-DUNICODE) ADD_DEFINITIONS(-D_UNICODE) list(APPEND targetSrc - ${CMAKE_CURRENT_LIST_DIR}/licenser.cpp - ${CMAKE_CURRENT_LIST_DIR}/tcpserver.cpp - ${CMAKE_CURRENT_LIST_DIR}/httpclient.cpp - ${CMAKE_CURRENT_LIST_DIR}/jsonhandler.cpp - ${CMAKE_CURRENT_LIST_DIR}/utils.cpp - ${CMAKE_CURRENT_LIST_DIR}/licdsetup.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/licenser.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/tcpserver.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/httpclient.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/jsonhandler.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/utils.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/licdsetup.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/clienthandler.cpp ) list(APPEND includes ${CMAKE_CURRENT_LIST_DIR}/include + ${CMAKE_CURRENT_LIST_DIR}/src/daemon_clients ${CMAKE_CURRENT_LIST_DIR}/3rdparty ) @@ -66,13 +68,11 @@ message("Libs: ${libs}") add_subdirectory(3rdparty/hmac_sha256) add_subdirectory(qtlicensetool) add_subdirectory(mocwrapper) -add_executable(licheck dummy_licheck.cpp) +add_executable(licheck src/dummy_licheck.cpp) add_executable(${PROJECT_NAME} ${targetSrc}) if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") target_link_options(${PROJECT_NAME} PRIVATE "/NODEFAULTLIB:LIBCMT") target_link_options(${PROJECT_NAME} PRIVATE "/NODEFAULTLIB:MSVCRTD") - #add_executable(svccontrol windowsdaemon/SvcControl.cpp) - #add_executable(svcconfig windowsdaemon/SvcConfig.cpp) endif () target_include_directories(${PROJECT_NAME} PUBLIC ${includes}) target_link_libraries(${PROJECT_NAME} ${libs} ) diff --git a/include/commonsetup.h b/include/commonsetup.h index f523216..a3cf72e 100644 --- a/include/commonsetup.h +++ b/include/commonsetup.h @@ -2,10 +2,10 @@ * * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 */ - #pragma once #include <map> +#include <string> #include "version.h" #define DAEMON_ADDR "localhost" @@ -16,17 +16,24 @@ #define DEFAULT_USER_SETTINGS_TAG "[default]" #define SERVER_VERSION_CMD "serverversion" -#define DAEMON_VERSION_CMD "version" -#define RESERVATION_QUERY_CMD "reservation" +#define DAEMON_VERSION_CMD "daemon_version" +#define RESERVATION_QUERY_CMD "reservation_query" #define LICENSE_REQUEST_CMD "license" #define LONGTERM_REQUEST_CMD "longterm" -#define LONGTERM_ADD_OP "add" -#define LONGTERM_REMOVE_OP "remove" +#define OP_ADD_RESERVATION "add" +#define OP_REMOVE_RESERVATION "remove" +#define QTLICENSETOOL_APP_NAME "clitool" +#define SQUISH_IDE_APP_NAME "squish-ide" +#define SQUISH_APP_NAME "squish" +#define COCO_APP_NAME "coco" #define MOCWRAPPER_APP_NAME "moc" +#define CREATOR_APPNAME "qtcreator" +#define DESIGN_STUDIO_APP_NAME "qtdesignstudio" #define ORIGINAL_MOC_PREFIX "orig_" -#define QTLICENSETOOL_APP_NAME "Qt License Tool" +#define LICENSE_FILE_PREFIX "lic_" +#define LICENSE_FILE_EXTENSION ".json" #if __APPLE__ || __MACH__ #define WORKING_DIR "/opt/licd" @@ -36,7 +43,76 @@ #define WORKING_DIR "C:/Program Files/licd/" #endif #define DAEMON_SETTINGS_FILE WORKING_DIR "/licd.ini" -#define LICENSE_FILE_BASE WORKING_DIR "/lic_" #define SHA256_HASH_SIZE 32 #define SECS_IN_HOUR 3600 -#define SECS_IN_DAY (SECS_IN_HOUR * 24) +#define SECS_IN_DAY 86400 + +struct License { + uint64_t last_timestamp = 0; // | + uint64_t current_timestamp = 0; // | For internal use only, not in server resp JSON + uint64_t expiry_epoch = 0; // _| + uint16_t leeway_hours = 0; + std::string message; + bool status = false; + std::string expiry_date; + std::string operation; + std::string license_key; + std::string license_id; + std::string reservation_id; + std::string parent_reservation_id; // TODO Coming with qa tools: Check with server end! + std::string user_id; +}; + +enum RequestReply { + e_bad_request = -1, + e_got_response = 0, + e_license_granted = 1, + e_license_rejected = 2, + e_no_conn_leeway = 3, + e_license_pool_full = 4, + e_bad_connection = 5 +}; + +enum class RequestType { + no_request = 0, + license_request = 1, + keepalive_report = 2, + license_release = 3, + long_term_request = 4, + server_version = 5, + daemon_version = 6, + reservation_query = 7, +}; + +enum class ClientType { + client_undefined = -1, + client_moc = 1, + client_plugin, + client_CLI, + client_squish, + client_squish_ide, + client_coco +}; + +// Struct to store request info +struct RequestInfo { + uint16_t socketId; + RequestType type = RequestType::no_request; + ClientType client; + uint16_t updateIntervalSecs; + std::string licenseFile; + std::string reservationID; + std::string operation; + std::string appName; + std::string appVersion; + std::string userId; + std::string licenseId; + std::string email; + std::string payload; + std::string serverAddr; + uint64_t startTimestamp; // used by QA-Tools only + uint64_t stopTimestamp = 0; // used by QA-Tools only + std::string runnerType; // 'tester' | 'exe', used by QA-Tools only + std::string parentReservationId; // used by QA-Tools only +}; + diff --git a/include/jsonhandler.h b/include/jsonhandler.h index 1f334cd..18baad0 100644 --- a/include/jsonhandler.h +++ b/include/jsonhandler.h @@ -2,7 +2,6 @@ * * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 */ - #pragma once #include <algorithm> diff --git a/include/licdsetup.h b/include/licdsetup.h index ec2ed71..41c5564 100644 --- a/include/licdsetup.h +++ b/include/licdsetup.h @@ -2,6 +2,8 @@ * * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 */ +#pragma once + #include "commonsetup.h" #include "utils.h" @@ -22,7 +24,10 @@ enum settings_owner_type { e_set_type_daemon = 0, e_set_type_moc = 1, e_set_type_licensetool = 2, - e_set_type_other = 3 + e_set_type_squish = 3, + e_set_type_squish_ide = 4, + e_aet_type_coco = 5, + e_set_type_other }; class LicdSetup diff --git a/include/licenser.h b/include/licenser.h index 00ba815..449ee9a 100644 --- a/include/licenser.h +++ b/include/licenser.h @@ -13,91 +13,56 @@ #include <ctime> #include <cassert> #if _WIN32 - #define WIN32_LEAN_AND_MEAN // Windows + #define WIN32_LEAN_AND_MEAN //#include <windows.h> #else #endif -#include "commonsetup.h" #include "httpclient.h" #include "tcpserver.h" #include "utils.h" #include "hmac_sha256.h" #include "jsonhandler.h" #include "licdsetup.h" - -enum RequestReply { - e_bad_request = -1, - e_license_granted = 0, - e_license_rejected = 1, - e_no_conn_leeway = 2, - e_license_pool_full = 3, - e_bad_connection = 4 -}; - -enum class RequestType { - no_request = 0, - normal_license_renewal = 1, - long_term_request = 2, - server_version = 3, - daemon_version = 4, - reservation_query = 5 -}; - -// Struct to store incoming request info for easier handling -struct RequestInfo { - RequestType type = RequestType::no_request; - std::string operation; - std::string appName; - std::string appVersion; - std::string userId; - std::string licenseId; - std::string email; - std::string licenseFile; - std::string userHome; - std::string payload; - std::string serverAddr; -}; +#include "clienthandler.h" +#include "mochandler.h" +#include "clitoolhandler.h" +#include "pluginhandler.h" +#include "cocohandler.h" +#include "squishhandler.h" +#include "squishidehandler.h" class Licenser { public: explicit Licenser(uint16_t tcpPort = 0); ~Licenser(); + int listen(); - int sendLicensingRequest(std::string &reply, const RequestInfo &request); - int sendLongTermRequest(std::string &reply, const RequestInfo &request); - int getListeningPort(); + int sendServerRequest(std::string &reply); private: HttpClient *m_http; TcpServer *m_server; uint64_t m_mocInterval; std::string m_infoString; + ClientHandler *m_currentClient; + std::vector<ClientHandler*> m_floatingClients; - int parseIncoming(const std::string &incoming, RequestInfo &request); - int buildRequestJson(RequestInfo &request); - int parseAndSaveJsonReply(std::string &reply, const RequestInfo &request); - bool checkLicenseExpiryDate(std::string &reply, const RequestInfo &request); - bool isMocRenewalDue(const std::string &licenseFile); + int parseInputAndCreateCLient(uint16_t socketId, const std::string &incoming); void doHmacHashSha256(const std::string &payload, const std::string &secret, std::string &authKey); int initDaemonSettings(); std::string checkReservations(); std::string askServerVersion(); - std::string getVersion(); + std::string getDaemonVersion(); + int checkReportsDue(); + void clientDisconnected(int socketId); + void setHwId(); LicdSetup *settings; - - std::map<RequestReply, std::string> replyString { - {e_bad_request, "ERROR Bad request"}, - {e_license_granted, "License acquired."}, - {e_license_rejected, "No valid license acquired"}, - {e_no_conn_leeway, "License granted with warning: No server connection. Leeway time left: "}, - {e_license_pool_full, "All licenses in use. No more license available on the server."}, - {e_bad_connection, "No connection to server. Try again later."} - }; }; + #ifndef __TIME_IMP #define __TIME_IMP #if _WIN32 diff --git a/include/tcpserver.h b/include/tcpserver.h index 57f7930..f7def7a 100644 --- a/include/tcpserver.h +++ b/include/tcpserver.h @@ -15,6 +15,8 @@ #define TRUE 1 #define FALSE 0 +#define CLIENT_CLOSED_MSG "closed" + #if __APPLE__ || __MACH__ || __linux__ #include <sys/types.h> #include <sys/socket.h> diff --git a/include/utils.h b/include/utils.h index b8eceb0..13a4e6c 100644 --- a/include/utils.h +++ b/include/utils.h @@ -3,9 +3,11 @@ * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 */ #pragma once +#include "commonsetup.h" #include <sys/stat.h> #include <sys/types.h> +#include <sys/time.h> #include <iostream> #include <fstream> #include <sstream> @@ -13,6 +15,8 @@ #include <algorithm> #include <ctime> #include <cstring> + + #if _WIN32 // Windows #define WIN32_LEAN_AND_MEAN @@ -61,13 +65,15 @@ bool fileExists(const std::string &name); std::string getFileOwnerName(const std::string &filename); int getFileOwner(const std::string &filename, uint16_t &owner, uint16_t &group); std::vector<std::string> getDirListing(const std::string &directory, const std::string &filter = ""); +int deleteFile(const std::string &filepath); /* * Other */ -// Time conversions +// Time utils std::string epochToString(time_t epochTime, const char* format = "%Y-%m-%d %H:%M:%S"); time_t stringToEpoch(const char* theTime, const char* format = "%Y-%m-%d %H:%M:%S"); +uint64_t getTimestampNow(); // Find out host OS std::string getOsName(); @@ -33,20 +33,21 @@ long-term_access_point=/api/v2/reservations/long-term # Type: string version_query_access_point=/api/v2/ping ################### -# moc_request_interval +# moc_renewal_interval # Duration (in hours) after which MOC license requests will be renewed (request forwarded to the server). # In the meantime, timestamp stored in the license file will be used # Type: int moc_renewal_interval=24 ################### -# license_file -# filename for storing license server reply JSON. Note! Username and license ID will be added to this -# Type: string -license_file_base=lic_ -# license_file_extension -# filename extension for storing license server reply JSON -# Type: string -license_file_extension=.json +# squish_report_interval +# Duration (in secs) between update calls to the server while Squish is working. +# Type: int +squish_report_interval=300 +################### +# coco_update_interval +# Duration (in secs) between update calls to the server while Coco is working. +# Type: int +coco_update_interval=5 ################### # TCP/IP port where the daemon is listening for requests tcp_listening_port=60000 diff --git a/licenser.cpp b/licenser.cpp deleted file mode 100644 index ca52e4b..0000000 --- a/licenser.cpp +++ /dev/null @@ -1,428 +0,0 @@ -/* Copyright (C) 2022 The Qt Company Ltd. - * - * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 -*/ - -#include "licenser.h" - -Licenser::Licenser(uint16_t tcpPort) -{ - // Init daemon settings - settings = new LicdSetup(e_set_type_daemon); - - if (tcpPort == 0) { - tcpPort = utils::strToInt(settings->get("tcp_listening_port")); - } - m_mocInterval = utils::strToInt(settings->get("moc_renewal_interval")) * 3600; // 3600 = secs in hour - // Start the HTTP client - m_http = new HttpClient(settings->get("server_addr"), - settings->get("reservation_access_point"), - settings->get("long-term_access_point"), - settings->get("version_query_access_point")); - // Start the TCP/IP m_server - m_server = new TcpServer(tcpPort); - - -} - -Licenser::~Licenser() -{ - delete(settings); - delete(m_server); - delete(m_http); - std::cout << "Daemon stopped." << std::endl; -} - -int Licenser::listen() -{ - - m_infoString = ""; - int socket = 0; // Placeholder for whatever socket gets active - std::string input = m_server->listenToClients(socket); - input = utils::trimStr(input); - if (input.empty()) { - return 0; //continue; - } - std::cout << "Got an request: " << input << std::endl; - RequestInfo request; - std::string reply = ""; - if (parseIncoming(input, request) == 0) { - std::string payload = ""; - if (request.type == RequestType::normal_license_renewal) { - std::cout << "Standard license renewal request\n"; - // See if it's MOC who's calling. Make sure app name is accepted, no matter of the case. - // request.appname is set in lowercase already in parseIncoming() method at this point - if (request.appName == utils::strToLower(MOCWRAPPER_APP_NAME)) { - // More than 24hrs (licd.ini default) from last successful request? - if (!isMocRenewalDue(request.licenseFile)) { - // No - Just send ok message and no further actions needed - reply = replyString[e_license_granted]; - m_server->sendResponse(socket, reply); - return 0; //continue; - } - } - if (buildRequestJson(request) == 0) { - if (sendLicensingRequest(reply, request) != 0) { - // Bad server connection: Check the existing license file for expiry date - checkLicenseExpiryDate(reply, request); - } else { - parseAndSaveJsonReply(reply, request); - } - } else { - reply = replyString[e_bad_request]; - } - } else if (request.type == RequestType::long_term_request) { - std::cout << "Long-term license request\n"; - if (buildRequestJson(request) == 0) { - if (sendLongTermRequest(reply, request) != 0) { - reply = replyString[e_bad_connection]; - reply += m_http->error(); - } else { - parseAndSaveJsonReply(reply, request); - } - } - } else if (request.type == RequestType::reservation_query) { - reply = checkReservations(); - } else if (request.type == RequestType::server_version) { - reply = askServerVersion(); - } - else if (request.type == RequestType::daemon_version) { - reply = getVersion(); - } - } else { - reply = replyString[e_bad_request] + '\n'; - } - reply += m_infoString; - reply +="\n"; - m_server->sendResponse(socket, reply); - std::cout << "Replied to socket " << socket << ": " << reply << std::endl; - return 0; -} - -std::string Licenser::getVersion() -{ - std::string version = "Qt License Daemon (licd) v"; - version += DAEMON_VERSION; - return version; -} - -int Licenser::parseIncoming(const std::string &incoming, RequestInfo &request) -{ - std::vector<std::string> req = utils::splitStr(incoming, '-'); - - // First find out the command (and operation for longterm case) - std::string cmd = utils::trimStr(req[0]); - if (cmd == LICENSE_REQUEST_CMD) { - request.type = RequestType::normal_license_renewal; - } else if (cmd.substr(0, 8) == LONGTERM_REQUEST_CMD) { - request.type = RequestType::long_term_request; - std::string op = utils::trimStr(cmd.substr(8, cmd.length() -1)); - if (op != LONGTERM_ADD_OP && op != LONGTERM_REMOVE_OP) { - m_infoString = "Invalid longterm operation: " + op; - std::cout << m_infoString; - return e_bad_request; - } - request.operation = op; - } else if (cmd == SERVER_VERSION_CMD) { - request.type = RequestType::server_version; - } else if (cmd == DAEMON_VERSION_CMD) { - request.type = RequestType::daemon_version; - return 0; - } else if (cmd == RESERVATION_QUERY_CMD) { - request.type = RequestType::reservation_query; - return 0; - } else { - std::cout << "Invalid command: " << cmd << std::endl; - return e_bad_request; - } - - // Then cycle through parameters. Start from [1] because [0] is the command - for (int i = 1; i < req.size(); i++) { - std::string item = req[i]; - char arg = (char)item[0]; - std::string val = utils::trimStr(item.substr(1, item.length() - 1)); - if (val.length() == 0) { - m_infoString = "No value for argument: -"; - m_infoString.push_back(arg); - std::cout << m_infoString << std::endl; - return e_bad_request; - } - - if (arg == 'a') request.appName = utils::strToLower(val); - else if (arg == 'v') request.appVersion = val; - else if (arg == 'u') request.userId = val; - else if (arg == 'i') request.licenseId = val; - else if (arg == 'l') request.serverAddr = val; - else { - m_infoString = "Invalid argument: -"; - m_infoString.push_back(arg); - return e_bad_request; - } - } - - // Sanity checks. May not be needed after all, if the clients are made correctly. - if (request.type == RequestType::server_version) { - return 0; // version query has no mandatory parameters, no checks - } - - bool fail = false; - if (request.userId.empty()) { - std::cout << "Missing argument: Username (-u)\n"; - fail = true; - } - if (request.licenseId.empty()) { - std::cout << "Missing argument: License ID (-i)\n"; - fail = true; - } - if (request.type == RequestType::normal_license_renewal) { - if (request.appName.empty()) { - std::cout << "Missing argument: Application name (-a)\n"; - fail = true; - } - if (request.appVersion.empty()) { - std::cout << "Missing argument: Application version (-v)\n"; - fail = true; - } - } else if (request.type == RequestType::long_term_request) { - if (request.operation.empty()) { - m_infoString = "Missing argument: Longterm operation (add/remove)\n"; - std::cout << m_infoString << std::endl; // For logging - fail = true; - } - } - if (fail) { - return e_bad_request; - } - - // Add user info to the license filename - std::stringstream ss; - ss << WORKING_DIR << DIR_SEPARATOR - << settings->get("license_file_base") << request.userId - << "_" << request.licenseId << settings->get("license_file_extension"); - request.licenseFile = ss.str(); - - return 0; -} - -int Licenser::buildRequestJson(RequestInfo &request) -{ - std::stringstream pay; - pay << "{"; - pay<< "\"license_number\":" << "\"" << request.licenseId << "\","; - pay << "\"user_id\":" << "\"" << request.userId << "\","; - if (request.type == RequestType::normal_license_renewal) { - pay << "\"hw_id\":" << "\"" << settings->get("hw_id") << "\","; - pay << "\"src\":" << "\"" << request.appName << "\","; - pay << "\"host_os\":" << "\"" << settings->get("host_os") << "\","; - pay << "\"src_version\":" << "\"" << request.appVersion << "\","; - pay << "\"email\":" << "\"" << request.email << "\""; - } else if (request.type == RequestType::long_term_request) { - pay << "\"operation\":" << "\"" << request.operation << "\""; - } - pay << "}"; - request.payload = pay.str(); - return 0; -} - -int Licenser::parseAndSaveJsonReply(std::string &reply, const RequestInfo &request) -{ - JsonHandler json(reply); - std::stringstream ss; - if (request.type == RequestType::long_term_request) { - // long-term request: Have "message" from response JSON directly as a reply to the user - reply = json.get("message"); - if (json.get("status") != "true" ) { - return 1; - } - // Nothing to add to the server message - } else { - if (json.get("status") == "true") { - ss << replyString[e_license_granted]; - ss << " expiry_date=" << json.get("expiry_date"); - ss << " license_id=" << json.get("license_number"); - ss << " reservation_id=" << json.get("reservation_id"); - reply = ss.str(); - } else { - if (json.get("message") == "License fully reserved") { - reply = replyString[e_license_pool_full]; - } else { - reply = replyString[e_license_rejected]; - } - // Do not save the response json, just return - return 1; - } - } - - // Add timestamp into license JSON - struct timeval tp; - gettimeofday(&tp, NULL); - uint64_t timeNow = tp.tv_sec; - - // Save the JSON, adding timestamp - json.add("last_timestamp", timeNow); - int result = utils::writeToFile(request.licenseFile, json.dump(4)); - if (result != 0) { - std::cout << "ERROR saving license file: '" << request.licenseFile << "': " << strerror(result) << std::endl; - } - return 0; -} - -bool Licenser::checkLicenseExpiryDate(std::string &reply, const RequestInfo &request) -{ - std::cout << "Offline - checking validity from license file " << request.licenseFile << std::endl; - // Open the license file - std::string data; - if (utils::readFile(data, request.licenseFile) != 0) { - std::cout << "No license file - rejecting license " << request.licenseFile << std::endl; - reply = replyString[e_bad_connection]; - return false; - } - JsonHandler license(data); - // Check license status - if (license.get("status") != "true") { - std::cout << "License status = false: Rejecting license\n"; - reply = replyString[e_bad_connection]; - return false; - } - // Expiry date. Add 1 day to expiry date to get the end actually at the beginning of the next day - std::string expDate = license.get("expiry_date"); - std::time_t expEpoch = utils::stringToEpoch(expDate.c_str()) + SECS_IN_DAY; - // Current date - std::time_t current = std::time(0); - // See if the time has expire - if (current > expEpoch) { - std::cout << "Expiry date " << expDate << " exceeded" << std::endl; - // License expired: Allow some leeway time for MOC and Plugin, but not for qtlicensetool - if (utils::strToLower(request.appName) != utils::strToLower(QTLICENSETOOL_APP_NAME)) { - int leewayTimeLeft = expEpoch + (license.getInt("leeway_hours") * SECS_IN_HOUR) - current; - if (leewayTimeLeft > 0) { - std::stringstream ss; - ss << replyString[RequestReply::e_no_conn_leeway] - << std::fixed << std::setprecision(1) - << (float)leewayTimeLeft / SECS_IN_DAY << " days"; - reply = ss.str(); - return true; - } else { - std::cout << "No leeway time left" << std::endl; - } - } - std::cout << "Rejecting license\n"; - reply = replyString[e_license_rejected]; - return false; - } - std::cout << "License granted, expires at " << expDate << std::endl; - reply = replyString[e_license_granted]; - return true; -} - -bool Licenser::isMocRenewalDue(const std::string &licenseFile) -{ - std::cout << "MOC calling, checking if renewal is needed\n"; - // Open the license file - std::string data; - if (utils::readFile(data, licenseFile) != 0) { - // If there is no license file yet, have to request anyway - std::cout << "No license file present (" << licenseFile << ") - requesting license\n"; - return true; - } - JsonHandler license(data); - if (license.get("status") != "true") { - // Failed license file shouldn't exist - just to be sure: - return true; - } - // Get current time - struct timeval tp; - gettimeofday(&tp, NULL); - uint64_t timeNow = tp.tv_sec; - if (timeNow > utils::strToInt(license.get("last_timestamp")) + m_mocInterval) { - // Renewal is due - std::cout << "Requesting license renewal\n"; - return true; - } - std::cout << "Renewal time not due, granting license\n"; - - return false; -} - -int Licenser::sendLicensingRequest(std::string &reply, const RequestInfo &request) -{ - // Generate auth hash - std::string authKey; - doHmacHashSha256(request.payload, settings->get("server_secret"), authKey); // 19755982232ff7b6f6d0f3c57ffc1c0e4f03060e7175d478f7b146fb1e000507"; - - // Send the request - if (m_http->sendRequest(reply, request.payload, request.serverAddr, authKey) != 0) { - return -1; - } - return 0; -} - -int Licenser::sendLongTermRequest(std::string &reply, const RequestInfo &request) -{ - if (m_http->sendRequest(reply, request.payload, request.serverAddr) != 0) { - return -1; - } - return 0; -} - -void Licenser::doHmacHashSha256(const std::string &payload, const std::string &secret, std::string &authKey) -{ - std::stringstream ss_result; - ss_result << "apikey "; - - // Allocate memory for the HMAC - std::vector<uint8_t> out(SHA256_HASH_SIZE); - - // Call hmac-sha256 function - hmac_sha256(secret.data(), secret.size(), payload.data(), payload.size(), - out.data(), out.size()); - - // Convert `out` to string with std::hex - for (uint8_t x : out) { - ss_result << std::hex << std::setfill('0') << std::setw(2) << (int)x; - } - - authKey = ss_result.str(); - JsonHandler pay(payload); - - // Print out the result - std::cout << "Message: " << pay.dump(4) << std::endl; - std::cout << "Key: " << secret << std::endl; - std::cout << "HMAC: " << authKey << std::endl; -} - -std::string Licenser::checkReservations() -{ - // Open all license files - std::string dir = WORKING_DIR; - std::string filter = settings->get("license_file_base"); - std::vector<std::string> files = utils::getDirListing(dir, filter); - std::stringstream reply; - reply << "Current reservations: \n"; - for (auto file : files) { - std::string data; - if (utils::readFile(data, file) != 0) { - std::cout << "Couldn't read license file: " << file << std::endl; - continue; - } - JsonHandler json(data); - reply << "\n--- License ID " << json.get("license_number") << " ---\n"; - reply << " User ID: " << json.get("user_id") << std::endl; - reply << " Expiry date: " << json.get("expiry_date") << std::endl; - reply << " Reservation ID: " << json.get("reservation_id") << std::endl; - } - return reply.str(); -} - -std::string Licenser::askServerVersion() -{ - std::string reply; - if (m_http->sendRequest(reply, "", "") != 0) { - reply = replyString[e_bad_connection]; - } else if (reply.find("Error") == std::string::npos) { - JsonHandler json(reply); - reply = "Qt License Server v"; - reply += json.get("version"); - } - return reply; -} diff --git a/linux_daemon/linuxdaemon.cpp b/linux_daemon/linuxdaemon.cpp index 3f9e565..55b883d 100644 --- a/linux_daemon/linuxdaemon.cpp +++ b/linux_daemon/linuxdaemon.cpp @@ -5,6 +5,7 @@ #include <sys/stat.h> #include <iostream> #include <unistd.h> + #include "licenser.h" #include "version.h" @@ -23,7 +24,6 @@ int startProcess(uint16_t tcpPort) { return -1; } } - } int main(int argc, char *argv[]) @@ -41,15 +41,15 @@ int main(int argc, char *argv[]) return res; } // If command-line parameter is "--version", show the version and exit. - if (strcmp(argv[1], "--version") == 0) { + else if (strcmp(argv[1], "--version") == 0) { std::cout << "Qt License Daemon (licd) v" << DAEMON_VERSION << " " << COPYRIGHT_TEXT << std::endl; return 0; } - if (strcmp(argv[i], "--port") == 0) { + else if (strcmp(argv[i], "--port") == 0) { if (i + 1 == argc) { all_ok = false; - printf("No port number given"); + printf("No port number given\n"); break; } try { @@ -67,7 +67,7 @@ int main(int argc, char *argv[]) break; } else { - printf("Invalid argument: %s", argv[i]); + printf("Invalid argument: %s\n", argv[i]); all_ok = false; break; } @@ -76,6 +76,8 @@ int main(int argc, char *argv[]) printf("\nUsage: qtlicenser [option] [value]\n"); printf("Supported options are:\n"); printf(" --port <port number> : Specify TCP/IP server port. If omitted, default is used.\n"); + printf(" --nodaemon : Run in non-daemon mode (in console like any other CLI app)\n"); + printf(" --version : Version info\n"); printf(" --help : This help\n\n"); return -1; } diff --git a/mocwrapper/CMakeLists.txt b/mocwrapper/CMakeLists.txt index 5ce10e8..0189642 100644 --- a/mocwrapper/CMakeLists.txt +++ b/mocwrapper/CMakeLists.txt @@ -19,7 +19,7 @@ include_directories ( add_executable(mocwrapper mocwrapper.cpp - ../tcpclient.cpp - ../utils.cpp - ../licdsetup.cpp + ../src/tcpclient.cpp + ../src/utils.cpp + ../src/licdsetup.cpp ) diff --git a/mocwrapper/mocwrapper.cpp b/mocwrapper/mocwrapper.cpp index f4ca9ef..d22137c 100644 --- a/mocwrapper/mocwrapper.cpp +++ b/mocwrapper/mocwrapper.cpp @@ -64,7 +64,7 @@ int requestLicense(const std::string &mocBinPath) std::string addr = user.get("licd_addr"); uint16_t port = utils::strToInt(user.get("licd_port")); std::stringstream ss; - ss << "license -u " << user.get("user_id") << " -i " << user.get("license_id") + ss << "license -u " << user.get("user_id") << "-e " << user.get("user_email") << " -i " << user.get("license_id") << " -a " << MOCWRAPPER_APP_NAME << " -v " << MOCWRAPPER_VERSION; if (user.get("license_server_addr").length() > 0) { ss << " -l " << user.get("license_server_addr") << ":" << user.get("license_server_port"); diff --git a/qtlicensetool/CMakeLists.txt b/qtlicensetool/CMakeLists.txt index 8e9fefd..c2f5c21 100644 --- a/qtlicensetool/CMakeLists.txt +++ b/qtlicensetool/CMakeLists.txt @@ -24,7 +24,7 @@ include_directories ( add_executable(qtlicensetool qtlicensetool.cpp - ../tcpclient.cpp - ../utils.cpp - ../licdsetup.cpp + ../src/tcpclient.cpp + ../src/utils.cpp + ../src/licdsetup.cpp ) diff --git a/qtlicensetool/qtlicensetool.cpp b/qtlicensetool/qtlicensetool.cpp index 66f07bd..75f0343 100644 --- a/qtlicensetool/qtlicensetool.cpp +++ b/qtlicensetool/qtlicensetool.cpp @@ -49,6 +49,10 @@ void showVersion() int askStatus(const std::string &statusRequest, LicdSetup *setup) { + std::string req = statusRequest + " -a "; + req += QTLICENSETOOL_APP_NAME; + req += " -v "; + req += QTLICENSETOOL_VERSION; std::string daemonAddr = setup->get("licd_addr"); int daemonPort = utils::strToInt(setup->get("licd_port")); if (daemonPort < 0) { @@ -58,7 +62,7 @@ int askStatus(const std::string &statusRequest, LicdSetup *setup) // Connect and send/receive the request TcpClient tcp(daemonAddr, daemonPort); std::string reply; - int result = tcp.sendAndReceive(statusRequest, reply); + int result = tcp.sendAndReceive(req, reply); if (result != e_tcp_success) { std::cout << tcp.errorString(result) << std::endl; return e_tcp_error_conn; @@ -81,7 +85,7 @@ void overrideSetup(LicdSetup *setup, int argc, char *argv[]) } if (longterm) { // Longterm operation (add|remove) has to come right after the -L switch, not later - if (sw == LONGTERM_ADD_OP || sw == LONGTERM_REMOVE_OP) { + if (sw == OP_ADD_RESERVATION || sw == OP_REMOVE_RESERVATION) { setup->set("operation", sw); longterm = false; continue; @@ -133,8 +137,8 @@ void overrideSetup(LicdSetup *setup, int argc, char *argv[]) int doLongtermRequest(LicdSetup *setup) { - std::string operation = setup->get("operation"); // utils::trimStr(argv[2]); - if (operation != LONGTERM_ADD_OP && operation != LONGTERM_REMOVE_OP) { + std::string operation = setup->get("operation"); + if (operation != OP_ADD_RESERVATION && operation != OP_REMOVE_RESERVATION) { std::string error = "Invalid operation \"" + operation; error += "\""; errorAndExit(error); @@ -148,7 +152,10 @@ int doLongtermRequest(LicdSetup *setup) std::stringstream request; request << LONGTERM_REQUEST_CMD << " " << setup->get("operation") << " -u " << setup->get("user_id") - << " -i " << setup->get("license_id"); + << " -i " << setup->get("license_id") + << " -e " << setup->get("user_email") + << " -a " << QTLICENSETOOL_APP_NAME + << " -v " << QTLICENSETOOL_VERSION; if (!setup->get("license_server_addr").empty()) { // Add optional license server URL if specified request << " -l " << setup->get("license_server_addr") @@ -190,6 +197,7 @@ void helpAndExit() << " -v or --version : Prints out qtlicensetool version\n" << " -S or --serverversion : Prints out License Server version\n" << " -D or --daemonversion : Prints out the License Service (daemon) version\n" + << " -r or --reservation : Prints out the list of active reservations\n" << " -L or --longterm : Longterm reservation (add or remove)\n" << " For longterm usage ('-L' or '--longterm'), you must tell which operation to do:\n" << " add|remove <--- Either one needs to be there\n" diff --git a/src/clienthandler.cpp b/src/clienthandler.cpp new file mode 100644 index 0000000..789176b --- /dev/null +++ b/src/clienthandler.cpp @@ -0,0 +1,120 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ + +#include "clienthandler.h" + +bool ClientHandler::checkLicenseExpiryTime(std::string &reply) +{ + std::cout << "Offline - checking validity from license file " << m_request.licenseFile << std::endl; + // Open the license file + std::string data; + if (utils::readFile(data, m_request.licenseFile) != 0) { + std::cout << "No license file - rejecting license " << m_request.licenseFile << std::endl; + reply = replyString[e_bad_connection]; + return false; + } + JsonHandler license(data); + // Check license status + if (license.get("status") != "true") { + std::cout << "License status = false: Rejecting license\n"; + reply = replyString[e_bad_connection]; + return false; + } + // Expiry date. Add 1 day to expiry date to get the end actually at the beginning of the next day + std::string expDate = license.get("expiry_date"); + std::time_t expEpoch = utils::stringToEpoch(expDate.c_str()) + SECS_IN_DAY; + // Current date + std::time_t current = std::time(0); + // See if the time has expire + if (current > expEpoch) { + // Store the leeway time if applicable + if(license.get("leeway_hours") != "") { + m_license.leeway_hours = license.getInt("leeway_hours"); + m_license.current_timestamp = current; + m_license.expiry_epoch = expEpoch; + } + return false; + } + std::cout << "License granted, expires at " << expDate << std::endl; + reply = replyString[e_license_granted]; + return true; +} + +int ClientHandler::parseRequest() { + // First find out the command (and operation for longterm case) + std::string cmd = utils::trimStr(params[0]); + m_request.type = RequestType::no_request; + if (cmd == LICENSE_REQUEST_CMD) { + m_request.type = RequestType::license_request; + } else if (cmd == LONGTERM_REQUEST_CMD) { + m_request.type = RequestType::long_term_request; + // find either 'add' or 'remove' + std::string op = utils::trimStr(cmd.substr(8, cmd.length() -1)); + if (op != OP_ADD_RESERVATION && op != OP_REMOVE_RESERVATION) { + std::cout << "Invalid longterm operation: " << op; + return e_bad_request; + } + m_request.operation = op; + } else if (cmd == SERVER_VERSION_CMD) { + m_request.type = RequestType::server_version; + buildRequestJson(); + return 0; + } else if (cmd == DAEMON_VERSION_CMD) { + m_request.type = RequestType::daemon_version; + } else if (cmd == RESERVATION_QUERY_CMD) { + m_request.type = RequestType::reservation_query; + } else { + std::cout << "Invalid command: " << cmd << std::endl; + return e_bad_request; + } + // Then cycle through parameters. Start from [1] because [0] is the command + for (int i = 1; i < params.size(); i++) { + std::string item = params[i]; + char arg = (char)item[0]; + std::string val = utils::trimStr(item.substr(1, item.length() - 1)); + if (val.length() == 0) { + std::cout << "No value for argument: -" << arg << std::endl; + return e_bad_request; + } + if (arg == 'a') m_request.appName = utils::strToLower(val); + else if (arg == 'v') m_request.appVersion = val; + else if (arg == 'u') m_request.userId = val; + else if (arg == 'i') m_request.licenseId = val; + else if (arg == 'p') m_request.parentReservationId = val; + else if (arg == 'r') m_request.runnerType = val; + else if (arg == 'e') m_request.email = val; + else if (arg == 'l') m_request.serverAddr = val; + else { + std::cout << "Invalid argument: -" << arg << std::endl; + return e_bad_request; + } + } + std::stringstream ss; + ss << WORKING_DIR << DIR_SEPARATOR << LICENSE_FILE_PREFIX; + ss << m_request.userId << "_" << m_request.licenseId << LICENSE_FILE_EXTENSION; + m_request.licenseFile += ss.str(); + buildRequestJson(); + return 0; +} + +bool ClientHandler::checkLeewayTime(std::string &reply) +{ + // Leeway time granted? + if (m_license.leeway_hours == 0) { + return false; + } + // License expired and offline: Allow some leeway time + int leewayTimeLeft = m_license.expiry_epoch + (m_license.leeway_hours * SECS_IN_HOUR) - m_license.current_timestamp; + if (leewayTimeLeft > 0) { + std::stringstream ss; + ss << replyString[e_no_conn_leeway] + << std::fixed << std::setprecision(1) + << (float)leewayTimeLeft / SECS_IN_DAY << " days"; + reply = ss.str(); + return true; + } + return false; +} + diff --git a/src/daemon_clients/clienthandler.h b/src/daemon_clients/clienthandler.h new file mode 100644 index 0000000..1181294 --- /dev/null +++ b/src/daemon_clients/clienthandler.h @@ -0,0 +1,64 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ + +#pragma once + +#include <iostream> +#include <string> +#include "utils.h" +#include "commonsetup.h" +#include "licdsetup.h" +#include "jsonhandler.h" + +class ClientHandler +{ + public: + ClientHandler(const RequestInfo &request, const LicdSetup &settings) : + m_request(request), + m_settings(settings) + { } + virtual ~ClientHandler() + { } + // If this client is using a floating model, it needs to be cached + // (Squish, Coco - Socket is alive until client stops working): Set this to true + + std::vector<std::string> params; + + virtual bool isCachedReservationValid(std::string &reply) = 0; + virtual bool isLicenseRequestDue() = 0; + virtual int parseAndSaveResponse(std::string &response) = 0; + virtual void buildRequestJson() = 0; + + bool hasFloatingLicense() { return m_floatingLicense; } + void updateLicense(const std::string &responseJson); + int parseRequest(); + RequestInfo getRequest() {return m_request;} + int getSocketId() { return m_request.socketId; } + RequestType getRequestType() { return m_request.type; } + int getClientType() { return (int)m_request.client; } + virtual void release() { return; } + + protected: + License m_license; + RequestInfo m_request; + LicdSetup m_settings;; + uint64_t m_updateInterval; + bool m_floatingLicense = false; + + bool checkLicenseExpiryTime(std::string &reply); + bool checkLeewayTime(std::string &reply); +}; + +static std::map<RequestReply, std::string> replyString { + {e_bad_request, "ERROR Bad request"}, + {e_license_granted, "License acquired."}, + {e_license_rejected, "No valid license acquired"}, + {e_no_conn_leeway, "License granted with warning: No server connection. Leeway time left: "}, + {e_license_pool_full, "All licenses in use. No more license available on the server."}, + {e_bad_connection, "No connection to server. Try again later."} + }; + + + diff --git a/src/daemon_clients/clitoolhandler.h b/src/daemon_clients/clitoolhandler.h new file mode 100644 index 0000000..a63dd40 --- /dev/null +++ b/src/daemon_clients/clitoolhandler.h @@ -0,0 +1,74 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ +#pragma once + +#include "clienthandler.h" + +class CliToolHandler : public virtual ClientHandler { + + public: + CliToolHandler(const RequestInfo &request, const LicdSetup &settings) + : ClientHandler(request, settings) {} + + bool isLicenseRequestDue() override { return true; } + bool isCachedReservationValid(std::string &reply) override { return false; } + + private: + + void buildRequestJson() override + { + if (m_request.type == RequestType::server_version) { + m_request.payload = ""; + return; + } + std::stringstream pay; + pay << "{"; + pay<< "\"license_number\":" << "\"" << m_request.licenseId << "\","; + pay << "\"user_id\":" << "\"" << m_request.userId << "\","; + pay << "\"operation\":" << "\"" << m_request.operation << "\""; + pay << "}"; + m_request.payload = pay.str(); + } + + int parseAndSaveResponse(std::string &response) override + { + JsonHandler json(response); + std::stringstream ss; + std::cout << json.dump(4); + + if (m_request.type == RequestType::long_term_request) { + // long-term request: Have "message" field from response JSON directly as a reply to the user + response = json.get("message"); + if (json.get("status") != "true" ) { + if (json.get("message") == "License fully reserved") { + response = replyString[e_license_pool_full]; + } else { + response = replyString[e_license_rejected]; + } + // Do not touch the license file, just return + return 1; + } + } else if (m_request.type == RequestType::server_version) { + std::cout << "Parsing server version response\n"; + response = "Qt License Server v"; + response += json.get("version"); + return 0; // Do not touch cached license files pls. + } + ss << replyString[e_license_granted]; + ss << " expiry_date=" << json.get("expiry_date"); + ss << " license_id=" << json.get("license_number"); + ss << " reservation_id=" << json.get("reservation_id"); + response = ss.str(); + + // Add timestamp into license JSON + json.add("last_timestamp", utils::getTimestampNow()); + + int result = utils::writeToFile(m_request.licenseFile, json.dump(4)); + if (result != 0) { + std::cout << "ERROR saving license file: '" << m_request.licenseFile << "': " << strerror(result) << std::endl; + } + return 0; + } +}; diff --git a/src/daemon_clients/cocohandler.h b/src/daemon_clients/cocohandler.h new file mode 100644 index 0000000..d25b36c --- /dev/null +++ b/src/daemon_clients/cocohandler.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ +#pragma once + +#include "clienthandler.h" + +class CocoHandler : public ClientHandler { + + public: + CocoHandler(const RequestInfo &request, const LicdSetup &settings) + : ClientHandler(request, settings) + { + m_updateInterval = utils::strToInt(m_settings.get("coco_report_interval")); + } + + bool isLicenseRequestDue() override { return true; } + bool isCachedReservationValid(std::string &reply) override {return true;} + int parseAndSaveResponse(std::string &response) override { return 0; } + void buildRequestJson() override {return;} + void release() override + { + // TODO + return; + }; + +};
\ No newline at end of file diff --git a/src/daemon_clients/mochandler.h b/src/daemon_clients/mochandler.h new file mode 100644 index 0000000..d0dc579 --- /dev/null +++ b/src/daemon_clients/mochandler.h @@ -0,0 +1,105 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ +#pragma once + +#include "clienthandler.h" + +class MocHandler : virtual public ClientHandler { + public: + MocHandler(const RequestInfo &request, const LicdSetup &settings) + : ClientHandler(request, settings) + { + m_updateInterval = utils::strToInt(m_settings.get("moc_renewal_interval")) * SECS_IN_HOUR; + } + + bool isCachedReservationValid(std::string &reply) override + { + if (!checkLicenseExpiryTime(reply)) { + if (checkLeewayTime(reply)) { + return true; + } + } else { + reply = replyString[e_license_granted]; + return true; + } + return false; + } + + void buildRequestJson() override + { + std::stringstream pay; + pay << "{"; + pay<< "\"license_number\":" << "\"" << m_request.licenseId << "\","; + pay << "\"user_id\":" << "\"" << m_request.userId << "\","; + pay << "\"hw_id\":" << "\"" << m_settings.get("hw_id") << "\","; + pay << "\"src\":" << "\"" << m_request.appName << "\","; + pay << "\"host_os\":" << "\"" << m_settings.get("host_os") << "\","; + pay << "\"src_version\":" << "\"" << m_request.appVersion << "\","; + pay << "\"email\":" << "\"" << m_request.email << "\""; + pay << "}"; + m_request.payload = pay.str(); + } + + bool isLicenseRequestDue() override + { + std::cout << "MOC calling, checking if renewal is needed\n"; + // Open the license file + std::string data; + if (utils::readFile(data, m_request.licenseFile) != 0) { + // If there is no license file yet, have to request anyway + std::cout << "No license present (" << m_request.licenseFile << ") - requesting license\n"; + return true; + } + JsonHandler license(data); + if (license.get("status") != "true") { + // Cached license not valid + return true; + } + uint64_t timeNow = utils::getTimestampNow(); + if (timeNow > utils::strToInt(license.get("last_timestamp")) + m_updateInterval) { + // Renewal is due + std::cout << "Requesting license renewal\n"; + return true; + } + std::cout << "Renewal time not due, granting license\n"; + + return false; + } + + int parseAndSaveResponse(std::string &response) override + { + JsonHandler json(response); + // response JSON is converted to a reply string to the client from now on + std::stringstream ss; + + if (json.get("status") != "true") { + if (json.get("message") == "License fully reserved") { + response = replyString[e_license_pool_full]; + } else { + response = replyString[e_license_rejected]; + } + // Remove the license file and return + utils::deleteFile(m_request.licenseFile); + return 1; + } + + ss << replyString[e_license_granted]; + ss << " expiry_date=" << json.get("expiry_date"); + ss << " license_id=" << json.get("license_number"); + ss << " reservation_id=" << json.get("reservation_id"); + response = ss.str(); + + // Add timestamp into license JSON + json.add("last_timestamp", utils::getTimestampNow()); + + // Save the license JSON + int result = utils::writeToFile(m_request.licenseFile, json.dump(4)); + if (result != 0) { + std::cout << "ERROR saving license file: '" << m_request.licenseFile << "': " << strerror(result) << std::endl; + } + return 0; + } + +};
\ No newline at end of file diff --git a/src/daemon_clients/pluginhandler.h b/src/daemon_clients/pluginhandler.h new file mode 100644 index 0000000..132f128 --- /dev/null +++ b/src/daemon_clients/pluginhandler.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ +#pragma once + +#include "clienthandler.h" + +class PluginHandler : public ClientHandler { + + public: + PluginHandler(const RequestInfo &request, const LicdSetup &settings) + : ClientHandler(request, settings) {} + + bool isLicenseRequestDue() override { return true; } // Pluging does timed requests, so every request counts + + void buildRequestJson() override + { + std::stringstream pay; + pay << "{"; + pay<< "\"license_number\":" << "\"" << m_request.licenseId << "\","; + pay << "\"user_id\":" << "\"" << m_request.userId << "\","; + pay << "\"hw_id\":" << "\"" << m_settings.get("hw_id") << "\","; + pay << "\"src\":" << "\"" << m_request.appName << "\","; + pay << "\"host_os\":" << "\"" << m_settings.get("host_os") << "\","; + pay << "\"src_version\":" << "\"" << m_request.appVersion << "\","; + pay << "\"email\":" << "\"" << m_request.email << "\""; + //pay << "\"operation\":" << "\"" << m_request.operation << "\""; + pay << "}"; + m_request.payload = pay.str(); + } + + bool isCachedReservationValid(std::string &reply) override + { + if (!checkLicenseExpiryTime(reply)) { + if (checkLeewayTime(reply)) { + return true; + } + } else { + reply = replyString[e_license_granted]; + return true; + } + return false; + } + + int parseAndSaveResponse(std::string &response) override + { + JsonHandler json(response); + // response JSON is converted to a reply string to the client from now on + std::stringstream ss; + + if (json.get("status") != "true") { + if (json.get("message") == "License fully reserved") { + response = replyString[e_license_pool_full]; + } else { + response = replyString[e_license_rejected]; + } + // Remove the license file and return + utils::deleteFile(m_request.licenseFile); + return 1; + } + + ss << replyString[e_license_granted]; + ss << " expiry_date=" << json.get("expiry_date"); + ss << " license_id=" << json.get("license_number"); + ss << " reservation_id=" << json.get("reservation_id"); + response = ss.str(); + + // Save the license JSON + int result = utils::writeToFile(m_request.licenseFile, json.dump(4)); + if (result != 0) { + std::cout << "ERROR saving license file: '" << m_request.licenseFile << "': " << strerror(result) << std::endl; + } + return 0; + } + +};
\ No newline at end of file diff --git a/src/daemon_clients/squishhandler.h b/src/daemon_clients/squishhandler.h new file mode 100644 index 0000000..366c624 --- /dev/null +++ b/src/daemon_clients/squishhandler.h @@ -0,0 +1,90 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ +#pragma once + +#include "clienthandler.h" + +class SquishHandler : public ClientHandler { + + public: + SquishHandler(const RequestInfo &request, const LicdSetup &settings) + : ClientHandler(request, settings) + { + m_updateInterval = utils::strToInt(m_settings.get("squish_report_interval")); + m_request.operation = OP_ADD_RESERVATION; + m_request.startTimestamp = utils::getTimestampNow(); + } + + bool isLicenseRequestDue() override { return true; } + bool isCachedReservationValid(std::string &reply) override { return true; } + + void buildRequestJson() override + { + std::stringstream pay; + pay << "{"; + pay<< "\"license_number\":" << "\"" << m_request.licenseId << "\","; + pay << "\"user_id\":" << "\"" << m_request.userId << "\","; + pay << "\"hw_id\":" << "\"" << m_settings.get("hw_id") << "\","; + pay << "\"operation\":" << "\"" << m_request.operation << "\","; + pay << "\"src\":" << "\"" << m_request.appName << "\","; + pay << "\"host_os\":" << "\"" << m_settings.get("host_os") << "\","; + pay << "\"src_version\":" << "\"" << m_request.appVersion << "\","; + pay << "\"email\":" << "\"" << m_request.email << "\""; + pay << "}"; + m_request.payload = pay.str(); + m_floatingLicense = true; + } + + int parseAndSaveResponse(std::string &response) override + { + JsonHandler json(response); + // response JSON is converted to a reply string to the client from now on + std::stringstream ss; + m_license.status = (json.get("status") == "true") ? true : false; + + + if (m_license.status) { + ss << replyString[e_license_granted]; + ss << " expiry_date=" << json.get("expiry_date"); + ss << " license_id=" << json.get("license_number"); + ss << " reservation_id=" << json.get("reservation_id"); + response = ss.str(); + } else { + if (json.get("message") == "License fully reserved") { + response = replyString[e_license_pool_full]; + } else { + response = replyString[e_license_rejected]; + } + // Do not save the response json, just return + return 1; + } + m_license.message = json.get("message"); + m_license.license_id = json.get("license_number"); + m_license.user_id = json.get("user_id"); + m_license.reservation_id = json.get("reservation_id"); + m_license.license_key = json.get("license_key"); + m_license.parent_reservation_id = json.get("parent_reservvation_id"); + m_license.operation = "add"; + // Add timestamp into license JSON + struct timeval tp; + gettimeofday(&tp, NULL); + uint64_t timeNow = tp.tv_sec; + + // Save the JSON, adding timestamp + json.add("last_timestamp", timeNow); + int result = utils::writeToFile(m_request.licenseFile, json.dump(4)); + if (result != 0) { + std::cout << "ERROR saving license file: '" << m_request.licenseFile << "': " << strerror(result) << std::endl; + } + return 0; + } + + void release() override + { + m_request.operation = OP_REMOVE_RESERVATION; + m_request.stopTimestamp = utils::getTimestampNow(); + buildRequestJson(); + } +};
\ No newline at end of file diff --git a/src/daemon_clients/squishidehandler.h b/src/daemon_clients/squishidehandler.h new file mode 100644 index 0000000..a17b2db --- /dev/null +++ b/src/daemon_clients/squishidehandler.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ +#pragma once + +#include "clienthandler.h" + +class SquishIdeHandler : public ClientHandler { + + public: + SquishIdeHandler(const RequestInfo &request, const LicdSetup &settings) + : ClientHandler(request, settings) + { + m_updateInterval = utils::strToInt(m_settings.get("squish_report_interval")); + m_request.operation = OP_ADD_RESERVATION; + } + + bool isLicenseRequestDue() override { return true; } + bool isCachedReservationValid(std::string &reply) override {return true;} + void buildRequestJson() override {return;} + int parseAndSaveResponse(std::string &response) override { return 0; } + void release() override + { + // TODO + return; + }; +};
\ No newline at end of file diff --git a/dummy_licheck.cpp b/src/dummy_licheck.cpp index e38c627..e38c627 100644 --- a/dummy_licheck.cpp +++ b/src/dummy_licheck.cpp diff --git a/httpclient.cpp b/src/httpclient.cpp index 33329c6..730b310 100644 --- a/httpclient.cpp +++ b/src/httpclient.cpp @@ -51,7 +51,6 @@ int HttpClient::sendRequest(std::string &reply, const std::string &payload, request.authKey = authKey; request.payload = payload; - if (!request.authKey.empty()) { // Set authorization only if applicable std::string auth = "Authorization: " + request.authKey; diff --git a/jsonhandler.cpp b/src/jsonhandler.cpp index f46fd72..f46fd72 100644 --- a/jsonhandler.cpp +++ b/src/jsonhandler.cpp diff --git a/licdsetup.cpp b/src/licdsetup.cpp index 2594485..2594485 100644 --- a/licdsetup.cpp +++ b/src/licdsetup.cpp diff --git a/src/licenser.cpp b/src/licenser.cpp new file mode 100644 index 0000000..66c4c2d --- /dev/null +++ b/src/licenser.cpp @@ -0,0 +1,280 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ + +#include "licenser.h" + +Licenser::Licenser(uint16_t tcpPort) +{ + // Init daemon settings + settings = new LicdSetup(e_set_type_daemon); + + // Check if our setup already has some hw id - generate it if not + if (settings->get("hw_id").empty()) { + setHwId(); + } + + if (tcpPort == 0) { + tcpPort = utils::strToInt(settings->get("tcp_listening_port")); + } + m_mocInterval = utils::strToInt(settings->get("moc_renewal_interval")) * 3600; // 3600 = secs in hour + // Start the HTTP client + m_http = new HttpClient(settings->get("server_addr"), + settings->get("reservation_access_point"), + settings->get("long-term_access_point"), + settings->get("version_query_access_point")); + // Start the TCP/IP server + m_server = new TcpServer(tcpPort); +} + +Licenser::~Licenser() +{ + delete(m_currentClient); + delete(settings); + delete(m_server); + delete(m_http); + std::cout << "Daemon stopped." << std::endl; +} + + +int Licenser::listen() +{ + m_infoString = ""; + int socket = 0; // Placeholder for whatever socket gets active + std::string input = m_server->listenToClients(socket); + input = utils::trimStr(input); + if (input.empty()) { + return 0; //continue; + } + if (input == CLIENT_CLOSED_MSG) { + std::cout << "Client disconnected, socket id " << socket << std::endl; + // Now to check if the client had a floating license: + clientDisconnected(socket); + return 0; + } + std::cout << "Got an request: " << input << std::endl; + + std::string reply = ""; // Holds server response first (if got any). After parsing, holds reply to the client + + int clientType = parseInputAndCreateCLient(socket, input); + std::cout << "Client type: " << clientType << std::endl; + if (clientType != (int)ClientType::client_undefined) + { + if (m_currentClient->parseRequest() != e_bad_request) { + RequestType reqType = m_currentClient->getRequestType(); + std::string payload = ""; + if (reqType == RequestType::reservation_query) { + reply = checkReservations(); + } else if (reqType == RequestType::daemon_version) { + reply = getDaemonVersion(); + } else { + std::cout << "Initiating request to server\n"; + if (m_currentClient->isLicenseRequestDue()) { + if (sendServerRequest(reply) == e_bad_connection) { + // Bad server connection: Check the existing license file for expiry date + if (!m_currentClient->isCachedReservationValid(reply)) { + reply = replyString[e_bad_connection]; + } + } else { + m_currentClient->parseAndSaveResponse(reply); + } + } else { + // No need to contact server + reply = replyString[e_license_granted]; + } + } + } else { + reply = replyString[e_bad_request]; + } + } else { + reply = replyString[e_bad_request]; + } + + if (m_currentClient->hasFloatingLicense()) { + // QA tool, Coco or Squish (cLient with floating license): Store it in cache + m_floatingClients.push_back(m_currentClient); + } + + reply += m_infoString; + reply +="\n"; + m_server->sendResponse(socket, reply); + std::cout << "Replied to socket " << socket << ": " << reply << std::endl; + + if (m_floatingClients.size() > 0) { + checkReportsDue(); + } + return 0; +} + +int Licenser::checkReportsDue() { + // Check if there's any floating clients which has periodic reports due + // TODO!! + ClientHandler *client; + for (int i = 0 ; i < m_floatingClients.size(); i++) { + client = m_floatingClients[i]; + if (client->isLicenseRequestDue()) { + // TODO Ping server (Req Json might need updating here) + } + } + return 0; +} + + +int Licenser::parseInputAndCreateCLient(uint16_t socketId, const std::string &incoming) +{ + // This parses client type from the input string and decides which type of client + // to initiate into m_currentClient member + RequestInfo request; + request.socketId = socketId; + // Find out which class of client is calling + ClientType retVal = ClientType::client_undefined; + std::vector<std::string> paramList = utils::splitStr(incoming, '-'); + std::string client; + for ( int i = 0; i < paramList.size() ;i++ ) { + std::string item = paramList.at(i); + if (item[0] == 'a') { + client = utils::trimStr(item.substr(1, item.length() -1)); + break; + } + } + if (client.empty()) { + // Make this thing backwards compatible with old qtlicensetool + // which does not send -a switch (app name) + std::cout << "Warning: '-a' switch not found: Client undefined, assuming it's a CLI Tool" << std::endl; + client = QTLICENSETOOL_APP_NAME; + } + if (client == MOCWRAPPER_APP_NAME) { + std::cout << "MOC calling" << std::endl; + request.client = ClientType::client_moc; + request.updateIntervalSecs = utils::strToInt(settings->get("moc_renewal_interval")); + m_currentClient = new MocHandler(request, *settings); + } else if (client == CREATOR_APPNAME || client == DESIGN_STUDIO_APP_NAME) { + // No need to have them separately - it's a Plugin making the calls for both + request.client = ClientType::client_plugin; + m_currentClient = new PluginHandler(request, *settings); + } else if (client == QTLICENSETOOL_APP_NAME) { + request.client = ClientType::client_CLI; + m_currentClient = new CliToolHandler(request, *settings); + } else if (client == SQUISH_IDE_APP_NAME) { + request.client = ClientType::client_squish_ide; + m_currentClient = new SquishIdeHandler(request, *settings); + } else if (client == SQUISH_APP_NAME) { + request.updateIntervalSecs = + utils::strToInt(settings->get("squish_report_interval")); + request.client = ClientType::client_squish; + m_currentClient = new SquishHandler(request, *settings); + } else if (client == COCO_APP_NAME) { + request.updateIntervalSecs = + utils::strToInt(settings->get("coco_report_interval")); + request.client = ClientType::client_coco; + m_currentClient = new CocoHandler(request, *settings); + } + else { + std::cout << "Warning: Client undefined!" << std::endl; + return (int)ClientType::client_undefined; + } + m_currentClient->params = paramList; + return m_currentClient->getClientType(); +} + +int Licenser::sendServerRequest(std::string &reply) +{ + std::string authKey; + RequestInfo request = m_currentClient->getRequest(); + if (request.client != ClientType::client_CLI) { + // Generate auth hash for HTTP headers (CLI tool does not need it) + doHmacHashSha256(request.payload, settings->get("server_secret"), authKey); // 19755982232ff7b6f6d0f3c57ffc1c0e4f03060e7175d478f7b146fb1e000507"; + } + // Send the request + if (m_http->sendRequest(reply, request.payload, request.serverAddr, authKey) != 0) { + return e_bad_connection; + } + return e_got_response; +} + +void Licenser::doHmacHashSha256(const std::string &payload, const std::string &secret, std::string &authKey) +{ + std::stringstream ss_result; + ss_result << "apikey "; + + // Allocate memory for the HMAC + std::vector<uint8_t> out(SHA256_HASH_SIZE); + + // Call hmac-sha256 function + hmac_sha256(secret.data(), secret.size(), payload.data(), payload.size(), + out.data(), out.size()); + + // Convert to string with std::hex + for (uint8_t x : out) { + ss_result << std::hex << std::setfill('0') << std::setw(2) << (int)x; + } + + authKey = ss_result.str(); + JsonHandler pay(payload); + + // Print out the result + std::cout << "Message: " << pay.dump(4) << std::endl; + std::cout << "Key: " << secret << std::endl; + std::cout << "HMAC: " << authKey << std::endl; +} + +std::string Licenser::checkReservations() +{ + // Open all license files + std::string dir = WORKING_DIR; + std::string filter = LICENSE_FILE_PREFIX; + std::vector<std::string> files = utils::getDirListing(dir, filter); + + std::stringstream reply; + reply << "Current reservations: \n"; + for (auto file : files) { + file = DIR_SEPARATOR + file; + file = WORKING_DIR + file; + std::cout << "File list: " << file << std::endl; + std::string data; + if (utils::readFile(data, file) != 0) { + std::cout << "Couldn't read license file: " << file << std::endl; + continue; + } + JsonHandler json(data); + reply << "\n--- License ID " << json.get("license_number") << " ---\n"; + reply << " User ID: " << json.get("user_id") << std::endl; + reply << " Expiry date: " << json.get("expiry_date") << std::endl; + reply << " Reservation ID: " << json.get("reservation_id") << std::endl; + } + return reply.str(); +} + +void Licenser::clientDisconnected(int socketId) { + int index = -1; + ClientHandler *client; + for (int i = 0; i < m_floatingClients.size(); i++) { + client = m_floatingClients[i]; + if (client->getSocketId() == socketId) { + index = i; + m_currentClient = client; + break; + } + } + if (index != -1 && m_currentClient->hasFloatingLicense()) { + m_currentClient->release(); // not implemented yet + // m_floatingClients.erase(index); // TODO don't erase yet, server might be down + } + +} + + +std::string Licenser::getDaemonVersion() +{ + std::string version = "Qt License Daemon (qtlicd) v"; + version += DAEMON_VERSION; + return version; +} + +void Licenser::setHwId() { + + // TODO calculate hwid hash out of MAC, uname etc... + // Edit the qtlicd.ini to make it permanent + return; +}
\ No newline at end of file diff --git a/tcpclient.cpp b/src/tcpclient.cpp index 1922b76..1922b76 100644 --- a/tcpclient.cpp +++ b/src/tcpclient.cpp diff --git a/tcpserver.cpp b/src/tcpserver.cpp index 5ee4e89..b1205ba 100644 --- a/tcpserver.cpp +++ b/src/tcpserver.cpp @@ -8,7 +8,6 @@ TcpServer::TcpServer(uint16_t serverPort) { int opt = TRUE; - // Initialize all m_clientSocket[] to 0 so not checked for (uint8_t i = 0; i < MAX_CLIENTS; i++) { m_clientSocket[i] = 0; @@ -29,9 +28,7 @@ TcpServer::TcpServer(uint16_t serverPort) perror("socket failed"); exit(EXIT_FAILURE); } - // Set master socket to allow multiple connections - if (setsockopt(m_masterSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0) { perror("setsockopt failed"); @@ -50,7 +47,7 @@ TcpServer::TcpServer(uint16_t serverPort) m_address.sin_addr.s_addr = INADDR_ANY; m_address.sin_port = htons(serverPort); - // Bind the socket to localhost port 8888 + // Bind the socket to localhost if (bind(m_masterSocket, (struct sockaddr *)&m_address, sizeof(m_address)) < 0) { doCloseSocket(m_masterSocket); perror("bind failed"); @@ -149,11 +146,10 @@ std::string TcpServer::listenToClients(int &socket) // Somebody disconnected, get his details and print getpeername(sd, (struct sockaddr *)&m_address, (socklen_t *)&m_addrlen); - std::cout << "Client disconnected, socket id " << sd << std::endl; - // Close the socket and mark as 0 in list for reuse doCloseSocket(sd); m_clientSocket[i] = 0; + data = "closed"; } else { // Set the string terminating NULL byte // on the end of the data read @@ -176,7 +172,7 @@ int TcpServer::sendResponse(int socketIndex, const std::string &message) printf("[TcpServer] Send failed\n"); return 1; } - doCloseSocket(sd); - m_clientSocket[socketIndex] = 0; + // doCloseSocket(sd); + // m_clientSocket[socketIndex] = 0; return 0; } diff --git a/utils.cpp b/src/utils.cpp index 95c5b66..cb68829 100644 --- a/utils.cpp +++ b/src/utils.cpp @@ -4,7 +4,6 @@ */ #include "utils.h" -#include "commonsetup.h" namespace utils { @@ -236,8 +235,16 @@ std::vector<std::string> getDirListing(const std::string &directory, const std:: return files; } -#include <time.h> - +int deleteFile(const std::string &filepath) +{ + std::remove(filepath.c_str()); // delete file + bool failed = !std::ifstream("file1.txt"); + if(failed) { + std::perror("Error deleting file"); + return 1; + } + return 0; +} std::string getOsName() { @@ -288,6 +295,14 @@ time_t stringToEpoch(const char* theTime, const char* format) return mktime(&tmTime); } +uint64_t getTimestampNow() +{ + // Get current time + struct timeval tp; + gettimeofday(&tp, NULL); + return (uint64_t)tp.tv_sec; +} + /* * App-specific utils here (not generic) */ |