diff --git a/net/nginx-util/Makefile b/net/nginx-util/Makefile new file mode 100644 index 000000000..4730b2d1a --- /dev/null +++ b/net/nginx-util/Makefile @@ -0,0 +1,70 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=nginx-util +PKG_VERSION:=1.0 +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +define Package/nginx-util + SECTION:=net + CATEGORY:=Network + SUBMENU:=Web Servers/Proxies + TITLE:=Builder of LAN listen directives for Nginx + DEPENDS:=+libstdcpp +libubus +libubox +libpthread + PROVIDES:=nginx-util +endef + +define Package/nginx-ssl-util/default + $(Package/nginx-util) + TITLE+= and manager of its SSL certificates + DEPENDS+=+libopenssl +endef + +define Package/nginx-ssl-util-nopcre + $(Package/nginx-ssl-util/default) + TITLE+= (using ) +endef + +define Package/nginx-ssl-util + $(Package/nginx-ssl-util/default) + TITLE+= (using PCRE) + DEPENDS+=+libpcre +endef + +define Package/nginx-util/description + Utility that builds dynamically LAN listen directives for Nginx. +endef + +Package/nginx-ssl-util/default/description = $(Package/nginx-util/description)\ + Furthermore, it manages SSL directives for its server parts and can create \ + corresponding (self-signed) certificates. + +Package/nginx-ssl-util/description = \ + $(Package/nginx-ssl-util/default/description) \ + It uses the PCRE library for performance. + +Package/nginx-ssl-util-nopcre/description = \ + $(Package/nginx-ssl-util/default/description) \ + It uses the standard regex library of C++. + +define Package/nginx-util/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/nginx-util $(1)/usr/bin/nginx-util +endef + +define Package/nginx-ssl-util/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/nginx-ssl-util $(1)/usr/bin/nginx-util +endef + +define Package/nginx-ssl-util-nopcre/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/nginx-ssl-util-nopcre \ + $(1)/usr/bin/nginx-util +endef + +$(eval $(call BuildPackage,nginx-util)) +$(eval $(call BuildPackage,nginx-ssl-util)) +$(eval $(call BuildPackage,nginx-ssl-util-nopcre)) diff --git a/net/nginx-util/src/CMakeLists.txt b/net/nginx-util/src/CMakeLists.txt new file mode 100644 index 000000000..d78ec5d59 --- /dev/null +++ b/net/nginx-util/src/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(nginx-util CXX) + +INCLUDE(CheckFunctionExists) + +FIND_PATH(ubus_include_dir libubus.h) +FIND_LIBRARY(ubox NAMES ubox) +FIND_LIBRARY(ubus NAMES ubus) +INCLUDE_DIRECTORIES(${ubus_include_dir}) + +ADD_DEFINITIONS(-Os -Wall -Werror -Wextra --std=c++17 -g3) +ADD_DEFINITIONS(-Wno-unused-parameter -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +ADD_EXECUTABLE(px5g px5g.cpp) +TARGET_LINK_LIBRARIES(px5g ssl crypto) + +ADD_EXECUTABLE(nginx-util nginx-util.cpp) +TARGET_LINK_LIBRARIES(nginx-util ${ubox} ${ubus} pthread) + +ADD_EXECUTABLE(nginx-ssl-util nginx-ssl-util.cpp) +TARGET_LINK_LIBRARIES(nginx-ssl-util ${ubox} ${ubus} pthread ssl crypto pcre) + +ADD_EXECUTABLE(nginx-ssl-util-nopcre nginx-ssl-util.cpp) +TARGET_COMPILE_DEFINITIONS(nginx-ssl-util-nopcre PUBLIC -DNO_PCRE) +TARGET_LINK_LIBRARIES(nginx-ssl-util-nopcre ${ubox} ${ubus} pthread ssl crypto) + +ADD_EXECUTABLE(nginx-ssl-util-noubus nginx-ssl-util.cpp) +TARGET_COMPILE_DEFINITIONS(nginx-ssl-util-noubus PUBLIC -DNO_UBUS) +TARGET_LINK_LIBRARIES(nginx-ssl-util-noubus pthread ssl crypto pcre) + +INSTALL(TARGETS px5g RUNTIME DESTINATION bin) +INSTALL(TARGETS nginx-util RUNTIME DESTINATION bin) +INSTALL(TARGETS nginx-ssl-util RUNTIME DESTINATION bin) +INSTALL(TARGETS nginx-ssl-util-nopcre RUNTIME DESTINATION bin) +# INSTALL(TARGETS nginx-ssl-util-noubus RUNTIME DESTINATION bin) diff --git a/net/nginx-util/src/nginx-ssl-util.cpp b/net/nginx-util/src/nginx-ssl-util.cpp new file mode 100644 index 000000000..f4a857397 --- /dev/null +++ b/net/nginx-util/src/nginx-ssl-util.cpp @@ -0,0 +1,658 @@ +#include + +#ifdef NO_PCRE +#include +namespace rgx = std; +#else +#include "regex-pcre.hpp" +#endif + +#include "nginx-util.hpp" +#include "px5g-openssl.hpp" + + +#ifndef NO_UBUS +static constexpr auto UBUS_TIMEOUT = 1000; +#endif + +// once a year: +static constexpr auto CRON_INTERVAL = std::string_view{"3 3 12 12 *"}; + +static constexpr auto LAN_SSL_LISTEN = + std::string_view{"/var/lib/nginx/lan_ssl.listen"}; + +static constexpr auto LAN_SSL_LISTEN_DEFAULT = + std::string_view{"/var/lib/nginx/lan_ssl.listen.default"}; + +static constexpr auto ADD_SSL_FCT = std::string_view{"add_ssl"}; + +static constexpr auto SSL_SESSION_CACHE_ARG = + [](const std::string_view & /*name*/) -> std::string + { return "shared:SSL:32k"; }; + +static constexpr auto SSL_SESSION_TIMEOUT_ARG = std::string_view{"64m"}; + + +using _Line = + std::array< std::string (*)(const std::string &, const std::string &), 2 >; + +class Line { + +private: + + _Line _line; + +public: + + explicit Line(const _Line & line) noexcept : _line{line} {} + + template + static auto build() noexcept -> Line + { + return Line{_Line{ + [](const std::string & p, const std::string & b) -> std::string + { return (... + xn[0](p, b)); }, + [](const std::string & p, const std::string & b) -> std::string + { return (... + xn[1](p, b)); } + }}; + } + + + [[nodiscard]] auto STR(const std::string & param, const std::string & begin) + const -> std::string + { return _line[0](param, begin); } + + + [[nodiscard]] auto RGX() const -> rgx::regex + { return rgx::regex{_line[1]("", "")}; } + +}; + + +auto get_if_missed(const std::string & conf, const Line & LINE, + const std::string & val, + const std::string & indent="\n ", bool compare=true) + -> std::string; + + +auto delete_if(const std::string & conf, const rgx::regex & rgx, + const std::string & val="", bool compare=false) + -> std::string; + + +void add_ssl_directives_to(const std::string & name, bool isdefault); + + +void create_ssl_certificate(const std::string & crtpath, + const std::string & keypath, + int days=792); + + +void use_cron_to_recreate_certificate(const std::string & name); + + +void add_ssl_if_needed(const std::string & name); + + +void del_ssl_directives_from(const std::string & name, bool isdefault); + + +void del_ssl(const std::string & name); + + +static constexpr auto _begin = _Line{ + [](const std::string & /*param*/, const std::string & begin) -> std::string + { return begin; }, + + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { return R"([{;](\s*))"; } +}; + + +static constexpr auto _space = _Line{ + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { return std::string{" "}; }, + + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { return R"(\s+)"; } +}; + + +static constexpr auto _newline = _Line{ + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { return std::string{"\n"}; }, + + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { return std::string{"\n"}; } +}; + + +static constexpr auto _end = _Line{ + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { return std::string{";"}; }, + + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { return std::string{R"(\s*;)"}; } +}; + + +template +static constexpr auto _capture = _Line{ + [](const std::string & param, const std::string & /*begin*/) -> std::string + { return '\'' + param + '\''; }, + + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { + const auto lim = clim=='\0' ? std::string{"\\s"} : std::string{clim}; + return std::string{R"(((?:(?:"[^"]*")|(?:[^'")"} + + lim + "][^" + lim + "]*)|(?:'[^']*'))+)"; + } +}; + + +template +static constexpr auto _escape = _Line{ + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { return clim + std::string{strptr.data()} + clim; }, + + [](const std::string & /*param*/, const std::string & /*begin*/) + -> std::string + { + std::string ret{}; + for (char c : strptr) { + switch(c) { + case '^': ret += '\\'; [[fallthrough]]; + case '_': [[fallthrough]]; + case '-': ret += c; + break; + default: + if ((isalpha(c)!=0) || (isdigit(c)!=0)) { ret += c; } + else { ret += std::string{"["}+c+"]"; } + } + } + return "(?:"+ret+"|'"+ret+"'"+"|\""+ret+"\""+")"; + } +}; + + +static constexpr std::string_view _server_name = "server_name"; + +static constexpr std::string_view _include = "include"; + +static constexpr std::string_view _ssl_certificate = "ssl_certificate"; + +static constexpr std::string_view _ssl_certificate_key = "ssl_certificate_key"; + +static constexpr std::string_view _ssl_session_cache = "ssl_session_cache"; + +static constexpr std::string_view _ssl_session_timeout = "ssl_session_timeout"; + + +// For a compile time regex lib, this must be fixed, use one of these options: +// * Hand craft or macro concat them (loosing more or less flexibility). +// * Use Macro concatenation of __VA_ARGS__ with the help of: +// https://p99.gforge.inria.fr/p99-html/group__preprocessor__for.html +// * Use constexpr---not available for strings or char * for now---look at lib. + +static const auto CRON_CMD = Line::build + < _space, _escape, _space, _escape, _space, + _capture<>, _newline >(); + +static const auto NGX_SERVER_NAME = + Line::build<_begin, _escape<_server_name>, _space, _capture<';'>, _end>(); + +static const auto NGX_INCLUDE_LAN_LISTEN = Line::build + <_begin, _escape<_include>, _space, _escape, _end>(); + +static const auto NGX_INCLUDE_LAN_LISTEN_DEFAULT = Line::build + < _begin, _escape<_include>, _space, + _escape, _end >(); + +static const auto NGX_INCLUDE_LAN_SSL_LISTEN = Line::build + <_begin, _escape<_include>, _space, _escape, _end>(); + +static const auto NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT = Line::build + < _begin, _escape<_include>, _space, + _escape, _end >(); + +static const auto NGX_SSL_CRT = Line::build + <_begin, _escape<_ssl_certificate>, _space, _capture<';'>, _end>(); + +static const auto NGX_SSL_KEY = Line::build + <_begin, _escape<_ssl_certificate_key>, _space, _capture<';'>, _end>(); + +static const auto NGX_SSL_SESSION_CACHE = Line::build + <_begin, _escape<_ssl_session_cache>, _space, _capture<';'>, _end>(); + +static const auto NGX_SSL_SESSION_TIMEOUT = Line::build + <_begin, _escape<_ssl_session_timeout>, _space, _capture<';'>, _end>(); + + +auto get_if_missed(const std::string & conf, const Line & LINE, + const std::string & val, + const std::string & indent, bool compare) + -> std::string +{ + if (!compare || val.empty()) { + return rgx::regex_search(conf, LINE.RGX()) ? "" : LINE.STR(val, indent); + } + + rgx::smatch match; // assuming last capture has the value! + + for (auto pos = conf.begin(); + rgx::regex_search(pos, conf.end(), match, LINE.RGX()); + pos += match.position(0) + match.length(0)) + { + const std::string_view value = match.str(match.size() - 1); + + if (value==val || value=="'"+val+"'" || value=='"'+val+'"') { + return ""; + } + } + + return LINE.STR(val, indent); +} + + +auto delete_if(const std::string & conf, const rgx::regex & rgx, + const std::string & val, const bool compare) + -> std::string +{ + std::string ret{}; + auto pos = conf.begin(); + + for (rgx::smatch match; + rgx::regex_search(pos, conf.end(), match, rgx); + pos += match.position(0) + match.length(0)) + { + const std::string_view value = match.str(match.size() - 1); + + auto skip = 1; // one for delimiter! + if (compare && value!=val && value!="'"+val+"'" && value!='"'+val+'"') { + skip = match.length(0); + } + ret.append(pos, pos + match.position(0) + skip); + } + + ret.append(pos, conf.end()); + return ret; +} + + +void add_ssl_directives_to(const std::string & name, const bool isdefault) +{ + const std::string prefix = std::string{CONF_DIR} + name; + + std::string conf = read_file(prefix+".conf"); + + const std::string & const_conf = conf; // iteration needs const string. + rgx::smatch match; // captures str(1)=indentation spaces, str(2)=server name + for (auto pos = const_conf.begin(); + rgx::regex_search(pos, const_conf.end(), match, NGX_SERVER_NAME.RGX()); + pos += match.position(0) + match.length(0)) + { + if (match.str(2).find(name) == std::string::npos) { continue; } + + const std::string indent = match.str(1); + + std::string adds = isdefault ? + get_if_missed(conf, NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT,"",indent) : + get_if_missed(conf, NGX_INCLUDE_LAN_SSL_LISTEN, "", indent); + + adds += get_if_missed(conf, NGX_SSL_CRT, prefix+".crt", indent); + + adds += get_if_missed(conf, NGX_SSL_KEY, prefix+".key", indent); + + adds += get_if_missed(conf, NGX_SSL_SESSION_CACHE, + SSL_SESSION_CACHE_ARG(name), indent, false); + + adds += get_if_missed(conf, NGX_SSL_SESSION_TIMEOUT, + std::string{SSL_SESSION_TIMEOUT_ARG}, indent, false); + + if (adds.length() > 0) { + pos += match.position(0) + match.length(0); + + conf = std::string(const_conf.begin(), pos) + adds + + std::string(pos, const_conf.end()); + + conf = isdefault ? + delete_if(conf, NGX_INCLUDE_LAN_LISTEN_DEFAULT.RGX()) : + delete_if(conf, NGX_INCLUDE_LAN_LISTEN.RGX()); + + write_file(prefix+".conf", conf); + + std::cerr<<"Added SSL directives to "< +inline auto num2hex(T bytes) -> std::array +{ + constexpr auto n = 2*sizeof(bytes); + std::array str{}; + + for (size_t i=0; i hex{"0123456789ABCDEF"}; + static constexpr auto get = 0x0fU; + str.at(i) = hex.at(bytes & get); + + static constexpr auto move = 4U; + bytes >>= move; + } + + str[n] = '\0'; + return str; +} + + +template +inline auto get_nonce(const T salt=0) -> T +{ + T nonce = 0; + + std::ifstream urandom{"/dev/urandom"}; + + static constexpr auto move = 6U; + + constexpr size_t steps = (sizeof(nonce)*8 - 1)/move + 1; + + for (size_t i=0; i(urandom.get()); + } + + nonce ^= salt; + + return nonce; +} + + +void create_ssl_certificate(const std::string & crtpath, + const std::string & keypath, + const int days) +{ + size_t nonce = 0; + + try { nonce = get_nonce(nonce); } + + catch (...) { // the address of a variable should be random enough: + auto addr = &crtpath; + auto addrptr = static_cast( + static_cast(&addr) ); + nonce += *addrptr; + } + + auto noncestr = num2hex(nonce); + + const auto tmpcrtpath = crtpath + ".new-" + noncestr.data(); + const auto tmpkeypath = keypath + ".new-" + noncestr.data(); + + try { + auto pkey = gen_eckey(NID_secp384r1); + + write_key(pkey, tmpkeypath); + + std::string subject {"/C=ZZ/ST=Somewhere/L=None/CN=OpenWrt/O=OpenWrt"}; + subject += noncestr.data(); + + selfsigned(pkey, days, subject, tmpcrtpath); + + static constexpr auto to_seconds = 24*60*60; + static constexpr auto leeway = 42; + if (!checkend(tmpcrtpath, days*to_seconds - leeway)) { + throw std::runtime_error("bug: created certificate is not valid!!"); + } + + } catch (...) { + std::cerr<<"create_ssl_certificate error: "; + std::cerr<<"cannot create selfsigned certificate, "; + std::cerr<<"removing temporary files ..."< 0) { +#ifndef NO_UBUS + auto service = ubus::call("service","list",UBUS_TIMEOUT).filter("cron"); + + if (!service) { + std::string errmsg{"use_cron_to_recreate_certificate error: "}; + errmsg += "Cron unavailable to re-create the ssl certificate for "; + errmsg += name + "\n"; + throw std::runtime_error(errmsg.c_str()); + } // else active with or without instances: +#endif + + write_file(filename, std::string{CRON_INTERVAL}+add, std::ios::app); + +#ifndef NO_UBUS + call("/etc/init.d/cron", "reload"); +#endif + + std::cerr<<"Rebuild the ssl certificate for '"; + std::cerr< 0) { + pos += match.position(0) + 1; + + conf = std::string(const_conf.begin(), pos) + adds + + std::string(pos, const_conf.end()); + + conf = isdefault ? + delete_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT.RGX()) + : delete_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN.RGX()); + + const auto crtpath = prefix+".crt"; + conf = delete_if(conf, NGX_SSL_CRT.RGX(), crtpath, true); + + const auto keypath = prefix+".key"; + conf = delete_if(conf, NGX_SSL_KEY.RGX(), keypath, true); + + conf = delete_if(conf, NGX_SSL_SESSION_CACHE.RGX()); + + conf = delete_if(conf, NGX_SSL_SESSION_TIMEOUT.RGX()); + + write_file(prefix+".conf", conf); + + std::cerr<<"Deleted SSL directives from "< void + { + if (ip.empty()) { return; } + const std::string val = pre + ip + suf; + listen += "\tlisten " + val + ":80;\n"; + listen_default += "\tlisten " + val + ":80 default_server;\n"; +#ifdef NGINX_OPENSSL + ssl_listen += "\tlisten " + val + ":443 ssl;\n"; + ssl_listen_default += "\tlisten " + val + ":443 ssl default_server;\n"; +#endif + }; + + add_listen("", "127.0.0.1", ""); + add_listen("[", "::1", "]"); + +#ifndef NO_UBUS + auto lan_status = ubus::call("network.interface.lan", "status"); + + for (auto ip : lan_status.filter("ipv4-address", "", "address")) { + add_listen("", static_cast(blobmsg_data(ip)), ""); + } + + for (auto ip : lan_status.filter("ipv6-address", "", "address")) { + add_listen("[", static_cast(blobmsg_data(ip)), "]"); + } +#endif + + write_file(LAN_LISTEN, listen); + write_file(LAN_LISTEN_DEFAULT, listen_default); +#ifdef NGINX_OPENSSL + write_file(LAN_SSL_LISTEN, ssl_listen); + write_file(LAN_SSL_LISTEN_DEFAULT, ssl_listen_default); +#endif +} + + +void init_lan() +{ + std::exception_ptr ex; + +#ifdef NGINX_OPENSSL + auto thrd = std::thread([&ex]{ + try { add_ssl_if_needed(std::string{LAN_NAME}); } + catch (...) { + std::cerr<<"init_lan error: cannot add SSL for "< int +{ + // TODO(pst): use std::span when available: + auto args = std::basic_string_view{argv, static_cast(argc)}; + + auto cmds = std::array{ + std::array{"init_lan", ""}, + std::array{"get_env", ""}, +#ifdef NGINX_OPENSSL + std::array{ADD_SSL_FCT, " server_name" }, + std::array{"del_ssl", " server_name" }, +#endif + }; + + try { + + if (argc==2 && args[1]==cmds[0][0]) { init_lan(); } + + else if (argc==2 && args[1]==cmds[1][0]) { get_env(); } + +#ifdef NGINX_OPENSSL + else if (argc==3 && args[1]==cmds[2][0]) + { add_ssl_if_needed(std::string{args[2]});} + + else if (argc==3 && args[1]==cmds[3][0]) + { del_ssl(std::string{args[2]}); } + + else if (argc==2 && args[1]==cmds[3][0]) + { del_ssl(std::string{LAN_NAME}); } +#endif + + else { + auto usage = std::string{"usage: "} + *argv + " ["; + for (auto cmd : cmds) { + usage += std::string{cmd[0]}; + usage += std::string{cmd[1]} + "|"; + } + usage[usage.size()-1] = ']'; + + std::cerr< +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NO_UBUS +#include "ubus-cxx.hpp" +#endif + + +static constexpr auto NGINX_UTIL = std::string_view{"/usr/bin/nginx-util"}; + +static constexpr auto NGINX_CONF = std::string_view{"/etc/nginx/nginx.conf"}; + +static constexpr auto CONF_DIR = std::string_view{"/etc/nginx/conf.d/"}; + +static constexpr auto LAN_NAME = std::string_view{"_lan"}; + +static constexpr auto LAN_LISTEN =std::string_view{"/var/lib/nginx/lan.listen"}; + +static constexpr auto LAN_LISTEN_DEFAULT = + std::string_view{"/var/lib/nginx/lan.listen.default"}; + + +// mode: optional ios::binary and/or ios::app (default ios::trunc) +void write_file(const std::string_view & name, const std::string & str, + std::ios_base::openmode flag=std::ios::trunc); + + +// mode: optional ios::binary (internally ios::ate|ios::in) +auto read_file(const std::string_view & name, + std::ios_base::openmode mode=std::ios::in) -> std::string; + + +// all S must be convertible to const char[] +template +auto call(const std::string & program, S... args) -> pid_t; + + +void create_lan_listen(); + + +void init_lan(); + + +void get_env(); + + + +// --------------------- partial implementation: ------------------------------ + + +void write_file(const std::string_view & name, const std::string & str, + const std::ios_base::openmode flag) +{ + std::ofstream file(name.data(), flag); + if (!file.good()) { + throw std::ofstream::failure( + "write_file error: cannot open " + std::string{name}); + } + + file< std::string +{ + std::ifstream file(name.data(), mode|std::ios::ate); + if (!file.good()) { + throw std::ifstream::failure( + "read_file error: cannot open " + std::string{name}); + } + + std::string ret{}; + const size_t size = file.tellg(); + ret.reserve(size); + + file.seekg(0); + ret.assign((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + file.close(); + return ret; +} + + +template +auto call(const char * program, S... args) -> pid_t +{ + pid_t pid = fork(); + + if (pid==0) { //child: + std::array argv = + { strdup(program), strdup(args)..., nullptr }; + + execv(program, argv.data()); // argv cannot be const char * const[]! + + _exit(EXIT_FAILURE); // exec never returns. + } else if (pid>0) { //parent: + return pid; + } + + std::string errmsg = "call error: cannot fork ("; + errmsg += std::to_string(errno) + "): " + std::strerror(errno); + throw std::runtime_error(errmsg.c_str()); +} + + +#endif diff --git a/net/nginx-util/src/px5g-openssl.hpp b/net/nginx-util/src/px5g-openssl.hpp new file mode 100644 index 000000000..380eba335 --- /dev/null +++ b/net/nginx-util/src/px5g-openssl.hpp @@ -0,0 +1,407 @@ +#ifndef _PX5G_OPENSSL_HPP +#define _PX5G_OPENSSL_HPP + +// #define OPENSSL_API_COMPAT 0x10102000L +#include +#include +#include +#include +#include +#include +#include +#include + +static constexpr auto rsa_min_modulus_bits = 512; + +using EVP_PKEY_ptr = std::unique_ptr; + +using X509_NAME_ptr = std::unique_ptr; + + +auto checkend(const std::string & crtpath, + time_t seconds=0, bool use_pem=true) -> bool; + + +auto gen_eckey(int curve) -> EVP_PKEY_ptr; + + +auto gen_rsakey(int keysize, BN_ULONG exponent=RSA_F4) -> EVP_PKEY_ptr; + + +void write_key(const EVP_PKEY_ptr & pkey, + const std::string & keypath="", bool use_pem=true); + + +auto subject2name(const std::string & subject) -> X509_NAME_ptr; + + +void selfsigned(const EVP_PKEY_ptr & pkey, int days, + const std::string & subject="", const std::string & crtpath="", + bool use_pem=true); + + + +// ------------------------- implementation: ---------------------------------- + + +inline auto print_error(const char * str, const size_t /*len*/, void * errmsg) + -> int +{ + *static_cast(errmsg) += str; + return 0; +} + + +auto checkend(const std::string & crtpath, + const time_t seconds, const bool use_pem) -> bool +{ + BIO * bio = crtpath.empty() ? + BIO_new_fp(stdin, BIO_NOCLOSE | (use_pem ? BIO_FP_TEXT : 0)) : + BIO_new_file(crtpath.c_str(), (use_pem ? "r" : "rb")); + + X509 * x509 = nullptr; + + if (bio != nullptr) { + x509 = use_pem ? + PEM_read_bio_X509_AUX(bio, nullptr, nullptr, nullptr) : + d2i_X509_bio(bio, nullptr); + BIO_free(bio); + } + + if (x509==nullptr) { + std::string errmsg{"checkend error: unable to load certificate\n"}; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + + time_t checktime = time(nullptr) + seconds; + auto cmp = X509_cmp_time(X509_get0_notAfter(x509), &checktime); + + X509_free(x509); + + return (cmp >= 0); +} + + +auto gen_eckey(const int curve) -> EVP_PKEY_ptr +{ + EC_GROUP * group = curve!=0 ? EC_GROUP_new_by_curve_name(curve) : nullptr; + + if (group == nullptr) { + std::string errmsg{"gen_eckey error: cannot build group for curve id "}; + errmsg += std::to_string(curve) + "\n"; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + + EC_GROUP_set_asn1_flag(group, OPENSSL_EC_NAMED_CURVE); + + EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED); + + auto eckey = EC_KEY_new(); + + if (eckey != nullptr) { + if ( (EC_KEY_set_group(eckey, group) == 0) || + (EC_KEY_generate_key(eckey) == 0) ) + { + EC_KEY_free(eckey); + eckey = nullptr; + } + } + + EC_GROUP_free(group); + + if (eckey == nullptr) { + std::string errmsg{"gen_eckey error: cannot build key with curve id "}; + errmsg += std::to_string(curve) + "\n"; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + + EVP_PKEY_ptr pkey{EVP_PKEY_new(), ::EVP_PKEY_free}; + + auto tmp = static_cast(static_cast(eckey)); + + if (!EVP_PKEY_assign_EC_KEY(pkey.get(), tmp)) { + EC_KEY_free(eckey); + std::string errmsg{"gen_eckey error: cannot assign EC key to EVP\n"}; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + + return pkey; +} + + +auto gen_rsakey(const int keysize, const BN_ULONG exponent) -> EVP_PKEY_ptr +{ + if (keysizeOPENSSL_RSA_MAX_MODULUS_BITS) { + std::string errmsg{"gen_rsakey error: RSA keysize ("}; + errmsg += std::to_string(keysize) + ") out of range [512.."; + errmsg += std::to_string(OPENSSL_RSA_MAX_MODULUS_BITS) + "]"; + throw std::runtime_error(errmsg.c_str()); + } + auto bignum = BN_new(); + + if (bignum == nullptr) { + std::string errmsg{"gen_rsakey error: cannot get big number struct\n"}; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + + auto rsa = RSA_new(); + + if (rsa != nullptr) { + if ((BN_set_word(bignum, exponent) == 0) || + (RSA_generate_key_ex(rsa, keysize, bignum, nullptr) == 0)) + { + RSA_free(rsa); + rsa = nullptr; + } + } + + BN_free(bignum); + + if (rsa == nullptr) { + std::string errmsg{"gen_rsakey error: cannot create RSA key with size"}; + errmsg += std::to_string(keysize) + " and exponent "; + errmsg += std::to_string(exponent) + "\n"; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + + EVP_PKEY_ptr pkey{EVP_PKEY_new(), ::EVP_PKEY_free}; + + auto tmp = static_cast(static_cast(rsa)); + + if (!EVP_PKEY_assign_RSA(pkey.get(), tmp)) { + RSA_free(rsa); + std::string errmsg{"gen_rsakey error: cannot assign RSA key to EVP\n"}; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + + return pkey; +} + + +void write_key(const EVP_PKEY_ptr & pkey, + const std::string & keypath, const bool use_pem) +{ + BIO * bio = nullptr; + + if (keypath.empty()) { + bio = BIO_new_fp( stdout, BIO_NOCLOSE | (use_pem ? BIO_FP_TEXT : 0) ); + + } else { // BIO_new_file(keypath.c_str(), (use_pem ? "w" : "wb") ); + + static constexpr auto mask = 0600; + // auto fd = open(keypath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mask); + auto fd = creat(keypath.c_str(), mask); // the same without va_args. + + if (fd >= 0) { + auto fp = fdopen(fd, (use_pem ? "w" : "wb") ); + + if (fp != nullptr) { + bio = BIO_new_fp(fp, BIO_CLOSE | (use_pem ? BIO_FP_TEXT : 0)); + if (bio == nullptr) { fclose(fp); } // (fp owns fd) + } + else { close(fd); } + } + + } + + if (bio == nullptr) { + std::string errmsg{"write_key error: cannot open "}; + errmsg += keypath.empty() ? "stdout" : keypath; + errmsg += "\n"; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + + int len = 0; + + auto key = pkey.get(); + switch (EVP_PKEY_base_id(key)) { // use same format as px5g: + case EVP_PKEY_EC: + len = use_pem ? + PEM_write_bio_ECPrivateKey(bio, EVP_PKEY_get0_EC_KEY(key), + nullptr, nullptr, 0, nullptr, nullptr) : + i2d_ECPrivateKey_bio(bio, EVP_PKEY_get0_EC_KEY(key)); + break; + case EVP_PKEY_RSA: + len = use_pem ? + PEM_write_bio_RSAPrivateKey(bio, EVP_PKEY_get0_RSA(key), + nullptr, nullptr, 0, nullptr, nullptr) : + i2d_RSAPrivateKey_bio(bio, EVP_PKEY_get0_RSA(key)); + break; + default: + len = use_pem ? + PEM_write_bio_PrivateKey(bio, key, + nullptr, nullptr, 0, nullptr, nullptr) : + i2d_PrivateKey_bio(bio, key); + } + + BIO_free_all(bio); + + if (len==0) { + std::string errmsg{"write_key error: cannot write EVP pkey to "}; + errmsg += keypath.empty() ? "stdout" : keypath; + errmsg += "\n"; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } +} + + +auto subject2name(const std::string & subject) -> X509_NAME_ptr +{ + if (!subject.empty() && subject[0]!='/') { + throw std::runtime_error("subject2name errror: not starting with /"); + } + + X509_NAME_ptr name = {X509_NAME_new(), ::X509_NAME_free}; + + if (!name) { + std::string errmsg{"subject2name error: cannot create X509 name \n"}; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + + if (subject.empty()) { return name; } + + size_t prev = 1; + std::string type{}; + char chr = '='; + for (size_t i=0; subject[i] != 0; ) { + ++i; + if (subject[i]=='\\' && subject[++i]=='\0') { + throw std::runtime_error("subject2name errror: escape at the end"); + } + if (subject[i]!=chr && subject[i]!='\0') { continue; } + if (chr == '=') { + type = subject.substr(prev, i-prev); + chr = '/'; + } else { + auto nid = OBJ_txt2nid(type.c_str()); + if (nid == NID_undef) { + // skip unknown entries (silently?). + } else { + auto val = static_cast( + static_cast(&subject[prev]) ); + + auto len = i - prev; + + if ( X509_NAME_add_entry_by_NID(name.get(), nid, MBSTRING_ASC, + val, len, -1, 0) + == 0 ) + { + std::string errmsg{"subject2name error: cannot add "}; + errmsg += "/" + type +"="+ subject.substr(prev, len) +"\n"; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + } + chr = '='; + } + prev = i+1; + } + + return name; +} + + +void selfsigned(const EVP_PKEY_ptr & pkey, const int days, + const std::string & subject, const std::string & crtpath, + const bool use_pem) +{ + auto x509 = X509_new(); + + if (x509 == nullptr) { + std::string errmsg{"selfsigned error: cannot create X509 structure\n"}; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } + + auto freeX509_and_throw = [&x509](const std::string & what) + { + X509_free(x509); + std::string errmsg{"selfsigned error: cannot set "}; + errmsg += what + " in X509 certificate\n"; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + }; + + if (X509_set_version(x509, 2) == 0) { freeX509_and_throw("version"); } + + if (X509_set_pubkey(x509, pkey.get()) == 0) { freeX509_and_throw("pubkey");} + + if ((X509_gmtime_adj(X509_getm_notBefore(x509), 0) == nullptr) || + (X509_time_adj_ex(X509_getm_notAfter(x509), days,0,nullptr) == nullptr)) + { + freeX509_and_throw("times"); + } + + X509_NAME_ptr name{nullptr, ::X509_NAME_free}; + + try { name = subject2name(subject); } + catch (...) { + X509_free(x509); + throw; + } + + if (X509_set_subject_name(x509, name.get()) == 0) { + freeX509_and_throw("subject"); + } + + if (X509_set_issuer_name(x509, name.get()) == 0) { + freeX509_and_throw("issuer"); + } + + auto bignum = BN_new(); + + if (bignum == nullptr) { freeX509_and_throw("serial (creating big number struct)"); } + + static const auto BITS = 159; + if (BN_rand(bignum, BITS, BN_RAND_TOP_ANY, BN_RAND_BOTTOM_ANY) == 0) { + BN_free(bignum); + freeX509_and_throw("serial (creating random number)"); + } + + if (BN_to_ASN1_INTEGER(bignum, X509_get_serialNumber(x509)) == nullptr) { + BN_free(bignum); + freeX509_and_throw("random serial"); + } + + BN_free(bignum); + + if (X509_sign(x509, pkey.get(), EVP_sha256()) == 0) { + freeX509_and_throw("signing digest"); + } + + BIO * bio = crtpath.empty() ? + BIO_new_fp(stdout, BIO_NOCLOSE | (use_pem ? BIO_FP_TEXT : 0)) : + BIO_new_file(crtpath.c_str(), (use_pem ? "w" : "wb")); + + int len = 0; + + if (bio != nullptr) { + len = use_pem ? + PEM_write_bio_X509(bio, x509) : + i2d_X509_bio(bio, x509); + BIO_free_all(bio); + } + + X509_free(x509); + + if (len==0) { + std::string errmsg{"selfsigned error: cannot write certificate to "}; + errmsg += crtpath.empty() ? "stdout" : crtpath; + errmsg += "\n"; + ERR_print_errors_cb(print_error, &errmsg); + throw std::runtime_error(errmsg.c_str()); + } +} + + +#endif diff --git a/net/nginx-util/src/px5g.cpp b/net/nginx-util/src/px5g.cpp new file mode 100644 index 000000000..56a063254 --- /dev/null +++ b/net/nginx-util/src/px5g.cpp @@ -0,0 +1,451 @@ +#include +#include +#include +#include +#include +#include "px5g-openssl.hpp" + + +class argv_view { // TODO(pst): use std::span when available. + +private: + + std::basic_string_view data; + +public: + + argv_view(const argv_view &) = delete; + + + argv_view(argv_view &&) = delete; + + + auto operator=(const argv_view &) -> argv_view & = delete; + + + auto operator=(argv_view &&) -> argv_view & = delete; + + + argv_view(const char ** argv, int argc) : + data{argv, static_cast(argc)} {} + + + inline auto operator[] (size_t pos) const -> std::string_view + { return std::string_view{data[pos]}; } + + + [[nodiscard]] inline constexpr auto size() const noexcept -> size_t + { return data.size(); } + + + ~argv_view() = default; + +}; + + +static const auto default_validity = 30; + + +auto checkend(const argv_view & argv) -> int; + + +void eckey(const argv_view & argv); + + +void rsakey(const argv_view & argv); + + +void selfsigned(const argv_view & argv); + + +inline auto parse_int(const std::string_view & arg) -> int +{ + size_t pos; + int ret = stoi(std::string{arg}, &pos); + if (pos < arg.size()) { + throw std::runtime_error("number has trailing char"); + } + return ret; +} + + +inline auto parse_curve(const std::string_view & name) -> int +{ + if (name=="P-384") { return NID_secp384r1; } + if (name=="P-521") { return NID_secp521r1; } + if (name=="P-256" || name=="secp256r1") { return NID_X9_62_prime256v1; } + if (name=="secp192r1") { return NID_X9_62_prime192v1; } + return OBJ_sn2nid(name.data()); + // not: if (curve == 0) { curve = EC_curve_nist2nid(name.c_str()); } +} + + +auto checkend(const argv_view & argv) -> int +{ + bool use_pem = true; + std::string crtpath{}; + time_t seconds = 0; + + for (size_t i=2; i= argv.size()) { + throw std::runtime_error("checkend error: -in misses filename"); + } + + if (!crtpath.empty()) { + if (argv[i] == crtpath) { + std::cerr<<"checkend warning: repeated same -in file\n"; + } else { + throw std::runtime_error + ("checkend error: more than one -in file"); + } + } + + crtpath = argv[i]; + } + + else if (argv[i][0]=='-') { + std::cerr<<"checkend warning: skipping option "<(num); + + if (num!=static_cast(seconds)) { + auto errmsg = std::string{"checkend error: time too big "}; + errmsg += argv[i]; + throw std::runtime_error(errmsg.c_str()); + } + } + } + + bool valid = checkend(crtpath, seconds, use_pem); + std::cout<<"Certificate will"<<(valid ? " not " : " ")<<"expire"<= argv.size()) { + throw std::runtime_error("eckey error: -out misses filename"); + } + + if (!keypath.empty()) { + if (argv[i]==keypath) { + std::cerr<<"eckey warning: repeated same -out file\n"; + } else { + throw std::runtime_error + ("eckey error: more than one -out file"); + } + } + + keypath = argv[i]; + } + + else if (argv[i][0]=='-') { + std::cerr<<"eckey warning: skipping option "<= argv.size()) { + throw std::runtime_error("rsakey error: -out misses filename"); + } + + if (!keypath.empty()) { + if (argv[i]==keypath) { + std::cerr<<"rsakey warning: repeated -out file"<= argv.size()) { + throw std::runtime_error + ("selfsigned error: -newkey misses algorithm option"); + } + + static constexpr auto rsa_prefix = std::string_view{"rsa:"}; + + if (argv[i]=="ec") { + use_rsa = false; + } else if (argv[i].rfind(rsa_prefix, 0) == 0) { + use_rsa = true; + try { + keysize = parse_int(argv[i].substr(rsa_prefix.size())); + } catch (...) { + std::string errmsg{"selfsigned error: invalid keysize "}; + errmsg += argv[i].substr(4); + std::throw_with_nested(std::runtime_error(errmsg.c_str())); + } + } else { + throw std::runtime_error("selfsigned error: invalid algorithm"); + } + } + + else if (argv[i]=="-pkeyopt") { + ++i; + + if (i >= argv.size()) { + throw std::runtime_error + ("selfsigned error: -pkeyopt misses value"); + } + + static constexpr auto curve_prefix = + std::string_view{"ec_paramgen_curve:"}; + + if (argv[i].rfind(curve_prefix, 0) != 0) { + throw std::runtime_error("selfsigned error: -pkeyopt invalid"); + } + + curve = parse_curve(argv[i].substr(curve_prefix.size())); + } + + else if (argv[i]=="-keyout") { + ++i; + + if (i >= argv.size()) { + throw std::runtime_error + ("selfsigned error: -keyout misses path"); + } + + if (!keypath.empty()) { + if (argv[i]==keypath) { + std::cerr<<"selfsigned warning: repeated -keyout file\n"; + } else { + throw std::runtime_error + ("selfsigned error: more than one -keyout file"); + } + } + + keypath = argv[i]; + } + + else if (argv[i]=="-out") { + ++i; + + if (i >= argv.size()) { + throw std::runtime_error + ("selfsigned error: -out misses filename"); + } + + if (!crtpath.empty()) { + if (argv[i]==crtpath) { + std::cerr<<"selfsigned warning: repeated same -out file\n"; + } else { + throw std::runtime_error + ("selfsigned error: more than one -out file"); + } + } + + crtpath = argv[i]; + } + + else if (argv[i]=="-subj") { + ++i; + + if (i >= argv.size()) { + throw std::runtime_error + ("selfsigned error: -subj misses value"); + } + + if (!subject.empty()) { + if (argv[i]==subject) { + std::cerr<<"selfsigned warning: repeated same -subj\n"; + } else { + throw std::runtime_error + ("selfsigned error: more than one -subj value"); + } + } + + subject = argv[i]; + } + + else { + std::cerr<<"selfsigned warning: skipping option "< int +{ + auto args = argv_view{argv, argc}; + + auto cmds = std::array{ + std::array{"checkend", + " [-der] [-in certificate_path] [seconds_remaining]" + }, + std::array{"eckey", + " [-der] [-out key_path] [curve_name]" + }, + std::array{"rsakey", + " [-der] [-out key_path] [-3] [key_size]" + }, + std::array{"selfsigned", + " [-der] [-keyout key_path] [-out certificate_path]" + " [-newkey ec|rsa:key_size] [-pkeyopt ec_paramgen_curve:name]" + " [-days validity] [-subj /C=.../ST=.../L=.../O=.../CN=.../... ]" + }, + }; + + try { + if (argc < 2) { throw std::runtime_error("error: no argument"); } + + if (args[1]==cmds[0][0]) {return checkend(args);} + + if (args[1]==cmds[1][0]) { eckey(args); } + + else if (args[1]==cmds[2][0]) { rsakey(args); } + + else if (args[1]==cmds[3][0]) { selfsigned(args); } + + else { throw std::runtime_error("error: argument not recognized"); } + } + + catch (const std::exception & e) { + + auto usage = std::string{"usage: \n"} ; + for (auto cmd : cmds) { + usage += std::string{4, ' '} + *argv +" "+ cmd[0] + cmd[1] +"\n"; + } + + std::cerr< void + { + std::cerr< functions using pcre for performance: + +#ifndef __REGEXP_PCRE_HPP +#define __REGEXP_PCRE_HPP + +#include +#include +#include +#include +#include + + +namespace rgx { + + +namespace regex_constants { + enum error_type + { + _enum_error_collate, + _enum_error_ctype, + _enum_error_escape, + _enum_error_backref, + _enum_error_brack, + _enum_error_paren, + _enum_error_brace, + _enum_error_badbrace, + _enum_error_range, + _enum_error_space, + _enum_error_badrepeat, + _enum_error_complexity, + _enum_error_stack, + _enum_error_last + }; + static const error_type error_collate(_enum_error_collate); + static const error_type error_ctype(_enum_error_ctype); + static const error_type error_escape(_enum_error_escape); + static const error_type error_backref(_enum_error_backref); + static const error_type error_brack(_enum_error_brack); + static const error_type error_paren(_enum_error_paren); + static const error_type error_brace(_enum_error_brace); + static const error_type error_badbrace(_enum_error_badbrace); + static const error_type error_range(_enum_error_range); + static const error_type error_space(_enum_error_space); + static const error_type error_badrepeat(_enum_error_badrepeat); + static const error_type error_complexity(_enum_error_complexity); + static const error_type error_stack(_enum_error_stack); +} // namespace regex_constants + + + +class regex_error : public std::runtime_error { + +private: + + regex_constants::error_type errcode; + + +public: + + explicit regex_error(regex_constants::error_type code, + const char * what="regex error") + : runtime_error(what), errcode(code) + { } + + + [[nodiscard]] auto code() const -> regex_constants::error_type + { return errcode; } + +}; + + + +class regex { + +private: + + int errcode = 0; + + const char * errptr = nullptr; + + int erroffset = 0; + + pcre * const re = nullptr; + + static const std::array errcode_pcre2regex; + + static const auto BASE = 10; + + +public: + + inline regex() = default; + + + inline regex(const regex &) = delete; + + + inline regex(regex &&) = default; + + + inline auto operator=(const regex &) -> regex & = delete; + + + inline auto operator=(regex &&) -> regex & = delete; + + + explicit regex(const std::string & str) + : re{ pcre_compile2(str.c_str(), 0, &errcode, &errptr, &erroffset,nullptr) } + { + if (re==nullptr) { + std::string what = std::string("regex error: ") + errptr + '\n'; + what += " '" + str + "'\n"; + what += " " + std::string(erroffset, ' ') + '^'; + + throw regex_error(errcode_pcre2regex.at(errcode), what.c_str()); + } + } + + + ~regex() { if (re != nullptr) { pcre_free(re); } } + + + inline auto operator()() const -> const pcre * { return re; } + +}; + + + +class smatch { + + friend auto regex_search(std::string::const_iterator begin, + std::string::const_iterator end, + smatch & match, + const regex & rgx); + + +private: + + std::string::const_iterator begin; + + std::string::const_iterator end; + + std::vector vec{}; + + int n = 0; + + +public: + + [[nodiscard]] inline auto position(int i=0) const { + return (i<0 || i>=n) ? std::string::npos : vec[2*i]; + } + + + [[nodiscard]] inline auto length(int i=0) const { + return (i<0 || i>=n) ? 0 : vec[2*i+1] - vec[2*i]; + } + + + [[nodiscard]] auto str(int i=0) const -> std::string { // should we throw? + if (i<0 || i>=n) { return ""; } + int x = vec[2*i]; + if (x<0) { return ""; } + int y = vec[2*i+1]; + return std::string{begin + x, begin + y}; + } + + + [[nodiscard]] auto format(const std::string & fmt) const; + + + [[nodiscard]] auto size() const -> int { return n; } + + + [[nodiscard]] inline auto empty() const { return n<0; } + + + [[nodiscard]] inline auto ready() const { return !vec.empty(); } + +}; + + +inline auto regex_search(const std::string & subj, const regex & rgx); + + +auto regex_replace(const std::string & subj, + const regex & rgx, + const std::string & insert); + + +inline auto regex_search(const std::string & subj, smatch & match, + const regex & rgx); + + +auto regex_search(std::string::const_iterator begin, + std::string::const_iterator end, + smatch & match, + const regex & rgx); + + + +// ------------------------- implementation: ---------------------------------- + + +inline auto regex_search(const std::string & subj, const regex & rgx) +{ + if (rgx()==nullptr) { + throw std::runtime_error("regex_search error: no regex given"); + } + int n = pcre_exec(rgx(), nullptr, subj.c_str(), subj.length(), + 0, 0, nullptr, 0); + return n>=0; +} + + +auto regex_search(const std::string::const_iterator begin, + const std::string::const_iterator end, + smatch & match, + const regex & rgx) +{ + if (rgx()==nullptr) { + throw std::runtime_error("regex_search error: no regex given"); + } + + int sz = 0; + pcre_fullinfo(rgx(), nullptr, PCRE_INFO_CAPTURECOUNT, &sz); + sz = 3*(sz + 1); + + match.vec.reserve(sz); + + const char * subj = &*begin; + size_t len = &*end - subj; + + match.begin = begin; + match.end = end; + + match.n = pcre_exec(rgx(), nullptr, subj, len, 0, 0, &match.vec[0], sz); + + if (match.n<0) { return false; } + if (match.n==0) { match.n = sz/3; } + + return true; +} + + +inline auto regex_search(const std::string & subj, smatch & match, + const regex & rgx) +{ + return regex_search(subj.begin(), subj.end(), match, rgx); +} + + +auto smatch::format(const std::string & fmt) const { + std::string ret{}; + size_t index = 0; + + size_t pos; + while ((pos=fmt.find('$', index)) != std::string::npos) { + ret.append(fmt, index, pos-index); + index = pos + 1; + + char chr = fmt[index++]; + int n = 0; + static const auto BASE = 10; + switch(chr) { + + case '&': // match + ret += this->str(0); + break; + + case '`': // prefix + ret.append(begin, begin+vec[0]); + break; + + case '\'': // suffix + ret.append(begin+vec[1], end); + break; + + default: // number => submatch + while (isdigit(chr) != 0) { + n = BASE*n + chr - '0'; + chr = fmt[index++]; + } + + ret += n>0 ? str(n) : std::string{"$"}; + + [[fallthrough]]; + + case '$': // escaped + ret += chr; + } + } + ret.append(fmt, index); + return ret; +} + + +auto regex_replace(const std::string & subj, + const regex & rgx, + const std::string & insert) +{ + if (rgx()==nullptr) { + throw std::runtime_error("regex_replace error: no regex given"); + } + + std::string ret{}; + auto pos = subj.begin(); + + for (smatch match; + regex_search(pos, subj.end(), match, rgx); + pos += match.position(0) + match.length(0)) + { + ret.append(pos, pos + match.position(0)); + ret.append(match.format(insert)); + } + + ret.append(pos, subj.end()); + return ret; +} + + + +// ------------ There is only the translation table below : ------------------- + + +const std::array regex::errcode_pcre2regex = { + // 0 no error + regex_constants::error_type::_enum_error_last, + // 1 \ at end of pattern + regex_constants::error_escape, + // 2 \c at end of pattern + regex_constants::error_escape, + // 3 unrecognized character follows \ . + regex_constants::error_escape, + // 4 numbers out of order in {} quantifier + regex_constants::error_badbrace, + // 5 number too big in {} quantifier + regex_constants::error_badbrace, + // 6 missing terminating for character class + regex_constants::error_brack, + // 7 invalid escape sequence in character class + regex_constants::error_escape, + // 8 range out of order in character class + regex_constants::error_range, + // 9 nothing to repeat + regex_constants::error_badrepeat, + // 10 [this code is not in use + regex_constants::error_type::_enum_error_last, + // 11 internal error: unexpected repeat + regex_constants::error_badrepeat, + // 12 unrecognized character after (? or (?- + regex_constants::error_backref, + // 13 POSIX named classes are supported only within a class + regex_constants::error_range, + // 14 missing ) + regex_constants::error_paren, + // 15 reference to non-existent subpattern + regex_constants::error_backref, + // 16 erroffset passed as NULL + regex_constants::error_type::_enum_error_last, + // 17 unknown option bit(s) set + regex_constants::error_type::_enum_error_last, + // 18 missing ) after comment + regex_constants::error_paren, + // 19 [this code is not in use + regex_constants::error_type::_enum_error_last, + // 20 regular expression is too large + regex_constants::error_space, + // 21 failed to get memory + regex_constants::error_stack, + // 22 unmatched parentheses + regex_constants::error_paren, + // 23 internal error: code overflow + regex_constants::error_stack, + // 24 unrecognized character after (?< + regex_constants::error_backref, + // 25 lookbehind assertion is not fixed length + regex_constants::error_backref, + // 26 malformed number or name after (?( + regex_constants::error_backref, + // 27 conditional group contains more than two branches + regex_constants::error_backref, + // 28 assertion expected after (?( + regex_constants::error_backref, + // 29 (?R or (?[+-digits must be followed by ) + regex_constants::error_backref, + // 30 unknown POSIX class name + regex_constants::error_ctype, + // 31 POSIX collating elements are not supported + regex_constants::error_collate, + // 32 this version of PCRE is compiled without UTF support + regex_constants::error_collate, + // 33 [this code is not in use + regex_constants::error_type::_enum_error_last, + // 34 character value in \x{} or \o{} is too large + regex_constants::error_escape, + // 35 invalid condition (?(0) + regex_constants::error_backref, + // 36 \C not allowed in lookbehind assertion + regex_constants::error_escape, + // 37 PCRE does not support \L, \l, \N{name}, \U, or \u + regex_constants::error_escape, + // 38 number after (?C is > 255 + regex_constants::error_backref, + // 39 closing ) for (?C expected + regex_constants::error_paren, + // 40 recursive call could loop indefinitely + regex_constants::error_complexity, + // 41 unrecognized character after (?P + regex_constants::error_backref, + // 42 syntax error in subpattern name (missing terminator) + regex_constants::error_paren, + // 43 two named subpatterns have the same name + regex_constants::error_backref, + // 44 invalid UTF-8 string (specifically UTF-8) + regex_constants::error_collate, + // 45 support for \P, \p, and \X has not been compiled + regex_constants::error_escape, + // 46 malformed \P or \p sequence + regex_constants::error_escape, + // 47 unknown property name after \P or \p + regex_constants::error_escape, + // 48 subpattern name is too long (maximum 32 characters) + regex_constants::error_backref, + // 49 too many named subpatterns (maximum 10000) + regex_constants::error_complexity, + // 50 [this code is not in use + regex_constants::error_type::_enum_error_last, + // 51 octal value is greater than \377 in 8-bit non-UTF-8 mode + regex_constants::error_escape, + // 52 internal error: overran compiling workspace + regex_constants::error_type::_enum_error_last, + // 53 internal error: previously-checked referenced subpattern not found + regex_constants::error_type::_enum_error_last, + // 54 DEFINE group contains more than one branch + regex_constants::error_backref, + // 55 repeating a DEFINE group is not allowed + regex_constants::error_backref, + // 56 inconsistent NEWLINE options + regex_constants::error_escape, + // 57 \g is not followed by a braced, angle-bracketed, or quoted name/number or by a plain number + regex_constants::error_backref, + // 58 a numbered reference must not be zero + regex_constants::error_backref, + // 59 an argument is not allowed for (*ACCEPT), (*FAIL), or (*COMMIT) + regex_constants::error_complexity, + // 60 (*VERB) not recognized or malformed + regex_constants::error_complexity, + // 61 number is too big + regex_constants::error_complexity, + // 62 subpattern name expected + regex_constants::error_backref, + // 63 digit expected after (?+ + regex_constants::error_backref, + // 64 is an invalid data character in JavaScript compatibility mode + regex_constants::error_escape, + // 65 different names for subpatterns of the same number are not allowed + regex_constants::error_backref, + // 66 (*MARK) must have an argument + regex_constants::error_complexity, + // 67 this version of PCRE is not compiled with Unicode property support + regex_constants::error_collate, + // 68 \c must be followed by an ASCII character + regex_constants::error_escape, + // 69 \k is not followed by a braced, angle-bracketed, or quoted name + regex_constants::error_backref, + // 70 internal error: unknown opcode in find_fixedlength() + regex_constants::error_type::_enum_error_last, + // 71 \N is not supported in a class + regex_constants::error_ctype, + // 72 too many forward references + regex_constants::error_backref, + // 73 disallowed Unicode code point (>= 0xd800 && <= 0xdfff) + regex_constants::error_escape, + // 74 invalid UTF-16 string (specifically UTF-16) + regex_constants::error_collate, + // 75 name is too long in (*MARK), (*PRUNE), (*SKIP), or (*THEN) + regex_constants::error_complexity, + // 76 character value in \u.... sequence is too large + regex_constants::error_escape, + // 77 invalid UTF-32 string (specifically UTF-32) + regex_constants::error_collate, + // 78 setting UTF is disabled by the application + regex_constants::error_collate, + // 79 non-hex character in \x{} (closing brace missing?) + regex_constants::error_escape, + // 80 non-octal character in \o{} (closing brace missing?) + regex_constants::error_escape, + // 81 missing opening brace after \o + regex_constants::error_brace, + // 82 parentheses are too deeply nested + regex_constants::error_complexity, + // 83 invalid range in character class + regex_constants::error_range, + // 84 group name must start with a non-digit + regex_constants::error_backref, + // 85 parentheses are too deeply nested (stack check) + regex_constants::error_stack +}; + + +} // namespace rgx + + +#endif diff --git a/net/nginx-util/src/test-px5g.sh b/net/nginx-util/src/test-px5g.sh new file mode 100755 index 000000000..9bae051c6 --- /dev/null +++ b/net/nginx-util/src/test-px5g.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +PRINT_PASSED=2 + + +OPENSSL_PEM="$(mktemp)" +OPENSSL_DER="$(mktemp)" + +NONCE=$(dd if=/dev/urandom bs=1 count=4 2>/dev/null | hexdump -e '1/1 "%02x"') +SUBJECT=/C="ZZ"/ST="Somewhere"/L="None"/O="OpenWrt'$NONCE'"/CN="OpenWrt" + +openssl req -x509 -nodes -days 1 -keyout /dev/null 2>/dev/null \ + -out "$OPENSSL_PEM" -subj "$SUBJECT" \ +|| ( printf "error: generating PEM certificate with openssl"; return 1) +openssl req -x509 -nodes -days 1 -keyout /dev/null 2>/dev/null \ + -out "$OPENSSL_DER" -outform der -subj "$SUBJECT" \ +|| ( printf "error: generating DER certificate with openssl"; return 1) + + +function test() { + MSG="$1 >/dev/null \t (-> $2?) \t" + eval "$1 >/dev/null " + if [ $? -eq $2 ] + then + [ "$PRINT_PASSED" -gt 0 ] && printf "$MSG passed.\n" + else + printf "$MSG failed!!!\n" + [ "$PRINT_PASSED" -gt 1 ] && exit 1 + fi +} + + +[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting openssl itself ...\n" + +[ "$PRINT_PASSED" -gt 1 ] && printf " * right PEM:\n" +test 'cat "$OPENSSL_PEM" | openssl x509 -checkend 0 ' 0 +test 'cat "$OPENSSL_PEM" | openssl x509 -checkend 86300 ' 0 +test 'cat "$OPENSSL_PEM" | openssl x509 -checkend 86400 ' 1 + +[ "$PRINT_PASSED" -gt 1 ] && printf " * right DER:\n" +test 'cat "$OPENSSL_DER" | openssl x509 -checkend 0 -inform der ' 0 +test 'cat "$OPENSSL_DER" | openssl x509 -checkend 86300 -inform der ' 0 +test 'cat "$OPENSSL_DER" | openssl x509 -checkend 86400 -inform der ' 1 + +[ "$PRINT_PASSED" -gt 1 ] && printf " * wrong:\n" +test 'cat "$OPENSSL_PEM" | openssl x509 -checkend 0 -inform der 2>/dev/null' 1 +test 'cat "$OPENSSL_DER" | openssl x509 -checkend 0 2>/dev/null' 1 + + +[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting px5g checkend ...\n" + +[ "$PRINT_PASSED" -gt 1 ] && printf " * right PEM:\n" +test 'cat "$OPENSSL_PEM" | ./px5g checkend 0 ' 0 +test 'cat "$OPENSSL_PEM" | ./px5g checkend 86300 ' 0 +test 'cat "$OPENSSL_PEM" | ./px5g checkend 86400 ' 1 + +[ "$PRINT_PASSED" -gt 1 ] && printf " * right DER:\n" +test 'cat "$OPENSSL_DER" | ./px5g checkend -der 0 ' 0 +test 'cat "$OPENSSL_DER" | ./px5g checkend -der 86300 ' 0 +test 'cat "$OPENSSL_DER" | ./px5g checkend -der 86400 ' 1 + +[ "$PRINT_PASSED" -gt 1 ] && printf " * in option:\n" +test 'cat "$OPENSSL_DER" | ./px5g checkend -in /proc/self/fd/0 -der 0 ' 0 +test 'cat "$OPENSSL_DER" | ./px5g checkend -der -in /proc/self/fd/0 99 ' 0 + +[ "$PRINT_PASSED" -gt 1 ] && printf " * wrong:\n" +test 'cat "$OPENSSL_PEM" | ./px5g checkend -der 0 2>/dev/null' 1 +test 'cat "$OPENSSL_DER" | ./px5g checkend 0 2>/dev/null' 1 + + +[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting px5g eckey ...\n" + +[ "$PRINT_PASSED" -gt 1 ] && printf " * standard curves:\n" +test './px5g eckey P-256 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey P-384 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey secp384r1 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey secp256r1 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey secp256k1 | openssl ec -check 2>/dev/null' 0 + +[ "$PRINT_PASSED" -gt 1 ] && printf " * more curves:\n" +test './px5g eckey P-521 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey secp521r1 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey secp224r1 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey secp224k1 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey secp192r1 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey secp192k1 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey brainpoolP512r1 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey brainpoolP384r1 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey brainpoolP256r1 | openssl ec -check 2>/dev/null' 0 + +[ "$PRINT_PASSED" -gt 1 ] && printf " * other options:\n" +test './px5g eckey -out /proc/self/fd/1 | openssl ec -check 2>/dev/null' 0 +test './px5g eckey -der | openssl ec -check -inform der 2>/dev/null' 0 + + +[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting px5g rsakey ...\n" + +[ "$PRINT_PASSED" -gt 1 ] && printf " * standard exponent:\n" +test './px5g rsakey | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey 512 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey 1024 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey 2048 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey 4096 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey 1111 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey 0 2>/dev/null' 1 + +[ "$PRINT_PASSED" -gt 1 ] && printf " * small exponent:\n" +test './px5g rsakey -3 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey -3 512 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey -3 1024 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey -3 2048 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey -3 4096 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey -3 1111 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey -3 0 2>/dev/null' 1 + +[ "$PRINT_PASSED" -gt 1 ] && printf " * other options:\n" +test './px5g rsakey -out /proc/self/fd/1 | openssl rsa -check 2>/dev/null' 0 +test './px5g rsakey -der | openssl rsa -check -inform der 2>/dev/null' 0 + + +[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting px5g selfsigned ...\n" + +test './px5g selfsigned -der | openssl x509 -checkend 0 -inform der ' 0 +test './px5g selfsigned -days 1 | openssl x509 -checkend 0 ' 0 +test './px5g selfsigned -days 1 | openssl x509 -checkend 86300' 0 +test './px5g selfsigned -days 1 | openssl x509 -checkend 86400' 1 +test './px5g selfsigned -out /proc/self/fd/1 | openssl x509 -checkend 0 ' 0 +test './px5g selfsigned -newkey rsa:666 | openssl x509 -checkend 0 ' 0 +test './px5g selfsigned -newkey ec | openssl x509 -checkend 0 ' 0 +test './px5g selfsigned -newkey ec -pkeyopt ec_paramgen_curve:secp384r1 \ + | openssl x509 -checkend 0 ' 0 +test './px5g selfsigned -subj $SUBJECT | openssl x509 -noout \ + -subject -nameopt compat | grep -q subject=$SUBJECT 2>/dev/null' 0 +test './px5g selfsigned -out /dev/null -keyout /proc/self/fd/1 \ + | openssl rsa -check 2>/dev/null ' 0 + + +rm "$OPENSSL_PEM" "$OPENSSL_DER" diff --git a/net/nginx-util/src/ubus-cxx.hpp b/net/nginx-util/src/ubus-cxx.hpp new file mode 100644 index 000000000..21dc26ff8 --- /dev/null +++ b/net/nginx-util/src/ubus-cxx.hpp @@ -0,0 +1,446 @@ +#ifndef _UBUS_CXX_HPP +#define _UBUS_CXX_HPP + +extern "C" { //TODO(pst): remove when in upstream +#include +} +#include +#include +#include +#include +#include +#include +#ifndef NDEBUG +#include +#endif + + +// // example for checking if there is a key: +// if (ubus::call("service", "list", 1000).filter("cron")) { +// std::cout<<"Cron is active (with or without instances) "< void +// { +// auto end = message.end(); +// auto explore_internal = +// [&end](auto & explore_ref, auto it, size_t depth=1) -> void +// { +// std::cout<; + + +inline void append(strings & /*dest*/) {} + + +template +inline void append(strings & dest, strings src, Strings ...more) +{ + dest.reserve(dest.size() + src.size()); + dest.insert(std::end(dest), std::make_move_iterator(std::begin(src)), + std::make_move_iterator(std::end(src))); + append(dest, std::move(more)...); +} + + +template +inline void append(strings & dest, S src, Strings ...more) +{ + dest.push_back(std::move(src)); + append(dest, std::move(more)...); +} + + + +class iterator { + +private: + + const strings & keys; + + const size_t n = 0; + + size_t i = 0; + + const blob_attr * pos = nullptr; + + std::unique_ptr cur{}; + + iterator * parent = nullptr; + + size_t rem = 0; + + + [[nodiscard]] inline auto matches() const -> bool + { + return (keys[i].empty() || blobmsg_name(cur->pos)==keys[i]); + } + + + explicit iterator(iterator * par) + : keys{par->keys}, n{par->n}, pos{par->pos}, cur{this}, parent{par} + { + if (pos!=nullptr) { + rem = blobmsg_data_len(pos); + pos = static_cast(blobmsg_data(pos)); + } + } + + +public: + + + explicit iterator(const blob_attr * msg, const strings & filter) + : keys{filter}, n{keys.size()-1}, pos{msg}, cur{this} + { + if (pos!=nullptr) { + rem = blobmsg_data_len(pos); + pos = static_cast(blobmsg_data(pos)); + + if (rem==0) { pos = nullptr; } + else if (i!=n || !matches()) { ++*this; } + } + } + + + inline iterator(iterator &&) = default; + + + inline iterator(const iterator &) = delete; + + + inline auto operator=(const iterator &) -> iterator & = delete; + + + inline auto operator=(iterator &&) -> iterator & = delete; + + + inline auto operator*() { return cur->pos; } + + + inline auto operator!=(const iterator & rhs) + { return (cur->rem!=rhs.cur->rem || cur->pos!=rhs.cur->pos); } + + + auto operator++() -> iterator &; + + + inline ~iterator() = default; + +}; + + + +class message { + +private: + + const std::shared_ptr msg{}; // initialized by callback. + + const strings keys{}; + + +public: + + inline explicit message(std::shared_ptr message, + strings filter={""}) + : msg{std::move(message)}, keys{std::move(filter)} {} + + + inline message(message &&) = default; + + + inline message(const message &) = delete; + + + inline auto operator=(message &&) -> message & = delete; + + + inline auto operator=(const message &) -> message & = delete; + + + [[nodiscard]] inline auto begin() const -> iterator + { return iterator{msg.get(), keys}; } + + + [[nodiscard]] inline auto end() const -> iterator + { return iterator{nullptr, keys}; } + + + inline explicit operator bool() const { return begin()!=end(); } + + + template + auto filter(Strings ...filter) + { + strings both{}; + if (keys.size()!=1 || !keys[0].empty()) { both = keys; } + append(both, std::move(filter)...); + return std::move(message{msg, std::move(both)}); + } + + + inline ~message() = default; + +}; + + + +class ubus { + +private: + + static std::mutex buffering; + + +public: + + inline ubus() = delete; + + + inline ubus(ubus &&) = delete; + + + inline ubus(const ubus &) = delete; + + + inline auto operator=(ubus &&) -> auto && = delete; + + + inline auto operator=(const ubus &) -> auto & = delete; + + + static auto get_context() -> ubus_context * + { + static auto ubus_freeing = [] (ubus_context * ctx) { ubus_free(ctx); }; + static std::unique_ptr + lazy_ctx{ubus_connect(nullptr), ubus_freeing}; + + if (!lazy_ctx) { // it could be available on a later call: + static std::mutex connecting; + + connecting.lock(); + if (!lazy_ctx) { lazy_ctx.reset(ubus_connect(nullptr)); } + connecting.unlock(); + + if (!lazy_ctx) { + throw std::runtime_error("ubus error: cannot connect context"); + } + } + + return lazy_ctx.get(); + } + + + static auto lock_and_get_shared_blob_buf() -> blob_buf * + { + static blob_buf buf; + + static auto blob_buf_freeing = [] (blob_buf * b) { blob_buf_free(b); }; + static std::unique_ptr + created_to_free_on_the_end_of_life{&buf, blob_buf_freeing}; + + buffering.lock(); + blob_buf_init(&buf, 0); + return &buf; + } + + + static void unlock_shared_blob_buf() { buffering.unlock(); } + + + inline ~ubus() = delete; + +}; + + +auto call(const char * path, const char * method="", int timeout=500); + + + +// ------------------------- implementation: ---------------------------------- + + +std::mutex ubus::buffering; + + +inline auto iterator::operator++() -> iterator & +{ + for(;;) { + #ifndef NDEBUG + std::cout<')<<" look for "<pos)<pos); + if ( (id==BLOBMSG_TYPE_TABLE || id==BLOBMSG_TYPE_ARRAY) + && ipos)>0 ) + { //immmerge: + ++i; + + auto tmp = cur.release(); + cur = std::unique_ptr{new iterator(tmp)}; + + } else { + while (true) { + cur->rem -= blob_pad_len(cur->pos); + cur->pos = blob_next(cur->pos); + auto len = blob_pad_len(cur->pos); + + if (cur->rem>0 && len<=cur->rem && len>=sizeof(blob_attr)) + { break; } + + //emerge: + auto tmp = cur->parent; + + if (tmp == nullptr) { + cur->pos = nullptr; + return *cur; + } + + cur.reset(tmp); + + --i; + } + } + if (i==n && matches()) { return *cur; } + } +} + + +inline auto call(const char * path, const char * method, const int timeout) +{ + auto ctx = ubus::get_context(); + + uint32_t id; + int err = ubus_lookup_id(ctx, path, &id); + + if (err==0) { // call + ubus_request req{}; + + auto buf = ubus::lock_and_get_shared_blob_buf(); + err = ubus_invoke_async(ctx, id, method, buf->head, &req); + ubus::unlock_shared_blob_buf(); + + if (err==0) { + using msg_t = std::shared_ptr; + + msg_t msg; + req.priv = &msg; + + /* Cannot capture anything (msg), the lambda would be another type. + * Pass a location where to save the message as priv pointer when + * invoking and get it back here: + */ + req.data_cb = [](ubus_request * req, int /*type*/, blob_attr * msg) + { + if ((req == nullptr) || (msg == nullptr)) { return; } + + auto saved = static_cast(req->priv); + if (saved==nullptr || *saved) { return; } + + saved->reset(blob_memdup(msg), free); + if (!*saved) { throw std::bad_alloc(); } + }; + + err = ubus_complete_request(ctx, &req, timeout); + + if (err==0) { return message{msg}; } + } + } + + std::string errmsg = "ubus::call error: cannot invoke"; + errmsg += " (" + std::to_string(err) + ") " + path + " " + method; + throw std::runtime_error(errmsg.c_str()); +} + + +} // namespace ubus + + +#endif