[impl] Support for online activation

This commit is contained in:
Peter Sykora 2021-11-15 00:10:53 +01:00
parent 564dda8539
commit c27d732655
27 changed files with 542 additions and 23 deletions

View File

@ -1,4 +1,4 @@
# CMakeList.txt : CMake project for CMakeProject1, include source and define # CMakeList.txt : CMake project for CCEngineServer, include source and define
# project specific logic here. # project specific logic here.
# #
cmake_minimum_required (VERSION 3.8) cmake_minimum_required (VERSION 3.8)
@ -30,8 +30,19 @@ else(WIN32)
endif() endif()
# Add source to this project's executable. # Add source to this project's executable.
add_executable (CMakeProject1 "CCServer.cpp" "CCServer.h" "CryptoUtils.cpp" "CryptoUtils.h" "CMakeProject1.cpp" "CMakeProject1.h" "HashUtils.cpp" "HashUtils.h" "JSONSerialization.cpp" "JSONSerialization.h" "LicenseClient.cpp" "LicenseClient.h" "SystemParams.cpp" "SystemParams.h" "SystemParamsProvider.h" "SystemParamsProvider_win.cpp" "SystemParamsProvider_win.h" "SystemParamsProvider_linux.cpp" "SystemParamsProvider_win.cpp" "OSUtils.h" "OSUtils_win.cpp") add_executable (
CCEngineServer
"CCServer.cpp" "CCServer.h"
"CryptoUtils.cpp" "CryptoUtils.h"
"HashUtils.cpp" "HashUtils.h"
"HTTPClient.cpp" "HTTPClient.h"
"JSONSerialization.cpp" "JSONSerialization.h"
"LicenseClient.cpp" "LicenseClient.h"
"SystemParams.cpp" "SystemParams.h"
"SystemParamsProvider.h" "SystemParamsProvider_win.cpp" "SystemParamsProvider_win.h" "SystemParamsProvider_linux.cpp" "SystemParamsProvider_win.cpp"
"OSUtils.h" "OSUtils_win.cpp"
"main.cpp" "version.h")
target_link_libraries(CMakeProject1 ${CONAN_LIBS}) target_link_libraries(CCEngineServer ${CONAN_LIBS})
# TODO: Add tests and install targets if needed. # TODO: Add tests and install targets if needed.

View File

@ -0,0 +1,321 @@
#include "HTTPClient.h"
struct IUnknown; // Workaround for "combaseapi.h(229): error C2187: syntax error: 'identifier' was unexpected here" when using /permissive-
#include <curl/curl.h>
#include <memory>
#include <sstream>
#include <iostream>
namespace
{
static
void dump(const char *text,
FILE *stream, unsigned char *ptr, size_t size)
{
unsigned int width = 0x10;
fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n",
text, (long)size, (long)size);
fwrite(ptr, 1, size, stream);
fputc('\n', stream); // newline
}
static
int my_trace(CURL *handle, curl_infotype type,
char *data, size_t size,
void *userp)
{
const char *text;
(void)handle; /* prevent compiler warning */
(void)userp;
switch (type) {
case CURLINFO_TEXT:
fprintf(stderr, "== Info: %s", data);
default: /* in case a new one is introduced to shock us */
return 0;
case CURLINFO_HEADER_OUT:
text = "=> Send header";
break;
case CURLINFO_DATA_OUT:
text = "=> Send data";
break;
case CURLINFO_SSL_DATA_OUT:
text = "=> Send SSL data";
break;
case CURLINFO_HEADER_IN:
text = "<= Recv header";
break;
case CURLINFO_DATA_IN:
text = "<= Recv data";
break;
case CURLINFO_SSL_DATA_IN:
text = "<= Recv SSL data";
break;
}
dump(text, stderr, (unsigned char *)data, size);
return 0;
}
static size_t reader(char *ptr, size_t size, size_t nmemb, std::istream *is)
{
std::streamsize totalRead = 0;
if (*is)
{
is->read(&ptr[totalRead], size * nmemb - totalRead);
// is->read(&ptr[totalRead], 1);
totalRead = is->gcount();
}
return static_cast<size_t>(totalRead);
}
static size_t writer(char *data, size_t size, size_t nmemb, std::ostream *os)
{
if (os == NULL)
return 0;
os->write(data, size*nmemb);
return size * nmemb;
}
struct CURLDeleter
{
void operator() (CURL* ptr)
{
if (ptr)
{
curl_easy_cleanup(ptr);
}
}
};
struct curl_slist_deleter
{
void operator() (curl_slist* ptr)
{
if (ptr)
{
curl_slist_free_all(ptr);
}
}
};
struct DownloadSession
{
char errorBuffer[CURL_ERROR_SIZE] = { 0 };
std::unique_ptr<CURL, CURLDeleter> conn;
std::unique_ptr<curl_slist, curl_slist_deleter> extraHeaders;
};
DownloadSession initCurlRequest(const std::string& url, std::ostream &os)
{
DownloadSession result;
CURLcode code;
std::unique_ptr<CURL, CURLDeleter> conn(curl_easy_init());
if (!conn)
{
throw std::runtime_error("Failed to create CURL connection");
}
code = curl_easy_setopt(conn.get(), CURLOPT_ERRORBUFFER, result.errorBuffer);
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set error buffer [" << code << "]";
throw std::runtime_error(oss.str());
}
const char* errorBuffer = result.errorBuffer;
code = curl_easy_setopt(conn.get(), CURLOPT_URL, url.c_str());
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set URL [" << errorBuffer << "]";
throw std::runtime_error(oss.str());
}
code = curl_easy_setopt(conn.get(), CURLOPT_FOLLOWLOCATION, 1L);
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set redirect option [" << errorBuffer << "]";
throw std::runtime_error(oss.str());
}
code = curl_easy_setopt(conn.get(), CURLOPT_MAXREDIRS, 5L);
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set maximum number of redirects [" << errorBuffer << "]";
throw std::runtime_error(oss.str());
}
code = curl_easy_setopt(conn.get(), CURLOPT_WRITEFUNCTION, writer);
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set write function [" << errorBuffer << "]";
throw std::runtime_error(oss.str());
}
code = curl_easy_setopt(conn.get(), CURLOPT_WRITEDATA, &os);
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set write data [" << errorBuffer << "]";
throw std::runtime_error(oss.str());
}
#ifndef NDEBUG
code = curl_easy_setopt(conn.get(), CURLOPT_DEBUGFUNCTION, my_trace);
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set debug function";
throw std::runtime_error(oss.str());
}
code = curl_easy_setopt(conn.get(), CURLOPT_VERBOSE, 1);
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set verbose mode";
throw std::runtime_error(oss.str());
}
#endif // !NDEBUG
result.conn = std::move(conn);
return result;
}
DownloadSession initCurlPostJsonRequest(const std::string& url, std::istream& is, size_t length, std::ostream &os)
{
DownloadSession result = initCurlRequest(url, os);
CURLcode code;
auto contentLengthHeader = std::string("Content-Length: ") + std::to_string(length);
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Accept: application/json");
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, contentLengthHeader.c_str());
headers = curl_slist_append(headers, "Charsets: utf-8");
result.extraHeaders.reset(headers);
code = curl_easy_setopt(result.conn.get(), CURLOPT_POST, 1L);
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set post option [" << result.errorBuffer << "]";
throw std::runtime_error(oss.str());
}
code = curl_easy_setopt(result.conn.get(), CURLOPT_HTTPHEADER, result.extraHeaders.get());
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set http headers [" << result.errorBuffer << "]";
throw std::runtime_error(oss.str());
}
code = curl_easy_setopt(result.conn.get(), CURLOPT_READFUNCTION, reader);
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set read function [" << result.errorBuffer << "]";
throw std::runtime_error(oss.str());
}
code = curl_easy_setopt(result.conn.get(), CURLOPT_READDATA, &is);
if (code != CURLE_OK)
{
std::ostringstream oss;
oss << "Failed to set read data [" << result.errorBuffer << "]";
throw std::runtime_error(oss.str());
}
return result;
}
} // anonymous namespace
HTTPClient::HTTPClient()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
}
void HTTPClient::get(const std::string& url, std::ostream& dstStream)
{
auto session = initCurlRequest(url, dstStream);
auto code = curl_easy_perform(session.conn.get());
if (code != CURLE_OK)
{
session.conn.release();
if (code == CURLE_COULDNT_CONNECT)
{
throw CouldNotConnectException();
}
std::ostringstream oss;
oss << "Failed to get '" << url << "' [" << session.errorBuffer << "]";
throw std::runtime_error(oss.str());
}
else
{
long http_code = 0;
curl_easy_getinfo(session.conn.get(), CURLINFO_RESPONSE_CODE, &http_code);
if (http_code >= 400)
{
std::ostringstream oss;
oss << "Failed to get '" << url << "' [HTTP code: " << http_code << "]";
throw std::runtime_error(oss.str());
}
}
}
void HTTPClient::postJson(const std::string& url, std::istream& json, size_t length, std::ostream& dstStream)
{
auto session = initCurlPostJsonRequest(url, json, length, dstStream);
auto code = curl_easy_perform(session.conn.get());
if (code != CURLE_OK)
{
session.conn.release();
if (code == CURLE_COULDNT_CONNECT)
{
throw CouldNotConnectException();
}
std::ostringstream oss;
oss << "Failed to post '" << url << "' [" << session.errorBuffer << "]";
throw std::runtime_error(oss.str());
}
else
{
long http_code = 0;
curl_easy_getinfo(session.conn.get(), CURLINFO_RESPONSE_CODE, &http_code);
if (http_code >= 400)
{
std::ostringstream oss;
oss << "Failed to get '" << url << "' [HTTP code: " << http_code << "]";
throw std::runtime_error(oss.str());
}
}
}
void HTTPClient::postJson(const std::string& url, const std::string& json, std::ostream& dstStream)
{
std::istringstream is(json);
return postJson(url, is, json.size(), dstStream);
}

View File

@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <ostream>
class CouldNotConnectException : public std::exception
{
public:
CouldNotConnectException()
{}
virtual ~CouldNotConnectException() throw () {}
};
class HTTPClient
{
public:
HTTPClient();
public:
void get(const std::string& url, std::ostream& dstStream);
void postJson(const std::string& url, std::istream& json, size_t length, std::ostream& dstStream);
void postJson(const std::string& url, const std::string& json, std::ostream& dstStream);
};

View File

@ -58,6 +58,7 @@ namespace
{ {
static const std::string productId = "cocserver"; static const std::string productId = "cocserver";
static const std::string serverPath = "https://license.apis.sk/api/v1";
static const uint32_t initializationVectorSize = AES::BLOCKSIZE; static const uint32_t initializationVectorSize = AES::BLOCKSIZE;
static const uint32_t macTagSize = 16; static const uint32_t macTagSize = 16;
@ -228,7 +229,15 @@ void serialize(std::ostream& os, const ActivationRequest& a)
void deserialize(const pt::ptree& tree, ActivationResponse& a) void deserialize(const pt::ptree& tree, ActivationResponse& a)
{ {
deserialize(tree.get_child("success"), a.success); auto successNode = tree.get_child_optional("success");
if (successNode)
{
deserialize(successNode.value(), a.success);
}
else
{
a.success = false;
}
auto licenseFileOpt = tree.get_child_optional("licenseFile"); auto licenseFileOpt = tree.get_child_optional("licenseFile");
if (licenseFileOpt) if (licenseFileOpt)
{ {
@ -277,7 +286,7 @@ void LicenseClient::init()
loadActivationData(); loadActivationData();
} }
std::string LicenseClient::buildActivationRequest() std::string LicenseClient::buildOfflineActivationRequest()
{ {
PreactivationRequest req{ productId, m_systemParams }; PreactivationRequest req{ productId, m_systemParams };
@ -324,6 +333,74 @@ std::string LicenseClient::buildActivationRequest()
std::string("-----END ACTIVATION REQUEST-----\n"); std::string("-----END ACTIVATION REQUEST-----\n");
} }
bool LicenseClient::tryPreactivate(HTTPClient &httpClient)
{
PreactivationRequest req{ productId, m_systemParams };
std::string jsonReq;
{
std::ostringstream ss1;
serialize(ss1, req);
jsonReq = ss1.str();
}
std::string jsonRes;
{
std::ostringstream ss2;
httpClient.postJson(serverPath + "/activate0", jsonReq, ss2);
jsonRes = ss2.str();
}
ActivationResponse activationResponse;
pt::ptree root;
std::istringstream ss2(jsonRes);
pt::read_json(ss2, root);
deserialize(root, activationResponse);
if (activationResponse.success)
{
{
const auto& licenseData = activationResponse.licenseFile.value();
std::ofstream os(m_licenseFile, std::ofstream::binary);
os.write(licenseData.data(), licenseData.size());
}
return loadActivationData();
}
return false;
}
bool LicenseClient::activate(HTTPClient &httpClient, const std::string & licenseNumber)
{
auto validLicenseKey = validateLicenseKey(licenseNumber);
if (!validLicenseKey)
{
return false;
}
std::ostringstream ss1;
ActivationRequest req{ productId, m_systemParams, validLicenseKey.value() };
serialize(ss1, req);
std::stringstream ss2;
httpClient.postJson(serverPath + "/activate", ss1.str() , ss2);
ActivationResponse activationResponse;
pt::ptree root;
pt::read_json(ss2, root);
deserialize(root, activationResponse);
if (activationResponse.success)
{
{
const auto& licenseData = activationResponse.licenseFile.value();
std::ofstream os(m_licenseFile, std::ofstream::binary);
os.write(licenseData.data(), licenseData.size());
}
return loadActivationData();
}
return false;
}
bool LicenseClient::loadActivationData() bool LicenseClient::loadActivationData()
{ {
if (fs::is_regular_file(m_licenseFile)) if (fs::is_regular_file(m_licenseFile))

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "HTTPClient.h"
#include "SystemParamsProvider.h" #include "SystemParamsProvider.h"
#include <optional> #include <optional>
@ -25,9 +26,11 @@ public:
public: public:
void init(); void init();
std::string buildActivationRequest(); std::string buildOfflineActivationRequest();
bool isActivated() const { return m_activationData.has_value(); } bool isActivated() const { return m_activationData.has_value(); }
std::optional<std::string> activationNumber() const { return m_activationData.has_value() ? m_activationData->activationId : std::optional<std::string>(); } std::optional<std::string> activationNumber() const { return m_activationData.has_value() ? m_activationData->activationId : std::optional<std::string>(); }
bool tryPreactivate(HTTPClient &httpClient);
bool activate(HTTPClient &httpClient, const std::string& licenseNumber );
auto licensedModules() { if (!isActivated()) { throw std::runtime_error("Not active"); } return m_activationData->licensedModules; } auto licensedModules() { if (!isActivated()) { throw std::runtime_error("Not active"); } return m_activationData->licensedModules; }
private: private:

View File

@ -1,6 +1,7 @@
[requires] [requires]
boost/1.76.0 boost/1.77.0
cryptopp/8.5.0 cryptopp/8.5.0
libcurl/7.79.1
[generators] [generators]
cmake cmake
@ -8,3 +9,5 @@ cmake
[options] [options]
boost:shared=False boost:shared=False
cryptopp:shared=False cryptopp:shared=False
libcurl:shared=False
libcurl:with_ssl=openssl

View File

@ -2,6 +2,7 @@
#include "LicenseClient.h" #include "LicenseClient.h"
#include "SystemParamsProvider.h" #include "SystemParamsProvider.h"
#include "version.h"
#include <boost/log/core.hpp> #include <boost/log/core.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
@ -26,7 +27,7 @@ namespace keywords = boost::log::keywords;
volatile bool signalCaught = false; volatile bool signalCaught = false;
void signalHandler(int signum) { void signalHandler(int signum) {
std::cout << "Interrupt signal (" << signum << ") received.\n"; BOOST_LOG_TRIVIAL(info) << "Interrupt signal (" << signum << ") received.";
signalCaught = true; signalCaught = true;
} }
@ -60,6 +61,13 @@ int main(int argc, const char* argv[])
po::options_description generalOptions("General options"); po::options_description generalOptions("General options");
generalOptions.add_options() generalOptions.add_options()
("help", "produce help message") ("help", "produce help message")
("version", "print application version")
("activate_online", "activate online")
("activate_offline", "start offline activation process")
#ifndef WIN32
("set_uid", po::value<uid_t>()->default_value(0), "Switch UID after successfull startup")
("set_gid", po::value<gid_t>()->default_value(0), "Switch GID after successfull startup")
#endif
("bind_ip", po::value<std::string>()->default_value("0.0.0.0"), "ip address to listen for connections") ("bind_ip", po::value<std::string>()->default_value("0.0.0.0"), "ip address to listen for connections")
("port", po::value<uint16_t>()->default_value(8080), "port") ("port", po::value<uint16_t>()->default_value(8080), "port")
("data_root", po::value<std::string>()->default_value("."), "root directory with data") ("data_root", po::value<std::string>()->default_value("."), "root directory with data")
@ -80,7 +88,14 @@ int main(int argc, const char* argv[])
po::notify(vm); po::notify(vm);
if (vm.count("help")) { if (vm.count("help")) {
std::cout << generalOptions << "\n"; std::cout << "CCEngineServer - Catalogue of Currencies serving solution" << std::endl;
std::cout << COPYRIGHT_NOTICE << std::endl << std::endl;
std::cout << generalOptions << std::endl;
return 1;
}
if (vm.count("version")) {
std::cout << VERSION << std::endl;
return 1; return 1;
} }
@ -97,11 +112,82 @@ int main(int argc, const char* argv[])
LicenseClient licenseClient(systemParamsProvider, "license.dat"); LicenseClient licenseClient(systemParamsProvider, "license.dat");
licenseClient.init(); licenseClient.init();
#ifndef WIN32
auto gidToSet = vm["set_gid"].as<gid_t>();
if (gidToSet > 0 && setgid(gidToSet) != 0) {
BOOST_LOG_TRIVIAL(error) << "Failed to set group id: " << gidToSet;
return -11;
}
auto uidToSet = vm["set_uid"].as<uid_t>();
if (uidToSet > 0 && setuid(uidToSet) != 0) {
BOOST_LOG_TRIVIAL(error) << "Failed to set user id: " << uidToSet;
return -10;
}
#endif
if (vm.count("activate_online")) {
HTTPClient httpClient;
try {
if (licenseClient.tryPreactivate(httpClient)) {
std::cout << "Pre-activation successfull. Please go ahead with normal application start" << std::endl;
return 3;
}
}
catch (CouldNotConnectException& e){
BOOST_LOG_TRIVIAL(error) << "Connection to server failed. Please check your internet"
<< "connection.If the problem persists, please contact customer support.";
BOOST_LOG_TRIVIAL(error) << e.what();
return -5;
}
catch (std::runtime_error &e) {
BOOST_LOG_TRIVIAL(error) << "Pre-activate failed. Please retry and if the problem persists, please contact customer support.";
BOOST_LOG_TRIVIAL(debug) << e.what();
return -6;
}
BOOST_LOG_TRIVIAL(info) << "Automatic pre-activation failed. Requesting license number...";
std::string licenseNumber;
std::cout << "Please enter license number (format XXXX-XXXX-XXXX-XXXX-XXXX-XXXX):" << std::endl;
std::getline(std::cin, licenseNumber);
std::cout << "Sending activation request to server..." << std::endl;
try {
if (!licenseClient.activate(httpClient, licenseNumber)) {
std::cerr << "Activation failed" << std::endl;
return -2;
}
}
catch (CouldNotConnectException& e) {
BOOST_LOG_TRIVIAL(error) << "Connection to server failed. Please check your internet"
<< "connection.If the problem persists, please contact customer support.";
BOOST_LOG_TRIVIAL(error) << e.what();
return -3;
}
catch (std::runtime_error& e) {
BOOST_LOG_TRIVIAL(error) << "Activation failed. Please retry and if the problem persists, please contact customer support.";
BOOST_LOG_TRIVIAL(debug) << e.what();
return -4;
}
std::cout << "Activation successfull. Please go ahead with normal application start" << std::endl;
return 2;
}
if (vm.count("activate_offline")) {
BOOST_LOG_TRIVIAL(debug) << "Offline activation was requested";
std::cout << "Offline activation was requested";
std::cout << "Copy following activation request and refer to the manual: " << std::endl
<< licenseClient.buildOfflineActivationRequest() << std::endl;
return 3;
}
if (!licenseClient.isActivated()) { if (!licenseClient.isActivated()) {
const std::string msg = "Application is not activated. Please contact Catalogue of Currencies support."; BOOST_LOG_TRIVIAL(warning) << "Application is not activated. Please contact customer support.";
BOOST_LOG_TRIVIAL(error) << msg << " Exiting..."; BOOST_LOG_TRIVIAL(info) << "Exiting...";
std::cout << msg << std::endl;
std::cout << "Activation request: " << std::endl << licenseClient.buildActivationRequest() << std::endl;
return -2; return -2;
} }

2
CCEngineServer/version.h Normal file
View File

@ -0,0 +1,2 @@
#define VERSION "21.45"
#define COPYRIGHT_NOTICE "©2021 Apis spol. s r.o."

View File

@ -7,11 +7,11 @@ if (${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Windows")
set (CMAKE_SYSTEM_VERSION 8.1 CACHE TYPE INTERNAL FORCE) #Force 8.1 SDK, to keep it compatible with win7 set (CMAKE_SYSTEM_VERSION 8.1 CACHE TYPE INTERNAL FORCE) #Force 8.1 SDK, to keep it compatible with win7
endif() endif()
project ("CMakeProject1") project ("CCEngineServer")
IF(WIN32) IF(WIN32)
ADD_DEFINITIONS(/bigobj) ADD_DEFINITIONS(/bigobj)
ENDIF(WIN32) ENDIF(WIN32)
# Include sub-projects. # Include sub-projects.
add_subdirectory ("CMakeProject1") add_subdirectory ("CCEngineServer")

View File

@ -1,8 +0,0 @@
// CMakeProject1.h : Include file for standard system include files,
// or project specific include files.
#pragma once
#include <iostream>
// TODO: Reference additional headers your program requires here.