342 lines
10 KiB
C++
Executable File
342 lines
10 KiB
C++
Executable File
#pragma once
|
|
|
|
#include <tl/expected.hpp>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include <rublon/memory.hpp>
|
|
#include <rublon/static_string.hpp>
|
|
#include <rublon/stdlib.hpp>
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
|
|
#include <security/pam_appl.h>
|
|
#include <security/pam_modules.h>
|
|
|
|
namespace rublon {
|
|
inline bool fileGood(const std::filesystem::path & path) {
|
|
std::ifstream file(path);
|
|
return file.good();
|
|
}
|
|
|
|
template < typename T >
|
|
inline bool readFile(const std::filesystem::path & path, T & destination) {
|
|
if(not fileGood(path))
|
|
return false;
|
|
|
|
std::ifstream file(path);
|
|
file.seekg(0, std::ios::end);
|
|
size_t size = file.tellg();
|
|
destination.resize(size);
|
|
file.seekg(0);
|
|
file.read(&destination[0], size);
|
|
return true;
|
|
}
|
|
|
|
enum class LogLevel { Trace, Debug, Info, Warning, Error };
|
|
|
|
inline StaticString< 32 > dateStr() {
|
|
StaticString< 32 > date;
|
|
time_t now;
|
|
time(&now);
|
|
strftime(date.data(), date.capacity(), "%FT%TZ", gmtime(&now));
|
|
return date;
|
|
}
|
|
|
|
constexpr const char * LogLevelNames[]{"Trace", "Debug", "Info", "Warning", "Error"};
|
|
LogLevel g_level = LogLevel::Debug;
|
|
constexpr bool syncLogFile = true;
|
|
static const char * application = "";
|
|
|
|
namespace details {
|
|
constexpr const char * logPath() {
|
|
constexpr auto path = "/var/log/rublon-ssh.log";
|
|
return path;
|
|
}
|
|
|
|
inline void touch(const char * filename) {
|
|
close(open(filename, O_CREAT | O_RDWR, 0640));
|
|
}
|
|
|
|
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(initLog(), "a+"), fclose);
|
|
if(fp) {
|
|
/// TODO add transaction ID
|
|
fprintf(
|
|
fp.get(), "%s %s[%s] %s\n", dateStr().c_str(), 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
|
|
|
|
inline void log(LogLevel level, const char * line) noexcept {
|
|
if(level < g_level)
|
|
return;
|
|
details::doLog(level, line);
|
|
}
|
|
|
|
template < typename... Ti >
|
|
void log(LogLevel level, const char * fmt, Ti &&... ti) noexcept {
|
|
if(level < g_level)
|
|
return;
|
|
constexpr auto maxEntryLength = 1000;
|
|
std::array< char, maxEntryLength > line;
|
|
snprintf(line.data(), maxEntryLength, fmt, std::forward< Ti >(ti)...);
|
|
details::doLog(level, line.data());
|
|
}
|
|
|
|
class PrintUser {
|
|
public:
|
|
template < typename Printer >
|
|
PrintUser(Printer & p) : _impl{std::make_unique< ModelImpl >(p)} {}
|
|
|
|
private:
|
|
struct model {
|
|
virtual ~model();
|
|
virtual void print(std::string_view line) const = 0;
|
|
};
|
|
|
|
template < typename Printer_T >
|
|
struct ModelImpl : public model {
|
|
ModelImpl(Printer_T & printer) : _printer{printer} {}
|
|
void print(std::string_view line) const override {
|
|
_printer.print(line);
|
|
}
|
|
Printer_T & _printer;
|
|
};
|
|
|
|
std::unique_ptr< model > _impl;
|
|
};
|
|
|
|
namespace conv {
|
|
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(userinput.cbegin(), userinput.cend(), buf.data(), asciitolower);
|
|
return strcmp(buf.data(), "true") == 0;
|
|
}
|
|
|
|
inline std::optional< std::uint32_t > to_uint32opt(std::string_view userinput) noexcept {
|
|
constexpr auto max = std::numeric_limits< uint32_t >::digits10 + 1;
|
|
if(userinput.empty() || userinput.size() >= max)
|
|
return std::nullopt; // Avoid large or empty inputs
|
|
|
|
char buffer[max]={0};
|
|
std::memcpy(buffer, userinput.data(), userinput.size());
|
|
buffer[userinput.size()] = '\0'; // Ensure null termination
|
|
|
|
char * endptr = nullptr;
|
|
errno = 0;
|
|
|
|
long result = std::strtol(buffer, &endptr, 10);
|
|
|
|
if(errno == ERANGE || endptr != buffer + userinput.size() || result < 0 || result > std::numeric_limits<uint32_t>::max()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return static_cast< std::uint32_t >(result);
|
|
}
|
|
|
|
inline tl::expected< std::uint32_t, Error > to_uint32(std::string_view userinput) noexcept {
|
|
auto val = to_uint32opt(userinput);
|
|
if(val)
|
|
return *val;
|
|
return tl::unexpected{Error::NotANumber};
|
|
}
|
|
} // namespace conv
|
|
|
|
namespace details {
|
|
static inline std::string_view ltrim(std::string_view s) {
|
|
while(s.length() && std::isspace(*s.begin()))
|
|
s.remove_prefix(1);
|
|
return s;
|
|
}
|
|
|
|
static inline std::string_view rtrim(std::string_view s) {
|
|
while(s.length() && std::isspace(*s.rbegin()))
|
|
s.remove_suffix(1);
|
|
return s;
|
|
}
|
|
|
|
static inline std::string_view trim(std::string_view s) {
|
|
return ltrim(rtrim(s));
|
|
}
|
|
|
|
template < typename StrT >
|
|
void trimInPlace(StrT & s) {
|
|
// Remove leading whitespace
|
|
size_t start = 0;
|
|
while(start < s.size() && isspace(static_cast< unsigned char >(s[start])))
|
|
++start;
|
|
|
|
// Remove trailing whitespace
|
|
size_t end = s.size();
|
|
while(end > start && isspace(static_cast< unsigned char >(s[end - 1])))
|
|
--end;
|
|
|
|
if(start > 0 || end < s.size()) {
|
|
s = s.substr(start, end - start);
|
|
}
|
|
}
|
|
|
|
template < typename Headers >
|
|
inline void headers(std::string_view data, Headers & headers) {
|
|
memory::Monotonic_4k_Resource stackResource;
|
|
std::pmr::string tmp{&stackResource};
|
|
tmp.reserve(300);
|
|
|
|
std::istringstream resp{};
|
|
resp.rdbuf()->pubsetbuf(const_cast< char * >(data.data()), data.size());
|
|
|
|
while(std::getline(resp, tmp)) {
|
|
if(tmp == "\r")
|
|
continue;
|
|
if(trim(tmp).empty())
|
|
break;
|
|
auto line = std::string_view(tmp);
|
|
auto index = tmp.find(':', 0);
|
|
if(index != std::string::npos) {
|
|
headers.insert({//
|
|
typename Headers::key_type{trim(line.substr(0, index)), headers.get_allocator()},
|
|
typename Headers::mapped_type{trim(line.substr(index + 1)), headers.get_allocator()}});
|
|
}
|
|
}
|
|
}
|
|
|
|
std::pmr::string hostname(std::pmr::memory_resource * mr) {
|
|
// longest hostname on linux is 253 characters
|
|
std::pmr::string hostname{255, '\0', mr};
|
|
if(gethostname(hostname.data(), hostname.size()) != 0) {
|
|
log(LogLevel::Warning, "Hostname is not available");
|
|
return "";
|
|
}
|
|
hostname.resize(hostname.find_first_of('\0'));
|
|
return hostname;
|
|
}
|
|
|
|
std::pmr::string osName(std::pmr::memory_resource * mr) {
|
|
memory::Monotonic_1k_Resource memoryResource;
|
|
|
|
std::ifstream file(std::filesystem::path{"/etc/os-release"});
|
|
if(not file.good())
|
|
return {"unknown", mr};
|
|
|
|
std::pmr::string line{&memoryResource};
|
|
std::pmr::string _key{&memoryResource};
|
|
std::pmr::string _value{&memoryResource};
|
|
line.reserve(100);
|
|
|
|
while(std::getline(file, 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);
|
|
|
|
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};
|
|
}
|
|
|
|
} // namespace details
|
|
|
|
template < class InputIterator, class OutputIterator, class UnaryOperator, class Pred >
|
|
OutputIterator transform_if(InputIterator first1, InputIterator last1, OutputIterator result, UnaryOperator op, Pred pred) {
|
|
while(first1 != last1) {
|
|
if(pred(*first1)) {
|
|
*result = op(*first1);
|
|
++result;
|
|
}
|
|
++first1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
struct ci_less {
|
|
template < typename S >
|
|
bool operator()(const S & s1, const S & s2) const {
|
|
return std::lexicographical_compare(
|
|
s1.cbegin(), s1.cend(), s2.cbegin(), s2.cend(), [](const auto & c1, const auto & c2) { return tolower(c1) < tolower(c2); });
|
|
}
|
|
};
|
|
|
|
template < typename Out, typename... Types >
|
|
constexpr std::array< Out, sizeof...(Types) > make_array(Types... names) {
|
|
return {std::forward< Types >(names)...};
|
|
}
|
|
|
|
template < typename T >
|
|
std::size_t size_buffer(const T & item) {
|
|
using U = std::decay_t< T >;
|
|
if constexpr(std::is_same_v< U, const char * >) {
|
|
return strlen(item);
|
|
} else if constexpr(std::is_same_v< U, std::pmr::string > || std::is_same_v< U, std::string >) {
|
|
return item.size();
|
|
} else if constexpr(std::is_integral_v< U > || std::is_floating_point_v< U >) {
|
|
return std::numeric_limits< U >::digits;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
template < typename T >
|
|
std::size_t size_buffer(const std::optional< T > & item) {
|
|
if(item.has_value())
|
|
return size_buffer(*item);
|
|
return 0;
|
|
}
|
|
|
|
// min + 10%
|
|
template < typename... Args >
|
|
std::size_t conservative_estimate(const Args &... args) {
|
|
auto min = (size_buffer(args) + ...);
|
|
return min + min * 10 / 100;
|
|
}
|
|
|
|
} // namespace rublon
|