From c1987ceb38db2d8dd4794ad5077e31b3b1108761 Mon Sep 17 00:00:00 2001 From: Mudit Vats Date: Mon, 1 Jun 2020 13:22:04 -0700 Subject: [PATCH] Initial commit. --- .gitignore | 11 + AMTHIClient/Include/GetControlModeCommand.h | 76 +++ AMTHIClient/Src/GetControlModeCommand.cpp | 51 ++ CMakeLists.txt | 131 +++++ README.md | 67 ++- commands.cpp | 499 ++++++++++++++++++++ commands.h | 48 ++ lms.cmake.in | 15 + lms.cpp | 88 ++++ lms.h | 44 ++ main.cpp | 480 +++++++++++++++++++ version.h.in | 10 + 12 files changed, 1519 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 AMTHIClient/Include/GetControlModeCommand.h create mode 100644 AMTHIClient/Src/GetControlModeCommand.cpp create mode 100644 CMakeLists.txt create mode 100644 commands.cpp create mode 100644 commands.h create mode 100644 lms.cmake.in create mode 100644 lms.cpp create mode 100644 lms.h create mode 100644 main.cpp create mode 100644 version.h.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b782ad2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +build/ +.vscode/* \ No newline at end of file diff --git a/AMTHIClient/Include/GetControlModeCommand.h b/AMTHIClient/Include/GetControlModeCommand.h new file mode 100644 index 0000000..d2caefd --- /dev/null +++ b/AMTHIClient/Include/GetControlModeCommand.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* + * Copyright (C) 2010-2019 Intel Corporation + */ +/*++ + +@file: GetControlModeCommand.h + +--*/ + +#ifndef __GET_CONTROL_MODE_COMMAND_H__ +#define __GET_CONTROL_MODE_COMMAND_H__ + +#include "AMTHICommand.h" +#include "MEIparser.h" + +namespace Intel +{ +namespace MEI_Client +{ +namespace AMTHI_Client +{ + struct GET_CONTROL_MODE_RESPONSE + { + uint32_t ControlMode; + + void parse (std::vector::const_iterator& itr, const std::vector::const_iterator end) + { + Intel::MEI_Client::parseData(*this, itr, end); + } + }; + + class GetControlModeRequest; + class GetControlModeCommand : public AMTHICommand + { + public: + + GetControlModeCommand(); + virtual ~GetControlModeCommand() {} + + virtual void reTransact(); + GET_CONTROL_MODE_RESPONSE getResponse(); + + private: + virtual void parseResponse(const std::vector& buffer); + + std::shared_ptr> m_response; + + static const uint32_t RESPONSE_COMMAND_NUMBER = 0x0480006B; + }; + + class GetControlModeRequest : public AMTHICommandRequest + { + public: + GetControlModeRequest() {} + virtual ~GetControlModeRequest() {} + + private: + static const uint32_t REQUEST_COMMAND_NUMBER = 0x0400006B; + virtual unsigned int requestHeaderCommandNumber() + { + //this is the command number (taken from the AMTHI document) + return REQUEST_COMMAND_NUMBER; + } + + virtual uint32_t requestDataSize() + { + return 0; + } + virtual std::vector SerializeData(); + }; +} // namespace AMTHI_Client +} // namespace MEI_Client +} // namespace Intel + +#endif //__GET_CONTROL_MODE_COMMAND_H__ \ No newline at end of file diff --git a/AMTHIClient/Src/GetControlModeCommand.cpp b/AMTHIClient/Src/GetControlModeCommand.cpp new file mode 100644 index 0000000..571ff7f --- /dev/null +++ b/AMTHIClient/Src/GetControlModeCommand.cpp @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* + * Copyright (C) 2010-2019 Intel Corporation + */ +/*++ + +@file: GetControlModeCommand.cpp + +--*/ + +#include "GetControlModeCommand.h" +#include "StatusCodeDefinitions.h" +#include + +using namespace std; + +using namespace Intel::MEI_Client::AMTHI_Client; + +GetControlModeCommand::GetControlModeCommand() +{ + shared_ptr tmp(new GetControlModeRequest()); + m_request = tmp; + Transact(); +} + +void GetControlModeCommand::reTransact() +{ + shared_ptr tmp(new GetControlModeRequest()); + m_request = tmp; + Transact(); +} + +GET_CONTROL_MODE_RESPONSE GetControlModeCommand::getResponse() +{ + return m_response->getResponse(); +} + +void +GetControlModeCommand::parseResponse(const vector& buffer) +{ + shared_ptr> tmp( + new AMTHICommandResponse(buffer, RESPONSE_COMMAND_NUMBER)); + m_response = tmp; +} + +std::vector +GetControlModeRequest::SerializeData() +{ + vector output; + return output; +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e2e0383 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,131 @@ +cmake_minimum_required (VERSION 3.1) + +project (rpc VERSION 1.0.0) + +set (CMAKE_CXX_STANDARD 11) + +# RPC version info +configure_file(version.h.in + version.h) +include_directories(${PROJECT_BINARY_DIR}) + +# TODO: figure out how to read the LMS version from repo like the main lms CMakeLists.txt +set (LMS_VERSION_STRING 1932.0.0.0) + +# Compiler settings [Obtained from CmakeLists.txt for lms] +string(APPEND CMAKE_CXX_FLAGS_DEBUG " -DDEBUG -D_DEBUG") +string(APPEND CMAKE_C_FLAGS_DEBUG " -DDEBUG -D_DEBUG") + +set (CMAKE_POSITION_INDEPENDENT_CODE ON) + +if (UNIX) + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -z noexecstack -z relro -z now") + set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -z noexecstack -z relro -z now") + + #CMake issue #14983 + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie") + + #Secure library usage and secure compile flags + add_definitions (-fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2 -Wformat -Wformat-security) + add_definitions (-fno-strict-overflow -fno-delete-null-pointer-checks -fwrapv) +else (UNIX) + add_definitions (/GS /sdl) + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /NXCompat /DynamicBase") + #add_definitions (/D UNICODE /D _UNICODE) + add_definitions (/D UNICODE /D _UNICODE /D_NO_ASYNCRTIMP /D_ASYNCRT_EXPORT /D_NO_PPLXIMP /DWIN32 /DMBCS /D_USRDLL /DCPPREST_EXCLUDE_COMPRESSION /D_WINSOCK_DEPRECATED_NO_WARNINGS) + add_compile_options ($<$:/O2>) + add_compile_options (/MT$<$:d>) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") +endif (UNIX) + + +# Download and unpack lms at configure time +configure_file(lms.cmake.in + lms-download/CMakeLists.txt) +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/lms-download ) +execute_process(COMMAND ${CMAKE_COMMAND} --build . + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/lms-download ) + +# Add MEIClient directly to our build. This adds +# the following targets: LmsMEIClient and LIBMETEE +add_subdirectory(${CMAKE_BINARY_DIR}/lms-src/MEIClient + ${CMAKE_BINARY_DIR}/lms-build/MEIClient) +add_dependencies(LmsMEIClient libmetee) + + +if (UNIX) + +# Find threads [unix it pthreads] +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +set(THREADS_PREFER_PTHREAD_FLAG TRUE) +find_package(Threads REQUIRED) + +# Find Boost +find_package(Boost COMPONENTS system REQUIRED) + +# Find OpenSSL +find_package(OpenSSL) + +# Download and build CppRestSDK, If GIT_TAG is changed then need to delete cpprestsdk-prefix because UPDATE_COMMAND is set to "" +include(ExternalProject) +ExternalProject_Add(cpprestsdk + GIT_REPOSITORY https://github.com/Microsoft/cpprestsdk.git + GIT_TAG v2.10.14 + CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=0 -DBUILD_SAMPLES=OFF -DBUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/../../install + TEST_COMMAND "" + UPDATE_COMMAND "" +) +ExternalProject_Get_Property(cpprestsdk SOURCE_DIR) +set(CPPRESTSDK_LIBARIES ${SOURCE_DIR}/../../install/lib/) +set(CPPRESTSDK_INCLUDE_DIR ${SOURCE_DIR}/../../install/include/) + +add_library(cpprest INTERFACE) +target_link_libraries(cpprest INTERFACE ${CPPRESTSDK_LIBARIES}/libcpprest.a OpenSSL::SSL OpenSSL::Crypto ${Boost_LIBRARIES} Threads::Threads) +target_include_directories(cpprest INTERFACE ${CPPRESTSDK_INCLUDE_DIR}) + +else (UNIX) + +# CppRestSDK +find_package(cpprestsdk CONFIG REQUIRED) + +endif (UNIX) + +# ccu-poc +add_executable (rpc + AMTHIClient/Include/GetControlModeCommand.h + AMTHIClient/Src/GetControlModeCommand.cpp + commands.h + commands.cpp + lms.h + lms.cpp + main.cpp +) + +target_include_directories(rpc PUBLIC + "AMTHIClient/Include/" +) + +if (UNIX) + +add_dependencies(rpc cpprestsdk) + +target_link_libraries (rpc PRIVATE + LmsMEIClient + cpprest +) + +else (UNIX) + +target_link_libraries (rpc PRIVATE + LmsMEIClient + iphlpapi + cpprestsdk::cpprest + cpprestsdk::cpprestsdk_zlib_internal + cpprestsdk::cpprestsdk_boost_internal + cpprestsdk::cpprestsdk_brotli_internal + ${Boost_LIBRARIES} +) + +endif (UNIX) diff --git a/README.md b/README.md index 1ba28df..0b773e5 100644 --- a/README.md +++ b/README.md @@ -1 +1,66 @@ -# rpc +# Remote Provisioning Client (RPC) + +RPC is an application which enables remote capabilities for AMT, such as as device activation. To accomplish this, RPC communicates with the RPS (Remote Provisioning Server). + +As a prerequisite, a Local Management Service (LMS) must be installed and running on the operating system. + +## Linux + +Steps below are for Ubuntu 18.04. + +### Dependencies + +- sudo apt install git cmake build-essential libboost-system-dev libboost-thread-dev libboost-random-dev libboost-regex-dev libboost-filesystem-dev libssl-dev zlib1g-dev + +### Build + +- mkdir build +- cd build +- cmake -DCMAKE_BUILD_TYPE=Release .. + - Build debug: cmake -DCMAKE_BUILD_TYPE=Debug .. +- cmake --build . + +### Run + +- See ./rpc --help for details. +- Example + - sudo ./rpc --url wss://localhost:8080 --profile profile1 + +## Windows + +Steps below are for Windows 10 and Visual Studio 2019 Professional. + +### Build VCPKG + +Open an x64 native command prompt for Visual Studio 2019 as Administrator. + +- git clone --branch 2020.01 https://github.com/microsoft/vcpkg.git +- cd vcpkg +- bootstrap-vcpkg.bat +- vcpkg integrate install + +### Build C++ REST SDK + +Open an x64 native tools command prompt for Visual Studio 2019. + +- cd vcpkg +- vcpkg install cpprestsdk:x64-windows-static + +### Build + +Open an x64 native tools command prompt for Visual Studio 2019. + +- mkdir build +- cd build +- cmake .. -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_TOOLCHAIN_FILE=/vcpkg/scripts/buildsystems/vcpkg.cmake +- cmake --build . --config Release + - Build debug: cmake --build . --config Debug + +### Run + +Open a command prompt as Administrator. + +- See rpc.exe --help for details. +- Example + - cd build\Release + - rpc.exe --url wss://localhost:8080 --profile profile1 diff --git a/commands.cpp b/commands.cpp new file mode 100644 index 0000000..7e94fe2 --- /dev/null +++ b/commands.cpp @@ -0,0 +1,499 @@ +/* +Copyright 2019 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "commands.h" + +#ifdef _WIN32 +#include +#include +#include +#endif + +#include "AMTHICommand.h" +#include "MEIClientException.h" +#include "GetUUIDCommand.h" +#include "GetLocalSystemAccountCommand.h" +#include "GetCodeVersionCommand.h" +#include "GetControlModeCommand.h" +#include "GetProvisioningStateCommand.h" +#include "GetDNSSuffixCommand.h" +#include "GetLanInterfaceSettingsCommand.h" +#include "GetCertificateHashEntryCommand.h" +#include "EnumerateHashHandlesCommand.h" +#include "MEIparser.h" +#include "version.h" +#include + +#include +#include +#include +#include +#include +#include +#include "lms.h" + +using namespace std; +using namespace Intel::MEI_Client::AMTHI_Client; +using namespace web::websockets::client; +using namespace web; + +#define WORKING_BUFFER_SIZE 15000 +#define MAX_TRIES 3 + + +#ifdef _WIN32 +std::string getDNSFromMAC(char *macAddress) +{ + std::string dnsSuffix = ""; + char dns[256]; + memset(dns, 0, 256); + + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + DWORD dwSize = 0; + DWORD dwRetVal = 0; + ULONG outBufLen = 0; + ULONG Iterations = 0; + outBufLen = WORKING_BUFFER_SIZE; + + // get info for all adapters + do { + + pAddresses = (IP_ADAPTER_ADDRESSES *) malloc(outBufLen); + if (pAddresses == NULL) { + cout << "dns memory error" << std::endl; + return dnsSuffix; + } + + dwRetVal = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, NULL, pAddresses, &outBufLen); + + if (dwRetVal == ERROR_BUFFER_OVERFLOW) + { + free(pAddresses); + pAddresses = NULL; + } + else + { + break; + } + + Iterations++; + + } while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (Iterations < MAX_TRIES)); + + // get DNS from MAC + PIP_ADAPTER_ADDRESSES pCurrAddresses = NULL; + pCurrAddresses = pAddresses; + while (pCurrAddresses) + { + if (pCurrAddresses->PhysicalAddressLength != 0) + { + if (memcmp(macAddress, (char *) pCurrAddresses->PhysicalAddress, 6) == 0) + { + if (wcslen(pCurrAddresses->DnsSuffix) > 0) + { + snprintf(dns, 256, "%ws", pCurrAddresses->DnsSuffix ); + break; + } + } + } + + pCurrAddresses = pCurrAddresses->Next; + } + + dnsSuffix = dns; + + return dnsSuffix; +} + +#else +std::string getDNSFromMAC(char *macAddress) +{ + std::string dnsSuffix = ""; + + // get socket + SOCKET s = 0; + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s < 0) + { + cout << "couldn't get socket" << endl; + return dnsSuffix; + } + + // get info for all adapters + struct ifconf ifc; + memset(&ifc, 0, sizeof(ifconf)); + char buffer[8192]; + memset(buffer, 0, sizeof(buffer)); + + ifc.ifc_buf = buffer; + ifc.ifc_len = sizeof(buffer); + if(ioctl(s, SIOCGIFCONF, &ifc) < 0) + { + cout << "ioctl SIOCGIFCONF failed" << endl; + return dnsSuffix; + } + + // get DNS from IP associated with MAC + struct ifreq *ifr = ifc.ifc_req; + int interfaceCount = ifc.ifc_len / sizeof(struct ifreq); + + char ip[INET6_ADDRSTRLEN] = {0}; + struct ifreq *item; + struct sockaddr *addr; + for(int i = 0; i < interfaceCount; i++) + { + item = &ifr[i]; + addr = &(item->ifr_addr); + + // get IP address + if(ioctl(s, SIOCGIFADDR, item) < 0) + { + cout << "ioctl SIOCGIFADDR failed" << endl; + continue; + } + + if (inet_ntop(AF_INET, &( ((struct sockaddr_in *)addr)->sin_addr ), + ip, sizeof(ip) ) == NULL) + { + cout << "inet_ntop" << endl; + continue; + } + + // get MAC address + if(ioctl(s, SIOCGIFHWADDR, item) < 0) + { + cout << "ioctl SIOCGIFHWADDR failed" << endl; + continue; + } + + if (memcmp(macAddress, (char *) item->ifr_hwaddr.sa_data, 6) == 0) + { + // Get host by using the IP address which AMT device is using + struct in_addr addr = {0}; + struct hostent *host; + addr.s_addr = inet_addr(ip); + host = gethostbyaddr((char *)&addr, 4, AF_INET); + if (host == NULL) + { + cout << "gethostbyaddr() failed"; + return dnsSuffix; + } + + // strip off the hostname to get actual domain name + int domainNameSize = 256; + char domainName[domainNameSize]; + memset(domainName, 0, domainNameSize); + + char *dn = strchr(host->h_name, '.'); + if (dn != NULL) + { + if (domainNameSize >= strlen(dn + 1)) + { + snprintf(domainName, domainNameSize, "%s", ++dn); + + dnsSuffix = domainName; + } + } + } + } + + return dnsSuffix; +} +#endif + + +json::value getCertificateHashes() +{ + json::value certHashes; + vector hashValues; + + // get the hash handles + EnumerateHashHandlesCommand command; + ENUMERATE_HASH_HANDLES_RESPONSE response = command.getResponse(); + + vector::iterator itr = response.HashHandles.begin(); + vector::iterator endItr = response.HashHandles.end(); + for (; itr != endItr; ++itr) + { + // get each entry + GetCertificateHashEntryCommand command(*itr); + GET_CERTIFICATE_HASH_ENTRY_RESPONSE response = command.getResponse(); + + int hashSize; + switch (response.HashAlgorithm) { + case 0: // MD5 + hashSize = 16; + break; + case 1: // SHA1 + hashSize = 20; + break; + case 2: // SHA256 + hashSize = 32; + break; + case 3: // SHA512 + hashSize = 64; + break; + default: + hashSize = 64; + break; + } + + if (response.IsActive == 1) { + string hashString; + hashString.clear(); + for (int i = 0; i < hashSize; i++) + { + char hex[10]; + snprintf(hex, 10, "%02x", response.CertificateHash[i]); + hashString += hex; + } + + hashValues.push_back( json::value::string( utility::conversions::convertstring(hashString) ) ); + } + } + + return json::value::array(hashValues); +} + +std::string getDNSInfo() +{ + std::string dnsSuffix; + + // Get interface info which AMT is using. We don't worry about wireless since + // only wired used for configuration + GetLanInterfaceSettingsCommand getLanInterfaceSettingsCommandWired(Intel::MEI_Client::AMTHI_Client::WIRED); + LAN_SETTINGS lanSettings = getLanInterfaceSettingsCommandWired.getResponse(); + + if (!lanSettings.Enabled) + { + cout << "error: no wired AMT interfaces enabled" << endl; + return ""; + } + + // Get DNS according to AMT + GetDNSSuffixCommand getDnsSuffixCommand; + dnsSuffix = getDnsSuffixCommand.getResponse(); + + // get DNS from OS + if (!dnsSuffix.length()) + { + dnsSuffix = getDNSFromMAC((char *)&lanSettings.MacAddress); + } + + return dnsSuffix; +} + + +string getActivateInfo(string profile) +{ + utility::string_t tmp; + + // Activation parameters + json::value activationParams; + + // Get code version + GetCodeVersionCommand codeVersionCommand; + CODE_VERSIONS codeVersion = codeVersionCommand.getResponse(); + + // Additional versions + // UINT8[16] UUID; + // AMT_VERSION_TYPE Version and Description are std::string. + for (vector::iterator it = codeVersion.Versions.begin(); it != codeVersion.Versions.end(); it++) + { + if (boost::iequals(it->Description, "AMT")) + { + tmp = utility::conversions::convertstring(it->Version); + activationParams[U("ver")] = json::value::string(tmp); + } + else if (boost::iequals(it->Description, "Build Number")) + { + tmp = utility::conversions::convertstring(it->Version); + activationParams[U("build")] = json::value::string(tmp); + } + else if (boost::iequals(it->Description, "Sku")) + { + tmp = utility::conversions::convertstring(it->Version); + activationParams[U("sku")] = json::value::string(tmp); + } + } + + // Get UUID + GetUUIDCommand get; + GET_UUID_RESPONSE res = get.getResponse(); + std::vector UUID; + for (int i = 0; i < 16; i++) + { + UUID.push_back(json::value(res.UUID[i])); + } + activationParams[U("uuid")] = json::value::array(UUID); + + // Get local system account + // User name in ASCII char-set. The string is NULL terminated. CFG_MAX_ACL_USER_LENGTH is 33 + // Password in ASCII char set. From AMT 6.1 this field is in BASE64 format. The string is NULL terminated. + GetLocalSystemAccountCommand sac; + tmp = utility::conversions::convertstring(sac.getResponse().UserName); + activationParams[U("username")] = json::value::string(tmp); + tmp = utility::conversions::convertstring(sac.getResponse().Password); + activationParams[U("password")] = json::value::string(tmp); + + // Get Control Mode + GetControlModeCommand controlModeCommand; + GET_CONTROL_MODE_RESPONSE controlMode = controlModeCommand.getResponse(); + activationParams[U("currentMode")] = json::value::number(controlMode.ControlMode); + + // Get DNS Info + tmp = utility::conversions::convertstring(""); + string dnsSuffix = getDNSInfo(); + + if (dnsSuffix.length()) + { + tmp = utility::conversions::convertstring(dnsSuffix); + } + + activationParams[U("fqdn")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring("PPC"); + activationParams[U("client")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring(profile); + activationParams[U("profile")] = json::value::string(tmp); + + // Get certificate hashes + activationParams[U("certHashes")] = getCertificateHashes(); + + // Return serialized parameters in base64 + string serializedParams = utility::conversions::to_utf8string(activationParams.serialize()); +#ifdef DEBUG + cout << "Activation info payload:" << serializedParams << std::endl; +#endif + + return encodeBase64(serializedParams); +} + +string encodeBase64(string str) +{ + std::vector strVector(str.begin(), str.end()); + utility::string_t base64 = utility::conversions::to_base64(strVector); + string encodedString = utility::conversions::to_utf8string(base64); + + return encodedString; +} + +string decodeBase64(string str) +{ + utility::string_t serializedData = utility::conversions::to_string_t(str); + std::vector strVector = utility::conversions::from_base64(serializedData); + string decodedString(strVector.begin(), strVector.end()); + + return decodedString; +} + +string createActivationRequest(string profile) +{ + // Activation parameters + json::value request; + + // placeholder stuff; will likely change + utility::string_t tmp = utility::conversions::convertstring("activation"); + request[U("method")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring("key"); + request[U("apiKey")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring(PROJECT_VER); + request[U("appVersion")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring(PROTOCOL_VERSION); + request[U("protocolVersion")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring("ok"); + request[U("status")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring("all\'s good!"); + request[U("message")] = json::value::string(tmp); + + // payload + string activationInfo = getActivateInfo(profile); + utility::string_t payload = utility::conversions::to_string_t(activationInfo); + + request[U("payload")] = json::value::string(payload); + + return utility::conversions::to_utf8string(request.serialize()); +} + +string createResponse(string payload) +{ + // Activation parameters + json::value response; + + // placeholder stuff; will likely change + utility::string_t tmp = utility::conversions::convertstring("response"); + response[U("method")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring("key"); + response[U("apiKey")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring(PROJECT_VER); + response[U("appVersion")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring(PROTOCOL_VERSION); + response[U("protocolVersion")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring("ok"); + response[U("status")] = json::value::string(tmp); + + tmp = utility::conversions::convertstring("all\'s good!"); + response[U("message")] = json::value::string(tmp); + + // payload + tmp = utility::conversions::convertstring( encodeBase64(payload) ); + response[U("payload")] = json::value::string(tmp); + + return utility::conversions::to_utf8string(response.serialize()); +} + +void dumpMessage(utility::string_t tmp) +{ + web::json::value parsed = web::json::value::parse(tmp); + + if ( !parsed.has_field(U("method")) || !parsed.has_field(U("apiKey")) || !parsed.has_field(U("appVersion")) || + !parsed.has_field(U("protocolVersion")) || !parsed.has_field(U("status")) || !parsed.has_field(U("message")) || + !parsed.has_field(U("payload")) ) { + cout << "error: dumpMessage message is empty" << endl; + return; + } + + utility::string_t out = parsed[U("method")].as_string(); + cout << "method: " << utility::conversions::to_utf8string(out) << endl; + + out = parsed[U("apiKey")].as_string(); + cout << "apiKey: " << utility::conversions::to_utf8string(out) << endl; + + out = parsed[U("appVersion")].as_string(); + cout << "appVersion: " << utility::conversions::to_utf8string(out) << endl; + + out = parsed[U("protocolVersion")].as_string(); + cout << "protocolVersion: " << utility::conversions::to_utf8string(out) << endl; + + out = parsed[U("status")].as_string(); + cout << "status: " << utility::conversions::to_utf8string(out) << endl; + + out = parsed[U("message")].as_string(); + cout << "message: " << utility::conversions::to_utf8string(out) << endl; + + out = parsed[U("payload")].as_string(); + cout << "payload: " << utility::conversions::to_utf8string(out) << endl; +} \ No newline at end of file diff --git a/commands.h b/commands.h new file mode 100644 index 0000000..a6ba8ce --- /dev/null +++ b/commands.h @@ -0,0 +1,48 @@ +/* +Copyright 2019 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef __COMMANDS_H__ +#define __COMMANDS_H__ + +#include +#include + +#include +#include +#include + +using namespace std; +using namespace web::websockets::client; +using namespace web; + +#define PROTOCOL_VERSION "2.0.0" + +#ifdef _WIN32 +#define convertstring to_utf16string +#else +#define convertstring to_utf8string +#endif + +string getDNSInfo(); +string createActivationRequest(string profile); +json::value getCertificateHashes(); +string createResponse(string payload); +string getActivateInfo(string profile); +string encodeBase64(string str); +string decodeBase64(string str); +void dumpMessage(string tmp); + +#endif \ No newline at end of file diff --git a/lms.cmake.in b/lms.cmake.in new file mode 100644 index 0000000..950ada4 --- /dev/null +++ b/lms.cmake.in @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.1) + +project(lms-download NONE) + +include(ExternalProject) +ExternalProject_Add(lms + GIT_REPOSITORY https://github.com/intel/lms.git + GIT_TAG v1932.0.0.0 + SOURCE_DIR "${CMAKE_BINARY_DIR}/lms-src" + BINARY_DIR "${CMAKE_BINARY_DIR}/lms-build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) \ No newline at end of file diff --git a/lms.cpp b/lms.cpp new file mode 100644 index 0000000..53724a9 --- /dev/null +++ b/lms.cpp @@ -0,0 +1,88 @@ +/* +Copyright 2019 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#include "lms.h" +#include + +#ifdef _WIN32 +// Windows +#include +#else +// Linux +#include +#include +#include +#endif + +SOCKET lmsConnect() +{ + std::string lmsAddress = "localhost"; + std::string lmsPort = "16992"; + SOCKET s = INVALID_SOCKET; + struct addrinfo *addr, hints; + +#ifdef _WIN32 + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) + { + throw std::runtime_error("error: unable to connect to LMS"); + } +#endif + + memset(&hints, 0, sizeof(hints)); + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + if (getaddrinfo(lmsAddress.c_str(), lmsPort.c_str(), &hints, &addr) != 0) + { + throw std::runtime_error("error: unable to connect to LMS"); + } + + if (addr == NULL) + { + throw std::runtime_error("error: unable to connect to LMS"); + } + + for (addr; addr != NULL; addr = addr->ai_next) + { + s = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (s == INVALID_SOCKET) + { + continue; + } + + if (connect(s, addr->ai_addr, (int)addr->ai_addrlen) == 0) + { + break; + } + + closesocket(s); + s = INVALID_SOCKET; + } + + if (addr != NULL) + { + freeaddrinfo(addr); + } + + if (s == INVALID_SOCKET) + { + throw std::runtime_error("error: unable to connect to LMS"); + } + + return s; +} \ No newline at end of file diff --git a/lms.h b/lms.h new file mode 100644 index 0000000..85a219c --- /dev/null +++ b/lms.h @@ -0,0 +1,44 @@ +/* +Copyright 2019 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef __LMS_H__ +#define __LMS_H__ + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +typedef int SOCKET; +#define INVALID_SOCKET (SOCKET)(-1) +#endif + +#ifdef _WIN32 +// Windows +#else +// Linux +static inline int closesocket(int fd) +{ + return close(fd); +} +#define SD_BOTH SHUT_RDWR +#endif + +SOCKET lmsConnect(); + +#endif \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..f530d34 --- /dev/null +++ b/main.cpp @@ -0,0 +1,480 @@ +/* +Copyright 2019 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include +#include +#include +#include +#include +#include +#include "commands.h" +#include "lms.h" +#include "version.h" + +using namespace std; +using namespace web; +using namespace web::websockets::client; + +#include + +void showUsage(); + +string websocket_address = ""; +string server_profile = ""; +string websocket_proxy = ""; + +long long timeoutTimer = 0; +const int MAX_TIMEOUT = 10; // seconds +bool timeoutThreadAlive = true; + +void timeoutFunc(std::condition_variable *cv, std::mutex *mx) +{ + while (timeoutThreadAlive) + { + std::chrono::time_point now = std::chrono::system_clock::now(); + auto duration = now.time_since_epoch(); + long long currTime = std::chrono::duration_cast(duration).count(); + + if (currTime > timeoutTimer) + { + if (currTime - timeoutTimer >= MAX_TIMEOUT) + { + cv->notify_all(); + + // check if timeoutTimer is not 0 since we explicitly set to zero when an + // activation is successfull. If it's not zero, we are in a time out scenario. + if (timeoutTimer) + { + cout << endl << "Timed-out due to inactivity." < now = std::chrono::system_clock::now(); + auto duration = now.time_since_epoch(); + timeoutTimer = std::chrono::duration_cast(duration).count(); + + try + { + // handle message from server... + string rcv_websocket_msg = ret_msg.extract_string().get(); +#ifdef DEBUG + cout << endl << "<<<<< Received Message " << endl; + cout << rcv_websocket_msg << endl; +#endif + cout << "." << std::flush; // dot status output + + // parse incoming JSON message + utility::string_t tmp = utility::conversions::convertstring(rcv_websocket_msg); + web::json::value parsed = web::json::value::parse(tmp); + + utility::string_t out = U(""); + string msgMethod = ""; + string msgApiKey = ""; + string msgAppVersion = ""; + string msgProtocolVersion = ""; + string msgStatus = ""; + string msgMessage = ""; + string msgPayload = ""; + string payloadDecoded = ""; + + if ( !parsed.has_field(U("method")) || !parsed.has_field(U("apiKey")) || !parsed.has_field(U("appVersion")) || + !parsed.has_field(U("protocolVersion")) || !parsed.has_field(U("status")) || !parsed.has_field(U("message")) || + !parsed.has_field(U("payload")) ) { + std::cerr << endl << "Received incorrectly formatted message." << endl; + cv.notify_all(); + timeoutThreadAlive = false; + return; + } + + try + { + out = parsed[U("method")].as_string(); + msgMethod = utility::conversions::to_utf8string(out); + + out = parsed[U("apiKey")].as_string(); + msgApiKey = utility::conversions::to_utf8string(out); + + out = parsed[U("appVersion")].as_string(); + msgAppVersion = utility::conversions::to_utf8string(out); + + out = parsed[U("protocolVersion")].as_string(); + msgProtocolVersion = utility::conversions::to_utf8string(out); + + out = parsed[U("status")].as_string(); + msgStatus = utility::conversions::to_utf8string(out); + + out = parsed[U("message")].as_string(); + msgMessage = utility::conversions::to_utf8string(out); + } + catch (...) + { + std::cerr << endl << "Received message parse error." << endl; + return; + } + + +#ifdef DEBUG + cout << msgMethod << ", " << msgStatus << ", " << msgMessage << endl; + cout << rcv_websocket_msg << endl; +#endif + + // process any messages we can + // - if success, done + // - if error, get out + if (boost::iequals(msgMethod, "success")) + { + // cleanup + timeoutTimer = 0; + + // exit + cout << endl << msgMessage << endl; + return; + } + else if (boost::iequals(msgMethod, "error")) + { + // cleanup + timeoutTimer = 0; + + // exit + cout << endl << msgMessage << endl; + return; + } + + // process payload afterward since success/error messages have zero length + // payloads which cause an exception to be thrown + try + { + out = parsed[U("payload")].as_string(); + + if (out.length()>0) + { + msgPayload = utility::conversions::to_utf8string(out); + + // decode payload + payloadDecoded = decodeBase64(msgPayload); + } + else + { + // no payload, nothing to do + return; + } + } + catch (...) + { + std::cerr << endl << "JSON format error. Unable to parse message." << endl; + return; + } + + try + { + // conntect to lms + s = lmsConnect(); + } + catch (...) + { + std::cerr << endl << "Unable to connect to Local Management Service (LMS). Please ensure LMS is running." << endl; + return; + } + +#ifdef DEBUG + cout << endl << "vvvvv Sending Message " << endl; + cout << payloadDecoded << endl; +#endif + + // send message to LMS + if (send(s, payloadDecoded.c_str(), (int)payloadDecoded.length(), 0) < 0) + { + throw std::runtime_error("error: socket send"); + } + + // handle response message from LMS + int fd = ((int) s) + 1; + fd_set rset; + FD_ZERO(&rset); + FD_SET(s, &rset); + + timeval timeout; + memset(&timeout, 0, sizeof(timeval)); + timeout.tv_sec = 2; + timeout.tv_usec = 0; + + // read until connection is closed by LMS + while (1) + { + string superBuffer = ""; + while (1) + { + int res = select(fd, &rset, NULL, NULL, &timeout); + if (res == 0) + { + // we timed-out waiting for the ME. ME usually delivers data very fast. If + // we time out, it means that there is no more data that the ME needs to send, + // but it's keeping the connection open. + break; + } + + // read from LMS + char recv_buffer[4096]; + memset(recv_buffer, 0, 4096); + res = recv(s, recv_buffer, 4096, 0); + if (res > 0) + { +#ifdef DEBUG + cout << endl << "^^^^^ Received Message" << endl; + cout << recv_buffer << endl; +#endif + superBuffer += recv_buffer; + } + else if (res < 0) + { + // on LMS read exception + break; + } + else + { + // case where res is zero bytes + // discussion below, but select returns 1 with recv returning 0 to indicate close + // https://stackoverflow.com/questions/2992547/waiting-for-data-via-select-not-working + break; + } + } // while select() + + // if there is some data send it + if (superBuffer.length() > 0) + { + string response = createResponse(superBuffer.c_str()); + websocket_outgoing_message send_websocket_msg; + string send_websocket_buffer(response); + send_websocket_msg.set_utf8_message(send_websocket_buffer); +#ifdef DEBUG + cout << endl << ">>>>> Sending Message" << endl; + cout << superBuffer << endl; + cout << send_websocket_buffer << endl; +#endif + client.send(send_websocket_msg).wait(); + + // done + closesocket(s); + return; + } + } + + closesocket(s); + } + catch (...) + { + std::cerr << endl << "Communication error in receive handler." << endl; + closesocket(s); + } + }); + + // set close handler + client.set_close_handler([&mx,&cv](websocket_close_status status, const utility::string_t &reason, const std::error_code &code) + { + // websocket closed by server, notify main thread + cv.notify_all(); + }); + + try + { + // Connect to web socket server; AMT activation server + client.connect(utility::conversions::to_string_t(websocket_address)).wait(); + } + catch (...) + { + std::cerr << "Unable to connect to websocket server. Please check url." << endl; + exit(1); + } + + try + { + // Send activationParams to websocket + websocket_outgoing_message out_msg; + out_msg.set_utf8_message(activationInfo); + +#ifdef DEBUG + cout << endl << ">>>>> Sending Activiation Info" << endl; + cout << activationInfo << endl; +#endif + client.send(out_msg).wait(); + } + catch (...) + { + std::cerr << endl << "Unable to send message to websocket server." << endl; + exit(1); + } + + std::chrono::time_point now = std::chrono::system_clock::now(); + auto duration = now.time_since_epoch(); + timeoutTimer = std::chrono::duration_cast(duration).count(); + std::thread timeoutThread(timeoutFunc, &cv, &mx); + + // wait for server to send success/failure command + std::unique_lock lock(mx); + cv.wait(lock); + + // wait for timeout thread + timeoutThread.join(); + + // clean-up websocket + client.close().wait(); + + // clean-up socket + if (s) { + shutdown(s, SD_BOTH); + closesocket(s); + } + + exit(0); +} + +void showUsage() +{ + cout << "Usage: " << PROJECT_NAME << " --url --profile [--proxy ]" << endl; + cout << "Required:" << endl; + cout << " --u, --url websocket server" << endl; + cout << " --p, --profile server profile" << endl; + cout << "Optional:" << endl; + cout << " --x, --proxy proxy address and port" << endl; + cout << endl; + cout << "Examples:" << endl; + cout << " " << PROJECT_NAME << " --url wss://localhost --profile profile1" << endl; + cout << " " << PROJECT_NAME << " --u wss://localhost --profile profile1 --proxy http://proxy.com:1000" << endl; + cout << " " << PROJECT_NAME << " --u wss://localhost --p profile1 --x http://proxy.com:1000" << endl; + cout << endl; +} diff --git a/version.h.in b/version.h.in new file mode 100644 index 0000000..f3e351c --- /dev/null +++ b/version.h.in @@ -0,0 +1,10 @@ +#ifndef __VERSION_H__ +#define __VERSION_H__ + +#define PROJECT_NAME "@PROJECT_NAME@" +#define PROJECT_VER "@PROJECT_VERSION@" +#define PROJECT_VER_MAJOR "@PROJECT_VERSION_MAJOR@" +#define PROJECT_VER_MINOR "@PROJECT_VERSION_MINOR@" +#define PTOJECT_VER_PATCH "@PROJECT_VERSION_PATCH@" + +#endif // __VERSION_H__