From ef4ba13bff5d2d0499e8af9f405ecf506aaa85d6 Mon Sep 17 00:00:00 2001 From: Sami Littow Date: Mon, 19 Dec 2022 10:31:49 +0200 Subject: Refactoring (prepare for QA tools support) --- CHANGELOG | 6 + CMakeLists.txt | 18 +- dummy_licheck.cpp | 10 - httpclient.cpp | 130 ----------- include/commonsetup.h | 92 +++++++- include/jsonhandler.h | 1 - include/licdsetup.h | 7 +- include/licenser.h | 71 ++---- include/tcpserver.h | 2 + include/utils.h | 8 +- jsonhandler.cpp | 140 ----------- licd.ini | 19 +- licdsetup.cpp | 305 ------------------------ licenser.cpp | 428 ---------------------------------- linux_daemon/linuxdaemon.cpp | 12 +- mocwrapper/CMakeLists.txt | 6 +- mocwrapper/mocwrapper.cpp | 2 +- qtlicensetool/CMakeLists.txt | 6 +- qtlicensetool/qtlicensetool.cpp | 18 +- src/clienthandler.cpp | 120 ++++++++++ src/daemon_clients/clienthandler.h | 64 +++++ src/daemon_clients/clitoolhandler.h | 74 ++++++ src/daemon_clients/cocohandler.h | 28 +++ src/daemon_clients/mochandler.h | 105 +++++++++ src/daemon_clients/pluginhandler.h | 77 ++++++ src/daemon_clients/squishhandler.h | 90 +++++++ src/daemon_clients/squishidehandler.h | 28 +++ src/dummy_licheck.cpp | 10 + src/httpclient.cpp | 129 ++++++++++ src/jsonhandler.cpp | 140 +++++++++++ src/licdsetup.cpp | 305 ++++++++++++++++++++++++ src/licenser.cpp | 280 ++++++++++++++++++++++ src/tcpclient.cpp | 76 ++++++ src/tcpserver.cpp | 178 ++++++++++++++ src/utils.cpp | 311 ++++++++++++++++++++++++ tcpclient.cpp | 76 ------ tcpserver.cpp | 182 --------------- utils.cpp | 296 ----------------------- 38 files changed, 2184 insertions(+), 1666 deletions(-) delete mode 100644 dummy_licheck.cpp delete mode 100644 httpclient.cpp delete mode 100644 jsonhandler.cpp delete mode 100644 licdsetup.cpp delete mode 100644 licenser.cpp create mode 100644 src/clienthandler.cpp create mode 100644 src/daemon_clients/clienthandler.h create mode 100644 src/daemon_clients/clitoolhandler.h create mode 100644 src/daemon_clients/cocohandler.h create mode 100644 src/daemon_clients/mochandler.h create mode 100644 src/daemon_clients/pluginhandler.h create mode 100644 src/daemon_clients/squishhandler.h create mode 100644 src/daemon_clients/squishidehandler.h create mode 100644 src/dummy_licheck.cpp create mode 100644 src/httpclient.cpp create mode 100644 src/jsonhandler.cpp create mode 100644 src/licdsetup.cpp create mode 100644 src/licenser.cpp create mode 100644 src/tcpclient.cpp create mode 100644 src/tcpserver.cpp create mode 100644 src/utils.cpp delete mode 100644 tcpclient.cpp delete mode 100644 tcpserver.cpp delete mode 100644 utils.cpp diff --git a/CHANGELOG b/CHANGELOG index 4fec1af..3a43a14 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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/dummy_licheck.cpp b/dummy_licheck.cpp deleted file mode 100644 index e38c627..0000000 --- a/dummy_licheck.cpp +++ /dev/null @@ -1,10 +0,0 @@ -/* Copyright (C) 2022 The Qt Company Ltd. - * - * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 -*/ - -// Dummy replacement for legacy licheck to have -// Qt5 qmake satisfied with return value of '0' -int main(int argc, char *argv[]) { - return 0; -} diff --git a/httpclient.cpp b/httpclient.cpp deleted file mode 100644 index 33329c6..0000000 --- a/httpclient.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* Copyright (C) 2022 The Qt Company Ltd. - * - * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 -*/ - -#include "httpclient.h" - -size_t WriteCallback(char *contents, size_t size, size_t nmemb, void *userp) -{ - std::cout << "Reading data\n"; - ((std::string *)userp)->append(contents, size * nmemb); - return size * nmemb; -} - -HttpClient::HttpClient( const std::string &serverUrl, - const std::string &requestAccessPoint, - const std::string &longtermAccessPoint, - const std::string &versionAccessPoint) : - m_serverUrl(serverUrl), - m_requestAccessPoint(requestAccessPoint), - m_longtermAccessPoint(longtermAccessPoint), - m_versionAccessPoint(versionAccessPoint) -{ - m_userAgent += DAEMON_VERSION; -} - -int HttpClient::sendRequest(std::string &reply, const std::string &payload, - const std::string &server, const std::string &authKey) -{ - HttpRequest request; - m_lastError = "No errors"; - - /* specify URL to POST */ - request.url = server; - if (server.empty()) { - // If server URL is not given as param, we use the default - request.url = m_serverUrl; - } - if (!authKey.empty()) { - // normal request - request.url += m_requestAccessPoint; - } else { - if (payload.empty()) { - // version query - request.url += m_versionAccessPoint; - } else { - // long-term request - request.url += m_longtermAccessPoint; - } - } - request.authKey = authKey; - request.payload = payload; - - - if (!request.authKey.empty()) { - // Set authorization only if applicable - std::string auth = "Authorization: " + request.authKey; - request.headers = curl_slist_append(request.headers, auth.c_str()); - } - std::string agent = "User-Agent: " + m_userAgent; - request.headers = curl_slist_append(request.headers, agent.c_str()); - request.headers = curl_slist_append(request.headers, "Accept: */*"); - request.headers = curl_slist_append(request.headers, "Content-Type: application/json"); - request.headers = curl_slist_append(request.headers, "charset: utf-8"); - - std::cout << "HTTPClient() -- server URL " << request.url << std::endl; - - int retVal = 0; - - /* init the curl session */ - curl_global_init(CURL_GLOBAL_ALL); - CURL *curl = curl_easy_init(); - try { - if (doRequest(curl, request) != 0) { - retVal = 1; - } - } catch(...) { - m_lastError = "Connection failed"; - retVal = 1; - } - if (retVal == 0) { - std::cout << request.reply.length() << " bytes retrieved from license server:\n"; - std::cout << request.reply << std::endl; - } else { - std::cout << m_lastError << std::endl; - } - - /* cleanup curl stuff */ - - curl_easy_cleanup(curl); - curl_global_cleanup(); - curl_slist_free_all(request.headers); - reply = request.reply; - return retVal; -} - -int HttpClient::doRequest(CURL *curl, HttpRequest &request) -{ - // Set all received data to be send to our callback function - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); - - std::string readBuffer; - // Pass the buffer to a callback - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - - // Set the URL / headers - curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request.headers); - if (!request.payload.empty()) { - curl_easy_setopt(curl, CURLOPT_POST, 1); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.payload.c_str()); - } - else { - curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); - } - curl_easy_setopt(curl, CURLOPT_TIMEOUT, SERVER_CONN_TIMEOUT); - - // get it - CURLcode res; - res = curl_easy_perform(curl); - - // check for errors - if (res != CURLE_OK) { - std::cout << "HTTP transfer failed, URL: " << request.url << std::endl; - m_lastError = curl_easy_strerror(res); - return 1; - } - request.reply = readBuffer; - return 0; -} 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 +#include #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 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 #include #if _WIN32 - #define WIN32_LEAN_AND_MEAN // Windows + #define WIN32_LEAN_AND_MEAN //#include #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 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 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 #include 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 #include +#include #include #include #include @@ -13,6 +15,8 @@ #include #include #include + + #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 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(); diff --git a/jsonhandler.cpp b/jsonhandler.cpp deleted file mode 100644 index f46fd72..0000000 --- a/jsonhandler.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* Copyright (C) 2022 The Qt Company Ltd. - * - * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 -*/ - -#include "jsonhandler.h" - -JsonHandler::JsonHandler(const std::string &jsonString) -{ - if (preParse(jsonString) != 0) { - std::cout << "Error reading JSON:\n" << jsonString << std::endl; - } -} - -std::string JsonHandler::get(const std::string &item) -{ - return getStr(item); -} - -int JsonHandler::getInt(const std::string &item) -{ - if (!m_items.count(item)) { - std::cout << "ERROR no item '" << item << "' (integer) found in JSON\n"; - return INT16_MIN; - } - // Is it an integer? - try { - return std::stoi(m_items[item]); - } - catch (...) { - return INT16_MIN; - } - -} - -std::string JsonHandler::getStr(const std::string &item) -{ - if (!m_items.count(item)) { - std::cout << "ERROR no item '" << item << "' (string) found in JSON\n"; - return("Error in license service"); - } - return (std::string)m_items[item]; -} - -bool JsonHandler::getBool(const std::string &item) -{ - if (!m_items.count(item)) { - std::cout << "ERROR no item '" << item << "' (boolean) found in JSON\n"; - } - return (m_items[item] == "true")? true : false; -} - -void JsonHandler::add(const std::string &item, const std::string &value) -{ - m_items[item] = value; -} - -void JsonHandler::add(const std::string &item, uint64_t value) -{ - m_items[item] = std::to_string(value); -} - -void JsonHandler::add(const std::string &item, bool value) -{ - m_items[item] = (value) ? "true" : "false"; -} - -std::string JsonHandler::dump(uint8_t indent) -{ - // Stringify a JSON - // NOTE!! Specific to license daemon, NOT GENERIC - std::string ind(indent, ' '); - uint8_t itemCount = m_items.size(); - std::stringstream top; // For JSON top level - std::stringstream res; // for items going under "reservation" - top << "{"; - if (indent > 0) { - res << ind << "\"reservation\": {" << std::endl; - top << std::endl; - } else { - res << "\"reservation\":{"; - } - - uint8_t count = 0; - for (auto const &item : m_items) { - count ++; - if (std::find(reservation.begin(), reservation.end(), - item.first) != reservation.end()) { - res << ind << ind << "\"" << item.first << "\":"; - if (indent > 0) res << " "; - res << "\"" << item.second << "\""; - if (count == itemCount) { - if (indent > 0) { - res << std::endl << ind << "}"; - } else { - res << "}" << std::endl; - } - continue; - } else res << ","; - if (indent > 0) res << std::endl; - } else { - if (indent > 0) res << std::setfill(' ') << std::setw(indent); - top << ind << "\"" << item.first << "\":"; - if (indent > 0) top << " "; - top << "\"" << item.second << "\","; - if (indent > 0) top << std::endl; - } - } - top << res.str(); - if (indent > 0) top << std::endl; - top << "}"; - return top.str(); -} - -int JsonHandler::preParse(const std::string &jsonString) -{ - std::string str = jsonString; - /* First throw away everything we dont need - */ - size_t idx = str.find("\"reservation\":"); - // To prevent a crash if that string is not found: - if (idx != std::string::npos ) - str.erase(idx +1, 13); - str.erase(std::remove(str.begin(), str.end(), '{'), str.end()); - str.erase(std::remove(str.begin(), str.end(), '}'), str.end()); - str.erase(std::remove(str.begin(), str.end(), '"'), str.end()); - - // Split the item/value pairs - std::vector items = utils::splitStr(str, ','); - - // Populate m_items map - for (std::string pair : items) { - std::string item = pair.substr(0, pair.find(':')); - item = utils::trimStr(item); - std::string value = utils::trimStr(pair.substr(pair.find(':') + 1, pair.length() - 1)); - m_items[item] = value; - } - return 0; - -} diff --git a/licd.ini b/licd.ini index e619c4b..c9ee56f 100644 --- a/licd.ini +++ b/licd.ini @@ -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/licdsetup.cpp b/licdsetup.cpp deleted file mode 100644 index 2594485..0000000 --- a/licdsetup.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/* Copyright (C) 2022 The Qt Company Ltd. - * - * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 -*/ - -#include "licdsetup.h" - -LicdSetup::LicdSetup(int type, const std::string &callerBinaryPath) : - m_setType(type) -{ - if (m_setType == e_set_type_daemon) { - m_filepath = DAEMON_SETTINGS_FILE; - initSettings(); - } else if (m_setType == e_set_type_moc) { - m_filepath = getQtAppDataLocation() + USER_SETTINGS_FILE; - m_tag = parseSettingTag(callerBinaryPath); - } else { - m_filepath = getQtAppDataLocation() + USER_SETTINGS_FILE; - m_tag = DEFAULT_USER_SETTINGS_TAG; - } -} - - -std::string LicdSetup::get(const std::string &item) -{ - try { - auto x = m_settings.at(item); - } - catch (...) { - std::cout << "Warning: No settings item '" << item << "' found\n"; - return ""; - } - return m_settings[item]; -} - -void LicdSetup::set(const std::string &item, const std::string &value) -{ - //std::cout << "Setting '" << item << "' to '" << value << "'\n"; - m_settings[item] = value; -} - -int LicdSetup::initSettings() -{ - int result = 0; - if (m_setType == e_set_type_daemon) { - // read daemon info - result = getDaemonInfo(); - } else { - // Read user info - result = getUserInfo(); - if (result == e_file_not_present) { - // No settings file found: Create it - if (createUserSettingsFile() != 0) { - std::cout << "WARNING! Not able to create settings file\n"; - } - return e_file_not_present; - } else if (result == e_no_settings_tag && m_setType != e_set_type_licensetool) { - std::string content = getTaggedSettingsSkeleton(); - if (appendNewSection(content) != 0) { - std::cout << "WARNING! Not able to write into settings file\n"; - } - } - } - if (result == e_general_file_error) { - std::cout << "Error reading file:\n"; - return e_general_file_error; - } else if (result == e_file_invalid_content) { - std::cout << "Error somewhere in settings:\n"; - for (auto& item: m_settings) { - std::cout << item.first << ": " << item.second << '\n'; - } - return e_file_invalid_content; - } - return e_settings_ok; -} - - -int LicdSetup::getUserInfo() -{ - if (!utils::fileExists(m_filepath)) { - return e_file_not_present; - } - std::ifstream file; - bool defaultFound = false; - bool inDefault = false; - bool inTag = false; - bool tagFound = false; - try - { - file.open(m_filepath, std::ios::in); - if (file.is_open()) - { - std::string line; - while (getline(file, line)) - { - line = utils::trimStr(line); - // Continue until correct tag is found: - if (line.length() < 1) continue; // omit empty lines - if (line[0] == '#') continue; // omit comment line - if (line[0] == '[' && line[line.length()-1] == ']') { - inTag = false; - inDefault = false; - if (!tagFound && !inTag) { - if (line.compare(m_tag) == 0) { - tagFound = true; - inTag = true; - inDefault = false; - } - } - if (!defaultFound && !inDefault && !tagFound) { - if (line == DEFAULT_USER_SETTINGS_TAG) { - // Pick default settings only if correct tag is not found yet - defaultFound = true; - inDefault = true; - inTag = false; - } - } - continue; - } - if (inTag || inDefault) { - std::string item; - std::string value; - std::vector data = utils::splitStr(line, '='); - if (data.size() > 0) { - item = data[0]; - if (data.size() > 1) { - value = data[1]; - } else { - value = ""; - } - m_settings[item] = value; - } - } - } - file.close(); - } else { - std::cout << "\nERROR not able to open the settings file, check " << m_filepath << std::endl; - file.close(); - return e_general_file_error; - } - } - catch (...) - { - std::cout << "\nERROR reading the settings file, check " << m_filepath << std::endl; - file.close(); - return e_general_file_error; - } - if (!tagFound) { - if (!defaultFound) { - return e_file_invalid_content; - } - return e_no_settings_tag; - } - // Everything is in place? - if (m_settings["user_id"].length() == 0 || m_settings["license_id"].length() == 0) { - return e_file_invalid_content; - } - std::string addr = m_settings["licd_addr"]; - uint16_t port = utils::strToInt(m_settings["licd_port"]); - if (port == 0) { - return e_file_invalid_content; - } - return e_settings_ok; -} - -int LicdSetup::getDaemonInfo() -{ - m_settings["host_os"] = utils::getOsName(); - std::cout << "Host: " << m_settings["host_os"] << std::endl; - std::ifstream file; - try - { - std::cout << "Opening file " << DAEMON_SETTINGS_FILE << std::endl; - file.open(DAEMON_SETTINGS_FILE, std::ios::in); - if (file.is_open()) - { - std::string line; - while (getline(file, line)) - { - line = utils::trimStr(line); - if (line.length() > 0) { - if (line[0] == '#' || line[0] == '[') continue; // omit these lines - std::string item; - std::string value; - std::vector data = utils::splitStr(line, '='); - if (data.size() > 0) { - item = data[0]; - if (data.size() > 1) { - value = data[1]; - } else { - value = ""; - } - m_settings[item] = value; - std::cout << "Setting '" << item << "' to '" << value <<"'\n"; - } - } - } - } else { - std::cout << "ERROR opening the settings file" << std::endl; - file.close(); - return e_general_file_error; - } - } - catch (...) - { - std::cout << "ERROR opening/reading the settings file" << std::endl; - file.close(); - return e_general_file_error; - } - file.close(); - return e_settings_ok; -} - -std::string LicdSetup::parseSettingTag(const std::string &callerBinaryPath) -{ - // Qt uses absolute paths, like: (linux example): - // "/home//Qt/6.3.1/gcc_64/./libexec/moc" - size_t loc = callerBinaryPath.rfind(DIR_SEPARATOR); - loc = callerBinaryPath.rfind(DIR_SEPARATOR, loc-1); - std::string tag = "[" + callerBinaryPath.substr(0, loc); - while (tag[tag.length()-1] == DIR_SEPARATOR || tag[tag.length()-1] == '.' ) { - tag.erase(tag.length() -1); - } - tag += "]"; - std::replace(tag.begin(), tag.end(), DIR_SEPARATOR, '_'); - return tag; -} - -int LicdSetup::createUserSettingsFile() -{ - if (!utils::fileExists(m_filepath)) { - std::cout << "Settings file does not exist." << std::endl; - if (utils::writeToFile(m_filepath, getUserFileSkeleton(), false) != 0) { - std::cout << "Failed to create file " << m_filepath << std::endl; - return -1; - } - std::cout << "New settings file created in " << m_filepath.c_str() - << "\nCheck and edit the " << DEFAULT_USER_SETTINGS_TAG << " section\n"; - } - return 0; -} - -int LicdSetup::appendNewSection(const std::string &contentStr) -{ - // Append new section in the file - if (utils::writeToFile(m_filepath, contentStr, true) != 0) { - return -1; - } - std::cout << "Note! Default settings copied under section:\n" << m_tag << std::endl; - return 0; -} - -std::string LicdSetup::getUserFileSkeleton() -{ - std::stringstream ss; - ss << "#" << std::endl; - ss << "# User settings for Qt licensing tools" << std::endl; - ss << "# Please fill up, and note that at least user_id" << std::endl; - ss << "# and license id MUST be set here correctly" << std::endl; - ss << "#\n" << "# Required fields:" << std::endl; - ss << "# user_id: Your Qt license username" << std::endl; - ss << "# license_id: Your Qt license ID" << std::endl; - ss << "# licd_addr: URL of the qt license daemon." << std::endl; - ss << "# licd_port: TCP/IP port of the qt license daemon" << std::endl; - ss << "#\n" << "# Optional fields:" << std::endl; - ss << "# user_email: For cloud usage" << std::endl; - ss << "# license_server_addr: If your setup differs from system default license server" << std::endl; - ss << "# license_server_port: If your setup differs from system default license server" << std::endl; - ss << "\n\n# Default settings section (do not remove):" << std::endl; - ss << DEFAULT_USER_SETTINGS_TAG << std::endl; - ss << "user_id=foobar" << std::endl; - ss << "user_email=foobar@foo.bar" << std::endl; - ss << "license_id=12345678" << std::endl; - ss << "licd_addr=" << DAEMON_ADDR << std::endl; - ss << "licd_port=" << DAEMON_PORT << std::endl; - ss << "license_server_addr=" << std::endl; - ss << "license_server_port=" << std::endl; - ss << std::endl; - return ss.str(); -} - -std::string LicdSetup::getTaggedSettingsSkeleton() -{ - std::stringstream ss; - ss << std::endl << m_tag << std::endl; - ss << "user_id=" << m_settings["user_id"] << std::endl; - ss << "user_email=" << m_settings["user_email"] << std::endl; - ss << "license_id=" << m_settings["license_id"] << std::endl; - ss << "licd_addr=" << m_settings["licd_addr"] << std::endl; - ss << "licd_port=" << m_settings["licd_port"] << std::endl; - ss << "license_server_addr=" << m_settings["license_server_addr"] << std::endl; - ss << "license_server_port=" << m_settings["license_server_port"] << std::endl; - ss << std::endl; - - return ss.str(); -} - -std::string LicdSetup::getQtAppDataLocation() -{ - std::string retVal = utils::getUserHomeDir(); - retVal += DIR_SEPARATOR; - retVal += USER_SETTINGS_DIR; - retVal += DIR_SEPARATOR; - return retVal; -} 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 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 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 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 #include #include + #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 : 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 +#include +#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 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 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/src/dummy_licheck.cpp b/src/dummy_licheck.cpp new file mode 100644 index 0000000..e38c627 --- /dev/null +++ b/src/dummy_licheck.cpp @@ -0,0 +1,10 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ + +// Dummy replacement for legacy licheck to have +// Qt5 qmake satisfied with return value of '0' +int main(int argc, char *argv[]) { + return 0; +} diff --git a/src/httpclient.cpp b/src/httpclient.cpp new file mode 100644 index 0000000..730b310 --- /dev/null +++ b/src/httpclient.cpp @@ -0,0 +1,129 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ + +#include "httpclient.h" + +size_t WriteCallback(char *contents, size_t size, size_t nmemb, void *userp) +{ + std::cout << "Reading data\n"; + ((std::string *)userp)->append(contents, size * nmemb); + return size * nmemb; +} + +HttpClient::HttpClient( const std::string &serverUrl, + const std::string &requestAccessPoint, + const std::string &longtermAccessPoint, + const std::string &versionAccessPoint) : + m_serverUrl(serverUrl), + m_requestAccessPoint(requestAccessPoint), + m_longtermAccessPoint(longtermAccessPoint), + m_versionAccessPoint(versionAccessPoint) +{ + m_userAgent += DAEMON_VERSION; +} + +int HttpClient::sendRequest(std::string &reply, const std::string &payload, + const std::string &server, const std::string &authKey) +{ + HttpRequest request; + m_lastError = "No errors"; + + /* specify URL to POST */ + request.url = server; + if (server.empty()) { + // If server URL is not given as param, we use the default + request.url = m_serverUrl; + } + if (!authKey.empty()) { + // normal request + request.url += m_requestAccessPoint; + } else { + if (payload.empty()) { + // version query + request.url += m_versionAccessPoint; + } else { + // long-term request + request.url += m_longtermAccessPoint; + } + } + request.authKey = authKey; + request.payload = payload; + + if (!request.authKey.empty()) { + // Set authorization only if applicable + std::string auth = "Authorization: " + request.authKey; + request.headers = curl_slist_append(request.headers, auth.c_str()); + } + std::string agent = "User-Agent: " + m_userAgent; + request.headers = curl_slist_append(request.headers, agent.c_str()); + request.headers = curl_slist_append(request.headers, "Accept: */*"); + request.headers = curl_slist_append(request.headers, "Content-Type: application/json"); + request.headers = curl_slist_append(request.headers, "charset: utf-8"); + + std::cout << "HTTPClient() -- server URL " << request.url << std::endl; + + int retVal = 0; + + /* init the curl session */ + curl_global_init(CURL_GLOBAL_ALL); + CURL *curl = curl_easy_init(); + try { + if (doRequest(curl, request) != 0) { + retVal = 1; + } + } catch(...) { + m_lastError = "Connection failed"; + retVal = 1; + } + if (retVal == 0) { + std::cout << request.reply.length() << " bytes retrieved from license server:\n"; + std::cout << request.reply << std::endl; + } else { + std::cout << m_lastError << std::endl; + } + + /* cleanup curl stuff */ + + curl_easy_cleanup(curl); + curl_global_cleanup(); + curl_slist_free_all(request.headers); + reply = request.reply; + return retVal; +} + +int HttpClient::doRequest(CURL *curl, HttpRequest &request) +{ + // Set all received data to be send to our callback function + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + + std::string readBuffer; + // Pass the buffer to a callback + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); + + // Set the URL / headers + curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request.headers); + if (!request.payload.empty()) { + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.payload.c_str()); + } + else { + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); + } + curl_easy_setopt(curl, CURLOPT_TIMEOUT, SERVER_CONN_TIMEOUT); + + // get it + CURLcode res; + res = curl_easy_perform(curl); + + // check for errors + if (res != CURLE_OK) { + std::cout << "HTTP transfer failed, URL: " << request.url << std::endl; + m_lastError = curl_easy_strerror(res); + return 1; + } + request.reply = readBuffer; + return 0; +} diff --git a/src/jsonhandler.cpp b/src/jsonhandler.cpp new file mode 100644 index 0000000..f46fd72 --- /dev/null +++ b/src/jsonhandler.cpp @@ -0,0 +1,140 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ + +#include "jsonhandler.h" + +JsonHandler::JsonHandler(const std::string &jsonString) +{ + if (preParse(jsonString) != 0) { + std::cout << "Error reading JSON:\n" << jsonString << std::endl; + } +} + +std::string JsonHandler::get(const std::string &item) +{ + return getStr(item); +} + +int JsonHandler::getInt(const std::string &item) +{ + if (!m_items.count(item)) { + std::cout << "ERROR no item '" << item << "' (integer) found in JSON\n"; + return INT16_MIN; + } + // Is it an integer? + try { + return std::stoi(m_items[item]); + } + catch (...) { + return INT16_MIN; + } + +} + +std::string JsonHandler::getStr(const std::string &item) +{ + if (!m_items.count(item)) { + std::cout << "ERROR no item '" << item << "' (string) found in JSON\n"; + return("Error in license service"); + } + return (std::string)m_items[item]; +} + +bool JsonHandler::getBool(const std::string &item) +{ + if (!m_items.count(item)) { + std::cout << "ERROR no item '" << item << "' (boolean) found in JSON\n"; + } + return (m_items[item] == "true")? true : false; +} + +void JsonHandler::add(const std::string &item, const std::string &value) +{ + m_items[item] = value; +} + +void JsonHandler::add(const std::string &item, uint64_t value) +{ + m_items[item] = std::to_string(value); +} + +void JsonHandler::add(const std::string &item, bool value) +{ + m_items[item] = (value) ? "true" : "false"; +} + +std::string JsonHandler::dump(uint8_t indent) +{ + // Stringify a JSON + // NOTE!! Specific to license daemon, NOT GENERIC + std::string ind(indent, ' '); + uint8_t itemCount = m_items.size(); + std::stringstream top; // For JSON top level + std::stringstream res; // for items going under "reservation" + top << "{"; + if (indent > 0) { + res << ind << "\"reservation\": {" << std::endl; + top << std::endl; + } else { + res << "\"reservation\":{"; + } + + uint8_t count = 0; + for (auto const &item : m_items) { + count ++; + if (std::find(reservation.begin(), reservation.end(), + item.first) != reservation.end()) { + res << ind << ind << "\"" << item.first << "\":"; + if (indent > 0) res << " "; + res << "\"" << item.second << "\""; + if (count == itemCount) { + if (indent > 0) { + res << std::endl << ind << "}"; + } else { + res << "}" << std::endl; + } + continue; + } else res << ","; + if (indent > 0) res << std::endl; + } else { + if (indent > 0) res << std::setfill(' ') << std::setw(indent); + top << ind << "\"" << item.first << "\":"; + if (indent > 0) top << " "; + top << "\"" << item.second << "\","; + if (indent > 0) top << std::endl; + } + } + top << res.str(); + if (indent > 0) top << std::endl; + top << "}"; + return top.str(); +} + +int JsonHandler::preParse(const std::string &jsonString) +{ + std::string str = jsonString; + /* First throw away everything we dont need + */ + size_t idx = str.find("\"reservation\":"); + // To prevent a crash if that string is not found: + if (idx != std::string::npos ) + str.erase(idx +1, 13); + str.erase(std::remove(str.begin(), str.end(), '{'), str.end()); + str.erase(std::remove(str.begin(), str.end(), '}'), str.end()); + str.erase(std::remove(str.begin(), str.end(), '"'), str.end()); + + // Split the item/value pairs + std::vector items = utils::splitStr(str, ','); + + // Populate m_items map + for (std::string pair : items) { + std::string item = pair.substr(0, pair.find(':')); + item = utils::trimStr(item); + std::string value = utils::trimStr(pair.substr(pair.find(':') + 1, pair.length() - 1)); + m_items[item] = value; + } + return 0; + +} diff --git a/src/licdsetup.cpp b/src/licdsetup.cpp new file mode 100644 index 0000000..2594485 --- /dev/null +++ b/src/licdsetup.cpp @@ -0,0 +1,305 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ + +#include "licdsetup.h" + +LicdSetup::LicdSetup(int type, const std::string &callerBinaryPath) : + m_setType(type) +{ + if (m_setType == e_set_type_daemon) { + m_filepath = DAEMON_SETTINGS_FILE; + initSettings(); + } else if (m_setType == e_set_type_moc) { + m_filepath = getQtAppDataLocation() + USER_SETTINGS_FILE; + m_tag = parseSettingTag(callerBinaryPath); + } else { + m_filepath = getQtAppDataLocation() + USER_SETTINGS_FILE; + m_tag = DEFAULT_USER_SETTINGS_TAG; + } +} + + +std::string LicdSetup::get(const std::string &item) +{ + try { + auto x = m_settings.at(item); + } + catch (...) { + std::cout << "Warning: No settings item '" << item << "' found\n"; + return ""; + } + return m_settings[item]; +} + +void LicdSetup::set(const std::string &item, const std::string &value) +{ + //std::cout << "Setting '" << item << "' to '" << value << "'\n"; + m_settings[item] = value; +} + +int LicdSetup::initSettings() +{ + int result = 0; + if (m_setType == e_set_type_daemon) { + // read daemon info + result = getDaemonInfo(); + } else { + // Read user info + result = getUserInfo(); + if (result == e_file_not_present) { + // No settings file found: Create it + if (createUserSettingsFile() != 0) { + std::cout << "WARNING! Not able to create settings file\n"; + } + return e_file_not_present; + } else if (result == e_no_settings_tag && m_setType != e_set_type_licensetool) { + std::string content = getTaggedSettingsSkeleton(); + if (appendNewSection(content) != 0) { + std::cout << "WARNING! Not able to write into settings file\n"; + } + } + } + if (result == e_general_file_error) { + std::cout << "Error reading file:\n"; + return e_general_file_error; + } else if (result == e_file_invalid_content) { + std::cout << "Error somewhere in settings:\n"; + for (auto& item: m_settings) { + std::cout << item.first << ": " << item.second << '\n'; + } + return e_file_invalid_content; + } + return e_settings_ok; +} + + +int LicdSetup::getUserInfo() +{ + if (!utils::fileExists(m_filepath)) { + return e_file_not_present; + } + std::ifstream file; + bool defaultFound = false; + bool inDefault = false; + bool inTag = false; + bool tagFound = false; + try + { + file.open(m_filepath, std::ios::in); + if (file.is_open()) + { + std::string line; + while (getline(file, line)) + { + line = utils::trimStr(line); + // Continue until correct tag is found: + if (line.length() < 1) continue; // omit empty lines + if (line[0] == '#') continue; // omit comment line + if (line[0] == '[' && line[line.length()-1] == ']') { + inTag = false; + inDefault = false; + if (!tagFound && !inTag) { + if (line.compare(m_tag) == 0) { + tagFound = true; + inTag = true; + inDefault = false; + } + } + if (!defaultFound && !inDefault && !tagFound) { + if (line == DEFAULT_USER_SETTINGS_TAG) { + // Pick default settings only if correct tag is not found yet + defaultFound = true; + inDefault = true; + inTag = false; + } + } + continue; + } + if (inTag || inDefault) { + std::string item; + std::string value; + std::vector data = utils::splitStr(line, '='); + if (data.size() > 0) { + item = data[0]; + if (data.size() > 1) { + value = data[1]; + } else { + value = ""; + } + m_settings[item] = value; + } + } + } + file.close(); + } else { + std::cout << "\nERROR not able to open the settings file, check " << m_filepath << std::endl; + file.close(); + return e_general_file_error; + } + } + catch (...) + { + std::cout << "\nERROR reading the settings file, check " << m_filepath << std::endl; + file.close(); + return e_general_file_error; + } + if (!tagFound) { + if (!defaultFound) { + return e_file_invalid_content; + } + return e_no_settings_tag; + } + // Everything is in place? + if (m_settings["user_id"].length() == 0 || m_settings["license_id"].length() == 0) { + return e_file_invalid_content; + } + std::string addr = m_settings["licd_addr"]; + uint16_t port = utils::strToInt(m_settings["licd_port"]); + if (port == 0) { + return e_file_invalid_content; + } + return e_settings_ok; +} + +int LicdSetup::getDaemonInfo() +{ + m_settings["host_os"] = utils::getOsName(); + std::cout << "Host: " << m_settings["host_os"] << std::endl; + std::ifstream file; + try + { + std::cout << "Opening file " << DAEMON_SETTINGS_FILE << std::endl; + file.open(DAEMON_SETTINGS_FILE, std::ios::in); + if (file.is_open()) + { + std::string line; + while (getline(file, line)) + { + line = utils::trimStr(line); + if (line.length() > 0) { + if (line[0] == '#' || line[0] == '[') continue; // omit these lines + std::string item; + std::string value; + std::vector data = utils::splitStr(line, '='); + if (data.size() > 0) { + item = data[0]; + if (data.size() > 1) { + value = data[1]; + } else { + value = ""; + } + m_settings[item] = value; + std::cout << "Setting '" << item << "' to '" << value <<"'\n"; + } + } + } + } else { + std::cout << "ERROR opening the settings file" << std::endl; + file.close(); + return e_general_file_error; + } + } + catch (...) + { + std::cout << "ERROR opening/reading the settings file" << std::endl; + file.close(); + return e_general_file_error; + } + file.close(); + return e_settings_ok; +} + +std::string LicdSetup::parseSettingTag(const std::string &callerBinaryPath) +{ + // Qt uses absolute paths, like: (linux example): + // "/home//Qt/6.3.1/gcc_64/./libexec/moc" + size_t loc = callerBinaryPath.rfind(DIR_SEPARATOR); + loc = callerBinaryPath.rfind(DIR_SEPARATOR, loc-1); + std::string tag = "[" + callerBinaryPath.substr(0, loc); + while (tag[tag.length()-1] == DIR_SEPARATOR || tag[tag.length()-1] == '.' ) { + tag.erase(tag.length() -1); + } + tag += "]"; + std::replace(tag.begin(), tag.end(), DIR_SEPARATOR, '_'); + return tag; +} + +int LicdSetup::createUserSettingsFile() +{ + if (!utils::fileExists(m_filepath)) { + std::cout << "Settings file does not exist." << std::endl; + if (utils::writeToFile(m_filepath, getUserFileSkeleton(), false) != 0) { + std::cout << "Failed to create file " << m_filepath << std::endl; + return -1; + } + std::cout << "New settings file created in " << m_filepath.c_str() + << "\nCheck and edit the " << DEFAULT_USER_SETTINGS_TAG << " section\n"; + } + return 0; +} + +int LicdSetup::appendNewSection(const std::string &contentStr) +{ + // Append new section in the file + if (utils::writeToFile(m_filepath, contentStr, true) != 0) { + return -1; + } + std::cout << "Note! Default settings copied under section:\n" << m_tag << std::endl; + return 0; +} + +std::string LicdSetup::getUserFileSkeleton() +{ + std::stringstream ss; + ss << "#" << std::endl; + ss << "# User settings for Qt licensing tools" << std::endl; + ss << "# Please fill up, and note that at least user_id" << std::endl; + ss << "# and license id MUST be set here correctly" << std::endl; + ss << "#\n" << "# Required fields:" << std::endl; + ss << "# user_id: Your Qt license username" << std::endl; + ss << "# license_id: Your Qt license ID" << std::endl; + ss << "# licd_addr: URL of the qt license daemon." << std::endl; + ss << "# licd_port: TCP/IP port of the qt license daemon" << std::endl; + ss << "#\n" << "# Optional fields:" << std::endl; + ss << "# user_email: For cloud usage" << std::endl; + ss << "# license_server_addr: If your setup differs from system default license server" << std::endl; + ss << "# license_server_port: If your setup differs from system default license server" << std::endl; + ss << "\n\n# Default settings section (do not remove):" << std::endl; + ss << DEFAULT_USER_SETTINGS_TAG << std::endl; + ss << "user_id=foobar" << std::endl; + ss << "user_email=foobar@foo.bar" << std::endl; + ss << "license_id=12345678" << std::endl; + ss << "licd_addr=" << DAEMON_ADDR << std::endl; + ss << "licd_port=" << DAEMON_PORT << std::endl; + ss << "license_server_addr=" << std::endl; + ss << "license_server_port=" << std::endl; + ss << std::endl; + return ss.str(); +} + +std::string LicdSetup::getTaggedSettingsSkeleton() +{ + std::stringstream ss; + ss << std::endl << m_tag << std::endl; + ss << "user_id=" << m_settings["user_id"] << std::endl; + ss << "user_email=" << m_settings["user_email"] << std::endl; + ss << "license_id=" << m_settings["license_id"] << std::endl; + ss << "licd_addr=" << m_settings["licd_addr"] << std::endl; + ss << "licd_port=" << m_settings["licd_port"] << std::endl; + ss << "license_server_addr=" << m_settings["license_server_addr"] << std::endl; + ss << "license_server_port=" << m_settings["license_server_port"] << std::endl; + ss << std::endl; + + return ss.str(); +} + +std::string LicdSetup::getQtAppDataLocation() +{ + std::string retVal = utils::getUserHomeDir(); + retVal += DIR_SEPARATOR; + retVal += USER_SETTINGS_DIR; + retVal += DIR_SEPARATOR; + return retVal; +} 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 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 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 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/src/tcpclient.cpp b/src/tcpclient.cpp new file mode 100644 index 0000000..1922b76 --- /dev/null +++ b/src/tcpclient.cpp @@ -0,0 +1,76 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ + +#include "tcpclient.h" + +TcpClient::TcpClient(const std::string &connAddr, uint16_t port) +{ +#ifdef _WIN32 + // Initialize Winsock + WSADATA wsaData; + int iResult = 0; + iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (iResult != NO_ERROR) { + wprintf(L"Error at WSAStartup()\n"); + exit(EXIT_FAILURE); + } + const int inetSuccess = inet_pton(AF_INET, connAddr.c_str(), &m_server.sin_addr.s_addr); +#else + const int inetSuccess = inet_aton(connAddr.c_str(), &m_server.sin_addr); +#endif + if (!inetSuccess) { + // Try to resolve hostname + struct hostent *host; + struct in_addr **addrList; + if ( (host = gethostbyname(connAddr.c_str()) ) == nullptr){ + std::cout << "Failed to resolve hostname\n"; + exit(EXIT_FAILURE); + } + addrList = (struct in_addr **) host->h_addr_list; + m_server.sin_addr = *addrList[0]; + } + m_server.sin_port = htons(port); +} + +int TcpClient::sendAndReceive(const std::string &message, std::string &reply) +{ + m_server.sin_family = AF_INET; + m_socketFD = (socket(AF_INET , SOCK_STREAM, 0)); + if (m_socketFD < 0) { + doCloseSocket(); + return e_tcp_fail_socket; + } + + // Connect to daemon + int connectR = connect(m_socketFD, (struct sockaddr *)&m_server, sizeof(m_server)); + if (connectR == -1) { + doCloseSocket(); + return e_tcp_error_conn; + } + const size_t bytesSent = send(m_socketFD, message.c_str(), (int)message.length(), 0); + + if (bytesSent < 0 ) { // send failed + doCloseSocket(); + return e_tcp_error_send; + } + + std::string resp(BUFFER_SIZE, ' '); + size_t bytes_recv; + try { + bytes_recv = recv(m_socketFD, &resp.front(), (int)resp.size(), 0); + } + catch (...) { + std::cout << "Error receiving" << std::endl; + doCloseSocket(); + return e_tcp_error_recv; + } + if (bytes_recv == -1) { + doCloseSocket(); + return e_tcp_error_recv; + } + doCloseSocket(); + reply = resp.substr(0, bytes_recv); // Had to cut it like this for windows + return e_tcp_success; +} diff --git a/src/tcpserver.cpp b/src/tcpserver.cpp new file mode 100644 index 0000000..b1205ba --- /dev/null +++ b/src/tcpserver.cpp @@ -0,0 +1,178 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ + +#include "tcpserver.h" + +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; + } + +#ifdef _WIN32 + // Initialize Winsock + WSADATA wsaData; + int iResult = 0; + iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (iResult != NO_ERROR) { + wprintf(L"Error at WSAStartup()\n"); + exit(EXIT_FAILURE); + } +#endif + // Create a master socket + if ((m_masterSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == 0) { + 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"); + exit(EXIT_FAILURE); + } + timeval tv {2, 0}; //t.tv_sec = 2; t.tv_usec = 0; + int ret = setsockopt(m_masterSocket, SOL_SOCKET, SO_RCVTIMEO, + (char*)&tv, sizeof(timeval)); + if (ret < 0) { + printf("Socket timeout error code: %d\n", ret); + perror("Socket timeout failed"); + } + + // Type of socket created + m_address.sin_family = AF_INET; + m_address.sin_addr.s_addr = INADDR_ANY; + m_address.sin_port = htons(serverPort); + + // Bind the socket to localhost + if (bind(m_masterSocket, (struct sockaddr *)&m_address, sizeof(m_address)) < 0) { + doCloseSocket(m_masterSocket); + perror("bind failed"); + exit(EXIT_FAILURE); + } + std::cout << "Listening to port " << serverPort << std::endl; + unsigned long mode = 1; + //ioctlsocket(m_masterSocket, FIONBIO, &mode); + // Try to specify maximum of 3 pending connections for the master socket + if (listen(m_masterSocket, 3) < 0) { + doCloseSocket(m_masterSocket); + perror("listen"); + exit(EXIT_FAILURE); + } + m_addrlen = sizeof(m_address); + + // Accept the incoming connection + std::cout << "Waiting for connections ...\n"; +} + +TcpServer::~TcpServer() +{ + doCloseSocket(m_masterSocket); + for (uint8_t i = 0; i < MAX_CLIENTS; i++) { + doCloseSocket(m_clientSocket[i]); + } + +} + + +std::string TcpServer::listenToClients(int &socket) +{ + std::string data; + bool gotData = false; + //do { + // Clear the socket set + FD_ZERO(&m_readfds); + + type_socket max_sd = m_masterSocket; + + // Add master socket to set + FD_SET(m_masterSocket, &m_readfds); + + // Add child sockets to set + for (int i = 0; i < MAX_CLIENTS; i++) { + // Socket descriptor + type_socket sd = m_clientSocket[i]; + + // If valid socket descriptor then add to read list + if (sd > 0) + FD_SET(sd, &m_readfds); + + // Highest file descriptor number, need it for the select function + if (sd > max_sd) + max_sd = sd; + } + // timeout + timeval tv{2, 0}; //t.tv_sec = 2; t.tv_usec = 0; + // Wait for an activity on one of the sockets. + int activity = select((int)max_sd + 1, &m_readfds, NULL, NULL, &tv); + + if ((activity < 0) && (errno != EINTR)) { + std::cout << "select error \n"; + } + // If something happened on the master socket, then its an incoming connection + if (FD_ISSET(m_masterSocket, &m_readfds)) { + type_socket new_socket; + if ((new_socket = accept(m_masterSocket, + (struct sockaddr *)&m_address, (socklen_t *)&m_addrlen)) < 0) { + perror("accept"); + exit(EXIT_FAILURE); + } + // Add new socket to array of sockets + for (int i = 0; i < MAX_CLIENTS; i++) { + // If position is free + if (m_clientSocket[i] == 0) { + m_clientSocket[i] = new_socket; + std::cout << "New connection - adding to list of sockets with id " << i << std::endl; + break; + } + } + } + // Else its some IO operation on some other socket + for (int i = 0; i < MAX_CLIENTS; i++) { + type_socket sd = m_clientSocket[i]; + + if (FD_ISSET(sd, &m_readfds)) { + // Check if it was for closing , and also read the + // incoming message +#ifdef _WIN32 + int valread = recv(sd, m_buffer, sizeof(m_buffer), 0); +#else + int valread = read(sd, m_buffer, sizeof(m_buffer)); +#endif + if (valread == 0) { + // Somebody disconnected, get his details and print + getpeername(sd, (struct sockaddr *)&m_address, + (socklen_t *)&m_addrlen); + // 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 + gotData = true; + m_buffer[valread] = '\0'; + socket = i; + data = m_buffer; + } + } + } + //} while (!gotData); + + return data; +} + +int TcpServer::sendResponse(int socketIndex, const std::string &message) +{ + type_socket sd = m_clientSocket[socketIndex]; + if (send(sd, message.c_str(), (int)message.length(), 0) != message.length()) { + printf("[TcpServer] Send failed\n"); + return 1; + } + // doCloseSocket(sd); + // m_clientSocket[socketIndex] = 0; + return 0; +} diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..cb68829 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,311 @@ +/* Copyright (C) 2022 The Qt Company Ltd. + * + * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 +*/ + +#include "utils.h" + +namespace utils +{ + +// Trim whitespaces from start of a string +void trimLeft(std::string &s) +{ + s.erase(s.begin(), std::find_if (s.begin(), s.end(), [](unsigned char ch) + { return !std::isspace(ch); })); +} + +// Trim whitespaces from end of a string +void trimRight(std::string &s) +{ + s.erase(std::find_if (s.rbegin(), s.rend(), [](unsigned char ch) + { return !std::isspace(ch); }) + .base(), + s.end()); +} + +// Trim whitespaces from both ends of a string +std::string trimStr(const std::string &s) +{ + std::string trimmed = s; + trimLeft(trimmed); + trimRight(trimmed); + + return trimmed; +} + +// Split a string by a char (Defaults to a space) +std::vector splitStr(const std::string &str, const char delimiter) +{ + std::vector strings; + std::istringstream ss(str); + std::string tmpStr; + while (getline(ss, tmpStr, delimiter)) { + tmpStr = trimStr(tmpStr); + strings.push_back(tmpStr); + } + return strings; +} + +// Convert string to lower case +std::string strToLower(const std::string &str) +{ + std::string ret = str; + std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower); + return ret; +} + +// Convert string to upper case +std::string strToUpper(const std::string &str) +{ + std::string ret = str; + std::transform(ret.begin(), ret.end(), ret.begin(), ::toupper); + return ret; +} + +// String to int: Because stoi() raises an exception upon failing +int strToInt(const std::string &str) +{ + int retVal = 0; + try { + retVal = stoi(str); + } + catch (...) { + printf("ERROR: Unable to convert '%s' to an int\n", str.c_str()); + } + return retVal; +} + +std::string getUserHomeDir() +{ + // Try to find current user's home + std::string retVal = ""; + const char* homedir; +#if __linux__ || __APPLE__ || __MACH__ + // Linux and Mac + homedir = std::getenv("HOME"); + if (homedir == nullptr) { + homedir = getpwuid(getuid())->pw_dir; + } +#else + // Windows + homedir = getenv("HOMEPATH"); + if (homedir != nullptr) { + retVal = SYSTEM_ROOT; + } +#endif + if (homedir == nullptr) { + printf("ERROR Not able to determine user's home directory\n"); + return "Not able to get homedir"; + } + retVal += std::string(homedir); + return retVal; +} + +// Write data to the file +int writeToFile(const std::string &filepath, const std::string &data, bool append) +{ + std::string path; + std::ofstream file; + // can't enable exception now because of gcc bug that raises ios_base::failure with useless message + if (append) { + file.open(filepath, std::ios::out | std::ios::app); + } else { + file.open(filepath, std::ios::out); + } + if (file.fail()) { + return -1; + } + try { + file << data << std::endl; + } catch (...) { + file.close(); + std::cout << "Failed to write data in file " << filepath << std::endl; + return -1; + } + file.close(); + return 0; +} + +int createDir(const std::string &filepath, uint16_t user, uint16_t group) +{ + std::vector pathVector = utils::splitStr(filepath, DIR_SEPARATOR); + std::string path; + for (std::string dir : pathVector) { + path += SYSTEM_ROOT; + path += dir; + if (!utils::fileExists(path)) { +#if __linux__ || __APPLE__ || __MACH__ + if (mkdir(path.c_str(), 0777) != 0) { + return -1; + } + chown(path.c_str(), user, group); +#else + + printf("WARNING: Unimplemented: createDir() for Windows\n"); +#endif + } + } + return 0; +} + +int readFile(std::string &str, const std::string &filepath) +{ + std::ifstream file; + std::stringstream ss; + + file.open(filepath, std::ios::in); + if (file.is_open()) { + std::string line; + while (getline(file, line)) { + ss << line; + } + file.close(); + } else { + return -1; + } + str = ss.str(); + return 0; +} + +bool fileExists(const std::string &name) +{ +#if __linux__ || __APPLE__ || __MACH__ + struct stat buffer; + return (stat (name.c_str(), &buffer) == 0); +#else + if (_access(name.c_str(), 0) == -1) { + std::cout << "File " << name << " doesn't exist\n"; + return false; // not accessible + } + return true; +#endif +} + +std::string getFileOwnerName(const std::string &filename) +{ + std::string retVal; +#if __linux__ || __APPLE__ || __MACH__ + struct stat info; + stat(filename.c_str(), &info); // Error check omitted + struct passwd *pw = getpwuid(info.st_uid); + if (pw != 0) retVal = pw->pw_name; // contains the user name + +#else + printf("WARNING! Uniplemented: Win version of getFileOwnerName()\n"); + retVal = "NULL"; +#endif + return retVal; +} + +int getFileOwner(const std::string &filename, uint16_t &owner, uint16_t &group) +{ +#if __linux__ || __APPLE__ || __MACH__ + struct stat info; + stat(filename.c_str(), &info); // Error check omitted + std::string name = getFileOwnerName(filename); + owner = info.st_uid; + group = info.st_gid; +#else + printf("WARNING! Uniplemented: Win version of getFileOwner()\n"); +#endif + return 0; +} + +std::vector getDirListing(const std::string &directory, const std::string &filter) +{ + /* Open directory stream */ + DIR *dir = opendir(directory.c_str()); + if (!dir) { + /* Could not open directory */ + std::cout << "Cannot open directory" << directory << std::endl;; + } + /* Gather files and directories within the directory */ + std::vector files; + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + if (ent->d_type == DT_REG) { + std::string name = ent->d_name; + if (name.substr(0, filter.length()) == filter || filter.empty()) { + files.push_back(name); + } + } + } + closedir(dir); + return files; +} + +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() +{ +#if _WIN32 + return "Windows"; +#elif __APPLE__ || __MACH__ + return "macOS"; +#elif __linux__ + return "Linux"; +#elif __FreeBSD__ + return "FreeBSD"; +#elif __unix || __unix__ + return "Unix"; +#else + return "Unknown OS"; +#endif +} + +#if _WIN32 +/* + * Windows implementation of missing POSIX strptime() +*/ +char* strptime(const char* s, + const char* f, + struct tm* tm) { + std::istringstream input(s); + input.imbue(std::locale(setlocale(LC_ALL, nullptr))); + input >> std::get_time(tm, f); + if (input.fail()) { + return nullptr; + } + return (char*)(s + input.tellg()); +} +#endif + +std::string epochToString(time_t epochTime, const char* format) +{ + char timestamp[64] = {0}; + strftime(timestamp, sizeof(timestamp), format, localtime(&epochTime)); + return timestamp; +} + +time_t stringToEpoch(const char* theTime, const char* format) +{ + std::tm tmTime; + memset(&tmTime, 0, sizeof(tmTime)); + strptime(theTime, format, &tmTime); + 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) +*/ + + +} // namespace utils diff --git a/tcpclient.cpp b/tcpclient.cpp deleted file mode 100644 index 1922b76..0000000 --- a/tcpclient.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* Copyright (C) 2022 The Qt Company Ltd. - * - * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 -*/ - -#include "tcpclient.h" - -TcpClient::TcpClient(const std::string &connAddr, uint16_t port) -{ -#ifdef _WIN32 - // Initialize Winsock - WSADATA wsaData; - int iResult = 0; - iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (iResult != NO_ERROR) { - wprintf(L"Error at WSAStartup()\n"); - exit(EXIT_FAILURE); - } - const int inetSuccess = inet_pton(AF_INET, connAddr.c_str(), &m_server.sin_addr.s_addr); -#else - const int inetSuccess = inet_aton(connAddr.c_str(), &m_server.sin_addr); -#endif - if (!inetSuccess) { - // Try to resolve hostname - struct hostent *host; - struct in_addr **addrList; - if ( (host = gethostbyname(connAddr.c_str()) ) == nullptr){ - std::cout << "Failed to resolve hostname\n"; - exit(EXIT_FAILURE); - } - addrList = (struct in_addr **) host->h_addr_list; - m_server.sin_addr = *addrList[0]; - } - m_server.sin_port = htons(port); -} - -int TcpClient::sendAndReceive(const std::string &message, std::string &reply) -{ - m_server.sin_family = AF_INET; - m_socketFD = (socket(AF_INET , SOCK_STREAM, 0)); - if (m_socketFD < 0) { - doCloseSocket(); - return e_tcp_fail_socket; - } - - // Connect to daemon - int connectR = connect(m_socketFD, (struct sockaddr *)&m_server, sizeof(m_server)); - if (connectR == -1) { - doCloseSocket(); - return e_tcp_error_conn; - } - const size_t bytesSent = send(m_socketFD, message.c_str(), (int)message.length(), 0); - - if (bytesSent < 0 ) { // send failed - doCloseSocket(); - return e_tcp_error_send; - } - - std::string resp(BUFFER_SIZE, ' '); - size_t bytes_recv; - try { - bytes_recv = recv(m_socketFD, &resp.front(), (int)resp.size(), 0); - } - catch (...) { - std::cout << "Error receiving" << std::endl; - doCloseSocket(); - return e_tcp_error_recv; - } - if (bytes_recv == -1) { - doCloseSocket(); - return e_tcp_error_recv; - } - doCloseSocket(); - reply = resp.substr(0, bytes_recv); // Had to cut it like this for windows - return e_tcp_success; -} diff --git a/tcpserver.cpp b/tcpserver.cpp deleted file mode 100644 index 5ee4e89..0000000 --- a/tcpserver.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/* Copyright (C) 2022 The Qt Company Ltd. - * - * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 -*/ - -#include "tcpserver.h" - -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; - } - -#ifdef _WIN32 - // Initialize Winsock - WSADATA wsaData; - int iResult = 0; - iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (iResult != NO_ERROR) { - wprintf(L"Error at WSAStartup()\n"); - exit(EXIT_FAILURE); - } -#endif - // Create a master socket - if ((m_masterSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == 0) { - 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"); - exit(EXIT_FAILURE); - } - timeval tv {2, 0}; //t.tv_sec = 2; t.tv_usec = 0; - int ret = setsockopt(m_masterSocket, SOL_SOCKET, SO_RCVTIMEO, - (char*)&tv, sizeof(timeval)); - if (ret < 0) { - printf("Socket timeout error code: %d\n", ret); - perror("Socket timeout failed"); - } - - // Type of socket created - m_address.sin_family = AF_INET; - m_address.sin_addr.s_addr = INADDR_ANY; - m_address.sin_port = htons(serverPort); - - // Bind the socket to localhost port 8888 - if (bind(m_masterSocket, (struct sockaddr *)&m_address, sizeof(m_address)) < 0) { - doCloseSocket(m_masterSocket); - perror("bind failed"); - exit(EXIT_FAILURE); - } - std::cout << "Listening to port " << serverPort << std::endl; - unsigned long mode = 1; - //ioctlsocket(m_masterSocket, FIONBIO, &mode); - // Try to specify maximum of 3 pending connections for the master socket - if (listen(m_masterSocket, 3) < 0) { - doCloseSocket(m_masterSocket); - perror("listen"); - exit(EXIT_FAILURE); - } - m_addrlen = sizeof(m_address); - - // Accept the incoming connection - std::cout << "Waiting for connections ...\n"; -} - -TcpServer::~TcpServer() -{ - doCloseSocket(m_masterSocket); - for (uint8_t i = 0; i < MAX_CLIENTS; i++) { - doCloseSocket(m_clientSocket[i]); - } - -} - - -std::string TcpServer::listenToClients(int &socket) -{ - std::string data; - bool gotData = false; - //do { - // Clear the socket set - FD_ZERO(&m_readfds); - - type_socket max_sd = m_masterSocket; - - // Add master socket to set - FD_SET(m_masterSocket, &m_readfds); - - // Add child sockets to set - for (int i = 0; i < MAX_CLIENTS; i++) { - // Socket descriptor - type_socket sd = m_clientSocket[i]; - - // If valid socket descriptor then add to read list - if (sd > 0) - FD_SET(sd, &m_readfds); - - // Highest file descriptor number, need it for the select function - if (sd > max_sd) - max_sd = sd; - } - // timeout - timeval tv{2, 0}; //t.tv_sec = 2; t.tv_usec = 0; - // Wait for an activity on one of the sockets. - int activity = select((int)max_sd + 1, &m_readfds, NULL, NULL, &tv); - - if ((activity < 0) && (errno != EINTR)) { - std::cout << "select error \n"; - } - // If something happened on the master socket, then its an incoming connection - if (FD_ISSET(m_masterSocket, &m_readfds)) { - type_socket new_socket; - if ((new_socket = accept(m_masterSocket, - (struct sockaddr *)&m_address, (socklen_t *)&m_addrlen)) < 0) { - perror("accept"); - exit(EXIT_FAILURE); - } - // Add new socket to array of sockets - for (int i = 0; i < MAX_CLIENTS; i++) { - // If position is free - if (m_clientSocket[i] == 0) { - m_clientSocket[i] = new_socket; - std::cout << "New connection - adding to list of sockets with id " << i << std::endl; - break; - } - } - } - // Else its some IO operation on some other socket - for (int i = 0; i < MAX_CLIENTS; i++) { - type_socket sd = m_clientSocket[i]; - - if (FD_ISSET(sd, &m_readfds)) { - // Check if it was for closing , and also read the - // incoming message -#ifdef _WIN32 - int valread = recv(sd, m_buffer, sizeof(m_buffer), 0); -#else - int valread = read(sd, m_buffer, sizeof(m_buffer)); -#endif - if (valread == 0) { - // 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; - } else { - // Set the string terminating NULL byte - // on the end of the data read - gotData = true; - m_buffer[valread] = '\0'; - socket = i; - data = m_buffer; - } - } - } - //} while (!gotData); - - return data; -} - -int TcpServer::sendResponse(int socketIndex, const std::string &message) -{ - type_socket sd = m_clientSocket[socketIndex]; - if (send(sd, message.c_str(), (int)message.length(), 0) != message.length()) { - printf("[TcpServer] Send failed\n"); - return 1; - } - doCloseSocket(sd); - m_clientSocket[socketIndex] = 0; - return 0; -} diff --git a/utils.cpp b/utils.cpp deleted file mode 100644 index 95c5b66..0000000 --- a/utils.cpp +++ /dev/null @@ -1,296 +0,0 @@ -/* Copyright (C) 2022 The Qt Company Ltd. - * - * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 -*/ - -#include "utils.h" -#include "commonsetup.h" - -namespace utils -{ - -// Trim whitespaces from start of a string -void trimLeft(std::string &s) -{ - s.erase(s.begin(), std::find_if (s.begin(), s.end(), [](unsigned char ch) - { return !std::isspace(ch); })); -} - -// Trim whitespaces from end of a string -void trimRight(std::string &s) -{ - s.erase(std::find_if (s.rbegin(), s.rend(), [](unsigned char ch) - { return !std::isspace(ch); }) - .base(), - s.end()); -} - -// Trim whitespaces from both ends of a string -std::string trimStr(const std::string &s) -{ - std::string trimmed = s; - trimLeft(trimmed); - trimRight(trimmed); - - return trimmed; -} - -// Split a string by a char (Defaults to a space) -std::vector splitStr(const std::string &str, const char delimiter) -{ - std::vector strings; - std::istringstream ss(str); - std::string tmpStr; - while (getline(ss, tmpStr, delimiter)) { - tmpStr = trimStr(tmpStr); - strings.push_back(tmpStr); - } - return strings; -} - -// Convert string to lower case -std::string strToLower(const std::string &str) -{ - std::string ret = str; - std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower); - return ret; -} - -// Convert string to upper case -std::string strToUpper(const std::string &str) -{ - std::string ret = str; - std::transform(ret.begin(), ret.end(), ret.begin(), ::toupper); - return ret; -} - -// String to int: Because stoi() raises an exception upon failing -int strToInt(const std::string &str) -{ - int retVal = 0; - try { - retVal = stoi(str); - } - catch (...) { - printf("ERROR: Unable to convert '%s' to an int\n", str.c_str()); - } - return retVal; -} - -std::string getUserHomeDir() -{ - // Try to find current user's home - std::string retVal = ""; - const char* homedir; -#if __linux__ || __APPLE__ || __MACH__ - // Linux and Mac - homedir = std::getenv("HOME"); - if (homedir == nullptr) { - homedir = getpwuid(getuid())->pw_dir; - } -#else - // Windows - homedir = getenv("HOMEPATH"); - if (homedir != nullptr) { - retVal = SYSTEM_ROOT; - } -#endif - if (homedir == nullptr) { - printf("ERROR Not able to determine user's home directory\n"); - return "Not able to get homedir"; - } - retVal += std::string(homedir); - return retVal; -} - -// Write data to the file -int writeToFile(const std::string &filepath, const std::string &data, bool append) -{ - std::string path; - std::ofstream file; - // can't enable exception now because of gcc bug that raises ios_base::failure with useless message - if (append) { - file.open(filepath, std::ios::out | std::ios::app); - } else { - file.open(filepath, std::ios::out); - } - if (file.fail()) { - return -1; - } - try { - file << data << std::endl; - } catch (...) { - file.close(); - std::cout << "Failed to write data in file " << filepath << std::endl; - return -1; - } - file.close(); - return 0; -} - -int createDir(const std::string &filepath, uint16_t user, uint16_t group) -{ - std::vector pathVector = utils::splitStr(filepath, DIR_SEPARATOR); - std::string path; - for (std::string dir : pathVector) { - path += SYSTEM_ROOT; - path += dir; - if (!utils::fileExists(path)) { -#if __linux__ || __APPLE__ || __MACH__ - if (mkdir(path.c_str(), 0777) != 0) { - return -1; - } - chown(path.c_str(), user, group); -#else - - printf("WARNING: Unimplemented: createDir() for Windows\n"); -#endif - } - } - return 0; -} - -int readFile(std::string &str, const std::string &filepath) -{ - std::ifstream file; - std::stringstream ss; - - file.open(filepath, std::ios::in); - if (file.is_open()) { - std::string line; - while (getline(file, line)) { - ss << line; - } - file.close(); - } else { - return -1; - } - str = ss.str(); - return 0; -} - -bool fileExists(const std::string &name) -{ -#if __linux__ || __APPLE__ || __MACH__ - struct stat buffer; - return (stat (name.c_str(), &buffer) == 0); -#else - if (_access(name.c_str(), 0) == -1) { - std::cout << "File " << name << " doesn't exist\n"; - return false; // not accessible - } - return true; -#endif -} - -std::string getFileOwnerName(const std::string &filename) -{ - std::string retVal; -#if __linux__ || __APPLE__ || __MACH__ - struct stat info; - stat(filename.c_str(), &info); // Error check omitted - struct passwd *pw = getpwuid(info.st_uid); - if (pw != 0) retVal = pw->pw_name; // contains the user name - -#else - printf("WARNING! Uniplemented: Win version of getFileOwnerName()\n"); - retVal = "NULL"; -#endif - return retVal; -} - -int getFileOwner(const std::string &filename, uint16_t &owner, uint16_t &group) -{ -#if __linux__ || __APPLE__ || __MACH__ - struct stat info; - stat(filename.c_str(), &info); // Error check omitted - std::string name = getFileOwnerName(filename); - owner = info.st_uid; - group = info.st_gid; -#else - printf("WARNING! Uniplemented: Win version of getFileOwner()\n"); -#endif - return 0; -} - -std::vector getDirListing(const std::string &directory, const std::string &filter) -{ - /* Open directory stream */ - DIR *dir = opendir(directory.c_str()); - if (!dir) { - /* Could not open directory */ - std::cout << "Cannot open directory" << directory << std::endl;; - } - /* Gather files and directories within the directory */ - std::vector files; - struct dirent *ent; - while ((ent = readdir(dir)) != NULL) { - if (ent->d_type == DT_REG) { - std::string name = ent->d_name; - if (name.substr(0, filter.length()) == filter || filter.empty()) { - files.push_back(name); - } - } - } - closedir(dir); - return files; -} - -#include - - -std::string getOsName() -{ -#if _WIN32 - return "Windows"; -#elif __APPLE__ || __MACH__ - return "macOS"; -#elif __linux__ - return "Linux"; -#elif __FreeBSD__ - return "FreeBSD"; -#elif __unix || __unix__ - return "Unix"; -#else - return "Unknown OS"; -#endif -} - -#if _WIN32 -/* - * Windows implementation of missing POSIX strptime() -*/ -char* strptime(const char* s, - const char* f, - struct tm* tm) { - std::istringstream input(s); - input.imbue(std::locale(setlocale(LC_ALL, nullptr))); - input >> std::get_time(tm, f); - if (input.fail()) { - return nullptr; - } - return (char*)(s + input.tellg()); -} -#endif - -std::string epochToString(time_t epochTime, const char* format) -{ - char timestamp[64] = {0}; - strftime(timestamp, sizeof(timestamp), format, localtime(&epochTime)); - return timestamp; -} - -time_t stringToEpoch(const char* theTime, const char* format) -{ - std::tm tmTime; - memset(&tmTime, 0, sizeof(tmTime)); - strptime(theTime, format, &tmTime); - return mktime(&tmTime); -} - -/* -* App-specific utils here (not generic) -*/ - - -} // namespace utils -- cgit v1.2.3