// // License type: BSD 3-Clause License // License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE // // Created by Stephane Bourque on 2021-03-04. // Arilia Wireless Inc. // #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include // This must be defined for poco_debug and poco_trace macros to function. #ifndef POCO_LOG_DEBUG #define POCO_LOG_DEBUG true #endif namespace OpenWifi { inline uint64_t Now() { return std::time(nullptr); }; } namespace OpenWifi::Utils { std::vector base64decode(const std::string& input); std::string base64encode(const unsigned char *input, uint32_t size); } using namespace std::chrono_literals; #include "Poco/Util/Application.h" #include "Poco/Util/ServerApplication.h" #include "Poco/Util/Option.h" #include "Poco/Util/OptionSet.h" #include "Poco/UUIDGenerator.h" #include "Poco/ErrorHandler.h" #include "Poco/Crypto/RSAKey.h" #include "Poco/Crypto/CipherFactory.h" #include "Poco/Crypto/Cipher.h" #include "Poco/SHA2Engine.h" #include "Poco/Net/HTTPServerRequest.h" #include "Poco/Process.h" #include "Poco/Net/Context.h" #include "Poco/Net/SecureServerSocket.h" #include "Poco/Net/Socket.h" #include "Poco/Net/SSLManager.h" #include "Poco/Net/PrivateKeyPassphraseHandler.h" #include "Poco/Net/HTTPServerResponse.h" #include "Poco/Net/HTTPServer.h" #include "Poco/Net/FTPStreamFactory.h" #include "Poco/Net/FTPSStreamFactory.h" #include "Poco/Net/HTTPSStreamFactory.h" #include "Poco/Net/HTTPStreamFactory.h" #include "Poco/File.h" #include "Poco/Net/HTTPRequestHandler.h" #include "Poco/Net/OAuth20Credentials.h" #include "Poco/Util/HelpFormatter.h" #include "Poco/Net/PartHandler.h" #include "Poco/TemporaryFile.h" #include "Poco/NullStream.h" #include "Poco/CountingStream.h" #include "Poco/URI.h" #include "Poco/Net/HTTPSClientSession.h" #include "Poco/Net/NetworkInterface.h" #include "Poco/ExpireLRUCache.h" #include "Poco/JSON/Object.h" #include "Poco/JSON/Parser.h" #include "Poco/StringTokenizer.h" #include "Poco/AsyncChannel.h" #include "Poco/ConsoleChannel.h" #include "Poco/AutoPtr.h" #include "Poco/FormattingChannel.h" #include "Poco/PatternFormatter.h" #include "Poco/FileChannel.h" #include "Poco/SimpleFileChannel.h" #include "Poco/Util/PropertyFileConfiguration.h" #include "Poco/SplitterChannel.h" #include "Poco/JWT/Signer.h" #include "Poco/DeflatingStream.h" #include "Poco/Net/SocketReactor.h" #include "Poco/Net/WebSocket.h" #include "Poco/Environment.h" #include "Poco/NObserver.h" #include "Poco/Net/SocketNotification.h" #include "Poco/Base64Decoder.h" #include "cppkafka/cppkafka.h" #include "framework/OpenWifiTypes.h" #include "framework/KafkaTopics.h" #include "framework/ow_constants.h" #include "RESTObjects/RESTAPI_SecurityObjects.h" #include "nlohmann/json.hpp" #include "ow_version.h" #include "fmt/core.h" #define _OWDEBUG_ std::cout<< __FILE__ <<":" << __LINE__ << std::endl; // #define _OWDEBUG_ Logger().debug(Poco::format("%s: %lu",__FILE__,__LINE__)); namespace OpenWifi { enum UNAUTHORIZED_REASON { SUCCESS=0, PASSWORD_CHANGE_REQUIRED, INVALID_CREDENTIALS, PASSWORD_ALREADY_USED, USERNAME_PENDING_VERIFICATION, PASSWORD_INVALID, INTERNAL_ERROR, ACCESS_DENIED, INVALID_TOKEN, EXPIRED_TOKEN, RATE_LIMIT_EXCEEDED, BAD_MFA_TRANSACTION, MFA_FAILURE, SECURITY_SERVICE_UNREACHABLE, CANNOT_REFRESH_TOKEN }; class AppServiceRegistry { public: inline AppServiceRegistry(); static AppServiceRegistry & instance() { static auto instance_= new AppServiceRegistry; return *instance_; } inline ~AppServiceRegistry() { Save(); } inline void Save() { std::istringstream IS( to_string(Registry_)); std::ofstream OF; OF.open(FileName,std::ios::binary | std::ios::trunc); Poco::StreamCopier::copyStream(IS, OF); } inline void Set(const char *Key, uint64_t Value ) { Registry_[Key] = Value; Save(); } inline void Set(const char *Key, const std::string &Value ) { Registry_[Key] = Value; Save(); } inline void Set(const char *Key, bool Value ) { Registry_[Key] = Value; Save(); } inline bool Get(const char *Key, bool & Value ) { if(Registry_[Key].is_boolean()) { Value = Registry_[Key].get(); return true; } return false; } inline bool Get(const char *Key, uint64_t & Value ) { if(Registry_[Key].is_number_unsigned()) { Value = Registry_[Key].get(); return true; } return false; } inline bool Get(const char *Key, std::string & Value ) { if(Registry_[Key].is_string()) { Value = Registry_[Key].get(); return true; } return false; } private: std::string FileName; nlohmann::json Registry_; }; inline auto AppServiceRegistry() { return AppServiceRegistry::instance(); } } namespace OpenWifi::RESTAPI_utils { inline void EmbedDocument(const std::string & ObjName, Poco::JSON::Object & Obj, const std::string &ObjStr) { std::string D = ObjStr.empty() ? "{}" : ObjStr; Poco::JSON::Parser P; Poco::Dynamic::Var result = P.parse(D); const auto &DetailsObj = result.extract(); Obj.set(ObjName, DetailsObj); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, bool V) { Obj.set(Field,V); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, double V) { Obj.set(Field,V); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, float V) { Obj.set(Field,V); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const std::string & S) { Obj.set(Field,S); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const char * S) { Obj.set(Field,S); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, int16_t Value) { Obj.set(Field, Value); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, int32_t Value) { Obj.set(Field, Value); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, int64_t Value) { Obj.set(Field, Value); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint16_t Value) { Obj.set(Field, Value); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint32_t Value) { Obj.set(Field, Value); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint64_t Value) { Obj.set(Field,Value); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Poco::Data::BLOB &Value) { auto Result = Utils::base64encode((const unsigned char *)Value.rawContent(),Value.size()); Obj.set(Field,Result); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::StringPairVec & S) { Poco::JSON::Array Array; for(const auto &i:S) { Poco::JSON::Object O; O.set("tag",i.first); O.set("value", i.second); Array.add(O); } Obj.set(Field,Array); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::StringVec &V) { Poco::JSON::Array A; for(const auto &i:V) A.add(i); Obj.set(Field,A); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::TagList &V) { Poco::JSON::Array A; for(const auto &i:V) A.add(i); Obj.set(Field,A); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::CountedMap &M) { Poco::JSON::Array A; for(const auto &[Key,Value]:M) { Poco::JSON::Object O; O.set("tag",Key); O.set("value", Value); A.add(O); } Obj.set(Field,A); } inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::Counted3DMapSII &M) { Poco::JSON::Array A; for(const auto &[OrgName,MonthlyNumberMap]:M) { Poco::JSON::Object OrgObject; OrgObject.set("tag",OrgName); Poco::JSON::Array MonthlyArray; for(const auto &[Month,Counter]:MonthlyNumberMap) { Poco::JSON::Object Inner; Inner.set("value", Month); Inner.set("counter", Counter); MonthlyArray.add(Inner); } OrgObject.set("index",MonthlyArray); A.add(OrgObject); } Obj.set(Field, A); } template void field_to_json(Poco::JSON::Object &Obj, const char *Field, const T &V, std::function F) { Obj.set(Field, F(V)); } template void field_to_json(Poco::JSON::Object &Obj, const char *Field, const std::vector &Value) { Poco::JSON::Array Arr; for(const auto &i:Value) { Poco::JSON::Object AO; i.to_json(AO); Arr.add(AO); } Obj.set(Field, Arr); } template void field_to_json(Poco::JSON::Object &Obj, const char *Field, const T &Value) { Poco::JSON::Object Answer; Value.to_json(Answer); Obj.set(Field, Answer); } /////////////////////////// /////////////////////////// /////////////////////////// /////////////////////////// template bool field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, T & V, std::function F) { if(Obj->has(Field) && !Obj->isNull(Field)) V = F(Obj->get(Field).toString()); return true; } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, std::string &S) { if(Obj->has(Field) && !Obj->isNull(Field)) S = Obj->get(Field).toString(); } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, double & Value) { if(Obj->has(Field) && !Obj->isNull(Field)) Value = (double)Obj->get(Field); } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, float & Value) { if(Obj->has(Field) && !Obj->isNull(Field)) Value = (float)Obj->get(Field); } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, bool &Value) { if(Obj->has(Field) && !Obj->isNull(Field)) Value = (Obj->get(Field).toString() == "true"); } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, int16_t &Value) { if(Obj->has(Field) && !Obj->isNull(Field)) Value = (int16_t)Obj->get(Field); } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, int32_t &Value) { if(Obj->has(Field) && !Obj->isNull(Field)) Value = (int32_t) Obj->get(Field); } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, int64_t &Value) { if(Obj->has(Field) && !Obj->isNull(Field)) Value = (int64_t)Obj->get(Field); } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, uint16_t &Value) { if(Obj->has(Field) && !Obj->isNull(Field)) Value = (uint16_t)Obj->get(Field); } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, uint32_t &Value) { if(Obj->has(Field) && !Obj->isNull(Field)) Value = (uint32_t)Obj->get(Field); } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, uint64_t &Value) { if(Obj->has(Field) && !Obj->isNull(Field)) Value = (uint64_t)Obj->get(Field); } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Poco::Data::BLOB &Value) { if(Obj->has(Field) && !Obj->isNull(Field)) { auto Result = Utils::base64decode(Obj->get(Field).toString()); Value.assignRaw((const unsigned char *)&Result[0],Result.size()); } } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Types::StringPairVec &Vec) { if(Obj->isArray(Field) && !Obj->isNull(Field)) { auto O = Obj->getArray(Field); for(const auto &i:*O) { std::string S1,S2; auto Inner = i.extract(); if(Inner->has("tag")) S1 = Inner->get("tag").toString(); if(Inner->has("value")) S2 = Inner->get("value").toString(); auto P = std::make_pair(S1,S2); Vec.push_back(P); } } } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Types::StringVec &Value) { if(Obj->isArray(Field) && !Obj->isNull(Field)) { Value.clear(); Poco::JSON::Array::Ptr A = Obj->getArray(Field); for(const auto &i:*A) { Value.push_back(i.toString()); } } } inline void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, Types::TagList &Value) { if(Obj->isArray(Field) && !Obj->isNull(Field)) { Value.clear(); Poco::JSON::Array::Ptr A = Obj->getArray(Field); for(const auto &i:*A) { Value.push_back(i); } } } template void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, std::vector &Value) { if(Obj->isArray(Field) && !Obj->isNull(Field)) { Poco::JSON::Array::Ptr Arr = Obj->getArray(Field); for(auto &i:*Arr) { auto InnerObj = i.extract(); T NewItem; NewItem.from_json(InnerObj); Value.push_back(NewItem); } } } template void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, T &Value) { if(Obj->isObject(Field) && !Obj->isNull(Field)) { Poco::JSON::Object::Ptr A = Obj->getObject(Field); Value.from_json(A); } } inline std::string to_string(const Types::TagList & ObjectArray) { Poco::JSON::Array OutputArr; if(ObjectArray.empty()) return "[]"; for(auto const &i:ObjectArray) { OutputArr.add(i); } std::ostringstream OS; Poco::JSON::Stringifier::stringify(OutputArr,OS, 0,0, Poco::JSON_PRESERVE_KEY_ORDER ); return OS.str(); } inline std::string to_string(const Types::StringVec & ObjectArray) { Poco::JSON::Array OutputArr; if(ObjectArray.empty()) return "[]"; for(auto const &i:ObjectArray) { OutputArr.add(i); } std::ostringstream OS; Poco::JSON::Stringifier::condense(OutputArr,OS); return OS.str(); } inline std::string to_string(const Types::StringPairVec & ObjectArray) { Poco::JSON::Array OutputArr; if(ObjectArray.empty()) return "[]"; for(auto const &i:ObjectArray) { Poco::JSON::Array InnerArray; InnerArray.add(i.first); InnerArray.add(i.second); OutputArr.add(InnerArray); } std::ostringstream OS; Poco::JSON::Stringifier::condense(OutputArr,OS); return OS.str(); } template std::string to_string(const std::vector & ObjectArray) { Poco::JSON::Array OutputArr; if(ObjectArray.empty()) return "[]"; for(auto const &i:ObjectArray) { Poco::JSON::Object O; i.to_json(O); OutputArr.add(O); } std::ostringstream OS; Poco::JSON::Stringifier::condense(OutputArr,OS); return OS.str(); } template std::string to_string(const std::vector> & ObjectArray) { Poco::JSON::Array OutputArr; if(ObjectArray.empty()) return "[]"; for(auto const &i:ObjectArray) { Poco::JSON::Array InnerArr; for(auto const &j:i) { if constexpr(std::is_integral::value) { InnerArr.add(j); } if constexpr(std::is_same_v) { InnerArr.add(j); } else { InnerArr.add(j); Poco::JSON::Object O; j.to_json(O); InnerArr.add(O); } } OutputArr.add(InnerArr); } std::ostringstream OS; Poco::JSON::Stringifier::condense(OutputArr,OS); return OS.str(); } template std::string to_string(const T & Object) { Poco::JSON::Object OutputObj; Object.to_json(OutputObj); std::ostringstream OS; Poco::JSON::Stringifier::condense(OutputObj,OS); return OS.str(); } inline Types::StringVec to_object_array(const std::string & ObjectString) { Types::StringVec Result; if(ObjectString.empty()) return Result; try { Poco::JSON::Parser P; auto Object = P.parse(ObjectString).template extract(); for (auto const &i : *Object) { Result.push_back(i.toString()); } } catch (...) { } return Result; } inline OpenWifi::Types::TagList to_taglist(const std::string & ObjectString) { Types::TagList Result; if(ObjectString.empty()) return Result; try { Poco::JSON::Parser P; auto Object = P.parse(ObjectString).template extract(); for (auto const &i : *Object) { Result.push_back(i); } } catch (...) { } return Result; } inline Types::StringPairVec to_stringpair_array(const std::string &S) { Types::StringPairVec R; if(S.empty()) return R; try { Poco::JSON::Parser P; auto Object = P.parse(S).template extract(); for (const auto &i : *Object) { auto InnerObject = i.template extract(); if(InnerObject->size()==2) { auto S1 = InnerObject->getElement(0); auto S2 = InnerObject->getElement(1); R.push_back(std::make_pair(S1,S2)); } } } catch (...) { } return R; } template std::vector to_object_array(const std::string & ObjectString) { std::vector Result; if(ObjectString.empty()) return Result; try { Poco::JSON::Parser P; auto Object = P.parse(ObjectString).template extract(); for (auto const &i : *Object) { auto InnerObject = i.template extract(); T Obj; Obj.from_json(InnerObject); Result.push_back(Obj); } } catch (...) { } return Result; } template std::vector> to_array_of_array_of_object(const std::string & ObjectString) { std::vector> Result; if(ObjectString.empty()) return Result; try { Poco::JSON::Parser P1; auto OutterArray = P1.parse(ObjectString).template extract(); for (auto const &i : *OutterArray) { Poco::JSON::Parser P2; auto InnerArray = P2.parse(i).template extract(); std::vector InnerVector; for(auto const &j: *InnerArray) { auto Object = j.template extract(); T Obj; Obj.from_json(Object); InnerVector.push_back(Obj); } Result.push_back(InnerVector); } } catch (...) { } return Result; } template T to_object(const std::string & ObjectString) { T Result; if(ObjectString.empty()) return Result; Poco::JSON::Parser P; auto Object = P.parse(ObjectString).template extract(); Result.from_json(Object); return Result; } template bool from_request(T & Obj, Poco::Net::HTTPServerRequest &Request) { Poco::JSON::Parser IncomingParser; auto RawObject = IncomingParser.parse(Request.stream()).extract(); Obj.from_json(RawObject); return true; } } namespace OpenWifi::Utils { inline void SetThreadName(const char *name) { #ifdef __linux__ Poco::Thread::current()->setName(name); pthread_setname_np(pthread_self(), name); #endif #ifdef __APPLE__ Poco::Thread::current()->setName(name); pthread_setname_np(name); #endif } inline void SetThreadName(Poco::Thread &thr, const char *name) { #ifdef __linux__ thr.setName(name); pthread_setname_np(thr.tid(), name); #endif #ifdef __APPLE__ thr.setName(name); #endif } enum MediaTypeEncodings { PLAIN, BINARY, BASE64 }; struct MediaTypeEncoding { MediaTypeEncodings Encoding=PLAIN; std::string ContentType; }; [[nodiscard]] inline bool ValidSerialNumber(const std::string &Serial) { return ((Serial.size() < uCentralProtocol::SERIAL_NUMBER_LENGTH) && std::all_of(Serial.begin(),Serial.end(),[](auto i){return std::isxdigit(i);})); } [[nodiscard]] inline bool ValidUUID(const std::string &UUID) { if(UUID.size()>36) return false; uint dashes=0; return (std::all_of(UUID.begin(),UUID.end(),[&](auto i){ if(i=='-') dashes++; return i=='-' || std::isxdigit(i);})) && (dashes>0); } template std::string ComputeHash(Args&&... args) { Poco::SHA2Engine E; auto as_string = [](auto p) { if constexpr(std::is_arithmetic_v) { return std::to_string(p); } else { return p; } }; (E.update(as_string(args)),...); return Poco::SHA2Engine::digestToHex(E.digest()); } [[nodiscard]] inline std::vector Split(const std::string &List, char Delimiter=',' ) { std::vector ReturnList; unsigned long P=0; while(P12) R = R.substr(0,12); char buf[18]; buf[0] = R[0]; buf[1] = R[1] ; buf[2] = ':' ; buf[3] = R[2] ; buf[4] = R[3]; buf[5] = ':' ; buf[6] = R[4]; buf[7] = R[5] ; buf[8] = ':' ; buf[9] = R[6] ; buf[10]= R[7]; buf[11] = ':'; buf[12] = R[8] ; buf[13]= R[9]; buf[14] = ':'; buf[15] = R[10] ; buf[16]= R[11];buf[17] = 0; return buf; } inline uint64_t MACToInt(const std::string &MAC) { uint64_t Result = 0 ; for(const auto &c:MAC) { if(c==':') continue; Result <<= 4; if(c>='0' && c<='9') { Result += (c - '0'); } else if (c>='a' && c<='f') { Result += (c-'a'+10); } else if (c>='A' && c<='F') { Result += (c-'A'+10); } } return Result; } [[nodiscard]] inline std::string ToHex(const std::vector & B) { std::string R; R.reserve(B.size()*2); static const char hex[] = "0123456789abcdef"; for(const auto &i:B) { R += (hex[ (i & 0xf0) >> 4]); R += (hex[ (i & 0x0f) ]); } return R; } inline static const char kEncodeLookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; inline static const char kPadCharacter = '='; using byte = std::uint8_t; [[nodiscard]] inline std::string base64encode(const byte *input, uint32_t size) { std::string encoded; encoded.reserve(((size / 3) + (size % 3 > 0)) * 4); std::uint32_t temp,i,ee; ee = (size/3); for (i = 0; i < 3*ee; ++i) { temp = input[i++] << 16; temp += input[i++] << 8; temp += input[i]; encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]); encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]); encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]); encoded.append(1, kEncodeLookup[(temp & 0x0000003F)]); } switch (size % 3) { case 1: temp = input[i] << 16; encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]); encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]); encoded.append(2, kPadCharacter); break; case 2: temp = input[i++] << 16; temp += input[i] << 8; encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]); encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]); encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6]); encoded.append(1, kPadCharacter); break; } return encoded; } [[nodiscard]] inline std::vector base64decode(const std::string& input) { if(input.length() % 4) throw std::runtime_error("Invalid base64 length!"); std::size_t padding=0; if(input.length()) { if(input[input.length() - 1] == kPadCharacter) padding++; if(input[input.length() - 2] == kPadCharacter) padding++; } std::vector decoded; decoded.reserve(((input.length() / 4) * 3) - padding); std::uint32_t temp=0; auto it = input.begin(); while(it < input.end()) { for(std::size_t i = 0; i < 4; ++i) { temp <<= 6; if (*it >= 0x41 && *it <= 0x5A) temp |= *it - 0x41; else if(*it >= 0x61 && *it <= 0x7A) temp |= *it - 0x47; else if(*it >= 0x30 && *it <= 0x39) temp |= *it + 0x04; else if(*it == 0x2B) temp |= 0x3E; else if(*it == 0x2F) temp |= 0x3F; else if(*it == kPadCharacter) { switch(input.end() - it) { case 1: decoded.push_back((temp >> 16) & 0x000000FF); decoded.push_back((temp >> 8 ) & 0x000000FF); return decoded; case 2: decoded.push_back((temp >> 10) & 0x000000FF); return decoded; default: throw std::runtime_error("Invalid padding in base64!"); } } else throw std::runtime_error("Invalid character in base64!"); ++it; } decoded.push_back((temp >> 16) & 0x000000FF); decoded.push_back((temp >> 8 ) & 0x000000FF); decoded.push_back((temp ) & 0x000000FF); } return decoded; } inline bool ParseTime(const std::string &Time, int & Hours, int & Minutes, int & Seconds) { Poco::StringTokenizer TimeTokens(Time,":",Poco::StringTokenizer::TOK_TRIM); Hours = Minutes = Seconds = 0 ; if(TimeTokens.count()==1) { Hours = std::atoi(TimeTokens[0].c_str()); } else if(TimeTokens.count()==2) { Hours = std::atoi(TimeTokens[0].c_str()); Minutes = std::atoi(TimeTokens[1].c_str()); } else if(TimeTokens.count()==3) { Hours = std::atoi(TimeTokens[0].c_str()); Minutes = std::atoi(TimeTokens[1].c_str()); Seconds = std::atoi(TimeTokens[2].c_str()); } else return false; return true; } inline bool ParseDate(const std::string &Time, int & Year, int & Month, int & Day) { Poco::StringTokenizer DateTokens(Time,"-",Poco::StringTokenizer::TOK_TRIM); Year = Month = Day = 0 ; if(DateTokens.count()==3) { Year = std::atoi(DateTokens[0].c_str()); Month = std::atoi(DateTokens[1].c_str()); Day = std::atoi(DateTokens[2].c_str()); } else return false; return true; } inline bool CompareTime( int H1, int H2, int M1, int M2, int S1, int S2) { if(H1H2) return false; if(M1M1) return false; if(S1<=S2) return true; return false; } [[nodiscard]] inline std::string LogLevelToString(int Level) { switch(Level) { case Poco::Message::PRIO_DEBUG: return "debug"; case Poco::Message::PRIO_INFORMATION: return "information"; case Poco::Message::PRIO_FATAL: return "fatal"; case Poco::Message::PRIO_WARNING: return "warning"; case Poco::Message::PRIO_NOTICE: return "notice"; case Poco::Message::PRIO_CRITICAL: return "critical"; case Poco::Message::PRIO_ERROR: return "error"; case Poco::Message::PRIO_TRACE: return "trace"; default: return "none"; } } [[nodiscard]] inline uint64_t SerialNumberToInt(const std::string & S) { return std::stoull(S,nullptr,16); } [[nodiscard]] inline std::string IntToSerialNumber(uint64_t S) { char b[16]; for(int i=0;i<12;++i) { int B = (S & 0x0f); if(B<10) b[11-i] = B+'0'; else b[11-i] = B - 10 + 'a'; S >>= 4 ; } b[12]=0; return b; } [[nodiscard]] inline bool SerialNumberMatch(const std::string &S1, const std::string &S2, int Bits=2) { auto S1_i = SerialNumberToInt(S1); auto S2_i = SerialNumberToInt(S2); return ((S1_i>>Bits)==(S2_i>>Bits)); } [[nodiscard]] inline uint64_t SerialNumberToOUI(const std::string & S) { uint64_t Result = 0 ; int Digits=0; for(const auto &i:S) { if(std::isxdigit(i)) { if(i>='0' && i<='9') { Result <<=4; Result += i-'0'; } else if(i>='A' && i<='F') { Result <<=4; Result += i-'A'+10; } else if(i>='a' && i<='f') { Result <<=4; Result += i-'a'+10; } Digits++; if(Digits==6) break; } } return Result; } [[nodiscard]] inline uint64_t GetDefaultMacAsInt64() { uint64_t Result=0; auto IFaceList = Poco::Net::NetworkInterface::list(); for(const auto &iface:IFaceList) { if(iface.isRunning() && !iface.isLoopback()) { auto MAC = iface.macAddress(); for (auto const &i : MAC) { Result <<= 8; Result += (uint8_t)i; } if (Result != 0) break; } } return Result; } [[nodiscard]] inline uint64_t InitializeSystemId() { std::random_device RDev; std::srand(RDev()); std::chrono::high_resolution_clock Clock; auto Now = Clock.now().time_since_epoch().count(); auto S = (GetDefaultMacAsInt64() + std::rand() + Now) ; OpenWifi::AppServiceRegistry().Set("systemid",S); return S; } [[nodiscard]] inline uint64_t GetSystemId(); [[nodiscard]] inline bool ValidEMailAddress(const std::string &email) { // define a regular expression static const std::regex pattern ("[_a-z0-9-]+(\\.[_a-z0-9-]+)*(\\+[a-z0-9-]+)?@[a-z0-9-]+(\\.[a-z0-9-]+)*"); // try to match the string with the regular expression return std::regex_match(email, pattern); } [[nodiscard]] inline std::string LoadFile( const Poco::File & F) { std::string Result; try { std::ostringstream OS; std::ifstream IF(F.path()); Poco::StreamCopier::copyStream(IF, OS); Result = OS.str(); } catch (...) { } return Result; } inline void ReplaceVariables( std::string & Content , const Types::StringPairVec & P) { for(const auto &[Variable,Value]:P) { Poco::replaceInPlace(Content,"${" + Variable + "}", Value); } } [[nodiscard]] inline MediaTypeEncoding FindMediaType(const Poco::File &F) { const auto E = Poco::Path(F.path()).getExtension(); if(E=="png") return MediaTypeEncoding{ .Encoding = BINARY, .ContentType = "image/png" }; if(E=="gif") return MediaTypeEncoding{ .Encoding = BINARY, .ContentType = "image/gif" }; if(E=="jpeg" || E=="jpg") return MediaTypeEncoding{ .Encoding = BINARY, .ContentType = "image/jpeg" }; if(E=="svg" || E=="svgz") return MediaTypeEncoding{ .Encoding = PLAIN, .ContentType = "image/svg+xml" }; if(E=="html") return MediaTypeEncoding{ .Encoding = PLAIN, .ContentType = "text/html" }; if(E=="css") return MediaTypeEncoding{ .Encoding = PLAIN, .ContentType = "text/css" }; if(E=="js") return MediaTypeEncoding{ .Encoding = PLAIN, .ContentType = "application/javascript" }; return MediaTypeEncoding{ .Encoding = BINARY, .ContentType = "application/octet-stream" }; } [[nodiscard]] inline std::string BinaryFileToHexString(const Poco::File &F) { static const char hex[] = "0123456789abcdef"; std::string Result; try { std::ifstream IF(F.path()); int Count = 0; while (IF.good()) { if (Count) Result += ", "; if ((Count % 32) == 0) Result += "\r\n"; Count++; unsigned char C = IF.get(); Result += "0x"; Result += (char) (hex[(C & 0xf0) >> 4]); Result += (char) (hex[(C & 0x0f)]); } } catch(...) { } return Result; } [[nodiscard]] inline std::string SecondsToNiceText(uint64_t Seconds) { std::string Result; int Days = Seconds / (24*60*60); Seconds -= Days * (24*60*60); int Hours= Seconds / (60*60); Seconds -= Hours * (60*60); int Minutes = Seconds / 60; Seconds -= Minutes * 60; Result = std::to_string(Days) +" days, " + std::to_string(Hours) + ":" + std::to_string(Minutes) + ":" + std::to_string(Seconds); return Result; } [[nodiscard]] inline bool wgets(const std::string &URL, std::string &Response) { try { Poco::URI uri(URL); Poco::Net::HTTPSClientSession session(uri.getHost(), uri.getPort()); // prepare path std::string path(uri.getPathAndQuery()); if (path.empty()) { path = "/"; } // send request Poco::Net::HTTPRequest req(Poco::Net::HTTPRequest::HTTP_GET, path, Poco::Net::HTTPMessage::HTTP_1_1); session.sendRequest(req); Poco::Net::HTTPResponse res; std::istream &is = session.receiveResponse(res); std::ostringstream os; Poco::StreamCopier::copyStream(is,os); Response = os.str(); return true; } catch (...) { } return false; } template< typename T > std::string int_to_hex( T i ) { std::stringstream stream; stream << std::setfill ('0') << std::setw(12) << std::hex << i; return stream.str(); } inline bool ExtractBase64CompressedData(const std::string &CompressedData, std::string &UnCompressedData, uint64_t compress_sz ) { std::istringstream ifs(CompressedData); Poco::Base64Decoder b64in(ifs); std::ostringstream ofs; Poco::StreamCopier::copyStream(b64in, ofs); int factor = 20; unsigned long MaxSize = compress_sz ? (unsigned long) (compress_sz + 5000) : (unsigned long) (ofs.str().size() * factor); while(true) { std::vector UncompressedBuffer(MaxSize); unsigned long FinalSize = MaxSize; auto status = uncompress((uint8_t *)&UncompressedBuffer[0], &FinalSize, (uint8_t *)ofs.str().c_str(), ofs.str().size()); if(status==Z_OK) { UncompressedBuffer[FinalSize] = 0; UnCompressedData = (char *)&UncompressedBuffer[0]; return true; } if(status==Z_BUF_ERROR) { if(factor<300) { factor+=10; MaxSize = ofs.str().size() * factor; continue; } else { return false; } } return false; } return false; } } namespace OpenWifi { static const std::string uSERVICE_SECURITY{"owsec"}; static const std::string uSERVICE_GATEWAY{"owgw"}; static const std::string uSERVICE_FIRMWARE{ "owfms"}; static const std::string uSERVICE_TOPOLOGY{ "owtopo"}; static const std::string uSERVICE_PROVISIONING{ "owprov"}; static const std::string uSERVICE_OWLS{ "owls"}; static const std::string uSERVICE_SUBCRIBER{ "owsub"}; static const std::string uSERVICE_INSTALLER{ "owinst"}; static const std::string uSERVICE_ANALYTICS{ "owanalytics"}; static const std::string uSERVICE_OWRRM{ "owrrm"}; class ConfigurationEntry { public: template explicit ConfigurationEntry(T def) : Default_(def), Current_(def){ } template explicit ConfigurationEntry(T def, T cur, const std::string &Hint="") : Default_(def), Current_(cur), Hint_(Hint){ } inline ConfigurationEntry()=default; inline ~ConfigurationEntry()=default; template explicit operator T () const { return std::get(Current_); } inline ConfigurationEntry & operator=(const char *v) { Current_ = std::string(v); return *this;} template ConfigurationEntry & operator=(T v) { Current_ = (T) v; return *this;} void reset() { Current_ = Default_; } private: std::variant Default_, Current_; std::string Hint_; }; inline std::string to_string(const ConfigurationEntry &v) { return (std::string) v; } typedef std::map ConfigurationMap_t; template class FIFO { public: explicit FIFO(uint32_t Size) : Size_(Size) { Buffer_ = new T [Size_]; } ~FIFO() { delete [] Buffer_; } mutable Poco::BasicEvent Writable_; mutable Poco::BasicEvent Readable_; inline bool Read(T &t) { { std::lock_guard M(Mutex_); if (Write_ == Read_) { return false; } t = Buffer_[Read_++]; if (Read_ == Size_) { Read_ = 0; } Used_--; } bool flag = true; Writable_.notify(this, flag); return true; } inline bool Write(const T &t) { { std::lock_guard M(Mutex_); Buffer_[Write_++] = t; if (Write_ == Size_) { Write_ = 0; } Used_++; MaxEverUsed_ = std::max(Used_,MaxEverUsed_); } bool flag = true; Readable_.notify(this, flag); return false; } inline bool isFull() { std::lock_guard M(Mutex_); return Used_==Buffer_->capacity(); } inline auto MaxEverUser() const { return MaxEverUsed_; } private: std::recursive_mutex Mutex_; uint32_t Size_=0; uint32_t Read_=0; uint32_t Write_=0; uint32_t Used_=0; uint32_t MaxEverUsed_=0; T * Buffer_ = nullptr; }; template class RecordCache { public: explicit RecordCache( KeyType Record::* Q) : MemberOffset(Q){ }; inline auto update(const Record &R) { return Cache_.update(R.*MemberOffset, R); } inline auto get(const KeyType &K) { return Cache_.get(K); } inline auto remove(const KeyType &K) { return Cache_.remove(K); } inline auto remove(const Record &R) { return Cache_.remove(R.*MemberOffset); } private: KeyType Record::* MemberOffset; Poco::ExpireLRUCache Cache_{Size,Expiry}; }; class MyErrorHandler : public Poco::ErrorHandler { public: explicit MyErrorHandler(Poco::Util::Application &App) : App_(App) {} inline void exception(const Poco::Exception & E) { Poco::Thread * CurrentThread = Poco::Thread::current(); App_.logger().log(E); poco_error(App_.logger(), fmt::format("Exception occurred in {}",CurrentThread->getName())); } inline void exception(const std::exception & E) { Poco::Thread * CurrentThread = Poco::Thread::current(); poco_warning(App_.logger(), fmt::format("std::exception in {}: {}",CurrentThread->getName(),E.what())); } inline void exception() { Poco::Thread * CurrentThread = Poco::Thread::current(); poco_warning(App_.logger(), fmt::format("exception in {}",CurrentThread->getName())); } private: Poco::Util::Application &App_; }; class BusEventManager : public Poco::Runnable { public: explicit BusEventManager(Poco::Logger &L) : Logger_(L) { } inline void run() final; inline void Start(); inline void Stop(); inline Poco::Logger & Logger() { return Logger_; } private: mutable std::atomic_bool Running_ = false; Poco::Thread Thread_; Poco::Logger &Logger_; }; class MyPrivateKeyPassphraseHandler : public Poco::Net::PrivateKeyPassphraseHandler { public: explicit MyPrivateKeyPassphraseHandler(const std::string &Password, Poco::Logger & Logger): PrivateKeyPassphraseHandler(true), Password_(Password), Logger_(Logger) {} void onPrivateKeyRequested([[maybe_unused]] const void * pSender,std::string & privateKey) { Logger_.information("Returning key passphrase."); privateKey = Password_; }; inline Poco::Logger & Logger() { return Logger_; } private: std::string Password_; Poco::Logger & Logger_; }; class PropertiesFileServerEntry { public: PropertiesFileServerEntry(std::string Address, uint32_t port, std::string Key_file, std::string Cert_file, std::string RootCa, std::string Issuer, std::string ClientCas, std::string Cas, std::string Key_file_password = "", std::string Name = "", Poco::Net::Context::VerificationMode M = Poco::Net::Context::VerificationMode::VERIFY_RELAXED, int backlog = 64) : address_(std::move(Address)), port_(port), cert_file_(std::move(Cert_file)), key_file_(std::move(Key_file)), root_ca_(std::move(RootCa)), key_file_password_(std::move(Key_file_password)), issuer_cert_file_(std::move(Issuer)), client_cas_(std::move(ClientCas)), cas_(std::move(Cas)), name_(std::move(Name)), backlog_(backlog), level_(M) {}; [[nodiscard]] inline const std::string &Address() const { return address_; }; [[nodiscard]] inline uint32_t Port() const { return port_; }; [[nodiscard]] inline auto KeyFile() const { return key_file_; }; [[nodiscard]] inline auto CertFile() const { return cert_file_; }; [[nodiscard]] inline auto RootCA() const { return root_ca_; }; [[nodiscard]] inline auto KeyFilePassword() const { return key_file_password_; }; [[nodiscard]] inline auto IssuerCertFile() const { return issuer_cert_file_; }; [[nodiscard]] inline auto Name() const { return name_; }; [[nodiscard]] inline int Backlog() const { return backlog_; } [[nodiscard]] inline auto Cas() const { return cas_; } [[nodiscard]] inline Poco::Net::SecureServerSocket CreateSecureSocket(Poco::Logger &L) const { Poco::Net::Context::Params P; P.verificationMode = level_; P.verificationDepth = 9; P.loadDefaultCAs = root_ca_.empty(); P.cipherList = "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"; P.dhUse2048Bits = true; P.caLocation = cas_; auto Context = Poco::AutoPtr(new Poco::Net::Context(Poco::Net::Context::TLS_SERVER_USE, P)); if(!key_file_password_.empty()) { auto PassphraseHandler = Poco::SharedPtr( new MyPrivateKeyPassphraseHandler(key_file_password_,L)); Poco::Net::SSLManager::instance().initializeServer(PassphraseHandler, nullptr,Context); } if (!cert_file_.empty() && !key_file_.empty()) { Poco::Crypto::X509Certificate Cert(cert_file_); Poco::Crypto::X509Certificate Root(root_ca_); Context->useCertificate(Cert); Context->addChainCertificate(Root); Context->addCertificateAuthority(Root); if (level_ == Poco::Net::Context::VERIFY_STRICT) { if (issuer_cert_file_.empty()) { L.fatal("In strict mode, you must supply ans issuer certificate"); } if (client_cas_.empty()) { L.fatal("In strict mode, client cas must be supplied"); } Poco::Crypto::X509Certificate Issuing(issuer_cert_file_); Context->addChainCertificate(Issuing); Context->addCertificateAuthority(Issuing); } Poco::Crypto::RSAKey Key("", key_file_, key_file_password_); Context->usePrivateKey(Key); SSL_CTX *SSLCtx = Context->sslContext(); if (!SSL_CTX_check_private_key(SSLCtx)) { L.fatal(fmt::format("Wrong Certificate({}) for Key({})", cert_file_, key_file_)); } SSL_CTX_set_verify(SSLCtx, SSL_VERIFY_PEER, nullptr); if (level_ == Poco::Net::Context::VERIFY_STRICT) { SSL_CTX_set_client_CA_list(SSLCtx, SSL_load_client_CA_file(client_cas_.c_str())); } SSL_CTX_enable_ct(SSLCtx, SSL_CT_VALIDATION_STRICT); SSL_CTX_dane_enable(SSLCtx); Context->enableSessionCache(); Context->setSessionCacheSize(0); Context->setSessionTimeout(60); Context->enableExtendedCertificateVerification(true); Context->disableStatelessSessionResumption(); } if (address_ == "*") { Poco::Net::IPAddress Addr(Poco::Net::IPAddress::wildcard( Poco::Net::Socket::supportsIPv6() ? Poco::Net::AddressFamily::IPv6 : Poco::Net::AddressFamily::IPv4)); Poco::Net::SocketAddress SockAddr(Addr, port_); return Poco::Net::SecureServerSocket(SockAddr, backlog_, Context); } else { Poco::Net::IPAddress Addr(address_); Poco::Net::SocketAddress SockAddr(Addr, port_); return Poco::Net::SecureServerSocket(SockAddr, backlog_, Context); } } [[nodiscard]] inline Poco::Net::ServerSocket CreateSocket([[maybe_unused]] Poco::Logger &L) const { Poco::Net::Context::Params P; if (address_ == "*") { Poco::Net::IPAddress Addr(Poco::Net::IPAddress::wildcard( Poco::Net::Socket::supportsIPv6() ? Poco::Net::AddressFamily::IPv6 : Poco::Net::AddressFamily::IPv4)); Poco::Net::SocketAddress SockAddr(Addr, port_); return Poco::Net::ServerSocket(SockAddr, backlog_); } else { Poco::Net::IPAddress Addr(address_); Poco::Net::SocketAddress SockAddr(Addr, port_); return Poco::Net::ServerSocket(SockAddr, backlog_); } } inline void LogCertInfo(Poco::Logger &L, const Poco::Crypto::X509Certificate &C) const { L.information("============================================================================================="); L.information(fmt::format("> Issuer: {}", C.issuerName())); L.information("---------------------------------------------------------------------------------------------"); L.information(fmt::format("> Common Name: {}", C.issuerName(Poco::Crypto::X509Certificate::NID_COMMON_NAME))); L.information(fmt::format("> Country: {}", C.issuerName(Poco::Crypto::X509Certificate::NID_COUNTRY))); L.information(fmt::format("> Locality: {}", C.issuerName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME))); L.information(fmt::format("> State/Prov: {}", C.issuerName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE))); L.information(fmt::format("> Org name: {}", C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME))); L.information( fmt::format("> Org unit: {}", C.issuerName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME))); L.information( fmt::format("> Email: {}", C.issuerName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS))); L.information(fmt::format("> Serial#: {}", C.issuerName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER))); L.information("---------------------------------------------------------------------------------------------"); L.information(fmt::format("> Subject: {}", C.subjectName())); L.information("---------------------------------------------------------------------------------------------"); L.information(fmt::format("> Common Name: {}", C.subjectName(Poco::Crypto::X509Certificate::NID_COMMON_NAME))); L.information(fmt::format("> Country: {}", C.subjectName(Poco::Crypto::X509Certificate::NID_COUNTRY))); L.information(fmt::format("> Locality: {}", C.subjectName(Poco::Crypto::X509Certificate::NID_LOCALITY_NAME))); L.information( fmt::format("> State/Prov: {}", C.subjectName(Poco::Crypto::X509Certificate::NID_STATE_OR_PROVINCE))); L.information( fmt::format("> Org name: {}", C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_NAME))); L.information( fmt::format("> Org unit: {}", C.subjectName(Poco::Crypto::X509Certificate::NID_ORGANIZATION_UNIT_NAME))); L.information( fmt::format("> Email: {}", C.subjectName(Poco::Crypto::X509Certificate::NID_PKCS9_EMAIL_ADDRESS))); L.information(fmt::format("> Serial#: {}", C.subjectName(Poco::Crypto::X509Certificate::NID_SERIAL_NUMBER))); L.information("---------------------------------------------------------------------------------------------"); L.information(fmt::format("> Signature Algo: {}", C.signatureAlgorithm())); auto From = Poco::DateTimeFormatter::format(C.validFrom(), Poco::DateTimeFormat::HTTP_FORMAT); L.information(fmt::format("> Valid from: {}", From)); auto Expires = Poco::DateTimeFormatter::format(C.expiresOn(), Poco::DateTimeFormat::HTTP_FORMAT); L.information(fmt::format("> Expires on: {}", Expires)); L.information(fmt::format("> Version: {}", (int)C.version())); L.information(fmt::format("> Serial #: {}", C.serialNumber())); L.information("============================================================================================="); } inline void LogCert(Poco::Logger &L) const { try { Poco::Crypto::X509Certificate C(cert_file_); L.information("============================================================================================="); L.information("============================================================================================="); L.information(fmt::format("Certificate Filename: {}", cert_file_)); LogCertInfo(L, C); L.information("============================================================================================="); if (!issuer_cert_file_.empty()) { Poco::Crypto::X509Certificate C1(issuer_cert_file_); L.information("============================================================================================="); L.information("============================================================================================="); L.information(fmt::format("Issues Certificate Filename: {}", issuer_cert_file_)); LogCertInfo(L, C1); L.information("============================================================================================="); } if (!client_cas_.empty()) { std::vector Certs = Poco::Net::X509Certificate::readPEM(client_cas_); L.information("============================================================================================="); L.information("============================================================================================="); L.information(fmt::format("Client CAs Filename: {}", client_cas_)); L.information("============================================================================================="); auto i = 1; for (const auto &C3 : Certs) { L.information(fmt::format(" Index: {}", i)); L.information("============================================================================================="); LogCertInfo(L, C3); i++; } L.information("============================================================================================="); } } catch (const Poco::Exception &E) { L.log(E); } } inline void LogCas(Poco::Logger &L) const { try { std::vector Certs = Poco::Net::X509Certificate::readPEM(root_ca_); L.information("============================================================================================="); L.information("============================================================================================="); L.information(fmt::format("CA Filename: {}", root_ca_)); L.information("============================================================================================="); auto i = 1; for (const auto &C : Certs) { L.information(fmt::format(" Index: {}", i)); L.information("============================================================================================="); LogCertInfo(L, C); i++; } L.information("============================================================================================="); } catch (const Poco::Exception &E) { L.log(E); } } private: std::string address_; uint32_t port_; std::string cert_file_; std::string key_file_; std::string root_ca_; std::string key_file_password_; std::string issuer_cert_file_; std::string client_cas_; std::string cas_; std::string name_; int backlog_; Poco::Net::Context::VerificationMode level_; }; class SubSystemServer : public Poco::Util::Application::Subsystem { public: SubSystemServer(const std::string & Name, const std::string &LoggingPrefix, const std::string & SubSystemConfigPrefix); inline void initialize(Poco::Util::Application &self) override; inline void uninitialize() override { } inline void reinitialize([[maybe_unused]] Poco::Util::Application &self) override { Logger_->L_.information("Reloading of this subsystem is not supported."); } inline void defineOptions([[maybe_unused]] Poco::Util::OptionSet &options) override { } inline const std::string & Name() const { return Name_; }; inline const char * name() const override { return Name_.c_str(); } inline const PropertiesFileServerEntry & Host(uint64_t index) { return ConfigServersList_[index]; }; inline uint64_t HostSize() const { return ConfigServersList_.size(); } inline Poco::Logger & Logger() const { return Logger_->L_; } inline void SetLoggingLevel(const std::string & levelName) { Logger_->L_.setLevel(Poco::Logger::parseLevel(levelName)); } inline int GetLoggingLevel() { return Logger_->L_.getLevel(); } virtual int Start() = 0; virtual void Stop() = 0; struct LoggerWrapper { Poco::Logger & L_; LoggerWrapper(Poco::Logger &L) : L_(L) {} }; protected: std::recursive_mutex Mutex_; std::vector ConfigServersList_; private: std::unique_ptr Logger_; std::string Name_; std::string LoggerPrefix_; std::string SubSystemConfigPrefix_; }; class RESTAPI_GenericServer { public: enum { LOG_GET=0, LOG_DELETE, LOG_PUT, LOG_POST }; void inline SetFlags(bool External, const std::string &Methods) { Poco::StringTokenizer Tokens(Methods,","); auto Offset = (External ? 0 : 4); for(const auto &i:Tokens) { if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_DELETE)==0) LogFlags_[Offset+LOG_DELETE]=true; else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_PUT)==0) LogFlags_[Offset+LOG_PUT]=true; else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_POST)==0) LogFlags_[Offset+LOG_POST]=true; else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_GET)==0) LogFlags_[Offset+LOG_GET]=true; } } inline void InitLogging(); [[nodiscard]] inline bool LogIt(const std::string &Method, bool External) const { auto Offset = (External ? 0 : 4); if(Method == Poco::Net::HTTPRequest::HTTP_GET) return LogFlags_[Offset+LOG_GET]; if(Method == Poco::Net::HTTPRequest::HTTP_POST) return LogFlags_[Offset+LOG_POST]; if(Method == Poco::Net::HTTPRequest::HTTP_PUT) return LogFlags_[Offset+LOG_PUT]; if(Method == Poco::Net::HTTPRequest::HTTP_DELETE) return LogFlags_[Offset+LOG_DELETE]; return false; }; [[nodiscard]] inline bool LogBadTokens(bool External) const { return LogBadTokens_[ (External ? 0 : 1) ]; }; private: std::array LogFlags_{false}; std::array LogBadTokens_{false}; }; class RESTAPI_PartHandler: public Poco::Net::PartHandler { public: RESTAPI_PartHandler(): _length(0) { } inline void handlePart(const Poco::Net::MessageHeader& header, std::istream& stream) override { _type = header.get("Content-Type", "(unspecified)"); if (header.has("Content-Disposition")) { std::string disp; Poco::Net::NameValueCollection params; Poco::Net::MessageHeader::splitParameters(header["Content-Disposition"], disp, params); _name = params.get("name", "(unnamed)"); _fileName = params.get("filename", "(unnamed)"); } Poco::CountingInputStream istr(stream); Poco::NullOutputStream ostr; Poco::StreamCopier::copyStream(istr, ostr); _length = (int)istr.chars(); } [[nodiscard]] inline int length() const { return _length; } [[nodiscard]] inline const std::string& name() const { return _name; } [[nodiscard]] inline const std::string& fileName() const { return _fileName; } [[nodiscard]] inline const std::string& contentType() const { return _type; } private: int _length; std::string _type; std::string _name; std::string _fileName; }; class RESTAPI_RateLimiter : public SubSystemServer { public: struct ClientCacheEntry { int64_t Start=0; int Count=0; }; static auto instance() { static auto instance_ = new RESTAPI_RateLimiter; return instance_; } inline int Start() final { return 0;}; inline void Stop() final { }; inline bool IsRateLimited(const Poco::Net::HTTPServerRequest &R, int64_t Period, int64_t MaxCalls) { Poco::URI uri(R.getURI()); auto H = str_hash(uri.getPath() + R.clientAddress().host().toString()); auto E = Cache_.get(H); auto Now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); if(E.isNull()) { Cache_.add(H,ClientCacheEntry{.Start=Now, .Count=1}); return false; } if((Now-E->Start)Count++; Cache_.update(H,E); if(E->Count > MaxCalls) { poco_warning(Logger(),fmt::format("RATE-LIMIT-EXCEEDED: from '{}'", R.clientAddress().toString())); return true; } return false; } E->Start = Now; E->Count = 1; Cache_.update(H,E); return false; } inline void Clear() { Cache_.clear(); } private: Poco::ExpireLRUCache Cache_{2048}; std::hash str_hash; RESTAPI_RateLimiter() noexcept: SubSystemServer("RateLimiter", "RATE-LIMITER", "rate.limiter") { } }; inline auto RESTAPI_RateLimiter() { return RESTAPI_RateLimiter::instance(); } class RESTAPIHandler : public Poco::Net::HTTPRequestHandler { public: struct QueryBlock { uint64_t StartDate = 0 , EndDate = 0 , Offset = 0 , Limit = 0, LogType = 0 ; std::string SerialNumber, Filter; std::vector Select; bool Lifetime=false, LastOnly=false, Newest=false, CountOnly=false, AdditionalInfo=false; }; typedef std::map BindingMap; struct RateLimit { int64_t Interval=1000; int64_t MaxCalls=10; }; RESTAPIHandler( BindingMap map, Poco::Logger &l, std::vector Methods, RESTAPI_GenericServer & Server, uint64_t TransactionId, bool Internal, bool AlwaysAuthorize=true, bool RateLimited=false, const RateLimit & Profile = RateLimit{.Interval=1000,.MaxCalls=100}, bool SubscriberOnly=false) : Bindings_(std::move(map)), Logger_(l), Methods_(std::move(Methods)), Internal_(Internal), RateLimited_(RateLimited), SubOnlyService_(SubscriberOnly), AlwaysAuthorize_(AlwaysAuthorize), Server_(Server), MyRates_(Profile), TransactionId_(TransactionId) { } inline bool RoleIsAuthorized([[maybe_unused]] const std::string & Path, [[maybe_unused]] const std::string & Method, [[maybe_unused]] std::string & Reason) { return true; } inline void handleRequest(Poco::Net::HTTPServerRequest &RequestIn, Poco::Net::HTTPServerResponse &ResponseIn) final { try { Request = &RequestIn; Response = &ResponseIn; // std::string th_name = "restsvr_" + std::to_string(TransactionId_); // Utils::SetThreadName(th_name.c_str()); if(Request->getContentLength()>0) { if(Request->getContentType().find("application/json")!=std::string::npos) { ParsedBody_ = IncomingParser_.parse(Request->stream()).extract(); } } if(RateLimited_ && RESTAPI_RateLimiter()->IsRateLimited(RequestIn,MyRates_.Interval, MyRates_.MaxCalls)) { return UnAuthorized(RESTAPI::Errors::RATE_LIMIT_EXCEEDED); } if (!ContinueProcessing()) return; bool Expired=false, Contacted=false; if (AlwaysAuthorize_ && !IsAuthorized(Expired, Contacted, SubOnlyService_)) { if(Expired) return UnAuthorized(RESTAPI::Errors::EXPIRED_TOKEN); if(Contacted) return UnAuthorized(RESTAPI::Errors::INVALID_TOKEN); else return UnAuthorized(RESTAPI::Errors::SECURITY_SERVICE_UNREACHABLE); } std::string Reason; if(!RoleIsAuthorized(RequestIn.getURI(), Request->getMethod(), Reason)) { return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); } ParseParameters(); if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_GET) return DoGet(); else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_POST) return DoPost(); else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_DELETE) return DoDelete(); else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_PUT) return DoPut(); return BadRequest(RESTAPI::Errors::UnsupportedHTTPMethod); } catch (const Poco::Exception &E) { Logger_.log(E); return BadRequest(RESTAPI::Errors::InternalError); } } [[nodiscard]] inline bool NeedAdditionalInfo() const { return QB_.AdditionalInfo; } [[nodiscard]] inline const std::vector & SelectedRecords() const { return QB_.Select; } inline static bool ParseBindings(const std::string & Request, const std::list & EndPoints, BindingMap &bindings) { bindings.clear(); auto PathItems = Poco::StringTokenizer(Request, "/"); for(const auto &EndPoint:EndPoints) { auto ParamItems = Poco::StringTokenizer(EndPoint, "/"); if (PathItems.count() != ParamItems.count()) continue; bool Matched = true; for (size_t i = 0; i < PathItems.count(); i++) { if (PathItems[i] != ParamItems[i]) { if (ParamItems[i][0] == '{') { auto ParamName = ParamItems[i].substr(1, ParamItems[i].size() - 2); bindings[Poco::toLower(ParamName)] = PathItems[i]; } else { Matched = false; break; } } } if(Matched) return true; } return false; } inline void PrintBindings() { for (const auto &[key, value] : Bindings_) std::cout << "Key = " << key << " Value= " << value << std::endl; } inline void ParseParameters() { Poco::URI uri(Request->getURI()); Parameters_ = uri.getQueryParameters(); InitQueryBlock(); } inline static bool is_number(const std::string &s) { return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit); } inline static bool is_bool(const std::string &s) { if (s == "true" || s == "false") return true; return false; } [[nodiscard]] inline uint64_t GetParameter(const std::string &Name, const uint64_t Default) { auto Hint = std::find_if(Parameters_.begin(),Parameters_.end(),[&](const std::pair &S){ return S.first==Name; }); if(Hint==Parameters_.end() || !is_number(Hint->second)) return Default; return std::stoull(Hint->second); } [[nodiscard]] inline bool GetBoolParameter(const std::string &Name, bool Default=false) { auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[&](const std::pair &S){ return S.first==Name; }); if(Hint==end(Parameters_) || !is_bool(Hint->second)) return Default; return Hint->second=="true"; } [[nodiscard]] inline std::string GetParameter(const std::string &Name, const std::string &Default="") { auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[&](const std::pair &S){ return S.first==Name; }); if(Hint==end(Parameters_)) return Default; return Hint->second; } [[nodiscard]] inline bool HasParameter(const std::string &Name, std::string &Value) { auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[&](const std::pair &S){ return S.first==Name; }); if(Hint==end(Parameters_)) return false; Value = Hint->second; return true; } [[nodiscard]] inline bool HasParameter(const std::string &Name, uint64_t & Value) { auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[&](const std::pair &S){ return S.first==Name; }); if(Hint==end(Parameters_)) return false; Value = std::stoull(Hint->second); return true; } [[nodiscard]] inline const std::string & GetBinding(const std::string &Name, const std::string &Default="") { auto E = Bindings_.find(Poco::toLower(Name)); if (E == Bindings_.end()) return Default; return E->second; } [[nodiscard]] inline static std::string MakeList(const std::vector &L) { std::string Return; for (const auto &i : L) { if (Return.empty()) Return = i; else Return += ", " + i; } return Return; } static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, Types::UUIDvec_t & Value) { if(O->has(Field) && O->isArray(Field)) { auto Arr = O->getArray(Field); for(const auto &i:*Arr) Value.emplace_back(i.toString()); return true; } return false; } static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, std::string &Value) { if(O->has(Field)) { Value = O->get(Field).toString(); return true; } return false; } static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, uint64_t &Value) { if(O->has(Field)) { Value = O->get(Field); return true; } return false; } static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, bool &Value) { if(O->has(Field)) { Value = O->get(Field).toString()=="true"; return true; } return false; } static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, double &Value) { if(O->has(Field)) { Value = (double) O->get(Field); return true; } return false; } static inline bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, Poco::Data::BLOB &Value) { if(O->has(Field)) { std::string Content = O->get(Field).toString(); auto DecodedBlob = Utils::base64decode(Content); Value.assignRaw((const unsigned char *)&DecodedBlob[0],DecodedBlob.size()); return true; } return false; } template bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, const T &value, T & assignee) { if(O->has(Field)) { assignee = value; return true; } return false; } inline void SetCommonHeaders(bool CloseConnection=false) { Response->setVersion(Poco::Net::HTTPMessage::HTTP_1_1); Response->setChunkedTransferEncoding(true); Response->setContentType("application/json"); auto Origin = Request->find("Origin"); if (Origin != Request->end()) { Response->set("Access-Control-Allow-Origin", Origin->second); } else { Response->set("Access-Control-Allow-Origin", "*"); } Response->set("Vary", "Origin, Accept-Encoding"); if(CloseConnection) { Response->set("Connection", "close"); Response->setKeepAlive(false); } else { Response->setKeepAlive(true); Response->set("Connection", "Keep-Alive"); Response->set("Keep-Alive", "timeout=30, max=1000"); } } inline void ProcessOptions() { Response->setVersion(Poco::Net::HTTPMessage::HTTP_1_1); Response->setChunkedTransferEncoding(true); auto Origin = Request->find("Origin"); if (Origin != Request->end()) { Response->set("Access-Control-Allow-Origin", Origin->second); } else { Response->set("Access-Control-Allow-Origin", "*"); } Response->set("Access-Control-Allow-Methods", MakeList(Methods_)); auto RequestHeaders = Request->find("Access-Control-Request-Headers"); if(RequestHeaders!=Request->end()) Response->set("Access-Control-Allow-Headers", RequestHeaders->second); Response->set("Vary", "Origin, Accept-Encoding"); Response->set("Access-Control-Allow-Credentials", "true"); Response->set("Access-Control-Max-Age", "86400"); Response->set("Connection", "Keep-Alive"); Response->set("Keep-Alive", "timeout=30, max=1000"); Response->setContentLength(0); Response->setStatus(Poco::Net::HTTPResponse::HTTP_OK); Response->send(); } inline void PrepareResponse(Poco::Net::HTTPResponse::HTTPStatus Status = Poco::Net::HTTPResponse::HTTP_OK, bool CloseConnection = false) { Response->setStatus(Status); SetCommonHeaders(CloseConnection); } inline void BadRequest(const OpenWifi::RESTAPI::Errors::msg &E, const std::string & Extra="") { PrepareResponse(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST); Poco::JSON::Object ErrorObject; ErrorObject.set("ErrorCode",400); ErrorObject.set("ErrorDetails",Request->getMethod()); if(Extra.empty()) ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ; else ErrorObject.set("ErrorDescription",fmt::format("{}: {} ({})",E.err_num,E.err_txt, Extra)) ; std::ostream &Answer = Response->send(); Poco::JSON::Stringifier::stringify(ErrorObject, Answer); } /* inline void BadRequest(uint64_t ErrorCode, const std::string & ErrorText) { PrepareResponse(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST); Poco::JSON::Object ErrorObject; ErrorObject.set("ErrorCode", ErrorCode); ErrorObject.set("ErrorDetails",Request->getMethod()); ErrorObject.set("ErrorDescription", ErrorText) ; std::ostream &Answer = Response->send(); Poco::JSON::Stringifier::stringify(ErrorObject, Answer); } */ inline void InternalError(const OpenWifi::RESTAPI::Errors::msg &E) { PrepareResponse(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR); Poco::JSON::Object ErrorObject; ErrorObject.set("ErrorCode",500); ErrorObject.set("ErrorDetails",Request->getMethod()); ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ; std::ostream &Answer = Response->send(); Poco::JSON::Stringifier::stringify(ErrorObject, Answer); } inline void UnAuthorized(const OpenWifi::RESTAPI::Errors::msg &E) { PrepareResponse(Poco::Net::HTTPResponse::HTTP_FORBIDDEN); Poco::JSON::Object ErrorObject; ErrorObject.set("ErrorCode",E.err_num); ErrorObject.set("ErrorDetails",Request->getMethod()); ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ; std::ostream &Answer = Response->send(); Poco::JSON::Stringifier::stringify(ErrorObject, Answer); } inline void NotFound() { PrepareResponse(Poco::Net::HTTPResponse::HTTP_NOT_FOUND); Poco::JSON::Object ErrorObject; ErrorObject.set("ErrorCode",404); ErrorObject.set("ErrorDetails",Request->getMethod()); const auto & E = OpenWifi::RESTAPI::Errors::Error404; ErrorObject.set("ErrorDescription",fmt::format("{}: {}",E.err_num,E.err_txt)) ; std::ostream &Answer = Response->send(); Poco::JSON::Stringifier::stringify(ErrorObject, Answer); poco_debug(Logger_,fmt::format("RES-NOTFOUND: User='{}@{}' Method='{}' Path='{}", UserInfo_.userinfo.email, Utils::FormatIPv6(Request->clientAddress().toString()), Request->getMethod(), Request->getURI())); } inline void OK() { PrepareResponse(); if( Request->getMethod()==Poco::Net::HTTPRequest::HTTP_DELETE || Request->getMethod()==Poco::Net::HTTPRequest::HTTP_OPTIONS) { Response->send(); } else { Poco::JSON::Object ErrorObject; ErrorObject.set("Code", 0); ErrorObject.set("Operation", Request->getMethod()); ErrorObject.set("Details", "Command completed."); std::ostream &Answer = Response->send(); Poco::JSON::Stringifier::stringify(ErrorObject, Answer); } } inline void SendCompressedTarFile(const std::string & FileName, const std::string & Content) { Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); SetCommonHeaders(); Response->set("Content-Type","application/gzip"); Response->set("Content-Disposition", "attachment; filename=" + FileName ); Response->set("Content-Transfer-Encoding","binary"); Response->set("Accept-Ranges", "bytes"); Response->set("Cache-Control", "no-store"); Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); Response->setStatus(Poco::Net::HTTPResponse::HTTP_OK); Response->setContentLength(Content.size()); Response->setChunkedTransferEncoding(true); std::ostream& OutputStream = Response->send(); OutputStream << Content; } inline void SendFile(Poco::File & File, const std::string & UUID) { Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); SetCommonHeaders(); Response->set("Content-Type","application/octet-stream"); Response->set("Content-Disposition", "attachment; filename=" + UUID ); Response->set("Content-Transfer-Encoding","binary"); Response->set("Accept-Ranges", "bytes"); Response->set("Cache-Control", "no-store"); Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); Response->setContentLength(File.getSize()); Response->sendFile(File.path(),"application/octet-stream"); } inline void SendFile(Poco::File & File) { Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); SetCommonHeaders(); Poco::Path P(File.path()); auto MT = Utils::FindMediaType(File); if(MT.Encoding==Utils::BINARY) { Response->set("Content-Transfer-Encoding","binary"); Response->set("Accept-Ranges", "bytes"); } Response->set("Cache-Control", "no-store"); Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); Response->sendFile(File.path(),MT.ContentType); } inline void SendFile(Poco::TemporaryFile &TempAvatar, [[maybe_unused]] const std::string &Type, const std::string & Name) { Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); SetCommonHeaders(); auto MT = Utils::FindMediaType(Name); if(MT.Encoding==Utils::BINARY) { Response->set("Content-Transfer-Encoding","binary"); Response->set("Accept-Ranges", "bytes"); } Response->set("Content-Disposition", "attachment; filename=" + Name ); Response->set("Accept-Ranges", "bytes"); Response->set("Cache-Control", "no-store"); Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); Response->setContentLength(TempAvatar.getSize()); Response->sendFile(TempAvatar.path(),MT.ContentType); } inline void SendFileContent(const std::string &Content, const std::string &Type, const std::string & Name) { Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); SetCommonHeaders(); auto MT = Utils::FindMediaType(Name); if(MT.Encoding==Utils::BINARY) { Response->set("Content-Transfer-Encoding","binary"); Response->set("Accept-Ranges", "bytes"); } Response->set("Content-Disposition", "attachment; filename=" + Name ); Response->set("Accept-Ranges", "bytes"); Response->set("Cache-Control", "no-store"); Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); Response->setContentLength(Content.size()); Response->setContentType(Type ); auto & OutputStream = Response->send(); OutputStream << Content ; } inline void SendHTMLFileBack(Poco::File & File, const Types::StringPairVec & FormVars) { Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK); SetCommonHeaders(); Response->set("Pragma", "private"); Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); std::string FormContent = Utils::LoadFile(File.path()); Utils::ReplaceVariables(FormContent, FormVars); Response->setContentLength(FormContent.size()); Response->setChunkedTransferEncoding(true); Response->setContentType("text/html"); std::ostream& ostr = Response->send(); ostr << FormContent; } inline void ReturnStatus(Poco::Net::HTTPResponse::HTTPStatus Status, bool CloseConnection=false) { PrepareResponse(Status, CloseConnection); if(Status == Poco::Net::HTTPResponse::HTTP_NO_CONTENT) { Response->setContentLength(0); Response->erase("Content-Type"); Response->setChunkedTransferEncoding(false); } Response->send(); } inline bool ContinueProcessing() { if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) { ProcessOptions(); return false; } else if (std::find(Methods_.begin(), Methods_.end(), Request->getMethod()) == Methods_.end()) { BadRequest(RESTAPI::Errors::UnsupportedHTTPMethod); return false; } return true; } inline bool IsAuthorized(bool & Expired, bool & Contacted, bool SubOnly = false ); inline void ReturnObject(Poco::JSON::Object &Object) { PrepareResponse(); if(Request!= nullptr) { // can we compress ??? auto AcceptedEncoding = Request->find("Accept-Encoding"); if(AcceptedEncoding!=Request->end()) { if( AcceptedEncoding->second.find("gzip")!=std::string::npos || AcceptedEncoding->second.find("compress")!=std::string::npos) { Response->set("Content-Encoding", "gzip"); std::ostream &Answer = Response->send(); Poco::DeflatingOutputStream deflater(Answer, Poco::DeflatingStreamBuf::STREAM_GZIP); Poco::JSON::Stringifier::stringify(Object, deflater); deflater.close(); return; } } } std::ostream &Answer = Response->send(); Poco::JSON::Stringifier::stringify(Object, Answer); } inline void ReturnCountOnly(uint64_t Count) { Poco::JSON::Object Answer; Answer.set("count", Count); ReturnObject(Answer); } inline bool InitQueryBlock() { if(QueryBlockInitialized_) return true; QueryBlockInitialized_=true; QB_.SerialNumber = GetParameter(RESTAPI::Protocol::SERIALNUMBER, ""); QB_.StartDate = GetParameter(RESTAPI::Protocol::STARTDATE, 0); QB_.EndDate = GetParameter(RESTAPI::Protocol::ENDDATE, 0); QB_.Offset = GetParameter(RESTAPI::Protocol::OFFSET, 0); QB_.Limit = GetParameter(RESTAPI::Protocol::LIMIT, 100); QB_.Filter = GetParameter(RESTAPI::Protocol::FILTER, ""); QB_.Lifetime = GetBoolParameter(RESTAPI::Protocol::LIFETIME,false); QB_.LogType = GetParameter(RESTAPI::Protocol::LOGTYPE,0); QB_.LastOnly = GetBoolParameter(RESTAPI::Protocol::LASTONLY,false); QB_.Newest = GetBoolParameter(RESTAPI::Protocol::NEWEST,false); QB_.CountOnly = GetBoolParameter(RESTAPI::Protocol::COUNTONLY,false); QB_.AdditionalInfo = GetBoolParameter(RESTAPI::Protocol::WITHEXTENDEDINFO,false); auto RawSelect = GetParameter(RESTAPI::Protocol::SELECT, ""); auto Entries = Poco::StringTokenizer(RawSelect,","); for(const auto &i:Entries) { QB_.Select.emplace_back(i); } if(QB_.Offset<1) QB_.Offset=0; return true; } [[nodiscard]] inline uint64_t Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default=0){ if(Obj->has(Parameter)) return Obj->get(Parameter); return Default; } [[nodiscard]] inline std::string GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default=""){ if(Obj->has(Parameter)) return Obj->get(Parameter).toString(); return Default; } [[nodiscard]] inline bool GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default=false){ if(Obj->has(Parameter)) return Obj->get(Parameter).toString()=="true"; return Default; } [[nodiscard]] inline uint64_t GetWhen(const Poco::JSON::Object::Ptr &Obj) { return RESTAPIHandler::Get(RESTAPI::Protocol::WHEN, Obj); } template void ReturnObject(const char *Name, const std::vector & Objects) { Poco::JSON::Object Answer; RESTAPI_utils::field_to_json(Answer,Name,Objects); ReturnObject(Answer); } Poco::Logger & Logger() { return Logger_; } virtual void DoGet() = 0 ; virtual void DoDelete() = 0 ; virtual void DoPost() = 0 ; virtual void DoPut() = 0 ; Poco::Net::HTTPServerRequest *Request= nullptr; Poco::Net::HTTPServerResponse *Response= nullptr; SecurityObjects::UserInfoAndPolicy UserInfo_; QueryBlock QB_; const std::string & Requester() const { return REST_Requester_; } protected: BindingMap Bindings_; Poco::URI::QueryParameters Parameters_; Poco::Logger &Logger_; std::string SessionToken_; std::vector Methods_; bool Internal_=false; bool RateLimited_=false; bool QueryBlockInitialized_=false; bool SubOnlyService_=false; bool AlwaysAuthorize_=true; Poco::JSON::Parser IncomingParser_; RESTAPI_GenericServer & Server_; RateLimit MyRates_; uint64_t TransactionId_; Poco::JSON::Object::Ptr ParsedBody_; std::string REST_Requester_; }; class RESTAPI_UnknownRequestHandler : public RESTAPIHandler { public: RESTAPI_UnknownRequestHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer & Server, uint64_t TransactionId, bool Internal) : RESTAPIHandler(bindings, L, std::vector{}, Server, TransactionId, Internal) {} inline void DoGet() override {}; inline void DoPost() override {}; inline void DoPut() override {}; inline void DoDelete() override {}; }; template constexpr auto test_has_PathName_method(T*) -> decltype( T::PathName() , std::true_type{} ) { return std::true_type{}; } constexpr auto test_has_PathName_method(...) -> std::false_type { return std::false_type{}; } template RESTAPIHandler * RESTAPI_Router(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & Logger, RESTAPI_GenericServer & Server, uint64_t TransactionId) { static_assert(test_has_PathName_method((T*)nullptr), "Class must have a static PathName() method."); if(RESTAPIHandler::ParseBindings(RequestedPath,T::PathName(),Bindings)) { return new T(Bindings, Logger, Server, TransactionId, false); } if constexpr (sizeof...(Args) == 0) { return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server, TransactionId, false); } else { return RESTAPI_Router(RequestedPath, Bindings, Logger, Server, TransactionId); } } template RESTAPIHandler * RESTAPI_Router_I(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & Logger, RESTAPI_GenericServer & Server, uint64_t TransactionId) { static_assert(test_has_PathName_method((T*)nullptr), "Class must have a static PathName() method."); if(RESTAPIHandler::ParseBindings(RequestedPath,T::PathName(),Bindings)) { return new T(Bindings, Logger, Server, TransactionId, true ); } if constexpr (sizeof...(Args) == 0) { return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server, TransactionId, true); } else { return RESTAPI_Router_I(RequestedPath, Bindings, Logger, Server, TransactionId); } } class OpenAPIRequestGet { public: explicit OpenAPIRequestGet( const std::string & Type, const std::string & EndPoint, const Types::StringPairVec & QueryData, uint64_t msTimeout): Type_(Type), EndPoint_(EndPoint), QueryData_(QueryData), msTimeout_(msTimeout) {}; inline Poco::Net::HTTPServerResponse::HTTPStatus Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken = ""); private: std::string Type_; std::string EndPoint_; Types::StringPairVec QueryData_; uint64_t msTimeout_; }; class OpenAPIRequestPut { public: explicit OpenAPIRequestPut( const std::string & Type, const std::string & EndPoint, const Types::StringPairVec & QueryData, const Poco::JSON::Object & Body, uint64_t msTimeout): Type_(Type), EndPoint_(EndPoint), QueryData_(QueryData), msTimeout_(msTimeout), Body_(Body){}; inline Poco::Net::HTTPServerResponse::HTTPStatus Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken = ""); private: std::string Type_; std::string EndPoint_; Types::StringPairVec QueryData_; uint64_t msTimeout_; Poco::JSON::Object Body_; }; class OpenAPIRequestPost { public: explicit OpenAPIRequestPost( const std::string & Type, const std::string & EndPoint, const Types::StringPairVec & QueryData, const Poco::JSON::Object & Body, uint64_t msTimeout): Type_(Type), EndPoint_(EndPoint), QueryData_(QueryData), msTimeout_(msTimeout), Body_(Body){}; inline Poco::Net::HTTPServerResponse::HTTPStatus Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken = ""); private: std::string Type_; std::string EndPoint_; Types::StringPairVec QueryData_; uint64_t msTimeout_; Poco::JSON::Object Body_; }; class OpenAPIRequestDelete { public: explicit OpenAPIRequestDelete( const std::string & Type, const std::string & EndPoint, const Types::StringPairVec & QueryData, uint64_t msTimeout): Type_(Type), EndPoint_(EndPoint), QueryData_(QueryData), msTimeout_(msTimeout){}; inline Poco::Net::HTTPServerResponse::HTTPStatus Do(const std::string & BearerToken = ""); private: std::string Type_; std::string EndPoint_; Types::StringPairVec QueryData_; uint64_t msTimeout_; Poco::JSON::Object Body_; }; class KafkaMessage: public Poco::Notification { public: KafkaMessage( const std::string &Topic, const std::string &Key, const std::string & Payload) : Topic_(Topic), Key_(Key), Payload_(Payload) { } inline const std::string & Topic() { return Topic_; } inline const std::string & Key() { return Key_; } inline const std::string & Payload() { return Payload_; } private: std::string Topic_; std::string Key_; std::string Payload_; }; class KafkaProducer : public Poco::Runnable { public: inline void run () override; inline void Start() { if(!Running_) { Running_=true; Worker_.start(*this); } } inline void Stop() { if(Running_) { Running_=false; Queue_.wakeUpAll(); Worker_.join(); } } inline void Produce(const std::string &Topic, const std::string &Key, const std::string &Payload) { std::lock_guard G(Mutex_); Queue_.enqueueNotification( new KafkaMessage(Topic,Key,Payload)); } private: std::recursive_mutex Mutex_; Poco::Thread Worker_; mutable std::atomic_bool Running_=false; Poco::NotificationQueue Queue_; }; class KafkaConsumer : public Poco::Runnable { public: inline void run() override; void Start() { if(!Running_) { Running_=true; Worker_.start(*this); } } void Stop() { if(Running_) { Running_=false; Worker_.wakeUp(); Worker_.join(); } } private: std::recursive_mutex Mutex_; Poco::Thread Worker_; mutable std::atomic_bool Running_=false; }; class KafkaDispatcher : public Poco::Runnable { public: inline void Start() { if(!Running_) { Running_=true; Worker_.start(*this); } } inline void Stop() { if(Running_) { Running_=false; Queue_.wakeUpAll(); Worker_.join(); } } inline auto RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F) { std::lock_guard G(Mutex_); auto It = Notifiers_.find(Topic); if(It == Notifiers_.end()) { Types::TopicNotifyFunctionList L; L.emplace(L.end(),std::make_pair(F,FunctionId_)); Notifiers_[Topic] = std::move(L); } else { It->second.emplace(It->second.end(),std::make_pair(F,FunctionId_)); } return FunctionId_++; } inline void UnregisterTopicWatcher(const std::string &Topic, int Id) { std::lock_guard G(Mutex_); auto It = Notifiers_.find(Topic); if(It != Notifiers_.end()) { Types::TopicNotifyFunctionList & L = It->second; for(auto it=L.begin(); it!=L.end(); it++) if(it->second == Id) { L.erase(it); break; } } } void Dispatch(const std::string &Topic, const std::string &Key, const std::string &Payload) { std::lock_guard G(Mutex_); auto It = Notifiers_.find(Topic); if(It!=Notifiers_.end()) { Queue_.enqueueNotification(new KafkaMessage(Topic, Key, Payload)); } } inline void run() override { Poco::AutoPtr Note(Queue_.waitDequeueNotification()); Utils::SetThreadName("kafka:dispatch"); while(Note && Running_) { auto Msg = dynamic_cast(Note.get()); if(Msg!= nullptr) { auto It = Notifiers_.find(Msg->Topic()); if (It != Notifiers_.end()) { const auto & FL = It->second; for(const auto &[CallbackFunc,_]:FL) { CallbackFunc(Msg->Key(), Msg->Payload()); } } } Note = Queue_.waitDequeueNotification(); } } inline void Topics(std::vector &T) { T.clear(); for(const auto &[TopicName,_]:Notifiers_) T.push_back(TopicName); } private: std::recursive_mutex Mutex_; Types::NotifyTable Notifiers_; Poco::Thread Worker_; mutable std::atomic_bool Running_=false; uint64_t FunctionId_=1; Poco::NotificationQueue Queue_; }; class KafkaManager : public SubSystemServer { public: friend class KafkaConsumer; friend class KafkaProducer; inline void initialize(Poco::Util::Application & self) override; static auto instance() { static auto instance_ = new KafkaManager; return instance_; } inline int Start() override { if(!KafkaEnabled_) return 0; ConsumerThr_.Start(); ProducerThr_.Start(); Dispatcher_.Start(); return 0; } inline void Stop() override { if(KafkaEnabled_) { poco_information(Logger(),"Stopping..."); Dispatcher_.Stop(); ProducerThr_.Stop(); ConsumerThr_.Stop(); poco_information(Logger(),"Stopped..."); return; } } inline void PostMessage(const std::string &topic, const std::string & key, const std::string &PayLoad, bool WrapMessage = true ) { if(KafkaEnabled_) { ProducerThr_.Produce(topic,key,WrapMessage ? WrapSystemId(PayLoad) : PayLoad); } } inline void Dispatch(const std::string &Topic, const std::string & Key, const std::string &Payload) { Dispatcher_.Dispatch(Topic, Key, Payload); } [[nodiscard]] inline std::string WrapSystemId(const std::string & PayLoad) { return SystemInfoWrapper_ + PayLoad + "}"; } [[nodiscard]] inline bool Enabled() const { return KafkaEnabled_; } inline uint64_t RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F) { if(KafkaEnabled_) { return Dispatcher_.RegisterTopicWatcher(Topic,F); } else { return 0; } } inline void UnregisterTopicWatcher(const std::string &Topic, uint64_t Id) { if(KafkaEnabled_) { Dispatcher_.UnregisterTopicWatcher(Topic, Id); } } inline void Topics(std::vector &T) { Dispatcher_.Topics(T); } private: bool KafkaEnabled_ = false; std::string SystemInfoWrapper_; KafkaProducer ProducerThr_; KafkaConsumer ConsumerThr_; KafkaDispatcher Dispatcher_; inline void PartitionAssignment(const cppkafka::TopicPartitionList& partitions) { Logger().information(fmt::format("Partition assigned: {}...", partitions.front().get_partition())); } inline void PartitionRevocation(const cppkafka::TopicPartitionList& partitions) { Logger().information(fmt::format("Partition revocation: {}...",partitions.front().get_partition())); } KafkaManager() noexcept: SubSystemServer("KafkaManager", "KAFKA-SVR", "openwifi.kafka") { } }; inline auto KafkaManager() { return KafkaManager::instance(); } class AuthClient : public SubSystemServer { public: explicit AuthClient() noexcept: SubSystemServer("Authentication", "AUTH-CLNT", "authentication") { } static auto instance() { static auto instance_ = new AuthClient; return instance_; } inline int Start() override { return 0; } inline void Stop() override { std::lock_guard G(Mutex_); Cache_.clear(); } inline void RemovedCachedToken(const std::string &Token) { std::lock_guard G(Mutex_); Cache_.remove(Token); } inline static bool IsTokenExpired(const SecurityObjects::WebToken &T) { return ((T.expires_in_+T.created_) < OpenWifi::Now()); } inline bool RetrieveTokenInformation(const std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired, bool & Contacted, bool Sub=false) { try { Types::StringPairVec QueryData; QueryData.push_back(std::make_pair("token",SessionToken)); OpenAPIRequestGet Req( uSERVICE_SECURITY, Sub ? "/api/v1/validateSubToken" : "/api/v1/validateToken", QueryData, 10000); Poco::JSON::Object::Ptr Response; auto StatusCode = Req.Do(Response); if(StatusCode==Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT) { Contacted = false; return false; } Contacted = true; if(StatusCode==Poco::Net::HTTPServerResponse::HTTP_OK) { if(Response->has("tokenInfo") && Response->has("userInfo")) { UInfo.from_json(Response); if(IsTokenExpired(UInfo.webtoken)) { Expired = true; return false; } Expired = false; std::lock_guard G(Mutex_); Cache_.update(SessionToken, UInfo); return true; } else { return false; } } } catch (...) { } Expired = false; return false; } inline bool IsAuthorized(const std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired, bool & Contacted, bool Sub = false) { std::lock_guard G(Mutex_); auto User = Cache_.get(SessionToken); if(!User.isNull()) { if(IsTokenExpired(User->webtoken)) { Expired = true; return false; } Expired = false; UInfo = *User; return true; } return RetrieveTokenInformation(SessionToken, UInfo, Expired, Contacted, Sub); } private: Poco::ExpireLRUCache Cache_{512,1200000 }; }; inline auto AuthClient() { return AuthClient::instance(); } class ALBRequestHandler: public Poco::Net::HTTPRequestHandler /// Return a HTML document with the current date and time. { public: explicit ALBRequestHandler(Poco::Logger & L, uint64_t id) : Logger_(L), id_(id) { } void handleRequest([[maybe_unused]] Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response) override { Utils::SetThreadName("alb-request"); try { if((id_ % 100) == 0) { poco_debug(Logger_,fmt::format("ALB-REQUEST({}): ALB Request {}.", Request.clientAddress().toString(), id_)); } Response.setChunkedTransferEncoding(true); Response.setContentType("text/html"); Response.setDate(Poco::Timestamp()); Response.setStatus(Poco::Net::HTTPResponse::HTTP_OK); Response.setKeepAlive(true); Response.set("Connection", "keep-alive"); Response.setVersion(Poco::Net::HTTPMessage::HTTP_1_1); std::ostream &Answer = Response.send(); Answer << "process Alive and kicking!"; } catch (...) { } } private: Poco::Logger & Logger_; uint64_t id_; }; class ALBRequestHandlerFactory: public Poco::Net::HTTPRequestHandlerFactory { public: explicit ALBRequestHandlerFactory(Poco::Logger & L): Logger_(L) { } ALBRequestHandler* createRequestHandler(const Poco::Net::HTTPServerRequest& request) override { if (request.getURI() == "/") return new ALBRequestHandler(Logger_, req_id_++); else return nullptr; } private: Poco::Logger &Logger_; inline static std::atomic_uint64_t req_id_=1; }; class ALBHealthCheckServer : public SubSystemServer { public: ALBHealthCheckServer() noexcept: SubSystemServer("ALBHealthCheckServer", "ALB-SVR", "alb") { } static auto instance() { static auto instance = new ALBHealthCheckServer; return instance; } inline int Start() override; inline void Stop() override { poco_information(Logger(),"Stopping..."); if(Running_) Server_->stopAll(true); poco_information(Logger(),"Stopped..."); } private: std::unique_ptr Server_; std::unique_ptr Socket_; int Port_ = 0; mutable std::atomic_bool Running_=false; }; inline auto ALBHealthCheckServer() { return ALBHealthCheckServer::instance(); } Poco::Net::HTTPRequestHandler * RESTAPI_ExtRouter(const std::string &Path, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t Id); Poco::Net::HTTPRequestHandler * RESTAPI_IntRouter(const std::string &Path, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t Id); class RESTAPI_ExtServer : public SubSystemServer { public: static auto instance() { static auto instance_ = new RESTAPI_ExtServer; return instance_; } int Start() override; inline void Stop() override { Logger().information("Stopping..."); for( const auto & svr : RESTServers_ ) svr->stopAll(true); Pool_.stopAll(); Pool_.joinAll(); RESTServers_.clear(); Logger().information("Stopped..."); } inline void reinitialize(Poco::Util::Application &self) override; inline Poco::Net::HTTPRequestHandler *CallServer(const std::string &Path, uint64_t Id) { RESTAPIHandler::BindingMap Bindings; Utils::SetThreadName(fmt::format("x-rest:{}",Id).c_str()); return RESTAPI_ExtRouter(Path, Bindings, Logger(), Server_, Id); } const Poco::ThreadPool & Pool() { return Pool_; } private: std::vector> RESTServers_; Poco::ThreadPool Pool_{"x-rest",4,128}; RESTAPI_GenericServer Server_; RESTAPI_ExtServer() noexcept: SubSystemServer("RESTAPI_ExtServer", "REST-XSRV", "openwifi.restapi") { } }; inline auto RESTAPI_ExtServer() { return RESTAPI_ExtServer::instance(); }; class ExtRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory { public: ExtRequestHandlerFactory() = default; inline Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &Request) override { try { Poco::URI uri(Request.getURI()); Utils::SetThreadName(fmt::format("x-rest:{}",TransactionId_).c_str()); return RESTAPI_ExtServer()->CallServer(uri.getPath(), TransactionId_++); } catch (...) { } return nullptr; } private: static inline std::atomic_uint64_t TransactionId_ = 1; }; class LogMuxer : public Poco::Channel { public: inline std::string getProperty( [[maybe_unused]] const std::string &p ) const final { return ""; } inline void close() final { } inline void open() final { } inline static std::string to_string(Poco::Message::Priority p) { switch(p) { case Poco::Message::PRIO_INFORMATION: return "information"; case Poco::Message::PRIO_CRITICAL: return "critical"; case Poco::Message::PRIO_DEBUG: return "debug"; case Poco::Message::PRIO_ERROR: return "error"; case Poco::Message::PRIO_FATAL: return "level"; case Poco::Message::PRIO_NOTICE: return "notice"; case Poco::Message::PRIO_TRACE: return "trace"; case Poco::Message::PRIO_WARNING: return "warning"; default: return "none"; } } inline void log(const Poco::Message &m) final { if(Enabled_) { /* nlohmann::json log_msg; log_msg["msg"] = m.getText(); log_msg["level"] = to_string(m.getPriority()); log_msg["timestamp"] = Poco::DateTimeFormatter::format(m.getTime(), Poco::DateTimeFormat::ISO8601_FORMAT); log_msg["source"] = m.getSource(); log_msg["thread_name"] = m.getThread(); log_msg["thread_id"] = m.getTid(); std::cout << log_msg << std::endl; */ std::lock_guard G(Mutex_); std::vector Remove; for(const auto &[Id,CallBack]:CallBacks_) { try { CallBack(m); } catch (...) { Remove.push_back(Id); } } for(const auto &i:Remove) CallBacks_.erase(i); } } inline void setProperty([[maybe_unused]] const std::string &name, [[maybe_unused]] const std::string &value) final { } inline static auto instance() { static auto instance_ = new LogMuxer; return instance_; } inline void Enable(bool enable) { Enabled_ = enable; } typedef std::function logmuxer_callback_func_t; inline void RegisterCallback(const logmuxer_callback_func_t & R, uint64_t &Id) { std::lock_guard G(Mutex_); Id = CallBackId_++; CallBacks_[Id] = R; } private: std::recursive_mutex Mutex_; std::map CallBacks_; inline static uint64_t CallBackId_=1; bool Enabled_ = false; }; inline auto LogMuxer() { return LogMuxer::instance(); } class RESTAPI_IntServer : public SubSystemServer { public: static auto instance() { static auto instance_ = new RESTAPI_IntServer; return instance_; } inline int Start() override; inline void Stop() override { Logger().information("Stopping..."); for( const auto & svr : RESTServers_ ) svr->stopAll(true); Pool_.stopAll(); Pool_.joinAll(); Logger().information("Stopped..."); } inline void reinitialize(Poco::Util::Application &self) override; inline Poco::Net::HTTPRequestHandler *CallServer(const std::string &Path, uint64_t Id) { RESTAPIHandler::BindingMap Bindings; Utils::SetThreadName(fmt::format("i-rest:{}",Id).c_str()); return RESTAPI_IntRouter(Path, Bindings, Logger(), Server_, Id); } const Poco::ThreadPool & Pool() { return Pool_; } private: std::vector> RESTServers_; Poco::ThreadPool Pool_{"i-rest",4,96}; RESTAPI_GenericServer Server_; RESTAPI_IntServer() noexcept: SubSystemServer("RESTAPI_IntServer", "REST-ISRV", "openwifi.internal.restapi") { } }; inline auto RESTAPI_IntServer() { return RESTAPI_IntServer::instance(); }; class IntRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory { public: inline IntRequestHandlerFactory() = default; inline Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &Request) override { Utils::SetThreadName(fmt::format("i-rest:{}",TransactionId_).c_str()); Poco::URI uri(Request.getURI()); return RESTAPI_IntServer()->CallServer(uri.getPath(), TransactionId_); } private: static inline std::atomic_uint64_t TransactionId_ = 1; }; struct MicroServiceMeta { uint64_t Id=0; std::string Type; std::string PrivateEndPoint; std::string PublicEndPoint; std::string AccessKey; std::string Version; uint64_t LastUpdate=0; }; class SubSystemServer; typedef std::map MicroServiceMetaMap; typedef std::vector MicroServiceMetaVec; typedef std::vector SubSystemVec; class MicroService : public Poco::Util::ServerApplication { public: explicit MicroService( std::string PropFile, std::string RootEnv, std::string ConfigVar, std::string AppName, uint64_t BusTimer, SubSystemVec Subsystems) : DAEMON_PROPERTIES_FILENAME(std::move(PropFile)), DAEMON_ROOT_ENV_VAR(std::move(RootEnv)), DAEMON_CONFIG_ENV_VAR(std::move(ConfigVar)), DAEMON_APP_NAME(std::move(AppName)), DAEMON_BUS_TIMER(BusTimer), SubSystems_(std::move(Subsystems)), Logger_(Poco::Logger::get("FRAMEWORK")) { instance_ = this; RandomEngine_.seed(std::chrono::steady_clock::now().time_since_epoch().count()); // Logger_ = Poco::Logger::root().get("BASE-SVC"); } [[nodiscard]] std::string Version() { return Version_; } [[nodiscard]] inline const std::string & DataDir() { return DataDir_; } [[nodiscard]] inline const std::string & WWWAssetsDir() { return WWWAssetsDir_; } [[nodiscard]] bool Debug() const { return DebugMode_; } [[nodiscard]] uint64_t ID() const { return ID_; } [[nodiscard]] std::string Hash() const { return MyHash_; }; [[nodiscard]] std::string ServiceType() const { return DAEMON_APP_NAME; }; [[nodiscard]] std::string PrivateEndPoint() const { return MyPrivateEndPoint_; }; [[nodiscard]] std::string PublicEndPoint() const { return MyPublicEndPoint_; }; [[nodiscard]] const SubSystemVec & GetFullSubSystems() { return SubSystems_; } inline uint64_t DaemonBusTimer() const { return DAEMON_BUS_TIMER; }; [[nodiscard]] const std::string & AppName() { return DAEMON_APP_NAME; } static inline uint64_t GetPID() { return Poco::Process::id(); }; [[nodiscard]] inline const std::string GetPublicAPIEndPoint() { return MyPublicEndPoint_ + "/api/v1"; }; [[nodiscard]] inline const std::string & GetUIURI() const { return UIURI_;}; [[nodiscard]] inline uint64_t Random(uint64_t ceiling) { return (RandomEngine_() % ceiling); } [[nodiscard]] inline uint64_t Random(uint64_t min, uint64_t max) { return ((RandomEngine_() % (max-min)) + min); } /* inline Poco::Logger & GetLogger(const std::string &Name) { static auto initialized = false; if(!initialized) { initialized = true; InitializeLoggingSystem(); } return Poco::Logger::get(Name); } */ virtual void GetExtraConfiguration(Poco::JSON::Object & Cfg) { Cfg.set("additionalConfiguration",false); } static inline void Exit(int Reason); inline void BusMessageReceived(const std::string &Key, const std::string & Payload); inline MicroServiceMetaVec GetServices(const std::string & Type); inline MicroServiceMetaVec GetServices(); inline void LoadConfigurationFile(); inline void Reload(); inline void LoadMyConfig(); inline void initialize(Poco::Util::Application &self) override; inline void uninitialize() override; inline void reinitialize(Poco::Util::Application &self) override; inline void defineOptions(Poco::Util::OptionSet &options) override; inline void handleHelp(const std::string &name, const std::string &value); inline void handleVersion(const std::string &name, const std::string &value); inline void handleDebug(const std::string &name, const std::string &value); inline void handleLogs(const std::string &name, const std::string &value); inline void handleConfig(const std::string &name, const std::string &value); inline void displayHelp(); inline void InitializeSubSystemServers(); inline void StartSubSystemServers(); inline void StopSubSystemServers(); [[nodiscard]] static inline std::string CreateUUID(); inline bool SetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level); inline void Reload(const std::string &Sub); inline Types::StringVec GetSubSystems() const; inline Types::StringPairVec GetLogLevels(); inline const Types::StringVec & GetLogLevelNames(); inline uint64_t ConfigGetInt(const std::string &Key,uint64_t Default); inline uint64_t ConfigGetInt(const std::string &Key); inline uint64_t ConfigGetBool(const std::string &Key,bool Default); inline uint64_t ConfigGetBool(const std::string &Key); inline std::string ConfigGetString(const std::string &Key,const std::string & Default); inline std::string ConfigGetString(const std::string &Key); inline std::string ConfigPath(const std::string &Key,const std::string & Default); inline std::string ConfigPath(const std::string &Key); inline std::string Encrypt(const std::string &S); inline std::string Decrypt(const std::string &S); inline std::string MakeSystemEventMessage( const std::string & Type ) const; [[nodiscard]] inline bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request); inline static void SavePID(); inline int main(const ArgVec &args) override; static MicroService & instance() { return *instance_; } inline void InitializeLoggingSystem(); inline void SaveConfig() { PropConfigurationFile_->save(ConfigFileName_); } inline auto UpdateConfig() { return PropConfigurationFile_; } inline void AddActivity(const std::string &Activity) { if(!DataDir_.empty()) { std::string ActivityFile{ DataDir_ + "/activity.log"}; try { std::ofstream of(ActivityFile,std::ios_base::app | std::ios_base::out ); auto t = std::chrono::system_clock::now(); std::time_t now = std::chrono::system_clock::to_time_t(t); of << Activity << " at " << std::ctime(&now) ; } catch (...) { } } } inline bool NoAPISecurity() const { return NoAPISecurity_; } [[nodiscard]] inline std::string Sign(Poco::JWT::Token &T, const std::string &Algo) { if(NoBuiltInCrypto_) { return T.toString(); } else { return Signer_.sign(T,Algo); } } inline Poco::ThreadPool & TimerPool() { return TimerPool_; } private: static MicroService * instance_; bool HelpRequested_ = false; std::string LogDir_; std::string ConfigFileName_; Poco::UUIDGenerator UUIDGenerator_; uint64_t ID_ = 1; Poco::SharedPtr AppKey_; bool DebugMode_ = false; std::string DataDir_; std::string WWWAssetsDir_; Poco::Crypto::CipherFactory & CipherFactory_ = Poco::Crypto::CipherFactory::defaultFactory(); Poco::Crypto::Cipher * Cipher_ = nullptr; MicroServiceMetaMap Services_; std::string MyHash_; std::string MyPrivateEndPoint_; std::string MyPublicEndPoint_; std::string UIURI_; std::string Version_{ OW_VERSION::VERSION + "("+ OW_VERSION::BUILD + ")" + " - " + OW_VERSION::HASH }; std::recursive_mutex InfraMutex_; std::default_random_engine RandomEngine_; Poco::Util::PropertyFileConfiguration * PropConfigurationFile_ = nullptr; std::string DAEMON_PROPERTIES_FILENAME; std::string DAEMON_ROOT_ENV_VAR; std::string DAEMON_CONFIG_ENV_VAR; std::string DAEMON_APP_NAME; uint64_t DAEMON_BUS_TIMER; SubSystemVec SubSystems_; bool NoAPISecurity_=false; bool NoBuiltInCrypto_=false; Poco::JWT::Signer Signer_; Poco::Logger &Logger_; Poco::ThreadPool TimerPool_{"timer:pool",2,16}; std::unique_ptr BusEventManager_; }; inline void MicroService::Exit(int Reason) { std::exit(Reason); } inline void MicroService::BusMessageReceived([[maybe_unused]] const std::string &Key, const std::string & Payload) { std::lock_guard G(InfraMutex_); try { Poco::JSON::Parser P; auto Object = P.parse(Payload).extract(); if (Object->has(KafkaTopics::ServiceEvents::Fields::ID) && Object->has(KafkaTopics::ServiceEvents::Fields::EVENT)) { uint64_t ID = Object->get(KafkaTopics::ServiceEvents::Fields::ID); auto Event = Object->get(KafkaTopics::ServiceEvents::Fields::EVENT).toString(); if (ID != ID_) { if( Event==KafkaTopics::ServiceEvents::EVENT_JOIN || Event==KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE || Event==KafkaTopics::ServiceEvents::EVENT_LEAVE ) { if( Object->has(KafkaTopics::ServiceEvents::Fields::TYPE) && Object->has(KafkaTopics::ServiceEvents::Fields::PUBLIC) && Object->has(KafkaTopics::ServiceEvents::Fields::PRIVATE) && Object->has(KafkaTopics::ServiceEvents::Fields::VRSN) && Object->has(KafkaTopics::ServiceEvents::Fields::KEY)) { auto PrivateEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(); if (Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE && Services_.find(PrivateEndPoint) != Services_.end()) { Services_[PrivateEndPoint].LastUpdate = OpenWifi::Now(); } else if (Event == KafkaTopics::ServiceEvents::EVENT_LEAVE) { Services_.erase(PrivateEndPoint); poco_debug(logger(),fmt::format("Service {} ID={} leaving system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID)); } else if (Event == KafkaTopics::ServiceEvents::EVENT_JOIN || Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE) { poco_debug(logger(),fmt::format("Service {} ID={} joining system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID)); Services_[PrivateEndPoint] = MicroServiceMeta{ .Id = ID, .Type = Poco::toLower(Object->get(KafkaTopics::ServiceEvents::Fields::TYPE).toString()), .PrivateEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(), .PublicEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PUBLIC).toString(), .AccessKey = Object->get(KafkaTopics::ServiceEvents::Fields::KEY).toString(), .Version = Object->get(KafkaTopics::ServiceEvents::Fields::VRSN).toString(), .LastUpdate = OpenWifi::Now() }; std::string SvcList; for (const auto &Svc: Services_) { if(SvcList.empty()) SvcList = Svc.second.Type; else SvcList += ", " + Svc.second.Type; } logger().information(fmt::format("Current list of microservices: {}", SvcList)); } } else { poco_error(logger(),fmt::format("KAFKA-MSG: invalid event '{}', missing a field.",Event)); } } else if (Event==KafkaTopics::ServiceEvents::EVENT_REMOVE_TOKEN) { if(Object->has(KafkaTopics::ServiceEvents::Fields::TOKEN)) { #ifndef TIP_SECURITY_SERVICE AuthClient()->RemovedCachedToken(Object->get(KafkaTopics::ServiceEvents::Fields::TOKEN).toString()); #endif } else { poco_error(logger(),fmt::format("KAFKA-MSG: invalid event '{}', missing token",Event)); } } else { poco_error(logger(),fmt::format("Unknown Event: {} Source: {}", Event, ID)); } } } else { poco_error(logger(),"Bad bus message."); } auto i=Services_.begin(); auto now = OpenWifi::Now(); for(;i!=Services_.end();) { if((now - i->second.LastUpdate)>60) { i = Services_.erase(i); } else ++i; } } catch (const Poco::Exception &E) { logger().log(E); } } inline MicroServiceMetaVec MicroService::GetServices(const std::string & Type) { std::lock_guard G(InfraMutex_); auto T = Poco::toLower(Type); MicroServiceMetaVec Res; for(const auto &[_,ServiceRec]:Services_) { if(ServiceRec.Type==T) Res.push_back(ServiceRec); } return Res; } inline MicroServiceMetaVec MicroService::GetServices() { std::lock_guard G(InfraMutex_); MicroServiceMetaVec Res; for(const auto &[_,ServiceRec]:Services_) { Res.push_back(ServiceRec); } return Res; } inline void MicroService::LoadConfigurationFile() { std::string Location = Poco::Environment::get(DAEMON_CONFIG_ENV_VAR,"."); ConfigFileName_ = ConfigFileName_.empty() ? Location + "/" + DAEMON_PROPERTIES_FILENAME : ConfigFileName_; Poco::Path ConfigFile(ConfigFileName_); if(!ConfigFile.isFile()) { std::cerr << DAEMON_APP_NAME << ": Configuration " << ConfigFile.toString() << " does not seem to exist. Please set " + DAEMON_CONFIG_ENV_VAR + " env variable the path of the " + DAEMON_PROPERTIES_FILENAME + " file." << std::endl; std::exit(Poco::Util::Application::EXIT_CONFIG); } // loadConfiguration(ConfigFile.toString()); PropConfigurationFile_ = new Poco::Util::PropertyFileConfiguration(ConfigFile.toString()); configPtr()->addWriteable(PropConfigurationFile_, PRIO_DEFAULT); } inline void MicroService::Reload() { LoadConfigurationFile(); LoadMyConfig(); } inline void MicroService::LoadMyConfig() { NoAPISecurity_ = ConfigGetBool("openwifi.security.restapi.disable",false); std::string KeyFile = ConfigPath("openwifi.service.key",""); if(!KeyFile.empty()) { std::string KeyFilePassword = ConfigPath("openwifi.service.key.password", ""); AppKey_ = Poco::SharedPtr(new Poco::Crypto::RSAKey("", KeyFile, KeyFilePassword)); Cipher_ = CipherFactory_.createCipher(*AppKey_); Signer_.setRSAKey(AppKey_); Signer_.addAllAlgorithms(); NoBuiltInCrypto_ = false; } else { NoBuiltInCrypto_ = true; } ID_ = Utils::GetSystemId(); if(!DebugMode_) DebugMode_ = ConfigGetBool("openwifi.system.debug",false); MyPrivateEndPoint_ = ConfigGetString("openwifi.system.uri.private"); MyPublicEndPoint_ = ConfigGetString("openwifi.system.uri.public"); UIURI_ = ConfigGetString("openwifi.system.uri.ui"); MyHash_ = Utils::ComputeHash(MyPublicEndPoint_); } void MicroServicePostInitialization(); inline void MicroService::InitializeLoggingSystem() { static auto initialized = false; if(!initialized) { initialized = true; LoadConfigurationFile(); auto LoggingDestination = MicroService::instance().ConfigGetString("logging.type", "file"); auto LoggingFormat = MicroService::instance().ConfigGetString("logging.format", "%Y-%m-%d %H:%M:%S %s: [%p] %t"); if (LoggingDestination == "console") { Poco::AutoPtr Console(new Poco::ConsoleChannel); Poco::AutoPtr Async(new Poco::AsyncChannel(Console)); Poco::AutoPtr Formatter(new Poco::PatternFormatter); Formatter->setProperty("pattern", LoggingFormat); Poco::AutoPtr FormattingChannel( new Poco::FormattingChannel(Formatter, Async)); Poco::Logger::root().setChannel(FormattingChannel); } else if (LoggingDestination == "colorconsole") { Poco::AutoPtr Console(new Poco::ColorConsoleChannel); Poco::AutoPtr Async(new Poco::AsyncChannel(Console)); Poco::AutoPtr Formatter(new Poco::PatternFormatter); Formatter->setProperty("pattern", LoggingFormat); Poco::AutoPtr FormattingChannel( new Poco::FormattingChannel(Formatter, Async)); Poco::Logger::root().setChannel(FormattingChannel); } else if (LoggingDestination == "sql") { //"CREATE TABLE T_POCO_LOG (Source VARCHAR, Name VARCHAR, ProcessId INTEGER, Thread VARCHAR, ThreadId INTEGER, Priority INTEGER, Text VARCHAR, DateTime DATE)" } else if (LoggingDestination == "syslog") { } else { auto LoggingLocation = MicroService::instance().ConfigPath("logging.path", "$OWCERT_ROOT/logs") + "/log"; Poco::AutoPtr FileChannel(new Poco::FileChannel); FileChannel->setProperty("rotation", "10 M"); FileChannel->setProperty("archive", "timestamp"); FileChannel->setProperty("path", LoggingLocation); Poco::AutoPtr Async_File(new Poco::AsyncChannel(FileChannel)); Poco::AutoPtr Async_Muxer(new Poco::AsyncChannel(LogMuxer())); Poco::AutoPtr Splitter(new Poco::SplitterChannel); Splitter->addChannel(Async_File); Splitter->addChannel(Async_Muxer); Poco::AutoPtr Formatter(new Poco::PatternFormatter); Formatter->setProperty("pattern", LoggingFormat); Poco::AutoPtr FormattingChannel( new Poco::FormattingChannel(Formatter, Splitter)); Poco::Logger::root().setChannel(FormattingChannel); } auto Level = Poco::Logger::parseLevel(MicroService::instance().ConfigGetString("logging.level", "debug")); Poco::Logger::root().setLevel(Level); } } void DaemonPostInitialization(Poco::Util::Application &self); inline void MicroService::initialize(Poco::Util::Application &self) { // add the default services LoadConfigurationFile(); InitializeLoggingSystem(); SubSystems_.push_back(KafkaManager()); SubSystems_.push_back(ALBHealthCheckServer()); SubSystems_.push_back(RESTAPI_ExtServer()); SubSystems_.push_back(RESTAPI_IntServer()); Poco::Net::initializeSSL(); Poco::Net::HTTPStreamFactory::registerFactory(); Poco::Net::HTTPSStreamFactory::registerFactory(); Poco::Net::FTPStreamFactory::registerFactory(); Poco::Net::FTPSStreamFactory::registerFactory(); Poco::File DataDir(ConfigPath("openwifi.system.data")); DataDir_ = DataDir.path(); if(!DataDir.exists()) { try { DataDir.createDirectory(); } catch (const Poco::Exception &E) { logger().log(E); } } WWWAssetsDir_ = ConfigPath("openwifi.restapi.wwwassets",""); if(WWWAssetsDir_.empty()) WWWAssetsDir_ = DataDir_; LoadMyConfig(); InitializeSubSystemServers(); ServerApplication::initialize(self); DaemonPostInitialization(self); Types::TopicNotifyFunction F = [this](const std::string &Key,const std::string &Payload) { this->BusMessageReceived(Key, Payload); }; KafkaManager()->RegisterTopicWatcher(KafkaTopics::SERVICE_EVENTS, F); } inline void MicroService::uninitialize() { // add your own uninitialization code here ServerApplication::uninitialize(); } inline void MicroService::reinitialize(Poco::Util::Application &self) { ServerApplication::reinitialize(self); // add your own reinitialization code here } inline void MicroService::defineOptions(Poco::Util::OptionSet &options) { ServerApplication::defineOptions(options); options.addOption( Poco::Util::Option("help", "", "display help information on command line arguments") .required(false) .repeatable(false) .callback(Poco::Util::OptionCallback(this, &MicroService::handleHelp))); options.addOption( Poco::Util::Option("file", "", "specify the configuration file") .required(false) .repeatable(false) .argument("file") .callback(Poco::Util::OptionCallback(this, &MicroService::handleConfig))); options.addOption( Poco::Util::Option("debug", "", "to run in debug, set to true") .required(false) .repeatable(false) .callback(Poco::Util::OptionCallback(this, &MicroService::handleDebug))); options.addOption( Poco::Util::Option("logs", "", "specify the log directory and file (i.e. dir/file.log)") .required(false) .repeatable(false) .argument("dir") .callback(Poco::Util::OptionCallback(this, &MicroService::handleLogs))); options.addOption( Poco::Util::Option("version", "", "get the version and quit.") .required(false) .repeatable(false) .callback(Poco::Util::OptionCallback(this, &MicroService::handleVersion))); } inline void MicroService::handleHelp([[maybe_unused]] const std::string &name, [[maybe_unused]] const std::string &value) { HelpRequested_ = true; displayHelp(); stopOptionsProcessing(); } inline void MicroService::handleVersion([[maybe_unused]] const std::string &name, [[maybe_unused]] const std::string &value) { HelpRequested_ = true; std::cout << Version() << std::endl; stopOptionsProcessing(); } inline void MicroService::handleDebug([[maybe_unused]] const std::string &name, const std::string &value) { if(value == "true") DebugMode_ = true ; } inline void MicroService::handleLogs([[maybe_unused]] const std::string &name, const std::string &value) { LogDir_ = value; } inline void MicroService::handleConfig([[maybe_unused]] const std::string &name, const std::string &value) { ConfigFileName_ = value; } inline void MicroService::displayHelp() { Poco::Util::HelpFormatter helpFormatter(options()); helpFormatter.setCommand(commandName()); helpFormatter.setUsage("OPTIONS"); helpFormatter.setHeader("A " + DAEMON_APP_NAME + " implementation for TIP."); helpFormatter.format(std::cout); } inline void MicroService::InitializeSubSystemServers() { for(auto i:SubSystems_) { addSubsystem(i); } } inline void MicroService::StartSubSystemServers() { AddActivity("Starting"); for(auto i:SubSystems_) { i->Start(); } BusEventManager_ = std::make_unique(Poco::Logger::create("BusEventManager",Poco::Logger::root().getChannel(),Poco::Logger::root().getLevel())); BusEventManager_->Start(); } inline void MicroService::StopSubSystemServers() { AddActivity("Stopping"); BusEventManager_->Stop(); for(auto i=SubSystems_.rbegin(); i!=SubSystems_.rend(); ++i) { (*i)->Stop(); } } [[nodiscard]] inline std::string MicroService::CreateUUID() { static std::random_device rd; static std::mt19937_64 gen(rd()); static std::uniform_int_distribution<> dis(0, 15); static std::uniform_int_distribution<> dis2(8, 11); std::stringstream ss; int i; ss << std::hex; for (i = 0; i < 8; i++) { ss << dis(gen); } ss << "-"; for (i = 0; i < 4; i++) { ss << dis(gen); } ss << "-4"; for (i = 0; i < 3; i++) { ss << dis(gen); } ss << "-"; ss << dis2(gen); for (i = 0; i < 3; i++) { ss << dis(gen); } ss << "-"; for (i = 0; i < 12; i++) { ss << dis(gen); }; return ss.str(); } inline bool MicroService::SetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level) { try { auto P = Poco::Logger::parseLevel(Level); auto Sub = Poco::toLower(SubSystem); if (Sub == "all") { for (auto i : SubSystems_) { i->Logger().setLevel(P); } return true; } else { for (auto i : SubSystems_) { if (Sub == Poco::toLower(i->Name())) { i->Logger().setLevel(P); return true; } } } } catch (const Poco::Exception & E) { std::cerr << "Exception" << std::endl; } return false; } inline void MicroService::Reload(const std::string &Sub) { for (auto i : SubSystems_) { if (Poco::toLower(Sub) == Poco::toLower(i->Name())) { i->reinitialize(Poco::Util::Application::instance()); return; } } } inline Types::StringVec MicroService::GetSubSystems() const { Types::StringVec Result; for(auto i:SubSystems_) Result.push_back(Poco::toLower(i->Name())); return Result; } inline Types::StringPairVec MicroService::GetLogLevels() { Types::StringPairVec Result; for(auto &i:SubSystems_) { auto P = std::make_pair( i->Name(), Utils::LogLevelToString(i->GetLoggingLevel())); Result.push_back(P); } return Result; } inline const Types::StringVec & MicroService::GetLogLevelNames() { static Types::StringVec LevelNames{"none", "fatal", "critical", "error", "warning", "notice", "information", "debug", "trace" }; return LevelNames; } inline uint64_t MicroService::ConfigGetInt(const std::string &Key,uint64_t Default) { return (uint64_t) config().getInt64(Key,Default); } inline uint64_t MicroService::ConfigGetInt(const std::string &Key) { return config().getInt(Key); } inline uint64_t MicroService::ConfigGetBool(const std::string &Key,bool Default) { return config().getBool(Key,Default); } inline uint64_t MicroService::ConfigGetBool(const std::string &Key) { return config().getBool(Key); } inline std::string MicroService::ConfigGetString(const std::string &Key,const std::string & Default) { return config().getString(Key, Default); } inline std::string MicroService::ConfigGetString(const std::string &Key) { return config().getString(Key); } inline std::string MicroService::ConfigPath(const std::string &Key,const std::string & Default) { std::string R = config().getString(Key, Default); return Poco::Path::expand(R); } inline std::string MicroService::ConfigPath(const std::string &Key) { std::string R = config().getString(Key); return Poco::Path::expand(R); } inline std::string MicroService::Encrypt(const std::string &S) { if(NoBuiltInCrypto_) { return S; } return Cipher_->encryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);; } inline std::string MicroService::Decrypt(const std::string &S) { if(NoBuiltInCrypto_) { return S; } return Cipher_->decryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);; } inline std::string MicroService::MakeSystemEventMessage( const std::string & Type ) const { Poco::JSON::Object Obj; Obj.set(KafkaTopics::ServiceEvents::Fields::EVENT,Type); Obj.set(KafkaTopics::ServiceEvents::Fields::ID,ID_); Obj.set(KafkaTopics::ServiceEvents::Fields::TYPE,Poco::toLower(DAEMON_APP_NAME)); Obj.set(KafkaTopics::ServiceEvents::Fields::PUBLIC,MyPublicEndPoint_); Obj.set(KafkaTopics::ServiceEvents::Fields::PRIVATE,MyPrivateEndPoint_); Obj.set(KafkaTopics::ServiceEvents::Fields::KEY,MyHash_); Obj.set(KafkaTopics::ServiceEvents::Fields::VRSN,Version_); std::stringstream ResultText; Poco::JSON::Stringifier::stringify(Obj, ResultText); return ResultText.str(); } [[nodiscard]] inline bool MicroService::IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request) { try { auto APIKEY = Request.get("X-API-KEY"); return APIKEY == MyHash_; } catch (const Poco::Exception &E) { logger().log(E); } return false; } inline void MicroService::SavePID() { try { std::ofstream O; O.open(MicroService::instance().DataDir() + "/pidfile",std::ios::binary | std::ios::trunc); O << Poco::Process::id(); O.close(); } catch (...) { std::cout << "Could not save system ID" << std::endl; } } inline SubSystemServer::SubSystemServer(const std::string &Name, const std::string &LoggingPrefix, const std::string &SubSystemConfigPrefix): Name_(Name), LoggerPrefix_(LoggingPrefix), SubSystemConfigPrefix_(SubSystemConfigPrefix) { } inline int RESTAPI_ExtServer::Start() { Logger().information("Starting."); Server_.InitLogging(); for(const auto & Svr: ConfigServersList_) { if(MicroService::instance().NoAPISecurity()) { Logger().information(fmt::format("Starting: {}:{}. Security has been disabled for APIs.", Svr.Address(), Svr.Port())); } else { Logger().information(fmt::format("Starting: {}:{} Keyfile:{} CertFile: {}", Svr.Address(), Svr.Port(), Svr.KeyFile(),Svr.CertFile())); Svr.LogCert(Logger()); if (!Svr.RootCA().empty()) Svr.LogCas(Logger()); } Poco::Net::HTTPServerParams::Ptr Params = new Poco::Net::HTTPServerParams; Params->setMaxThreads(50); Params->setMaxQueued(200); Params->setKeepAlive(true); Params->setName("ws:xrest"); std::unique_ptr NewServer; if(MicroService::instance().NoAPISecurity()) { auto Sock{Svr.CreateSocket(Logger())}; NewServer = std::make_unique(new ExtRequestHandlerFactory, Pool_, Sock, Params); } else { auto Sock{Svr.CreateSecureSocket(Logger())}; NewServer = std::make_unique(new ExtRequestHandlerFactory, Pool_, Sock, Params); }; NewServer->start(); RESTServers_.push_back(std::move(NewServer)); } return 0; } inline int RESTAPI_IntServer::Start() { Logger().information("Starting."); Server_.InitLogging(); for(const auto & Svr: ConfigServersList_) { if(MicroService::instance().NoAPISecurity()) { Logger().information(fmt::format("Starting: {}:{}. Security has been disabled for APIs.", Svr.Address(), Svr.Port())); } else { Logger().information(fmt::format("Starting: {}:{}. Keyfile:{} CertFile: {}", Svr.Address(), Svr.Port(), Svr.KeyFile(),Svr.CertFile())); Svr.LogCert(Logger()); if (!Svr.RootCA().empty()) Svr.LogCas(Logger()); } auto Params = new Poco::Net::HTTPServerParams; Params->setMaxThreads(50); Params->setMaxQueued(200); Params->setKeepAlive(true); Params->setName("ws:irest"); std::unique_ptr NewServer; if(MicroService::instance().NoAPISecurity()) { auto Sock{Svr.CreateSocket(Logger())}; NewServer = std::make_unique(new IntRequestHandlerFactory, Pool_, Sock, Params); } else { auto Sock{Svr.CreateSecureSocket(Logger())}; NewServer = std::make_unique(new IntRequestHandlerFactory, Pool_, Sock, Params); }; NewServer->start(); RESTServers_.push_back(std::move(NewServer)); } return 0; } inline int MicroService::main([[maybe_unused]] const ArgVec &args) { MyErrorHandler ErrorHandler(*this); Poco::ErrorHandler::set(&ErrorHandler); if (!HelpRequested_) { SavePID(); Poco::Logger &logger = Poco::Logger::get(DAEMON_APP_NAME); logger.notice(fmt::format("Starting {} version {}.",DAEMON_APP_NAME, Version())); if(Poco::Net::Socket::supportsIPv6()) logger.information("System supports IPv6."); else logger.information("System does NOT support IPv6."); if (config().getBool("application.runAsDaemon", false)) { logger.information("Starting as a daemon."); } logger.information(fmt::format("System ID set to {}",ID_)); StartSubSystemServers(); waitForTerminationRequest(); StopSubSystemServers(); logger.notice(fmt::format("Stopped {}...",DAEMON_APP_NAME)); } return Application::EXIT_OK; } AppServiceRegistry::AppServiceRegistry() { FileName = MicroService::instance().DataDir() + "/registry.json"; Poco::File F(FileName); try { if(F.exists()) { std::ostringstream OS; std::ifstream IF(FileName); Poco::StreamCopier::copyStream(IF, OS); Registry_ = nlohmann::json::parse(OS.str()); } } catch (...) { Registry_ = nlohmann::json::parse("{}"); } } inline void SubSystemServer::initialize([[maybe_unused]] Poco::Util::Application &self) { auto i = 0; bool good = true; auto NewLevel = MicroService::instance().ConfigGetString("logging.level." + Name_, ""); if(NewLevel.empty()) Logger_ = std::make_unique(Poco::Logger::create(LoggerPrefix_, Poco::Logger::root().getChannel(), Poco::Logger::root().getLevel())); else Logger_ = std::make_unique(Poco::Logger::create(LoggerPrefix_, Poco::Logger::root().getChannel(), Poco::Logger::parseLevel(NewLevel))); ConfigServersList_.clear(); while (good) { std::string root{SubSystemConfigPrefix_ + ".host." + std::to_string(i) + "."}; std::string address{root + "address"}; if (MicroService::instance().ConfigGetString(address, "").empty()) { good = false; } else { std::string port{root + "port"}; std::string key{root + "key"}; std::string key_password{root + "key.password"}; std::string cert{root + "cert"}; std::string name{root + "name"}; std::string backlog{root + "backlog"}; std::string rootca{root + "rootca"}; std::string issuer{root + "issuer"}; std::string clientcas(root + "clientcas"); std::string cas{root + "cas"}; std::string level{root + "security"}; Poco::Net::Context::VerificationMode M = Poco::Net::Context::VERIFY_RELAXED; auto L = MicroService::instance().ConfigGetString(level, ""); if (L == "strict") { M = Poco::Net::Context::VERIFY_STRICT; } else if (L == "none") { M = Poco::Net::Context::VERIFY_NONE; } else if (L == "relaxed") { M = Poco::Net::Context::VERIFY_RELAXED; } else if (L == "once") M = Poco::Net::Context::VERIFY_ONCE; PropertiesFileServerEntry entry(MicroService::instance().ConfigGetString(address, ""), MicroService::instance().ConfigGetInt(port, 0), MicroService::instance().ConfigPath(key, ""), MicroService::instance().ConfigPath(cert, ""), MicroService::instance().ConfigPath(rootca, ""), MicroService::instance().ConfigPath(issuer, ""), MicroService::instance().ConfigPath(clientcas, ""), MicroService::instance().ConfigPath(cas, ""), MicroService::instance().ConfigGetString(key_password, ""), MicroService::instance().ConfigGetString(name, ""), M, (int)MicroService::instance().ConfigGetInt(backlog, 64)); ConfigServersList_.push_back(entry); i++; } } } inline int ALBHealthCheckServer::Start() { if(MicroService::instance().ConfigGetBool("alb.enable",false)) { Running_=true; Port_ = (int)MicroService::instance().ConfigGetInt("alb.port",15015); Socket_ = std::make_unique(Port_); auto Params = new Poco::Net::HTTPServerParams; Params->setName("ws:alb"); Server_ = std::make_unique(new ALBRequestHandlerFactory(Logger()), *Socket_, Params); Server_->start(); } return 0; } inline void BusEventManager::run() { Running_ = true; Utils::SetThreadName("fmwk:EventMgr"); auto Msg = MicroService::instance().MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_JOIN); KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,MicroService::instance().PrivateEndPoint(),Msg, false); while(Running_) { Poco::Thread::trySleep((unsigned long)MicroService::instance().DaemonBusTimer()); if(!Running_) break; Msg = MicroService::instance().MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE); KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,MicroService::instance().PrivateEndPoint(),Msg, false); } Msg = MicroService::instance().MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_LEAVE); KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,MicroService::instance().PrivateEndPoint(),Msg, false); }; inline void BusEventManager::Start() { if(KafkaManager()->Enabled()) { Thread_.start(*this); } } inline void BusEventManager::Stop() { if(KafkaManager()->Enabled()) { poco_information(Logger(),"Stopping..."); Running_ = false; Thread_.wakeUp(); Thread_.join(); poco_information(Logger(),"Stopped..."); } } inline void KafkaManager::initialize(Poco::Util::Application & self) { SubSystemServer::initialize(self); KafkaEnabled_ = MicroService::instance().ConfigGetBool("openwifi.kafka.enable",false); } inline void KafkaLoggerFun([[maybe_unused]] cppkafka::KafkaHandleBase & handle, int level, const std::string & facility, const std::string &message) { switch ((cppkafka::LogLevel) level) { case cppkafka::LogLevel::LogNotice: { poco_notice(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message)); } break; case cppkafka::LogLevel::LogDebug: { poco_debug(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message)); } break; case cppkafka::LogLevel::LogInfo: { poco_information(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message)); } break; case cppkafka::LogLevel::LogWarning: { poco_warning(KafkaManager()->Logger(), fmt::format("kafka-log: facility: {} message: {}",facility, message)); } break; case cppkafka::LogLevel::LogAlert: case cppkafka::LogLevel::LogCrit: { poco_critical(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message)); } break; case cppkafka::LogLevel::LogErr: case cppkafka::LogLevel::LogEmerg: default: { poco_error(KafkaManager()->Logger(),fmt::format("kafka-log: facility: {} message: {}",facility, message)); } break; } } inline void KafkaErrorFun([[maybe_unused]] cppkafka::KafkaHandleBase & handle, int error, const std::string &reason) { poco_error(KafkaManager()->Logger(),fmt::format("kafka-error: {}, reason: {}", error, reason)); } inline void AddKafkaSecurity(cppkafka::Configuration & Config) { auto CA = MicroService::instance().ConfigGetString("openwifi.kafka.ssl.ca.location",""); auto Certificate = MicroService::instance().ConfigGetString("openwifi.kafka.ssl.certificate.location",""); auto Key = MicroService::instance().ConfigGetString("openwifi.kafka.ssl.key.location",""); auto Password = MicroService::instance().ConfigGetString("openwifi.kafka.ssl.key.password",""); if(CA.empty() || Certificate.empty() || Key.empty()) return; Config.set("ssl.ca.location", CA); Config.set("ssl.certificate.location", Certificate); Config.set("ssl.key.location", Key); if(!Password.empty()) Config.set("ssl.key.password", Password); } inline void KafkaProducer::run() { Utils::SetThreadName("Kafka:Prod"); cppkafka::Configuration Config({ { "client.id", MicroService::instance().ConfigGetString("openwifi.kafka.client.id") }, { "metadata.broker.list", MicroService::instance().ConfigGetString("openwifi.kafka.brokerlist") } }); AddKafkaSecurity(Config); Config.set_log_callback(KafkaLoggerFun); Config.set_error_callback(KafkaErrorFun); KafkaManager()->SystemInfoWrapper_ = R"lit({ "system" : { "id" : )lit" + std::to_string(MicroService::instance().ID()) + R"lit( , "host" : ")lit" + MicroService::instance().PrivateEndPoint() + R"lit(" } , "payload" : )lit" ; cppkafka::Producer Producer(Config); Running_ = true; Poco::AutoPtr Note(Queue_.waitDequeueNotification()); while(Note && Running_) { try { auto Msg = dynamic_cast(Note.get()); if (Msg != nullptr) { Producer.produce( cppkafka::MessageBuilder(Msg->Topic()).key(Msg->Key()).payload(Msg->Payload())); } } catch (const cppkafka::HandleException &E) { poco_warning(KafkaManager()->Logger(),fmt::format("Caught a Kafka exception (producer): {}", E.what())); } catch( const Poco::Exception &E) { KafkaManager()->Logger().log(E); } catch (...) { poco_error(KafkaManager()->Logger(),"std::exception"); } Note = Queue_.waitDequeueNotification(); } } inline void KafkaConsumer::run() { Utils::SetThreadName("Kafka:Cons"); cppkafka::Configuration Config({ { "client.id", MicroService::instance().ConfigGetString("openwifi.kafka.client.id") }, { "metadata.broker.list", MicroService::instance().ConfigGetString("openwifi.kafka.brokerlist") }, { "group.id", MicroService::instance().ConfigGetString("openwifi.kafka.group.id") }, { "enable.auto.commit", MicroService::instance().ConfigGetBool("openwifi.kafka.auto.commit",false) }, { "auto.offset.reset", "latest" } , { "enable.partition.eof", false } }); AddKafkaSecurity(Config); Config.set_log_callback(KafkaLoggerFun); Config.set_error_callback(KafkaErrorFun); cppkafka::TopicConfiguration topic_config = { { "auto.offset.reset", "smallest" } }; // Now configure it to be the default topic config Config.set_default_topic_configuration(topic_config); cppkafka::Consumer Consumer(Config); Consumer.set_assignment_callback([](cppkafka::TopicPartitionList& partitions) { if(!partitions.empty()) { KafkaManager()->Logger().information(fmt::format("Partition assigned: {}...", partitions.front().get_partition())); } }); Consumer.set_revocation_callback([](const cppkafka::TopicPartitionList& partitions) { if(!partitions.empty()) { KafkaManager()->Logger().information(fmt::format("Partition revocation: {}...", partitions.front().get_partition())); } }); bool AutoCommit = MicroService::instance().ConfigGetBool("openwifi.kafka.auto.commit",false); auto BatchSize = MicroService::instance().ConfigGetInt("openwifi.kafka.consumer.batchsize",20); Types::StringVec Topics; KafkaManager()->Topics(Topics); Consumer.subscribe(Topics); Running_ = true; while(Running_) { try { std::vector MsgVec = Consumer.poll_batch(BatchSize, std::chrono::milliseconds(100)); for(auto const &Msg:MsgVec) { if (!Msg) continue; if (Msg.get_error()) { if (!Msg.is_eof()) { poco_error(KafkaManager()->Logger(),fmt::format("Error: {}", Msg.get_error().to_string())); } if(!AutoCommit) Consumer.async_commit(Msg); continue; } KafkaManager()->Dispatch(Msg.get_topic(), Msg.get_key(),Msg.get_payload() ); if (!AutoCommit) Consumer.async_commit(Msg); } } catch (const cppkafka::HandleException &E) { poco_warning(KafkaManager()->Logger(),fmt::format("Caught a Kafka exception (consumer): {}", E.what())); } catch (const Poco::Exception &E) { KafkaManager()->Logger().log(E); } catch (...) { poco_error(KafkaManager()->Logger(),"std::exception"); } } Consumer.unsubscribe(); } inline void RESTAPI_ExtServer::reinitialize([[maybe_unused]] Poco::Util::Application &self) { MicroService::instance().LoadConfigurationFile(); Logger().information("Reinitializing."); Stop(); Start(); } void RESTAPI_IntServer::reinitialize([[maybe_unused]] Poco::Util::Application &self) { MicroService::instance().LoadConfigurationFile(); Logger().information("Reinitializing."); Stop(); Start(); } class RESTAPI_system_command : public RESTAPIHandler { public: RESTAPI_system_command(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer & Server, uint64_t TransactionId, bool Internal) : RESTAPIHandler(bindings, L, std::vector{Poco::Net::HTTPRequest::HTTP_POST, Poco::Net::HTTPRequest::HTTP_GET, Poco::Net::HTTPRequest::HTTP_OPTIONS}, Server, TransactionId, Internal) {} static auto PathName() { return std::list{"/api/v1/system"};} inline void DoGet() { std::string Arg; if(HasParameter("command",Arg) && Arg=="info") { Poco::JSON::Object Answer; Answer.set(RESTAPI::Protocol::VERSION, MicroService::instance().Version()); Answer.set(RESTAPI::Protocol::UPTIME, MicroService::instance().uptime().totalSeconds()); Answer.set(RESTAPI::Protocol::START, MicroService::instance().startTime().epochTime()); Answer.set(RESTAPI::Protocol::OS, Poco::Environment::osName()); Answer.set(RESTAPI::Protocol::PROCESSORS, Poco::Environment::processorCount()); Answer.set(RESTAPI::Protocol::HOSTNAME, Poco::Environment::nodeName()); Answer.set(RESTAPI::Protocol::UI, MicroService::instance().GetUIURI()); Poco::JSON::Array Certificates; auto SubSystems = MicroService::instance().GetFullSubSystems(); std::set CertNames; for(const auto &i:SubSystems) { auto Hosts=i->HostSize(); for(uint64_t j=0;jHost(j).CertFile(); if(!CertFileName.empty()) { Poco::File F1(CertFileName); if(F1.exists()) { auto InsertResult = CertNames.insert(CertFileName); if(InsertResult.second) { Poco::JSON::Object Inner; Poco::Path F(CertFileName); Inner.set("filename", F.getFileName()); Poco::Crypto::X509Certificate C(CertFileName); auto ExpiresOn = C.expiresOn(); Inner.set("expiresOn", ExpiresOn.timestamp().epochTime()); Certificates.add(Inner); } } } } } Answer.set("certificates", Certificates); return ReturnObject(Answer); } if(GetBoolParameter("extraConfiguration")) { Poco::JSON::Object Answer; MicroService::instance().GetExtraConfiguration(Answer); return ReturnObject(Answer); } BadRequest(RESTAPI::Errors::InvalidCommand); } inline void DoPost() final { const auto & Obj = ParsedBody_; if (Obj->has(RESTAPI::Protocol::COMMAND)) { auto Command = Poco::toLower(Obj->get(RESTAPI::Protocol::COMMAND).toString()); if (Command == RESTAPI::Protocol::SETLOGLEVEL) { if (Obj->has(RESTAPI::Protocol::SUBSYSTEMS) && Obj->isArray(RESTAPI::Protocol::SUBSYSTEMS)) { auto ParametersBlock = Obj->getArray(RESTAPI::Protocol::SUBSYSTEMS); for (const auto &i : *ParametersBlock) { Poco::JSON::Parser pp; auto InnerObj = pp.parse(i).extract(); if (InnerObj->has(RESTAPI::Protocol::TAG) && InnerObj->has(RESTAPI::Protocol::VALUE)) { auto Name = GetS(RESTAPI::Protocol::TAG, InnerObj); auto Value = GetS(RESTAPI::Protocol::VALUE, InnerObj); MicroService::instance().SetSubsystemLogLevel(Name, Value); Logger_.information( fmt::format("Setting log level for {} at {}", Name, Value)); } } return OK(); } } else if (Command == RESTAPI::Protocol::GETLOGLEVELS) { auto CurrentLogLevels = MicroService::instance().GetLogLevels(); Poco::JSON::Object Result; Poco::JSON::Array Array; for (auto &[Name, Level] : CurrentLogLevels) { Poco::JSON::Object Pair; Pair.set(RESTAPI::Protocol::TAG, Name); Pair.set(RESTAPI::Protocol::VALUE, Level); Array.add(Pair); } Result.set(RESTAPI::Protocol::TAGLIST, Array); return ReturnObject(Result); } else if (Command == RESTAPI::Protocol::GETLOGLEVELNAMES) { Poco::JSON::Object Result; Poco::JSON::Array LevelNamesArray; const Types::StringVec &LevelNames = MicroService::instance().GetLogLevelNames(); for (const auto &i : LevelNames) LevelNamesArray.add(i); Result.set(RESTAPI::Protocol::LIST, LevelNamesArray); return ReturnObject(Result); } else if (Command == RESTAPI::Protocol::GETSUBSYSTEMNAMES) { Poco::JSON::Object Result; Poco::JSON::Array LevelNamesArray; const Types::StringVec &SubSystemNames = MicroService::instance().GetSubSystems(); for (const auto &i : SubSystemNames) LevelNamesArray.add(i); Result.set(RESTAPI::Protocol::LIST, LevelNamesArray); return ReturnObject(Result); } else if (Command == RESTAPI::Protocol::STATS) { } else if (Command == RESTAPI::Protocol::RELOAD) { if (Obj->has(RESTAPI::Protocol::SUBSYSTEMS) && Obj->isArray(RESTAPI::Protocol::SUBSYSTEMS)) { auto SubSystems = Obj->getArray(RESTAPI::Protocol::SUBSYSTEMS); std::vector Names; for (const auto &i : *SubSystems) Names.push_back(i.toString()); std::thread ReloadThread([Names](){ std::this_thread::sleep_for(10000ms); for(const auto &i:Names) { if(i=="daemon") MicroService::instance().Reload(); else MicroService::instance().Reload(i); } }); ReloadThread.detach(); } return OK(); } } else { return BadRequest(RESTAPI::Errors::InvalidCommand); } BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); } void DoPut() final {}; void DoDelete() final {}; }; inline Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestGet::Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken) { try { auto Services = MicroService::instance().GetServices(Type_); for(auto const &Svc:Services) { Poco::URI URI(Svc.PrivateEndPoint); auto Secure = (URI.getScheme() == "https"); URI.setPath(EndPoint_); for (const auto &qp : QueryData_) URI.addQueryParameter(qp.first, qp.second); std::string Path(URI.getPathAndQuery()); Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_GET, Path, Poco::Net::HTTPMessage::HTTP_1_1); poco_debug(Poco::Logger::get("REST-CALLER-GET"),fmt::format(" {}", URI.toString())); if(BearerToken.empty()) { Request.add("X-API-KEY", Svc.AccessKey); Request.add("X-INTERNAL-NAME", MicroService::instance().PublicEndPoint()); } else { // Authorization: Bearer ${token} Request.add("Authorization", "Bearer " + BearerToken); } if(Secure) { Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort()); Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000)); Session.sendRequest(Request); Poco::Net::HTTPResponse Response; std::istream &is = Session.receiveResponse(Response); if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) { Poco::JSON::Parser P; ResponseObject = P.parse(is).extract(); } return Response.getStatus(); } else { Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort()); Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000)); Session.sendRequest(Request); Poco::Net::HTTPResponse Response; std::istream &is = Session.receiveResponse(Response); if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) { Poco::JSON::Parser P; ResponseObject = P.parse(is).extract(); } return Response.getStatus(); } } } catch (const Poco::Exception &E) { Poco::Logger::get("REST-CALLER-GET").log(E); } return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT; } inline Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestPut::Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken) { try { auto Services = MicroService::instance().GetServices(Type_); for(auto const &Svc:Services) { Poco::URI URI(Svc.PrivateEndPoint); auto Secure = (URI.getScheme() == "https"); URI.setPath(EndPoint_); for (const auto &qp : QueryData_) URI.addQueryParameter(qp.first, qp.second); poco_debug(Poco::Logger::get("REST-CALLER-PUT"),fmt::format("{}", URI.toString())); std::string Path(URI.getPathAndQuery()); Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_PUT, Path, Poco::Net::HTTPMessage::HTTP_1_1); std::ostringstream obody; Poco::JSON::Stringifier::stringify(Body_,obody); Request.setContentType("application/json"); Request.setContentLength(obody.str().size()); if(BearerToken.empty()) { Request.add("X-API-KEY", Svc.AccessKey); Request.add("X-INTERNAL-NAME", MicroService::instance().PublicEndPoint()); } else { // Authorization: Bearer ${token} Request.add("Authorization", "Bearer " + BearerToken); } if(Secure) { Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort()); Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000)); std::ostream &os = Session.sendRequest(Request); os << obody.str(); Poco::Net::HTTPResponse Response; std::istream &is = Session.receiveResponse(Response); if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) { Poco::JSON::Parser P; ResponseObject = P.parse(is).extract(); } else { Poco::JSON::Parser P; ResponseObject = P.parse(is).extract(); } return Response.getStatus(); } else { Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort()); Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000)); std::ostream &os = Session.sendRequest(Request); os << obody.str(); Poco::Net::HTTPResponse Response; std::istream &is = Session.receiveResponse(Response); if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) { Poco::JSON::Parser P; ResponseObject = P.parse(is).extract(); } else { Poco::JSON::Parser P; ResponseObject = P.parse(is).extract(); } return Response.getStatus(); } } } catch (const Poco::Exception &E) { Poco::Logger::get("REST-CALLER-PUT").log(E); } return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT; } inline Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestPost::Do(Poco::JSON::Object::Ptr &ResponseObject, const std::string & BearerToken) { try { auto Services = MicroService::instance().GetServices(Type_); for(auto const &Svc:Services) { Poco::URI URI(Svc.PrivateEndPoint); auto Secure = (URI.getScheme() == "https"); URI.setPath(EndPoint_); for (const auto &qp : QueryData_) URI.addQueryParameter(qp.first, qp.second); poco_debug(Poco::Logger::get("REST-CALLER-POST"),fmt::format(" {}", URI.toString())); std::string Path(URI.getPathAndQuery()); Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_POST, Path, Poco::Net::HTTPMessage::HTTP_1_1); std::ostringstream obody; Poco::JSON::Stringifier::stringify(Body_,obody); Request.setContentType("application/json"); Request.setContentLength(obody.str().size()); if(BearerToken.empty()) { Request.add("X-API-KEY", Svc.AccessKey); Request.add("X-INTERNAL-NAME", MicroService::instance().PublicEndPoint()); } else { // Authorization: Bearer ${token} Request.add("Authorization", "Bearer " + BearerToken); } if(Secure) { Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort()); Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000)); std::ostream &os = Session.sendRequest(Request); os << obody.str(); Poco::Net::HTTPResponse Response; std::istream &is = Session.receiveResponse(Response); if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) { Poco::JSON::Parser P; ResponseObject = P.parse(is).extract(); } else { Poco::JSON::Parser P; ResponseObject = P.parse(is).extract(); } return Response.getStatus(); } else { Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort()); Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000)); std::ostream &os = Session.sendRequest(Request); os << obody.str(); Poco::Net::HTTPResponse Response; std::istream &is = Session.receiveResponse(Response); if (Response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) { Poco::JSON::Parser P; ResponseObject = P.parse(is).extract(); } else { Poco::JSON::Parser P; ResponseObject = P.parse(is).extract(); } return Response.getStatus(); } } } catch (const Poco::Exception &E) { Poco::Logger::get("REST-CALLER-POST").log(E); } return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT; } inline Poco::Net::HTTPServerResponse::HTTPStatus OpenAPIRequestDelete::Do(const std::string & BearerToken) { try { auto Services = MicroService::instance().GetServices(Type_); for(auto const &Svc:Services) { Poco::URI URI(Svc.PrivateEndPoint); auto Secure = (URI.getScheme() == "https"); URI.setPath(EndPoint_); for (const auto &qp : QueryData_) URI.addQueryParameter(qp.first, qp.second); poco_debug(Poco::Logger::get("REST-CALLER-DELETE"),fmt::format(" {}", URI.toString())); std::string Path(URI.getPathAndQuery()); Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_DELETE, Path, Poco::Net::HTTPMessage::HTTP_1_1); if(BearerToken.empty()) { Request.add("X-API-KEY", Svc.AccessKey); Request.add("X-INTERNAL-NAME", MicroService::instance().PublicEndPoint()); } else { // Authorization: Bearer ${token} Request.add("Authorization", "Bearer " + BearerToken); } if(Secure) { Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort()); Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000)); Session.sendRequest(Request); Poco::Net::HTTPResponse Response; Session.receiveResponse(Response); return Response.getStatus(); } else { Poco::Net::HTTPClientSession Session(URI.getHost(), URI.getPort()); Session.setTimeout(Poco::Timespan(msTimeout_ / 1000, msTimeout_ % 1000)); Session.sendRequest(Request); Poco::Net::HTTPResponse Response; Session.receiveResponse(Response); return Response.getStatus(); } } } catch (const Poco::Exception &E) { Poco::Logger::get("REST-CALLER-DELETE").log(E); } return Poco::Net::HTTPServerResponse::HTTP_GATEWAY_TIMEOUT; } inline void RESTAPI_GenericServer::InitLogging() { std::string Public = MicroService::instance().ConfigGetString("apilogging.public.methods","PUT,POST,DELETE"); SetFlags(true, Public); std::string Private = MicroService::instance().ConfigGetString("apilogging.private.methods","PUT,POST,DELETE"); SetFlags(false, Private); std::string PublicBadTokens = MicroService::instance().ConfigGetString("apilogging.public.badtokens.methods",""); LogBadTokens_[0] = (Poco::icompare(PublicBadTokens,"true")==0); std::string PrivateBadTokens = MicroService::instance().ConfigGetString("apilogging.private.badtokens.methods",""); LogBadTokens_[1] = (Poco::icompare(PrivateBadTokens,"true")==0); } #ifdef TIP_SECURITY_SERVICE [[nodiscard]] bool AuthServiceIsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired , bool Sub ); #endif inline bool RESTAPIHandler::IsAuthorized( bool & Expired , [[maybe_unused]] bool & Contacted , bool Sub ) { if(Internal_ && Request->has("X-INTERNAL-NAME")) { auto Allowed = MicroService::instance().IsValidAPIKEY(*Request); Contacted = true; if(!Allowed) { if(Server_.LogBadTokens(false)) { poco_debug(Logger_,fmt::format("I-REQ-DENIED({}): Method={} Path={}", Utils::FormatIPv6(Request->clientAddress().toString()), Request->getMethod(), Request->getURI())); } } else { auto Id = Request->get("X-INTERNAL-NAME", "unknown"); REST_Requester_ = Id; if(Server_.LogIt(Request->getMethod(),true)) { poco_debug(Logger_,fmt::format("I-REQ-ALLOWED({}): User='{}' Method={} Path={}", Utils::FormatIPv6(Request->clientAddress().toString()), Id, Request->getMethod(), Request->getURI())); } } return Allowed; } else { if (SessionToken_.empty()) { try { Poco::Net::OAuth20Credentials Auth(*Request); if (Auth.getScheme() == "Bearer") { SessionToken_ = Auth.getBearerToken(); } } catch (const Poco::Exception &E) { Logger_.log(E); } } #ifdef TIP_SECURITY_SERVICE if (AuthServiceIsAuthorized(*Request, SessionToken_, UserInfo_, Expired, Sub)) { #else if (AuthClient()->IsAuthorized( SessionToken_, UserInfo_, Expired, Contacted, Sub)) { #endif REST_Requester_ = UserInfo_.userinfo.email; if(Server_.LogIt(Request->getMethod(),true)) { poco_debug(Logger_,fmt::format("X-REQ-ALLOWED({}): User='{}@{}' Method={} Path={}", UserInfo_.userinfo.email, Utils::FormatIPv6(Request->clientAddress().toString()), Request->clientAddress().toString(), Request->getMethod(), Request->getURI())); } return true; } else { if(Server_.LogBadTokens(true)) { poco_debug(Logger_,fmt::format("X-REQ-DENIED({}): Method={} Path={}", Utils::FormatIPv6(Request->clientAddress().toString()), Request->getMethod(), Request->getURI())); } } return false; } } inline MicroService * MicroService::instance_ = nullptr; template struct WebSocketNotification { inline static uint64_t xid=1; uint64_t notification_id=++xid; std::string type; ContentStruct content; void to_json(Poco::JSON::Object &Obj) const; bool from_json(const Poco::JSON::Object::Ptr &Obj); }; template void WebSocketNotification::to_json(Poco::JSON::Object &Obj) const { RESTAPI_utils::field_to_json(Obj,"notification_id",notification_id); RESTAPI_utils::field_to_json(Obj,"type",type); RESTAPI_utils::field_to_json(Obj,"content",content); } template bool WebSocketNotification::from_json(const Poco::JSON::Object::Ptr &Obj) { try { RESTAPI_utils::field_from_json(Obj,"notification_id",notification_id); RESTAPI_utils::field_from_json(Obj,"content",content); RESTAPI_utils::field_from_json(Obj,"type",type); return true; } catch(...) { } return false; } class WebSocketClientProcessor { public: virtual void Processor(const Poco::JSON::Object::Ptr &O, std::string &Answer, bool &Done ) = 0; private: }; /* class MyParallelSocketReactor { public: explicit MyParallelSocketReactor(uint32_t NumReactors = 8); ~MyParallelSocketReactor(); Poco::Net::SocketReactor &Reactor(); private: uint32_t NumReactors_; Poco::Net::SocketReactor *Reactors_; Poco::ThreadPool ReactorPool_; }; */ class WebSocketClient; class WebSocketClientServer : public SubSystemServer, Poco::Runnable { public: static auto instance() { static auto instance_ = new WebSocketClientServer; return instance_; } int Start() override; void Stop() override; void run() override; // MyParallelSocketReactor &ReactorPool(); Poco::Net::SocketReactor & Reactor() { return Reactor_; } void NewClient(Poco::Net::WebSocket &WS, const std::string &Id, const std::string &UserName); bool Register(WebSocketClient *Client, const std::string &Id); void SetProcessor(WebSocketClientProcessor *F); void UnRegister(const std::string &Id); void SetUser(const std::string &Id, const std::string &UserId); [[nodiscard]] inline bool GeoCodeEnabled() const { return GeoCodeEnabled_; } [[nodiscard]] inline std::string GoogleApiKey() const { return GoogleApiKey_; } [[nodiscard]] bool Send(const std::string &Id, const std::string &Payload); template bool SendUserNotification(const std::string &userName, const WebSocketNotification &Notification) { Poco::JSON::Object Payload; Notification.to_json(Payload); Poco::JSON::Object Msg; Msg.set("notification",Payload); std::ostringstream OO; Msg.stringify(OO); return SendToUser(userName,OO.str()); } template void SendNotification(const WebSocketNotification &Notification) { Poco::JSON::Object Payload; Notification.to_json(Payload); Poco::JSON::Object Msg; Msg.set("notification",Payload); std::ostringstream OO; Msg.stringify(OO); SendToAll(OO.str()); } [[nodiscard]] bool SendToUser(const std::string &userName, const std::string &Payload); void SendToAll(const std::string &Payload); private: mutable std::atomic_bool Running_ = false; Poco::Thread Thr_; // std::unique_ptr ReactorPool_; Poco::Net::SocketReactor Reactor_; Poco::Thread ReactorThread_; bool GeoCodeEnabled_ = false; std::string GoogleApiKey_; std::map> Clients_; WebSocketClientProcessor *Processor_ = nullptr; WebSocketClientServer() noexcept; }; inline auto WebSocketClientServer() { return WebSocketClientServer::instance(); } class WebSocketClient { public: explicit WebSocketClient(Poco::Net::WebSocket &WS, const std::string &Id, const std::string &UserName, Poco::Logger &L, WebSocketClientProcessor *Processor); virtual ~WebSocketClient(); [[nodiscard]] inline const std::string &Id(); [[nodiscard]] Poco::Logger &Logger(); inline bool Send(const std::string &Payload); private: std::unique_ptr WS_; Poco::Net::SocketReactor &Reactor_; std::string Id_; std::string UserName_; Poco::Logger &Logger_; std::atomic_bool Authenticated_ = false; SecurityObjects::UserInfoAndPolicy UserInfo_; WebSocketClientProcessor *Processor_ = nullptr; void OnSocketReadable(const Poco::AutoPtr &pNf); void OnSocketShutdown(const Poco::AutoPtr &pNf); void OnSocketError(const Poco::AutoPtr &pNf); }; inline void WebSocketClientServer::NewClient(Poco::Net::WebSocket & WS, const std::string &Id, const std::string &UserName ) { std::lock_guard G(Mutex_); auto Client = new WebSocketClient(WS,Id,UserName,Logger(), Processor_); Clients_[Id] = std::make_pair(Client,""); } inline bool WebSocketClientServer::Register( WebSocketClient * Client, const std::string &Id) { std::lock_guard G(Mutex_); Clients_[Id] = std::make_pair(Client,""); return true; } inline void WebSocketClientServer::SetProcessor( WebSocketClientProcessor * F) { Processor_ = F; } inline void WebSocketClientServer::UnRegister(const std::string &Id) { std::lock_guard G(Mutex_); Clients_.erase(Id); } inline void WebSocketClientServer::SetUser(const std::string &Id, const std::string &UserId) { std::lock_guard G(Mutex_); auto it=Clients_.find(Id); if(it!=Clients_.end()) { Clients_[Id] = std::make_pair(it->second.first,UserId); } } [[nodiscard]] inline bool SendToUser(const std::string &userName, const std::string &Payload); inline WebSocketClientServer::WebSocketClientServer() noexcept: SubSystemServer("WebSocketClientServer", "UI-WSCLNT-SVR", "websocketclients") { } inline void WebSocketClientServer::run() { Running_ = true ; Utils::SetThreadName("ws:uiclnt-svr"); while(Running_) { Poco::Thread::trySleep(2000); if(!Running_) break; } }; inline int WebSocketClientServer::Start() { GoogleApiKey_ = MicroService::instance().ConfigGetString("google.apikey",""); GeoCodeEnabled_ = !GoogleApiKey_.empty(); // ReactorPool_ = std::make_unique(); ReactorThread_.start(Reactor_); Thr_.start(*this); return 0; }; inline void WebSocketClientServer::Stop() { if(Running_) { Reactor_.stop(); ReactorThread_.join(); Running_ = false; Thr_.wakeUp(); Thr_.join(); } }; inline void WebSocketClient::OnSocketError([[maybe_unused]] const Poco::AutoPtr &pNf) { delete this; } inline bool WebSocketClientServer::Send(const std::string &Id, const std::string &Payload) { std::lock_guard G(Mutex_); auto It = Clients_.find(Id); if(It!=Clients_.end()) return It->second.first->Send(Payload); return false; } inline bool WebSocketClientServer::SendToUser(const std::string &UserName, const std::string &Payload) { std::lock_guard G(Mutex_); uint64_t Sent=0; for(const auto &client:Clients_) { if(client.second.second == UserName) { try { if (client.second.first->Send(Payload)) Sent++; } catch (...) { return false; } } } return Sent>0; } inline void WebSocketClientServer::SendToAll(const std::string &Payload) { std::lock_guard G(Mutex_); for(const auto &client:Clients_) { try { client.second.first->Send(Payload); } catch (...) { } } } inline void WebSocketClient::OnSocketReadable([[maybe_unused]] const Poco::AutoPtr &pNf) { int flags; int n; bool Done=false; try { Poco::Buffer IncomingFrame(0); n = WS_->receiveFrame(IncomingFrame, flags); auto Op = flags & Poco::Net::WebSocket::FRAME_OP_BITMASK; if (n == 0) { poco_warning(Logger(),Poco::format("CLOSE(%s): %s UI Client is closing WS connection.", Id_, UserName_)); return delete this; } switch (Op) { case Poco::Net::WebSocket::FRAME_OP_PING: { WS_->sendFrame("", 0, (int)Poco::Net::WebSocket::FRAME_OP_PONG | (int)Poco::Net::WebSocket::FRAME_FLAG_FIN); } break; case Poco::Net::WebSocket::FRAME_OP_PONG: { } break; case Poco::Net::WebSocket::FRAME_OP_CLOSE: { poco_warning(Logger(),Poco::format("CLOSE(%s): %s UI Client is closing WS connection.", Id_, UserName_)); Done = true; } break; case Poco::Net::WebSocket::FRAME_OP_TEXT: { IncomingFrame.append(0); if (!Authenticated_) { std::string Frame{IncomingFrame.begin()}; auto Tokens = Utils::Split(Frame, ':'); bool Expired = false, Contacted = false; if (Tokens.size() == 2 && AuthClient()->IsAuthorized(Tokens[1], UserInfo_, Expired, Contacted)) { Authenticated_ = true; UserName_ = UserInfo_.userinfo.email; poco_warning(Logger(),Poco::format("START(%s): %s UI Client is starting WS connection.", Id_, UserName_)); std::string S{"Welcome! Bienvenue! Bienvenidos!"}; WS_->sendFrame(S.c_str(), S.size()); WebSocketClientServer()->SetUser(Id_, UserInfo_.userinfo.email); } else { std::string S{"Invalid token. Closing connection."}; WS_->sendFrame(S.c_str(), S.size()); Done = true; } } else { try { Poco::JSON::Parser P; auto Obj = P.parse(IncomingFrame.begin()).extract(); std::string Answer; if (Processor_ != nullptr) Processor_->Processor(Obj, Answer, Done); if (!Answer.empty()) WS_->sendFrame(Answer.c_str(), (int)Answer.size()); else { WS_->sendFrame("{}", 2); } } catch (const Poco::JSON::JSONException &E) { Logger().log(E); Done=true; } } } break; default: { } } } catch (...) { Done=true; } if(Done) { delete this; } } inline void WebSocketClient::OnSocketShutdown([[maybe_unused]] const Poco::AutoPtr &pNf) { delete this; } inline WebSocketClient::WebSocketClient( Poco::Net::WebSocket & WS , const std::string &Id, const std::string &UserName, Poco::Logger & L, WebSocketClientProcessor * Processor) : Reactor_(WebSocketClientServer()->Reactor()), Id_(Id), UserName_(UserName), Logger_(L), Processor_(Processor) { try { WS_ = std::make_unique(WS); Reactor_.addEventHandler(*WS_, Poco::NObserver( *this, &WebSocketClient::OnSocketReadable)); Reactor_.addEventHandler(*WS_, Poco::NObserver( *this, &WebSocketClient::OnSocketShutdown)); Reactor_.addEventHandler(*WS_, Poco::NObserver( *this, &WebSocketClient::OnSocketError)); WS_->setNoDelay(true); WS_->setKeepAlive(true); WS_->setBlocking(false); } catch (...) { delete this; } } inline WebSocketClient::~WebSocketClient() { try { WebSocketClientServer()->UnRegister(Id_); Reactor_.removeEventHandler(*WS_, Poco::NObserver(*this,&WebSocketClient::OnSocketReadable)); Reactor_.removeEventHandler(*WS_, Poco::NObserver(*this,&WebSocketClient::OnSocketShutdown)); Reactor_.removeEventHandler(*WS_, Poco::NObserver(*this,&WebSocketClient::OnSocketError)); (*WS_).shutdown(); (*WS_).close(); WebSocketClientServer()->UnRegister(Id_); } catch(...) { } } [[nodiscard]] inline const std::string & WebSocketClient::Id() { return Id_; }; [[nodiscard]] inline Poco::Logger & WebSocketClient::Logger() { return Logger_; } [[nodiscard]] inline bool WebSocketClient::Send(const std::string &Payload) { try { WS_->sendFrame(Payload.c_str(),Payload.size()); return true; } catch (...) { } return false; } class RESTAPI_webSocketServer : public RESTAPIHandler { public: inline RESTAPI_webSocketServer(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) : RESTAPIHandler(bindings, L, std::vector{ Poco::Net::HTTPRequest::HTTP_GET, Poco::Net::HTTPRequest::HTTP_OPTIONS}, Server, TransactionId, Internal,false) {} static auto PathName() { return std::list{"/api/v1/ws"};} void DoGet() final; void DoDelete() final {}; void DoPost() final {}; void DoPut() final {}; private: }; inline void RESTAPI_webSocketServer::DoGet() { try { if(Request->find("Upgrade") != Request->end() && Poco::icompare((*Request)["Upgrade"], "websocket") == 0) { try { Poco::Net::WebSocket WS(*Request, *Response); auto Id = MicroService::CreateUUID(); WebSocketClientServer()->NewClient(WS,Id,UserInfo_.userinfo.email); } catch (...) { std::cout << "Cannot create websocket client..." << std::endl; } } } catch(...) { std::cout << "Cannot upgrade connection..." << std::endl; } } } namespace OpenWifi::Utils { [[nodiscard]] inline uint64_t GetSystemId() { uint64_t ID=0; if(!AppServiceRegistry().Get("systemid",ID)) { return InitializeSystemId(); } return ID; } } namespace OpenWifi::CIDR { static bool cidr_match(const in_addr &addr, const in_addr &net, uint8_t bits) { if (bits == 0) { return true; } return !((addr.s_addr ^ net.s_addr) & htonl(0xFFFFFFFFu << (32 - bits))); } static bool cidr6_match(const in6_addr &address, const in6_addr &network, uint8_t bits) { #ifdef __linux__ const uint32_t *a = address.s6_addr32; const uint32_t *n = network.s6_addr32; #else const uint32_t *a = address.__u6_addr.__u6_addr32; const uint32_t *n = network.__u6_addr.__u6_addr32; #endif int bits_whole, bits_incomplete; bits_whole = bits >> 5; // number of whole u32 bits_incomplete = bits & 0x1F; // number of bits in incomplete u32 if (bits_whole) { if (memcmp(a, n, bits_whole << 2)!=0) { return false; } } if (bits_incomplete) { uint32_t mask = htonl((0xFFFFFFFFu) << (32 - bits_incomplete)); if ((a[bits_whole] ^ n[bits_whole]) & mask) { return false; } } return true; } static bool ConvertStringToLong(const char *S, unsigned long &L) { char *end; L = std::strtol(S,&end,10); return end != S; } static bool CidrIPinRange(const Poco::Net::IPAddress &IP, const std::string &Range) { Poco::StringTokenizer TimeTokens(Range,"/",Poco::StringTokenizer::TOK_TRIM); Poco::Net::IPAddress RangeIP; if(Poco::Net::IPAddress::tryParse(TimeTokens[0],RangeIP)) { if(TimeTokens.count()==2) { if (RangeIP.family() == Poco::Net::IPAddress::IPv4) { unsigned long MaskLength; if (ConvertStringToLong(TimeTokens[1].c_str(), MaskLength)) { return cidr_match(*static_cast(RangeIP.addr()), *static_cast(IP.addr()), MaskLength); } } else if (RangeIP.family() == Poco::Net::IPAddress::IPv6) { unsigned long MaskLength; if (ConvertStringToLong(TimeTokens[1].c_str(), MaskLength)) { return cidr6_match(*static_cast(RangeIP.addr()), *static_cast(IP.addr()), MaskLength); } } } return false; } return false; } // // Ranges can be a single IP, of IP1-IP2, of A set of IPs: IP1,IP2,IP3, or a cidr IP/24 // These can work for IPv6 too... // static bool ValidateRange(const std::string &R) { auto Tokens = Poco::StringTokenizer(R,"-"); if(Tokens.count()==2) { Poco::Net::IPAddress a,b; if(!Poco::Net::IPAddress::tryParse(Tokens[0],a) && Poco::Net::IPAddress::tryParse(Tokens[1],b)) return false; return a.family() == b.family(); } Tokens = Poco::StringTokenizer(R,","); if(Tokens.count()>1) { return std::all_of(Tokens.begin(), Tokens.end(), [](const std::string &A) { Poco::Net::IPAddress a; return Poco::Net::IPAddress::tryParse(A,a); } ); } Tokens = Poco::StringTokenizer(R,"/"); if(Tokens.count()==2) { Poco::Net::IPAddress a; if(!Poco::Net::IPAddress::tryParse(Tokens[0],a)) return false; if(std::atoi(Tokens[1].c_str())==0) return false; return true; } Poco::Net::IPAddress a; return Poco::Net::IPAddress::tryParse(R,a); } static bool IpInRange(const Poco::Net::IPAddress & target, const std::string & R) { auto Tokens = Poco::StringTokenizer(R,"-"); if(Tokens.count()==2) { auto a = Poco::Net::IPAddress::parse(Tokens[0]); auto b = Poco::Net::IPAddress::parse(Tokens[1]); if(target.family() != a.family()) return false; return (a<=target && b>=target); } Tokens = Poco::StringTokenizer(R,","); if(Tokens.count()>1) { return std::any_of(Tokens.begin(), Tokens.end(), [target](const std::string &Element) { return Poco::Net::IPAddress::parse(Element) == target ; }); } Tokens = Poco::StringTokenizer(R,"/"); if(Tokens.count()==2) { return CidrIPinRange(target,R); } return Poco::Net::IPAddress::parse(R)==target; } [[nodiscard]] inline bool IpInRanges(const std::string &IP, const Types::StringVec &R) { Poco::Net::IPAddress Target; if(!Poco::Net::IPAddress::tryParse(IP,Target)) return false; return std::any_of(cbegin(R),cend(R),[Target](const std::string &i) { return IpInRange(Target,i); }); } [[nodiscard]] inline bool ValidateIpRanges(const Types::StringVec & Ranges) { return std::all_of(cbegin(Ranges), cend(Ranges), ValidateRange); } }