From e8cc0a38b45046463f36ee5dd5e26c776aca6896 Mon Sep 17 00:00:00 2001 From: Bartosz Wieczorek Date: Tue, 23 Sep 2025 14:32:15 +0200 Subject: [PATCH] Make implementation that uses only libwebsockets --- CMakeLists.txt | 5 +- PAM/ssh/CMakeLists.txt | 26 ++- PAM/ssh/bin/CMakeLists.txt | 1 - PAM/ssh/bin/rublon_application.cpp | 16 +- PAM/ssh/include/rublon/bits.hpp | 4 +- PAM/ssh/include/rublon/core_handler.hpp | 3 +- PAM/ssh/include/rublon/curl.hpp | 32 --- PAM/ssh/include/rublon/utils.hpp | 34 +++ PAM/ssh/include/rublon/websockets.hpp | 5 +- PAM/ssh/include/rublon/ws_http.hpp | 285 ++++++++++++++++++++++++ PAM/ssh/lib/CMakeLists.txt | 1 - 11 files changed, 353 insertions(+), 59 deletions(-) create mode 100644 PAM/ssh/include/rublon/ws_http.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cd38a3b..2bb8c09 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_CXX_EXTENSIONS NO) set(CMAKE_POSITION_INDEPENDENT_CODE YES) -add_compile_options(-Wall -Wextra -Wno-format-security) +include(CheckIPOSupported) + +set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +add_compile_options(-Wall -Wextra -Wno-format-security -fvisibility=hidden -fno-exceptions) # add_compile_options(-g -fsanitize=address,undefined,float-divide-by-zero,float-cast-overflow,null -fsanitize-address-use-after-scope -fno-sanitize-recover=all -fno-sanitize=alignment -fno-omit-frame-pointer) # add_link_options(-g -fsanitize=address,undefined,float-divide-by-zero,float-cast-overflow,null -fsanitize-address-use-after-scope -fno-sanitize-recover=all -fno-sanitize=alignment -fno-omit-frame-pointer) diff --git a/PAM/ssh/CMakeLists.txt b/PAM/ssh/CMakeLists.txt index d00c296..71c3b48 100755 --- a/PAM/ssh/CMakeLists.txt +++ b/PAM/ssh/CMakeLists.txt @@ -8,6 +8,7 @@ set(INC ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler_interface.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/curl.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/ws_http.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error_handler.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/finish.hpp @@ -68,6 +69,7 @@ set(LWS_WITH_SHARED OFF) set(LWS_ROLE_RAW_FILE OFF) set(LWS_ROLE_DBUS OFF) + set(LWS_WITHOUT_DAEMONIZE ON) set(LWS_WITHOUT_SERVER ON) set(LWS_WITHOUT_TESTAPPS ON) @@ -77,12 +79,16 @@ set(LWS_WITHOUT_TEST_SERVER ON) set(LWS_WITHOUT_TEST_SERVER_EXTPOLL ON) set(LWS_UNIX_SOCK OFF) - set(LWS_WITH_DIR OFF) set(LWS_WITH_FILE_OPS OFF) set(LWS_FOR_GITOHASHI OFF) set(LWS_WITH_HTTP2 OFF) set(LWS_WITH_HTTP_BASIC_AUTH OFF) +set(LWS_WITH_HTTP_DIGEST_AUTH OFF) +set(LWS_WITH_HTTP_UNCOMMON_HEADERS OFF) +set(LWS_WITH_SYS_STATE OFF) +set(LWS_WITH_SYS_SMD OFF) + set(LWS_WITH_JPEG OFF) set(LWS_WITH_JSONRPC OFF) set(LWS_WITH_LEJP OFF) @@ -99,11 +105,14 @@ set(LWS_WITH_UDP OFF) set(LWS_WITH_HTTP_STREAM_COMPRESSION OFF) set(LWS_WITH_HTTP_BROTLI OFF) set(LWS_WITH_ZLIB OFF) +set(LWS_WITH_SEQUENCER OFF) +set(LWS_WITH_LWSAC OFF) -set(RAPIDJSON_BUILD_DOC OFF CACHE BOOL "" FORCE) -set(RAPIDJSON_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -set(RAPIDJSON_BUILD_TESTS OFF CACHE BOOL "" FORCE) -set(RAPIDJSON_BUILD_CXX17 ON CACHE BOOL "" FORCE) +set(RAPIDJSON_BUILD_DOC OFF CACHE BOOL "" FORCE) +set(RAPIDJSON_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(RAPIDJSON_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(RAPIDJSON_BUILD_CXX17 ON CACHE BOOL "" FORCE) +set(RAPIDJSON_HAS_STDSTRING OFF) cmake_policy(SET CMP0077 NEW) @@ -118,13 +127,6 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(libwebsockets) - - -set(RAPIDJSON_BUILD_DOC OFF) -set(RAPIDJSON_BUILD_EXAMPLES OFF) -set(RAPIDJSON_BUILD_TESTS OFF) -set(RAPIDJSON_HAS_STDSTRING OFF) - FetchContent_Declare( RapidJSON URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.zip diff --git a/PAM/ssh/bin/CMakeLists.txt b/PAM/ssh/bin/CMakeLists.txt index 7949185..7fb47d6 100644 --- a/PAM/ssh/bin/CMakeLists.txt +++ b/PAM/ssh/bin/CMakeLists.txt @@ -6,7 +6,6 @@ target_link_libraries(rublon_application PUBLIC rublon-ssh-ifc websockets - -lcurl -lssl -lcrypto ) diff --git a/PAM/ssh/bin/rublon_application.cpp b/PAM/ssh/bin/rublon_application.cpp index 19d998b..0a754ff 100644 --- a/PAM/ssh/bin/rublon_application.cpp +++ b/PAM/ssh/bin/rublon_application.cpp @@ -70,14 +70,14 @@ int main(int argc, const char ** argv) { return printAuthMessageAndExit(rublon::ErrorHandler{pam, session.config()}.printErrorDetails(error)); }; - { - CheckApplication ca{session}; - auto ret = ca.call(CH, session.config().systemToken).or_else(mapError); - if(not ret.has_value()) { - log(LogLevel::Error, "Check Application step failed, check configration"); - return PAM_MAXTRIES; - } - } + // { + // CheckApplication ca{session}; + // auto ret = ca.call(CH, session.config().systemToken).or_else(mapError); + // if(not ret.has_value()) { + // log(LogLevel::Error, "Check Application step failed, check configration"); + // return PAM_MAXTRIES; + // } + // } auto ret = Init{session} .handle(CH, pam) // diff --git a/PAM/ssh/include/rublon/bits.hpp b/PAM/ssh/include/rublon/bits.hpp index c69cc19..90cef33 100644 --- a/PAM/ssh/include/rublon/bits.hpp +++ b/PAM/ssh/include/rublon/bits.hpp @@ -1,7 +1,9 @@ #pragma once #include + #include +#include #ifndef RUBLON_USE_STDOUT #include @@ -18,5 +20,5 @@ using Pam_t = PamStub; #endif namespace rublon { -using CoreHandler_t = CoreHandler< CURL >; +using CoreHandler_t = CoreHandler< LWS >; } // namespace rublon diff --git a/PAM/ssh/include/rublon/core_handler.hpp b/PAM/ssh/include/rublon/core_handler.hpp index cf74d6f..e27f26e 100755 --- a/PAM/ssh/include/rublon/core_handler.hpp +++ b/PAM/ssh/include/rublon/core_handler.hpp @@ -13,6 +13,7 @@ namespace rublon { + template < typename HttpHandler = CURL > class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { std::reference_wrapper< const Configuration > _config; @@ -163,9 +164,9 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { request.headers["Accept"] = "application/json"; stringifyTo(body, request.body); - signRequest(request); std::pmr::string uri{&memoryResource}; + uri.reserve(conservative_estimate(config().apiServer, path.size())); uri += config().apiServer; uri += path.data(); diff --git a/PAM/ssh/include/rublon/curl.hpp b/PAM/ssh/include/rublon/curl.hpp index 4e7b7fc..b42b2fe 100644 --- a/PAM/ssh/include/rublon/curl.hpp +++ b/PAM/ssh/include/rublon/curl.hpp @@ -18,38 +18,6 @@ namespace { } } // namespace -struct Request { - std::pmr::memory_resource * _mr; - - std::pmr::map< std::pmr::string, std::pmr::string > headers; - std::pmr::string body; - - public: - Request(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {}; - - Request(const Request & res) = delete; - Request & operator=(const Request & res) = delete; - - Request(Request && res) = delete; - Request & operator=(Request &&) = delete; -}; - -struct Response { - std::pmr::memory_resource * _mr; - - std::pmr::map< std::pmr::string, std::pmr::string, ci_less > headers; - std::pmr::string body; - - public: - Response(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {}; - - Response(const Response & res) = delete; - Response & operator=(const Response & res) = delete; - - Response(Response && res) noexcept = default; - Response & operator=(Response && res) noexcept = default; -}; - class CURL { std::unique_ptr< ::CURL, void (*)(::CURL *) > curl; std::reference_wrapper< const Configuration > _config; diff --git a/PAM/ssh/include/rublon/utils.hpp b/PAM/ssh/include/rublon/utils.hpp index a8bb5a8..47d06fa 100755 --- a/PAM/ssh/include/rublon/utils.hpp +++ b/PAM/ssh/include/rublon/utils.hpp @@ -21,6 +21,7 @@ #include namespace rublon { + inline bool fileGood(const std::filesystem::path & path) { std::ifstream file(path); return file.good(); @@ -290,6 +291,39 @@ struct ci_less { } }; +struct Request { + std::pmr::memory_resource * _mr; + + std::pmr::map< std::pmr::string, std::pmr::string > headers; + std::pmr::string body; + + public: + Request(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {}; + + Request(const Request & res) = delete; + Request & operator=(const Request & res) = delete; + + Request(Request && res) = delete; + Request & operator=(Request &&) = delete; +}; + +struct Response { + std::pmr::memory_resource * _mr; + + std::pmr::map< std::pmr::string, std::pmr::string, ci_less > headers; + std::pmr::string body; + + public: + Response(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {}; + + Response(const Response & res) = delete; + Response & operator=(const Response & res) = delete; + + Response(Response && res) noexcept = default; + Response & operator=(Response && res) noexcept = default; +}; + + template < typename Out, typename... Types > constexpr std::array< Out, sizeof...(Types) > make_array(Types... names) { return {std::forward< Types >(names)...}; diff --git a/PAM/ssh/include/rublon/websockets.hpp b/PAM/ssh/include/rublon/websockets.hpp index 38328a3..507f8fc 100644 --- a/PAM/ssh/include/rublon/websockets.hpp +++ b/PAM/ssh/include/rublon/websockets.hpp @@ -24,7 +24,7 @@ enum TransactionConfirmationStatus { transactionConfirmed, transactionDenied }; struct RublonEventData { TransactionConfirmationStatus status; - StaticString< 32 > transactionID; /// TODO tid verification? + StaticString< 32 > transactionID; /// TODO: tid verification? std::optional< StaticString< 60 > > accessToken; }; @@ -300,5 +300,6 @@ class WebSocket { const struct lws_protocols WebSocket::protocols[] = { // {"wss", WebSocket::callback_ws, 1024, 1024, 0, NULL, 0}, - {nullptr, nullptr, 0, 0, 0, nullptr, 0}}; + {nullptr, nullptr, 0, 0, 0, nullptr, 0} +}; } // namespace rublon diff --git a/PAM/ssh/include/rublon/ws_http.hpp b/PAM/ssh/include/rublon/ws_http.hpp new file mode 100644 index 0000000..d7fbcd9 --- /dev/null +++ b/PAM/ssh/include/rublon/ws_http.hpp @@ -0,0 +1,285 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include + +namespace rublon { +// Struktura do obsługi połączenia WebSocket +class LWS { + std::reference_wrapper< const Configuration > _config; + + const Configuration & conf() const noexcept { + return _config.get(); + } + + struct session_data { + const Request & request; + Response & response; + int status{}; + bool done{false}; + }; + + static int callback(struct lws * wsi, enum lws_callback_reasons reason, void * user, void * in, size_t len) { + auto * data = reinterpret_cast< session_data * >(user); + int n; + + switch(reason) { + /* because we are protocols[0] ... */ + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + log(LogLevel::Error, "CLIENT_CONNECTION_ERROR: %s\n", in ? ( char * ) in : "(null)"); + if(data) + data->done = true; + lws_cancel_service(lws_get_context(wsi)); + return 0; + + case LWS_CALLBACK_CLOSED_CLIENT_HTTP: + if(data) + data->done = true; + lws_cancel_service(lws_get_context(wsi)); + return 0; + + /* ...callbacks related to receiving the result... */ + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { + data->status = lws_http_client_http_response(wsi); + log(LogLevel::Info, "Connected with server response: %d\n", data->status); + + char tmp[512]; + + auto copy_header = [&](const auto name) { + if (lws_hdr_custom_length(wsi, name.data(), name.size()) > 0 && + lws_hdr_custom_copy(wsi, tmp, sizeof(tmp), name.data(), name.size()) > 0) { + data->response.headers[{name.data(), name.size()-1}] = tmp; + } + }; + using namespace std::string_view_literals; + copy_header("x-ratelimit-limit:"sv); + copy_header("x-ratelimit-remaining:"sv); + copy_header("x-rublon-signature:"sv); + + break; + } + + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: { + log(LogLevel::Info, "RECEIVE_CLIENT_HTTP_READ: read %d\n", ( int ) len); + if(data && in && len) { + data->response.body.append(static_cast< const char * >(in), len); + } + return 0; /* don't passthru */ + } + + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: { + // Drain the socket; lws will subsequently trigger READ callbacks with 'in/len' + char buf[LWS_PRE + 2048]; + char * p = buf + LWS_PRE; + int n = ( int ) sizeof(buf) - LWS_PRE; + if(lws_http_client_read(wsi, &p, &n) < 0) + return -1; + return 0; /* don't passthru */ + } + + case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: + log(LogLevel::Info, "LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); + if(data) + data->done = true; + lws_cancel_service(lws_get_context(wsi)); + return 0; + + case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: { + if(!lws_http_is_redirected_to_get(wsi)) { + unsigned char ** pp = ( unsigned char ** ) in; + unsigned char * end = (*pp) + len; + + // Add user headers (ensure names include ':' e.g. "Content-Type:") + for(const auto & [name, value] : data->request.headers) { + if(lws_add_http_header_by_name(wsi, + ( const unsigned char * ) name.c_str(), + ( const unsigned char * ) value.c_str(), + ( int ) value.size(), + pp, + end)) { + log(LogLevel::Error, "Header add error"); + return -1; + } + } + + // Ensure Content-Length if sending a body + if(!data->request.body.empty()) { + std::string cl = std::to_string(data->request.body.size()); + if(lws_add_http_header_by_name(wsi, + ( const unsigned char * ) "Content-Length:", + ( const unsigned char * ) cl.c_str(), + ( int ) cl.size(), + pp, + end)) { + log(LogLevel::Error, "Header Content-Length error"); + return -1; + } + } + + // Tell lws the body will follow + if(!data->request.body.empty()) + lws_client_http_body_pending(wsi, 1); + + lws_callback_on_writable(wsi); + } + return 0; + } + + case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: + // according to doc, there is no need for LWS_PRE when using HTTP_WRITE + n = lws_write(wsi, ( unsigned char * ) data->request.body.c_str(), data->request.body.size(), LWS_WRITE_HTTP_FINAL); + if(n < 0) + return -1; + /* we only had one thing to send, so inform lws we are donefi + * if we had more to send, call lws_callback_on_writable(wsi); + * and just return 0 from callback. On having sent the last + * part, call the below api instead.*/ + lws_client_http_body_pending(wsi, 0); + return 0; + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); + }; + + static inline lws_protocols protocols[] = {{ + "http-client", // const char *name + LWS::callback, // lws_callback_function * + 0, // size_t per_session_data_size + 0, // size_t rx_buffer_size + 0, // unsigned int id + 0, // void *user + 0 // size_t tx_packet_size + }, + {nullptr, nullptr, 0, 0, 0, nullptr, 0}}; + + public: + lws_context_creation_info info{}; + lws_context * _context{nullptr}; + + LWS(const Configuration & config) : _config{config} { + std::memset(&info, 0, sizeof(info)); + + info.port = CONTEXT_PORT_NO_LISTEN; + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.protocols = protocols; + + _context = lws_create_context(&info); + assert(_context != nullptr); + + auto lws_log_emit = [](int level, const char * line) { + LogLevel rlevel{LLL_DEBUG}; + if(level == LLL_ERR) + rlevel = LogLevel::Error; + if(level == LLL_WARN) + rlevel = LogLevel::Warning; + if(level == LLL_INFO) + rlevel = LogLevel::Info; + if(level == LLL_NOTICE) + rlevel = LogLevel::Debug; + if(level == LLL_DEBUG) + rlevel = LogLevel::Debug; + + log(rlevel, "libwesockets: %s", line); + }; + + // if(_config.get().logging) { + lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG | LLL_USER | LLL_HEADER | LLL_CLIENT, lws_log_emit); + // } else { + // lws_set_log_level(LLL_ERR | LLL_WARN, lws_log_emit); + // } + } + + LWS(const LWS &) = delete; + LWS(LWS &&) = delete; + + LWS & operator=(const LWS &) = delete; + LWS & operator=(LWS &&) = delete; + + tl::expected< std::reference_wrapper< Response >, ConnectionError > + request(std::string_view uri, const Request & request, Response & response) const { + using namespace memory::literals; + memory::Monotonic_8k_Resource memoryResource; + + // --- Parse URL --- + std::pmr::string protocol = "https"; + std::pmr::string host{&memoryResource}; + std::pmr::string path{"/", &memoryResource}; + std::string_view sv_uri(uri); + + if(auto pos = sv_uri.find("://"); pos != std::string_view::npos) { + protocol = std::string(sv_uri.substr(0, pos)); + sv_uri.remove_prefix(pos + 3); + } + + auto slash_pos = sv_uri.find('/'); + if(slash_pos != std::string_view::npos) { + host = std::pmr::string(sv_uri.substr(0, slash_pos), &memoryResource); + path = std::pmr::string(sv_uri.substr(slash_pos), &memoryResource); + } else { + host = std::pmr::string(sv_uri, &memoryResource); + } + + const int use_ssl = (protocol == "https") ? LCCSCF_USE_SSL : 0; + + // --- Callback funkcji obsługującej klienta --- + session_data client_data{request, response}; + + lws_client_connect_info ccinfo = {}; + ccinfo.alpn = "http/1.1"; + ccinfo.context = _context; + ccinfo.address = host.c_str(); + ccinfo.port = (protocol == "https") ? 443 : 80; + ccinfo.path = path.c_str(); + ccinfo.ssl_connection = use_ssl; + ccinfo.host = host.c_str(); + ccinfo.origin = host.c_str(); + ccinfo.method = "POST"; + ccinfo.protocol = protocols[0].name; + ccinfo.userdata = &client_data; + ccinfo.ietf_version_or_minus_one = -1; + ccinfo.pwsi = nullptr; + + struct lws * wsi = lws_client_connect_via_info(&ccinfo); + if(!wsi) { + lws_context_destroy(_context); + return tl::unexpected{ConnectionError{ConnectionError::Timeout, 0}}; + } + + // --- Pętla serwisu --- + while(lws_service(_context, 1000) >= 0) { + if(!response.body.empty()) + break; // odebrano dane + } + const auto http_code = client_data.status; + if(http_code >= 500) { + log(LogLevel::Error, "%s response with code %d ", "CURL", http_code); + return tl::unexpected{ConnectionError{ConnectionError::HttpError, http_code}}; + } + + for(const auto & [name, value] : response.headers) { + log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str()); + } + log(LogLevel::Debug, "Body %s", response.body.c_str()); + + return response; + } + + ~LWS() { + if(_context) { + lws_context_destroy(_context); + _context = nullptr; + } + } +}; +} // namespace rublon diff --git a/PAM/ssh/lib/CMakeLists.txt b/PAM/ssh/lib/CMakeLists.txt index 332fd7d..b5d8ad3 100755 --- a/PAM/ssh/lib/CMakeLists.txt +++ b/PAM/ssh/lib/CMakeLists.txt @@ -26,7 +26,6 @@ target_link_libraries(rublon-ssh PUBLIC rublon-ssh-ifc websockets - -lcurl -lssl -lcrypto )