diff --git a/README b/README index 9fa6e9ac..9cf71ee5 100644 --- a/README +++ b/README @@ -19,4 +19,17 @@ Added automated build/install script setup-all.sh For unattended setup, installing all prereqs and deleting conflicting packages, run the script as root, specifying the following command: ./setup-all.sh -f -i -d -For addition details on the setup script and webconnect prereqs consult wsgate/README. \ No newline at end of file +For addition details on the setup script and webconnect prereqs consult wsgate/README. + + +FreeRDP-WebConnect also uses a plugin system based on shared objects that will be placed in a "plugins" subdirectory +In order to implement a new plugin, one must build a new shared library that will include the following files +/wsgate/pluginTemplate/pluginCommon.cpp +this facilitates the query parsing for easier use + +/wsgate/pluginTemplate/pluginCommon.hpp + +and also an implementation of entryPoint function as described in +/wsgate/pluginTemplate/pluginExample.cpp + +One can also follow the pluginOpenstack for reference diff --git a/wsgate/CMakeLists.txt b/wsgate/CMakeLists.txt index 4fd6d1a8..976df770 100644 --- a/wsgate/CMakeLists.txt +++ b/wsgate/CMakeLists.txt @@ -300,6 +300,11 @@ set(CMAKE_EXTRA_INCLUDE_FILES) # linked libraries list SET(LIBS) +#if UNIX add DL library for dynamic linking +if(UNIX) + set(LIBS ${LIBS} dl) +endif() + # if win32 use PTHREADS_WIN32 if(WIN32) @@ -345,6 +350,12 @@ include_directories(${PNG_INCLUDE_DIR}) # add png to libs list set(LIBS ${LIBS} ${PNG_LIBRARIES}) +# find casablanca +find_package(Casablanca REQUIRED) +include_directories(${CASABLANCA_INCLUDE_DIR}) +# add casablanca to libs list +set(LIBS ${LIBS} ${CASABLANCA_LIBRARIES}) + # find freerdp find_package(FreeRDP REQUIRED) include_directories(${FREERDP_INCLUDE_DIR}) @@ -365,13 +376,6 @@ set(HAVE_EHS_H 1) # add ehs to libs list set(LIBS ${LIBS} ${EHS_LIBRARIES}) -# find casablanca -find_package(Casablanca REQUIRED) -include_directories(${CASABLANCA_INCLUDE_DIR}) -# add casablanca to libs list -set(LIBS ${LIBS} ${CASABLANCA_LIBRARIES}) - - CHECK_FUNCTION_EXISTS(memset HAVE_MEMSET) CHECK_FUNCTION_EXISTS(select HAVE_SELECT) CHECK_FUNCTION_EXISTS(setlocale HAVE_SETLOCALE) @@ -591,13 +595,13 @@ set(WSGATE_SOURCES base64.cpp btexception.cpp logging.cpp sha1.cpp wsgate_main.cpp RDP.cpp Update.cpp Primary.cpp myBindHelper.cpp myWsHandler.cpp myrawsocket.cpp wsendpoint.cpp wsgateEHS.cpp wshandler.cpp - Png.cpp nova_token_auth.cpp) + Png.cpp pluginManager.cpp) if (WIN32) set(WSGATE_SOURCES "${WSGATE_SOURCES}" NTService.cpp wsGateService.cpp) # in order for header files to appear in VS solution, add them to the sources list set(WSGATE_SOURCES "${WSGATE_SOURCES}" ${CMAKE_CURRENT_BINARY_DIR}/config.h base64.hpp btexception.hpp common.hpp - logging.hpp myrawsocket.hpp nova_token_auth.hpp NTService.hpp + logging.hpp myrawsocket.hpp pluginManager.hpp NTService.hpp Png.hpp Primary.hpp rdpcommon.hpp RDP.hpp sha1.hpp Update.hpp wscommon.hpp wsendpoint.hpp wsframe.hpp wsgate.hpp wshandler.hpp myBindHelper.hpp myWsHandler.hpp wsGateService.hpp wsgateEHS.hpp @@ -607,3 +611,18 @@ endif() add_executable(wsgate ${WSGATE_SOURCES}) target_link_libraries(wsgate ${LIBS}) + +#pluginOpenstack +set(PLUGINOPENSTACK_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/pluginOpenstack/nova_token_auth.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pluginOpenstack/pluginOpenstack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pluginTemplate/pluginCommon.cpp) + +if(WIN32) + set(PLUGINOPENSTACK_SOURCE ${PLUGINOPENSTACK_SOURCE} pluginOpenstack/nova_token_auth.hpp pluginTemplate/pluginCommon.hpp config.h) +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +#plugin should go into ./plugins/ folder +set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/plugins) +#build library for openstack +add_library(pluginOpenstack SHARED ${PLUGINOPENSTACK_SOURCE}) +target_link_libraries(pluginOpenstack ${LIBS}) diff --git a/wsgate/nova_token_auth.cpp b/wsgate/nova_token_auth.cpp deleted file mode 100644 index 5008e1e9..00000000 --- a/wsgate/nova_token_auth.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* vim: set et ts=4 sw=4 cindent: - * - * Copyright 2013 Cloudbase Solutions Srl - * - * 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 "nova_token_auth.hpp" - -using namespace pplx; -using namespace std; -using namespace web; -using namespace wsgate; -using namespace utility::conversions; - - -class nova_console_token_auth_impl : public nova_console_token_auth -{ -private: - web::json::value execute_request_and_get_json_value( - web::http::client::http_client& client, - web::http::http_request& request); - - web::json::value get_auth_token_data(std::string osAuthUrl, - std::string osUserName, - std::string osPassword, - std::string osTenantName); - - web::json::value get_console_token_data(std::string authToken, - std::string novaUrl, - std::string consoleToken); - - utility::string_t get_nova_url(web::json::value token_data); - -public: - virtual nova_console_info get_console_info(std::string osAuthUrl, - std::string osUserName, - std::string osPassword, - std::string osTenantName, - std::string consoleToken); -}; - - - -json::value nova_console_token_auth_impl::execute_request_and_get_json_value( - http::client::http_client& client, - http::http_request& request) -{ - auto response_task = client.request(request); - response_task.wait(); - auto response = response_task.get(); - - if(response.status_code() >= 400) - { - throw http_exception(response.status_code(), to_utf8string(response.reason_phrase())); - } - - auto json_task = response.extract_json(); - json_task.wait(); - - return json_task.get(); -} - -json::value nova_console_token_auth_impl::get_auth_token_data( - string osAuthUrl, string osUserName, - string osPassword, string osTenantName) -{ - auto jsonRequestBody = json::value::object(); - auto auth = json::value::object(); - auto cred = json::value::object(); - cred[U("username")] = json::value::string(to_string_t(osUserName)); - cred[U("password")] = json::value::string(to_string_t(osPassword)); - auth[U("passwordCredentials")] = cred; - auth[U("tenantName")] = json::value::string(to_string_t(osTenantName)); - jsonRequestBody[U("auth")] = auth; - http::http_request request(http::methods::POST); - request.set_request_uri(U("tokens")); - request.headers().add(http::header_names::accept, U("application/json")); - request.headers().set_content_type(U("application/json")); - request.set_body(jsonRequestBody); - - http::client::http_client client(to_string_t(osAuthUrl)); - return execute_request_and_get_json_value(client, request); -} - -json::value nova_console_token_auth_impl::get_console_token_data( - string authToken, string novaUrl, string consoleToken) -{ - http::client::http_client client(to_string_t(novaUrl)); - http::uri_builder console_token_uri; - console_token_uri.append(U("os-console-auth-tokens")); - console_token_uri.append(to_string_t(consoleToken)); - - http::http_request request(http::methods::GET); - request.set_request_uri(console_token_uri.to_string()); - request.headers().add(U("X-Auth-Token"), to_string_t(authToken)); - request.headers().add(http::header_names::accept, U("application/json")); - request.headers().set_content_type(U("application/json")); - - return execute_request_and_get_json_value(client, request); -} - -utility::string_t nova_console_token_auth_impl::get_nova_url(web::json::value token_data) -{ - for (auto serviceCatalog : token_data[U("access")][U("serviceCatalog")].as_array()) - if (serviceCatalog[U("name")].as_string() == U("nova")) - return serviceCatalog[U("endpoints")][0][U("adminURL")].as_string(); -} - -nova_console_info nova_console_token_auth_impl::get_console_info( - std::string osAuthUrl, std::string osUserName, - std::string osPassword, std::string osTenantName, - std::string consoleToken) -{ - auto token_data = get_auth_token_data(osAuthUrl, osUserName, - osPassword, osTenantName); - - auto novaUrl = get_nova_url(token_data); - - auto authToken = token_data[U("access")][U("token")] - [U("id")].as_string(); - - auto consoleTokenData = get_console_token_data(to_utf8string(authToken), - to_utf8string(novaUrl), - consoleToken); - - nova_console_info info; - - info.host = to_utf8string(consoleTokenData[U("console")][U("host")].as_string()); - - auto portValue = consoleTokenData[U("console")][U("port")]; - if(portValue.is_string()) - { - istringstream(to_utf8string(portValue.as_string())) >> info.port; - } - else - { - info.port = portValue.as_integer(); - } - - auto internalAccessPathValue = consoleTokenData[U("console")] - [U("internal_access_path")]; - - if(internalAccessPathValue.is_string()) - { - info.internal_access_path = to_utf8string(internalAccessPathValue.as_string()); - } - - return info; -} - - -nova_console_token_auth* nova_console_token_auth_factory::instance = NULL; - - -nova_console_token_auth* nova_console_token_auth_factory::get_instance() -{ - if(!instance) - instance = new nova_console_token_auth_impl(); - - return instance; -} diff --git a/wsgate/pluginManager.cpp b/wsgate/pluginManager.cpp new file mode 100644 index 00000000..f9761ef0 --- /dev/null +++ b/wsgate/pluginManager.cpp @@ -0,0 +1,216 @@ +#include "pluginManager.hpp" +#include +#include +#include + +PluginManager* PluginManager::instance = NULL; + +PluginManager::PluginManager(){ + this->loadPlugins(false); +} + +PluginManager::~PluginManager(){ + this->loadPlugins(true); +} + +void PluginManager::shutDown(){ + delete PluginManager::instance; +} + +void PluginManager::loadPlugins(bool orUnload){ + if (!orUnload){ + std::vector files; + listPlugins("plugins/", files); + for (int i = 0; i < files.size(); i++){ + loadPlugin(files[i]); + } + } + //unload + else{ + for (int i = 0; i < pluginHandles.size(); i++){ + unloadPlugin(pluginHandles[i]); + } + } +} + +PluginManager* PluginManager::getInstance(){ + if (PluginManager::instance == NULL) + PluginManager::instance = new PluginManager(); + return PluginManager::instance; +} + +#define PLUGIN_BUFFER_SIZE 2048 + +bool PluginManager::queryPlugins(std::string query, std::string configFile, std::map& output){ + bool result = false; + //execute queries with respect to the config file + for (int i = 0; i < pluginOrder.size(); i++){ + for (int j = 0; j < pluginNames.size(); j++){ + if (boost::algorithm::ends_with(pluginNames[j], pluginOrder[i])){ + char* buffer = new char[PLUGIN_BUFFER_SIZE]; + result |= functionPointers[j](query.c_str(), configFile.c_str(), buffer); + deserialize(buffer, output); + delete[] buffer; + + doLogging(pluginNames[j], output); + } + } + } + //execute remaining queries + for (int j = 0; j < pluginNames.size(); j++){ + bool found = false; + for (int i = 0; i < pluginOrder.size(); i++){ + if (boost::algorithm::ends_with(pluginNames[j], pluginOrder[i])){ + found = true; + } + } + if (!found){ + char buffer[PLUGIN_BUFFER_SIZE]; + result |= functionPointers[j](query.c_str(), configFile.c_str(), buffer); + deserialize(buffer, output); + + doLogging(pluginNames[j], output); + } + } + return result; +} + +void PluginManager::deserialize(char* serialized, std::map& output){ + std::vector pairs; + std::string input(serialized); + + boost::algorithm::split(pairs, input, boost::is_any_of("\2")); + for (int i = 0; i < pairs.size(); i++){ + std::vector pair; + boost::algorithm::split(pair, pairs[i], boost::is_any_of("\1")); + if (pair.size() == 2) + if (pair[1].length() > 0){ + output[pair[0]] = pair[1]; + } + } +} + +void PluginManager::doLogging(std::string pluginName, std::map output){ + if (output.count("err")){ + wsgate::logger::err << pluginName << ":" << output["err"] << std::endl; + } + + if (output.count("debug")){ + wsgate::logger::debug << pluginName << ":" << output["debug"] << std::endl; + } +} + +#ifdef _WIN32 +#define string2LPCSTR(str) (LPCSTR)str.c_str() +#endif +std::string getexepath() +{ + char result[MAX_PATH]; +#ifdef _WIN32 + return std::string(result, GetCurrentDirectory(MAX_PATH, result)); +#else + return std::string(result, getcwd(result, MAX_PATH)); +#endif +} + +void PluginManager::loadPlugin(std::string fileName){ +#ifdef _WIN32 + LIBHANDLER handle = LoadLibrary(string2LPCSTR(fileName)); +#else + LIBHANDLER handle = dlopen(fileName.c_str(), RTLD_NOW); +#endif + if (handle != NULL){ +#ifdef _WIN32 + queryPluginFUNC function = (queryPluginFUNC)GetProcAddress(handle, "queryPlugin"); +#else + queryPluginFUNC function = (queryPluginFUNC)dlsym(handle, "queryPlugin"); +#endif + if (function != NULL){ + wsgate::logger::debug << "Found plugin " << fileName << std::endl; + pluginHandles.push_back(handle); + functionPointers.push_back(function); + pluginNames.push_back(fileName); + } + else{ + unloadPlugin(handle); + wsgate::logger::err << "Error getting function pointer to \"queryPlugin\" in " << fileName << std::endl; + } + } + else{ +#ifdef _WIN32 + DWORD lastError = GetLastError(); + wsgate::logger::err << "Error loading plugin " << fileName << " ; error code:" << lastError << std::endl; +#else + wsgate::logger::err << "Error loading plugin " << fileName << " ; error message:\"" << dlerror() << "\"" << std::endl; +#endif + } +} + +void PluginManager::unloadPlugin(LIBHANDLER handle){ +#ifdef _WIN32 + FreeLibrary(handle); +#else + dlclose(handle); +#endif +} + +void PluginManager::listPlugins(std::string findPath, std::vector& pluginFileNames){ + std::string path(getexepath()); + //check if path ends with '/' or '\\' +#ifdef _WIN32 + if (!boost::algorithm::ends_with(path, "\\")) + path += "\\"; +#endif + + path += findPath; + findPath = path; + + namespace fs = boost::filesystem; + fs::path directory(findPath); + fs::directory_iterator end; + + if (fs::exists(directory) && fs::is_directory(directory)) + { + for (fs::directory_iterator i(path); i != end; ++i) + { + if (fs::is_regular_file(i->status())) + { + pluginFileNames.push_back(i->path().string()); + } + } + } + +/* WIN32_FIND_DATA file; + size_t length_of_arg; + HANDLE findHandle = INVALID_HANDLE_VALUE; + DWORD dwError = 0; + std::string path(getexepath()); + //find path delimiter + int n = path.length(); + int i = n - 1; + while (path[i] != '\\') i--; + path.erase(i + 1, n); + path += findPath; + findPath = path; + findHandle = FindFirstFile(string2LPCSTR((findPath + "*.dll")), &file); + + if (findHandle == INVALID_HANDLE_VALUE) + { + wsgate::logger::err << "Bad find path." << std::endl; + return; + } + + do + { + if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + //TODO find recursively + } + else + { + pluginFileNames.push_back(findPath + std::string(file.cFileName)); + } + } while (FindNextFile(findHandle, &file) != 0); + + FindClose(findHandle);*/ +} diff --git a/wsgate/pluginManager.hpp b/wsgate/pluginManager.hpp new file mode 100644 index 00000000..96bbeedc --- /dev/null +++ b/wsgate/pluginManager.hpp @@ -0,0 +1,44 @@ +#ifndef _PLUGIN_MANAGER_ +#define _PLUGIN_MANAGER_ + +#include "logging.hpp" + +#ifdef _WIN32 +#include +#define LIBHANDLER HMODULE +#else +#include +#include +#define LIBHANDLER void* +#define MAX_PATH PATH_MAX +#endif + +#include +#include +#include + +typedef bool(*queryPluginFUNC)(const char* queryInput, const char* configFile, char* resultBuffer); + +class PluginManager { +public: + static PluginManager* getInstance(); + static void shutDown(); + bool queryPlugins(std::string query, std::string configFile, std::map& output); + inline void setOrder(std::vector order){ pluginOrder = order; } +private: + static PluginManager* instance; + void listPlugins(std::string findPath, std::vector& pluginFileNames); + void loadPlugins(bool orUnload); + void loadPlugin(std::string fileName); + void unloadPlugin(LIBHANDLER handle); + void deserialize(char* serialized, std::map& output); + void doLogging(std::string pluginName, std::map output); + std::vector functionPointers; + PluginManager(); + ~PluginManager(); + + std::vector pluginHandles; + std::vector pluginOrder; + std::vector pluginNames; +}; +#endif //_PLUGIN_MANAGER_ diff --git a/wsgate/pluginOpenstack/nova_token_auth.cpp b/wsgate/pluginOpenstack/nova_token_auth.cpp new file mode 100644 index 00000000..0cbb4bf3 --- /dev/null +++ b/wsgate/pluginOpenstack/nova_token_auth.cpp @@ -0,0 +1,296 @@ +/* vim: set et ts=4 sw=4 cindent: + * + * Copyright 2013 Cloudbase Solutions Srl + * + * 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. + */ +#ifdef HAVE_CONFIG_H +#ifdef _WIN32 +#include "../config.h" +#else +#include "../build/config.h" +#endif +#endif + +#include +#include +#include + +#include "nova_token_auth.hpp" +#include + +using namespace pplx; +using namespace std; +using namespace web; +using namespace wsgate; +using namespace utility::conversions; + +class nova_console_token_auth_impl : public nova_console_token_auth +{ +private: + web::http::http_response execute_request_and_get_response( + web::http::client::http_client& client, + web::http::http_request& request); + + web::json::value get_json_from_response(web::http::http_response response); + + std::pair get_auth_token_data_v2(std::string osAuthUrl, + std::string osUserName, + std::string osPassword, + std::string osTenantName, + std::string osRegion); + + std::pair get_auth_token_data_v3(std::string osAuthUrl, + std::string osUserName, + std::string osPassword, + std::string osTenantName, + std::string osRegion); + + web::json::value get_console_token_data(std::string authToken, + std::string novaUrl, + std::string consoleToken); + +public: + virtual nova_console_info get_console_info(std::string osAuthUrl, + std::string osUserName, + std::string osPassword, + std::string osTenantName, + std::string consoleToken, + std::string keystoneVersion, + std::string osRegion); +}; + + + +http::http_response nova_console_token_auth_impl::execute_request_and_get_response( + http::client::http_client& client, + http::http_request& request) +{ + auto response_task = client.request(request); + response_task.wait(); + auto response = response_task.get(); + + if(response.status_code() >= 400) + { + throw http_exception(response.status_code(), to_utf8string(response.reason_phrase())); + } + + return response; +} + +json::value nova_console_token_auth_impl::get_json_from_response( + http::http_response response) +{ + auto json_task = response.extract_json(); + json_task.wait(); + + return json_task.get(); +} + +std::pair nova_console_token_auth_impl::get_auth_token_data_v2( + string osAuthUrl, string osUserName, + string osPassword, string osTenantName, + string osRegion) +{ + auto jsonRequestBody = json::value::object(); + auto auth = json::value::object(); + auto cred = json::value::object(); + + cred[U("username")] = json::value::string(utility::conversions::to_string_t(osUserName)); + cred[U("password")] = json::value::string(utility::conversions::to_string_t(osPassword)); + auth[U("passwordCredentials")] = cred; + auth[U("tenantName")] = json::value::string(utility::conversions::to_string_t(osTenantName)); + jsonRequestBody[U("auth")] = auth; + http::http_request request(http::methods::POST); + request.set_request_uri(U("tokens")); + request.headers().add(http::header_names::accept, U("application/json")); + request.headers().set_content_type(U("application/json")); + request.set_body(jsonRequestBody); + + http::client::http_client client(to_string_t(osAuthUrl)); + auto response_json = get_json_from_response(execute_request_and_get_response(client, request)); + + utility::string_t authToken; + utility::string_t novaUrl; + //get the authentication token + authToken = response_json[U("access")][U("token")][U("id")].as_string(); + + //get the nova api endpoint + for (auto serviceCatalog : response_json[U("access")][U("serviceCatalog")].as_array()) + if (serviceCatalog[U("name")].as_string() == U("nova")){ + if (osRegion.empty()){ + novaUrl = serviceCatalog[U("endpoints")][0][U("adminURL")].as_string(); + } + else{ + for (auto endpoint : serviceCatalog[U("endpoints")].as_array()){ + if (endpoint[U("region")].as_string() == to_string_t(osRegion)){ + novaUrl = endpoint[U("adminURL")].as_string(); + } + } + } + } + + + return std::pair( + to_utf8string(authToken), + to_utf8string(novaUrl)); +} + +std::pair nova_console_token_auth_impl::get_auth_token_data_v3( + string osAuthUrl, string osUserName, + string osPassword, string osTenantName, + string osRegion) +{ + auto jsonRequestBody = json::value::object(); + auto auth = json::value::object(); + auto identity = json::value::object(); + auto methods = json::value::array(); + methods[0] = json::value::string(to_string_t("password")); + + auto password = json::value::object(); + auto user = json::value::object(); + user[U("name")] = json::value::string(to_string_t(osUserName)); + user[U("password")] = json::value::string(to_string_t(osPassword)); + + auto domain = json::value::object(); + domain[U("id")] = json::value::string(to_string_t("default")); + + user[U("domain")] = domain; + password[U("user")] = user; + identity[U("methods")] = methods; + identity[U("password")] = password; + + auto scope = json::value::object(); + auto project = json::value::object(); + project[U("name")] = json::value::string(to_string_t(osTenantName)); + project[U("domain")] = domain; + scope[U("project")] = project; + + auth[U("identity")] = identity; + auth[U("scope")] = scope; + jsonRequestBody[U("auth")] = auth; + + http::http_request request(http::methods::POST); + request.set_request_uri(U("auth/tokens")); + request.headers().add(http::header_names::accept, U("application/json")); + request.headers().set_content_type(U("application/json")); + request.set_body(jsonRequestBody); + + http::client::http_client client(to_string_t(osAuthUrl)); + auto response = execute_request_and_get_response(client, request); + auto response_json = get_json_from_response(response); + + utility::string_t authToken; + utility::string_t novaUrl; + //get the authentication token + authToken = response.headers()[U("X-Subject-Token")]; + + //get the nova api endpoint + for (auto serviceCatalog : response_json[U("token")][U("catalog")].as_array()) + if (serviceCatalog[U("name")].as_string() == U("nova")) + for (auto endpoint : serviceCatalog[U("endpoints")].as_array()) + if (endpoint[U("interface")].as_string() == U("admin") && + (endpoint[U("region")].as_string() == to_string_t(osRegion) || osRegion.empty())){ + novaUrl = endpoint[U("url")].as_string(); + } + + return std::pair( + to_utf8string(authToken), + to_utf8string(novaUrl)); +} + +json::value nova_console_token_auth_impl::get_console_token_data( + string authToken, string novaUrl, string consoleToken) +{ + http::client::http_client client(utility::conversions::to_string_t(novaUrl)); + http::uri_builder console_token_uri; + console_token_uri.append(U("os-console-auth-tokens")); + console_token_uri.append(utility::conversions::to_string_t(consoleToken)); + + http::http_request request(http::methods::GET); + request.set_request_uri(console_token_uri.to_string()); + request.headers().add(U("X-Auth-Token"), utility::conversions::to_string_t(authToken)); + request.headers().add(http::header_names::accept, U("application/json")); + request.headers().set_content_type(U("application/json")); + + return get_json_from_response(execute_request_and_get_response(client, request)); +} + +nova_console_info nova_console_token_auth_impl::get_console_info( + std::string osAuthUrl, std::string osUserName, + std::string osPassword, std::string osTenantName, + std::string consoleToken, std::string keystoneVersion, + std::string osRegion) +{ + std::string authToken; + std::string novaUrl; + + if (keystoneVersion == KEYSTONE_V2){ + std::tie(authToken, novaUrl) = get_auth_token_data_v2(osAuthUrl, + osUserName, + osPassword, + osTenantName, + osRegion); + } + else if (keystoneVersion == KEYSTONE_V3){ + std::tie(authToken, novaUrl) = get_auth_token_data_v3(osAuthUrl, + osUserName, + osPassword, + osTenantName, + osRegion); + } + else{ + throw std::invalid_argument("Unknown Keystone version"); + } + + nova_console_info info; + + auto consoleTokenData = get_console_token_data( + authToken, + novaUrl, + consoleToken); + + info.host = to_utf8string(consoleTokenData[U("console")][U("host")].as_string()); + + auto portValue = consoleTokenData[U("console")][U("port")]; + if(portValue.is_string()) + { + istringstream(to_utf8string(portValue.as_string())) >> info.port; + } + else + { + info.port = portValue.as_integer(); + } + + auto internalAccessPathValue = consoleTokenData[U("console")] + [U("internal_access_path")]; + + if(internalAccessPathValue.is_string()) + { + info.internal_access_path = to_utf8string(internalAccessPathValue.as_string()); + } + + return info; +} + + +nova_console_token_auth* nova_console_token_auth_factory::instance = NULL; + + +nova_console_token_auth* nova_console_token_auth_factory::get_instance() +{ + if(!instance) + instance = new nova_console_token_auth_impl(); + + return instance; +} diff --git a/wsgate/nova_token_auth.hpp b/wsgate/pluginOpenstack/nova_token_auth.hpp similarity index 90% rename from wsgate/nova_token_auth.hpp rename to wsgate/pluginOpenstack/nova_token_auth.hpp index 45fd9c87..90aa9414 100644 --- a/wsgate/nova_token_auth.hpp +++ b/wsgate/pluginOpenstack/nova_token_auth.hpp @@ -14,13 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #ifndef _NOVA_TOKEN_AUTH_ #define _NOVA_TOKEN_AUTH_ #include #include +#define KEYSTONE_V2 "v2.0" +#define KEYSTONE_V3 "v3" + namespace wsgate { class http_exception: public std::exception { @@ -66,7 +68,9 @@ namespace wsgate { std::string osUserName, std::string osPassword, std::string osTenantName, - std::string consoleToken) = 0; + std::string consoleToken, + std::string keystoneVersion, + std::string osRegion) = 0; }; diff --git a/wsgate/pluginOpenstack/pluginOpenstack.cpp b/wsgate/pluginOpenstack/pluginOpenstack.cpp new file mode 100644 index 00000000..0b8704ee --- /dev/null +++ b/wsgate/pluginOpenstack/pluginOpenstack.cpp @@ -0,0 +1,148 @@ +#include "../pluginTemplate/pluginCommon.hpp" +#include "nova_token_auth.hpp" +#include +#include +#include +#include +#include +#include + +using namespace wsgate; + +std::string m_sOpenStackAuthUrl; +std::string m_sOpenStackUsername; +std::string m_sOpenStackPassword; +std::string m_sOpenStackTenantName; +std::string m_sOpenStackKeystoneVersion; +std::string m_sOpenStackRegion; +std::string m_sHyperVHostUsername; +std::string m_sHyperVHostPassword; + +bool readConfigFile(std::map & result){ + std::stringstream err; + bool returnValue = false; + + if (result.count("configfile")){ + try { + boost::property_tree::ptree pt; + boost::property_tree::ini_parser::read_ini(result["configfile"], pt); + + try { + if (pt.get_optional("openstack.authurl")) { + m_sOpenStackAuthUrl.assign(pt.get("openstack.authurl")); + } + else { + m_sOpenStackAuthUrl.clear(); + } + if (pt.get_optional("openstack.username")) { + m_sOpenStackUsername.assign(pt.get("openstack.username")); + } + else { + m_sOpenStackUsername.clear(); + } + if (pt.get_optional("openstack.password")) { + m_sOpenStackPassword.assign(pt.get("openstack.password")); + } + else { + m_sOpenStackPassword.clear(); + } + if (pt.get_optional("openstack.tenantname")) { + m_sOpenStackTenantName.assign(pt.get("openstack.tenantname")); + } + else { + m_sOpenStackTenantName.clear(); + } + if (pt.get_optional("openstack.keystoneversion")) { + m_sOpenStackKeystoneVersion.assign(pt.get("openstack.keystoneversion")); + } + else { + m_sOpenStackKeystoneVersion = KEYSTONE_V2; + } + if (pt.get_optional("openstack.region")) { + m_sOpenStackRegion.assign(pt.get("openstack.region")); + } + else { + m_sOpenStackRegion.clear(); + } + if (pt.get_optional("hyperv.hostusername")) { + m_sHyperVHostUsername.assign(pt.get("hyperv.hostusername")); + } + else { + m_sHyperVHostUsername.clear(); + } + if (pt.get_optional("hyperv.hostpassword")) { + m_sHyperVHostPassword.assign(pt.get("hyperv.hostpassword")); + } + else { + m_sHyperVHostPassword.clear(); + } + returnValue = true; + } + catch (const std::exception & e) { + err << e.what() << std::endl; + err << e.what() << std::endl; + } + + } + catch (const boost::property_tree::ini_parser_error &e) { + err << e.what() << std::endl; + } + } + else{ + err << "Plugin got no config file path" << std::endl; + } + result["err"] = result["err"] + err.str(); + return returnValue; +} + +bool entryPoint(std::map formValues, std::map & result){ + bool returnValue = false; + std::stringstream debug; + std::stringstream err; + + if (readConfigFile(result)){ + if (formValues.count("token") && formValues.count("title")) + { + // OpenStack console authentication + try + { + debug << "Starting OpenStack token authentication" << std::endl; + + std::string tokenId = formValues["token"]; + + nova_console_token_auth* token_auth = nova_console_token_auth_factory::get_instance(); + + nova_console_info info = token_auth->get_console_info(m_sOpenStackAuthUrl, m_sOpenStackUsername, + m_sOpenStackPassword, m_sOpenStackTenantName, + tokenId, m_sOpenStackKeystoneVersion, m_sOpenStackRegion); + + debug << "Host: " << info.host << " Port: " << info.port + << " Internal access path: " << info.internal_access_path + << std::endl; + + result["rdphost"] = info.host; + result["rdpport"] = boost::lexical_cast(info.port); + result["rdppcb"] = info.internal_access_path; + + result["rdpuser"] = m_sHyperVHostUsername; + result["rdppass"] = m_sHyperVHostPassword; + + returnValue = true; + } + catch (std::exception& ex) + { + err << "OpenStack token authentication failed: " << ex.what() << std::endl; + } + } + } + else{ + err << "Error parsing config file." << std::endl; + } + + if (!err.str().empty()) + result["err"] = err.str(); + + if (!debug.str().empty()) + result["debug"] = debug.str(); + return returnValue; +} \ No newline at end of file diff --git a/wsgate/pluginTemplate/pluginCommon.cpp b/wsgate/pluginTemplate/pluginCommon.cpp new file mode 100644 index 00000000..66830ae4 --- /dev/null +++ b/wsgate/pluginTemplate/pluginCommon.cpp @@ -0,0 +1,59 @@ +#include "pluginCommon.hpp" +#include + +void split(std::string input, std::vector& tokens, char delim){ + std::stringstream stream(input); + std::string token; + while (std::getline(stream, token, delim)){ + tokens.push_back(token); + } +} +extern "C" { +#ifdef _WIN32 +bool EXPORT_FUNC queryPlugin(const char* queryInput, const char* configFile, char* resultBuffer) +#else +bool queryPlugin(const char* queryInput, const char* configFile, char* resultBuffer) +#endif +{ + std::map params; + //split the link in link body and parameter body + std::vector trims; + split(std::string(queryInput), trims, '?'); + //split parameters + std::vector tokens; + split(trims[1], tokens, '&'); + for (int i = 0; i < tokens.size(); i++){ + //split by name and value + std::vector splits; + split(tokens[i], splits, '='); + //test is value exists + if (splits.size() == 1){ + params[splits[0]] = std::string(); + } + else{ + params[splits[0]] = splits[1]; + } + splits.resize(0); + } + + std::map result; + result["configfile"] = std::string(configFile); + + bool success = entryPoint(params, result); + + //serialize the results + std::string serialized; + for (std::map::iterator i = result.begin(); i != result.end(); i++){ + //each key-value pair is stored as + //key\1value\2 + serialized += i->first; + serialized += "\1"; + serialized += i->second; + serialized += "\2"; + } + + strcpy(resultBuffer, serialized.c_str()); + + return success; +} +} diff --git a/wsgate/pluginTemplate/pluginCommon.hpp b/wsgate/pluginTemplate/pluginCommon.hpp new file mode 100644 index 00000000..1deb624c --- /dev/null +++ b/wsgate/pluginTemplate/pluginCommon.hpp @@ -0,0 +1,25 @@ +#ifndef _PLUGIN_COMMON_ +#define _PLUGIN_COMMON_ + +#ifdef _WIN32 +#define EXPORT_FUNC __declspec(dllexport) +#endif + +#include +#include +#include +#include + + +bool entryPoint(std::map formValues, std::map & result); + +extern "C" +{ +#ifdef _WIN32 + bool EXPORT_FUNC queryPlugin(const char* queryInput, const char* configFile, char* resultBuffer); +#else + bool queryPlugin(const char* queryInput, const char* configFile, char* resultBuffer); +#endif +} + +#endif //_PLUGIN_COMMON_ diff --git a/wsgate/pluginTemplate/pluginExample.cpp b/wsgate/pluginTemplate/pluginExample.cpp new file mode 100644 index 00000000..b18f14c9 --- /dev/null +++ b/wsgate/pluginTemplate/pluginExample.cpp @@ -0,0 +1,27 @@ +#include "pluginCommon.h" + +bool entryPoint(std::map formValues, std::map & result){ + //to test if a field exists use formValues.count("field_name") + //to get a value for a field use formValues["field_name"] + // + //to change the RDP parameters fill the following fields + //result["rdphost"] = "127.0.0.1" + //result["rdpport"] = "12345" + //result["rdppcb"] = "pcb id" + //result["rdpuser"] = "username" + //result["rdppass"] = "password" + // + //to pass a debug message to wsgate use + //result["debug"] = "somedebug message" + // + //debug messages can be split by new line character and + //will appear as separate debug messages + //result["debug"] = "somedebug message" + "\n" + "otherdebug message" + // + //to pass an error message + //result["err"] = "error message here" + "\n" + "error message there" + // + //to take the path for the config file that WebConnect takes as "-c" parameter use + //result["configfile"] + return true; +} diff --git a/wsgate/wsGateService.cpp b/wsgate/wsGateService.cpp index f2a12efb..f88ee3d1 100644 --- a/wsgate/wsGateService.cpp +++ b/wsgate/wsGateService.cpp @@ -42,15 +42,15 @@ namespace wsgate{ void WsGateService::RunService(){ WsGateService::g_signaled = false; - // On Windows, always set out working dir to ../ relatively seen from - // the binary's path. + + //change path to the running binary path p(m_sModulePath); - string wdir(p.branch_path().branch_path().string()); - chdir(wdir.c_str()); + chdir(p.branch_path().string().c_str()); + char *argv[] = { strdup("wsgate"), strdup("-c"), - strdup("etc/wsgate.ini"), + strdup("../etc/wsgate.ini"), NULL }; int r = _service_main(3, argv); diff --git a/wsgate/wsgate.ini.sample.in b/wsgate/wsgate.ini.sample.in index 8b9a80fc..c4a9c938 100644 --- a/wsgate/wsgate.ini.sample.in +++ b/wsgate/wsgate.ini.sample.in @@ -151,9 +151,12 @@ nofullwindowdrag = true [openstack] #authurl = http://10.0.0.1:5000/v2.0 +#Specify the version for keystone +#keystoneversion = v2.0 or v3 #username = admin #password = secret #tenantname = admin +#region = optional region [hyperv] diff --git a/wsgate/wsgateEHS.cpp b/wsgate/wsgateEHS.cpp index fd4998c3..58daa239 100644 --- a/wsgate/wsgateEHS.cpp +++ b/wsgate/wsgateEHS.cpp @@ -1,5 +1,6 @@ #include "wsgateEHS.hpp" #include "wsgate.hpp" +#include "pluginManager.hpp" namespace wsgate{ WsGate::MimeType WsGate::simpleMime(const string & filename) @@ -273,39 +274,19 @@ namespace wsgate{ bool setCookie = true; EmbeddedContext embeddedContext = CONTEXT_PLAIN; - if(boost::starts_with(uri, "/wsgate?token=")) - { - // OpenStack console authentication - setCookie = false; - try - { - log::info << "Starting OpenStack token authentication" << endl; - - string tokenId = request->FormValues("token").m_sBody; - - nova_console_token_auth* token_auth = nova_console_token_auth_factory::get_instance(); - - nova_console_info info = token_auth->get_console_info(m_sOpenStackAuthUrl, m_sOpenStackUsername, - m_sOpenStackPassword, m_sOpenStackTenantName, - tokenId); - - log::info << "Host: " << info.host << " Port: " << info.port - << " Internal access path: " << info.internal_access_path - << endl; - - rdphost = info.host; - rdpport = info.port; - rdppcb = info.internal_access_path; - - rdpuser = m_sHyperVHostUsername; - rdppass = m_sHyperVHostPassword; + if (boost::contains(uri, "?")){ + std::map pluginOutput; + if (PluginManager::getInstance()->queryPlugins(uri, m_sConfigFile, pluginOutput)){ + //wsgate will get auth info from the plugin + setCookie = false; embeddedContext = CONTEXT_EMBEDDED; - } - catch(exception& ex) - { - log::err << "OpenStack token authentication failed: " << ex.what() << endl; - return HTTPRESPONSECODE_400_BADREQUEST; + + rdphost = pluginOutput["rdphost"]; + rdpport = std::stoi(pluginOutput["rdpport"]); + rdppcb = pluginOutput["rdppcb"]; + rdpuser = pluginOutput["rdpuser"]; + rdppass = pluginOutput["rdppass"]; } } @@ -899,35 +880,12 @@ namespace wsgate{ } else { m_sHostname.clear(); } - if (pt.get_optional("openstack.authurl")) { - m_sOpenStackAuthUrl.assign(pt.get("openstack.authurl")); - } else { - m_sOpenStackAuthUrl.clear(); - } - if (pt.get_optional("openstack.username")) { - m_sOpenStackUsername.assign(pt.get("openstack.username")); - } else { - m_sOpenStackUsername.clear(); - } - if (pt.get_optional("openstack.password")) { - m_sOpenStackPassword.assign(pt.get("openstack.password")); - } else { - m_sOpenStackPassword.clear(); - } - if (pt.get_optional("openstack.tenantname")) { - m_sOpenStackTenantName.assign(pt.get("openstack.tenantname")); - } else { - m_sOpenStackTenantName.clear(); - } - if (pt.get_optional("hyperv.hostusername")) { - m_sHyperVHostUsername.assign(pt.get("hyperv.hostusername")); - } else { - m_sHyperVHostUsername.clear(); - } - if (pt.get_optional("hyperv.hostpassword")) { - m_sHyperVHostPassword.assign(pt.get("hyperv.hostpassword")); - } else { - m_sHyperVHostPassword.clear(); + + if (pt.get_optional("plugins.order")){ + std::string str = pt.get("plugins.order"); + std::vector order; + boost::split(order, str, boost::is_any_of(";")); + PluginManager::getInstance()->setOrder(order); } } catch (const tracing::invalid_argument & e) { cerr << e.what() << endl; diff --git a/wsgate/wsgateEHS.hpp b/wsgate/wsgateEHS.hpp index ef72cefb..fa7940e2 100644 --- a/wsgate/wsgateEHS.hpp +++ b/wsgate/wsgateEHS.hpp @@ -34,7 +34,6 @@ #include "logging.hpp" #include "wsendpoint.hpp" #include "myrawsocket.hpp" -#include "nova_token_auth.hpp" using namespace std; using boost::algorithm::iequals; @@ -115,6 +114,8 @@ namespace wsgate{ string m_sOpenStackUsername; string m_sOpenStackPassword; string m_sOpenStackTenantName; + string m_sOpenStackKeystoneVersion; + string m_sOpenStackRegion; string m_sHyperVHostUsername; string m_sHyperVHostPassword; @@ -133,4 +134,5 @@ namespace wsgate{ }; } -#endif \ No newline at end of file +#endif +