rublon-ssh/PAM/ssh/include/rublon/configuration.hpp
rublon-bwi 6a3882fa47
Bwi/v2.0.2 rc1 (#10)
* Remove unused options from rublon default config

* Remove safe|secure options

* Allow 9 digits long passcode for passcode bypass

* Change name of 'Mobile Passcode' to 'Passcode'

* Do not display any prompt when user is waiting

* remove unused alloca.h header

* Add autopushPrompt option

* Change name OTP method

* Change enrolement message handling

* ad static string ctor

* Addded postrm script

* Rename 01_rublon_ssh.conf to 01-rublon-ssh.conf

* restart sshd service after rublon package instalation

* Fix sshd not restarting bug on ubuntu 24.04

* disable logging from websocket-io

* change package name to match old package name

* Fix compilation issue when using non owning ptr

* Set version to 2.0.0
2024-06-17 08:57:26 +02:00

206 lines
7.3 KiB
C++
Executable File

#pragma once
#include <filesystem>
#include <fstream>
#include <map>
#include <memory_resource>
#include <optional>
#include <string>
#include <rublon/utils.hpp>
#include <rublon/error.hpp>
#include <rublon/static_string.hpp>
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;
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{};
int prompt{};
bool enablePasswdEmail{};
bool logging{};
bool autopushPrompt{};
FailMode failMode{};
};
namespace {
template < class C, typename T >
T member_ptr_t(T C::*v);
template < typename T >
tl::expected< T, ConfigurationError > to(std::string_view);
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< bool, ConfigurationError > {
return conv::to_bool(arg);
}
template <>
auto to(std::string_view arg) -> tl::expected< int, ConfigurationError > {
return conv::to_uint32(arg).value_or(0);
}
template <>
auto to(std::string_view arg) -> tl::expected< FailMode, ConfigurationError > {
if(arg == "bypass")
return FailMode::bypass;
if(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 };
template < auto member >
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 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;
};
return parse< pType >(userInput).and_then(saveValue).or_else(setDefaultValue);
};
}
const char * name;
const char * defaultValue;
tl::expected< Source, ConfigurationError > (*_read)(const Entry * _this, Configuration * configuration, std::string_view userInput);
bool read(Configuration * configuration, std::optional< std::string_view > userInput) const {
constexpr const auto emptyString = "";
const auto logStored = [&](const auto & source) -> tl::expected< Source, ConfigurationError > {
rublon::log(LogLevel::Debug,
"Configuration parameter '%s' was set to '%s'%s",
this->name,
this->defaultValue,
source == Source::DefaultValue ? " (default)" : "");
return source;
};
const auto logError = [&](const auto & error) -> tl::expected< Source, ConfigurationError > {
rublon::log(LogLevel::Error,
"Configuration parameter '%s' has no default value and is not provided in user configuraion, aborting",
this->name);
return tl::unexpected{error};
};
return _read(this, configuration, userInput.value_or(emptyString)).and_then(logStored).or_else(logError).has_value();
}
};
template < auto member >
constexpr auto make_entry(const char * name, const char * defaultValue) {
return Entry{name, defaultValue, Entry::make_read_function< member >()};
}
constexpr static inline std::array< Entry, 8 > configurationVariables = { //
make_entry< &Configuration::logging >("logging", "true"),
make_entry< &Configuration::systemToken >("systemToken", nullptr),
make_entry< &Configuration::secretKey >("secretKey", nullptr),
make_entry< &Configuration::apiServer >("rublonApiServer", nullptr),
make_entry< &Configuration::prompt >("prompt", "1"),
make_entry< &Configuration::enablePasswdEmail >("enablePasswdEmail", "true"),
make_entry< &Configuration::autopushPrompt >("autopushPrompt", "false"),
make_entry< &Configuration::failMode >("failMode", "deny")};
class ConfigurationFactory {
public:
ConfigurationFactory() = default;
std::optional< Configuration > systemConfig() {
memory::MonotonicStackResource< 8 * 1024 > stackResource;
Configuration configuration{};
std::ifstream file(std::filesystem::path{"/etc/rublon.config"});
if(not file.good())
return std::nullopt;
std::pmr::string line{&stackResource};
line.reserve(100);
std::pmr::map< std::pmr::string, std::pmr::string > parameters{&stackResource};
const auto readParameterByName = [&](std::string_view name) -> std::optional< std::string_view > {
return parameters.count(name.data()) ? std::optional< std::string_view >{parameters.at(name.data())} : std::nullopt;
};
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);
parameters[std::move(key)] = std::move(value);
}
for(const auto & entry : configurationVariables) {
if(not entry.read(&configuration, readParameterByName(entry.name)))
return std::nullopt;
}
return configuration;
}
};
} // namespace rublon