#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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, std::string_view line) noexcept { auto fp = std::unique_ptr< FILE, int (*)(FILE *) >(fopen(initLog(), "a+"), fclose); if(fp) { auto newl = line.back() == '\n' ? "" : "\n"; /// TODO add transaction ID fprintf(fp.get(), "%s %s[%s] %s%s", dateStr().c_str(), application == nullptr ? "" : application, LogLevelNames[( int ) level], line.data(), newl); 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; auto len = snprintf(line.data(), maxEntryLength, fmt, std::forward< Ti >(ti)...); if(len>0) details::doLog(level, {line.data(), static_cast< std::size_t >(len)}); } 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(s[start])) ++start; // Remove trailing whitespace size_t end = s.size(); while(end > start && isspace(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 char host[255]{}; if(gethostname(host, sizeof(host)) != 0) { log(LogLevel::Warning, "Hostname is not available"); return ""; } return {host, mr}; } 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