ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/external/subpack/net/nginx-util/src/nginx-ssl-util.hpp b/external/subpack/net/nginx-util/src/nginx-ssl-util.hpp
new file mode 100644
index 0000000..5a64b00
--- /dev/null
+++ b/external/subpack/net/nginx-util/src/nginx-ssl-util.hpp
@@ -0,0 +1,1124 @@
+#ifndef __NGINX_SSL_UTIL_HPP
+#define __NGINX_SSL_UTIL_HPP
+
+#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 =  // TODO(pst) deprecate
+    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 replace_if(const std::string& conf,
+                const rgx::regex& rgx,
+                const std::string& val,
+                const std::string& insert) -> std::string;
+
+auto replace_listen(const std::string& conf, const std::array<const char*, 2>& ngx_port)
+    -> std::string;
+
+auto check_ssl_certificate(const std::string& crtpath, const std::string& keypath) -> bool;
+
+auto contains(const std::string& sentence, const std::string& word) -> bool;
+
+auto get_uci_section_for_name(const std::string& name) -> uci::section;
+
+void add_ssl_if_needed(const std::string& name);
+
+void add_ssl_if_needed(const std::string& name,
+                       std::string_view manage,
+                       std::string_view crt,
+                       std::string_view key);
+
+void install_cron_job(const Line& CRON_LINE, const std::string& name = "");
+
+void remove_cron_job(const Line& CRON_LINE, const std::string& name = "");
+
+auto del_ssl_legacy(const std::string& name) -> bool;
+
+void del_ssl(const std::string& name);
+
+void del_ssl(const std::string& name, std::string_view manage);
+
+auto check_ssl(const uci::package& pkg, bool is_enabled) -> bool;
+
+inline void check_ssl(const uci::package& pkg)
+{
+    if (!check_ssl(pkg, is_enabled(pkg))) {
+#ifndef NO_UBUS
+        if (ubus::call("service", "list", UBUS_TIMEOUT).filter("nginx")) {
+            call("/etc/init.d/nginx", "reload");
+            std::cerr << "Reload Nginx.\n";
+        }
+#endif
+    }
+}
+
+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*#[^\n]*(?=\n))*(\s*))";
+    }};
+
+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+)"; }};
+
+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)"};
+    }  // capture it as _end captures it, too.
+};
+
+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*(;(?:[\t ]*#[^\n]*)?))"};
+          }};
+
+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 == '\0' ? std::string{strptr.data()} : 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 += '\\';
+                    ret += c;
+                    break;
+                case '_':
+                case '-':
+                    ret += c;
+                    break;
+                default:
+                    if ((isalpha(c) != 0) || (isdigit(c) != 0)) {
+                        ret += c;
+                    }
+                    else {
+                        ret += std::string{"["} + c + "]";
+                    }
+            }
+        }
+        return "(?:" + ret + "|'" + ret + "'" + "|\"" + ret + "\"" + ")";
+    }};
+
+constexpr std::string_view _check_ssl = "check_ssl";
+
+constexpr std::string_view _server_name = "server_name";
+
+constexpr std::string_view _listen = "listen";
+
+constexpr std::string_view _include = "include";
+
+constexpr std::string_view _ssl_certificate = "ssl_certificate";
+
+constexpr std::string_view _ssl_certificate_key = "ssl_certificate_key";
+
+constexpr std::string_view _ssl_session_cache = "ssl_session_cache";
+
+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_CHECK =
+    Line::build<_space, _escape<NGINX_UTIL>, _space, _escape<_check_ssl, '\''>, _newline>();
+
+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>();
+
+static const auto NGX_LISTEN = Line::build<_begin, _escape<_listen>, _space, _capture<';'>, _end>();
+
+static const auto NGX_PORT_80 = std::array<const char*, 2>{
+    R"(^\s*([^:]*:|\[[^\]]*\]:)?80(\s|$|;))",
+    "$01443 ssl$2",
+};
+
+static const auto NGX_PORT_443 = std::array<const char*, 2>{
+    R"(^\s*([^:]*:|\[[^\]]*\]:)?443(\s.*)?\sssl(\s|$|;))",
+    "$0180$2$3",
+};
+
+// ------------------------- implementation: ----------------------------------
+
+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 value = match.str(match.size() - 2);
+
+        if (value == val || value == "'" + val + "'" || value == '"' + val + '"') {
+            return "";
+        }
+    }
+
+    return LINE.STR(val, indent);
+}
+
+auto replace_if(const std::string& conf,
+                const rgx::regex& rgx,
+                const std::string& val,
+                const std::string& insert) -> std::string
+{
+    std::string ret{};
+    auto pos = conf.begin();
+
+    auto skip = 0;
+    for (rgx::smatch match; rgx::regex_search(pos, conf.end(), match, rgx);
+         pos += match.position(match.size() - 1))
+    {
+        auto i = match.size() - 2;
+        const std::string value = match.str(i);
+
+        bool compare = !val.empty();
+        if (compare && value != val && value != "'" + val + "'" && value != '"' + val + '"') {
+            ret.append(pos + skip, pos + match.position(i) + match.length(i));
+            skip = 0;
+        }
+        else {
+            ret.append(pos + skip, pos + match.position(match.size() > 2 ? 1 : 0));
+            ret += insert;
+            skip = 1;
+        }
+    }
+
+    ret.append(pos + skip, conf.end());
+    return ret;
+}
+
+auto replace_listen(const std::string& conf, const std::array<const char*, 2>& ngx_port)
+    -> std::string
+{
+    std::string ret{};
+    auto pos = conf.begin();
+
+    for (rgx::smatch match; rgx::regex_search(pos, conf.end(), match, NGX_LISTEN.RGX());
+         pos += match.position(match.size() - 1))
+    {
+        auto i = match.size() - 2;
+        ret.append(pos, pos + match.position(i));
+        ret += rgx::regex_replace(match.str(i), rgx::regex{ngx_port[0]}, ngx_port[1]);
+    }
+
+    ret.append(pos, conf.end());
+    return ret;
+}
+
+inline void add_ssl_directives_to(const std::string& name)
+{
+    const std::string prefix = std::string{CONF_DIR} + name;
+
+    const std::string const_conf = read_file(prefix + ".conf");
+
+    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 (!contains(match.str(2), name)) {
+            continue;
+        }  // else:
+
+        const std::string indent = match.str(1);
+
+        auto adds = std::string{};
+
+        adds += get_if_missed(const_conf, NGX_SSL_CRT, prefix + ".crt", indent);
+
+        adds += get_if_missed(const_conf, NGX_SSL_KEY, prefix + ".key", indent);
+
+        adds += get_if_missed(const_conf, NGX_SSL_SESSION_CACHE, SSL_SESSION_CACHE_ARG(name),
+                              indent, false);
+
+        adds += get_if_missed(const_conf, NGX_SSL_SESSION_TIMEOUT,
+                              std::string{SSL_SESSION_TIMEOUT_ARG}, indent, false);
+
+        pos += match.position(0) + match.length(0);
+        std::string conf =
+            std::string(const_conf.begin(), pos) + adds + std::string(pos, const_conf.end());
+
+        conf = replace_if(conf, NGX_INCLUDE_LAN_LISTEN_DEFAULT.RGX(), "",
+                          NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT.STR("", indent));
+
+        conf = replace_if(conf, NGX_INCLUDE_LAN_LISTEN.RGX(), "",
+                          NGX_INCLUDE_LAN_SSL_LISTEN.STR("", indent));
+
+        conf = replace_listen(conf, NGX_PORT_80);
+
+        if (conf != const_conf) {
+            write_file(prefix + ".conf", conf);
+            std::cerr << "Added SSL directives to " << prefix << ".conf\n";
+        }
+
+        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);
+}
+
+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;
+}
+
+inline void create_ssl_certificate(const std::string& crtpath,
+                                   const std::string& keypath,
+                                   const int days = 792)
+{
+    size_t nonce = 0;
+
+    try {
+        nonce = get_nonce(nonce);
+    }
+
+    catch (...) {  // the address of a variable should be random enough:
+        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) sic:
+        nonce += reinterpret_cast<size_t>(&crtpath);
+    }
+
+    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());
+    }
+
+    std::cerr << "Created self-signed SSL certificate '" << crtpath;
+    std::cerr << "' with key '" << keypath << "'.\n";
+}
+
+auto check_ssl_certificate(const std::string& crtpath, const std::string& keypath) -> bool
+{
+    {  // paths are relative to dir:
+        auto dir = std::string_view{"/etc/nginx"};
+        auto crt_rel = crtpath[0] != '/';
+        auto key_rel = keypath[0] != '/';
+        if ((crt_rel || key_rel) && (chdir(dir.data()) != 0)) {
+            auto errmsg = std::string{"check_ssl_certificate error: entering "};
+            errmsg += dir;
+            perror(errmsg.c_str());
+            errmsg += " (need to change directory since the given ";
+            errmsg += crt_rel ? "ssl_certificate '" + crtpath : std::string{};
+            errmsg += crt_rel && key_rel ? "' and " : "";
+            errmsg += key_rel ? "ssl_certificate_key '" + keypath : std::string{};
+            errmsg += crt_rel && key_rel ? "' are" : "' is a";
+            errmsg += " relative path";
+            errmsg += crt_rel && key_rel ? "s)" : ")";
+            throw std::runtime_error(errmsg);
+        }
+    }
+
+    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);
+    }
+
+    return is_valid;
+}
+
+auto contains(const std::string& sentence, const std::string& word) -> bool
+{
+    auto pos = sentence.find(word);
+    if (pos == std::string::npos) {
+        return false;
+    }
+    if (pos != 0 && (isgraph(sentence[pos - 1]) != 0)) {
+        return false;
+    }
+    if (isgraph(sentence[pos + word.size()]) != 0) {
+        return false;
+    }
+    // else:
+    return true;
+}
+
+auto get_uci_section_for_name(const std::string& name) -> uci::section
+{
+    auto pkg = uci::package{"nginx"};  // let it throw.
+
+    auto uci_enabled = is_enabled(pkg);
+
+    if (uci_enabled) {
+        for (auto sec : pkg) {
+            if (sec.name() == name) {
+                return sec;
+            }
+        }
+        // try interpreting 'name' as FQDN:
+        for (auto sec : pkg) {
+            for (auto opt : sec) {
+                if (opt.name() == "server_name") {
+                    for (auto itm : opt) {
+                        if (contains(itm.name(), name)) {
+                            return sec;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    auto errmsg = std::string{"lookup error: neither there is a file named '"};
+    errmsg += std::string{CONF_DIR} + name + ".conf' nor the UCI config has ";
+    if (uci_enabled) {
+        errmsg += "a nginx server with section name or 'server_name': " + name;
+    }
+    else {
+        errmsg += "been enabled by:\n\tuci set nginx.global.uci_enable=true";
+    }
+    throw std::runtime_error(errmsg);
+}
+
+inline auto add_ssl_to_config(const std::string& name,
+                              const std::string_view manage = "self-signed",
+                              const std::string_view crt = "",
+                              const std::string_view key = "")
+{
+    auto sec = get_uci_section_for_name(name);  // let it throw.
+    auto secname = sec.name();
+
+    struct {
+        std::string crt;
+        std::string key;
+    } ret;
+
+    std::cerr << "Adding SSL directives to UCI server: nginx." << secname << "\n";
+
+    std::cerr << "\t" << MANAGE_SSL << "='" << manage << "'\n";
+    sec.set(MANAGE_SSL.data(), manage.data());
+
+    if (!crt.empty() && !key.empty()) {
+        sec.set("ssl_certificate", crt.data());
+        std::cerr << "\tssl_certificate='" << crt << "'\n";
+        sec.set("ssl_certificate_key", key.data());
+        std::cerr << "\tssl_certificate_key='" << key << "'\n";
+    }
+
+    auto cache = false;
+    auto timeout = false;
+    for (auto opt : sec) {
+        if (opt.name() == "ssl_session_cache") {
+            cache = true;
+            continue;
+        }  // else:
+
+        if (opt.name() == "ssl_session_timeout") {
+            timeout = true;
+            continue;
+        }
+
+        // else:
+        for (auto itm : opt) {
+            if (opt.name() == "ssl_certificate_key") {
+                ret.key = itm.name();
+            }
+
+            else if (opt.name() == "ssl_certificate") {
+                ret.crt = itm.name();
+            }
+
+            else if (opt.name() == "listen") {
+                auto val = regex_replace(itm.name(), rgx::regex{NGX_PORT_80[0]}, NGX_PORT_80[1]);
+                if (val != itm.name()) {
+                    std::cerr << "\t" << opt.name() << "='" << val << "' (replacing)\n";
+                    itm.rename(val.c_str());
+                }
+            }
+        }
+    }
+
+    if (ret.crt.empty()) {
+        ret.crt = std::string{CONF_DIR} + name + ".crt";
+        std::cerr << "\tssl_certificate='" << ret.crt << "'\n";
+        sec.set("ssl_certificate", ret.crt.c_str());
+    }
+
+    if (ret.key.empty()) {
+        ret.key = std::string{CONF_DIR} + name + ".key";
+        std::cerr << "\tssl_certificate_key='" << ret.key << "'\n";
+        sec.set("ssl_certificate_key", ret.key.c_str());
+    }
+
+    if (!cache) {
+        std::cerr << "\tssl_session_cache='" << SSL_SESSION_CACHE_ARG(name) << "'\n";
+        sec.set("ssl_session_cache", SSL_SESSION_CACHE_ARG(name).data());
+    }
+
+    if (!timeout) {
+        std::cerr << "\tssl_session_timeout='" << SSL_SESSION_TIMEOUT_ARG << "'\n";
+        sec.set("ssl_session_timeout", SSL_SESSION_TIMEOUT_ARG.data());
+    }
+
+    sec.commit();
+
+    return ret;
+}
+
+void install_cron_job(const Line& CRON_LINE, 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_LINE, name);
+
+    if (add.length() > 0) {
+#ifndef NO_UBUS
+        if (!ubus::call("service", "list", UBUS_TIMEOUT).filter("cron")) {
+            std::string errmsg{"install_cron_job error: "};
+            errmsg += "Cron unavailable to re-create the ssl certificate";
+            errmsg += (name.empty() ? std::string{"s\n"} : " for '" + name + "'\n");
+            throw std::runtime_error(errmsg);
+        }  // else active with or without instances:
+#endif
+
+        const auto* pre = (conf.length() == 0 || conf.back() == '\n' ? "" : "\n");
+        write_file(filename, pre + std::string{CRON_INTERVAL} + add, std::ios::app);
+
+#ifndef NO_UBUS
+        call("/etc/init.d/cron", "reload");
+#endif
+
+        std::cerr << "Rebuild the self-signed SSL certificate";
+        std::cerr << (name.empty() ? std::string{"s"} : " for '" + name + "'");
+        std::cerr << " annually with cron." << std::endl;
+    }
+}
+
+void add_ssl_if_needed(const std::string& name)
+{
+    const auto legacypath = std::string{CONF_DIR} + name + ".conf";
+    if (access(legacypath.c_str(), R_OK) == 0) {
+        add_ssl_directives_to(name);  // let it throw.
+
+        const auto crtpath = std::string{CONF_DIR} + name + ".crt";
+        const auto keypath = std::string{CONF_DIR} + name + ".key";
+        check_ssl_certificate(crtpath, keypath);  // let it throw.
+
+        try {
+            install_cron_job(CRON_CMD, name);
+        }
+        catch (...) {
+            std::cerr << "add_ssl_if_needed warning: cannot use cron to rebuild ";
+            std::cerr << "the self-signed SSL certificate for " << name << "\n";
+        }
+        return;
+    }  // else:
+
+    auto paths = add_ssl_to_config(name);  // let it throw.
+
+    check_ssl_certificate(paths.crt, paths.key);  // let it throw.
+
+    try {
+        install_cron_job(CRON_CHECK);
+    }
+    catch (...) {
+        std::cerr << "add_ssl_if_needed warning: cannot use cron to rebuild ";
+        std::cerr << "the self-signed SSL certificates.\n";
+    }
+}
+
+void add_ssl_if_needed(const std::string& name,
+                       const std::string_view manage,
+                       const std::string_view crt,
+                       const std::string_view key)
+{
+    if (crt[0] != '/') {
+        auto errmsg = std::string{"add_ssl_if_needed error: ssl_certificate "};
+        errmsg += "path cannot be relative '" + std::string{crt} + "'";
+        throw std::runtime_error(errmsg);
+    }
+
+    if (key[0] != '/') {
+        auto errmsg = std::string{"add_ssl_if_needed error: path to ssl_key "};
+        errmsg += "cannot be relative '" + std::string{key} + "'";
+        throw std::runtime_error(errmsg);
+    }
+
+    const auto legacypath = std::string{CONF_DIR} + name + ".conf";
+
+    if (access(legacypath.c_str(), R_OK) != 0) {
+        add_ssl_to_config(name, manage, crt, key);  // let it throw.
+        return;
+    }  // else:
+
+    // symlink crt+key to the paths that add_ssl_directives_to uses (if needed):
+
+    auto crtpath = std::string{CONF_DIR} + name + ".crt";
+    if (crtpath != crt && /* then */ symlink(crt.data(), crtpath.c_str()) != 0) {
+        auto errmsg = std::string{"add_ssl_if_needed error: cannot link "};
+        errmsg += "ssl_certificate " + crtpath + " -> " + crt.data() + " (";
+        errmsg += std::to_string(errno) + "): " + std::strerror(errno);
+        throw std::runtime_error(errmsg);
+    }
+
+    auto keypath = std::string{CONF_DIR} + name + ".key";
+    if (keypath != key && /* then */ symlink(key.data(), keypath.c_str()) != 0) {
+        auto errmsg = std::string{"add_ssl_if_needed error: cannot link "};
+        errmsg += "ssl_certificate_key " + keypath + " -> " + key.data() + " (";
+        errmsg += std::to_string(errno) + "): " + std::strerror(errno);
+        throw std::runtime_error(errmsg);
+    }
+
+    add_ssl_directives_to(name);  // let it throw.
+}
+
+void remove_cron_job(const Line& CRON_LINE, const std::string& name)
+{
+    static const char* filename = "/etc/crontabs/root";
+
+    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);
+
+        if (line == replace_if(line, CRON_LINE.RGX(), name, "")) {
+            conf += line;
+        }
+        else {
+            changed = true;
+        }
+
+        prev = curr + 1;
+    }
+
+    if (changed) {
+        write_file(filename, conf);
+
+        std::cerr << "Do not rebuild the self-signed SSL certificate";
+        std::cerr << (name.empty() ? std::string{"s"} : " for '" + name + "'");
+        std::cerr << " 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
+    }
+}
+
+inline void del_ssl_directives_from(const std::string& name)
+{
+    const std::string prefix = std::string{CONF_DIR} + name;
+
+    const std::string const_conf = read_file(prefix + ".conf");
+
+    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 (!contains(match.str(2), name)) {
+            continue;
+        }  // else:
+
+        const std::string indent = match.str(1);
+
+        std::string conf = const_conf;
+
+        conf = replace_listen(conf, NGX_PORT_443);
+
+        conf = replace_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT.RGX(), "",
+                          NGX_INCLUDE_LAN_LISTEN_DEFAULT.STR("", indent));
+
+        conf = replace_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN.RGX(), "",
+                          NGX_INCLUDE_LAN_LISTEN.STR("", indent));
+
+        // NOLINTNEXTLINE(performance-inefficient-string-concatenation) prefix:
+        conf = replace_if(conf, NGX_SSL_CRT.RGX(), prefix + ".crt", "");
+
+        // NOLINTNEXTLINE(performance-inefficient-string-concatenation) prefix:
+        conf = replace_if(conf, NGX_SSL_KEY.RGX(), prefix + ".key", "");
+
+        conf = replace_if(conf, NGX_SSL_SESSION_CACHE.RGX(), "", "");
+
+        conf = replace_if(conf, NGX_SSL_SESSION_TIMEOUT.RGX(), "", "");
+
+        if (conf != const_conf) {
+            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);
+}
+
+inline auto del_ssl_from_config(const std::string& name,
+                                const std::string_view manage = "self-signed")
+{
+    auto sec = get_uci_section_for_name(name);  // let it throw.
+    auto secname = sec.name();
+
+    struct {
+        std::string crt;
+        std::string key;
+    } ret;
+
+    std::cerr << "Deleting SSL directives from UCI server: nginx." << secname << "\n";
+
+    auto manage_match = false;
+    for (auto opt : sec) {
+        for (auto itm : opt) {
+            if (opt.name() == "ssl_certificate_key") {
+                ret.key = itm.name();
+            }
+
+            else if (opt.name() == "ssl_certificate") {
+                ret.crt = itm.name();
+            }
+
+            else if (opt.name() == "ssl_session_cache" || opt.name() == "ssl_session_timeout") {
+            }
+
+            else if (opt.name() == MANAGE_SSL && itm.name() == manage) {
+                manage_match = true;
+            }
+
+            else if (opt.name() == "listen") {
+                auto val = regex_replace(itm.name(), rgx::regex{NGX_PORT_443[0]}, NGX_PORT_443[1]);
+                if (val != itm.name()) {
+                    std::cerr << "\t" << opt.name() << " (set back to '" << val << "')\n";
+                    itm.rename(val.c_str());
+                }
+                continue; /* not deleting opt, look at other itm : opt */
+            }
+
+            else {
+                continue; /* not deleting opt, look at other itm : opt */
+            }
+
+            // Delete matching opt (not skipped by continue):
+            std::cerr << "\t" << opt.name() << " (was '" << itm.name() << "')\n";
+            opt.del();
+            break;
+        }
+    }
+    if (manage_match) {
+        sec.commit();
+        return ret;
+    }  // else:
+
+    auto errmsg = std::string{"del_ssl error: not changing config wihtout: "};
+    errmsg += "uci set nginx." + secname + "." + MANAGE_SSL.data() + "='" + manage.data();
+    errmsg += "'";
+    throw std::runtime_error(errmsg);
+}
+
+auto del_ssl_legacy(const std::string& name) -> bool
+{
+    const auto legacypath = std::string{CONF_DIR} + name + ".conf";
+
+    if (access(legacypath.c_str(), R_OK) != 0) {
+        return false;
+    }
+
+    try {
+        remove_cron_job(CRON_CMD, name);
+    }
+    catch (...) {
+        std::cerr << "del_ssl warning: cannot remove cron job rebuilding ";
+        std::cerr << "the self-signed SSL certificate for " << name << "\n";
+    }
+
+    try {
+        del_ssl_directives_from(name);
+    }
+    catch (...) {
+        std::cerr << "del_ssl error: ";
+        std::cerr << "cannot delete SSL directives from " << name << ".conf\n";
+        throw;
+    }
+
+    return true;
+}
+
+void del_ssl(const std::string& name)
+{
+    auto crtpath = std::string{};
+    auto keypath = std::string{};
+
+    if (del_ssl_legacy(name)) {  // let it throw.
+        crtpath = std::string{CONF_DIR} + name + ".crt";
+        keypath = std::string{CONF_DIR} + name + ".key";
+    }
+
+    else {
+        auto paths = del_ssl_from_config(name);  // let it throw.
+        crtpath = paths.crt;
+        keypath = paths.key;
+    }
+
+    if (remove(crtpath.c_str()) != 0) {
+        auto errmsg = "del_ssl warning: cannot remove " + crtpath;
+        perror(errmsg.c_str());
+    }
+
+    if (remove(keypath.c_str()) != 0) {
+        auto errmsg = "del_ssl warning: cannot remove " + keypath;
+        perror(errmsg.c_str());
+    }
+}
+
+void del_ssl(const std::string& name, const std::string_view manage)
+{
+    const auto legacypath = std::string{CONF_DIR} + name + ".conf";
+
+    if (access(legacypath.c_str(), R_OK) != 0) {
+        del_ssl_from_config(name, manage);  // let it throw.
+        return;
+    }  // else:
+
+    del_ssl_directives_from(name);  // let it throw.
+
+    for (const auto* ext : {".crt", ".key"}) {
+        struct stat sb {};
+
+        auto path = std::string{CONF_DIR} + name + ext;
+
+        // managed version of add_ssl_if_needed created symlinks (if needed):
+        // NOLINTNEXTLINE(hicpp-signed-bitwise) S_ISLNK macro:
+        if (lstat(path.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode)) {
+            if (remove(path.c_str()) != 0) {
+                auto errmsg = "del_ssl warning: cannot remove " + path;
+                perror(errmsg.c_str());
+            }
+        }
+    }
+}
+
+auto check_ssl(const uci::package& pkg, bool is_enabled) -> bool
+{
+    auto are_valid = true;
+    auto is_enabled_and_at_least_one_has_manage_ssl = false;
+
+    if (is_enabled) {
+        for (auto sec : pkg) {
+            if (sec.anonymous() || sec.type() != "server") {
+                continue;
+            }  // else:
+
+            const auto legacypath = std::string{CONF_DIR} + sec.name() + ".conf";
+            if (access(legacypath.c_str(), R_OK) == 0) {
+                continue;
+            }  // else:
+
+            auto keypath = std::string{};
+            auto crtpath = std::string{};
+            auto self_signed = false;
+
+            for (auto opt : sec) {
+                for (auto itm : opt) {
+                    if (opt.name() == "ssl_certificate_key") {
+                        keypath = itm.name();
+                    }
+
+                    else if (opt.name() == "ssl_certificate") {
+                        crtpath = itm.name();
+                    }
+
+                    else if (opt.name() == MANAGE_SSL) {
+                        if (itm.name() == "self-signed") {
+                            self_signed = true;
+                        }
+
+                        // else if (itm.name()=="???") { /* manage other */ }
+
+                        else {
+                            continue;
+                        }  // no supported manage_ssl string.
+
+                        is_enabled_and_at_least_one_has_manage_ssl = true;
+                    }
+                }
+            }
+
+            if (self_signed && !crtpath.empty() && !keypath.empty()) {
+                try {
+                    if (!check_ssl_certificate(crtpath, keypath)) {
+                        are_valid = false;
+                    }
+                }
+                catch (...) {
+                    std::cerr << "check_ssl warning: cannot build certificate '";
+                    std::cerr << crtpath << "' or key '" << keypath << "'.\n";
+                }
+            }
+        }
+    }
+
+    auto suffix = std::string_view{" the cron job checking the managed SSL certificates.\n"};
+
+    if (is_enabled_and_at_least_one_has_manage_ssl) {
+        try {
+            install_cron_job(CRON_CHECK);
+        }
+        catch (...) {
+            std::cerr << "check_ssl warning: cannot install" << suffix;
+        }
+    }
+
+    else if (access("/etc/crontabs/root", R_OK) == 0) {
+        try {
+            remove_cron_job(CRON_CHECK);
+        }
+        catch (...) {
+            std::cerr << "check_ssl warning: cannot remove" << suffix;
+        }
+    }  // else: do nothing
+
+    return are_valid;
+}
+
+#endif