b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | #ifndef __NGINX_SSL_UTIL_HPP |
| 2 | #define __NGINX_SSL_UTIL_HPP |
| 3 | |
| 4 | #ifdef NO_PCRE |
| 5 | #include <regex> |
| 6 | namespace rgx = std; |
| 7 | #else |
| 8 | #include "regex-pcre.hpp" |
| 9 | #endif |
| 10 | |
| 11 | #include "nginx-util.hpp" |
| 12 | #include "px5g-openssl.hpp" |
| 13 | |
| 14 | #ifndef NO_UBUS |
| 15 | static constexpr auto UBUS_TIMEOUT = 1000; |
| 16 | #endif |
| 17 | |
| 18 | // once a year: |
| 19 | static constexpr auto CRON_INTERVAL = std::string_view{"3 3 12 12 *"}; |
| 20 | |
| 21 | static constexpr auto LAN_SSL_LISTEN = std::string_view{"/var/lib/nginx/lan_ssl.listen"}; |
| 22 | |
| 23 | static constexpr auto LAN_SSL_LISTEN_DEFAULT = // TODO(pst) deprecate |
| 24 | std::string_view{"/var/lib/nginx/lan_ssl.listen.default"}; |
| 25 | |
| 26 | static constexpr auto ADD_SSL_FCT = std::string_view{"add_ssl"}; |
| 27 | |
| 28 | static constexpr auto SSL_SESSION_CACHE_ARG = [](const std::string_view & /*name*/) -> std::string { |
| 29 | return "shared:SSL:32k"; |
| 30 | }; |
| 31 | |
| 32 | static constexpr auto SSL_SESSION_TIMEOUT_ARG = std::string_view{"64m"}; |
| 33 | |
| 34 | using _Line = std::array<std::string (*)(const std::string&, const std::string&), 2>; |
| 35 | |
| 36 | class Line { |
| 37 | private: |
| 38 | _Line _line; |
| 39 | |
| 40 | public: |
| 41 | explicit Line(const _Line& line) noexcept : _line{line} {} |
| 42 | |
| 43 | template <const _Line&... xn> |
| 44 | static auto build() noexcept -> Line |
| 45 | { |
| 46 | return Line{_Line{[](const std::string& p, const std::string& b) -> std::string { |
| 47 | return (... + xn[0](p, b)); |
| 48 | }, |
| 49 | [](const std::string& p, const std::string& b) -> std::string { |
| 50 | return (... + xn[1](p, b)); |
| 51 | }}}; |
| 52 | } |
| 53 | |
| 54 | [[nodiscard]] auto STR(const std::string& param, const std::string& begin) const -> std::string |
| 55 | { |
| 56 | return _line[0](param, begin); |
| 57 | } |
| 58 | |
| 59 | [[nodiscard]] auto RGX() const -> rgx::regex |
| 60 | { |
| 61 | return rgx::regex{_line[1]("", "")}; |
| 62 | } |
| 63 | }; |
| 64 | |
| 65 | auto get_if_missed(const std::string& conf, |
| 66 | const Line& LINE, |
| 67 | const std::string& val, |
| 68 | const std::string& indent = "\n ", |
| 69 | bool compare = true) -> std::string; |
| 70 | |
| 71 | auto replace_if(const std::string& conf, |
| 72 | const rgx::regex& rgx, |
| 73 | const std::string& val, |
| 74 | const std::string& insert) -> std::string; |
| 75 | |
| 76 | auto replace_listen(const std::string& conf, const std::array<const char*, 2>& ngx_port) |
| 77 | -> std::string; |
| 78 | |
| 79 | auto check_ssl_certificate(const std::string& crtpath, const std::string& keypath) -> bool; |
| 80 | |
| 81 | auto contains(const std::string& sentence, const std::string& word) -> bool; |
| 82 | |
| 83 | auto get_uci_section_for_name(const std::string& name) -> uci::section; |
| 84 | |
| 85 | void add_ssl_if_needed(const std::string& name); |
| 86 | |
| 87 | void add_ssl_if_needed(const std::string& name, |
| 88 | std::string_view manage, |
| 89 | std::string_view crt, |
| 90 | std::string_view key); |
| 91 | |
| 92 | void install_cron_job(const Line& CRON_LINE, const std::string& name = ""); |
| 93 | |
| 94 | void remove_cron_job(const Line& CRON_LINE, const std::string& name = ""); |
| 95 | |
| 96 | auto del_ssl_legacy(const std::string& name) -> bool; |
| 97 | |
| 98 | void del_ssl(const std::string& name); |
| 99 | |
| 100 | void del_ssl(const std::string& name, std::string_view manage); |
| 101 | |
| 102 | auto check_ssl(const uci::package& pkg, bool is_enabled) -> bool; |
| 103 | |
| 104 | inline void check_ssl(const uci::package& pkg) |
| 105 | { |
| 106 | if (!check_ssl(pkg, is_enabled(pkg))) { |
| 107 | #ifndef NO_UBUS |
| 108 | if (ubus::call("service", "list", UBUS_TIMEOUT).filter("nginx")) { |
| 109 | call("/etc/init.d/nginx", "reload"); |
| 110 | std::cerr << "Reload Nginx.\n"; |
| 111 | } |
| 112 | #endif |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | constexpr auto _begin = _Line{ |
| 117 | [](const std::string& /*param*/, const std::string& begin) -> std::string { return begin; }, |
| 118 | |
| 119 | [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string { |
| 120 | return R"([{;](?:\s*#[^\n]*(?=\n))*(\s*))"; |
| 121 | }}; |
| 122 | |
| 123 | constexpr auto _space = _Line{[](const std::string& /*param*/, const std::string & |
| 124 | /*begin*/) -> std::string { return std::string{" "}; }, |
| 125 | |
| 126 | [](const std::string& /*param*/, const std::string & |
| 127 | /*begin*/) -> std::string { return R"(\s+)"; }}; |
| 128 | |
| 129 | constexpr auto _newline = _Line{ |
| 130 | [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string { |
| 131 | return std::string{"\n"}; |
| 132 | }, |
| 133 | |
| 134 | [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string { |
| 135 | return std::string{"(\n)"}; |
| 136 | } // capture it as _end captures it, too. |
| 137 | }; |
| 138 | |
| 139 | constexpr auto _end = |
| 140 | _Line{[](const std::string& /*param*/, const std::string & /*begin*/) -> std::string { |
| 141 | return std::string{";"}; |
| 142 | }, |
| 143 | |
| 144 | [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string { |
| 145 | return std::string{R"(\s*(;(?:[\t ]*#[^\n]*)?))"}; |
| 146 | }}; |
| 147 | |
| 148 | template <char clim = '\0'> |
| 149 | static constexpr auto _capture = _Line{ |
| 150 | [](const std::string& param, const std::string & /*begin*/) -> std::string { |
| 151 | return '\'' + param + '\''; |
| 152 | }, |
| 153 | |
| 154 | [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string { |
| 155 | const auto lim = clim == '\0' ? std::string{"\\s"} : std::string{clim}; |
| 156 | return std::string{R"(((?:(?:"[^"]*")|(?:[^'")"} + lim + "][^" + lim + "]*)|(?:'[^']*'))+)"; |
| 157 | }}; |
| 158 | |
| 159 | template <const std::string_view& strptr, char clim = '\0'> |
| 160 | static constexpr auto _escape = _Line{ |
| 161 | [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string { |
| 162 | return clim == '\0' ? std::string{strptr.data()} : clim + std::string{strptr.data()} + clim; |
| 163 | }, |
| 164 | |
| 165 | [](const std::string& /*param*/, const std::string & /*begin*/) -> std::string { |
| 166 | std::string ret{}; |
| 167 | for (char c : strptr) { |
| 168 | switch (c) { |
| 169 | case '^': |
| 170 | ret += '\\'; |
| 171 | ret += c; |
| 172 | break; |
| 173 | case '_': |
| 174 | case '-': |
| 175 | ret += c; |
| 176 | break; |
| 177 | default: |
| 178 | if ((isalpha(c) != 0) || (isdigit(c) != 0)) { |
| 179 | ret += c; |
| 180 | } |
| 181 | else { |
| 182 | ret += std::string{"["} + c + "]"; |
| 183 | } |
| 184 | } |
| 185 | } |
| 186 | return "(?:" + ret + "|'" + ret + "'" + "|\"" + ret + "\"" + ")"; |
| 187 | }}; |
| 188 | |
| 189 | constexpr std::string_view _check_ssl = "check_ssl"; |
| 190 | |
| 191 | constexpr std::string_view _server_name = "server_name"; |
| 192 | |
| 193 | constexpr std::string_view _listen = "listen"; |
| 194 | |
| 195 | constexpr std::string_view _include = "include"; |
| 196 | |
| 197 | constexpr std::string_view _ssl_certificate = "ssl_certificate"; |
| 198 | |
| 199 | constexpr std::string_view _ssl_certificate_key = "ssl_certificate_key"; |
| 200 | |
| 201 | constexpr std::string_view _ssl_session_cache = "ssl_session_cache"; |
| 202 | |
| 203 | constexpr std::string_view _ssl_session_timeout = "ssl_session_timeout"; |
| 204 | |
| 205 | // For a compile time regex lib, this must be fixed, use one of these options: |
| 206 | // * Hand craft or macro concat them (loosing more or less flexibility). |
| 207 | // * Use Macro concatenation of __VA_ARGS__ with the help of: |
| 208 | // https://p99.gforge.inria.fr/p99-html/group__preprocessor__for.html |
| 209 | // * Use constexpr---not available for strings or char * for now---look at lib. |
| 210 | |
| 211 | static const auto CRON_CHECK = |
| 212 | Line::build<_space, _escape<NGINX_UTIL>, _space, _escape<_check_ssl, '\''>, _newline>(); |
| 213 | |
| 214 | static const auto CRON_CMD = Line::build<_space, |
| 215 | _escape<NGINX_UTIL>, |
| 216 | _space, |
| 217 | _escape<ADD_SSL_FCT, '\''>, |
| 218 | _space, |
| 219 | _capture<>, |
| 220 | _newline>(); |
| 221 | |
| 222 | static const auto NGX_SERVER_NAME = |
| 223 | Line::build<_begin, _escape<_server_name>, _space, _capture<';'>, _end>(); |
| 224 | |
| 225 | static const auto NGX_INCLUDE_LAN_LISTEN = |
| 226 | Line::build<_begin, _escape<_include>, _space, _escape<LAN_LISTEN, '\''>, _end>(); |
| 227 | |
| 228 | static const auto NGX_INCLUDE_LAN_LISTEN_DEFAULT = |
| 229 | Line::build<_begin, _escape<_include>, _space, _escape<LAN_LISTEN_DEFAULT, '\''>, _end>(); |
| 230 | |
| 231 | static const auto NGX_INCLUDE_LAN_SSL_LISTEN = |
| 232 | Line::build<_begin, _escape<_include>, _space, _escape<LAN_SSL_LISTEN, '\''>, _end>(); |
| 233 | |
| 234 | static const auto NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT = |
| 235 | Line::build<_begin, _escape<_include>, _space, _escape<LAN_SSL_LISTEN_DEFAULT, '\''>, _end>(); |
| 236 | |
| 237 | static const auto NGX_SSL_CRT = |
| 238 | Line::build<_begin, _escape<_ssl_certificate>, _space, _capture<';'>, _end>(); |
| 239 | |
| 240 | static const auto NGX_SSL_KEY = |
| 241 | Line::build<_begin, _escape<_ssl_certificate_key>, _space, _capture<';'>, _end>(); |
| 242 | |
| 243 | static const auto NGX_SSL_SESSION_CACHE = |
| 244 | Line::build<_begin, _escape<_ssl_session_cache>, _space, _capture<';'>, _end>(); |
| 245 | |
| 246 | static const auto NGX_SSL_SESSION_TIMEOUT = |
| 247 | Line::build<_begin, _escape<_ssl_session_timeout>, _space, _capture<';'>, _end>(); |
| 248 | |
| 249 | static const auto NGX_LISTEN = Line::build<_begin, _escape<_listen>, _space, _capture<';'>, _end>(); |
| 250 | |
| 251 | static const auto NGX_PORT_80 = std::array<const char*, 2>{ |
| 252 | R"(^\s*([^:]*:|\[[^\]]*\]:)?80(\s|$|;))", |
| 253 | "$01443 ssl$2", |
| 254 | }; |
| 255 | |
| 256 | static const auto NGX_PORT_443 = std::array<const char*, 2>{ |
| 257 | R"(^\s*([^:]*:|\[[^\]]*\]:)?443(\s.*)?\sssl(\s|$|;))", |
| 258 | "$0180$2$3", |
| 259 | }; |
| 260 | |
| 261 | // ------------------------- implementation: ---------------------------------- |
| 262 | |
| 263 | auto get_if_missed(const std::string& conf, |
| 264 | const Line& LINE, |
| 265 | const std::string& val, |
| 266 | const std::string& indent, |
| 267 | bool compare) -> std::string |
| 268 | { |
| 269 | if (!compare || val.empty()) { |
| 270 | return rgx::regex_search(conf, LINE.RGX()) ? "" : LINE.STR(val, indent); |
| 271 | } |
| 272 | |
| 273 | rgx::smatch match; // assuming last capture has the value! |
| 274 | |
| 275 | for (auto pos = conf.begin(); rgx::regex_search(pos, conf.end(), match, LINE.RGX()); |
| 276 | pos += match.position(0) + match.length(0)) |
| 277 | { |
| 278 | const std::string value = match.str(match.size() - 2); |
| 279 | |
| 280 | if (value == val || value == "'" + val + "'" || value == '"' + val + '"') { |
| 281 | return ""; |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | return LINE.STR(val, indent); |
| 286 | } |
| 287 | |
| 288 | auto replace_if(const std::string& conf, |
| 289 | const rgx::regex& rgx, |
| 290 | const std::string& val, |
| 291 | const std::string& insert) -> std::string |
| 292 | { |
| 293 | std::string ret{}; |
| 294 | auto pos = conf.begin(); |
| 295 | |
| 296 | auto skip = 0; |
| 297 | for (rgx::smatch match; rgx::regex_search(pos, conf.end(), match, rgx); |
| 298 | pos += match.position(match.size() - 1)) |
| 299 | { |
| 300 | auto i = match.size() - 2; |
| 301 | const std::string value = match.str(i); |
| 302 | |
| 303 | bool compare = !val.empty(); |
| 304 | if (compare && value != val && value != "'" + val + "'" && value != '"' + val + '"') { |
| 305 | ret.append(pos + skip, pos + match.position(i) + match.length(i)); |
| 306 | skip = 0; |
| 307 | } |
| 308 | else { |
| 309 | ret.append(pos + skip, pos + match.position(match.size() > 2 ? 1 : 0)); |
| 310 | ret += insert; |
| 311 | skip = 1; |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | ret.append(pos + skip, conf.end()); |
| 316 | return ret; |
| 317 | } |
| 318 | |
| 319 | auto replace_listen(const std::string& conf, const std::array<const char*, 2>& ngx_port) |
| 320 | -> std::string |
| 321 | { |
| 322 | std::string ret{}; |
| 323 | auto pos = conf.begin(); |
| 324 | |
| 325 | for (rgx::smatch match; rgx::regex_search(pos, conf.end(), match, NGX_LISTEN.RGX()); |
| 326 | pos += match.position(match.size() - 1)) |
| 327 | { |
| 328 | auto i = match.size() - 2; |
| 329 | ret.append(pos, pos + match.position(i)); |
| 330 | ret += rgx::regex_replace(match.str(i), rgx::regex{ngx_port[0]}, ngx_port[1]); |
| 331 | } |
| 332 | |
| 333 | ret.append(pos, conf.end()); |
| 334 | return ret; |
| 335 | } |
| 336 | |
| 337 | inline void add_ssl_directives_to(const std::string& name) |
| 338 | { |
| 339 | const std::string prefix = std::string{CONF_DIR} + name; |
| 340 | |
| 341 | const std::string const_conf = read_file(prefix + ".conf"); |
| 342 | |
| 343 | rgx::smatch match; // captures str(1)=indentation spaces, str(2)=server name |
| 344 | for (auto pos = const_conf.begin(); |
| 345 | rgx::regex_search(pos, const_conf.end(), match, NGX_SERVER_NAME.RGX()); |
| 346 | pos += match.position(0) + match.length(0)) |
| 347 | { |
| 348 | if (!contains(match.str(2), name)) { |
| 349 | continue; |
| 350 | } // else: |
| 351 | |
| 352 | const std::string indent = match.str(1); |
| 353 | |
| 354 | auto adds = std::string{}; |
| 355 | |
| 356 | adds += get_if_missed(const_conf, NGX_SSL_CRT, prefix + ".crt", indent); |
| 357 | |
| 358 | adds += get_if_missed(const_conf, NGX_SSL_KEY, prefix + ".key", indent); |
| 359 | |
| 360 | adds += get_if_missed(const_conf, NGX_SSL_SESSION_CACHE, SSL_SESSION_CACHE_ARG(name), |
| 361 | indent, false); |
| 362 | |
| 363 | adds += get_if_missed(const_conf, NGX_SSL_SESSION_TIMEOUT, |
| 364 | std::string{SSL_SESSION_TIMEOUT_ARG}, indent, false); |
| 365 | |
| 366 | pos += match.position(0) + match.length(0); |
| 367 | std::string conf = |
| 368 | std::string(const_conf.begin(), pos) + adds + std::string(pos, const_conf.end()); |
| 369 | |
| 370 | conf = replace_if(conf, NGX_INCLUDE_LAN_LISTEN_DEFAULT.RGX(), "", |
| 371 | NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT.STR("", indent)); |
| 372 | |
| 373 | conf = replace_if(conf, NGX_INCLUDE_LAN_LISTEN.RGX(), "", |
| 374 | NGX_INCLUDE_LAN_SSL_LISTEN.STR("", indent)); |
| 375 | |
| 376 | conf = replace_listen(conf, NGX_PORT_80); |
| 377 | |
| 378 | if (conf != const_conf) { |
| 379 | write_file(prefix + ".conf", conf); |
| 380 | std::cerr << "Added SSL directives to " << prefix << ".conf\n"; |
| 381 | } |
| 382 | |
| 383 | return; |
| 384 | } |
| 385 | |
| 386 | auto errmsg = std::string{"add_ssl_directives_to error: "}; |
| 387 | errmsg += "cannot add SSL directives to " + name + ".conf, missing: "; |
| 388 | errmsg += NGX_SERVER_NAME.STR(name, "\n ") + "\n"; |
| 389 | throw std::runtime_error(errmsg); |
| 390 | } |
| 391 | |
| 392 | template <typename T> |
| 393 | inline auto num2hex(T bytes) -> std::array<char, 2 * sizeof(bytes) + 1> |
| 394 | { |
| 395 | constexpr auto n = 2 * sizeof(bytes); |
| 396 | std::array<char, n + 1> str{}; |
| 397 | |
| 398 | for (size_t i = 0; i < n; ++i) { |
| 399 | static const std::array<char, 17> hex{"0123456789ABCDEF"}; |
| 400 | static constexpr auto get = 0x0fU; |
| 401 | str.at(i) = hex.at(bytes & get); |
| 402 | |
| 403 | static constexpr auto move = 4U; |
| 404 | bytes >>= move; |
| 405 | } |
| 406 | |
| 407 | str[n] = '\0'; |
| 408 | return str; |
| 409 | } |
| 410 | |
| 411 | template <typename T> |
| 412 | inline auto get_nonce(const T salt = 0) -> T |
| 413 | { |
| 414 | T nonce = 0; |
| 415 | |
| 416 | std::ifstream urandom{"/dev/urandom"}; |
| 417 | |
| 418 | static constexpr auto move = 6U; |
| 419 | |
| 420 | constexpr size_t steps = (sizeof(nonce) * 8 - 1) / move + 1; |
| 421 | |
| 422 | for (size_t i = 0; i < steps; ++i) { |
| 423 | if (!urandom.good()) { |
| 424 | throw std::runtime_error("get_nonce error"); |
| 425 | } |
| 426 | nonce = (nonce << move) + static_cast<unsigned>(urandom.get()); |
| 427 | } |
| 428 | |
| 429 | nonce ^= salt; |
| 430 | |
| 431 | return nonce; |
| 432 | } |
| 433 | |
| 434 | inline void create_ssl_certificate(const std::string& crtpath, |
| 435 | const std::string& keypath, |
| 436 | const int days = 792) |
| 437 | { |
| 438 | size_t nonce = 0; |
| 439 | |
| 440 | try { |
| 441 | nonce = get_nonce(nonce); |
| 442 | } |
| 443 | |
| 444 | catch (...) { // the address of a variable should be random enough: |
| 445 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) sic: |
| 446 | nonce += reinterpret_cast<size_t>(&crtpath); |
| 447 | } |
| 448 | |
| 449 | auto noncestr = num2hex(nonce); |
| 450 | |
| 451 | const auto tmpcrtpath = crtpath + ".new-" + noncestr.data(); |
| 452 | const auto tmpkeypath = keypath + ".new-" + noncestr.data(); |
| 453 | |
| 454 | try { |
| 455 | auto pkey = gen_eckey(NID_secp384r1); |
| 456 | |
| 457 | write_key(pkey, tmpkeypath); |
| 458 | |
| 459 | std::string subject{"/C=ZZ/ST=Somewhere/L=None/CN=OpenWrt/O=OpenWrt"}; |
| 460 | subject += noncestr.data(); |
| 461 | |
| 462 | selfsigned(pkey, days, subject, tmpcrtpath); |
| 463 | |
| 464 | static constexpr auto to_seconds = 24 * 60 * 60; |
| 465 | static constexpr auto leeway = 42; |
| 466 | if (!checkend(tmpcrtpath, days * to_seconds - leeway)) { |
| 467 | throw std::runtime_error("bug: created certificate is not valid!!"); |
| 468 | } |
| 469 | } |
| 470 | catch (...) { |
| 471 | std::cerr << "create_ssl_certificate error: "; |
| 472 | std::cerr << "cannot create selfsigned certificate, "; |
| 473 | std::cerr << "removing temporary files ..." << std::endl; |
| 474 | |
| 475 | if (remove(tmpcrtpath.c_str()) != 0) { |
| 476 | auto errmsg = "\t cannot remove " + tmpcrtpath; |
| 477 | perror(errmsg.c_str()); |
| 478 | } |
| 479 | |
| 480 | if (remove(tmpkeypath.c_str()) != 0) { |
| 481 | auto errmsg = "\t cannot remove " + tmpkeypath; |
| 482 | perror(errmsg.c_str()); |
| 483 | } |
| 484 | |
| 485 | throw; |
| 486 | } |
| 487 | |
| 488 | if (rename(tmpcrtpath.c_str(), crtpath.c_str()) != 0 || |
| 489 | rename(tmpkeypath.c_str(), keypath.c_str()) != 0) |
| 490 | { |
| 491 | auto errmsg = std::string{"create_ssl_certificate warning: "}; |
| 492 | errmsg += "cannot move " + tmpcrtpath + " to " + crtpath; |
| 493 | errmsg += " or " + tmpkeypath + " to " + keypath + ", continuing ... "; |
| 494 | perror(errmsg.c_str()); |
| 495 | } |
| 496 | |
| 497 | std::cerr << "Created self-signed SSL certificate '" << crtpath; |
| 498 | std::cerr << "' with key '" << keypath << "'.\n"; |
| 499 | } |
| 500 | |
| 501 | auto check_ssl_certificate(const std::string& crtpath, const std::string& keypath) -> bool |
| 502 | { |
| 503 | { // paths are relative to dir: |
| 504 | auto dir = std::string_view{"/etc/nginx"}; |
| 505 | auto crt_rel = crtpath[0] != '/'; |
| 506 | auto key_rel = keypath[0] != '/'; |
| 507 | if ((crt_rel || key_rel) && (chdir(dir.data()) != 0)) { |
| 508 | auto errmsg = std::string{"check_ssl_certificate error: entering "}; |
| 509 | errmsg += dir; |
| 510 | perror(errmsg.c_str()); |
| 511 | errmsg += " (need to change directory since the given "; |
| 512 | errmsg += crt_rel ? "ssl_certificate '" + crtpath : std::string{}; |
| 513 | errmsg += crt_rel && key_rel ? "' and " : ""; |
| 514 | errmsg += key_rel ? "ssl_certificate_key '" + keypath : std::string{}; |
| 515 | errmsg += crt_rel && key_rel ? "' are" : "' is a"; |
| 516 | errmsg += " relative path"; |
| 517 | errmsg += crt_rel && key_rel ? "s)" : ")"; |
| 518 | throw std::runtime_error(errmsg); |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | constexpr auto remaining_seconds = (365 + 32) * 24 * 60 * 60; |
| 523 | constexpr auto validity_days = 3 * (365 + 31); |
| 524 | |
| 525 | bool is_valid = true; |
| 526 | |
| 527 | if (access(keypath.c_str(), R_OK) != 0 || access(crtpath.c_str(), R_OK) != 0) { |
| 528 | is_valid = false; |
| 529 | } |
| 530 | |
| 531 | else { |
| 532 | try { |
| 533 | if (!checkend(crtpath, remaining_seconds)) { |
| 534 | is_valid = false; |
| 535 | } |
| 536 | } |
| 537 | catch (...) { // something went wrong, maybe it is in DER format: |
| 538 | try { |
| 539 | if (!checkend(crtpath, remaining_seconds, false)) { |
| 540 | is_valid = false; |
| 541 | } |
| 542 | } |
| 543 | catch (...) { // it has neither DER nor PEM format, rebuild. |
| 544 | is_valid = false; |
| 545 | } |
| 546 | } |
| 547 | } |
| 548 | |
| 549 | if (!is_valid) { |
| 550 | create_ssl_certificate(crtpath, keypath, validity_days); |
| 551 | } |
| 552 | |
| 553 | return is_valid; |
| 554 | } |
| 555 | |
| 556 | auto contains(const std::string& sentence, const std::string& word) -> bool |
| 557 | { |
| 558 | auto pos = sentence.find(word); |
| 559 | if (pos == std::string::npos) { |
| 560 | return false; |
| 561 | } |
| 562 | if (pos != 0 && (isgraph(sentence[pos - 1]) != 0)) { |
| 563 | return false; |
| 564 | } |
| 565 | if (isgraph(sentence[pos + word.size()]) != 0) { |
| 566 | return false; |
| 567 | } |
| 568 | // else: |
| 569 | return true; |
| 570 | } |
| 571 | |
| 572 | auto get_uci_section_for_name(const std::string& name) -> uci::section |
| 573 | { |
| 574 | auto pkg = uci::package{"nginx"}; // let it throw. |
| 575 | |
| 576 | auto uci_enabled = is_enabled(pkg); |
| 577 | |
| 578 | if (uci_enabled) { |
| 579 | for (auto sec : pkg) { |
| 580 | if (sec.name() == name) { |
| 581 | return sec; |
| 582 | } |
| 583 | } |
| 584 | // try interpreting 'name' as FQDN: |
| 585 | for (auto sec : pkg) { |
| 586 | for (auto opt : sec) { |
| 587 | if (opt.name() == "server_name") { |
| 588 | for (auto itm : opt) { |
| 589 | if (contains(itm.name(), name)) { |
| 590 | return sec; |
| 591 | } |
| 592 | } |
| 593 | } |
| 594 | } |
| 595 | } |
| 596 | } |
| 597 | |
| 598 | auto errmsg = std::string{"lookup error: neither there is a file named '"}; |
| 599 | errmsg += std::string{CONF_DIR} + name + ".conf' nor the UCI config has "; |
| 600 | if (uci_enabled) { |
| 601 | errmsg += "a nginx server with section name or 'server_name': " + name; |
| 602 | } |
| 603 | else { |
| 604 | errmsg += "been enabled by:\n\tuci set nginx.global.uci_enable=true"; |
| 605 | } |
| 606 | throw std::runtime_error(errmsg); |
| 607 | } |
| 608 | |
| 609 | inline auto add_ssl_to_config(const std::string& name, |
| 610 | const std::string_view manage = "self-signed", |
| 611 | const std::string_view crt = "", |
| 612 | const std::string_view key = "") |
| 613 | { |
| 614 | auto sec = get_uci_section_for_name(name); // let it throw. |
| 615 | auto secname = sec.name(); |
| 616 | |
| 617 | struct { |
| 618 | std::string crt; |
| 619 | std::string key; |
| 620 | } ret; |
| 621 | |
| 622 | std::cerr << "Adding SSL directives to UCI server: nginx." << secname << "\n"; |
| 623 | |
| 624 | std::cerr << "\t" << MANAGE_SSL << "='" << manage << "'\n"; |
| 625 | sec.set(MANAGE_SSL.data(), manage.data()); |
| 626 | |
| 627 | if (!crt.empty() && !key.empty()) { |
| 628 | sec.set("ssl_certificate", crt.data()); |
| 629 | std::cerr << "\tssl_certificate='" << crt << "'\n"; |
| 630 | sec.set("ssl_certificate_key", key.data()); |
| 631 | std::cerr << "\tssl_certificate_key='" << key << "'\n"; |
| 632 | } |
| 633 | |
| 634 | auto cache = false; |
| 635 | auto timeout = false; |
| 636 | for (auto opt : sec) { |
| 637 | if (opt.name() == "ssl_session_cache") { |
| 638 | cache = true; |
| 639 | continue; |
| 640 | } // else: |
| 641 | |
| 642 | if (opt.name() == "ssl_session_timeout") { |
| 643 | timeout = true; |
| 644 | continue; |
| 645 | } |
| 646 | |
| 647 | // else: |
| 648 | for (auto itm : opt) { |
| 649 | if (opt.name() == "ssl_certificate_key") { |
| 650 | ret.key = itm.name(); |
| 651 | } |
| 652 | |
| 653 | else if (opt.name() == "ssl_certificate") { |
| 654 | ret.crt = itm.name(); |
| 655 | } |
| 656 | |
| 657 | else if (opt.name() == "listen") { |
| 658 | auto val = regex_replace(itm.name(), rgx::regex{NGX_PORT_80[0]}, NGX_PORT_80[1]); |
| 659 | if (val != itm.name()) { |
| 660 | std::cerr << "\t" << opt.name() << "='" << val << "' (replacing)\n"; |
| 661 | itm.rename(val.c_str()); |
| 662 | } |
| 663 | } |
| 664 | } |
| 665 | } |
| 666 | |
| 667 | if (ret.crt.empty()) { |
| 668 | ret.crt = std::string{CONF_DIR} + name + ".crt"; |
| 669 | std::cerr << "\tssl_certificate='" << ret.crt << "'\n"; |
| 670 | sec.set("ssl_certificate", ret.crt.c_str()); |
| 671 | } |
| 672 | |
| 673 | if (ret.key.empty()) { |
| 674 | ret.key = std::string{CONF_DIR} + name + ".key"; |
| 675 | std::cerr << "\tssl_certificate_key='" << ret.key << "'\n"; |
| 676 | sec.set("ssl_certificate_key", ret.key.c_str()); |
| 677 | } |
| 678 | |
| 679 | if (!cache) { |
| 680 | std::cerr << "\tssl_session_cache='" << SSL_SESSION_CACHE_ARG(name) << "'\n"; |
| 681 | sec.set("ssl_session_cache", SSL_SESSION_CACHE_ARG(name).data()); |
| 682 | } |
| 683 | |
| 684 | if (!timeout) { |
| 685 | std::cerr << "\tssl_session_timeout='" << SSL_SESSION_TIMEOUT_ARG << "'\n"; |
| 686 | sec.set("ssl_session_timeout", SSL_SESSION_TIMEOUT_ARG.data()); |
| 687 | } |
| 688 | |
| 689 | sec.commit(); |
| 690 | |
| 691 | return ret; |
| 692 | } |
| 693 | |
| 694 | void install_cron_job(const Line& CRON_LINE, const std::string& name) |
| 695 | { |
| 696 | static const char* filename = "/etc/crontabs/root"; |
| 697 | |
| 698 | std::string conf{}; |
| 699 | try { |
| 700 | conf = read_file(filename); |
| 701 | } |
| 702 | catch (const std::ifstream::failure&) { /* is ok if not found, create. */ |
| 703 | } |
| 704 | |
| 705 | const std::string add = get_if_missed(conf, CRON_LINE, name); |
| 706 | |
| 707 | if (add.length() > 0) { |
| 708 | #ifndef NO_UBUS |
| 709 | if (!ubus::call("service", "list", UBUS_TIMEOUT).filter("cron")) { |
| 710 | std::string errmsg{"install_cron_job error: "}; |
| 711 | errmsg += "Cron unavailable to re-create the ssl certificate"; |
| 712 | errmsg += (name.empty() ? std::string{"s\n"} : " for '" + name + "'\n"); |
| 713 | throw std::runtime_error(errmsg); |
| 714 | } // else active with or without instances: |
| 715 | #endif |
| 716 | |
| 717 | const auto* pre = (conf.length() == 0 || conf.back() == '\n' ? "" : "\n"); |
| 718 | write_file(filename, pre + std::string{CRON_INTERVAL} + add, std::ios::app); |
| 719 | |
| 720 | #ifndef NO_UBUS |
| 721 | call("/etc/init.d/cron", "reload"); |
| 722 | #endif |
| 723 | |
| 724 | std::cerr << "Rebuild the self-signed SSL certificate"; |
| 725 | std::cerr << (name.empty() ? std::string{"s"} : " for '" + name + "'"); |
| 726 | std::cerr << " annually with cron." << std::endl; |
| 727 | } |
| 728 | } |
| 729 | |
| 730 | void add_ssl_if_needed(const std::string& name) |
| 731 | { |
| 732 | const auto legacypath = std::string{CONF_DIR} + name + ".conf"; |
| 733 | if (access(legacypath.c_str(), R_OK) == 0) { |
| 734 | add_ssl_directives_to(name); // let it throw. |
| 735 | |
| 736 | const auto crtpath = std::string{CONF_DIR} + name + ".crt"; |
| 737 | const auto keypath = std::string{CONF_DIR} + name + ".key"; |
| 738 | check_ssl_certificate(crtpath, keypath); // let it throw. |
| 739 | |
| 740 | try { |
| 741 | install_cron_job(CRON_CMD, name); |
| 742 | } |
| 743 | catch (...) { |
| 744 | std::cerr << "add_ssl_if_needed warning: cannot use cron to rebuild "; |
| 745 | std::cerr << "the self-signed SSL certificate for " << name << "\n"; |
| 746 | } |
| 747 | return; |
| 748 | } // else: |
| 749 | |
| 750 | auto paths = add_ssl_to_config(name); // let it throw. |
| 751 | |
| 752 | check_ssl_certificate(paths.crt, paths.key); // let it throw. |
| 753 | |
| 754 | try { |
| 755 | install_cron_job(CRON_CHECK); |
| 756 | } |
| 757 | catch (...) { |
| 758 | std::cerr << "add_ssl_if_needed warning: cannot use cron to rebuild "; |
| 759 | std::cerr << "the self-signed SSL certificates.\n"; |
| 760 | } |
| 761 | } |
| 762 | |
| 763 | void add_ssl_if_needed(const std::string& name, |
| 764 | const std::string_view manage, |
| 765 | const std::string_view crt, |
| 766 | const std::string_view key) |
| 767 | { |
| 768 | if (crt[0] != '/') { |
| 769 | auto errmsg = std::string{"add_ssl_if_needed error: ssl_certificate "}; |
| 770 | errmsg += "path cannot be relative '" + std::string{crt} + "'"; |
| 771 | throw std::runtime_error(errmsg); |
| 772 | } |
| 773 | |
| 774 | if (key[0] != '/') { |
| 775 | auto errmsg = std::string{"add_ssl_if_needed error: path to ssl_key "}; |
| 776 | errmsg += "cannot be relative '" + std::string{key} + "'"; |
| 777 | throw std::runtime_error(errmsg); |
| 778 | } |
| 779 | |
| 780 | const auto legacypath = std::string{CONF_DIR} + name + ".conf"; |
| 781 | |
| 782 | if (access(legacypath.c_str(), R_OK) != 0) { |
| 783 | add_ssl_to_config(name, manage, crt, key); // let it throw. |
| 784 | return; |
| 785 | } // else: |
| 786 | |
| 787 | // symlink crt+key to the paths that add_ssl_directives_to uses (if needed): |
| 788 | |
| 789 | auto crtpath = std::string{CONF_DIR} + name + ".crt"; |
| 790 | if (crtpath != crt && /* then */ symlink(crt.data(), crtpath.c_str()) != 0) { |
| 791 | auto errmsg = std::string{"add_ssl_if_needed error: cannot link "}; |
| 792 | errmsg += "ssl_certificate " + crtpath + " -> " + crt.data() + " ("; |
| 793 | errmsg += std::to_string(errno) + "): " + std::strerror(errno); |
| 794 | throw std::runtime_error(errmsg); |
| 795 | } |
| 796 | |
| 797 | auto keypath = std::string{CONF_DIR} + name + ".key"; |
| 798 | if (keypath != key && /* then */ symlink(key.data(), keypath.c_str()) != 0) { |
| 799 | auto errmsg = std::string{"add_ssl_if_needed error: cannot link "}; |
| 800 | errmsg += "ssl_certificate_key " + keypath + " -> " + key.data() + " ("; |
| 801 | errmsg += std::to_string(errno) + "): " + std::strerror(errno); |
| 802 | throw std::runtime_error(errmsg); |
| 803 | } |
| 804 | |
| 805 | add_ssl_directives_to(name); // let it throw. |
| 806 | } |
| 807 | |
| 808 | void remove_cron_job(const Line& CRON_LINE, const std::string& name) |
| 809 | { |
| 810 | static const char* filename = "/etc/crontabs/root"; |
| 811 | |
| 812 | const auto const_conf = read_file(filename); |
| 813 | |
| 814 | bool changed = false; |
| 815 | auto conf = std::string{}; |
| 816 | |
| 817 | size_t prev = 0; |
| 818 | size_t curr = 0; |
| 819 | while ((curr = const_conf.find('\n', prev)) != std::string::npos) { |
| 820 | auto line = const_conf.substr(prev, curr - prev + 1); |
| 821 | |
| 822 | if (line == replace_if(line, CRON_LINE.RGX(), name, "")) { |
| 823 | conf += line; |
| 824 | } |
| 825 | else { |
| 826 | changed = true; |
| 827 | } |
| 828 | |
| 829 | prev = curr + 1; |
| 830 | } |
| 831 | |
| 832 | if (changed) { |
| 833 | write_file(filename, conf); |
| 834 | |
| 835 | std::cerr << "Do not rebuild the self-signed SSL certificate"; |
| 836 | std::cerr << (name.empty() ? std::string{"s"} : " for '" + name + "'"); |
| 837 | std::cerr << " annually with cron anymore." << std::endl; |
| 838 | |
| 839 | #ifndef NO_UBUS |
| 840 | if (ubus::call("service", "list", UBUS_TIMEOUT).filter("cron")) { |
| 841 | call("/etc/init.d/cron", "reload"); |
| 842 | } |
| 843 | #endif |
| 844 | } |
| 845 | } |
| 846 | |
| 847 | inline void del_ssl_directives_from(const std::string& name) |
| 848 | { |
| 849 | const std::string prefix = std::string{CONF_DIR} + name; |
| 850 | |
| 851 | const std::string const_conf = read_file(prefix + ".conf"); |
| 852 | |
| 853 | rgx::smatch match; // captures str(1)=indentation spaces, str(2)=server name |
| 854 | for (auto pos = const_conf.begin(); |
| 855 | rgx::regex_search(pos, const_conf.end(), match, NGX_SERVER_NAME.RGX()); |
| 856 | pos += match.position(0) + match.length(0)) |
| 857 | { |
| 858 | if (!contains(match.str(2), name)) { |
| 859 | continue; |
| 860 | } // else: |
| 861 | |
| 862 | const std::string indent = match.str(1); |
| 863 | |
| 864 | std::string conf = const_conf; |
| 865 | |
| 866 | conf = replace_listen(conf, NGX_PORT_443); |
| 867 | |
| 868 | conf = replace_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT.RGX(), "", |
| 869 | NGX_INCLUDE_LAN_LISTEN_DEFAULT.STR("", indent)); |
| 870 | |
| 871 | conf = replace_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN.RGX(), "", |
| 872 | NGX_INCLUDE_LAN_LISTEN.STR("", indent)); |
| 873 | |
| 874 | // NOLINTNEXTLINE(performance-inefficient-string-concatenation) prefix: |
| 875 | conf = replace_if(conf, NGX_SSL_CRT.RGX(), prefix + ".crt", ""); |
| 876 | |
| 877 | // NOLINTNEXTLINE(performance-inefficient-string-concatenation) prefix: |
| 878 | conf = replace_if(conf, NGX_SSL_KEY.RGX(), prefix + ".key", ""); |
| 879 | |
| 880 | conf = replace_if(conf, NGX_SSL_SESSION_CACHE.RGX(), "", ""); |
| 881 | |
| 882 | conf = replace_if(conf, NGX_SSL_SESSION_TIMEOUT.RGX(), "", ""); |
| 883 | |
| 884 | if (conf != const_conf) { |
| 885 | write_file(prefix + ".conf", conf); |
| 886 | std::cerr << "Deleted SSL directives from " << prefix << ".conf\n"; |
| 887 | } |
| 888 | |
| 889 | return; |
| 890 | } |
| 891 | |
| 892 | auto errmsg = std::string{"del_ssl_directives_from error: "}; |
| 893 | errmsg += "cannot delete SSL directives from " + name + ".conf, missing: "; |
| 894 | errmsg += NGX_SERVER_NAME.STR(name, "\n ") + "\n"; |
| 895 | throw std::runtime_error(errmsg); |
| 896 | } |
| 897 | |
| 898 | inline auto del_ssl_from_config(const std::string& name, |
| 899 | const std::string_view manage = "self-signed") |
| 900 | { |
| 901 | auto sec = get_uci_section_for_name(name); // let it throw. |
| 902 | auto secname = sec.name(); |
| 903 | |
| 904 | struct { |
| 905 | std::string crt; |
| 906 | std::string key; |
| 907 | } ret; |
| 908 | |
| 909 | std::cerr << "Deleting SSL directives from UCI server: nginx." << secname << "\n"; |
| 910 | |
| 911 | auto manage_match = false; |
| 912 | for (auto opt : sec) { |
| 913 | for (auto itm : opt) { |
| 914 | if (opt.name() == "ssl_certificate_key") { |
| 915 | ret.key = itm.name(); |
| 916 | } |
| 917 | |
| 918 | else if (opt.name() == "ssl_certificate") { |
| 919 | ret.crt = itm.name(); |
| 920 | } |
| 921 | |
| 922 | else if (opt.name() == "ssl_session_cache" || opt.name() == "ssl_session_timeout") { |
| 923 | } |
| 924 | |
| 925 | else if (opt.name() == MANAGE_SSL && itm.name() == manage) { |
| 926 | manage_match = true; |
| 927 | } |
| 928 | |
| 929 | else if (opt.name() == "listen") { |
| 930 | auto val = regex_replace(itm.name(), rgx::regex{NGX_PORT_443[0]}, NGX_PORT_443[1]); |
| 931 | if (val != itm.name()) { |
| 932 | std::cerr << "\t" << opt.name() << " (set back to '" << val << "')\n"; |
| 933 | itm.rename(val.c_str()); |
| 934 | } |
| 935 | continue; /* not deleting opt, look at other itm : opt */ |
| 936 | } |
| 937 | |
| 938 | else { |
| 939 | continue; /* not deleting opt, look at other itm : opt */ |
| 940 | } |
| 941 | |
| 942 | // Delete matching opt (not skipped by continue): |
| 943 | std::cerr << "\t" << opt.name() << " (was '" << itm.name() << "')\n"; |
| 944 | opt.del(); |
| 945 | break; |
| 946 | } |
| 947 | } |
| 948 | if (manage_match) { |
| 949 | sec.commit(); |
| 950 | return ret; |
| 951 | } // else: |
| 952 | |
| 953 | auto errmsg = std::string{"del_ssl error: not changing config wihtout: "}; |
| 954 | errmsg += "uci set nginx." + secname + "." + MANAGE_SSL.data() + "='" + manage.data(); |
| 955 | errmsg += "'"; |
| 956 | throw std::runtime_error(errmsg); |
| 957 | } |
| 958 | |
| 959 | auto del_ssl_legacy(const std::string& name) -> bool |
| 960 | { |
| 961 | const auto legacypath = std::string{CONF_DIR} + name + ".conf"; |
| 962 | |
| 963 | if (access(legacypath.c_str(), R_OK) != 0) { |
| 964 | return false; |
| 965 | } |
| 966 | |
| 967 | try { |
| 968 | remove_cron_job(CRON_CMD, name); |
| 969 | } |
| 970 | catch (...) { |
| 971 | std::cerr << "del_ssl warning: cannot remove cron job rebuilding "; |
| 972 | std::cerr << "the self-signed SSL certificate for " << name << "\n"; |
| 973 | } |
| 974 | |
| 975 | try { |
| 976 | del_ssl_directives_from(name); |
| 977 | } |
| 978 | catch (...) { |
| 979 | std::cerr << "del_ssl error: "; |
| 980 | std::cerr << "cannot delete SSL directives from " << name << ".conf\n"; |
| 981 | throw; |
| 982 | } |
| 983 | |
| 984 | return true; |
| 985 | } |
| 986 | |
| 987 | void del_ssl(const std::string& name) |
| 988 | { |
| 989 | auto crtpath = std::string{}; |
| 990 | auto keypath = std::string{}; |
| 991 | |
| 992 | if (del_ssl_legacy(name)) { // let it throw. |
| 993 | crtpath = std::string{CONF_DIR} + name + ".crt"; |
| 994 | keypath = std::string{CONF_DIR} + name + ".key"; |
| 995 | } |
| 996 | |
| 997 | else { |
| 998 | auto paths = del_ssl_from_config(name); // let it throw. |
| 999 | crtpath = paths.crt; |
| 1000 | keypath = paths.key; |
| 1001 | } |
| 1002 | |
| 1003 | if (remove(crtpath.c_str()) != 0) { |
| 1004 | auto errmsg = "del_ssl warning: cannot remove " + crtpath; |
| 1005 | perror(errmsg.c_str()); |
| 1006 | } |
| 1007 | |
| 1008 | if (remove(keypath.c_str()) != 0) { |
| 1009 | auto errmsg = "del_ssl warning: cannot remove " + keypath; |
| 1010 | perror(errmsg.c_str()); |
| 1011 | } |
| 1012 | } |
| 1013 | |
| 1014 | void del_ssl(const std::string& name, const std::string_view manage) |
| 1015 | { |
| 1016 | const auto legacypath = std::string{CONF_DIR} + name + ".conf"; |
| 1017 | |
| 1018 | if (access(legacypath.c_str(), R_OK) != 0) { |
| 1019 | del_ssl_from_config(name, manage); // let it throw. |
| 1020 | return; |
| 1021 | } // else: |
| 1022 | |
| 1023 | del_ssl_directives_from(name); // let it throw. |
| 1024 | |
| 1025 | for (const auto* ext : {".crt", ".key"}) { |
| 1026 | struct stat sb {}; |
| 1027 | |
| 1028 | auto path = std::string{CONF_DIR} + name + ext; |
| 1029 | |
| 1030 | // managed version of add_ssl_if_needed created symlinks (if needed): |
| 1031 | // NOLINTNEXTLINE(hicpp-signed-bitwise) S_ISLNK macro: |
| 1032 | if (lstat(path.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode)) { |
| 1033 | if (remove(path.c_str()) != 0) { |
| 1034 | auto errmsg = "del_ssl warning: cannot remove " + path; |
| 1035 | perror(errmsg.c_str()); |
| 1036 | } |
| 1037 | } |
| 1038 | } |
| 1039 | } |
| 1040 | |
| 1041 | auto check_ssl(const uci::package& pkg, bool is_enabled) -> bool |
| 1042 | { |
| 1043 | auto are_valid = true; |
| 1044 | auto is_enabled_and_at_least_one_has_manage_ssl = false; |
| 1045 | |
| 1046 | if (is_enabled) { |
| 1047 | for (auto sec : pkg) { |
| 1048 | if (sec.anonymous() || sec.type() != "server") { |
| 1049 | continue; |
| 1050 | } // else: |
| 1051 | |
| 1052 | const auto legacypath = std::string{CONF_DIR} + sec.name() + ".conf"; |
| 1053 | if (access(legacypath.c_str(), R_OK) == 0) { |
| 1054 | continue; |
| 1055 | } // else: |
| 1056 | |
| 1057 | auto keypath = std::string{}; |
| 1058 | auto crtpath = std::string{}; |
| 1059 | auto self_signed = false; |
| 1060 | |
| 1061 | for (auto opt : sec) { |
| 1062 | for (auto itm : opt) { |
| 1063 | if (opt.name() == "ssl_certificate_key") { |
| 1064 | keypath = itm.name(); |
| 1065 | } |
| 1066 | |
| 1067 | else if (opt.name() == "ssl_certificate") { |
| 1068 | crtpath = itm.name(); |
| 1069 | } |
| 1070 | |
| 1071 | else if (opt.name() == MANAGE_SSL) { |
| 1072 | if (itm.name() == "self-signed") { |
| 1073 | self_signed = true; |
| 1074 | } |
| 1075 | |
| 1076 | // else if (itm.name()=="???") { /* manage other */ } |
| 1077 | |
| 1078 | else { |
| 1079 | continue; |
| 1080 | } // no supported manage_ssl string. |
| 1081 | |
| 1082 | is_enabled_and_at_least_one_has_manage_ssl = true; |
| 1083 | } |
| 1084 | } |
| 1085 | } |
| 1086 | |
| 1087 | if (self_signed && !crtpath.empty() && !keypath.empty()) { |
| 1088 | try { |
| 1089 | if (!check_ssl_certificate(crtpath, keypath)) { |
| 1090 | are_valid = false; |
| 1091 | } |
| 1092 | } |
| 1093 | catch (...) { |
| 1094 | std::cerr << "check_ssl warning: cannot build certificate '"; |
| 1095 | std::cerr << crtpath << "' or key '" << keypath << "'.\n"; |
| 1096 | } |
| 1097 | } |
| 1098 | } |
| 1099 | } |
| 1100 | |
| 1101 | auto suffix = std::string_view{" the cron job checking the managed SSL certificates.\n"}; |
| 1102 | |
| 1103 | if (is_enabled_and_at_least_one_has_manage_ssl) { |
| 1104 | try { |
| 1105 | install_cron_job(CRON_CHECK); |
| 1106 | } |
| 1107 | catch (...) { |
| 1108 | std::cerr << "check_ssl warning: cannot install" << suffix; |
| 1109 | } |
| 1110 | } |
| 1111 | |
| 1112 | else if (access("/etc/crontabs/root", R_OK) == 0) { |
| 1113 | try { |
| 1114 | remove_cron_job(CRON_CHECK); |
| 1115 | } |
| 1116 | catch (...) { |
| 1117 | std::cerr << "check_ssl warning: cannot remove" << suffix; |
| 1118 | } |
| 1119 | } // else: do nothing |
| 1120 | |
| 1121 | return are_valid; |
| 1122 | } |
| 1123 | |
| 1124 | #endif |