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__