This can do the main work of nginx/nginx-ssl init script. For nginx-ssl it can create selfsigned certificates, too. It uses libpcre and libopenssl iff nginx(-ssl) uses them. Signed-off-by: Peter Stadler <peter.stadler@student.uibk.ac.at>lilik-openwrt-22.03
@ -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 <regex>) | |||
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)) |
@ -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) |
@ -0,0 +1,658 @@ | |||
#include <thread> | |||
#ifdef NO_PCRE | |||
#include <regex> | |||
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<const _Line & ...xn> | |||
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<char clim='\0'> | |||
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<const std::string_view & strptr, char clim='\0'> | |||
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<NGINX_UTIL>, _space, _escape<ADD_SSL_FCT,'\''>, _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<LAN_LISTEN,'\''>, _end>(); | |||
static const auto NGX_INCLUDE_LAN_LISTEN_DEFAULT = Line::build | |||
< _begin, _escape<_include>, _space, | |||
_escape<LAN_LISTEN_DEFAULT, '\''>, _end >(); | |||
static const auto NGX_INCLUDE_LAN_SSL_LISTEN = Line::build | |||
<_begin, _escape<_include>, _space, _escape<LAN_SSL_LISTEN, '\''>, _end>(); | |||
static const auto NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT = Line::build | |||
< _begin, _escape<_include>, _space, | |||
_escape<LAN_SSL_LISTEN_DEFAULT, '\''>, _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 "<<prefix<<".conf: "; | |||
std::cerr<<adds<<std::endl; | |||
} | |||
return; | |||
} | |||
auto errmsg = std::string{"add_ssl_directives_to error: "}; | |||
errmsg += "cannot add SSL directives to " + name + ".conf, missing: "; | |||
errmsg += NGX_SERVER_NAME.STR(name, "\n ") + "\n"; | |||
throw std::runtime_error(errmsg.c_str()); | |||
} | |||
template<typename T> | |||
inline auto num2hex(T bytes) -> std::array<char, 2*sizeof(bytes)+1> | |||
{ | |||
constexpr auto n = 2*sizeof(bytes); | |||
std::array<char, n+1> str{}; | |||
for (size_t i=0; i<n; ++i) { | |||
static const std::array<char, 17> 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<typename T> | |||
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<steps; ++i) { | |||
if (!urandom.good()) { throw std::runtime_error("get_nonce error"); } | |||
nonce = (nonce << move) + static_cast<unsigned>(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<const size_t *>( | |||
static_cast<const void *>(&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 ..."<<std::endl; | |||
if (remove(tmpcrtpath.c_str())!=0) { | |||
auto errmsg = "\t cannot remove "+tmpcrtpath; | |||
perror(errmsg.c_str()); | |||
} | |||
if (remove(tmpkeypath.c_str())!=0) { | |||
auto errmsg = "\t cannot remove "+tmpkeypath; | |||
perror(errmsg.c_str()); | |||
} | |||
throw; | |||
} | |||
if ( rename(tmpcrtpath.c_str(), crtpath.c_str())!=0 || | |||
rename(tmpkeypath.c_str(), keypath.c_str())!=0 ) | |||
{ | |||
auto errmsg = std::string{"create_ssl_certificate warning: "}; | |||
errmsg += "cannot move "+tmpcrtpath+" to "+crtpath; | |||
errmsg += " or "+tmpkeypath+" to "+keypath+", continuing ... "; | |||
perror(errmsg.c_str()); | |||
} | |||
} | |||
void use_cron_to_recreate_certificate(const std::string & name) | |||
{ | |||
static const char * filename = "/etc/crontabs/root"; | |||
std::string conf{}; | |||
try { conf = read_file(filename); } | |||
catch (const std::ifstream::failure &) { /* is ok if not found, create. */ } | |||
const std::string add = get_if_missed(conf, CRON_CMD, name); | |||
if (add.length() > 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<<name<<"' annually with cron."<<std::endl; | |||
} | |||
} | |||
void add_ssl_if_needed(const std::string & name) | |||
{ | |||
try { add_ssl_directives_to(name, name==LAN_NAME); } | |||
catch (...) { | |||
std::cerr<<"add_ssl_if_needed error: "; | |||
std::cerr<<"cannot add SSL directives to "<<name<<".conf"<<std::endl; | |||
throw; | |||
} | |||
const auto crtpath = std::string{CONF_DIR} + name + ".crt"; | |||
const auto keypath = std::string{CONF_DIR} + name + ".key"; | |||
constexpr auto remaining_seconds = (365 + 32)*24*60*60; | |||
constexpr auto validity_days = 3*(365 + 31); | |||
bool is_valid = true; | |||
if (access(keypath.c_str(), R_OK) != 0 || | |||
access(crtpath.c_str(), R_OK) != 0) | |||
{ is_valid = false; } | |||
else { | |||
try { | |||
if (!checkend(crtpath, remaining_seconds)) { | |||
is_valid = false; | |||
} | |||
} | |||
catch (...) { // something went wrong, maybe it is in DER format: | |||
try { | |||
if (!checkend(crtpath, remaining_seconds, false)) { | |||
is_valid = false; | |||
} | |||
} | |||
catch (...) { // it has neither DER nor PEM format, rebuild. | |||
is_valid = false; | |||
} | |||
} | |||
} | |||
if (!is_valid) { create_ssl_certificate(crtpath, keypath, validity_days); } | |||
try { use_cron_to_recreate_certificate(name); } | |||
catch (...) { | |||
std::cerr<<"add_ssl_if_needed warning: "; | |||
std::cerr<<"cannot use cron to rebuild certificate for "<<name<<"\n"; | |||
} | |||
} | |||
void del_ssl_directives_from(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_LISTEN_DEFAULT,"",indent) : | |||
get_if_missed(conf, NGX_INCLUDE_LAN_LISTEN, "", indent); | |||
if (adds.length() > 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 "<<prefix<<".conf\n"; | |||
} | |||
return; | |||
} | |||
auto errmsg = std::string{"del_ssl_directives_from error: "}; | |||
errmsg += "cannot delete SSL directives from " + name + ".conf, missing: "; | |||
errmsg += NGX_SERVER_NAME.STR(name, "\n ") + "\n"; | |||
throw std::runtime_error(errmsg.c_str()); | |||
} | |||
void del_ssl(const std::string & name) | |||
{ | |||
static const char * filename = "/etc/crontabs/root"; | |||
try { | |||
const auto const_conf = read_file(filename); | |||
bool changed = false; | |||
auto conf = std::string{}; | |||
size_t prev = 0; | |||
size_t curr = 0; | |||
while ((curr=const_conf.find('\n', prev)) != std::string::npos) { | |||
auto line = const_conf.substr(prev, curr-prev+1); | |||
line = delete_if(line, CRON_CMD.RGX(), std::string{name}, true); | |||
if (line.substr(0,line.size()-1)==CRON_INTERVAL) { changed = true; } | |||
else { conf += line; } | |||
prev = curr + 1; | |||
} | |||
if (changed) { | |||
write_file(filename, conf); | |||
std::cerr<<"Do not rebuild the ssl certificate for '"; | |||
std::cerr<<name<<"' annually with cron anymore."<<std::endl; | |||
#ifndef NO_UBUS | |||
if (ubus::call("service", "list", UBUS_TIMEOUT).filter("cron")) | |||
{ call("/etc/init.d/cron", "reload"); } | |||
#endif | |||
} | |||
} catch (...) { | |||
std::cerr<<"del_ssl warning: "; | |||
std::cerr<<"cannot delete cron job for "<<name<<" in "<<filename<<"\n"; | |||
} | |||
try { del_ssl_directives_from(name, name==LAN_NAME); } | |||
catch (...) { | |||
std::cerr<<"del_ssl error: "; | |||
std::cerr<<"cannot delete SSL directives from "<<name<<".conf\n"; | |||
throw; | |||
} | |||
const auto crtpath = std::string{CONF_DIR} + name + ".crt"; | |||
if (remove(crtpath.c_str())!=0) { | |||
auto errmsg = "del_ssl warning: cannot remove "+crtpath; | |||
perror(errmsg.c_str()); | |||
} | |||
const auto keypath = std::string{CONF_DIR} + name + ".key"; | |||
if (remove(keypath.c_str())!=0) { | |||
auto errmsg = "del_ssl warning: cannot remove "+keypath; | |||
perror(errmsg.c_str()); | |||
} | |||
} | |||
// reuse main(...) and common functions: | |||
#define NGINX_OPENSSL | |||
#include "nginx-util.cpp" |
@ -0,0 +1,157 @@ | |||
// This file is included in nginx-ssl-util.cpp, which defines NGINX_OPENSSL. | |||
#ifndef __NGINX_UTIL_C | |||
#define __NGINX_UTIL_C | |||
#include "nginx-util.hpp" | |||
void create_lan_listen() | |||
{ | |||
std::string listen = "# This file is re-created if Nginx starts or" | |||
" a LAN address changes.\n"; | |||
std::string listen_default = listen; | |||
std::string ssl_listen = listen; | |||
std::string ssl_listen_default = listen; | |||
auto add_listen = [&listen, &listen_default | |||
#ifdef NGINX_OPENSSL | |||
,&ssl_listen, &ssl_listen_default | |||
#endif | |||
] | |||
(const std::string &pre, const std::string &ip, const std::string &suf) | |||
-> 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<const char *>(blobmsg_data(ip)), ""); | |||
} | |||
for (auto ip : lan_status.filter("ipv6-address", "", "address")) { | |||
add_listen("[", static_cast<const char *>(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 "<<LAN_NAME<<std::endl; | |||
ex = std::current_exception(); | |||
} | |||
}); | |||
#endif | |||
try { create_lan_listen(); } | |||
catch (...) { | |||
std::cerr<<"init_lan error: cannot create LAN listen directives"<<std::endl; | |||
ex = std::current_exception(); | |||
} | |||
#ifdef NGINX_OPENSSL | |||
thrd.join(); | |||
#endif | |||
if (ex) { std::rethrow_exception(ex); } | |||
} | |||
void get_env() | |||
{ | |||
std::cout<<"NGINX_CONF="<<"'"<<NGINX_CONF<<"'"<<std::endl; | |||
std::cout<<"CONF_DIR="<<"'"<<CONF_DIR<<"'"<<std::endl; | |||
std::cout<<"LAN_NAME="<<"'"<<LAN_NAME<<"'"<<std::endl; | |||
std::cout<<"LAN_LISTEN="<<"'"<<LAN_LISTEN<<"'"<<std::endl; | |||
#ifdef NGINX_OPENSSL | |||
std::cout<<"LAN_SSL_LISTEN="<<"'"<<LAN_SSL_LISTEN<<"'"<<std::endl; | |||
std::cout<<"SSL_SESSION_CACHE_ARG="<<"'"<<LAN_NAME<<"'"<<std::endl; | |||
std::cout<<"SSL_SESSION_TIMEOUT_ARG="<<"'"<<SSL_SESSION_TIMEOUT_ARG<<"'\n"; | |||
std::cout<<"ADD_SSL_FCT="<<"'"<<ADD_SSL_FCT<<"'"<<std::endl; | |||
#endif | |||
} | |||
auto main(int argc, char * argv[]) -> int | |||
{ | |||
// TODO(pst): use std::span when available: | |||
auto args = std::basic_string_view{argv, static_cast<size_t>(argc)}; | |||
auto cmds = std::array{ | |||
std::array<std::string_view, 2>{"init_lan", ""}, | |||
std::array<std::string_view, 2>{"get_env", ""}, | |||
#ifdef NGINX_OPENSSL | |||
std::array<std::string_view, 2>{ADD_SSL_FCT, " server_name" }, | |||
std::array<std::string_view, 2>{"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<<usage<<std::endl; | |||
throw std::runtime_error("main error: argument not recognized"); | |||
} | |||
return 0; | |||
} | |||
catch (const std::exception & e) { std::cerr<<e.what()<<std::endl; } | |||
catch (...) { perror("main error"); } | |||
return 1; | |||
} | |||
#endif |
@ -0,0 +1,120 @@ | |||
#ifndef __NGINX_UTIL_H | |||
#define __NGINX_UTIL_H | |||
#include <array> | |||
#include <cerrno> | |||
#include <cstdio> | |||
#include <cstring> | |||
#include <fstream> | |||
#include <iostream> | |||
#include <string> | |||
#include <string_view> | |||
#include <unistd.h> | |||
#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<typename ...S> | |||
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<<str<<std::flush; | |||
file.close(); | |||
} | |||
auto read_file(const std::string_view & name, | |||
const std::ios_base::openmode mode) -> 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<char>(file)), | |||
std::istreambuf_iterator<char>()); | |||
file.close(); | |||
return ret; | |||
} | |||
template<typename ...S> | |||
auto call(const char * program, S... args) -> pid_t | |||
{ | |||
pid_t pid = fork(); | |||
if (pid==0) { //child: | |||
std::array<char *, sizeof...(args)+2> 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 |
@ -0,0 +1,407 @@ | |||
#ifndef _PX5G_OPENSSL_HPP | |||
#define _PX5G_OPENSSL_HPP | |||
// #define OPENSSL_API_COMPAT 0x10102000L | |||
#include <fcntl.h> | |||
#include <memory> | |||
#include <openssl/bn.h> | |||
#include <openssl/err.h> | |||
#include <openssl/pem.h> | |||
#include <openssl/rsa.h> | |||
#include <string> | |||
#include <unistd.h> | |||
static constexpr auto rsa_min_modulus_bits = 512; | |||
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>; | |||
using X509_NAME_ptr = std::unique_ptr<X509_NAME, decltype(&::X509_NAME_free)>; | |||
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<std::string *>(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<char *>(static_cast<void *>(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 (keysize<rsa_min_modulus_bits || keysize>OPENSSL_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<char *>(static_cast<void *>(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<const unsigned char *>( | |||
static_cast<const void *>(&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 |
@ -0,0 +1,451 @@ | |||
#include <array> | |||
#include <iostream> | |||
#include <string> | |||
#include <string_view> | |||
#include <unistd.h> | |||
#include "px5g-openssl.hpp" | |||
class argv_view { // TODO(pst): use std::span when available. | |||
private: | |||
std::basic_string_view<const char *> 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<size_t>(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(); ++i) { | |||
if (argv[i]=="-der") { | |||
use_pem = false; | |||
} else if (argv[i]=="-in") { | |||
++i; | |||
if (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 "<<argv[i]<<std::endl; | |||
} else { // main option: | |||
intmax_t num = 0; | |||
try { | |||
num = parse_int(argv[i]); | |||
} catch (...) { | |||
auto errmsg = std::string{"checkend error: invalid time "}; | |||
errmsg += argv[i]; | |||
std::throw_with_nested(std::runtime_error(errmsg.c_str())); | |||
} | |||
seconds = static_cast<time_t>(num); | |||
if (num!=static_cast<intmax_t>(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"<<std::endl; | |||
return (valid ? 0 : 1); | |||
} | |||
void eckey(const argv_view & argv) | |||
{ | |||
bool has_main_option = false; | |||
bool use_pem = true; | |||
std::string keypath{}; | |||
int curve = NID_X9_62_prime256v1; | |||
for (size_t i=2; i < argv.size(); ++i) { | |||
if (argv[i]=="-der") { | |||
use_pem = false; | |||
} else if (argv[i]=="-out") { | |||
++i; | |||
if (i >= 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[i]<<std::endl; | |||
} else { //main option: | |||
if (has_main_option) { | |||
throw std::runtime_error | |||
("eckey error: more than one main option"); | |||
} // else: | |||
has_main_option = true; | |||
curve = parse_curve(argv[i]); | |||
} | |||
} | |||
write_key(gen_eckey(curve), keypath, use_pem); | |||
} | |||
void rsakey(const argv_view & argv) | |||
{ | |||
bool has_main_option = false; | |||
bool use_pem = true; | |||
std::string keypath{}; | |||
BN_ULONG exponent = RSA_F4; | |||
int keysize = rsa_min_modulus_bits; | |||
for (size_t i=2; i < argv.size(); ++i) { | |||
if (argv[i]=="-der") { | |||
use_pem = false; | |||
} else if (argv[i]=="-3") { | |||
exponent = 3; | |||
} else if (argv[i]=="-out") { | |||
++i; | |||
if (i >= 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"<<std::endl; | |||
} else { | |||
throw std::runtime_error | |||
("rsakey error: more than one -out file"); | |||
} | |||
} | |||
keypath = argv[i]; | |||
} | |||
else if (argv[i][0]=='-') { | |||
std::cerr<<"rsakey warning: skipping option "<<argv[i]<<std::endl; | |||
} else { //main option: | |||
if (has_main_option) { | |||
throw std::runtime_error("rsakey error: more than one keysize"); | |||
} // else: | |||
has_main_option = true; | |||
try { | |||
keysize = parse_int(argv[i]); | |||
} catch (...) { | |||
std::string errmsg{"rsakey error: invalid keysize "}; | |||
errmsg += argv[i]; | |||
std::throw_with_nested(std::runtime_error(errmsg.c_str())); | |||
} | |||
} | |||
} | |||
write_key(gen_rsakey(keysize, exponent), keypath, use_pem); | |||
} | |||
void selfsigned(const argv_view & argv) | |||
{ | |||
bool use_pem = true; | |||
int days = default_validity; | |||
std::string keypath{}; | |||
std::string crtpath{}; | |||
std::string subject{}; | |||
bool use_rsa = true; | |||
int keysize = rsa_min_modulus_bits; | |||
BN_ULONG exponent = RSA_F4; | |||
int curve = NID_X9_62_prime256v1; | |||
for (size_t i=2; i < argv.size(); ++i) { | |||
if (argv[i]=="-der") { | |||
use_pem = false; | |||
} else if (argv[i]=="-days") { | |||
++i; | |||
try { | |||
days = parse_int(argv[i]); | |||
} catch (...) { | |||
std::string errmsg{"selfsigned error: not a number for -days "}; | |||
errmsg += argv[i].substr(4); | |||
std::throw_with_nested(std::runtime_error(errmsg.c_str())); | |||
} | |||
} | |||
else if (argv[i]=="-newkey") { | |||
++i; | |||
if (i >= 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 "<<argv[i]<<std::endl; | |||
} | |||
} | |||
auto pkey = use_rsa ? gen_rsakey(keysize, exponent) : gen_eckey(curve); | |||
selfsigned(pkey, days, subject, crtpath, use_pem); | |||
if (!keypath.empty()) { write_key(pkey, keypath, use_pem); } | |||
} | |||
auto main(int argc, const char ** argv) -> int | |||
{ | |||
auto args = argv_view{argv, argc}; | |||
auto cmds = std::array{ | |||
std::array<std::string, 2>{"checkend", | |||
" [-der] [-in certificate_path] [seconds_remaining]" | |||
}, | |||
std::array<std::string, 2>{"eckey", | |||
" [-der] [-out key_path] [curve_name]" | |||
}, | |||
std::array<std::string, 2>{"rsakey", | |||
" [-der] [-out key_path] [-3] [key_size]" | |||
}, | |||
std::array<std::string, 2>{"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<<usage<<std::flush; | |||
auto print_nested = | |||
[](auto && self, const std::exception & outer, int depth=0) -> void | |||
{ | |||
std::cerr<<std::string(depth, '\t')<<outer.what()<<std::endl; | |||
try { std::rethrow_if_nested(outer); } | |||
catch (const std::exception & inner) { self(self, inner, depth+1); } | |||
}; | |||
print_nested(print_nested, e); | |||
return 1; | |||
} | |||
catch (...) { | |||
std::cerr<<*argv<<" unknown error."<<std::endl; | |||
return 2; | |||
} | |||
return 0; | |||
} |
@ -0,0 +1,505 @@ | |||
// implementing *some* <regex> functions using pcre for performance: | |||
#ifndef __REGEXP_PCRE_HPP | |||
#define __REGEXP_PCRE_HPP | |||
#include <array> | |||
#include <pcre.h> | |||
#include <stdexcept> | |||
#include <string> | |||
#include <vector> | |||
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<regex_constants::error_type,86> 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 <int> 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_constants::error_type, 86> 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 |
@ -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" |
@ -0,0 +1,446 @@ | |||
#ifndef _UBUS_CXX_HPP | |||
#define _UBUS_CXX_HPP | |||
extern "C" { //TODO(pst): remove when in upstream | |||
#include <libubus.h> | |||
} | |||
#include <cassert> | |||
#include <memory> | |||
#include <mutex> | |||
#include <string> | |||
#include <utility> | |||
#include <vector> | |||
#ifndef NDEBUG | |||
#include <iostream> | |||
#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) "<<std::endl; | |||
// } | |||
// // example for getting values: | |||
// auto lan_status = ubus::call("network.interface.lan", "status"); | |||
// for (auto x : lan_status.filter("ipv6-address", "", "address")) { | |||
// std::cout<<"["<<blobmsg_get_string(x)<<"] "; | |||
// } | |||
// for (auto x : lan_status.filter("ipv4-address", "").filter("address")) { | |||
// std::cout<<blobmsg_get_string(x)<<" "; | |||
// } | |||
// std::cout<<std::endl; | |||
// // example for exploring: | |||
// ubus::strings keys{"ipv4-address", "", "*"}; | |||
// for (auto x : ubus::call("network.interface.lan", "status").filter(keys)) { | |||
// std::cout<<blobmsg_name(x)<<": "; | |||
// switch (blob_id(x)) { | |||
// case BLOBMSG_TYPE_UNSPEC: std::cout<<"[unspecified]"; break; | |||
// case BLOBMSG_TYPE_ARRAY: std::cout<<"[array]"; break; | |||
// case BLOBMSG_TYPE_TABLE: std::cout<<"[table]"; break; | |||
// case BLOBMSG_TYPE_STRING: std::cout<<blobmsg_get_string(x); break; | |||
// case BLOBMSG_TYPE_INT64: std::cout<<blobmsg_get_u64(x); break; | |||
// case BLOBMSG_TYPE_INT32: std::cout<<blobmsg_get_u32(x); break; | |||
// case BLOBMSG_TYPE_INT16: std::cout<<blobmsg_get_u16(x); break; | |||
// case BLOBMSG_TYPE_BOOL: std::cout<<blobmsg_get_bool(x); break; | |||
// case BLOBMSG_TYPE_DOUBLE: std::cout<<blobmsg_get_double(x); break; | |||
// default: std::cout<<"[unknown]"; | |||
// } | |||
// std::cout<<std::endl; | |||
// } | |||
// // example for recursive exploring (output like from the original ubus call) | |||
// const auto explore = [](auto message) -> void | |||
// { | |||
// auto end = message.end(); | |||
// auto explore_internal = | |||
// [&end](auto & explore_ref, auto it, size_t depth=1) -> void | |||
// { | |||
// std::cout<<std::endl; | |||
// bool first = true; | |||
// for (; it!=end; ++it) { | |||
// auto * attr = *it; | |||
// if (first) { first = false; } | |||
// else { std::cout<<",\n"; } | |||
// std::cout<<std::string(depth, '\t'); | |||
// std::string name = blobmsg_name(attr); | |||
// if (name != "") { std::cout<<"\""<<name<<"\": "; } | |||
// switch (blob_id(attr)) { | |||
// case BLOBMSG_TYPE_UNSPEC: std::cout<<"(unspecified)"; break; | |||
// case BLOBMSG_TYPE_ARRAY: | |||
// std::cout<<"["; | |||
// explore_ref(explore_ref, ubus::iterator{attr}, depth+1); | |||
// std::cout<<"\n"<<std::string(depth, '\t')<<"]"; | |||
// break; | |||
// case BLOBMSG_TYPE_TABLE: | |||
// std::cout<<"{"; | |||
// explore_ref(explore_ref, ubus::iterator{attr}, depth+1); | |||
// std::cout<<"\n"<<std::string(depth, '\t')<<"}"; | |||
// break; | |||
// case BLOBMSG_TYPE_STRING: | |||
// std::cout<<"\""<<blobmsg_get_string(attr)<<"\""; | |||
// break; | |||
// case BLOBMSG_TYPE_INT64: | |||
// std::cout<<blobmsg_get_u64(attr); | |||
// break; | |||
// case BLOBMSG_TYPE_INT32: | |||
// std::cout<<blobmsg_get_u32(attr); | |||
// break; | |||
// case BLOBMSG_TYPE_INT16: | |||
// std::cout<<blobmsg_get_u16(attr); | |||
// break; | |||
// case BLOBMSG_TYPE_BOOL: | |||
// std::cout<<(blobmsg_get_bool(attr) ? "true" : "false"); | |||
// break; | |||
// case BLOBMSG_TYPE_DOUBLE: | |||
// std::cout<<blobmsg_get_double(attr); | |||
// break; | |||
// default: std::cout<<"(unknown)"; break; | |||
// } | |||
// } | |||
// }; | |||
// std::cout<<"{"; | |||
// explore_internal(explore_internal, message.begin()); | |||
// std::cout<<"\n}"<<std::endl; | |||
// }; | |||
// explore(ubus::call("network.interface.lan", "status")); | |||
namespace ubus { | |||
using strings = std::vector<std::string>; | |||
inline void append(strings & /*dest*/) {} | |||
template<class ...Strings> | |||
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<class S, class ...Strings> | |||
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<iterator> 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<blob_attr *>(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<blob_attr *>(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<const blob_attr> msg{}; // initialized by callback. | |||
const strings keys{}; | |||
public: | |||
inline explicit message(std::shared_ptr<const blob_attr> 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<class ...Strings> | |||
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<ubus_context, decltype(ubus_freeing)> | |||
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<blob_buf, decltype(blob_buf_freeing)> | |||
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<<std::string(i,'>')<<" look for "<<keys[i]<<" at "; | |||
std::cout<<blobmsg_name(cur->pos)<<std::endl; | |||
#endif | |||
auto id = blob_id(cur->pos); | |||
if ( (id==BLOBMSG_TYPE_TABLE || id==BLOBMSG_TYPE_ARRAY) | |||
&& i<n | |||
&& matches() | |||
&& blobmsg_data_len(cur->pos)>0 ) | |||
{ //immmerge: | |||
++i; | |||
auto tmp = cur.release(); | |||
cur = std::unique_ptr<iterator>{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<const blob_attr>; | |||
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<msg_t *>(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 |