/* Copyright (C) 2023 The Qt Company Ltd. * * SPDX-License-Identifier: GPL-3.0-only WITH Qt-GPL-exception-1.0 */ #include "licenser.h" #include "commonsetup.h" #include "clienthandlerfactory.h" #include "clitoolhandler.h" #include "version.h" #include "utils.h" #include "errors.h" namespace QLicenseService { Licenser::Licenser(uint16_t tcpPort, const std::string &settingsPath) { // Init daemon settings m_settings = new LicdSetup(e_set_type_daemon, settingsPath.empty() ? DAEMON_SETTINGS_FILE : settingsPath); m_settings->initSettings(); if (m_settings->get("license_key").empty()) { std::cout << "Warning: No license key provided in qtlicd.ini, required for cloud operation\n"; } if (tcpPort == 0) { tcpPort = utils::strToInt(m_settings->get("tcp_listening_port")); } m_mocInterval = utils::strToInt(m_settings->get("moc_renewal_interval")) * SECS_IN_HOUR; // Start the HTTP client m_http = new HttpClient(m_settings->get("server_addr")); // Start the TCP/IP server m_tcpServer = new TcpServer(tcpPort); if (!m_tcpServer->init()) { throw Error("Error while initializing TCP server in port: " + std::to_string(tcpPort)); } } Licenser::~Licenser() { delete(m_settings); delete(m_tcpServer); delete(m_http); std::cout << "Daemon stopped." << std::endl; } int Licenser::listen() { m_infoString = ""; uint16_t socketId = 0; // Placeholder for whatever socket gets active std::string input; if (!m_tcpServer->listenToClients(socketId, &input)) throw QLicenseService::Error("Error while listening to clients!"); input = utils::trimStr(input); if (input.empty()) { if (m_floatingClients.size() > 0) { checkTasksDue(); } return 0; //continue; } if (input == SOCKET_CLOSED_MSG) { std::cout << "Client disconnected, socket id " << socketId << std::endl; removeReservation(socketId); return 0; } std::cout << "Got a request: " << input << std::endl; // Check if we already have this client in storage (floating one): if (clientInStorage(socketId)) { // do nothing - floating clients should only send request at startup std::cout << "Floating client with ID " << socketId << " sent something while working: ignoring\n"; m_tcpServer->sendResponse(socketId, "NOK\n"); return 0; } std::string reply = ""; // Holds server response first (if got any). After parsing, holds reply to the client bool removeRes = false; std::unique_ptr currentClient(parseInputAndCreateCLient(socketId, input)); if (currentClient) { if (currentClient->parseRequest() != e_bad_request) { RequestType reqType = currentClient->getRequestType(); if (reqType == RequestType::reservation_query) { reply = checkReservations(); } else if (reqType == RequestType::daemon_version) { reply = getDaemonVersion(); } else { if (currentClient->isLicenseRequestDue()) { std::cout << "Initiating a request to the server\n"; if (sendServerRequest(currentClient->getRequest(), reply) == e_bad_connection) { // Bad server connection: Check the existing license file for expiry date if (!currentClient->isCachedReservationValid(reply)) { reply = replyString[e_bad_connection]; removeRes = true; } } else if (currentClient->parseAndSaveResponse(reply) != 0) { removeRes = true; } } else { // No need to contact server reply = replyString[e_license_granted]; } } } else { reply = replyString[e_bad_request]; removeRes = true; } } else { reply = replyString[e_bad_request]; removeRes = true; } if (currentClient->hasFloatingLicense() && !clientInStorage(socketId) && !removeRes) { // Store QA tool, Coco or Squish client (floating license) if not already stored if (currentClient->hasParent()) { if (!addClientToParent(currentClient.release())) { reply = replyString[e_license_rejected]; m_infoString = " Parent reservation not found"; } } else { std::cout << "Storing client in main cache\n"; m_floatingClients[socketId] = currentClient.release(); } } if (!m_infoString.empty()) { reply += ": "; reply += m_infoString; } reply +="\n"; m_tcpServer->sendResponse(socketId, reply); std::cout << "Replied to socket " << socketId << ": " << reply << std::endl; if (removeRes) { // No license, remove reservation from cache (floating license) // or license file (site license) removeReservation(socketId); return 0; } if (m_floatingClients.size() > 0) { checkTasksDue(); } return 0; } int Licenser::checkTasksDue() { // Check if there's any floating clients which has tasks to be done for (auto entry : m_floatingClients) { // First check if (parent) client has stopped and no children left if (entry.second->stopped() && !entry.second->hasChildren()) { removeReservation(entry.first); return 0; } // Then check if there is any server pings due if (entry.second->isLicenseRequestDue()) { std::string reply; if (sendServerRequest(entry.second->getRequest(), reply) != e_bad_connection) { std::cout << "Reported alive floating reservation with socket ID " << entry.first << std::endl; std::cout << "Client has " << entry.second->getNumberOfClients() << " sub-processes running\n"; entry.second->resetTime(); } } } return 0; } ClientHandler *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 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 == "-a" && paramList.size() > i) { client = paramList[i+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; } ClientHandler *currentClient = ClientHandlerFactory::instance().create(client, request, *m_settings); if (!currentClient) { std::cout << "Warning: Client undefined!" << std::endl; return nullptr; } currentClient->params = paramList; return currentClient; } int Licenser::sendServerRequest(const RequestInfo &request, std::string &reply) { std::string auth; // Generate auth hash for HTTP headers std::string hash; utils::doHmacHashSha256(request.payload, m_settings->get("server_secret"), hash); // For the HTTP 'Authorization' field auth = "apikey " + hash; // Send the request if (m_http->sendAndReceive(reply, request.payload, request.accessPoint, request.serverAddr, auth) != 0) { return e_bad_connection; } return e_got_response; } 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(); } bool Licenser::addClientToParent(ClientHandler *const client) { bool found = false; for (auto parent : m_floatingClients) { if (parent.second->getReservationId() == client->getParentReservationId()) { parent.second->addChildClient(client); found = true; std::cout << "Stored client under reservation ID " << client->getParentReservationId() << std::endl; break; } } if (!found) { std::cout << "Warning! Not able to add current client under reservation ID " << client->getParentReservationId() << std::endl; return false; } return true; } void Licenser::removeReservation(int socketId) { // Close the socket m_tcpServer->closeClientSocket(socketId); // Check if the client had a floating license, or is a child of one. // Need to loop through all of them because socketId can be under any parent for (auto element : m_floatingClients) { ClientHandler* client = element.second; if (element.first == socketId) { if (!client->stopped()) { client->prepareRelease(); // Don't remove parent yet, it might have child processes left } if (!client->hasChildren()) { // If client has no running children left, we can release the reservation and remove client std::string reply; client->release(); client->buildRequestJson(); if (sendServerRequest(client->getRequest(), reply) != e_bad_connection) { // if connection succeeds, we can forget about this client std::cout << "Removing floating reservation with ID " << client->getReservationId() << std::endl; m_floatingClients.erase(socketId); delete client; } } return; } else if (client->removeChildClient(socketId)) { std::cout << "Removed child client under reservation ID " << client->getReservationId() << std::endl; return; } } return; } bool Licenser::clientInStorage(uint16_t socketId) { for (auto client : m_floatingClients) { if (client.first == socketId) { return true; } if (client.second->hasChildren()) { if (client.second->clientInStorage(socketId)) { return true; } } } return false; } std::string Licenser::getDaemonVersion() { std::string version = "Qt License Daemon (qtlicd) v"; version += DAEMON_VERSION; return version; } } // namespace QLicenseService