rublon-ssh/PAM/ssh/include/rublon/configuration.hpp
2025-06-03 14:02:37 +02:00

224 lines
8.0 KiB
C++

#pragma once
#include <rublon/error.hpp>
#include <rublon/memory.hpp>
#include <rublon/static_string.hpp>
#include <rublon/utils.hpp>
#include <cctype>
#include <fstream>
#include <optional>
#include <string>
#include <memory_resource>
#include <unordered_map>
#include <utility>
namespace rublon {
class ConfigurationFactory;
enum class FailMode { bypass, deny };
class Configuration {
private:
std::pmr::memory_resource * memoryResource;
public:
Configuration(std::pmr::memory_resource * mr = memory::default_resource()) : memoryResource{mr} {}
// change to StaticString
std::pmr::string systemToken{memoryResource};
std::pmr::string secretKey{memoryResource};
std::pmr::string apiServer{memoryResource};
int prompt{};
bool enablePasswdEmail{};
bool logging{};
bool autopushPrompt{};
FailMode failMode{};
bool nonInteractiveMode{};
std::optional< std::pmr::string > proxyType{memoryResource};
std::optional< std::pmr::string > proxyServer{memoryResource};
std::optional< std::pmr::string > proxyUsername{memoryResource};
std::optional< std::pmr::string > proxyPass{memoryResource};
std::optional< int > proxyPort{};
bool proxyAuthRequired{}; // defaulted
bool proxyEnabled{}; // defaulted
};
class ConfigurationReader {
public:
ConfigurationReader(std::pmr::memory_resource * memResource = memory::default_resource()) : memoryResource(memResource) {}
// Load config from file path
bool loadFromFile(std::string_view filepath) {
using namespace memory::literals;
memory::MonotonicStackResource< 8_kB > stackResource;
std::ifstream file(filepath.data());
if(not file.good())
return false;
std::pmr::string line{&stackResource};
line.reserve(8000); // allocate full stack to line
while(std::getline(file, line)) {
std::pmr::string key{memoryResource};
std::pmr::string value{memoryResource};
details::trimInPlace(line);
if(!line.length())
continue;
if(line[0] == '#' || line[0] == ';')
continue;
auto posEqual = line.find('=');
key = line.substr(0, posEqual);
value = line.substr(posEqual + 1);
keyValues[std::move(key)] = std::move(value);
}
return true;
}
// Load values into Configuration object, with defaults provided
tl::expected< bool, ConfigurationError > applyTo(Configuration & config) {
// Helper lambdas for conversion
using string = std::pmr::string;
auto getStringOpt = [&](const string & key) -> std::optional< std::pmr::string > {
auto it = keyValues.find(key);
if(it == keyValues.end()) {
return std::nullopt;
}
return string{it->second.data(), it->second.size(), memoryResource};
};
auto getStringReq = [&](const string & key) -> tl::expected< std::pmr::string, ConfigurationError > {
auto val = getStringOpt(key);
if(val.has_value())
return std::move(val.value());
return tl::unexpected{ConfigurationError::ErrorClass::RequiredValueNotFound};
};
auto getInt = [&](const string & key) -> std::optional< int > {
auto it = keyValues.find(key);
if(it == keyValues.end())
return std::nullopt;
return conv::to_uint32opt(it->second);
};
auto getBool = [&](const string & key) -> std::optional< bool > {
memory::MonotonicStackResource< 64 > memoryResource;
auto it = keyValues.find(key);
if(it == keyValues.end())
return std::nullopt;
if(it->second.size() > 5) {
log(LogLevel::Warning, "Configuration value %s is too long, please check", key.c_str());
return std::nullopt;
}
std::pmr::string val{&memoryResource};
val = it->second;
std::transform(val.begin(), val.end(), val.begin(), [](unsigned char c) { return static_cast< char >(std::tolower(c)); });
if(val == "1" || val == "true" || val == "yes" || val == "on")
return true;
if(val == "0" || val == "false" || val == "no" || val == "off")
return false;
return std::nullopt;
};
auto getFailMode = [&](const string & key) -> std::optional< FailMode > {
auto it = keyValues.find(key);
if(it == keyValues.end())
return std::nullopt;
auto val = it->second;
if(val == "bypass")
return FailMode::bypass;
if(val == "deny")
return FailMode::deny;
return std::nullopt;
};
// Reading required fields
if(auto val = getStringReq("systemToken"); not val.has_value())
return tl::unexpected{val.error()};
else
config.systemToken = std::move(val.value());
if(auto val = getStringReq("secretKey"); not val.has_value())
return tl::unexpected{val.error()};
else
config.secretKey = std::move(val.value());
if(auto val = getStringReq("rublonApiServer"); not val.has_value())
return tl::unexpected{val.error()};
else
config.apiServer = std::move(val.value());
// optional
config.prompt = getInt("prompt").value_or(1);
config.enablePasswdEmail = getBool("enablePasswdEmail").value_or(true);
config.logging = getBool("logging").value_or(true);
config.autopushPrompt = getBool("autopushPrompt").value_or(false);
config.nonInteractiveMode = getBool("nonInteractiveMode").value_or(false);
config.failMode = getFailMode("failMode").value_or(FailMode::deny);
// reading proxy configuration
config.proxyEnabled = getBool("proxyEnabled").value_or(false);
config.proxyType = getStringOpt("proxyType");
config.proxyServer = getStringOpt("proxyServer");
if(config.proxyEnabled) {
if(not config.proxyType) {
log(LogLevel::Error, "Proxy is enabled but proxy type is not set");
return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration};
}
if(not config.proxyServer) {
log(LogLevel::Error, "Proxy is enabled but proxy server is not set");
return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration};
}
}
config.proxyAuthRequired = getBool("proxyAuthRequired").value_or(false);
config.proxyUsername = getStringOpt("proxyUsername");
config.proxyPass = getStringOpt("proxyPass");
if(config.proxyAuthRequired) {
if(not config.proxyUsername) {
log(LogLevel::Error, "Proxy auth is required but proxy proxy username is not set");
return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration};
}
if(not config.proxyPass) {
log(LogLevel::Error, "Proxy is enabled but proxy password is not set,");
return tl::unexpected{ConfigurationError::ErrorClass::BadConfiguration};
}
}
config.proxyPort = getInt("proxyPort").value_or(8080);
return true;
}
private:
std::pmr::memory_resource * memoryResource;
std::pmr::unordered_map< std::pmr::string, std::pmr::string > keyValues{memoryResource};
};
class ConfigurationFactory {
public:
ConfigurationFactory() = default;
std::optional< Configuration > systemConfig() {
std::optional< Configuration > conf{Configuration{}};
ConfigurationReader reader{};
reader.loadFromFile("/etc/rublon.config");
if(auto ok = reader.applyTo(conf.value()); not ok.has_value()) {
return std::nullopt;
}
return conf.value();
}
};
} // namespace rublon