diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index 5c63497..fbfcd79 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,9 @@ project(rublon-ssh LANGUAGES CXX) include(CTest) include(GNUInstallDirs) -set(PROJECT_VERSION_MAJOR 0) -set(PROJECT_VERSION_MINOR 1) -set(PROJECT_VERSION_PATCH 0) +set(PROJECT_VERSION_MAJOR 2) +set(PROJECT_VERSION_MINOR 0) +set(PROJECT_VERSION_PATCH 1) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED YES) @@ -15,7 +15,7 @@ set(CMAKE_CXX_EXTENSIONS NO) add_compile_options(-Wall -Wextra -Wpedantic -Wno-format-security) -option(ENABLE_TESTS "Enable tests" ON) +option(ENABLE_TESTS "Enable tests" OFF) add_custom_target(CONFIG_IDE SOURCES ${CMAKE_CURRENT_LIST_DIR}/rsc/rublon.config.defaults) add_custom_target(INSTSCRIPTS_IDE SUORCES ${CMAKE_CURRENT_LIST_DIR}/service/postinst) diff --git a/PAM/ssh/CMakeLists.txt b/PAM/ssh/CMakeLists.txt index 88fa13c..664fdf6 100755 --- a/PAM/ssh/CMakeLists.txt +++ b/PAM/ssh/CMakeLists.txt @@ -5,6 +5,7 @@ set(INC ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler_interface.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/curl.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/finish.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/init.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/json.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/method/method_select.hpp @@ -46,7 +47,8 @@ install( ) add_subdirectory(lib) +# add_subdirectory(bin) -if(${ENABLE_TESTS}) - add_subdirectory(tests) -endif() +# if(${ENABLE_TESTS}) +# add_subdirectory(tests) +# endif() diff --git a/PAM/ssh/bin/CMakeLists.txt b/PAM/ssh/bin/CMakeLists.txt new file mode 100644 index 0000000..c808fbb --- /dev/null +++ b/PAM/ssh/bin/CMakeLists.txt @@ -0,0 +1,30 @@ +add_executable(rublon_check_application + rublon_check_application.cpp +) + +target_link_options(rublon_check_application + PUBLIC + -fpic + -s + -Wl,--gc-sections + -flto +) + +target_link_libraries(rublon_check_application + PUBLIC + rublon-ssh + -lcurl + -lssl + -lcrypto +) + +include(GNUInstallDirs) + +install( + TARGETS + rublon_check_application + DESTINATION + ${CMAKE_INSTALL_SBINDIR} + COMPONENT + PAM +) diff --git a/PAM/ssh/bin/rublon_check_application.cpp b/PAM/ssh/bin/rublon_check_application.cpp new file mode 100644 index 0000000..2cb1a33 --- /dev/null +++ b/PAM/ssh/bin/rublon_check_application.cpp @@ -0,0 +1,23 @@ +#include + +#include +#include + +int main(){ + /// read system config + /// if valid send info + using namespace rublon; + details::initLog("CheckApplication"); + auto config = ConfigurationFactory{}.systemConfig(); + + if(not config){ + std::cout << "No valid rublon configuration available, check " << details::logPath() << " for more details"; + return -1; + } + + else{ + // run some tests on + } + + CoreHandler CH{*config}; +} diff --git a/PAM/ssh/extern/tl/expected.hpp b/PAM/ssh/extern/tl/expected.hpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/include/rublon/authentication_step_interface.hpp b/PAM/ssh/include/rublon/authentication_step_interface.hpp old mode 100644 new mode 100755 index 69b60e6..7f77141 --- a/PAM/ssh/include/rublon/authentication_step_interface.hpp +++ b/PAM/ssh/include/rublon/authentication_step_interface.hpp @@ -1,13 +1,15 @@ #pragma once +#include +#include #include #include #include #include +#include namespace rublon { -template < typename Impl > class AuthenticationStep { protected: std::string _systemToken; @@ -16,19 +18,6 @@ class AuthenticationStep { public: AuthenticationStep() = default; AuthenticationStep(std::string systemToken, std::string tid) : _systemToken{std::move(systemToken)}, _tid{std::move(tid)} {} - - template < typename Handler_t > - auto fire(const CoreHandlerInterface< Handler_t > & coreHandler) const { - log(LogLevel::Info, "Starting %s step", static_cast< const Impl * >(this)->name); - return static_cast< const Impl * >(this)->handle(coreHandler); - } - - template < typename Handler_t, typename PamInfo_t = LinuxPam > - auto fire(const CoreHandlerInterface< Handler_t > & coreHandler, const PamInfo_t & pam) const { - log(LogLevel::Info, "Starting %s step", static_cast< const Impl * >(this)->name); - return static_cast< const Impl * >(this)->handle(coreHandler, pam); - } - protected: void addSystemToken(Document & body) const { auto & alloc = body.GetAllocator(); @@ -39,6 +28,11 @@ class AuthenticationStep { auto & alloc = body.GetAllocator(); body.AddMember("tid", Value{this->_tid.c_str(), alloc}, alloc); } + + void addAccessToken(Document & body, std::string_view token) const { + auto & alloc = body.GetAllocator(); + body.AddMember("accessToken", Value{token.data(), static_cast< unsigned >(token.length()), alloc}, alloc); + } }; } // namespace rublon diff --git a/PAM/ssh/include/rublon/check_application.hpp b/PAM/ssh/include/rublon/check_application.hpp new file mode 100644 index 0000000..b15c629 --- /dev/null +++ b/PAM/ssh/include/rublon/check_application.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include + +namespace rublon { + +class Status { + std::string_view _statusDirPath = "/var/lib/rublon"; + std::string_view _statusFilePath = "/var/lib/rublon/install.json"; + + RapidJSONPMRStackAlloc< 4 * 1024 > _alloc; + Document _data; + bool _statusUpdated; + + std::string_view _appVersionKey = "/appVer"; + std::string_view _appTypeKey = "/type"; + std::string_view _paramSystemName = "/params/os"; + std::string_view _paramSSHDUsePamName = "/params/sshd_config/usePam"; + + public: + Status() : _data{&_alloc} { + if(not details::exists(_statusFilePath.data())) { + log(LogLevel::Info, "application first run, creating status file at %s", _statusFilePath.data()); + details::mkdir(_statusDirPath.data()); + details::touch(_statusFilePath.data()); + } + + std::ifstream ifs{_statusFilePath.data()}; + if(!ifs.is_open()) { + /// TODO handle no file error + } + + rapidjson::IStreamWrapper isw{ifs}; + _data.ParseStream(isw); + } + + void updateAppVersion(std::string_view newVersion) { + RapidJSONPMRStackAlloc< 128 > stackAlloc; + auto version = JSONPointer{_appVersionKey.data(), &stackAlloc}.Get(_data); + if(not version || version->GetString() != newVersion) { + _statusUpdated = true; + auto version = Value{newVersion.data(), _data.GetAllocator()}; + JSONPointer{_appVersionKey.data(), &stackAlloc}.Set(_data, version); + } + } + + void updateSystemVersion(std::string_view system) { + RapidJSONPMRStackAlloc< 128 > stackAlloc; + auto version = JSONPointer{_paramSystemName.data(), &stackAlloc}.Get(_data); + if(not version || version->GetString() != system) { + _statusUpdated = true; + auto version = Value{system.data(), _data.GetAllocator()}; + JSONPointer{_paramSystemName.data(), &stackAlloc}.Set(_data, version); + } + } + + bool updated() const { + return _statusUpdated; + } + + void save() { + if(updated()) { + memory::Monotonic_1k_HeapResource tmpResource; + RapidJSONPMRAlloc alloc{&tmpResource}; + FileWriter s{_statusFilePath}; + rapidjson::PrettyWriter< FileWriter, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc}; + writer.SetIndent(' ', 2); + _data.Accept(writer); + } + } + + Document & data() { + return _data; + } +}; + +class CheckApplication { + tl::expected< bool, Error > persistStatus(Status & status) const { + status.data().RemoveMember("systemToken"); + status.save(); + return true; + } + + public: + template < typename Hander_t > + tl::expected< int, Error > call(const CoreHandlerInterface< Hander_t > & coreHandler, std::string_view systemToken) const { + memory::Monotonic_1k_HeapResource mr; + RapidJSONPMRStackAlloc< 2048 > alloc{}; + constexpr std::string_view api = "/api/app/init"; + Status status; + + const auto persist = [&](const auto /*ok*/) { return this->persistStatus(status); }; + + status.updateAppVersion("2.0.2"); + status.updateSystemVersion(details::osName(&mr)); + + if(status.updated()) { + auto & alloc = status.data().GetAllocator(); + status.data().AddMember("systemToken", Value{systemToken.data(), alloc}, alloc); + return coreHandler.request(alloc, api, status.data()).and_then(persist); + } + return 0; + } +}; + +} // namespace rublon diff --git a/PAM/ssh/include/rublon/configuration.hpp b/PAM/ssh/include/rublon/configuration.hpp old mode 100644 new mode 100755 index bfb2b66..26d6d7c --- a/PAM/ssh/include/rublon/configuration.hpp +++ b/PAM/ssh/include/rublon/configuration.hpp @@ -7,20 +7,27 @@ #include #include -#include "utils.hpp" +#include +#include +#include + + +template < class T > +struct is_std_array : std::is_array< T > {}; +template < class T, std::size_t N > +struct is_std_array< std::array< T, N > > : std::true_type {}; + +template < typename T > +constexpr bool is_std_array_v = is_std_array< T >::value; namespace rublon { class ConfigurationFactory; -class ConfigurationError { - public: - enum class Cause { NoDefaultValue }; - const char * parameterName; - const char * cause; -}; +enum class FailMode { bypass, deny }; class Configuration { public: + // change to StaticString std::array< char, 33 > systemToken{}; std::array< char, 33 > secretKey{}; std::array< char, 300 > apiServer{}; @@ -28,8 +35,7 @@ class Configuration { bool enablePasswdEmail{}; bool logging{}; bool autopushPrompt{}; - bool bypass{}; - bool offlineBypas{}; + FailMode failMode{}; }; namespace { @@ -38,24 +44,15 @@ namespace { template < typename T > tl::expected< T, ConfigurationError > to(std::string_view); - - template <> - auto to(std::string_view arg) -> tl::expected< std::array, ConfigurationError > { - assert(arg.size()<=(33-1)); - - std::array value{}; + + template < class T > + auto to_array(std::string_view arg) -> tl::expected< T, ConfigurationError > { + T value{}; + assert(arg.size() <= (value.size() - 1)); std::memcpy(value.data(), arg.data(), arg.size()); return value; } - template <> - auto to(std::string_view arg) -> tl::expected< std::array, ConfigurationError > { - assert(arg.size()<=(300-1)); - - std::array value{}; - std::memcpy(value.data(), arg.data(), arg.size()); - return value; - } - + template <> auto to(std::string_view arg) -> tl::expected< bool, ConfigurationError > { return conv::to_bool(arg); @@ -65,6 +62,28 @@ namespace { return conv::to_uint32(arg).value_or(0); } + template <> + auto to(std::string_view arg) -> tl::expected< FailMode, ConfigurationError > { + if(arg == "safe" || "bypass") + return FailMode::bypass; + if(arg == "secure" || arg == "deny") + return FailMode::deny; + return tl::unexpected{ConfigurationError{ConfigurationError::ErrorClass::BadFailMode}}; + } + + template < typename T > + auto parse(std::string_view arg) -> tl::expected< T, ConfigurationError > { + if(arg.empty()) { + return tl::unexpected{ConfigurationError::ErrorClass::Empty}; + } else { + if constexpr(is_std_array_v< T >) { + return to_array< T >(arg); + } else { + return to< T >(arg); + } + } + } + } // namespace struct Entry { enum class Source { UserInput, DefaultValue }; @@ -72,33 +91,29 @@ struct Entry { static constexpr auto make_read_function() { using pType = decltype(member_ptr_t(member)); - return [](const Entry * _this, - Configuration * configuration, - std::string_view userInput) -> tl::expected< Source, ConfigurationError > { - const auto setDefaultValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > { - configuration->*member = value; - return Source::DefaultValue; - }; + return + [](const Entry * _this, Configuration * configuration, std::string_view userInput) -> tl::expected< Source, ConfigurationError > { + const auto setDefaultValue = [&](const ConfigurationError & error) -> tl::expected< Source, ConfigurationError > { + log(LogLevel::Warning, "applying user provided value for %s parameter, faild with %s", _this->name, error.what()); + if(_this->defaultValue != nullptr) { + configuration->*member = parse< pType >(_this->defaultValue).value(); + return Source::DefaultValue; + } else { + log(LogLevel::Error, "parameter %s has not been found and has no default value", _this->name); + if(userInput.empty()) + return tl::unexpected{ConfigurationError::ErrorClass::RequiredValueNotFound}; + else + return tl::unexpected{ConfigurationError::ErrorClass::BadInput}; + } + }; - const auto saveValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > { - configuration->*member = value; - return Source::UserInput; - }; + const auto saveValue = [&](const auto & value) -> tl::expected< Source, ConfigurationError > { + configuration->*member = value; + return Source::UserInput; + }; - const auto returnBadInput = [&](const auto & /*error*/) -> tl::expected< Source, ConfigurationError > { - return tl::unexpected{ConfigurationError{"", ""}}; - }; - - if(userInput.empty()) { - if(_this->defaultValue != nullptr) { - return to< pType >(_this->defaultValue).and_then(setDefaultValue).or_else(returnBadInput); - } else { - return tl::unexpected{ConfigurationError{_this->name, "No default value"}}; - } - } - - return to< pType >(userInput).and_then(saveValue).or_else(returnBadInput); - }; + return parse< pType >(userInput).and_then(saveValue).or_else(setDefaultValue); + }; } const char * name; @@ -118,7 +133,7 @@ struct Entry { const auto logError = [&](const auto & error) -> tl::expected< Source, ConfigurationError > { rublon::log(LogLevel::Error, - "Configuration parameter '%s' is has no default value and is not provided in user configuraion, aborting", + "Configuration parameter '%s' has no default value and is not provided in user configuraion, aborting", this->name); return tl::unexpected{error}; }; @@ -140,7 +155,7 @@ constexpr static inline std::array< Entry, 8 > configurationVariables = { // make_entry< &Configuration::prompt >("prompt", "1"), make_entry< &Configuration::enablePasswdEmail >("enablePasswdEmail", "true"), make_entry< &Configuration::autopushPrompt >("autopushPrompt", "false"), - make_entry< &Configuration::offlineBypas >("failMode", "bypas")}; + make_entry< &Configuration::failMode >("failMode", "deny")}; class ConfigurationFactory { public: diff --git a/PAM/ssh/include/rublon/core_handler.hpp b/PAM/ssh/include/rublon/core_handler.hpp old mode 100644 new mode 100755 index bf6b116..f0b4618 --- a/PAM/ssh/include/rublon/core_handler.hpp +++ b/PAM/ssh/include/rublon/core_handler.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -7,61 +8,87 @@ #include #include #include +#include #include +#include #include -#include - #include -#include - namespace rublon { template < typename HttpHandler = CURL > class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { std::string secretKey; - std::string url; - bool bypass; + std::string rublonCore; + + mutable std::unique_ptr< WebSocket > _ws; void signRequest(Request & request) const { request.headers["X-Rublon-Signature"] = std::pmr::string{signData(request.body, secretKey).data(), request.headers.get_allocator()}; } - bool responseSigned(const Response & response) const { + bool hasSignature(const Response & response) const { + if(not response.headers.count("x-rublon-signature")) { + log(LogLevel::Error, "No x-rublon-signature"); + return false; + } + return true; + } + + // bool hasRateLimit(const Response & response)const { + // if(not response.headers.count("x-ratelimit-remaining")) { + // log(LogLevel::Error, "No x-ratelimit-remaining"); + // return false; + // } + // return true; + // } + + // bool rateIsAcceptable(const Response &response)const{ + //// todo validate rate type and number + // } + + bool signatureIsNatValid(const Response & response) const { const auto & xRubResp = response.headers.at("x-rublon-signature"); const auto & sign = signData(response.body, secretKey); const bool signatureMatch = xRubResp == sign.data(); if(not signatureMatch) log(LogLevel::Error, "Signature mismatch %s != %s ", xRubResp.c_str(), sign.data()); - return signatureMatch; + return not signatureMatch; } - - bool containsException(const Document & coreResponse) const { + + bool hasException(const Document & coreResponse) const { using namespace std::string_view_literals; return coreResponse.HasMember("status") and coreResponse["status"].GetString() == "ERROR"sv; } bool isUnHealthy(const Document & coreResponse) const { - return coreResponse.HasParseError() or not coreResponse.HasMember("result"); + return coreResponse.HasParseError(); } protected: HttpHandler http{}; public: - CoreHandler(const Configuration & config) - : secretKey{config.secretKey.data()}, url{config.apiServer.data()}, bypass{config.bypass}, http{} {} + CoreHandler(const Configuration & config) : secretKey{config.secretKey.data()}, rublonCore{config.apiServer.data()}, http{} {} tl::expected< std::reference_wrapper< const Response >, Error > validateSignature(const Response & response) const { - if(not responseSigned(response)) { - log(LogLevel::Error, "rublon core response is not signed"); + if(hasSignature(response) and signatureIsNatValid(response)) { + log(LogLevel::Error, "rublon core response is not signed properly"); return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}}; } return response; } + // tl::expected< std::reference_wrapper< const Response >, Error > validateRate(const Response & response) const { + // if(hasSignature(response) and signatureIsNatValid(response)) { + // log(LogLevel::Error, "rublon core response is not signed properly"); + // return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}}; + // } + // return response; + // } + tl::expected< Document, Error > validateResponse(RapidJSONPMRAlloc & alloc, const Response & response) const { Document resp{&alloc}; resp.Parse(response.body.c_str()); @@ -71,40 +98,40 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { if(isUnHealthy(resp)) { log(LogLevel::Error, "Rublon Core responded with broken data"); return tl::unexpected{CoreHandlerError{CoreHandlerError::BrokenData}}; - } - - else if(containsException(resp)) { - const auto* exception = JSONPointer{"/result/exception", &alloc}.Get(resp); - log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception); + } else if(hasException(resp)) { + const auto * exception = JSONPointer{"/result/exception", &alloc}.Get(resp); + log(LogLevel::Error, "Rublon Core responded with '%s' exception", exception->GetString()); return handleCoreException(exception->GetString()); + } else if(not hasSignature(response)) { + // additional check for mallformed responses (A invalid response, without any x-rublon-signature will stop at this check) + return tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}}; } return resp; } tl::unexpected< Error > handleCoreException(std::string_view exceptionString) const { - if(exceptionString == "UserBypassedException" or exceptionString == "UserNotFoundException") { - return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserBaypass}}}; - } else { - return tl::unexpected{ - Error{CoreHandlerError{CoreHandlerError::RublonCoreException, std::string{exceptionString.data(), exceptionString.size()}}}}; - } + // can happen only dyring check application step + if(auto error = RublonCheckApplicationException::fromString(exceptionString); error.has_value()) + return tl::unexpected{Error{error.value()}}; + + // auth problems likely bad configuration in rublon admin console + if(auto error = RublonAuthenticationInterrupt::fromString(exceptionString); error.has_value()) + return tl::unexpected{Error{error.value()}}; + + // verification error wrong passcode etc. + if(auto error = WerificationError::fromString(exceptionString); error.has_value()) + return tl::unexpected{Error{error.value()}}; + + // other exceptions, just "throw" + return tl::unexpected{ + Error{CoreHandlerError{CoreHandlerError::RublonCoreException, std::string{exceptionString.data(), exceptionString.size()}}}}; } tl::expected< Document, Error > handleError(const Error & error) const { return tl::unexpected{Error{error}}; } - template < typename T > - static void stringifyTo(const Document & body, T & to) { - memory::Monotonic_2k_HeapResource tmpResource; - RapidJSONPMRAlloc alloc{&tmpResource}; - StringBuffer jsonStr{&alloc}; - Writer writer{jsonStr, &alloc}; - body.Accept(writer); - to = jsonStr.GetString(); - } - tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const Document & body) const { memory::StrictMonotonic_8k_HeapResource memoryResource; @@ -118,9 +145,10 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { request.headers["Content-Type"] = pmrs("application/json"); request.headers["Accept"] = pmrs("application/json"); + stringifyTo(body, request.body); signRequest(request); - std::pmr::string uri{url + path.data(), &memoryResource}; + std::pmr::string uri{rublonCore + path.data(), &memoryResource}; return http .request(uri, request, response) // @@ -128,18 +156,17 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { .and_then(validateResponse) .or_else(handleError); } - - ///TODO use WS_Handler type to allow mocking / changind implementation of WS - tl::expected< AuthenticationStatus, Error > waitForConfirmation(std::string_view tid) const { - bool aproved{}; - - WebSocket ws; - aproved = ws.connect(url, tid); - - log(LogLevel::Info, "websocket :s", aproved ? "approved" : "denied"); - return aproved ? AuthenticationStatus{AuthenticationStatus::Action::Confirmed} : - AuthenticationStatus{AuthenticationStatus::Action::Denied}; + bool createWSConnection(std::string_view tid) const { + if(not _ws) { + _ws = std::make_unique< WebSocket >(); + } + + return _ws->AttachToCore(rublonCore, tid); + } + + auto listen() const { + return _ws->listen(); } }; diff --git a/PAM/ssh/include/rublon/core_handler_interface.hpp b/PAM/ssh/include/rublon/core_handler_interface.hpp old mode 100644 new mode 100755 index 58f4a85..adc0cf0 --- a/PAM/ssh/include/rublon/core_handler_interface.hpp +++ b/PAM/ssh/include/rublon/core_handler_interface.hpp @@ -4,22 +4,27 @@ #include #include -#include #include +#include namespace rublon { template < typename Impl > class CoreHandlerInterface { public: - tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const rublon::Document & body) const { + tl::expected< Document, Error > request(RapidJSONPMRAlloc & mr, std::string_view path, const Document & body) const { rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::request"); return static_cast< const Impl * >(this)->request(mr, path, body); } - tl::expected< AuthenticationStatus, Error > waitForConfirmation(std::string_view tid) const { + bool createWSConnection(std::string_view tid) const { rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::listen"); - return static_cast< const Impl * >(this)->waitForConfirmation(tid); + return static_cast< const Impl * >(this)->createWSConnection(tid); + } + + auto listen() const { + rublon::log(LogLevel::Debug, "%s", "CoreHandlerInterface::listen"); + return static_cast< const Impl * >(this)->listen(); } }; } // namespace rublon diff --git a/PAM/ssh/include/rublon/curl.hpp b/PAM/ssh/include/rublon/curl.hpp old mode 100644 new mode 100755 index 13166df..b023d0b --- a/PAM/ssh/include/rublon/curl.hpp +++ b/PAM/ssh/include/rublon/curl.hpp @@ -6,10 +6,9 @@ #include #include #include -#include #include #include -#include +#include #include @@ -78,10 +77,10 @@ class CURL { tl::expected< std::reference_wrapper< Response >, ConnectionError > request(std::string_view uri, const Request & request, Response & response) const { - memory::MonotonicStackResource< 8 * 1024 > stackResource; + memory::MonotonicStackResource< 4 * 1024 > stackResource; std::pmr::string response_data{&stackResource}; - response_data.reserve(7000); + response_data.reserve(3000); /// TODO this can be done on stack using pmr auto curl_headers = std::unique_ptr< curl_slist, void (*)(curl_slist *) >(nullptr, curl_slist_free_all); @@ -99,7 +98,6 @@ class CURL { curl_easy_setopt(curl.get(), CURLOPT_HEADER, 1); curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data); - log(LogLevel::Debug, "Sending request to %s", uri.data()); for(const auto &[name, value] : request.headers){ diff --git a/PAM/ssh/include/rublon/error.hpp b/PAM/ssh/include/rublon/error.hpp old mode 100644 new mode 100755 index 425bce5..cbf5aa4 --- a/PAM/ssh/include/rublon/error.hpp +++ b/PAM/ssh/include/rublon/error.hpp @@ -1,6 +1,10 @@ #pragma once +#include +#include +#include #include +#include #include #include @@ -8,21 +12,41 @@ namespace rublon { +template < typename... Types > +constexpr std::array< std::string_view, sizeof...(Types) > make_array(Types... names) { + return {std::forward< Types >(names)...}; +} + +constexpr auto test = make_array("one", "two"); + #define names constexpr static inline const char * errorClassPrettyName[] +class ConfigurationError { + public: + enum class ErrorClass { RequiredValueNotFound, BadFailMode, BadInput, Empty }; + constexpr static auto errorClassPrettyName = make_array("RequiredValueNotFound", "BadFailMode", "BadInput", "Empty"); + constexpr static auto prettyName = "Configurtion Error"; + + constexpr ConfigurationError(ErrorClass e = ErrorClass::RequiredValueNotFound) : errorClass{e} {} + + constexpr const char * what() const { + return errorClassPrettyName[static_cast< int >(errorClass)].data(); + } + + ErrorClass errorClass; +}; class ConnectionError { public: - enum ErrorClass { Timeout, HttpError }; - names = {"Timeout", "Error"}; - - constexpr static inline auto prettyName = "Connection Error"; + enum ErrorClass { Timeout, HttpError, ClientError }; + constexpr static auto errorClassPrettyName = make_array("Timeout", "Error", "Client Error"); + constexpr static auto prettyName = "Connection Error"; constexpr ConnectionError() : errorClass{Timeout}, httpCode(200) {} constexpr ConnectionError(ErrorClass e, long httpCode) : errorClass{e}, httpCode(httpCode) {} constexpr const char * what() const { - return errorClassPrettyName[static_cast< int >(errorClass)]; + return errorClassPrettyName[static_cast< int >(errorClass)].data(); } ErrorClass errorClass; @@ -31,33 +55,34 @@ class ConnectionError { class CoreHandlerError { public: - enum ErrorClass { BadSigature, RublonCoreException, BrokenData }; - names = {"BadSigature", "RublonCoreException", "BrokenData"}; + enum ErrorClass { BadSigature, RublonCoreException, BrokenData, APIException, TransactionException }; + constexpr static auto errorClassPrettyName = + make_array("BadSigature", "RublonCoreException", "BrokenData", "APIException", "TransactionException"); + constexpr static auto prettyName = "Core Handler Error"; - constexpr static inline auto prettyName = "Core Handler Error"; + // APIException -> code: 10 + // TransactionException -> code: 11 - CoreHandlerError(ErrorClass e = BadSigature) : errorClass{e} {} - CoreHandlerError(ErrorClass e, std::string r) : errorClass{e}, reson{std::move(r)} {} + CoreHandlerError(ErrorClass e = BadSigature, std::string r = "") : errorClass{e}, reson{std::move(r)} {} constexpr const char * what() const { - return errorClassPrettyName[static_cast< int >(errorClass)]; + return errorClassPrettyName[static_cast< int >(errorClass)].data(); } ErrorClass errorClass; - std::string reson; + std::string reson; // TODO dynamic mem }; class MethodError { public: enum ErrorClass { BadMethod, BadUserInput, NoMethodAvailable }; - names = {"BadMethod", "BadUserInput", "NoMethodAvailable"}; - - constexpr static inline auto prettyName = "Method Error"; + constexpr static auto errorClassPrettyName = make_array("BadMethod", "BadUserInput", "NoMethodAvailable"); + constexpr static auto prettyName = "Method Error"; constexpr MethodError(ErrorClass e = BadMethod) : errorClass{e} {} constexpr const char * what() const { - return errorClassPrettyName[static_cast< int >(errorClass)]; + return errorClassPrettyName[static_cast< int >(errorClass)].data(); } ErrorClass errorClass; @@ -65,15 +90,26 @@ class MethodError { class WerificationError { public: - enum ErrorClass { WrongCode }; - names = {"WrongCode"}; + enum ErrorClass { + BadInput, // User input has incorrect characters or length + PasscodeException // Exception from core + }; + constexpr static auto errorClassPrettyName = make_array("BadInput", "PasscodeException"); + constexpr static inline auto prettyName = "Werification Error"; - constexpr static inline auto prettyName = "Werification Error"; - - constexpr WerificationError(ErrorClass e = WrongCode) : errorClass{e} {} + constexpr WerificationError(ErrorClass e = PasscodeException) : errorClass{e} {} constexpr const char * what() const { - return errorClassPrettyName[static_cast< int >(errorClass)]; + return errorClassPrettyName[static_cast< int >(errorClass)].data(); + } + + static std::optional< WerificationError > fromString(std::string_view name) { + for(std::size_t i{}; i < errorClassPrettyName.size(); i++) { + if(errorClassPrettyName.at(i) == name) { + return std::make_optional(WerificationError{static_cast< ErrorClass >(i)}); + } + } + return std::nullopt; } ErrorClass errorClass; @@ -81,35 +117,85 @@ class WerificationError { class RublonAuthenticationInterrupt { public: - enum ErrorClass { UserBaypass, UserDenied, UserPending }; - names = {"UserBaypass", "UserDenied", "UserPending"}; - - constexpr static inline auto prettyName = "Rublon Authentication Interrupt"; + enum ErrorClass { UserBaypass, UserDenied, UserPending, UserWaiting, UserNotFound }; + constexpr static auto errorClassPrettyName = + make_array("UserBypassedException", "UserDenied", "UserPending", "UserWaiting", "UserNotFoundException"); + constexpr static auto prettyName = "Rublon Authentication Interrupt"; RublonAuthenticationInterrupt(ErrorClass e = UserBaypass) : errorClass{e} {} constexpr const char * what() const { - return errorClassPrettyName[static_cast< int >(errorClass)]; + return errorClassPrettyName[static_cast< int >(errorClass)].data(); + } + + static std::optional< RublonAuthenticationInterrupt > fromString(std::string_view name) { + for(std::size_t i{}; i < errorClassPrettyName.size(); i++) { + if(errorClassPrettyName.at(i) == name) { + return std::make_optional(RublonAuthenticationInterrupt{static_cast< ErrorClass >(i)}); + } + } + return std::nullopt; + } + + ErrorClass errorClass; +}; + +class RublonCheckApplicationException { + public: + enum ErrorClass { ApplicationNotFoundException, InvalidSignatureException, UnsupportedVersionException }; + + constexpr static auto errorClassPrettyName = + make_array("ApplicationNotFoundException", "InvalidSignatureException", "UnsupportedVersionException"); + constexpr static auto prettyName = "Rublon Check Application Interrupt"; + + RublonCheckApplicationException(ErrorClass e = ApplicationNotFoundException) : errorClass{e} {} + + constexpr const char * what() const { + return errorClassPrettyName[static_cast< int >(errorClass)].data(); + } + + static std::optional< RublonCheckApplicationException > fromString(std::string_view name) { + for(std::size_t i{}; i < errorClassPrettyName.size(); i++) { + if(errorClassPrettyName.at(i) == name) { + return std::make_optional(RublonCheckApplicationException{static_cast< ErrorClass >(i)}); + } + } + return std::nullopt; } ErrorClass errorClass; }; class Error { - using Error_t = - std::variant< CoreHandlerError, ConnectionError, WerificationError, MethodError, RublonAuthenticationInterrupt >; + using Error_t = std::variant< ConfigurationError, + CoreHandlerError, + ConnectionError, + WerificationError, + MethodError, + RublonAuthenticationInterrupt, + RublonCheckApplicationException >; Error_t _error; public: - enum Category { k_CoreHandlerError, k_ConnectionError, k_WerificationError, k_MethodError, k_RublonAuthenticationInterrupt }; + enum Category { + k_ConfigurationError, + k_CoreHandlerError, + k_ConnectionError, + k_WerificationError, + k_MethodError, + k_RublonAuthenticationInterrupt, + k_RublonCheckApplication + }; Error() = default; + Error(ConfigurationError error) : _error{error} {} Error(CoreHandlerError error) : _error{error} {} Error(ConnectionError error) : _error{error} {} Error(MethodError error) : _error{error} {} Error(WerificationError error) : _error{error} {} Error(RublonAuthenticationInterrupt error) : _error{error} {} + Error(RublonCheckApplicationException error) : _error{error} {} Error(const Error &) = default; Error(Error &&) = default; @@ -147,6 +233,11 @@ class Error { return category() == Error{E{}}.category(); } + template < typename E > + constexpr bool is(typename E::ErrorClass errorClass) const { + return is< E >() && hasSameErrorClassAs(errorClass); + } + template < typename E > constexpr bool isSameCategoryAs(const E & e) const { return category() == Error{e}.category(); diff --git a/PAM/ssh/include/rublon/finish.hpp b/PAM/ssh/include/rublon/finish.hpp new file mode 100755 index 0000000..d8b2904 --- /dev/null +++ b/PAM/ssh/include/rublon/finish.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace rublon { +class Finish : public AuthenticationStep { + const char * apiPath = "/api/transaction/credentials"; + const std::string _accessToken; + + void addAccessToken(Document & coreRequest) const { + auto & alloc = coreRequest.GetAllocator(); + coreRequest.AddMember("accessToken", Value{_accessToken.c_str(), alloc}, alloc); + } + + tl::expected< bool, Error > returnOk(const Document & /*coreResponse*/) const { + return true; + } + public: + const char * name = "Finalization"; + + Finish(const rublon::Configuration & config, std::string accessToken) : AuthenticationStep(config.systemToken.data(), ""), _accessToken{std::move(accessToken)} {} + + template < typename Hander_t > + tl::expected< bool, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const { + const auto returnOk = [&](const auto & coreResponse) { return this->returnOk(coreResponse); }; + + RapidJSONPMRStackAlloc< 2048 > alloc{}; + Document body{rapidjson::kObjectType, &alloc}; + + this->addSystemToken(body); + this->addAccessToken(body); + + return coreHandler + .request(alloc, apiPath, body) // + .and_then(returnOk); + } +}; +} // namespace rublon diff --git a/PAM/ssh/include/rublon/init.hpp b/PAM/ssh/include/rublon/init.hpp old mode 100644 new mode 100755 index 51785a4..1021209 --- a/PAM/ssh/include/rublon/init.hpp +++ b/PAM/ssh/include/rublon/init.hpp @@ -2,59 +2,37 @@ #include #include +#include #include #include #include +#include #include namespace rublon { -class Verify {}; +/// TODO move to some other place +using PAM = LinuxPam; +using Session = SessionBase< PAM, CoreHandler< CURL > >; +using Transaction = TransactionBase< Session >; + } // namespace rublon +extern std::string g_tid; + namespace rublon { -std::pmr::string osName(std::pmr::memory_resource *mr) { - memory::MonotonicStackResource< 8 * 1024 > stackResource; - - std::ifstream file(std::filesystem::path{"/etc/os-release"}); - if(not file.good()) - return {"unknown", mr}; - - std::pmr::string line{&stackResource}; - line.reserve(100); - - while(std::getline(file, line)) { - std::pmr::string _key{&stackResource}; - std::pmr::string _value{&stackResource}; - - if(!line.length()) - continue; - - if(line[0] == '#' || line[0] == ';') - continue; - - auto posEqual = line.find('='); - _key = line.substr(0, posEqual); - _value = line.substr(posEqual + 1); - - if(_key == "PRETTY_NAME"){ - return {_value, mr}; - } - } - - return {"unknown", mr}; -} template < class MethodSelect_t = MethodSelect > -class Init : public AuthenticationStep< Init< MethodSelect_t > > { - using base_t = AuthenticationStep< Init< MethodSelect_t > >; +class Init : public AuthenticationStep { + using base_t = AuthenticationStep; const char * apiPath = "/api/transaction/init"; tl::expected< MethodSelect_t, Error > createMethod(const Document & coreResponse) const { const auto & rublonResponse = coreResponse["result"]; std::string tid = rublonResponse["tid"].GetString(); - return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"]}; + g_tid = tid; + return MethodSelect_t{this->_systemToken, tid, rublonResponse["methods"], _session.config().prompt}; } template < typename PamInfo_t > @@ -68,8 +46,8 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > { memory::MonotonicStackResource< 512 > stackResource; std::pmr::string releaseInfo{&stackResource}; auto & alloc = coreRequest.GetAllocator(); - - const auto os = osName(&stackResource); + + const auto os = details::osName(&stackResource); if(os == "unknown") { log(LogLevel::Warning, "No OS information available"); @@ -80,30 +58,26 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > { Value params{rapidjson::kObjectType}; params.AddMember("userIP", ip, alloc); - params.AddMember("appVer", "v.0.0.1", alloc); /// TODO add version to cmake + params.AddMember("appVer", "2.0.2", alloc); /// TODO add version to cmake params.AddMember("os", osNamePretty, alloc); coreRequest.AddMember("params", std::move(params), alloc); } - - template < typename PamInfo_t > - tl::expected< std::reference_wrapper< const Document >, Error > checkEnrolement(const Document & coreResponse, - const PamInfo_t & pam) const { + + template + tl::expected< std::reference_wrapper< const Document >, Error > checkEnrolement(const Document & coreResponse, const Pam_T pam) const { using namespace std::string_view_literals; const auto & resp = coreResponse; if(resp.HasMember("result") and resp["result"].IsObject() and resp["result"].HasMember("status")) { const auto & status = resp["result"]["status"].GetString(); log(LogLevel::Warning, "Got enrolement message with stats %s", status); - if(status == "pending"sv and resp["result"].HasMember("webURI")) { + if((status == "pending"sv || status == "waiting"sv) and resp["result"].HasMember("webURI")) { const auto & weburi = resp["result"]["webURI"].GetString(); - pam.print("It seams that your account is not configured properly,\nplease contact your administrator for more information"); - pam.print("also, please visit %s", weburi); - + pam.print("Visit %s", weburi); return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserPending}}}; } if(status == "denied"sv) { - pam.print("It seams that your account is disabled, please contact your administrator for more information"); return tl::unexpected{Error{RublonAuthenticationInterrupt{RublonAuthenticationInterrupt::UserDenied}}}; } } @@ -114,12 +88,17 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > { public: const char * name = "Initialization"; - Init(const rublon::Configuration & config) : base_t(config.systemToken.data(), "") {} + const Session & _session; + + Init(const Session & session) : base_t(std::string{session.config().systemToken.data(), 32}, ""), _session{session} { + log(LogLevel::Debug, "Init"); + } template < typename Hander_t, typename PamInfo_t = LinuxPam > - tl::expected< MethodSelect_t, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const { + [[deprecated]] tl::expected< MethodSelect_t, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, + const PamInfo_t & pam) const { const auto createMethod = [&](const auto & coreResponse) { return this->createMethod(coreResponse); }; - const auto checkEnrolement = [&](const auto & coreResponse) { return this->checkEnrolement(coreResponse, pam); }; + const auto checkEnrolement = [&](const auto & coreResponse) { return this->checkEnrolement(coreResponse,pam); }; RapidJSONPMRStackAlloc< 2048 > alloc{}; Document body{rapidjson::kObjectType, &alloc}; @@ -133,5 +112,22 @@ class Init : public AuthenticationStep< Init< MethodSelect_t > > { .and_then(checkEnrolement) .and_then(createMethod); } + + // tl::expected< Transaction, Error > openTransaction() const { + // const auto createTransaction = [&](const auto & coreResponse) { return this->createTransaction(coreResponse); }; + // const auto checkEnrolement = [&](const auto & coreResponse) { return this->checkEnrolement(coreResponse, _session.pam()); }; + + // RapidJSONPMRStackAlloc< 2048 > alloc{}; + // Document body{rapidjson::kObjectType, &alloc}; + + // this->addSystemToken(body); + // this->addPamInfo(body, _session.pam()); + // this->addParams(body, _session.pam()); + + // return _session.coreHandler() + // .request(alloc, apiPath, body) // + // .and_then(checkEnrolement) + // .and_then(createTransaction); + // } }; } // namespace rublon diff --git a/PAM/ssh/include/rublon/json.hpp b/PAM/ssh/include/rublon/json.hpp old mode 100644 new mode 100755 index a2ce1f2..16d85de --- a/PAM/ssh/include/rublon/json.hpp +++ b/PAM/ssh/include/rublon/json.hpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -77,7 +79,7 @@ struct RapidJSONPMRAlloc { template < std::size_t N > struct RapidJSONPMRStackAlloc : public RapidJSONPMRAlloc { private: - char _buffer[N]; + char _buffer[N]{}; std::pmr::monotonic_buffer_resource mr{_buffer, N}; public: @@ -95,4 +97,50 @@ using Writer = rapidjson::Writer< StringBuffer, rapidjson::UTF8<>, rapidjs using JSONPointer = rapidjson::GenericPointer< Value, RapidJSONPMRAlloc >; +template < typename Str_t > +struct StringWriter { + Str_t & _str; + using Ch = char; + StringWriter(Str_t & t) : _str{t} {} + void Put(Ch ch) { + _str += ch; + } + void Flush() {} +}; +struct FileWriter { + std::unique_ptr< FILE, int (*)(FILE *) > _fp{nullptr,nullptr}; + using Ch = char; + FileWriter(std::string_view filename) { + _fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(filename.data(), "w"), fclose); + } + void Put(Ch ch) { + fputc(ch, _fp.get()); + } + void Flush() {} +}; + + +template < typename T > +static void stringifyTo(const Document & body, T & to) { + memory::Monotonic_1k_HeapResource tmpResource; + RapidJSONPMRAlloc alloc{&tmpResource}; + StringWriter s{to}; + rapidjson::Writer< StringWriter,rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc}; + body.Accept(writer); +} + } // namespace rublon + +namespace std { +inline auto begin(const rublon::Value & __ils) noexcept { + return __ils.Begin(); +} +inline ::rublon::Value::ConstValueIterator end(const rublon::Value & __ils) noexcept { + return __ils.End(); +} + +[[nodiscard]] inline std::size_t size(const rublon::Value & __cont) { + return __cont.Size(); +} + +} // namespace std diff --git a/PAM/ssh/include/rublon/memory.hpp b/PAM/ssh/include/rublon/memory.hpp old mode 100644 new mode 100755 index bfe69fb..9ab46b7 --- a/PAM/ssh/include/rublon/memory.hpp +++ b/PAM/ssh/include/rublon/memory.hpp @@ -4,16 +4,16 @@ namespace rublon { namespace memory { - struct holder { + struct default_memory_resource { static inline std::pmr::memory_resource * _mr = std::pmr::get_default_resource(); }; inline void set_default_resource(std::pmr::memory_resource * memory_resource) { - holder{}._mr = memory_resource; + default_memory_resource{}._mr = memory_resource; } inline std::pmr::memory_resource * default_resource() { - return holder{}._mr; + return default_memory_resource{}._mr; } template < std::size_t N > @@ -79,7 +79,7 @@ class RublonMemory { std::pmr::monotonic_buffer_resource mr{sharedMemory, std::size(sharedMemory)}; std::pmr::unsynchronized_pool_resource rublonPoolResource{&mr}; - RublonMemory() { + RublonMemory() { std::pmr::set_default_resource(&rublonPoolResource); } }; diff --git a/PAM/ssh/include/rublon/method/EMAIL.hpp b/PAM/ssh/include/rublon/method/EMAIL.hpp new file mode 100755 index 0000000..cefdd1e --- /dev/null +++ b/PAM/ssh/include/rublon/method/EMAIL.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include +#include +#include + +#include + +namespace rublon::method { + +class EMAIL : public WebsocketBasedAuth { + public: + EMAIL(std::string systemToken, std::string tid) + : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "Email Link") {} +}; + +} // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/OTP.hpp b/PAM/ssh/include/rublon/method/OTP.hpp old mode 100644 new mode 100755 index 8f2ad56..4f08300 --- a/PAM/ssh/include/rublon/method/OTP.hpp +++ b/PAM/ssh/include/rublon/method/OTP.hpp @@ -12,8 +12,16 @@ namespace rublon::method { class OTP : public PasscodeBasedAuth { public: - OTP(std::string systemToken, std::string tid) - : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "OTP", "Enter code from Rublon Authenticator: ", 6, true) {} + OTP(std::string systemToken, std::string tid, int prompts) + : PasscodeBasedAuth(std::move(systemToken), + std::move(tid), + "", + "Mobile Passcode", + "Enter the passcode from the Rublon Authenticator mobile app: ", + 6, + true, + PasscodeBasedAuth::Endpoint::ConfirmCode, + prompts) {} }; } // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/PUSH.hpp b/PAM/ssh/include/rublon/method/PUSH.hpp old mode 100644 new mode 100755 index 0cf3ee3..5eefece --- a/PAM/ssh/include/rublon/method/PUSH.hpp +++ b/PAM/ssh/include/rublon/method/PUSH.hpp @@ -13,7 +13,7 @@ namespace rublon::method { class PUSH : public WebsocketBasedAuth { public: PUSH(std::string systemToken, std::string tid) - : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "PUSH") {} + : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "Mobile PUSH") {} }; } // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/SMS.hpp b/PAM/ssh/include/rublon/method/SMS.hpp old mode 100644 new mode 100755 index bff562c..0c052fa --- a/PAM/ssh/include/rublon/method/SMS.hpp +++ b/PAM/ssh/include/rublon/method/SMS.hpp @@ -12,7 +12,16 @@ namespace rublon::method { class SMS : public PasscodeBasedAuth { public: - SMS(std::string systemToken, std::string tid) : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "SMS", "Enter SMS passcode: ", 6, true) {} + SMS(std::string systemToken, std::string tid, int prompts) + : PasscodeBasedAuth(std::move(systemToken), + std::move(tid), + "", + "SMS", + "Enter SMS passcode: ", + 6, + true, + PasscodeBasedAuth::Endpoint::ConfirmCode, + prompts) {} }; } // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/SmsLink.hpp b/PAM/ssh/include/rublon/method/SmsLink.hpp old mode 100644 new mode 100755 index e4d1092..b5e0f1c --- a/PAM/ssh/include/rublon/method/SmsLink.hpp +++ b/PAM/ssh/include/rublon/method/SmsLink.hpp @@ -13,7 +13,7 @@ namespace rublon::method { class SmsLink : public WebsocketBasedAuth { public: SmsLink(std::string systemToken, std::string tid) - : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "smsLink") {} + : WebsocketBasedAuth(std::move(systemToken), std::move(tid), "SMS Link") {} }; } // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/YOTP.hpp b/PAM/ssh/include/rublon/method/YOTP.hpp old mode 100644 new mode 100755 index 9170220..f72bb76 --- a/PAM/ssh/include/rublon/method/YOTP.hpp +++ b/PAM/ssh/include/rublon/method/YOTP.hpp @@ -12,8 +12,15 @@ namespace rublon::method { class YOTP : public PasscodeBasedAuth { public: - YOTP(std::string systemToken, std::string tid) - : PasscodeBasedAuth(std::move(systemToken), std::move(tid), "YOTP", "Press Yubikey: ", 44, false) {} + YOTP(std::string systemToken, std::string tid, std::string accessToken, int prompts) + : PasscodeBasedAuth(std::move(systemToken), + std::move(tid), + std::move(accessToken), + "YubiKey OTP Security Key", + "Insert and tap your YubiKey: ", + 44, + false, + PasscodeBasedAuth::Endpoint::SecurityKeySSH, prompts) {} }; } // namespace rublon::method diff --git a/PAM/ssh/include/rublon/method/method_select.hpp b/PAM/ssh/include/rublon/method/method_select.hpp old mode 100644 new mode 100755 index fd24263..0bc585c --- a/PAM/ssh/include/rublon/method/method_select.hpp +++ b/PAM/ssh/include/rublon/method/method_select.hpp @@ -9,39 +9,19 @@ #include #include -#include +#include #include -#include #include + +#include +#include #include -template < class F > -struct return_type; - -template < class R, class... A > -struct return_type< R (*)(A...) > { - typedef R type; -}; - -template < typename T > -using return_type_t = typename return_type< T >::type; - -namespace std { -inline ::rublon::Value::ConstValueIterator begin(const rublon::Value & __ils) noexcept { - return __ils.Begin(); -} -inline ::rublon::Value::ConstValueIterator end(const rublon::Value & __ils) noexcept { - return __ils.End(); -} - -[[nodiscard]] inline std::size_t size(const rublon::Value & __cont) { - return __cont.Size(); -} - -} // namespace std +extern std::string g_tid; namespace rublon { + class MethodProxy { public: template < typename Method_t > @@ -49,41 +29,43 @@ class MethodProxy { template < typename Handler_t, typename PamInfo_t = LinuxPam > tl::expected< AuthenticationStatus, Error > fire(const CoreHandlerInterface< Handler_t > & coreHandler, const PamInfo_t & pam) const { + coreHandler.createWSConnection(g_tid); return std::visit( [&](const auto & method) { rublon::log(LogLevel::Info, "Using '%s' method", method.name); - return method.fire(coreHandler, pam); + return method.verify(coreHandler, pam); }, _impl); } private: - std::variant< method::OTP, method::SMS, method::PUSH, method::SmsLink, method::YOTP > _impl; + std::variant< method::OTP, method::SMS, method::PUSH, method::EMAIL, method::SmsLink, method::YOTP > _impl; }; -class PostMethod : public rublon::AuthenticationStep< PostMethod > { - using base_t = rublon::AuthenticationStep< PostMethod >; - +class PostMethod : public AuthenticationStep { + using base_t = AuthenticationStep; const char * uri = "/api/transaction/methodSSH"; std::string _method; + int _prompts; tl::expected< MethodProxy, Error > createMethod(const Document & coreResponse) const { const auto & rublonResponse = coreResponse["result"]; std::string tid = rublonResponse["tid"].GetString(); + std::string token = rublonResponse.HasMember("token") ? rublonResponse["token"].GetString() : ""; if(_method == "totp") { - return MethodProxy{method::OTP{this->_systemToken, std::move(tid)}}; + return MethodProxy{method::OTP{this->_systemToken, std::move(tid), _prompts}}; } else if(_method == "sms") { - return MethodProxy{method::SMS{this->_systemToken, std::move(tid)}}; - } else if(_method == "push"){ + return MethodProxy{method::SMS{this->_systemToken, std::move(tid), _prompts}}; + } else if(_method == "push") { + return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}}; + } else if(_method == "email") { return MethodProxy{method::PUSH{this->_systemToken, std::move(tid)}}; } else if(_method == "smsLink") { return MethodProxy{method::SmsLink{this->_systemToken, std::move(tid)}}; } else if(_method == "yotp") { - return MethodProxy{method::YOTP{this->_systemToken, std::move(tid)}}; - } - - else + return MethodProxy{method::YOTP{this->_systemToken, std::move(tid), std::move(token), _prompts}}; + } else return tl::unexpected{MethodError{MethodError::BadMethod}}; } @@ -95,8 +77,8 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > { public: const char * name = "Confirm Method"; - PostMethod(std::string systemToken, std::string tid, std::string method) - : base_t(std::move(systemToken), std::move(tid)), _method{method} {} + PostMethod(std::string systemToken, std::string tid, std::string method, int prompts) + : base_t(std::move(systemToken), std::move(tid)), _method{method}, _prompts{prompts} {} template < typename Hander_t, typename PamInfo_t = LinuxPam > tl::expected< MethodProxy, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler) const { @@ -117,14 +99,16 @@ class PostMethod : public rublon::AuthenticationStep< PostMethod > { class MethodSelect { std::string _systemToken; + std::string _accessToken; std::string _tid; + int _prompts; std::vector< std::string > _methods; public: template < typename Array_t > - MethodSelect(std::string systemToken, std::string tid, const Array_t & methodsAvailableForUser) - : _systemToken{std::move(systemToken)}, _tid{std::move(tid)} { + MethodSelect(std::string systemToken, std::string tid, const Array_t & methodsAvailableForUser, int prompts) + : _systemToken{std::move(systemToken)}, _tid{std::move(tid)}, _prompts{prompts} { _methods.reserve(std::size(methodsAvailableForUser)); std::transform( std::begin(methodsAvailableForUser), std::end(methodsAvailableForUser), std::back_inserter(_methods), [](const auto & method) { @@ -136,9 +120,12 @@ class MethodSelect { template < typename Pam_t > tl::expected< PostMethod, Error > create(Pam_t & pam) const { rublon::log(LogLevel::Debug, "prompting user to select method"); - memory::StrictMonotonic_2k_HeapResource memoryResource; + memory::StrictMonotonic_4k_HeapResource memoryResource; std::pmr::map< int, std::string > methods_id{&memoryResource}; - pam.print("select method: "); + std::pmr::map< int, std::string > methods_names{&memoryResource}; + int prompts = _prompts; + + pam.print("Select the authentication method to verify your identity: "); auto logMethodAvailable = [](auto & method) { // rublon::log(LogLevel::Debug, "Method %s found", method.c_str()); @@ -152,103 +139,130 @@ class MethodSelect { for(const auto & method : _methods) { if(method == "totp") { logMethodAvailable(method); - pam.print("%d: Mobile TOTP", i + 1); - methods_id[++i] = method; + pam.print("%d: Mobile Passcode", i + 1); + methods_id[++i] = method; + methods_names[i] = "Mobile Passcode"; continue; } - - /// Needs changes in Core to work - // if(method == "yotp") { - // logMethodAvailable(method); - // pam.print("%d: Yubikey", i + 1); - // methods_id[++i] = method; - // continue; - // } - + + if(method == "email") { + logMethodAvailable(method); + pam.print("%d: Email Link", i + 1); + methods_id[++i] = method; + methods_names[i] = "Email Link"; + continue; + } + + if(method == "yotp") { + logMethodAvailable(method); + pam.print("%d: YubiKey OTP Security Key", i + 1); + methods_id[++i] = method; + methods_names[i] = "YubiKey OTP Security Key"; + continue; + } + if(method == "sms") { logMethodAvailable(method); - pam.print("%d: SMS code", i + 1); - methods_id[++i] = method; + pam.print("%d: SMS Passcode", i + 1); + methods_id[++i] = method; + methods_names[i] = "SMS Passcode"; continue; } if(method == "push") { logMethodAvailable(method); - pam.print("%d: Mobile PUSH", i + 1); - methods_id[++i] = method; - continue; - } - - if(method == "smsLink") { - logMethodAvailable(method); - pam.print("%d: SMS Link", i + 1); - methods_id[++i] = method; + pam.print("%d: Mobile Push", i + 1); + methods_id[++i] = method; + methods_names[i] = "Mobile Push"; continue; } + if(method == "smsLink") { + logMethodAvailable(method); + pam.print("%d: SMS Link", i + 1); + methods_id[++i] = method; + methods_names[i] = "SMS Link"; + continue; + } logMethodNotAvailable(method); } if(i == 0) { + log(LogLevel::Warning, "None of provided methods are supported by the connector"); return tl::unexpected(MethodError(MethodError::ErrorClass::NoMethodAvailable)); } return i; }; const auto toMethodError = [&](conv::Error /*methodid*/) -> MethodError { - pam.print("Input is not an number, please correct"); + // NaN or out of range return MethodError{MethodError::BadUserInput}; }; const auto createMethod = [&](std::uint32_t methodid) -> tl::expected< PostMethod, MethodError > { auto hasMethod = methods_id.find(methodid) != methods_id.end(); - pam.print("\t selected: %s", hasMethod ? methods_id.at(methodid).c_str() : "unknown option"); + // pam.print("\t selected: %s", hasMethod ? methods_id.at(methodid).c_str() : "unknown option"); if(!hasMethod) { + log(LogLevel::Error, "User selected option %d, which is not corrent", methodid); return tl::unexpected{MethodError(MethodError::BadMethod)}; } else { - log(LogLevel::Info, "User selected option %d{%s}", methodid, methods_id.at(methodid).c_str()); - return PostMethod{_systemToken, _tid, methods_id.at(methodid)}; + log(LogLevel::Info, "User selected option %d{%s}", methodid, methods_names.at(methodid).c_str()); + return PostMethod{_systemToken, _tid, methods_id.at(methodid), _prompts}; } }; - const auto askForMethodAgain = [&]() -> tl::expected< PostMethod, MethodError > { - printAvailableMethods(); - return pam - .scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_id.size()) // - .transform_error(toMethodError) - .and_then(createMethod); - /// TODO or_else(printErrorAndDenyAccess); ?? - }; - const auto askForMethod = [&](int methods_number) -> tl::expected< uint32_t, MethodError > { if(methods_number == 1) { - pam.print("Only one method available"); + pam.print("Automatically selected the only available authentication method: %s", methods_id.at(1).c_str()); return 1; } return pam.scan(conv::to_uint32, "\nSelect method [1-%d]: ", methods_number).transform_error(toMethodError); }; - const auto handleErrors = [&](const MethodError & e) -> tl::expected< PostMethod, MethodError > { - switch(e.errorClass) { - case MethodError::BadMethod: // User provided a number but the number if not found - return askForMethodAgain(); - case MethodError::BadUserInput: // User provided id is invalid (NAN) - return askForMethodAgain(); - case MethodError::NoMethodAvailable: - /// TODO print enrolement - default: - return tl::unexpected(e); - } + auto reducePromptCount = [&](int selected_method) -> tl::expected< uint32_t, MethodError > { + prompts--; + return selected_method; + }; + + auto disableAnyNewMethodPrompts = [&]() { prompts = 0; }; + + const auto printEnrolementInformation = [&]() { + log(LogLevel::Warning, "Enrolement send to admin about new user: %s", pam.username()); + pam.print("Please check email"); + }; + + const auto handleErrors = [&](const MethodError & e) -> tl::expected< PostMethod, MethodError > { + switch(e.errorClass) { + case MethodError::NoMethodAvailable: + disableAnyNewMethodPrompts(); + printEnrolementInformation(); + break; + case MethodError::BadMethod: // User provided a number but the number if not found + pam.print("Input is not a valid method number. Enter a valid number"); + break; + case MethodError::BadUserInput: // User provided id is invalid (NAN) + pam.print("Input is not a number. Enter a valid number"); + break; + } + return tl::unexpected(e); }; - const auto toGenericError = [](const MethodError & e) -> Error { return Error{e}; }; - return printAvailableMethods() // - .and_then(askForMethod) - .and_then(createMethod) - .or_else(handleErrors) - .map_error(toGenericError); + return [&]() { + while(true) { + auto method = printAvailableMethods() // + .and_then(reducePromptCount) + .and_then(askForMethod) + .and_then(createMethod) + .or_else(handleErrors); + + if(not method && prompts > 0) + continue; + return method; + }; + }() + .map_error(toGenericError); } }; diff --git a/PAM/ssh/include/rublon/method/passcode_based_auth.hpp b/PAM/ssh/include/rublon/method/passcode_based_auth.hpp old mode 100644 new mode 100755 index 2b96c57..53dd05d --- a/PAM/ssh/include/rublon/method/passcode_based_auth.hpp +++ b/PAM/ssh/include/rublon/method/passcode_based_auth.hpp @@ -1,21 +1,31 @@ #pragma once +#include "rublon/utils.hpp" #include #include #include #include +#include namespace rublon::method { -class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > { +class PasscodeBasedAuth : public AuthenticationStep { protected: - using base_t = AuthenticationStep< PasscodeBasedAuth >; - const char * uri = "/api/transaction/confirmCode"; + const char * uri; + const char * confirmField; + + static constexpr const char * confirmCodeEndpoint = "/api/transaction/confirmCode"; + static constexpr const char * confirmSecuritySSHEndpoint = "/api/transaction/confirmSecurityKeySSH"; + + static constexpr const char * fieldVericode = "vericode"; + static constexpr const char * fieldOtp = "otp"; + const char * userMessage{nullptr}; const uint_fast8_t length; const bool onlyDigits; + int _prompts; constexpr static bool isdigit(char ch) { return std::isdigit(static_cast< unsigned char >(ch)); @@ -51,60 +61,101 @@ class PasscodeBasedAuth : public AuthenticationStep< PasscodeBasedAuth > { auto vericode = pam.scan([](const char * userInput) { return std::string{userInput}; }, userMessage); if(hasValidLength(vericode) and hasValidCharacters(vericode)) { - body.AddMember("vericode", Value{vericode.c_str(), alloc}, alloc); + Value confirmFieldValue(confirmField, alloc); + body.AddMember(confirmFieldValue, Value{vericode.c_str(), alloc}, alloc); + + if(token.size()) { + this->addAccessToken(body, token); + } return body; } - return tl::unexpected{Error{WerificationError{WerificationError::WrongCode}}}; + return tl::unexpected{Error{WerificationError{WerificationError::BadInput}}}; } template < typename PamInfo_t = LinuxPam > - tl::expected< std::reference_wrapper< Document >, Error > askForPasscodeAgain(Document & body, const PamInfo_t & pam) const { - pam.print("passcode has wrong number of digits or contains illegal characters, please correct"); - return readPasscode(body, pam); - } - - template < typename PamInfo_t = LinuxPam > - tl::expected< AuthenticationStatus, Error > checkAuthenticationStatus(const Document & coreResponse, const PamInfo_t & pam) const { - RapidJSONPMRStackAlloc< 1024 > alloc; - auto error = JSONPointer{"/result/error", &alloc}.Get(coreResponse); - - if(error) { - pam.print("Wrong code"); - return tl::unexpected{Error{WerificationError{WerificationError::WrongCode}}}; - } - - pam.print("Verification code validated"); + tl::expected< AuthenticationStatus, Error > checkAuthenticationStatus(const Document & /*coreResponse*/, const PamInfo_t & pam) const { + pam.print("The passcode has been validated"); return AuthenticationStatus{AuthenticationStatus::Action::Confirmed}; } + tl::expected< AuthenticationStatus, Error > waitForCoreConfirmation( + std::shared_ptr< WebSocketSingleShotEventListener > eventListener) const { + log(LogLevel::Info, "Listening to confirmation event in PasscodeBasedAuth"); + return eventListener->waitForEvent(); + } + + template < typename PamInfo_t = LinuxPam > + tl::expected< AuthenticationStatus, Error > errorHandler(Error error, const PamInfo_t & pam, int promptLeft) const { + if(promptLeft && error.is< WerificationError >()) { + switch(error.get< WerificationError >().errorClass) { + case WerificationError::ErrorClass::PasscodeException: + pam.print(R"(Incorrect passcode. Try again)"); + break; + case WerificationError::ErrorClass::BadInput: + pam.print("The passcode has an incorrect number of digits or contains invalid characters. Try again"); + break; + } + } + return tl::unexpected{error}; + } + public: const char * name; + std::string token; + + enum class Endpoint { ConfirmCode, SecurityKeySSH }; PasscodeBasedAuth(std::string systemToken, std::string tid, + std::string token, const char * name, const char * userMessage, + uint_fast8_t length, - bool numbersOnly) - : base_t(std::move(systemToken), std::move(tid)), userMessage{userMessage}, length{length}, onlyDigits{numbersOnly}, name{name} {} + bool numbersOnly, + Endpoint endpoint, + int prompts) + : AuthenticationStep(std::move(systemToken), std::move(tid)), + uri{(endpoint == Endpoint::ConfirmCode) ? confirmCodeEndpoint : confirmSecuritySSHEndpoint}, + confirmField{(endpoint == Endpoint::ConfirmCode) ? fieldVericode : fieldOtp}, + userMessage{userMessage}, + length{length}, + onlyDigits{numbersOnly}, + _prompts{prompts}, + name{name}, + token{std::move(token)} {} template < typename Hander_t, typename PamInfo_t = LinuxPam > - tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const { + tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const { RapidJSONPMRStackAlloc< 2048 > alloc{}; Document body{rapidjson::kObjectType, &alloc}; + std::shared_ptr< WebSocketSingleShotEventListener > eventListener = coreHandler.listen(); + int prompts = _prompts; - const auto checkCodeValidity = [&](const auto & coreResponse) { return this->checkAuthenticationStatus(coreResponse, pam); }; const auto requestAuthorization = [&](const auto & body) { return coreHandler.request(alloc, uri, body); }; - const auto askForPasscodeAgain = [&](const auto & /*error*/) { return this->askForPasscodeAgain(body, pam); }; + const auto checkCodeValidity = [&](const auto & coreResponse) { return this->checkAuthenticationStatus(coreResponse, pam); }; + const auto waitForCoreToConfirm = [&](const auto &) { return waitForCoreConfirmation(eventListener); }; + const auto handleError = [&](const auto error) { return errorHandler(error, pam, prompts); }; this->addSystemToken(body); this->addTid(body); - return readPasscode(body, pam) // - .or_else(askForPasscodeAgain) - .and_then(requestAuthorization) - .and_then(checkCodeValidity); + return [&]() { + while(true) { + prompts--; + auto access = readPasscode(body, pam) // + .and_then(requestAuthorization) + .and_then(checkCodeValidity) + .and_then(waitForCoreToConfirm) + .or_else(handleError); + if(not access && prompts) { + continue; + } else { + return access; + } + }; + }(); } }; diff --git a/PAM/ssh/include/rublon/method/websocket_based_auth.hpp b/PAM/ssh/include/rublon/method/websocket_based_auth.hpp old mode 100644 new mode 100755 index f7b866d..edf0c0e --- a/PAM/ssh/include/rublon/method/websocket_based_auth.hpp +++ b/PAM/ssh/include/rublon/method/websocket_based_auth.hpp @@ -11,21 +11,19 @@ namespace rublon::method { -class WebsocketBasedAuth : public AuthenticationStep< WebsocketBasedAuth > { - using base_t = AuthenticationStep< WebsocketBasedAuth >; - +class WebsocketBasedAuth : public AuthenticationStep { public: const char * name = ""; WebsocketBasedAuth(std::string systemToken, std::string tid, const char * name) - : base_t(std::move(systemToken), std::move(tid)), name{name} {} + : AuthenticationStep(std::move(systemToken), std::move(tid)), name{name} {} template < typename Hander_t, typename PamInfo_t = LinuxPam > - tl::expected< AuthenticationStatus, Error > handle(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const { + tl::expected< AuthenticationStatus, Error > verify(const CoreHandlerInterface< Hander_t > & coreHandler, const PamInfo_t & pam) const { log(LogLevel::Info, "starting WS"); - - pam.print("Waiting for approval"); - return coreHandler.waitForConfirmation(_tid); + auto listener = coreHandler.listen(); + pam.scan([](const auto/*ignored userinput*/){return "";}, "Rublon authentication initiated. Complete the authentication and press Enter to proceed"); + return listener->waitForEvent(); } }; diff --git a/PAM/ssh/include/rublon/non_owning_ptr.hpp b/PAM/ssh/include/rublon/non_owning_ptr.hpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/include/rublon/pam.hpp b/PAM/ssh/include/rublon/pam.hpp old mode 100644 new mode 100755 index 4d6f7fb..c70bd55 --- a/PAM/ssh/include/rublon/pam.hpp +++ b/PAM/ssh/include/rublon/pam.hpp @@ -43,8 +43,13 @@ class LinuxPam { template < typename... Ti > void print(const char * fmt, Ti... ti) const noexcept { - log(LogLevel::Debug, fmt, std::forward< Ti >(ti)...); - pam_prompt(pamh, PAM_TEXT_INFO, nullptr, fmt, std::forward< Ti >(ti)...); + char buf[256]={}; + sprintf(buf, fmt, std::forward< Ti >(ti)...); + log(LogLevel::Debug, "pam_print: '%s'", buf); + + if(auto r = pam_prompt(pamh, PAM_TEXT_INFO, nullptr, fmt, std::forward< Ti >(ti)...); r != PAM_SUCCESS){ + log(LogLevel::Error, "pam_print returned with error code %d", r); + } } template < typename Fun, typename... Ti > diff --git a/PAM/ssh/include/rublon/pam_action.hpp b/PAM/ssh/include/rublon/pam_action.hpp old mode 100644 new mode 100755 index c69685a..4aae138 --- a/PAM/ssh/include/rublon/pam_action.hpp +++ b/PAM/ssh/include/rublon/pam_action.hpp @@ -1,27 +1,33 @@ #pragma once +#include +#include +#include + namespace rublon { + class AuthenticationStatus { public: enum class Action { Denied, Confirmed, Bypass }; - AuthenticationStatus(Action action) : _action{action} {} + AuthenticationStatus(Action action, std::string authenticationToken = "") : _action{action}, _token{std::move(authenticationToken)} {} constexpr bool userAuthorized() const { return _action == Action::Confirmed; } - constexpr bool bypass() const { - return false; - } - Action action() const { return _action; } + + std::string_view accessToken() const { + return _token; + } private: Action _action; + std::string _token; }; } // namespace rublon diff --git a/PAM/ssh/include/rublon/rublon.hpp b/PAM/ssh/include/rublon/rublon.hpp old mode 100644 new mode 100755 index 88e223a..08f7ebb --- a/PAM/ssh/include/rublon/rublon.hpp +++ b/PAM/ssh/include/rublon/rublon.hpp @@ -1,72 +1,34 @@ #pragma once -#include +#include #include #include -#include #include +#include namespace rublon { -// template -// static Configuration readConfig(const Pam_t&pam){ -// auto rublonConfig = ConfigurationFactory{}.systemConfig(); -// if(not rublonConfig.has_value()) { -// pam.print("\n"); -// pam.print("Rublon configuration does not exists or is invalid"); -// pam.print("\tcheck '%s' for more details\n", details::logPath()); -// } -// } - -class RublonSession { - public: - std::pmr::string _tid; - - RublonSession() { - /// create memoryResource - /// - } - void startTransaction(std::string_view tid) {} -}; - -class RublonExceptionHandler{ - public: -}; - -template < typename Pam_T, typename CoreHandler_T > -class RublonBase { - const Pam_T & _pam; - Configuration _config{}; - - void initializeLogs() { - details::initLog(); - } - - public: - RublonBase(const Pam_T & pam, Configuration config) : _pam{pam}, _config{config} {} - - AuthenticationStatus authenticate() const { - - } -}; - -using RublonLinux = RublonBase< LinuxPam, CoreHandler< CURL > >; +using PAM = LinuxPam; +using Session = SessionBase>; +using Transaction = TransactionBase; class RublonFactory{ public: - template - tl::expected create(const Pam_t &pam){ + tl::expected startSession(const PAM &pam){ + details::initLog(); + auto config = ConfigurationFactory{}.systemConfig(); + if(not config.has_value()) { - pam.print("\n"); - pam.print("Rublon configuration does not exists or is invalid"); - pam.print("\tcheck '%s' for more details\n", details::logPath()); - return tl::unexpected{false}; + pam.print("The configuration file does not exist or contains incorrect values"); + return tl::unexpected{ConfigurationError{}}; } - return RublonLinux{pam, *config}; + return Session{pam, config.value()}; } }; } // namespace rublon + +#include diff --git a/PAM/ssh/include/rublon/session.hpp b/PAM/ssh/include/rublon/session.hpp new file mode 100644 index 0000000..77e04cc --- /dev/null +++ b/PAM/ssh/include/rublon/session.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "rublon/utils.hpp" +#include + +#include + +namespace rublon{ + +template +class SessionBase { + const Pam_t & _pam; + const Configuration _config; + + CoreHandler_t _coreHandler; + /// TODO log + /// TODO momory resource + public: + SessionBase(const Pam_t &pam, Configuration config) : _pam{pam}, _config{config}, _coreHandler{_config} { + log(LogLevel::Debug, __PRETTY_FUNCTION__); + } + + const auto & coreHandler() const {return _coreHandler; } + const auto & pam() const { return _pam; } + const auto & config() const { return _config; } +}; + +template +class TransactionBase{ + public: + const Session_t& _session; + std::string _tid; + + TransactionBase(const Session_t& session, std::string_view tid):_session{session},_tid{tid.data(), tid.size()} { + } +}; + + +} + diff --git a/PAM/ssh/include/rublon/sign.hpp b/PAM/ssh/include/rublon/sign.hpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/include/rublon/static_string.hpp b/PAM/ssh/include/rublon/static_string.hpp new file mode 100644 index 0000000..43542f0 --- /dev/null +++ b/PAM/ssh/include/rublon/static_string.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +// statically allocates a string buffer of (N+1) chars +template < size_t N > +class StaticString { + public: + constexpr StaticString() = default; + constexpr StaticString(const char * str) { + std::strncpy(m_str.data(), str, N); + } + + void operator=(const char * str) { + std::strncpy(m_str.data(), str, N); + } + + const char * c_str() const noexcept { + return &m_str[0]; + } + + const char * data() const noexcept { + return &m_str[0]; + } + + + std::size_t size() const { + return strlen(m_str.data()); + } + + private: + std::array< char, N + 1 > m_str{}; +}; diff --git a/PAM/ssh/include/rublon/utils.hpp b/PAM/ssh/include/rublon/utils.hpp old mode 100644 new mode 100755 index 9664644..51cb751 --- a/PAM/ssh/include/rublon/utils.hpp +++ b/PAM/ssh/include/rublon/utils.hpp @@ -8,15 +8,14 @@ #include #include #include -#include #include #include #include #include #include -#include +#include #include #include #include @@ -27,7 +26,6 @@ #include namespace rublon { - inline bool fileGood(const std::filesystem::path & path) { std::ifstream file(path); return file.good(); @@ -47,8 +45,6 @@ inline bool readFile(const std::filesystem::path & path, T & destination) { return true; } - - enum class LogLevel { Debug, Info, Warning, Error }; inline auto dateStr() { @@ -60,37 +56,129 @@ inline auto dateStr() { } constexpr const char * LogLevelNames[]{"Debug", "Info", "Warning", "Error"}; -constexpr LogLevel g_level = LogLevel::Debug; -constexpr bool syncLogFile = true; +LogLevel g_level = LogLevel::Debug; +constexpr bool syncLogFile = true; +static const char * application = ""; + + +// #include +// #include +// #include +// #include + +// unsigned char result[MD5_DIGEST_LENGTH]; + +// // Print the MD5 sum as hex-digits. +// void print_md5_sum(unsigned char* md) { +// int i; +// for(i=0; i stackResource; + + std::ifstream file(std::filesystem::path{"/etc/os-release"}); + if(not file.good()) + return {"unknown", mr}; + + std::pmr::string line{&stackResource}; + line.reserve(100); + + while(std::getline(file, line)) { + std::pmr::string _key{&stackResource}; + std::pmr::string _value{&stackResource}; + + if(!line.length()) + continue; + + if(line[0] == '#' || line[0] == ';') + continue; + + auto posEqual = line.find('='); + _key = line.substr(0, posEqual); + _value = line.substr(posEqual + 1); + + if(_key == "PRETTY_NAME") { + _value.erase(std::remove_if(_value.begin(), _value.end(), [](auto ch) { return ch == '"'; }), _value.end()); + return {_value, mr}; + } + } + + return {"unknown", mr}; + } + constexpr const char * logPath() { constexpr auto path = "/var/log/rublon-ssh.log"; return path; } - inline bool logExists() noexcept { - return std::unique_ptr< FILE, int (*)(FILE *) >(fopen(logPath(), "r"), fclose).get(); + inline void touch(const char * filename) { + close(open(filename, O_CREAT | O_RDWR, 0640)); } - inline void initLog() noexcept { - if(not logExists()) { - auto fp = fopen(logPath(), "w"); - if(fp) { - fclose(fp); - chmod(logPath(), S_IRUSR | S_IWUSR | S_IRGRP); - } + inline void mkdir(const char * dirname) { + ::mkdir(dirname, O_CREAT | O_RDWR); + } + + inline bool exists(const char * filename) { + return access(filename, F_OK) == 0; + } + + inline bool logMissing() noexcept { + return not exists(logPath()); + } + + inline const char * initLog(const char * app = nullptr) noexcept { + if(logMissing()) { + touch(logPath()); } + if(not app) + application = app; + return logPath(); } inline void doLog(LogLevel level, const char * line) noexcept { - auto fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(logPath(), "a+"), fclose); + auto fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(initLog(), "a+"), fclose); if(fp) { /// TODO add transaction ID - fprintf(fp.get(), "%s [%s] %s\n", dateStr().data(), LogLevelNames[( int ) level], line); + fprintf( + fp.get(), "%s %s[%s] %s\n", dateStr().data(), application == nullptr ? "" : application, LogLevelNames[( int ) level], line); if(syncLogFile) sync(); } + // openlog ("auth", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_AUTH); + // syslog (LOG_INFO, "[%s] %s", "pam_rublon", line); + // closelog (); } } // namespace details @@ -134,16 +222,20 @@ class PrintUser { }; namespace conv { - inline bool to_bool(std::string_view value) { - auto * buf = ( char * ) alloca(value.size() + 1); - buf[value.size()] = '\0'; + enum class Error { OutOfRange, NotANumber }; + + inline bool to_bool(std::string_view userinput) { + if(userinput.size() > 16) { + // todo return optional + return false; + } + std::array< char, 16 >buf{}; auto asciitolower = [](char in) { return in - ((in <= 'Z' && in >= 'A') ? ('Z' - 'z') : 0); }; - - std::transform(value.cbegin(), value.cend(), buf, asciitolower); - return strcmp(buf, "true") == 0; + + std::transform(userinput.cbegin(), userinput.cend(), buf.data(), asciitolower); + return strcmp(buf.data(), "true") == 0; } - enum class Error { OutOfRange, NotANumber }; inline tl::expected< std::uint32_t, Error > to_uint32(std::string_view userinput) noexcept { try { return std::stoi(userinput.data()); diff --git a/PAM/ssh/include/rublon/websockets.hpp b/PAM/ssh/include/rublon/websockets.hpp old mode 100644 new mode 100755 index f2722e5..75cb2d5 --- a/PAM/ssh/include/rublon/websockets.hpp +++ b/PAM/ssh/include/rublon/websockets.hpp @@ -1,19 +1,20 @@ #pragma once -#include +#include +#include -#include +#include +#include +#include #include #include +#include +#include namespace rublon { -#define TIMEOUT_SECONDS 90 -/// {"status":"ERROR","code":400,"result":{"exception":"MobileAppMissingException","code":32,"errorMessage":"No mobile app -/// detected","details":null}} - -class WebSocketConnectionListener { +class WebSocketConnectionListener : public std::enable_shared_from_this< WebSocketConnectionListener > { public: enum class Status { Disconnected, Conected }; @@ -28,26 +29,25 @@ class WebSocketConnectionListener { public: WebSocketConnectionListener(sio::client & ws, std::mutex & lock, std::condition_variable_any & cond) - : _ws(ws), _lock{lock}, _cond{cond} { - _ws.set_open_listener([this]() { this->on_connected(); }); - _ws.set_close_listener([this](sio::client::close_reason const & reason) { this->on_close(reason); }); - _ws.set_fail_listener([this]() { this->on_fail(); }); + : _ws(ws), _lock{lock}, _cond{cond} {} + + void makeCallbacks() { + auto _this = shared_from_this(); + _ws.set_open_listener([_this]() { _this->on_connected(); }); + _ws.set_close_listener([_this](sio::client::close_reason const & reason) { _this->on_close(reason); }); + _ws.set_fail_listener([_this]() { _this->on_fail(); }); } tl::expected< Status, bool > waitForConnection() { std::lock_guard lock{_lock}; - if(!handshake) { - log(LogLevel::Info, "Waiting for connection"); - _cond.wait(_lock); - log(LogLevel::Info, "Connection OK!"); - } - + log(LogLevel::Info, "Waiting for connection"); + _cond.wait(_lock); return _status; } void on_connected() { std::unique_lock{_lock}; - log(LogLevel::Info, "WebSocket connection estamblished"); + log(LogLevel::Info, "WebSocket connected"); handshake = true; _status = Status::Conected; _cond.notify_all(); @@ -65,16 +65,16 @@ class WebSocketConnectionListener { void on_fail() { std::unique_lock{_lock}; - log(LogLevel::Info, "WebSocket connection failed"); + log(LogLevel::Info, "WebSocket connection fail"); handshake = true; _status = Status::Disconnected; _cond.notify_all(); } }; -class WebSocketSingleShotEventListener { +class WebSocketSingleShotEventListener : public std::enable_shared_from_this< WebSocketSingleShotEventListener > { public: - enum class Status { Approved, Denied, Expired }; + enum class Status { Unknown, Approved, Denied, Expired }; private: sio::client & _ws; @@ -83,128 +83,149 @@ class WebSocketSingleShotEventListener { std::condition_variable_any & _cond; bool event_received = false; - Status _status{}; + Status _status{Status::Unknown}; + public: + std::string _data{}; + + private: void printobj(std::size_t i, const std::map< std::string, sio::message::ptr > & objects) { + std::size_t o{}; for(const auto & [name, msg] : objects) { - log(LogLevel::Debug, "Object %ld: %s", i, name.c_str()); - printMessage(i, msg); + log(LogLevel::Debug, "Object %ld: '%s'", i, name.c_str()); + printMessage(i, o++, msg); } }; - - void printMessage(std::size_t i, const sio::message::ptr & message) { + + void printMessage(std::size_t i, std::size_t o, const sio::message::ptr & message) { switch(message->get_flag()) { case sio::message::flag_integer: - log(LogLevel::Debug, "message %ld integer : %d", i, message->get_int()); + log(LogLevel::Debug, "message %ld/%ld integer : %d", i, o, message->get_int()); break; case sio::message::flag_double: - log(LogLevel::Debug, "message %ld double : %f", i, message->get_double()); + log(LogLevel::Debug, "message %ld/%ld double : %f", i, o, message->get_double()); break; case sio::message::flag_string: - log(LogLevel::Debug, "message %ld string : %s", i, message->get_string().c_str()); + log(LogLevel::Debug, "message %ld/%ld string : %s", i, o, message->get_string().c_str()); break; case sio::message::flag_binary: - log(LogLevel::Debug, "message %ld binary : %s", i, message->get_binary()->c_str()); + log(LogLevel::Debug, "message %ld/%ld binary : %s", i, o, message->get_binary()->c_str()); break; case sio::message::flag_array: - log(LogLevel::Debug, "message %ld array : blah", i); + log(LogLevel::Debug, "message %ld/%ld array : blah", i, o); break; case sio::message::flag_object: - log(LogLevel::Debug, "message %ld object : obj ->", i); + log(LogLevel::Debug, "message %ld/%ld object : obj ->", i, o); printobj(i, message->get_map()); break; case sio::message::flag_boolean: - log(LogLevel::Debug, "message %ld bool : %s", i, message->get_bool() ? "yes" : "no"); + log(LogLevel::Debug, "message %ld/%ld bool : %s", i, o, message->get_bool() ? "yes" : "no"); break; case sio::message::flag_null: - log(LogLevel::Debug, "message %ld NULL", i); + log(LogLevel::Debug, "message %ld/%ld NULL", i, o); break; default: log(LogLevel::Info, "Message with unknown type"); } }; - void generic(sio::event & e, Status status) { - std::lock_guard lock{_lock}; + void notify(Status status) { event_received = true; _status = status; - - log(LogLevel::Debug, "event name : %s", e.get_name().c_str()); - log(LogLevel::Debug, "event nsp : %s", e.get_nsp().c_str()); - log(LogLevel::Debug, "event messages: %d", e.get_messages().size()); - - for(std::size_t i = 0; i < e.get_messages().size(); i++) { - const auto & message = e.get_messages().at(i); - printMessage(i, message); - } - _cond.notify_all(); } void onConfirmed(sio::event & e) { - generic(e, Status::Approved); + std::lock_guard lock{_lock}; log(LogLevel::Info, "Autentication confirmed"); + _data = e.get_messages().at(1)->get_map()["data"]->get_string(); + notify(Status::Approved); } void onDenied(sio::event & e) { - generic(e, Status::Denied); + std::lock_guard lock{_lock}; log(LogLevel::Info, "Autentication denied by user"); + // _data = e.get_messages().at(1)->get_map()["redirectUrl"]->get_string(); + for(std::size_t i{}; i < e.get_messages().size(); i++) + printMessage(i, 0, e.get_messages().at(i)); + notify(Status::Denied); } - void onExpired(sio::event & e) { - generic(e, Status::Expired); - log(LogLevel::Error, "Autentication call expierd"); + void onExpired(sio::event & /*e*/) { + std::lock_guard lock{_lock}; + log(LogLevel::Error, "Autentication call expierd"); + notify(Status::Expired); } void onErrorEvent(sio::message::ptr const & message) { std::lock_guard lock{_lock}; - event_received = true; log(LogLevel::Error, "Error: %s", message->get_string().c_str()); - _cond.notify_all(); - } - - void onAny(sio::event & e) { - std::lock_guard lock{_lock}; event_received = true; - log(LogLevel::Info, "onAny message received: name %s", e.get_name().c_str()); _cond.notify_all(); } public: WebSocketSingleShotEventListener(sio::client & ws, std::mutex & lock, std::condition_variable_any & cond) - : _ws{ws}, _lock{lock}, _cond{cond} { - _ws.socket()->on("transactionConfirmed", [this](sio::event & e) { this->onConfirmed(e); }); - _ws.socket()->on("transactionDenied", [this](sio::event & e) { this->onDenied(e); }); - _ws.socket()->on("transactionExpired", [this](sio::event & e) { this->onExpired(e); }); + : _ws{ws}, _lock{lock}, _cond{cond} {} - _ws.socket()->on_error([this](sio::message::ptr const & message) { this->onErrorEvent(message); }); - _ws.socket()->on_any([this](sio::event & e) { this->onAny(e); }); + void makeCallbacks() { + auto _this = shared_from_this(); + _ws.socket()->on("transactionConfirmed", [_this](sio::event & e) { _this->onConfirmed(e); }); + _ws.socket()->on("transactionDenied", [_this](sio::event & e) { _this->onDenied(e); }); + _ws.socket()->on("transactionExpired", [_this](sio::event & e) { _this->onExpired(e); }); + + _ws.socket()->on_error([_this](sio::message::ptr const & message) { _this->onErrorEvent(message); }); + // _ws.socket()->on_any([_this](sio::event & e) { _this->onAny(e); }); } /// TODO pass timeout - tl::expected< Status, bool > waitForEvent() { + tl::expected< AuthenticationStatus, Error > waitForEvent() { std::lock_guard lock{_lock}; if(!event_received) { log(LogLevel::Info, "Waiting for WS event"); - if(_cond.wait_until(_lock, std::chrono::system_clock::now() + std::chrono::minutes{2}) == std::cv_status::timeout) { - log(LogLevel::Info, "Waiting for WS event failed due to WS timeout"); - }else{ - log(LogLevel::Info, "Event arrived"); + auto timeout = std::chrono::system_clock::now() + std::chrono::minutes{2}; + while(!event_received) { + if(_cond.wait_until(_lock, timeout) == std::cv_status::timeout) { + log(LogLevel::Info, "Waiting for WS event failed due to WS timeout"); + break; + } else { + if(event_received) { + log(LogLevel::Info, "Event arrived"); + break; + } else + log(LogLevel::Info, "Event arrived, but it's not an action"); + } } + } else { + log(LogLevel::Info, "event already received"); } - return _status; + AuthenticationStatus status{ + _status == Status::Approved ? AuthenticationStatus::Action::Confirmed : AuthenticationStatus::Action::Denied, _data}; + return status; } }; class WebSocket { - + std::mutex _lock; + std::condition_variable_any _cond; + sio::client ws; + public: - bool connect(std::string_view uri, std::string_view tid) { - std::mutex _lock; - std::condition_variable_any _cond; - sio::client ws; - + WebSocket() = default; + ~WebSocket() = default; + + std::shared_ptr< WebSocketSingleShotEventListener > listen() { + const auto createEventListener = [&]() -> std::shared_ptr< WebSocketSingleShotEventListener > { + log(LogLevel::Debug, "Initializiing WebSocketSingleShotEventListener"); + auto wsSingleShotEventListener = std::make_shared< WebSocketSingleShotEventListener >(ws, _lock, _cond); + wsSingleShotEventListener->makeCallbacks(); + return wsSingleShotEventListener; + }; + return createEventListener(); + } + + bool AttachToCore(std::string_view uri, std::string_view tid) { /// needed here only for rublon core api URL, so 1k fine memory::StrictMonotonic_1k_HeapResource memoryResource; std::pmr::string str{uri.data(), uri.size(), &memoryResource}; @@ -214,19 +235,19 @@ class WebSocket { str.replace(str.find(httpsPrefix), httpsPrefix.size(), "wss://"); } str += "/ws/socket.io/"; - // ws.set_logs_verbose(); - log(LogLevel::Debug, "WS connect to %s", str.c_str()); + // ws.set_logs_verbose(); ws.connect(str.c_str()); const auto attachToTransactionConfirmationChannel = [&](const auto & /*status*/) -> tl::expected< bool, bool > { + log(LogLevel::Debug, "Initializiing attachToTransactionConfirmationChannel"); + memory::StrictMonotonic_1k_HeapResource resource; std::pmr::string channel{&resource}; channel.reserve(100); channel += "transactionConfirmation."; channel += tid; - /// TODO check status auto message = std::dynamic_pointer_cast< sio::object_message >(sio::object_message::create()); message->insert("channel", channel.c_str()); log(LogLevel::Debug, "emiting %s message on subscribe channel", channel.c_str()); @@ -235,18 +256,15 @@ class WebSocket { return true; /// TODO }; - const auto waitForUserAction = [&](const auto & /*connected*/) -> tl::expected< WebSocketSingleShotEventListener::Status, bool > { - WebSocketSingleShotEventListener eventListener{ws, _lock, _cond}; - return eventListener.waitForEvent(); - }; + auto wsConnectionListener = std::make_shared< WebSocketConnectionListener >(ws, _lock, _cond); + wsConnectionListener->makeCallbacks(); - auto v = WebSocketConnectionListener{ws, _lock, _cond} - .waitForConnection() // - .and_then(attachToTransactionConfirmationChannel) - .and_then(waitForUserAction) - .value_or(WebSocketSingleShotEventListener::Status::Denied); - - return v == WebSocketSingleShotEventListener::Status::Approved; + auto v = wsConnectionListener + ->waitForConnection() // + .and_then(attachToTransactionConfirmationChannel); + + log(LogLevel::Debug, "WS Connected"); + return v.value_or(false); } }; } // namespace rublon diff --git a/PAM/ssh/lib/CMakeLists.txt b/PAM/ssh/lib/CMakeLists.txt old mode 100644 new mode 100755 index d0a421b..6f31017 --- a/PAM/ssh/lib/CMakeLists.txt +++ b/PAM/ssh/lib/CMakeLists.txt @@ -8,20 +8,13 @@ set_target_properties(rublon-ssh-pam PROPERTIES OUTPUT_NAME "pam_rublon") target_compile_options(rublon-ssh-pam PUBLIC - -fwhole-program - -fvisibility=hidden - -ffunction-sections - -fdata-sections - -fno-unwind-tables - -fno-asynchronous-unwind-tables -flto + -Wno-deprecated-declarations ) target_link_options(rublon-ssh-pam PUBLIC -fpic - -s - -Wl,--gc-sections -flto ) diff --git a/PAM/ssh/lib/pam.cpp b/PAM/ssh/lib/pam.cpp old mode 100644 new mode 100755 index 8bb25c1..b41f3a7 --- a/PAM/ssh/lib/pam.cpp +++ b/PAM/ssh/lib/pam.cpp @@ -5,7 +5,11 @@ #include #include +#include +#include +#include #include +#include #define DLL_PUBLIC __attribute__((visibility("default"))) @@ -23,43 +27,26 @@ DLL_PUBLIC int pam_sm_acct_mgmt([[maybe_unused]] pam_handle_t * pamh, return PAM_SUCCESS; } +std::string g_tid; + DLL_PUBLIC int pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unused]] int argc, [[maybe_unused]] const char ** argv) { using namespace rublon; - // std::freopen(rublon::details::logPath(), "a+", stdout); + details::initLog(); LinuxPam pam{pamh}; - // std::freopen(rublon::details::logPath(), "a+", stderr); - auto config = ConfigurationFactory{}.systemConfig(); - if(not config.has_value()) { - pam.print("\n"); - pam.print("Rublon configuration does not exists or is invalid"); - pam.print("\tcheck '%s' for more details\n", details::logPath()); - return PAM_SUCCESS; - } - - std::byte sharedMemory[32 * 1024] = {}; - std::pmr::monotonic_buffer_resource mr{sharedMemory, std::size(sharedMemory)}; - std::pmr::unsynchronized_pool_resource rublonPoolResource{&mr}; - std::pmr::set_default_resource(&rublonPoolResource); - - CoreHandler CH{*config}; - - auto selectMethod = [&](const MethodSelect & selector) { return selector.create(pam); }; - auto confirmMethod = [&](const PostMethod & confirm) { return confirm.fire(CH); }; - auto confirmCode = [&](const MethodProxy & method) { return method.fire(CH, pam); }; auto printAuthMessageAndExit = [&](const AuthenticationStatus status) { switch(status.action()) { case AuthenticationStatus::Action::Bypass: - pam.print("\n\tRUBLON authentication bypased"); + pam.print("RUBLON authentication BYPASSED"); return PAM_SUCCESS; case AuthenticationStatus::Action::Denied: - pam.print("\n\tRUBLON authentication FAILED"); + pam.print("RUBLON authentication FAILED"); return PAM_MAXTRIES; case AuthenticationStatus::Action::Confirmed: - pam.print("\n\tRUBLON authentication SUCCESS"); + pam.print("RUBLON authentication SUCCEEDED"); return PAM_SUCCESS; } @@ -67,19 +54,45 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu return PAM_MAXTRIES; }; - auto allowLogin = [&](const AuthenticationStatus & status) -> tl::expected< int, Error > { - return printAuthMessageAndExit(status); + auto session = rublon::RublonFactory{}.startSession(pam); + if(not session.has_value()) { + return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); + } + + auto & CH = session.value().coreHandler(); + + auto selectMethod = [&](const MethodSelect & selector) { return selector.create(pam); }; + auto confirmMethod = [&](const PostMethod & postMethod) { return postMethod.handle(CH); }; + auto confirmCode = [&](const MethodProxy & method) mutable { return method.fire(CH, pam); }; + + auto finalizeTransaction = [&](const AuthenticationStatus & status) -> tl::expected< AuthenticationStatus, Error > { + if(status.userAuthorized()) { + auto tok = std::string{status.accessToken().substr(10, 60).data(), 60}; + + Finish finish{session.value().config(), std::move(tok)}; + finish.handle(CH); + } + return status; }; + auto allowLogin = [&](const AuthenticationStatus & status) -> tl::expected< int, Error > { return printAuthMessageAndExit(status); }; + auto mapError = [&](const Error & error) -> tl::expected< int, Error > { - log(LogLevel::Error, "Authorization interrupted with {%s::%s}", error.errorClassName(), error.categoryName()); + log(LogLevel::Error, "Process interrupted by {%s::%s}", error.errorClassName(), error.categoryName()); if(error.is< RublonAuthenticationInterrupt >()) { switch(error.get< RublonAuthenticationInterrupt >().errorClass) { case RublonAuthenticationInterrupt::ErrorClass::UserBaypass: return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); case RublonAuthenticationInterrupt::ErrorClass::UserDenied: + pam.print("Access denied! Contact your administrator for more information"); return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); + case RublonAuthenticationInterrupt::ErrorClass::UserWaiting: case RublonAuthenticationInterrupt::ErrorClass::UserPending: + pam.print( + "Your account is awaiting administrator's approval. \n" + "Contact your administrator and ask them to approve your account"); + return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); + case RublonAuthenticationInterrupt::ErrorClass::UserNotFound: return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); } } @@ -94,34 +107,72 @@ pam_sm_authenticate(pam_handle_t * pamh, [[maybe_unused]] int flags, [[maybe_unu return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); } } - - + if(error.is< ConnectionError >()) { - if(config->bypass) { - pam.print("Connection Error, bypass enabled"); + if(session.value().config().failMode == FailMode::deny) { + pam.print("Incorrect response from the Rublon API, user bypassed"); return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); } else { - pam.print("Connection Error, bypass disabled"); + pam.print("Incorrect response from the Rublon API, user access denied"); return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); } } if(error.is< CoreHandlerError >()) { - const auto &reson = error.get< CoreHandlerError >().reson; - pam.print("\n RUBLON server returned '%s' exception", reson.c_str()); - + const auto & reson = error.get< CoreHandlerError >().reson; + pam.print("Something went wrong and authentication could not be completed, contact your administrator"); + if(reson == "UserBypassedException" or reson == "UserNotFoundException") return printAuthMessageAndExit(AuthenticationStatus::Action::Bypass); } - + + if(error.is< WerificationError >()) { + switch(error.get< WerificationError >().errorClass) { + case WerificationError::ErrorClass::PasscodeException: + pam.print(R"(Incorrect passcode)"); + return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); + case WerificationError::ErrorClass::BadInput: + pam.print(R"(Ensure that the Secret Key is correct.)"); + return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); + } + } + + if(error.is< RublonCheckApplicationException >()) { + switch(error.get< RublonCheckApplicationException >().errorClass) { + case RublonCheckApplicationException::ErrorClass::ApplicationNotFoundException: + log(LogLevel::Error, R"(Could not find the application in the Rublon Admin Console.)"); + log(LogLevel::Error, R"(Ensure that the application exists and the SystemToken is correct.)"); + return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); + case RublonCheckApplicationException::ErrorClass::InvalidSignatureException: + log(LogLevel::Error, R"(Could not verify the signature.)"); + log(LogLevel::Error, R"(Ensure that the Secret Key is correct.)"); + return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); + case RublonCheckApplicationException::ErrorClass::UnsupportedVersionException: + log(LogLevel::Error, R"(The provided version of the app is unsupported.)"); + log(LogLevel::Error, R"(Try changing the app version.)"); + return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); + } + } + return printAuthMessageAndExit(AuthenticationStatus::Action::Denied); }; + + { + CheckApplication ca; + auto ret = + ca.call(CH, {session.value().config().systemToken.data(), session.value().config().systemToken.size()}).or_else(mapError); + if(not ret.has_value()) { + log(LogLevel::Error, "Check Application step failed, check configration"); + return PAM_MAXTRIES; + } + } - auto ret = Init{config.value()} - .fire(CH, pam) // + auto ret = Init{session.value()} + .handle(CH, pam) // .and_then(selectMethod) .and_then(confirmMethod) .and_then(confirmCode) + .and_then(finalizeTransaction) .and_then(allowLogin) .or_else(mapError); diff --git a/PAM/ssh/tests/CMakeLists.txt b/PAM/ssh/tests/CMakeLists.txt old mode 100644 new mode 100755 diff --git a/PAM/ssh/tests/authentication_step_common_tests.cpp b/PAM/ssh/tests/authentication_step_common_tests.cpp old mode 100644 new mode 100755 index e2e6d3c..1a2e27d --- a/PAM/ssh/tests/authentication_step_common_tests.cpp +++ b/PAM/ssh/tests/authentication_step_common_tests.cpp @@ -1,3 +1,5 @@ #include #include #include + +std::string g_tid=""; diff --git a/PAM/ssh/tests/core_handler_mock.hpp b/PAM/ssh/tests/core_handler_mock.hpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/tests/core_handler_tests.cpp b/PAM/ssh/tests/core_handler_tests.cpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/tests/core_response_generator.hpp b/PAM/ssh/tests/core_response_generator.hpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/tests/gtest_matchers.hpp b/PAM/ssh/tests/gtest_matchers.hpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/tests/http_mock.hpp b/PAM/ssh/tests/http_mock.hpp old mode 100644 new mode 100755 index 1c8c641..3aa40ab --- a/PAM/ssh/tests/http_mock.hpp +++ b/PAM/ssh/tests/http_mock.hpp @@ -20,8 +20,7 @@ rublon::Configuration conf{rublon::Configuration{// true, true, false, - false, - true}}; + rublon::FailMode::safe}}; rublon::Configuration confBypass{rublon::Configuration{// {"320BAB778C4D4262B54CD243CDEFFAFD"}, {"39e8d771d83a2ed3cc728811911c25"}, @@ -30,8 +29,7 @@ rublon::Configuration confBypass{rublon::Configuration{// true, true, false, - true, - true}}; + rublon::FailMode::safe}}; inline std::string randomTID() { static const char alphanum[] = diff --git a/PAM/ssh/tests/init_test.cpp b/PAM/ssh/tests/init_test.cpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/tests/method_select_tests.cpp b/PAM/ssh/tests/method_select_tests.cpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/tests/pam_info_mock.hpp b/PAM/ssh/tests/pam_info_mock.hpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/tests/passcode_auth_tests.cpp b/PAM/ssh/tests/passcode_auth_tests.cpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/tests/sign_tests.cpp b/PAM/ssh/tests/sign_tests.cpp old mode 100644 new mode 100755 diff --git a/PAM/ssh/tests/utils_tests.cpp b/PAM/ssh/tests/utils_tests.cpp old mode 100644 new mode 100755 diff --git a/os/centos/stream9/Vagrantfile b/os/centos/stream9/Vagrantfile index e8f40f8..efb86cc 100755 --- a/os/centos/stream9/Vagrantfile +++ b/os/centos/stream9/Vagrantfile @@ -36,7 +36,7 @@ Vagrant.configure("2") do |config| config.vm.provision "shell", inline: <<-SHELL yum update - yum install -y gcc openssl-devel curl-devel pam-devel git rapidjson-devel cmake policycoreutils-devel checkpolicy + yum install -y gcc openssl-devel libcurl-devel pam-devel git rapidjson-devel cmake policycoreutils-devel checkpolicy # get dependencies git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git diff --git a/os/debian/11/Vagrantfile b/os/debian/11/Vagrantfile index 163a527..06e4b8e 100755 --- a/os/debian/11/Vagrantfile +++ b/os/debian/11/Vagrantfile @@ -53,14 +53,14 @@ Vagrant.configure("2") do |config| git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git mkdir socket.io-client-cpp/build; cd socket.io-client-cpp/build cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release .. - cmake --build . --target install + cmake --build . --target install # Build project cd /home/vagrant/Rublon-Linux - cmake -B build && cmake --build build + cmake -B buildir && cmake --build buildir # Install - sudo cmake --install build + sudo cmake --install buildir sudo install -m 644 rsc/rublon.config.defaults /etc/rublon.config # Register Rublon pam @@ -79,6 +79,4 @@ Vagrant.configure("2") do |config| service sshd restart SHELL - - config.vm.provision "file", source: "/etc/rublon.config", destination: "/etc/rublon.config" end diff --git a/os/debian/12/Vagrantfile b/os/debian/12/Vagrantfile index b611156..b8b8f35 100755 --- a/os/debian/12/Vagrantfile +++ b/os/debian/12/Vagrantfile @@ -25,6 +25,8 @@ Vagrant.configure("2") do |config| config.vm.provider "virtualbox" do |vb| # Display the VirtualBox GUI when booting the machine vb.gui = true + vb.memory = 1024 + vb.cpus = 4 # Fix for 'SSH auth method: Private key' stuck vb.customize ["modifyvm", :id, "--cableconnected1", "on"] @@ -45,7 +47,13 @@ Vagrant.configure("2") do |config| libssl-dev \ git \ rapidjson-dev \ - cmake + cmake + + # get dependencies + git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git + mkdir socket.io-client-cpp/build; cd socket.io-client-cpp/build + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release .. + cmake --build . --target install # Build project cd /home/vagrant/Rublon-Linux @@ -70,4 +78,5 @@ Vagrant.configure("2") do |config| echo "bwi:bwi"|chpasswd service sshd restart + SHELL end diff --git a/os/rhel/9/Vagrantfile b/os/rhel/9/Vagrantfile index 3aed343..047ec8c 100755 --- a/os/rhel/9/Vagrantfile +++ b/os/rhel/9/Vagrantfile @@ -21,7 +21,7 @@ Vagrant.configure("2") do |config| # the path on the guest to mount the folder. And the optional third # argument is a set of non-required options. config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux" - + config.vm.provider "virtualbox" do |vb| vb.memory = 1024 vb.cpus = 4 @@ -38,8 +38,14 @@ Vagrant.configure("2") do |config| config.vm.provision "shell", inline: <<-SHELL yum update - yum install -y gcc openssl-devel curl-devel pam-devel git rapidjson-devel cmake - + yum install -y gcc openssl-devel libcurl systemd-pam git-review rapidjson-devel cmake + wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.9.4.tar.xz + tar -xf git* + cd git* + sudo make configure + sudo ./configure --prefix=/usr + sudo make all + sudo make install # get dependencies git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git mkdir socket.io-client-cpp/build; cd socket.io-client-cpp/build @@ -56,7 +62,7 @@ Vagrant.configure("2") do |config| cmake -B build && cmake --build build # Install sudo cmake --install build - sudo install -m 644 rsc/rublon.config.defaults /etc/rublon.config + sudo install -m 644 /home/vagrant/Rublon-Linux/rsc/rublon.config.defaults /etc/rublon.config #handle semodule cd /home/vagrant/Rublon-Linux/service @@ -74,6 +80,5 @@ Vagrant.configure("2") do |config| systemctl restart sshd.service SHELL - config.vm.provision "file", source: "/etc/rublon.config", destination: "/etc/rublon.config" end diff --git a/os/ubuntu/20.04/Vagrantfile b/os/ubuntu/20.04/Vagrantfile index 727bc63..f7acaaf 100755 --- a/os/ubuntu/20.04/Vagrantfile +++ b/os/ubuntu/20.04/Vagrantfile @@ -23,8 +23,10 @@ Vagrant.configure("2") do |config| config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux" config.vm.provider "virtualbox" do |vb| + vb.memory = 2560 + vb.vcpu = 4 # Display the VirtualBox GUI when booting the machine - vb.gui = true + vb.gui = false # Fix for 'SSH auth method: Private key' stuck vb.customize ["modifyvm", :id, "--cableconnected1", "on"] @@ -34,47 +36,42 @@ Vagrant.configure("2") do |config| # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the # documentation for more information about their specific syntax and use. config.vm.provision "shell", inline: <<-SHELL + export BASE=/home/vagrant/Rublon-Linux + export DISTRO=ubuntu2004 + export BUILDDIR=${BASE}/${DISTRO} + + DEBIAN_FRONTEND=noniteracactive\ + apt-get update && apt-get install -y \ + gcc \ + g++ \ + build-essential \ + openssh-server \ + libcurl4-openssl-dev \ + libpam0g-dev \ + libssl-dev \ + git \ + rapidjson-dev \ + cmake + + # get dependencies + mkdir ${BUILDDIR} -p; cd ${BUILDDIR} + git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git + cmake -B socket.io-build -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release socket.io-client-cpp + cmake --build socket.io-build --target install -- -j + ln -s /usr/bin/make /usr/bin/gmake + # Build project + cd ${BUILDDIR} + cmake -B rublon_ssh-build .. + cmake --build rublon_ssh-build -- -j + cmake --build rublon_ssh-build --target package -- -j + # Register Rublon pam - DEBIAN_FRONTEND=noniteracactive\ - apt-get update && apt-get install -y \ - gcc \ - build-essential \ - openssh-server \ - libcurl4-openssl-dev \ - libpam0g-dev \ - libssl-dev \ - git \ - rapidjson-dev \ - cmake - - # get dependencies - git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git - mkdir socket.io-client-cpp/build; cd socket.io-client-cpp/build - cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release .. - cmake --build . --target install - - # Build project - cd /home/vagrant/Rublon-Linux - cmake -B build && cmake --build build - - # Install - sudo cmake --install build - sudo install -m 644 rsc/rublon.config.defaults /etc/rublon.config - - # Register Rublon pam - SSHD_CONF=/etc/ssh/sshd_config - SSHD_PAM_CONF=/etc/pam.d/sshd - - grep -qe "^PasswordAuthentication" $SSHD_CONF && sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || echo "PasswordAuthentication yes" >> $SSHD_CONF - grep -qe "^ChallengeResponseAuthentication" $SSHD_CONF && sed -i 's/^#*ChallengeResponseAuthentication[[:space:]]\+.*/ChallengeResponseAuthentication yes/' $SSHD_CONF || echo "ChallengeResponseAuthentication yes" >> $SSHD_CONF - grep -qe "^UsePAM" $SSHD_CONF && sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || echo "UsePAM yes" >> $SSHD_CONF - - grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '\$aauth required pam_rublon.so' $SSHD_PAM_CONF - grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '\$aaccount required pam_rublon.so' $SSHD_PAM_CONF - - useradd -s /bin/bash -m bwi - echo "bwi:bwi"|chpasswd - - service sshd restart - SHELL + SSHD_CONF=/etc/ssh/sshd_config + grep -qe "^PasswordAuthentication" $SSHD_CONF && sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF + useradd -s /bin/bash -m bwi + echo "bwi:bwi"|chpasswd + sudo dpkg -i /home/vagrant/Rublon-Linux/${BUILDDIR}/*pam_* + cp /home/vagrant/Rublon-Linux/rublon.config /etc/rublon.config + service sshd restart + SHELL end diff --git a/os/ubuntu/22.04/Vagrantfile b/os/ubuntu/22.04/Vagrantfile index 5ec8a93..8e1d0f3 100755 --- a/os/ubuntu/22.04/Vagrantfile +++ b/os/ubuntu/22.04/Vagrantfile @@ -23,8 +23,10 @@ Vagrant.configure("2") do |config| config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux" config.vm.provider "virtualbox" do |vb| + vb.memory = 2560 + vb.vcpu = 4 # Display the VirtualBox GUI when booting the machine - vb.gui = true + vb.gui = false # Fix for 'SSH auth method: Private key' stuck vb.customize ["modifyvm", :id, "--cableconnected1", "on"] @@ -64,17 +66,17 @@ Vagrant.configure("2") do |config| # Register Rublon pam SSHD_CONF=/etc/ssh/sshd_config SSHD_PAM_CONF=/etc/pam.d/sshd + SSHD_CONFD=/etc/ssh/sshd_config.d/60-cloudimg-settings.conf - grep -qe "^PasswordAuthentication" $SSHD_CONF && sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || echo "PasswordAuthentication yes" >> $SSHD_CONF - grep -qe "^ChallengeResponseAuthentication" $SSHD_CONF && sed -i 's/^#*ChallengeResponseAuthentication[[:space:]]\+.*/ChallengeResponseAuthentication yes/' $SSHD_CONF || echo "ChallengeResponseAuthentication yes" >> $SSHD_CONF - grep -qe "^UsePAM" $SSHD_CONF && sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || echo "UsePAM yes" >> $SSHD_CONF - - grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '\$aauth required pam_rublon.so' $SSHD_PAM_CONF - grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '\$aaccount required pam_rublon.so' $SSHD_PAM_CONF + grep -qe "^PasswordAuthentication" $SSHD_CONF && sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF + sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' $SSHD_CONFD useradd -s /bin/bash -m bwi echo "bwi:bwi"|chpasswd - + cd ./build + make package + sudo dpkg -i /home/vagrant/Rublon-Linux/build/*pam_* + cp /home/vagrant/Rublon-Linux/rublon.config /etc/rublon.config service sshd restart SHELL end diff --git a/os/ubuntu/24.04/Vagrantfile b/os/ubuntu/24.04/Vagrantfile new file mode 100644 index 0000000..b4b719e --- /dev/null +++ b/os/ubuntu/24.04/Vagrantfile @@ -0,0 +1,84 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Default user +# ---------------------- +# login: vagrant +# pass: vagrant +Vagrant.configure("2") do |config| + # Basic configuration + config.vm.box = "alvistack/ubuntu-24.04" + config.ssh.forward_agent = true + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + config.vm.network "public_network" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + config.vm.synced_folder "../../..", "/home/vagrant/Rublon-Linux" + + config.vm.provider "virtualbox" do |vb| + vb.memory = 2560 + vb.vcpu = 4 + # Display the VirtualBox GUI when booting the machine + vb.gui = false + + # Fix for 'SSH auth method: Private key' stuck + vb.customize ["modifyvm", :id, "--cableconnected1", "on"] + end + + # Enable provisioning with a shell script. Additional provisioners such as + # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the + # documentation for more information about their specific syntax and use. + config.vm.provision "shell", inline: <<-SHELL + + DEBIAN_FRONTEND=noniteracactive\ + apt-get update && apt-get install -y \ + gcc \ + build-essential \ + openssh-server \ + libcurl4-openssl-dev \ + libpam0g-dev \ + libssl-dev \ + git \ + rapidjson-dev \ + cmake + + # get dependencies + git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git + mkdir socket.io-client-cpp/build; cd socket.io-client-cpp/build + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release .. + cmake --build . --target install + + # Build project + cd /home/vagrant/Rublon-Linux + cmake -B build && cmake --build build + + # Install + sudo cmake --install build + sudo install -m 644 rsc/rublon.config.defaults /etc/rublon.config + + # Register Rublon pam + SSHD_CONF=/etc/ssh/sshd_config + SSHD_PAM_CONF=/etc/pam.d/sshd + + grep -qe "^PasswordAuthentication" $SSHD_CONF && sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || echo "PasswordAuthentication yes" >> $SSHD_CONF + grep -qe "^ChallengeResponseAuthentication" $SSHD_CONF && sed -i 's/^#*ChallengeResponseAuthentication[[:space:]]\+.*/ChallengeResponseAuthentication yes/' $SSHD_CONF || echo "ChallengeResponseAuthentication yes" >> $SSHD_CONF + grep -qe "^UsePAM" $SSHD_CONF && sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || echo "UsePAM yes" >> $SSHD_CONF + + grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '\$aauth required pam_rublon.so' $SSHD_PAM_CONF + grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '\$aaccount required pam_rublon.so' $SSHD_PAM_CONF + + useradd -s /bin/bash -m bwi + echo "bwi:bwi"|chpasswd +cd ./build +make package +sudo dpkg -i /home/vagrant/Rublon-Linux/build/*pam_* + cp /home/vagrant/Rublon-Linux/rublon.config /etc/rublon.config + service sshd restart + SHELL +end diff --git a/pack.cmake b/pack.cmake old mode 100644 new mode 100755 index 06388aa..6b8b484 --- a/pack.cmake +++ b/pack.cmake @@ -12,7 +12,6 @@ set(CPACK_PACKAGE_VENDOR "Rublon") #set(CPACK_VERBATIM_VARIABLES YES) - set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) @@ -30,8 +29,7 @@ set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS YES) set(CPACK_GENERATOR "DEB") # set(CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "libcurl4(>= 7.0.0), libc(>= 2.0)") -# set(CPACK_DEBIAN_PACKAGE_DEPENDS "libcurl4(>= 7.0.0), libc(>= 2.0)") - +# set(CPACK_DEBIAN_PACKAGE_DEPENDS "libcurl4(>= 7.0.0), libc(>= 2.0), libssl(>= 1.0)") set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/service/postinst") include(CPack) diff --git a/rsc/rublon.config.defaults b/rsc/rublon.config.defaults old mode 100644 new mode 100755 index b269719..583f45e --- a/rsc/rublon.config.defaults +++ b/rsc/rublon.config.defaults @@ -2,8 +2,7 @@ systemToken= secretKey= userDomain= rublonApiServer=https://core.rublon.net -failMode=bypass -offlineBypass=true +failMode=safe prompt=1 logging=true enablePasswdEmail=true diff --git a/service/login_rublon.mod b/service/login_rublon.mod old mode 100644 new mode 100755 diff --git a/service/login_rublon.pp b/service/login_rublon.pp old mode 100644 new mode 100755 diff --git a/service/login_rublon.te b/service/login_rublon.te old mode 100644 new mode 100755 diff --git a/service/postinst b/service/postinst old mode 100644 new mode 100755 index 6369730..1bd2921 --- a/service/postinst +++ b/service/postinst @@ -6,33 +6,29 @@ RUBLON_CONFIG=/etc/rublon.config if [ ! -f /etc/rublon.config ] then - mkdir -p /etc/rublon cp -a /usr/share/rublon/rublon.config.defaults $RUBLON_CONFIG chown root:root $RUBLON_CONFIG chmod 640 $RUBLON_CONFIG fi -# get system id -. /etc/os-release - +if [ -f /etc/os-release ] +then + . /etc/os-release +fi grep -qe "^PasswordAuthentication" $SSHD_CONF && \ sed -i 's/^#*PasswordAuthentication[[:space:]]\+.*/PasswordAuthentication yes/' $SSHD_CONF || \ echo "PasswordAuthentication yes" >> $SSHD_CONF + grep -qe "^ChallengeResponseAuthentication" $SSHD_CONF && \ sed -i 's/^#*ChallengeResponseAuthentication[[:space:]]\+.*/ChallengeResponseAuthentication yes/' $SSHD_CONF || \ echo "ChallengeResponseAuthentication yes" >> $SSHD_CONF + grep -qe "^UsePAM" $SSHD_CONF && \ sed -i 's/^#*UsePAM[[:space:]]\+.*/UsePAM yes/' $SSHD_CONF || \ echo "UsePAM yes" >> $SSHD_CONF + +sed -i 's/KbdInteractiveAuthentication/#KbdInteractiveAuthentication/' $SSHD_CONF grep -qe 'auth required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aauth required pam_rublon.so' $SSHD_PAM_CONF grep -qe 'account required pam_rublon.so' $SSHD_PAM_CONF || sed -i '$aaccount required pam_rublon.so' $SSHD_PAM_CONF - - -# ### UBUNTU 22.04 notes -# on ubuntu 22.04 options -# PasswordAuthentication yes -# ChallengeResponseAuthentication yes -# need to be set in configuration file AFTER UsePAM yes -# it will fail to use PAM (and will lock the machine) it this is not true