rublon-ssh/PAM/ssh/include/rublon/check_application.hpp
rublon-bwi 351964199a
Bwi/v2.3.2 (#19)
* Prevent printing in noninteractive mode

* Allow PAM modules to be configurated directly in pam.d

* Configuration should be redable by everybody

* Add a way to read ip address in when no IP is awailable

* Enable read ip from pam

* Fix veritas BUG
2025-09-11 10:35:22 +02:00

277 lines
9.7 KiB
C++

#pragma once
#include <rublon/configuration.hpp>
#include <rublon/session.hpp>
#include <rublon/bits.hpp>
#include <rublon/core_handler.hpp>
#include <rublon/curl.hpp>
#include <rublon/error.hpp>
#include <rublon/json.hpp>
#include <rublon/memory.hpp>
#include <rublon/utils.hpp>
namespace rublon {
struct closefile_deleter {
void operator()(FILE * f) const {
pclose(f);
}
};
std::string exec(const char * cmd) {
std::array< char, 128 > buffer;
std::string result;
std::unique_ptr< FILE, closefile_deleter > pipe(popen(cmd, "r"));
if(!pipe) {
return "";
}
while(fgets(buffer.data(), static_cast< int >(buffer.size()), pipe.get()) != nullptr) {
result += buffer.data();
}
return result;
}
std::map< std::string, std::string > getSSHDConfig() {
std::istringstream iss(exec("sshd -T"));
std::map< std::string, std::string > result;
for(std::string line; std::getline(iss, line);) {
auto first_token = line.substr(0, line.find(' '));
result[first_token] = line.substr(line.find(' ') + 1);
}
return result;
}
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 _paramSSHDBase = "/params/sshd_config/";
std::string_view _configBase = "/config/";
public:
Status() : _alloc{}, _data{&_alloc}, _statusUpdated{false} {
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()) {
return;
}
rapidjson::IStreamWrapper isw{ifs};
_data.ParseStream(isw);
}
void updateAppVersion(std::string_view newVersion) {
RapidJSONPMRStackAlloc< 512 > stackAlloc;
auto jsonPointer = JSONPointer{_appVersionKey.data(), &stackAlloc};
auto version = jsonPointer.Get(_data);
if(not version || version->GetString() != newVersion) {
markUpdated();
jsonPointer.Set(_data, Value{newVersion.data(), _data.GetAllocator()});
}
}
void updateSystemVersion(std::string_view system) {
RapidJSONPMRStackAlloc< 512 > stackAlloc;
auto jsonPointer = JSONPointer{_paramSystemName.data(), &stackAlloc};
auto version = jsonPointer.Get(_data);
if(not version || version->GetString() != system) {
markUpdated();
jsonPointer.Set(_data, Value{system.data(), _data.GetAllocator()});
}
}
void updateSSHDConfig() {
using namespace std::string_view_literals;
constexpr auto keys = make_array< std::string_view >("authenticationmethods"sv,
"challengeresponseauthentication"sv,
"kbdinteractiveauthentication"sv,
"logingracetime"sv,
"maxauthtries"sv,
"passwordauthentication"sv,
"permitemptypasswords"sv,
"permitrootlogin"sv,
"pubkeyauthentication"sv,
"usepam"sv);
auto config = getSSHDConfig();
for(const auto key : keys) {
auto [currentPair, inserted] = config.try_emplace(std::string{key.data()}, "N/A");
auto & [currentKey, currentValue] = *currentPair;
const auto jsonPath = std::string{_paramSSHDBase.data()} + key.data();
RapidJSONPMRStackAlloc< 512 > stackAlloc;
auto jsonPointer = JSONPointer{jsonPath.c_str(), &stackAlloc};
auto oldValue = jsonPointer.Get(_data);
if(not oldValue || oldValue->GetString() != currentValue) {
_statusUpdated = true;
jsonPointer.Set(_data, Value{currentValue.c_str(), _data.GetAllocator()});
}
}
}
template < typename T >
void updateRublonConfigParameter(std::string_view name, const T & newParam) {
memory::MonotonicStack_1k_Resource memoryResource{};
RapidJSONPMRAlloc stackAlloc{&memoryResource};
std::pmr::string jsonPath{&memoryResource};
jsonPath += _configBase;
jsonPath += name;
JSONPointer jsonPointer{jsonPath.c_str(), &stackAlloc};
auto * param = jsonPointer.Get(_data);
Document::AllocatorType & alloc = _data.GetAllocator();
Value newValue;
if constexpr(std::is_same_v< T, std::pmr::string > || std::is_same_v< T, std::string_view >) {
newValue.SetString(newParam.data(), static_cast< rapidjson::SizeType >(newParam.size()), alloc);
if(!param || !param->IsString() || param->GetString() != newParam) {
markUpdated();
jsonPointer.Set(_data, newValue);
}
} else if constexpr(std::is_same_v< T, bool >) {
newValue.SetBool(newParam);
if(!param || !param->IsBool() || param->GetBool() != newParam) {
markUpdated();
jsonPointer.Set(_data, newValue);
}
} else if constexpr(std::is_integral_v< T >) {
newValue.SetInt(static_cast< int >(newParam));
if(!param || !param->IsInt() || param->GetInt() != static_cast< int >(newParam)) {
markUpdated();
jsonPointer.Set(_data, newValue);
}
}
}
// For std::optional<T>
template < typename T >
void updateRublonConfigParameter(std::string_view name, const std::optional< T > & optParam) {
memory::MonotonicStack_1k_Resource memoryResource{};
RapidJSONPMRAlloc stackAlloc{&memoryResource};
std::pmr::string fullPath{&memoryResource};
fullPath += _configBase;
fullPath += name;
JSONPointer jsonPointer{fullPath.c_str(), &stackAlloc};
Value* existing = jsonPointer.Get(_data);
if (optParam.has_value()) {
// Delegate to regular (non-optional) setter
updateRublonConfigParameter(name, *optParam);
} else {
// Set the entire field to `null` if no value
if (!existing || !existing->IsNull()) {
markUpdated();
Value nullValue(rapidjson::kNullType);
jsonPointer.Set(_data, nullValue);
}
}
}
void updateRublonConfig(const Configuration & config) {
updateRublonConfigParameter("prompt", config.prompt);
updateRublonConfigParameter("logging", config.logging);
updateRublonConfigParameter("autopushPrompt", config.autopushPrompt);
updateRublonConfigParameter("failMode", static_cast<int>(config.failMode));
updateRublonConfigParameter("nonInteractiveMode", config.nonInteractiveMode);
updateRublonConfigParameter("proxyType", config.proxyType);
updateRublonConfigParameter("proxyHost", config.proxyHost);
updateRublonConfigParameter("proxyUsername", config.proxyUsername);
updateRublonConfigParameter("proxyPassword", config.proxyPass);
updateRublonConfigParameter("proxyPort", config.proxyPort);
updateRublonConfigParameter("proxyAuthRequired", config.proxyAuthRequired);
updateRublonConfigParameter("proxyEnabled", config.proxyEnabled);
}
void markUpdated() {
_statusUpdated = true;
}
bool updated() const {
return _statusUpdated;
}
void save() {
if(updated()) {
memory::Monotonic_8k_Resource tmpResource;
RapidJSONPMRAlloc alloc{&tmpResource};
FileWriter s{_statusFilePath};
rapidjson::PrettyWriter< FileWriter, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc};
writer.SetIndent(' ', 2);
_data.Accept(writer);
}
}
std::string print() {
std::string result;
memory::Monotonic_8k_Resource tmpResource;
RapidJSONPMRAlloc alloc{&tmpResource};
StringWriter s{result};
rapidjson::PrettyWriter< StringWriter< std::string >, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc > writer{s, &alloc};
writer.SetIndent(' ', 2);
_data.Accept(writer);
return result;
}
Document & data() {
return _data;
}
};
class CheckApplication {
const Session & _sesion;
tl::expected< bool, Error > persistStatus(Status & status) const {
status.data().RemoveMember("systemToken");
status.save();
return true;
}
public:
CheckApplication(const Session & session) : _sesion{session} {}
tl::expected< int, Error > call(const CoreHandler_t & coreHandler, std::string_view systemToken) const {
memory::MonotonicStack_2k_Resource mr;
RapidJSONPMRAlloc alloc{&mr};
constexpr std::string_view api = "/api/app/init";
Status status;
const auto persist = [&](const auto /*ok*/) { return this->persistStatus(status); };
status.updateAppVersion(RUBLON_VERSION_STRING);
status.updateSystemVersion(details::osName(&mr));
status.updateSSHDConfig();
status.updateRublonConfig(_sesion.config());
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