From f62599d27e6f8942a2adb8cf686f3c4d157bcc8a Mon Sep 17 00:00:00 2001 From: Peter Stadler Date: Thu, 23 Jul 2020 14:56:36 +0200 Subject: [PATCH] nginx-util: use UCI for server configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **tl;dr:** The functions `{add,del}_ssl` modify a server section of the UCI config if there is no `.conf` file with the same name in `/etc/nginx/conf.d/`. Then `init_lan` creates `/var/lib/nginx/uci.conf` files by copying the `/etc/nginx/uci.conf.template` and standard options from the UCI config; additionally the special path `logd` can be used in `{access,error}_log`. The init does not change the configuration beside re-creating self-signed certificates when needed. This is also the only purpose of the new `check_ssl`, which is installed as yearly cron job. **Initialization:** Invoking `nginx-util init_lan` parses the UCI configuration for package `nginx`. It creates a server part in `/var/lib/nginx/uci.conf` for each `section server '$name'` by copying all UCI options but the following: * `option uci_manage_ssl` is skipped. It is set to 'self-signed' by `nginx-util add_ssl $name`, removed by `nginx-util del_ssl $name` and used by `nginx-util check_ssl` (see below). * `logd` as path in `error_log` or `access_log` writes them to STDERR respective STDOUT, which are fowarded by Nginx's init to the log daemon. Specifically: `option error_log 'logd'` becomes `error_log stderr;` and `option access_log 'logd openwrt'` becomes `access_log /proc/self/fd/1 openwrt;` Other `[option|list] key 'value'` entries just become `key value;` directives. The init.d calls internally also `check_ssl` for rebuilding self-signed SSL certificates if needed (see below). And it still sets up `/var/lib/nginx/lan{,_ssl}.listen` files as it is doing in the current version (so they stay available). **Defaults:** The package installs the file `/etc/nginx/restrict_locally` containing allow/deny directives for restricting the access to LAN addresses by including it into a server part. The default server '_lan' includes this file and listens on all IPs (instead of only the local IPs as it did before; other servers do not need to listen explicitly on the local IPs anymore). The default server is contained together with a server that redirects HTTP requests for inexistent URLs to HTTPS in the UCI configuration file `/etc/config/nginx`. Furthermore, the packages installs a `/etc/nginx/uci.conf.template` containing the current setup and a marker, which will be replaced by the created UCI servers when calling `init_lan`. **Other:** If there is a file named `/etc/nginx/conf.d/$name.conf` the functions `init_lan`, `add_ssl $name` and `del_ssl $name` will use that file instead of a UCI server section (this is similar to the current version). Else it selects the UCI `section server $name`, or, when there is no such section, it searches for the first one having `option server_name '… $name …'`. For this section: * `nginx-util add_ssl $name` will add to it: `option uci_manage_ssl 'self-signed'` `option ssl_certificate '/etc/nginx/conf.d/$name.crt'` `option ssl_certificate_key '/etc/nginx/conf.d/$name.key'` `option ssl_session_cache 'shared:SSL:32k'` `option ssl_session_timeout '64m'` If these options are already present, they will stay the same; just the first option `uci_manage_ssl` will always be changed to 'self-signed'. The command also changes all `listen` list items to use port 443 and ssl instead of port 80 (without ssl). If they stated another port than 80 before, they are kept the same. Furthermore, it creates a self-signed SSL certificate if necessary, i.e., if there is no *valid* certificate and key at the locations given by the options `ssl_certificate` and `ssl_certificate_key`. * `nginx-util del_ssl $name` checks if `uci_manage_ssl` is set 'self-signed' in the corresponding UCI section. Only then it removes all of the above options regardless of the value looking just at the key name. Then, it also changes all `listen` list items to use port 80 (without ssl) instead of port 443 with ssl. If stating another port than 443, they are kept the same. Furthermore, it removes the SSL certificate and key that were indicated by `ssl_certificate{,_key}`. * `nginx-util check_ssl` looks through all server sections of the UCI config for `uci_manage_ssl 'self-signed'`. On every hit it checks if the SSL certificate-key-pair indicated by the options `ssl_certificate{,_key}` is expired. Then it re-creates a self-signed certificate. If there exists at least one `section server` with `uci_manage_ssl 'self-signed'`, it will try to install itself as cron job. If there are no such sections, it removes that cron job if possible. For installing a ssl certificate and key managed by another app, you can call: `nginx-util add_ssl $name $manager $crtpath $keypath` Hereby `$name` is as above, `$manager` is an arbitrary string, and the the ssl certificate and its key are indicated by their absolute path. If you want to remove the directives again, then you can use: `nginx-util del_ssl $name $manager` Signed-off-by: Peter Stadler --- net/nginx-util/Makefile | 50 +- net/nginx-util/files/README.sh | 475 +++++++++ net/nginx-util/files/nginx.config | 22 + net/nginx-util/files/restrict_locally | 10 + net/nginx-util/files/uci.conf.template | 32 + net/nginx-util/src/.clang-format | 171 +++ net/nginx-util/src/.clang-tidy | 407 +++++++ net/nginx-util/src/CMakeLists.txt | 21 +- net/nginx-util/src/LICENSE | 23 + net/nginx-util/src/nginx-ssl-util.hpp | 1121 ++++++++++++++------ net/nginx-util/src/nginx-util.cpp | 404 +++++-- net/nginx-util/src/nginx-util.hpp | 92 +- net/nginx-util/src/px5g-openssl.hpp | 229 ++-- net/nginx-util/src/px5g.cpp | 343 +++--- net/nginx-util/src/regex-pcre.hpp | 322 +++--- net/nginx-util/src/test-nginx-util-root.sh | 397 ++++++- net/nginx-util/src/test-nginx-util.sh | 11 +- net/nginx-util/src/ubus-cxx.cpp | 150 ++- net/nginx-util/src/ubus-cxx.hpp | 341 +++--- net/nginx-util/src/uci-cxx.cpp | 22 + net/nginx-util/src/uci-cxx.hpp | 474 +++++++++ 21 files changed, 3882 insertions(+), 1235 deletions(-) create mode 100755 net/nginx-util/files/README.sh create mode 100644 net/nginx-util/files/nginx.config create mode 100644 net/nginx-util/files/restrict_locally create mode 100644 net/nginx-util/files/uci.conf.template create mode 100644 net/nginx-util/src/.clang-format create mode 100644 net/nginx-util/src/.clang-tidy create mode 100644 net/nginx-util/src/LICENSE create mode 100644 net/nginx-util/src/uci-cxx.cpp create mode 100644 net/nginx-util/src/uci-cxx.hpp diff --git a/net/nginx-util/Makefile b/net/nginx-util/Makefile index 41903a58b..54eb8bf87 100644 --- a/net/nginx-util/Makefile +++ b/net/nginx-util/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=nginx-util -PKG_VERSION:=1.4 -PKG_RELEASE:=3 +PKG_VERSION:=1.5 +PKG_RELEASE:=1 PKG_MAINTAINER:=Peter Stadler include $(INCLUDE_DIR)/package.mk @@ -17,7 +17,7 @@ define Package/nginx-ssl-util/default CATEGORY:=Network SUBMENU:=Web Servers/Proxies TITLE:=Nginx configurator including SSL - DEPENDS:=+libstdcpp +libubus +libubox +libpthread +libopenssl + DEPENDS:=+libstdcpp +libuci +libubus +libubox +libpthread +libopenssl # TODO: remove after a transition period (together with below and pkg nginx): # It actually removes nginx-util (replacing it by a dummy pkg) to avoid # conflicts with nginx-ssl-util* @@ -58,19 +58,63 @@ Package/nginx-ssl-util-nopcre/description = \ It uses the standard regex library of C++. +define Package/nginx-ssl-util/install/default + $(INSTALL_DIR) $(1)/etc/nginx/conf.d/ + + $(INSTALL_CONF) ./files/uci.conf.template $(1)/etc/nginx/ + $(LN) /var/lib/nginx/uci.conf $(1)/etc/nginx/uci.conf + + $(INSTALL_CONF) ./files/restrict_locally $(1)/etc/nginx/ + + $(INSTALL_DIR) $(1)/etc/config/ + $(INSTALL_CONF) ./files/nginx.config $(1)/etc/config/nginx + +ifneq ($(CONFIG_IPV6),y) # the used IPv6 directives have `::` in them: + $(SED) "/::/d" $(1)/etc/nginx/restrict_locally + $(SED) "/::/d" $(1)/etc/config/nginx +endif +endef + + define Package/nginx-ssl-util/install + $(call Package/nginx-ssl-util/install/default, $(1)) $(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 + $(call Package/nginx-ssl-util/install/default, $(1)) $(INSTALL_DIR) $(1)/usr/bin $(INSTALL_BIN) $(PKG_BUILD_DIR)/nginx-ssl-util-nopcre \ $(1)/usr/bin/nginx-util endef +define Package/nginx-ssl-util/prerm +#!/bin/sh + +[ -n "$${IPKG_INSTROOT}" ] && exit 0 +[ "$${PKG_UPGRADE}" = "1" ] && exit 0 +case "$$(/sbin/uci get "nginx.global.uci_enable" 2>/dev/null)" in + 1|on|true|yes|enabled) ;; + *) exit 0;; +esac + +eval "$$(/usr/bin/nginx-util get_env)" && +[ "$$(/sbin/uci get "nginx.$${LAN_NAME}.$${MANAGE_SSL}" 2>/dev/null)" = \ + "self-signed" ] && +cd "/etc/nginx" && +rm -f "$$(/sbin/uci get "nginx.$${LAN_NAME}.ssl_certificate")" \ + "$$(/sbin/uci get "nginx.$${LAN_NAME}.ssl_certificate_key")" + +exit 0 +endef + + +Package/nginx-ssl-util-nopcre/prerm = $(Package/nginx-ssl-util/prerm) + + $(eval $(call BuildPackage,nginx-ssl-util)) $(eval $(call BuildPackage,nginx-ssl-util-nopcre)) diff --git a/net/nginx-util/files/README.sh b/net/nginx-util/files/README.sh new file mode 100755 index 000000000..7ca172964 --- /dev/null +++ b/net/nginx-util/files/README.sh @@ -0,0 +1,475 @@ +#!/bin/sh +# This is a template copy it by: ./README.sh | xclip -selection c +# to https://openwrt.org/docs/guide-user/services/webserver/nginx#configuration + + +NGINX_UTIL="/usr/bin/nginx-util" + +EXAMPLE_COM="example.com" + +MSG=" +/* Created by the following bash script that includes the source of some files: + * https://github.com/openwrt/packages/net/nginx-util/files/README.sh + */" + +eval $("${NGINX_UTIL}" get_env) + +code() { + local file + [ $# -gt 1 ] && file="$2" || file="$(basename "$1")" + printf "\n%s" "$1" "$(cat "${file}")"; +} + +ifConfEcho() { + sed -nE "s/^\s*$1=\s*(\S*)\s*\\\\$/\n$2 \"\1\";/p" ../../nginx/Makefile; +} + +cat <service nginx reload + +For OpenWrt we use a special initial configuration, which is explained in the +section [[#openwrt_s_defaults|🡓OpenWrt’s Defaults]]. +So, we can make a site available at a specific URL in the **LAN** by creating a +''.locations'' file in the directory ''${CONF_DIR}''. +Such a file consists just of some +[[https://nginx.org/en/docs/http/ngx_http_core_module.html#location| +location blocks]]. +Under the latter link, you can find also the official documentation for all +available directives of the HTTP core of Nginx. +Look for //location// in the Context list. + +The following example provides a simple template, see at the end for +different [[#locations_for_apps|🡓Locations for Apps]]((look for +[[https://github.com/search?utf8=%E2%9C%93&q=repo%3Aopenwrt%2Fpackages ++extension%3Alocations&type=Code&ref=advsearch&l=&l=| +other packages using a .locations file]], too.)): + + +location /ex/am/ple { + access_log off; # default: not logging accesses. + # access_log /proc/self/fd/1 openwrt; # use logd (init forwards stdout). + # error_log stderr; # default: logging to logd (init forwards stderr). + error_log /dev/null; # disable error logging after config file is read. + # (state path of a file for access_log/error_log to the file instead.) + index index.html; +} +# location /eg/static { … } + + +All location blocks in all ''.locations'' files must use different URLs, +since they are all included in the ''${LAN_NAME}'' server that is part of the +[[#openwrt_s_defaults|🡓OpenWrt’s Defaults]].(( +We reserve the ''location /'' for making LuCI available under the root URL, +e.g. [[https://192.168.1.1/|192.168.1.1/]]. +All other sites shouldn’t use the root ''location /'' without suffix.)) +We should use the root URL for other sites than LuCI only on **other** domain +names, e.g., we could make a site available at https://${EXAMPLE_COM}/. +In order to do that, we create [[#new_server_parts|🡓New Server Parts]] for all +domain names. +We can also activate SSL thereby, see +[[#ssl_server_parts|🡓SSL Server Parts]]. +We use such server parts also for publishing sites to the internet (WAN) +instead of making them available just locally (in the LAN). + +Via ''${CONF_DIR}*.conf'' files we can add directives to the //http// part of +the configuration. +If you would change the configuration ''$(basename "${UCI_CONF}").template'' +instead, it is not updated to new package's versions anymore. +Although it is not recommended, you can also disable the whole UCI config and +create your own ''${NGINX_CONF}''; then invoke: + +uci set nginx.global.uci_enable=false + + + +==== New Server Parts ====${MSG} + + +For making the router reachable from the WAN at a registered domain name, +it is not enough letting the +[[docs:guide-user:firewall:firewall_configuration|🡒firewall]] accept requests +(typically on ports 80 and 443) and giving the name server the internet IP +address of the router (maybe updated automatically by a +[[docs:guide-user:services:ddns:client|🡒DDNS Client]]). + +We also need to set up virtual hosting for this domain name by creating an +appropriate server section in ''/etc/config/nginx'' +(or in a ''${CONF_DIR}*.conf'' file, which cannot be changed using UCI). +All such parts are included in the main configuration of OpenWrt +([[#openwrt_s_defaults|🡓OpenWrt’s Defaults]]). + +In the server part, we state the domain as +[[https://nginx.org/en/docs/http/ngx_http_core_module.html#server_name| +server_name]]. +The link points to the same document as for the location blocks in the +[[#basic|🡑Basic Configuration]]: the official documentation for all available +directives of the HTTP core of Nginx. +This time look for //server// in the Context list, too. +The server part should also contain similar location blocks as +++before.| +We can re-include a ''.locations'' file that is included in the server part for +the LAN by default. +Then the site is reachable under the same path at both domains, e.g. by +https://192.168.1.1/ex/am/ple as well as by https://${EXAMPLE_COM}/ex/am/ple. +++ + +We can add directives to a server in the UCI configuration by invoking +''uci [set|add_list] nginx.${EXAMPLE_COM//./_}.key=value''. +If the //key// is not starting with //uci_//, it becomes a ''key value;'' +++directive.| +Although the UCI config does not support nesting like Nginx, we can add a whole +block as //value//. +++ + +We cannot use dots in a //key// name other than in the //value//. +In the following example we replace the dot in //${EXAMPLE_COM}// by an +underscore for the UCI name of the server, but not for Nginx's //server_name//: + + +uci add nginx server && +uci rename nginx.@server[-1]=${EXAMPLE_COM//./_} && +uci add_list nginx.${EXAMPLE_COM//./_}.listen='80' && +uci add_list nginx.${EXAMPLE_COM//./_}.listen='[::]:80' && +uci set nginx.${EXAMPLE_COM//./_}.server_name='${EXAMPLE_COM}' && +uci add_list nginx.${EXAMPLE_COM//./_}.include=\ +'$(basename ${CONF_DIR})/${EXAMPLE_COM}.locations' +# uci add_list nginx.${EXAMPLE_COM//./_}.location='/ { … }' \ +# root location for this server. + + +We can disable respective re-enable this server again by: + + +uci set nginx.${EXAMPLE_COM//./_}=disable # respective: \ +uci set nginx.${EXAMPLE_COM//./_}=server + + +These changes are made in the RAM (and can be used until a reboot), we can save +them permanently by: + +uci commit nginx + +For creating a similar ''${CONF_DIR}${EXAMPLE_COM}.conf'', we can adopt the +following: + + +server { + listen 80; + listen [::]:80; + server_name ${EXAMPLE_COM}; + include '$(basename ${CONF_DIR})/${EXAMPLE_COM}.locations'; + # location / { … } # root location for this server. +} + + +[[#openwrt_s_defaults|🡓OpenWrt’s Defaults]] include the UCI server +''config server '_redirect2ssl' ''. +It acts as //default_server// for HTTP and redirects requests for inexistent +URLs to HTTPS. +For making another domain name accessible to all addresses, the corresponding +server part should listen on port //80// and contain the FQDN as +//server_name//, cf. the official documentation on +[[https://nginx.org/en/docs/http/request_processing.html|request_processing]]. + +Furthermore, there is a UCI server named ''${LAN_NAME}''. +It is the //default_server// for HTTPS and allows connections from LAN only. +It includes the file ''$(dirname "${CONF_DIR}")/restrict_locally'' with +appropriate //allow/deny// directives, cf. the official documentation on +[[https://nginx.org/en/docs/http/ngx_http_access_module.html|limiting access]]. + + + +==== SSL Server Parts ====${MSG} + + +For enabling HTTPS for a domain we need a SSL certificate as well as its key and +add them by the directives //ssl_certificate// respective +//ssl_certificate_key// to the server part of the domain +([[https://nginx.org/en/docs/http/configuring_https_servers.html#sni|TLS SNI]] +is supported by default). +The rest of the configuration is similar as for general +[[#new_server_parts|🡑New Server Parts]]. +We only have to adjust the listen directives by adding the //ssl// parameter and +changing the port from //80// to //443//. + +The official documentation of the SSL module contains an +[[https://nginx.org/en/docs/http/ngx_http_ssl_module.html#example| +example]] with some optimizations. +We can extend an existing UCI server section similarly, e.g., for the above +''config server '${EXAMPLE_COM//./_}' '' we invoke: + + +# Instead of 'del_list' the listen* entries, we could use '443 ssl' beforehand. +uci del_list nginx.${EXAMPLE_COM//./_}.listen='80' && +uci del_list nginx.${EXAMPLE_COM//./_}.listen='[::]:80' && +uci add_list nginx.${EXAMPLE_COM//./_}.listen='443 ssl' && +uci add_list nginx.${EXAMPLE_COM//./_}.listen='[::]:443 ssl' && +uci set nginx.${EXAMPLE_COM//./_}.ssl_certificate=\ +'${CONF_DIR}${EXAMPLE_COM}.crt' && +uci set nginx.${EXAMPLE_COM//./_}.ssl_certificate_key=\ +'${CONF_DIR}${EXAMPLE_COM}.key' && +uci set nginx.${EXAMPLE_COM//./_}.ssl_session_cache=\ +'${SSL_SESSION_CACHE_ARG}' && +uci set nginx.${EXAMPLE_COM//./_}.ssl_session_timeout=\ +'${SSL_SESSION_TIMEOUT_ARG}' && +uci commit nginx + + +For making the server in ''${CONF_DIR}${EXAMPLE_COM}.conf'' available +via SSL, we can make similar changes there. + +The following command creates a **self-signed** SSL certificate and changes the +corresponding configuration: + +$(basename "${NGINX_UTIL}") ${ADD_SSL_FCT} ${EXAMPLE_COM} + + - If a ''$(basename "${CONF_DIR}")/${EXAMPLE_COM}.conf'' file exists, it\ + adds //ssl_*// directives and changes the //listen// directives there.\ + Else it does that similarly to the example above for a ++selected UCI\ + server.| Hereby it searches the UCI config first for a server with the\ + given name and then for a server whose //server_name// contains the name.\ + For //${EXAMPLE_COM}// it is the latter as a UCI key cannot have dots.++ + - It checks if there is a certificate with key for '${EXAMPLE_COM}' that is\ + valid for at least 13 months or tries to create a self-signed one. + - When cron is activated, it installs a cron job for renewing the self-signed\ + certificate every year if needed, too. We can activate cron by: \ + service cron enable && service cron start + +This can be undone by invoking: + +$(basename "${NGINX_UTIL}") del_ssl ${EXAMPLE_COM} + +For using an SSL certificate and key that are managed otherwise, there is: + +$(basename "${NGINX_UTIL}") add_ssl ${EXAMPLE_COM} "\$MANAGER" \ +"/absolute/path/to/crt" "/absolute/path/to/key" + +It only adds //ssl_*// directives and changes the //listen// directives in +the appropriate configuration, but does not create or change the certificate +or its key. This can be reverted by: + +$(basename "${NGINX_UTIL}") del_ssl ${EXAMPLE_COM} "\$MANAGER" + +For example [[https://github.com/ndilieto/uacme|uacme]] or +[[https://github.com/Neilpang/acme.sh|acme.sh]] can be used for creating an SSL +certificate signed by Let’s Encrypt and changing the config +++accordingly.| +They call ''$(basename "${NGINX_UTIL}") add_ssl \$FQDN acme \$CRT \$KEY'' +internally.++ +We can install them by: + + +opkg update && opkg install uacme #or: acme #and for LuCI: luci-app-acme + + +[[#openwrt_s_defaults|🡓OpenWrt’s Defaults]] include a UCI server for the LAN: +''config server '${LAN_NAME}' ''. +It has //ssl_*// directives prepared for a self-signed((Let’s Encrypt (and other +CAs) cannot sign certificates of a **local** server.)) +SSL certificate, which is created on the first start of Nginx. +The server listens on all addresses, is the //default_server// for HTTPS and +allows connections from LAN only (by including the file ''restrict_locally'' +with //allow/deny// directives, cf. the official documentation on +[[https://nginx.org/en/docs/http/ngx_http_access_module.html|limiting access]]). + +For making another domain name accessible to all addresses, the corresponding +SSL server part should listen on port //443// and contain the FQDN as +//server_name//, cf. the official documentation on +[[https://nginx.org/en/docs/http/request_processing.html|request_processing]]. + +Furthermore, there is also a UCI server named ''_redirect2ssl'', which listens +on all addresses, acts as //default_server// for HTTP and redirects requests for +inexistent URLs to HTTPS. + + + +==== OpenWrt’s Defaults ====${MSG} + + +Since Nginx is compiled with these presets, we can pretend that the main +configuration will always contain the following directives +(though we can overwrite them): + +$(ifConfEcho --pid-path pid)\ +$(ifConfEcho --lock-path lock_file)\ +$(ifConfEcho --error-log-path error_log)\ +$(false && ifConfEcho --http-log-path access_log)\ +$(ifConfEcho --http-proxy-temp-path proxy_temp_path)\ +$(ifConfEcho --http-client-body-temp-path client_body_temp_path)\ +$(ifConfEcho --http-fastcgi-temp-path fastcgi_temp_path)\ + + +When starting or reloading the Nginx service, the ''/etc/init.d/nginx'' script +sets also the following directives +(so we cannot change them in the used configuration file): + + +daemon off; # procd expects services to run in the foreground + + +Then, the init sript creates the main configuration +''$(basename "${UCI_CONF}")'' dynamically from the template: + +$(code "${UCI_CONF}.template") + +So, the access log is turned off by default and we can look at the error log +by ''logread'', as init.d script forwards stderr and stdout to the +[[docs:guide-user:base-system:log.essentials|🡒runtime log]]. +We can set the //error_log// and //access_log// to files, where the log +messages are forwarded to instead (after the configuration is read). +And for redirecting the access log of a //server// or //location// to the logd, +too, we insert the following directive in the corresponding block: + + access_log /proc/self/fd/1 openwrt; + +If we setup a server through UCI, we can use the options //error_log// and/or +//access_log// also with the special path +++'logd'.| +When initializing the Nginx service, this special path is replaced by //stderr// +respective ///proc/self/fd/1// (which are forwarded to the runtime log). +++ + +For creating the configuration from the template shown above, Nginx’s init +script replaces the comment ''#UCI_HTTP_CONFIG'' by all UCI servers. +For each server section in the the UCI configuration, it basically copies all +options into a Nginx //server { … }// part, in detail: + * Options starting with ''uci_'' are skipped. Currently there is only\ + the ''option ${MANAGE_SSL}=…'' in ++usage.| It is set to\ + //'self-signed'// when invoking\ + ''$(basename ${NGINX_UTIL}) ${ADD_SSL_FCT} \$NAME''.\ + Then the corresponding certificate is re-newed if it is about to expire.\ + All those certificates are checked on the initialization of the Nginx service\ + and if Cron is available, it is deployed for checking them annually, too.++ + * All other lists or options of the form ''key='value' '' are written\ + one-to-one as ''key value;'' directives to the configuration file.\ + Just the path //logd// has a special meaning for the logging directives\ + (described in the previous paragraph). + +The init.d script of Nginx uses the //$(basename ${NGINX_UTIL})// for creating +the configuration file +++in RAM.| +The main configuration ''${UCI_CONF}'' is a symbolic link to this place +(it is a dead link if the Nginx service is not running). +++ + +We could use a custom configuration created at ''${NGINX_CONF}'' instead of the +dynamic configuration, too.(( +For using a custom configuration at ''${NGINX_CONF}'', we execute +uci set nginx.global.uci_enable='false' +Then the rest of the UCI config is ignored and //init.d// will not create the +main configuration dynamically from the template anymore. +Invoking ''$(basename ${NGINX_UTIL}) [${ADD_SSL_FCT}|del_ssl] \$FQDN'' +will still try to change a server in ''$(basename "${CONF_DIR}")/\$FQDN.conf'' +(this is less reliable than for a UCI config as it uses regular expressions, not +a complete parser for the Nginx configuration).)) +This is not encouraged since you cannot setup servers using UCI anymore. +Rather, we can put custom configuration parts to ''.conf'' files in the +''${CONF_DIR}'' directory. +The main configuration pulls in all ''$(basename "${CONF_DIR}")/*.conf'' files +into the //http {…}// block behind the created UCI servers. + +The initial UCI config is enabled and contains two server section: + +$(code "/etc/config/nginx" "nginx.config") + +While the LAN server is the //default_server// for HTTPS, the server +redirecting requests for an inexistent ''server_name'' from HTTP to HTTPS acts +as //default_server// if there is ++no other|; +it uses an invalid name for that, more in the official documentation on +[[https://nginx.org/en/docs/http/request_processing.html|request_processing]] +++. + +The LAN server pulls in all ''.locations'' files from the directory +''${CONF_DIR}''. +We can install the location parts of different sites there (see +[[#basic|🡑Basic Configuration]]) and re-include them into other servers. +This is needed especially for making them available to the WAN +([[#new_server_parts|🡑New Server Parts]]). +The LAN server listens for all addresses on port //443// and restricts the +access to local addresses by including: +$(code "$(dirname "${CONF_DIR}")/restrict_locally") + +When starting or reloading the Nginx service, the init.d looks which UCI servers +have set ''option ${MANAGE_SSL} 'self-signed' '', e.g. the LAN server. +For all those servers it checks if there is a certificate that is still valid +for 13 months or (re-)creates a self-signed one. +If there is any such server, it installs also a cron job that checks the +corresponding certificates once a year. +The option ''${MANAGE_SSL}'' is set to //'self-signed'// respectively removed +from a UCI server named ''${EXAMPLE_COM//./_}'' by the following +(see [[#ssl_server_parts|🡑SSL Server Parts]], too): + + +$(basename ${NGINX_UTIL}) ${ADD_SSL_FCT} ${EXAMPLE_COM//./_} \ +# respectively: \ +$(basename ${NGINX_UTIL}) del_ssl ${EXAMPLE_COM//./_} + + + +EOF diff --git a/net/nginx-util/files/nginx.config b/net/nginx-util/files/nginx.config new file mode 100644 index 000000000..4f07ae1d4 --- /dev/null +++ b/net/nginx-util/files/nginx.config @@ -0,0 +1,22 @@ + +config main global + option uci_enable 'true' + +config server '_lan' + list listen '443 ssl default_server' + list listen '[::]:443 ssl default_server' + option server_name '_lan' + list include 'restrict_locally' + list include 'conf.d/*.locations' + option uci_manage_ssl 'self-signed' + option ssl_certificate '/etc/nginx/conf.d/_lan.crt' + option ssl_certificate_key '/etc/nginx/conf.d/_lan.key' + option ssl_session_cache 'shared:SSL:32k' + option ssl_session_timeout '64m' + option access_log 'off; # logd openwrt' + +config server '_redirect2ssl' + list listen '80' + list listen '[::]:80' + option server_name '_redirect2ssl' + option return '302 https://$host$request_uri' diff --git a/net/nginx-util/files/restrict_locally b/net/nginx-util/files/restrict_locally new file mode 100644 index 000000000..0b791cdfd --- /dev/null +++ b/net/nginx-util/files/restrict_locally @@ -0,0 +1,10 @@ + allow ::1; + allow fc00::/7; + allow fec0::/10; + allow fe80::/10; + allow 127.0.0.0/8; + allow 10.0.0.0/8; + allow 172.16.0.0/12; + allow 192.168.0.0/16; + allow 169.254.0.0/16; + deny all; diff --git a/net/nginx-util/files/uci.conf.template b/net/nginx-util/files/uci.conf.template new file mode 100644 index 000000000..1c611d9ad --- /dev/null +++ b/net/nginx-util/files/uci.conf.template @@ -0,0 +1,32 @@ +# Consider using UCI or creating files in /etc/nginx/conf.d/ for configuration. +# Parsing UCI configuration is skipped if uci set nginx.global.uci_enable=false +# For details see: https://openwrt.org/docs/guide-user/services/webserver/nginx + +worker_processes auto; + +user root; + +events {} + +http { + access_log off; + log_format openwrt + '$request_method $scheme://$host$request_uri => $status' + ' (${body_bytes_sent}B in ${request_time}s) <- $http_referer'; + + include mime.types; + default_type application/octet-stream; + sendfile on; + + client_max_body_size 128M; + large_client_header_buffers 2 1k; + + gzip on; + gzip_vary on; + gzip_proxied any; + + root /www; + + #UCI_HTTP_CONFIG + include conf.d/*.conf; +} diff --git a/net/nginx-util/src/.clang-format b/net/nginx-util/src/.clang-format new file mode 100644 index 000000000..3c1ba7372 --- /dev/null +++ b/net/nginx-util/src/.clang-format @@ -0,0 +1,171 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: true +# AllowShortEnumsOnASingleLine: true +AllowShortLambdasOnASingleLine: All +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Always +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: false +# BitFieldColonSpacing: After +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: MultiLine + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: true + BeforeElse: true + # BeforeLambdaBody: true + # BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: None +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + - Regex: '.*' + Priority: 3 + SortPriority: 0 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: true +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Auto +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +... + diff --git a/net/nginx-util/src/.clang-tidy b/net/nginx-util/src/.clang-tidy new file mode 100644 index 000000000..c34ab6ada --- /dev/null +++ b/net/nginx-util/src/.clang-tidy @@ -0,0 +1,407 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,*,-fuchsia-*,-misc-definitions-in-headers,-llvm-header-guard,-*-qualified-auto,-llvm-include-order' +WarningsAsErrors: '' +HeaderFilterRegex: '.*' +AnalyzeTemporaryDtors: false +FormatStyle: file +CheckOptions: + - key: abseil-string-find-startswith.AbseilStringsMatchHeader + value: 'absl/strings/match.h' + - key: abseil-string-find-startswith.IncludeStyle + value: llvm + - key: abseil-string-find-startswith.StringLikeClasses + value: '::std::basic_string' + - key: bugprone-argument-comment.CommentBoolLiterals + value: '0' + - key: bugprone-argument-comment.CommentCharacterLiterals + value: '0' + - key: bugprone-argument-comment.CommentFloatLiterals + value: '0' + - key: bugprone-argument-comment.CommentIntegerLiterals + value: '0' + - key: bugprone-argument-comment.CommentNullPtrs + value: '0' + - key: bugprone-argument-comment.CommentStringLiterals + value: '0' + - key: bugprone-argument-comment.CommentUserDefinedLiterals + value: '0' + - key: bugprone-argument-comment.IgnoreSingleArgument + value: '0' + - key: bugprone-argument-comment.StrictMode + value: '0' + - key: bugprone-assert-side-effect.AssertMacros + value: assert + - key: bugprone-assert-side-effect.CheckFunctionCalls + value: '0' + - key: bugprone-dangling-handle.HandleClasses + value: 'std::basic_string_view;std::experimental::basic_string_view' + - key: bugprone-dynamic-static-initializers.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: bugprone-exception-escape.FunctionsThatShouldNotThrow + value: '' + - key: bugprone-exception-escape.IgnoredExceptions + value: '' + - key: bugprone-misplaced-widening-cast.CheckImplicitCasts + value: '0' + - key: bugprone-not-null-terminated-result.WantToUseSafeFunctions + value: '1' + - key: bugprone-signed-char-misuse.CharTypdefsToIgnore + value: '' + - key: bugprone-sizeof-expression.WarnOnSizeOfCompareToConstant + value: '1' + - key: bugprone-sizeof-expression.WarnOnSizeOfConstant + value: '1' + - key: bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression + value: '0' + - key: bugprone-sizeof-expression.WarnOnSizeOfThis + value: '1' + - key: bugprone-string-constructor.LargeLengthThreshold + value: '8388608' + - key: bugprone-string-constructor.WarnOnLargeLength + value: '1' + - key: bugprone-suspicious-enum-usage.StrictMode + value: '0' + - key: bugprone-suspicious-missing-comma.MaxConcatenatedTokens + value: '5' + - key: bugprone-suspicious-missing-comma.RatioThreshold + value: '0.200000' + - key: bugprone-suspicious-missing-comma.SizeThreshold + value: '5' + - key: bugprone-suspicious-string-compare.StringCompareLikeFunctions + value: '' + - key: bugprone-suspicious-string-compare.WarnOnImplicitComparison + value: '1' + - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison + value: '0' + - key: bugprone-too-small-loop-variable.MagnitudeBitsUpperLimit + value: '16' + - key: bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField + value: '1' + - key: bugprone-unused-return-value.CheckedFunctions + value: '::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty' + - key: cert-dcl16-c.IgnoreMacros + value: '1' + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cert-dcl59-cpp.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: cert-err09-cpp.CheckThrowTemporaries + value: '1' + - key: cert-err61-cpp.CheckThrowTemporaries + value: '1' + - key: cert-msc32-c.DisallowedSeedTypes + value: 'time_t,std::time_t' + - key: cert-msc51-cpp.DisallowedSeedTypes + value: 'time_t,std::time_t' + - key: cert-oop11-cpp.IncludeStyle + value: llvm + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: '0' + - key: cppcoreguidelines-avoid-magic-numbers.IgnoredFloatingPointValues + value: '1.0;100.0;' + - key: cppcoreguidelines-avoid-magic-numbers.IgnoredIntegerValues + value: '1;2;3;4;' + - key: cppcoreguidelines-explicit-virtual-functions.AllowOverrideAndFinal + value: '0' + - key: cppcoreguidelines-explicit-virtual-functions.FinalSpelling + value: final + - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors + value: '1' + - key: cppcoreguidelines-explicit-virtual-functions.OverrideSpelling + value: override + - key: cppcoreguidelines-macro-usage.AllowedRegexp + value: '^DEBUG_*' + - key: cppcoreguidelines-macro-usage.CheckCapsOnly + value: '0' + - key: cppcoreguidelines-macro-usage.IgnoreCommandLineMacros + value: '1' + - key: cppcoreguidelines-no-malloc.Allocations + value: '::malloc;::calloc' + - key: cppcoreguidelines-no-malloc.Deallocations + value: '::free' + - key: cppcoreguidelines-no-malloc.Reallocations + value: '::realloc' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + - key: cppcoreguidelines-owning-memory.LegacyResourceConsumers + value: '::free;::realloc;::freopen;::fclose' + - key: cppcoreguidelines-owning-memory.LegacyResourceProducers + value: '::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile' + - key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader + value: '' + - key: cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle + value: '0' + - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays + value: '0' + - key: cppcoreguidelines-pro-type-member-init.UseAssignment + value: '0' + - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions + value: '0' + - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor + value: '0' + - key: google-build-namespaces.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: google-global-names-in-headers.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.BranchThreshold + value: '4294967295' + - key: google-readability-function-size.LineThreshold + value: '4294967295' + - key: google-readability-function-size.NestingThreshold + value: '4294967295' + - key: google-readability-function-size.ParameterThreshold + value: '4294967295' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-function-size.VariableThreshold + value: '4294967295' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: google-runtime-int.SignedTypePrefix + value: int + - key: google-runtime-int.TypeSuffix + value: '' + - key: google-runtime-int.UnsignedTypePrefix + value: uint + - key: google-runtime-references.WhiteListTypes + value: '' + - key: hicpp-braces-around-statements.ShortStatementLines + value: '0' + - key: hicpp-function-size.BranchThreshold + value: '4294967295' + - key: hicpp-function-size.LineThreshold + value: '4294967295' + - key: hicpp-function-size.NestingThreshold + value: '4294967295' + - key: hicpp-function-size.ParameterThreshold + value: '4294967295' + - key: hicpp-function-size.StatementThreshold + value: '800' + - key: hicpp-function-size.VariableThreshold + value: '4294967295' + - key: hicpp-member-init.IgnoreArrays + value: '0' + - key: hicpp-member-init.UseAssignment + value: '0' + - key: hicpp-move-const-arg.CheckTriviallyCopyableMove + value: '1' + - key: hicpp-multiway-paths-covered.WarnOnMissingElse + value: '0' + - key: hicpp-named-parameter.IgnoreFailedSplit + value: '0' + - key: hicpp-no-malloc.Allocations + value: '::malloc;::calloc' + - key: hicpp-no-malloc.Deallocations + value: '::free' + - key: hicpp-no-malloc.Reallocations + value: '::realloc' + - key: hicpp-signed-bitwise.IgnorePositiveIntegerLiterals + value: '0' + - key: hicpp-special-member-functions.AllowMissingMoveFunctions + value: '0' + - key: hicpp-special-member-functions.AllowSoleDefaultDtor + value: '0' + - key: hicpp-uppercase-literal-suffix.IgnoreMacros + value: '1' + - key: hicpp-uppercase-literal-suffix.NewSuffixes + value: '' + - key: hicpp-use-auto.MinTypeNameLength + value: '5' + - key: hicpp-use-auto.RemoveStars + value: '0' + - key: hicpp-use-emplace.ContainersWithPushBack + value: '::std::vector;::std::list;::std::deque' + - key: hicpp-use-emplace.SmartPointers + value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr' + - key: hicpp-use-emplace.TupleMakeFunctions + value: '::std::make_pair;::std::make_tuple' + - key: hicpp-use-emplace.TupleTypes + value: '::std::pair;::std::tuple' + - key: hicpp-use-equals-default.IgnoreMacros + value: '1' + - key: hicpp-use-equals-delete.IgnoreMacros + value: '1' + - key: hicpp-use-noexcept.ReplacementString + value: '' + - key: hicpp-use-noexcept.UseNoexceptFalse + value: '1' + - key: hicpp-use-nullptr.NullMacros + value: '' + - key: hicpp-use-override.AllowOverrideAndFinal + value: '0' + - key: hicpp-use-override.FinalSpelling + value: final + - key: hicpp-use-override.IgnoreDestructors + value: '0' + - key: hicpp-use-override.OverrideSpelling + value: override + - key: llvm-namespace-comment.ShortNamespaceLines + value: '1' + - key: llvm-namespace-comment.SpacesBeforeComments + value: '1' + - key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries + value: '1' + - key: misc-unused-parameters.StrictMode + value: '0' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-make-shared.IgnoreMacros + value: '1' + - key: modernize-make-shared.IncludeStyle + value: '0' + - key: modernize-make-shared.MakeSmartPtrFunction + value: 'std::make_shared' + - key: modernize-make-shared.MakeSmartPtrFunctionHeader + value: memory + - key: modernize-make-unique.IgnoreMacros + value: '1' + - key: modernize-make-unique.IncludeStyle + value: '0' + - key: modernize-make-unique.MakeSmartPtrFunction + value: 'std::make_unique' + - key: modernize-make-unique.MakeSmartPtrFunctionHeader + value: memory + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-pass-by-value.ValuesOnly + value: '0' + - key: modernize-raw-string-literal.ReplaceShorterLiterals + value: '0' + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-replace-random-shuffle.IncludeStyle + value: llvm + - key: modernize-use-auto.MinTypeNameLength + value: '5' + - key: modernize-use-auto.RemoveStars + value: '0' + - key: modernize-use-default-member-init.IgnoreMacros + value: '1' + - key: modernize-use-default-member-init.UseAssignment + value: '0' + - key: modernize-use-emplace.ContainersWithPushBack + value: '::std::vector;::std::list;::std::deque' + - key: modernize-use-emplace.SmartPointers + value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr' + - key: modernize-use-emplace.TupleMakeFunctions + value: '::std::make_pair;::std::make_tuple' + - key: modernize-use-emplace.TupleTypes + value: '::std::pair;::std::tuple' + - key: modernize-use-equals-default.IgnoreMacros + value: '1' + - key: modernize-use-equals-delete.IgnoreMacros + value: '1' + - key: modernize-use-nodiscard.ReplacementString + value: '[[nodiscard]]' + - key: modernize-use-noexcept.ReplacementString + value: '' + - key: modernize-use-noexcept.UseNoexceptFalse + value: '1' + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: modernize-use-override.AllowOverrideAndFinal + value: '0' + - key: modernize-use-override.FinalSpelling + value: final + - key: modernize-use-override.IgnoreDestructors + value: '0' + - key: modernize-use-override.OverrideSpelling + value: override + - key: modernize-use-transparent-functors.SafeMode + value: '0' + - key: modernize-use-using.IgnoreMacros + value: '1' + - key: objc-forbidden-subclassing.ForbiddenSuperClassNames + value: 'ABNewPersonViewController;ABPeoplePickerNavigationController;ABPersonViewController;ABUnknownPersonViewController;NSHashTable;NSMapTable;NSPointerArray;NSPointerFunctions;NSTimer;UIActionSheet;UIAlertView;UIImagePickerController;UITextInputMode;UIWebView' + - key: openmp-exception-escape.IgnoredExceptions + value: '' + - key: performance-faster-string-find.StringLikeClasses + value: 'std::basic_string' + - key: performance-for-range-copy.AllowedTypes + value: '' + - key: performance-for-range-copy.WarnOnAllAutoCopies + value: '0' + - key: performance-inefficient-string-concatenation.StrictMode + value: '0' + - key: performance-inefficient-vector-operation.EnableProto + value: '0' + - key: performance-inefficient-vector-operation.VectorLikeClasses + value: '::std::vector' + - key: performance-move-const-arg.CheckTriviallyCopyableMove + value: '1' + - key: performance-move-constructor-init.IncludeStyle + value: llvm + - key: performance-no-automatic-move.AllowedTypes + value: '' + - key: performance-type-promotion-in-math-fn.IncludeStyle + value: llvm + - key: performance-unnecessary-copy-initialization.AllowedTypes + value: '' + - key: performance-unnecessary-value-param.AllowedTypes + value: '' + - key: performance-unnecessary-value-param.IncludeStyle + value: llvm + - key: portability-simd-intrinsics.Std + value: '' + - key: portability-simd-intrinsics.Suggest + value: '0' + - key: readability-braces-around-statements.ShortStatementLines + value: '0' + - key: readability-else-after-return.WarnOnUnfixable + value: '1' + - key: readability-function-size.BranchThreshold + value: '4294967295' + - key: readability-function-size.LineThreshold + value: '4294967295' + - key: readability-function-size.NestingThreshold + value: '4294967295' + - key: readability-function-size.ParameterThreshold + value: '4294967295' + - key: readability-function-size.StatementThreshold + value: '800' + - key: readability-function-size.VariableThreshold + value: '4294967295' + - key: readability-identifier-naming.IgnoreFailedSplit + value: '0' + - key: readability-implicit-bool-conversion.AllowIntegerConditions + value: '0' + - key: readability-implicit-bool-conversion.AllowPointerConditions + value: '0' + - key: readability-inconsistent-declaration-parameter-name.IgnoreMacros + value: '1' + - key: readability-inconsistent-declaration-parameter-name.Strict + value: '0' + - key: readability-magic-numbers.IgnoredFloatingPointValues + value: '1.0;100.0;' + - key: readability-magic-numbers.IgnoredIntegerValues + value: '1;2;3;4;' + - key: readability-redundant-member-init.IgnoreBaseInCopyConstructors + value: '0' + - key: readability-redundant-smartptr-get.IgnoreMacros + value: '1' + - key: readability-redundant-string-init.StringNames + value: '::std::basic_string' + - key: readability-simplify-boolean-expr.ChainedConditionalAssignment + value: '0' + - key: readability-simplify-boolean-expr.ChainedConditionalReturn + value: '0' + - key: readability-simplify-subscript-expr.Types + value: '::std::basic_string;::std::basic_string_view;::std::vector;::std::array' + - key: readability-static-accessed-through-instance.NameSpecifierNestingThreshold + value: '3' + - key: readability-uppercase-literal-suffix.IgnoreMacros + value: '1' + - key: readability-uppercase-literal-suffix.NewSuffixes + value: '' + - key: zircon-temporary-objects.Names + value: '' +... + diff --git a/net/nginx-util/src/CMakeLists.txt b/net/nginx-util/src/CMakeLists.txt index 284853620..2adff1c71 100644 --- a/net/nginx-util/src/CMakeLists.txt +++ b/net/nginx-util/src/CMakeLists.txt @@ -10,6 +10,13 @@ ADD_DEFINITIONS(-Wno-unused-parameter -Wmissing-declarations -Wshadow) SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") +FIND_PATH(uci_include_dir uci.h) +FIND_LIBRARY(uci NAMES uci) +INCLUDE_DIRECTORIES(${uci_include_dir}) + +FIND_PATH(ubox_include_dir libubox/blobmsg.h) +FIND_LIBRARY(ubox NAMES ubox) +INCLUDE_DIRECTORIES(${ubox_include_dir}) IF(UBUS) @@ -19,17 +26,13 @@ FIND_PATH(ubus_include_dir libubus.h) FIND_LIBRARY(ubus NAMES ubus) INCLUDE_DIRECTORIES(${ubus_include_dir}) -FIND_PATH(ubox_include_dir libubox/blobmsg.h) -FIND_LIBRARY(ubox NAMES ubox) -INCLUDE_DIRECTORIES(${ubox_include_dir}) - ADD_EXECUTABLE(nginx-ssl-util nginx-util.cpp) -TARGET_LINK_LIBRARIES(nginx-ssl-util ${ubox} ${ubus} pthread ssl crypto pcre) +TARGET_LINK_LIBRARIES(nginx-ssl-util ${uci} ${ubox} ${ubus} pthread ssl crypto pcre) INSTALL(TARGETS nginx-ssl-util RUNTIME DESTINATION bin) ADD_EXECUTABLE(nginx-ssl-util-nopcre nginx-util.cpp) TARGET_COMPILE_DEFINITIONS(nginx-ssl-util-nopcre PUBLIC -DNO_PCRE) -TARGET_LINK_LIBRARIES(nginx-ssl-util-nopcre ${ubox} ${ubus} pthread ssl crypto) +TARGET_LINK_LIBRARIES(nginx-ssl-util-nopcre ${uci} ${ubox} ${ubus} pthread ssl crypto) INSTALL(TARGETS nginx-ssl-util-nopcre RUNTIME DESTINATION bin) ELSE() @@ -39,6 +42,8 @@ ADD_COMPILE_DEFINITIONS(VERSION=0) CONFIGURE_FILE(test-px5g.sh test-px5g.sh COPYONLY) CONFIGURE_FILE(test-nginx-util.sh test-nginx-util.sh COPYONLY) CONFIGURE_FILE(test-nginx-util-root.sh test-nginx-util-root.sh COPYONLY) +CONFIGURE_FILE(../files/nginx.config config-nginx-ssl COPYONLY) +CONFIGURE_FILE(../files/uci.conf.template uci.conf.template COPYONLY) ADD_EXECUTABLE(px5g px5g.cpp) TARGET_LINK_LIBRARIES(px5g ssl crypto) @@ -46,12 +51,12 @@ INSTALL(TARGETS px5g RUNTIME DESTINATION bin) ADD_EXECUTABLE(nginx-ssl-util-noubus nginx-util.cpp) TARGET_COMPILE_DEFINITIONS(nginx-ssl-util-noubus PUBLIC -DNO_UBUS) -TARGET_LINK_LIBRARIES(nginx-ssl-util-noubus pthread ssl crypto pcre) +TARGET_LINK_LIBRARIES(nginx-ssl-util-noubus ${uci} ${ubox} pthread ssl crypto pcre) INSTALL(TARGETS nginx-ssl-util-noubus RUNTIME DESTINATION bin) ADD_EXECUTABLE(nginx-ssl-util-nopcre-noubus nginx-util.cpp) TARGET_COMPILE_DEFINITIONS(nginx-ssl-util-nopcre-noubus PUBLIC -DNO_PCRE -DNO_UBUS) -TARGET_LINK_LIBRARIES(nginx-ssl-util-nopcre-noubus pthread ssl crypto) +TARGET_LINK_LIBRARIES(nginx-ssl-util-nopcre-noubus ${uci} ${ubox} pthread ssl crypto) INSTALL(TARGETS nginx-ssl-util-nopcre-noubus RUNTIME DESTINATION bin) ENDIF() diff --git a/net/nginx-util/src/LICENSE b/net/nginx-util/src/LICENSE new file mode 100644 index 000000000..bddd69079 --- /dev/null +++ b/net/nginx-util/src/LICENSE @@ -0,0 +1,23 @@ +/* Copyright 2020 Peter Stadler + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/net/nginx-util/src/nginx-ssl-util.hpp b/net/nginx-util/src/nginx-ssl-util.hpp index 895f9569f..4f12925db 100644 --- a/net/nginx-util/src/nginx-ssl-util.hpp +++ b/net/nginx-util/src/nginx-ssl-util.hpp @@ -11,7 +11,6 @@ namespace rgx = std; #include "nginx-util.hpp" #include "px5g-openssl.hpp" - #ifndef NO_UBUS static constexpr auto UBUS_TIMEOUT = 1000; #endif @@ -19,178 +18,175 @@ static constexpr auto UBUS_TIMEOUT = 1000; // 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 = std::string_view{"/var/lib/nginx/lan_ssl.listen"}; -static constexpr auto LAN_SSL_LISTEN_DEFAULT = +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_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 >; +using _Line = std::array; class Line { - -private: - + private: _Line _line; -public: + public: + explicit Line(const _Line& line) noexcept : _line{line} {} - explicit Line(const _Line & line) noexcept : _line{line} {} - - template + template static auto build() noexcept -> Line { - return Line{_Line{ - [](const std::string & p, const std::string & b) -> std::string - { return (... + xn[0](p, b)); }, - [](const std::string & p, const std::string & b) -> std::string - { return (... + xn[1](p, b)); } - }}; + 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 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]("", "")}; } - + { + 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 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 delete_if(const std::string & conf, const rgx::regex & rgx, - const std::string & val="", bool compare=false) +auto replace_listen(const std::string& conf, const std::array& ngx_port) -> std::string; +auto check_ssl_certificate(const std::string& crtpath, const std::string& keypath) -> bool; -void add_ssl_directives_to(const std::string & name, bool isdefault); - +auto contains(const std::string& sentence, const std::string& word) -> bool; -void create_ssl_certificate(const std::string & crtpath, - const std::string & keypath, - int days=792); +auto get_uci_section_for_name(const std::string& name) -> uci::section; +void add_ssl_if_needed(const std::string& name); -void use_cron_to_recreate_certificate(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 add_ssl_if_needed(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_directives_from(const std::string & name, bool isdefault); +void del_ssl(const std::string& name); +void del_ssl(const std::string& name, std::string_view manage); -void del_ssl(const std::string & name); +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 begin; }, - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { return R"([{;](?:\s*#[^\n]*(?=\n))*(\s*))"; } -}; + [](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{" "}; }, -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+)"; } -}; - + [](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"}; + }, - [](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{";"}; + }, -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]*)?))"}; + }}; - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { return std::string{R"(\s*;(?:[\t ]*#[^\n]*)?)"}; } -}; - - -template +template static constexpr auto _capture = _Line{ - [](const std::string & param, const std::string & /*begin*/) -> std::string - { return '\'' + param + '\''; }, - - [](const std::string & /*param*/, const std::string & /*begin*/) - -> std::string - { - const auto lim = clim=='\0' ? std::string{"\\s"} : std::string{clim}; - return std::string{R"(((?:(?:"[^"]*")|(?:[^'")"} + - lim + "][^" + lim + "]*)|(?:'[^']*'))+)"; - } -}; + [](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 -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; +template +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 - { + [](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+"]"; } - } + 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+"\""+")"; - } -}; + 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"; @@ -201,65 +197,82 @@ 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_CMD = Line::build - <_space, _escape, _space, _escape, _space, - _capture<>, _newline>(); +static const auto CRON_CHECK = + Line::build<_space, _escape, _space, _escape<_check_ssl, '\''>, _newline>(); + +static const auto CRON_CMD = Line::build<_space, + _escape, + _space, + _escape, + _space, + _capture<>, + _newline>(); static const auto NGX_SERVER_NAME = Line::build<_begin, _escape<_server_name>, _space, _capture<';'>, _end>(); -static const auto NGX_INCLUDE_LAN_LISTEN = Line::build - <_begin, _escape<_include>, _space, _escape, _end>(); +static const auto NGX_INCLUDE_LAN_LISTEN = + Line::build<_begin, _escape<_include>, _space, _escape, _end>(); -static const auto NGX_INCLUDE_LAN_LISTEN_DEFAULT = Line::build - <_begin, _escape<_include>, _space, - _escape, _end>(); +static const auto NGX_INCLUDE_LAN_LISTEN_DEFAULT = + Line::build<_begin, _escape<_include>, _space, _escape, _end>(); -static const auto NGX_INCLUDE_LAN_SSL_LISTEN = Line::build - <_begin, _escape<_include>, _space, _escape, _end>(); +static const auto NGX_INCLUDE_LAN_SSL_LISTEN = + Line::build<_begin, _escape<_include>, _space, _escape, _end>(); -static const auto NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT = Line::build - <_begin, _escape<_include>, _space, - _escape, _end>(); +static const auto NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT = + Line::build<_begin, _escape<_include>, _space, _escape, _end>(); -static const auto NGX_SSL_CRT = Line::build - <_begin, _escape<_ssl_certificate>, _space, _capture<';'>, _end>(); +static const auto NGX_SSL_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_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_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_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>(); -auto get_if_missed(const std::string & conf, const Line & LINE, - const std::string & val, - const std::string & indent, bool compare) - -> std::string +static const auto NGX_PORT_80 = std::array{ + R"(^\s*([^:]*:|\[[^\]]*\]:)?80(\s|$|;))", + "$01443 ssl$2", +}; + +static const auto NGX_PORT_443 = std::array{ + 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! + rgx::smatch match; // assuming last capture has the value! - for (auto pos = conf.begin(); - rgx::regex_search(pos, conf.end(), match, LINE.RGX()); + 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() - 1); + const std::string value = match.str(match.size() - 2); - if (value==val || value=="'"+val+"'" || value=='"'+val+'"') { + if (value == val || value == "'" + val + "'" || value == '"' + val + '"') { return ""; } } @@ -267,75 +280,99 @@ auto get_if_missed(const std::string & conf, const Line & LINE, 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 delete_if(const std::string & conf, const rgx::regex & rgx, - const std::string & val, const bool compare) + 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& ngx_port) -> 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)) + for (rgx::smatch match; rgx::regex_search(pos, conf.end(), match, NGX_LISTEN.RGX()); + pos += match.position(match.size() - 1)) { - const std::string value = match.str(match.size() - 1); - auto len = match.position(1); - if (compare && value!=val && value!="'"+val+"'" && value!='"'+val+'"') { - len = match.position(0) + match.length(0); - } - ret.append(pos, pos + len); + 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; } - -void add_ssl_directives_to(const std::string & name, const bool isdefault) +inline void add_ssl_directives_to(const std::string& name) { const std::string prefix = std::string{CONF_DIR} + name; - std::string conf = read_file(prefix+".conf"); + const std::string const_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 + 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)) + 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; } + if (!contains(match.str(2), name)) { + continue; + } // else: 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); + auto adds = std::string{}; - adds += get_if_missed(conf, NGX_SSL_CRT, prefix+".crt", indent); + adds += get_if_missed(const_conf, NGX_SSL_CRT, prefix + ".crt", indent); - adds += get_if_missed(conf, NGX_SSL_KEY, prefix+".key", indent); + adds += get_if_missed(const_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(const_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); + adds += get_if_missed(const_conf, NGX_SSL_SESSION_TIMEOUT, + std::string{SSL_SESSION_TIMEOUT_ARG}, indent, false); - if (adds.length() > 0) { - pos += match.position(0) + match.length(0); + pos += match.position(0) + match.length(0); + std::string conf = + std::string(const_conf.begin(), pos) + adds + std::string(pos, const_conf.end()); - 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 = isdefault ? - delete_if(conf, NGX_INCLUDE_LAN_LISTEN_DEFAULT.RGX()) : - delete_if(conf, NGX_INCLUDE_LAN_LISTEN.RGX()); + conf = replace_if(conf, NGX_INCLUDE_LAN_LISTEN.RGX(), "", + NGX_INCLUDE_LAN_SSL_LISTEN.STR("", indent)); - write_file(prefix+".conf", conf); + conf = replace_listen(conf, NGX_PORT_80); - std::cerr<<"Added SSL directives to "< -inline auto num2hex(T bytes) -> std::array +template +inline auto num2hex(T bytes) -> std::array { - constexpr auto n = 2*sizeof(bytes); - std::array str{}; + constexpr auto n = 2 * sizeof(bytes); + std::array str{}; - for (size_t i=0; i hex{"0123456789ABCDEF"}; static constexpr auto get = 0x0fU; str.at(i) = hex.at(bytes & get); @@ -367,9 +403,8 @@ inline auto num2hex(T bytes) -> std::array return str; } - -template -inline auto get_nonce(const T salt=0) -> T +template +inline auto get_nonce(const T salt = 0) -> T { T nonce = 0; @@ -377,10 +412,12 @@ inline auto get_nonce(const T salt=0) -> T static constexpr auto move = 6U; - constexpr size_t steps = (sizeof(nonce)*8 - 1)/move + 1; + constexpr size_t steps = (sizeof(nonce) * 8 - 1) / move + 1; - for (size_t i=0; i(urandom.get()); } @@ -389,20 +426,19 @@ inline auto get_nonce(const T salt=0) -> T return nonce; } - -void create_ssl_certificate(const std::string & crtpath, - const std::string & keypath, - const int days) +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); } + try { + nonce = get_nonce(nonce); + } - catch (...) { // the address of a variable should be random enough: - auto addr = &crtpath; - auto addrptr = static_cast( - static_cast(&addr) ); - nonce += *addrptr; + catch (...) { // the address of a variable should be random enough: + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) sic: + nonce += reinterpret_cast(&crtpath); } auto noncestr = num2hex(nonce); @@ -415,167 +451,434 @@ void create_ssl_certificate(const std::string & crtpath, write_key(pkey, tmpkeypath); - std::string subject {"/C=ZZ/ST=Somewhere/L=None/CN=OpenWrt/O=OpenWrt"}; + 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 to_seconds = 24 * 60 * 60; static constexpr auto leeway = 42; - if (!checkend(tmpcrtpath, days*to_seconds - leeway)) { + 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; - } catch (...) { - std::cerr<<"create_ssl_certificate error: "; - std::cerr<<"cannot create selfsigned certificate, "; - std::cerr<<"removing temporary files ..."< 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; +} -void use_cron_to_recreate_certificate(const std::string & name) +auto get_uci_section_for_name(const std::string& name) -> uci::section { - static const char * filename = "/etc/crontabs/root"; + 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. */ } + 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); + const std::string add = get_if_missed(conf, CRON_LINE, 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"; + 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: + } // else active with or without instances: #endif - write_file(filename, std::string{CRON_INTERVAL}+add, std::ios::app); + 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 ssl certificate for '"; - std::cerr< " + 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); + } -void add_ssl_if_needed(const std::string & name) + add_ssl_directives_to(name); // let it throw. +} + +void remove_cron_job(const Line& CRON_LINE, const std::string& name) { - add_ssl_directives_to(name, name==LAN_NAME); // let it throw. + static const char* filename = "/etc/crontabs/root"; - 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); + const auto const_conf = read_file(filename); - bool is_valid = true; + bool changed = false; + auto conf = std::string{}; - if (access(keypath.c_str(), R_OK) != 0 || - access(crtpath.c_str(), R_OK) != 0) - { is_valid = false; } + 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); - else { - try { - if (!checkend(crtpath, remaining_seconds)) { - is_valid = false; - } + if (line == replace_if(line, CRON_LINE.RGX(), name, "")) { + conf += line; } - 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; - } + else { + changed = true; } + + prev = curr + 1; } - if (!is_valid) { create_ssl_certificate(crtpath, keypath, validity_days); } + if (changed) { + write_file(filename, conf); - try { use_cron_to_recreate_certificate(name); } - catch (...) { - std::cerr<<"add_ssl_if_needed warning: "; - std::cerr<<"cannot use cron to rebuild certificate for "< 0) { - pos += match.position(1); + std::string conf = const_conf; - conf = std::string(const_conf.begin(), pos) + adds - + std::string(pos, const_conf.end()); + conf = replace_listen(conf, NGX_PORT_443); - conf = isdefault ? - delete_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT.RGX()) - : delete_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN.RGX()); + conf = replace_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN_DEFAULT.RGX(), "", + NGX_INCLUDE_LAN_LISTEN_DEFAULT.STR("", indent)); - const auto crtpath = prefix+".crt"; - conf = delete_if(conf, NGX_SSL_CRT.RGX(), crtpath, true); + conf = replace_if(conf, NGX_INCLUDE_LAN_SSL_LISTEN.RGX(), "", + NGX_INCLUDE_LAN_LISTEN.STR("", indent)); - const auto keypath = prefix+".key"; - conf = delete_if(conf, NGX_SSL_KEY.RGX(), keypath, true); + // NOLINTNEXTLINE(performance-inefficient-string-concatenation) prefix: + conf = replace_if(conf, NGX_SSL_CRT.RGX(), prefix + ".crt", ""); - conf = delete_if(conf, NGX_SSL_SESSION_CACHE.RGX()); + // NOLINTNEXTLINE(performance-inefficient-string-concatenation) prefix: + conf = replace_if(conf, NGX_SSL_KEY.RGX(), prefix + ".key", ""); - conf = delete_if(conf, NGX_SSL_SESSION_TIMEOUT.RGX()); + conf = replace_if(conf, NGX_SSL_SESSION_CACHE.RGX(), "", ""); - write_file(prefix+".conf", conf); + conf = replace_if(conf, NGX_SSL_SESSION_TIMEOUT.RGX(), "", ""); - std::cerr<<"Deleted SSL directives from "< bool +{ + const auto legacypath = std::string{CONF_DIR} + name + ".conf"; + + if (access(legacypath.c_str(), R_OK) != 0) { + return false; } - try { del_ssl_directives_from(name, name==LAN_NAME); } + 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 "< 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 diff --git a/net/nginx-util/src/nginx-util.cpp b/net/nginx-util/src/nginx-util.cpp index b0a724da6..3b4ad8c60 100644 --- a/net/nginx-util/src/nginx-util.cpp +++ b/net/nginx-util/src/nginx-util.cpp @@ -1,194 +1,378 @@ #include +#include -#include "nginx-util.hpp" - -#ifndef NO_SSL #include "nginx-ssl-util.hpp" -#endif +#include "nginx-util.hpp" +static auto constexpr file_comment_auto_created = + std::string_view{"# This file is re-created when Nginx starts.\n"}; -void create_lan_listen() +// TODO(pst) replace it with blobmsg_get_string if upstream takes const: +#ifndef NO_UBUS +static inline auto _pst_get_string(const blob_attr* attr) -> char* { - 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 -#ifndef NO_SSL - ,&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"; -#ifndef NO_SSL - ssl_listen += "\tlisten " + val + ":443 ssl;\n"; - ssl_listen_default += "\tlisten " + val + ":443 ssl default_server;\n"; + return static_cast(blobmsg_data(attr)); +} #endif - }; + +void create_lan_listen() // create empty files for compatibility: +{ + // TODO(pst): replace by dummies after transitioning nginx config to UCI: + std::vector ips; #ifndef NO_UBUS try { - auto loopback_status=ubus::call("network.interface.loopback", "status"); + auto loopback_status = ubus::call("network.interface.loopback", "status"); - for (auto ip : loopback_status.filter("ipv4-address", "", "address")) { - add_listen("", static_cast(blobmsg_data(ip)), ""); + for (const auto* ip : loopback_status.filter("ipv4-address", "", "address")) { + ips.emplace_back(_pst_get_string(ip)); } - for (auto ip : loopback_status.filter("ipv6-address", "", "address")) { - add_listen("[", static_cast(blobmsg_data(ip)), "]"); + for (const auto* ip : loopback_status.filter("ipv6-address", "", "address")) { + ips.emplace_back(std::string{"["} + _pst_get_string(ip) + "]"); } - } catch (const std::runtime_error &) { /* do nothing about it */ } + } + catch (const std::runtime_error&) { /* do nothing about it */ + } try { auto lan_status = ubus::call("network.interface.lan", "status"); - for (auto ip : lan_status.filter("ipv4-address", "", "address")) { - add_listen("", static_cast(blobmsg_data(ip)), ""); + for (const auto* ip : lan_status.filter("ipv4-address", "", "address")) { + ips.emplace_back(_pst_get_string(ip)); } - for (auto ip : lan_status.filter("ipv6-address", "", "address")) { - add_listen("[", static_cast(blobmsg_data(ip)), "]"); + for (const auto* ip : lan_status.filter("ipv6-address", "", "address")) { + ips.emplace_back(std::string{"["} + _pst_get_string(ip) + "]"); } - for (auto ip : lan_status.filter("ipv6-prefix-assignment", "", - "local-address", "address")) - { - add_listen("[", static_cast(blobmsg_data(ip)), "]"); + for (const auto* ip : + lan_status.filter("ipv6-prefix-assignment", "", "local-address", "address")) { + ips.emplace_back(std::string{"["} + _pst_get_string(ip) + "]"); } - } catch (const std::runtime_error &) { /* do nothing about it */ } + } + catch (const std::runtime_error&) { /* do nothing about it */ + } #else - add_listen("", "127.0.0.1", ""); + ips.emplace_back("127.0.0.1"); #endif + std::string listen = std::string{file_comment_auto_created}; + std::string listen_default = std::string{file_comment_auto_created}; + for (const auto& ip : ips) { + listen += "\tlisten " + ip + ":80;\n"; + listen_default += "\tlisten " + ip + ":80 default_server;\n"; + } write_file(LAN_LISTEN, listen); write_file(LAN_LISTEN_DEFAULT, listen_default); -#ifndef NO_SSL + + std::string ssl_listen = std::string{file_comment_auto_created}; + std::string ssl_listen_default = std::string{file_comment_auto_created}; + for (const auto& ip : ips) { + ssl_listen += "\tlisten " + ip + ":443 ssl;\n"; + ssl_listen_default += "\tlisten " + ip + ":443 ssl default_server;\n"; + } write_file(LAN_SSL_LISTEN, ssl_listen); write_file(LAN_SSL_LISTEN_DEFAULT, ssl_listen_default); -#endif } +inline auto change_if_starts_with(const std::string_view& subject, + const std::string_view& prefix, + const std::string_view& substitute, + const std::string_view& seperator = " \t\n;") -> std::string +{ + auto view = subject; + view = view.substr(view.find_first_not_of(seperator)); + if (view.rfind(prefix, 0) == 0) { + if (view.size() == prefix.size()) { + return std::string{substitute}; + } + view = view.substr(prefix.size()); + if (seperator.find(view[0]) != std::string::npos) { + auto ret = std::string{substitute}; + ret += view; + return ret; + } + } + return std::string{subject}; +} + +inline auto create_server_conf(const uci::section& sec, const std::string& indent = "") + -> std::string +{ + auto secname = sec.name(); + + auto legacypath = std::string{CONF_DIR} + secname + ".conf"; + if (access(legacypath.c_str(), R_OK) == 0) { + auto message = std::string{"skipped UCI server 'nginx."} + secname; + message += "' as it could conflict with: " + legacypath + "\n"; + + // TODO(pst) std::cerr<<"create_server_conf notice: "< bool +{ + for (auto sec : pkg) { + if (sec.type() != std::string_view{"main"}) { + continue; + } + if (sec.name() != std::string_view{"global"}) { + continue; + } + for (auto opt : sec) { + if (opt.name() != "uci_enable") { + continue; + } + for (auto itm : opt) { + if (itm) { + return true; + } + } + } + } + return false; +} + +/* + * ___________main_thread________________|______________thread_1________________ + * create_lan_listen() or do nothing | config = uci::package("nginx") + * if config_enabled (set in thread_1): | config_enabled = is_enabled(config) + * then init_uci(config) | check_ssl(config, config_enabled) + */ void init_lan() { std::exception_ptr ex; - -#ifndef NO_SSL - auto thrd = std::thread([]{ //&ex - try { add_ssl_if_needed(std::string{LAN_NAME}); } + std::unique_ptr config; + bool config_enabled = false; + std::mutex configuring; + + configuring.lock(); + auto thrd = std::thread([&config, &config_enabled, &configuring, &ex] { + try { + config = std::make_unique("nginx"); + config_enabled = is_enabled(*config); + configuring.unlock(); + check_ssl(*config, config_enabled); + } catch (...) { - std::cerr<<"init_lan notice: no server named "< int +auto main(int argc, char* argv[]) -> int { // TODO(pst): use std::span when available: - auto args = std::basic_string_view{argv, static_cast(argc)}; + auto args = std::basic_string_view{argv, static_cast(argc)}; auto cmds = std::array{ std::array{"init_lan", ""}, std::array{"get_env", ""}, -#ifndef NO_SSL - std::array{ADD_SSL_FCT, " server_name" }, - std::array{"del_ssl", " server_name" }, -#endif + std::array{ + ADD_SSL_FCT, "server_name [manager /path/to/ssl_certificate /path/to/ssl_key]"}, + std::array{"del_ssl", "server_name [manager]"}, + std::array{"check_ssl", ""}, }; try { + if (argc == 2 && args[1] == cmds[0][0]) { + init_lan(); + } - if (argc==2 && args[1]==cmds[0][0]) { init_lan(); } + else if (argc == 2 && args[1] == cmds[1][0]) { + get_env(); + } - else if (argc==2 && args[1]==cmds[1][0]) { get_env(); } + else if (argc == 3 && args[1] == cmds[2][0]) { + add_ssl_if_needed(std::string{args[2]}); + } -#ifndef NO_SSL - else if (argc==3 && args[1]==cmds[2][0]) - { add_ssl_if_needed(std::string{args[2]});} + // NOLINTNEXTLINE(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers): 6 + else if (argc == 6 && args[1] == cmds[2][0]) { + // NOLINTNEXTLINE(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers): 5 + add_ssl_if_needed(std::string{args[2]}, args[3], args[4], args[5]); + } - else if (argc==3 && args[1]==cmds[3][0]) - { del_ssl(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 if (argc == 4 && args[1] == cmds[3][0]) { + del_ssl(std::string{args[2]}, args[3]); + } + + else if (argc == 2 && args[1] == cmds[3][0]) // TODO(pst) deprecate + { + try { + auto name = std::string{LAN_NAME}; + if (del_ssl_legacy(name)) { + auto crtpath = std::string{CONF_DIR} + name + ".crt"; + remove(crtpath.c_str()); + auto keypath = std::string{CONF_DIR} + name + ".key"; + remove(keypath.c_str()); + } + } + catch (...) { /* do nothing. */ + } + } + + else if (argc == 2 && args[1] == cmds[4][0]) { + check_ssl(uci::package{"nginx"}); + } else { - std::cerr<<"Tool for creating Nginx configuration files ("; + std::cerr << "Tool for creating Nginx configuration files ("; #ifdef VERSION - std::cerr<<"version "< #include #include -#include +// #include +#include #include +#include #include #ifndef NO_UBUS #include "ubus-cxx.hpp" #endif +#include "uci-cxx.hpp" static constexpr auto NGINX_UTIL = std::string_view{"/usr/bin/nginx-util"}; +static constexpr auto VAR_UCI_CONF = std::string_view{"/var/lib/nginx/uci.conf"}; + +static constexpr auto UCI_CONF = std::string_view{"/etc/nginx/uci.conf"}; + 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 auto constexpr MANAGE_SSL = std::string_view{"uci_manage_ssl"}; -static constexpr auto LAN_LISTEN_DEFAULT = - std::string_view{"/var/lib/nginx/lan.listen.default"}; +static constexpr auto LAN_LISTEN = std::string_view{"/var/lib/nginx/lan.listen"}; +static constexpr auto LAN_LISTEN_DEFAULT = // TODO(pst) deprecate + 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); - +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; - +auto read_file(const std::string_view& name, std::ios_base::openmode mode = std::ios::in) + -> std::string; // all S must be convertible to const char[] -template -auto call(const std::string & program, S... args) -> pid_t; - +template +auto call(const std::string& program, S... args) -> pid_t; void create_lan_listen(); +void init_uci(const uci::package& pkg); -void init_lan(); +auto is_enabled(const uci::package& pkg) -> bool; +void init_lan(); void get_env(); - - // --------------------- partial implementation: ------------------------------ - -void write_file(const std::string_view & name, const std::string & str, +void write_file(const std::string_view& name, + const std::string& str, const std::ios_base::openmode flag) { auto tmp = std::string{name}; - if ( (flag & std::ios::ate) == 0 && (flag & std::ios::app) == 0 ) { + if ((flag & std::ios::ate) == 0 && (flag & std::ios::app) == 0) { tmp += ".tmp-XXXXXX"; auto fd = mkstemp(&tmp[0]); - if (fd==-1 || close(fd)!=0) - { throw std::runtime_error("write_file error: cannot access " + tmp); } + if (fd == -1 || close(fd) != 0) { + throw std::runtime_error("write_file error: cannot access " + tmp); + } } try { std::ofstream file(tmp.data(), flag); if (!file.good()) { - throw std::ofstream::failure - ("write_file error: cannot open " + std::string{tmp}); + throw std::ofstream::failure("write_file error: cannot open " + std::string{tmp}); } - file< std::string +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); + std::ifstream file(name.data(), mode | std::ios::ate); if (!file.good()) { - throw std::ifstream::failure( - "read_file error: cannot open " + std::string{name}); + throw std::ifstream::failure("read_file error: cannot open " + std::string{name}); } std::string ret{}; @@ -108,27 +113,25 @@ auto read_file(const std::string_view & name, ret.reserve(size); file.seekg(0); - ret.assign((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); + ret.assign((std::istreambuf_iterator(file)), std::istreambuf_iterator()); file.close(); return ret; } - -template -auto call(const char * program, S... args) -> pid_t +template +auto call(const char* program, S... args) -> pid_t { pid_t pid = fork(); - if (pid==0) { //child: - std::array argv = - { strdup(program), strdup(args)..., nullptr }; + if (pid == 0) { // child: + std::array argv = {strdup(program), strdup(args)..., nullptr}; - execv(program, argv.data()); // argv cannot be const char * const[]! + execv(program, argv.data()); // argv cannot be const char * const[]! _exit(EXIT_FAILURE); // exec never returns. - } else if (pid>0) { //parent: + } + else if (pid > 0) { // parent: return pid; } @@ -137,5 +140,4 @@ auto call(const char * program, S... args) -> pid_t throw std::runtime_error(errmsg); } - #endif diff --git a/net/nginx-util/src/px5g-openssl.hpp b/net/nginx-util/src/px5g-openssl.hpp index 1b70dc5b0..7c79bad97 100644 --- a/net/nginx-util/src/px5g-openssl.hpp +++ b/net/nginx-util/src/px5g-openssl.hpp @@ -3,15 +3,14 @@ // #define OPENSSL_API_COMPAT 0x10102000L #include -#include #include #include #include #include +#include +#include #include #include -#include - static constexpr auto rsa_min_modulus_bits = 512; @@ -19,67 +18,51 @@ using EVP_PKEY_ptr = std::unique_ptr; using X509_NAME_ptr = std::unique_ptr; - -auto checkend(const std::string & crtpath, - time_t seconds=0, bool use_pem=true) -> bool; - +auto 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; -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); +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 +inline auto print_error(const char* str, const size_t /*len*/, void* errmsg) -> int { - *static_cast(errmsg) += str; + *static_cast(errmsg) += str; return 0; } - // wrapper for clang-tidy: -inline auto _BIO_new_fp(FILE * stream, const bool use_pem, - const bool close=false) -> BIO * +inline auto _BIO_new_fp(FILE* stream, const bool use_pem, const bool close = false) -> BIO* { - return BIO_new_fp( stream, //NOLINTNEXTLINE(hicpp-signed-bitwise) macros: - (use_pem ? BIO_FP_TEXT : 0) | (close ? BIO_CLOSE : BIO_NOCLOSE) ); + return BIO_new_fp(stream, // NOLINTNEXTLINE(hicpp-signed-bitwise) macros: + (use_pem ? BIO_FP_TEXT : 0) | (close ? BIO_CLOSE : BIO_NOCLOSE)); } - -auto checkend(const std::string & crtpath, - const time_t seconds, const bool use_pem) -> bool +auto checkend(const std::string& crtpath, const time_t seconds, const bool use_pem) -> bool { - BIO * bio = crtpath.empty() ? - _BIO_new_fp(stdin, use_pem) : - BIO_new_file(crtpath.c_str(), (use_pem ? "r" : "rb")); + BIO* bio = crtpath.empty() ? _BIO_new_fp(stdin, use_pem) + : BIO_new_file(crtpath.c_str(), (use_pem ? "r" : "rb")); - X509 * x509 = nullptr; + X509* x509 = nullptr; if (bio != nullptr) { - x509 = use_pem ? - PEM_read_bio_X509_AUX(bio, nullptr, nullptr, nullptr) : - d2i_X509_bio(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) { + 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); @@ -93,10 +76,9 @@ auto checkend(const std::string & crtpath, 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; + 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 "}; @@ -109,12 +91,10 @@ auto gen_eckey(const int curve) -> EVP_PKEY_ptr EC_GROUP_set_point_conversion_form(group, POINT_CONVERSION_UNCOMPRESSED); - auto eckey = EC_KEY_new(); + auto* eckey = EC_KEY_new(); if (eckey != nullptr) { - if ( (EC_KEY_set_group(eckey, group) == 0) || - (EC_KEY_generate_key(eckey) == 0) ) - { + if ((EC_KEY_set_group(eckey, group) == 0) || (EC_KEY_generate_key(eckey) == 0)) { EC_KEY_free(eckey); eckey = nullptr; } @@ -132,7 +112,7 @@ auto gen_eckey(const int curve) -> EVP_PKEY_ptr EVP_PKEY_ptr pkey{EVP_PKEY_new(), ::EVP_PKEY_free}; // EVP_PKEY_assign_EC_KEY is a macro casting eckey to char *: - //NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) if (!EVP_PKEY_assign_EC_KEY(pkey.get(), eckey)) { EC_KEY_free(eckey); std::string errmsg{"gen_eckey error: cannot assign EC key to EVP\n"}; @@ -143,16 +123,15 @@ auto gen_eckey(const int curve) -> EVP_PKEY_ptr return pkey; } - auto gen_rsakey(const int keysize, const BN_ULONG exponent) -> EVP_PKEY_ptr { - if (keysizeOPENSSL_RSA_MAX_MODULUS_BITS) { + 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); } - auto bignum = BN_new(); + auto* bignum = BN_new(); if (bignum == nullptr) { std::string errmsg{"gen_rsakey error: cannot get big number struct\n"}; @@ -160,7 +139,7 @@ auto gen_rsakey(const int keysize, const BN_ULONG exponent) -> EVP_PKEY_ptr throw std::runtime_error(errmsg); } - auto rsa = RSA_new(); + auto* rsa = RSA_new(); if (rsa != nullptr) { if ((BN_set_word(bignum, exponent) == 0) || @@ -184,7 +163,7 @@ auto gen_rsakey(const int keysize, const BN_ULONG exponent) -> EVP_PKEY_ptr EVP_PKEY_ptr pkey{EVP_PKEY_new(), ::EVP_PKEY_free}; // EVP_PKEY_assign_RSA is a macro casting rsa to char *: - //NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) if (!EVP_PKEY_assign_RSA(pkey.get(), rsa)) { RSA_free(rsa); std::string errmsg{"gen_rsakey error: cannot assign RSA key to EVP\n"}; @@ -195,32 +174,36 @@ auto gen_rsakey(const int keysize, const BN_ULONG exponent) -> EVP_PKEY_ptr return pkey; } - -void write_key(const EVP_PKEY_ptr & pkey, - const std::string & keypath, const bool use_pem) +void write_key(const EVP_PKEY_ptr& pkey, const std::string& keypath, const bool use_pem) { - BIO * bio = nullptr; + BIO* bio = nullptr; - if (keypath.empty()) { bio = _BIO_new_fp(stdout, use_pem); } + if (keypath.empty()) { + bio = _BIO_new_fp(stdout, use_pem); + } - else { // BIO_new_file(keypath.c_str(), (use_pem ? "w" : "wb") ); + 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); // creat has no cloexec, alt. triggers cppcoreguidelines-pro-type-vararg - //NOLINTNEXTLINE(android-cloexec-creat) - auto fd = creat(keypath.c_str(), mask); // the same without va_args. + // NOLINTNEXTLINE(android-cloexec-creat) + auto fd = creat(keypath.c_str(), mask); // the same without va_args. if (fd >= 0) { - auto fp = fdopen(fd, (use_pem ? "w" : "wb") ); + auto* fp = fdopen(fd, (use_pem ? "w" : "wb")); if (fp != nullptr) { bio = _BIO_new_fp(fp, use_pem, true); - if (bio == nullptr) { fclose(fp); } // (fp owns fd) + if (bio == nullptr) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) fp owns fd: + fclose(fp); + } + } + else { + close(fd); } - else { close(fd); } } - } if (bio == nullptr) { @@ -233,30 +216,27 @@ void write_key(const EVP_PKEY_ptr & pkey, int len = 0; - auto key = pkey.get(); - switch (EVP_PKEY_base_id(key)) { // use same format as px5g: + 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)); + 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)); + 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); + 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) { + if (len == 0) { std::string errmsg{"write_key error: cannot write EVP pkey to "}; errmsg += keypath.empty() ? "stdout" : keypath; errmsg += "\n"; @@ -265,10 +245,9 @@ void write_key(const EVP_PKEY_ptr & pkey, } } - -auto subject2name(const std::string & subject) -> X509_NAME_ptr +auto subject2name(const std::string& subject) -> X509_NAME_ptr { - if (!subject.empty() && subject[0]!='/') { + if (!subject.empty() && subject[0] != '/') { throw std::runtime_error("subject2name errror: not starting with /"); } @@ -280,56 +259,63 @@ auto subject2name(const std::string & subject) -> X509_NAME_ptr throw std::runtime_error(errmsg); } - if (subject.empty()) { return name; } + if (subject.empty()) { + return name; + } - size_t prev = 1; + int prev = 1; std::string type{}; char chr = '='; - for (size_t i=0; subject[i] != 0; ) { + for (int i = 0; subject[i] != 0;) { ++i; - if (subject[i]=='\\' && subject[++i]=='\0') { + 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 (subject[i] != chr && subject[i] != '\0') { + continue; + } if (chr == '=') { - type = subject.substr(prev, i-prev); + type = subject.substr(prev, i - prev); chr = '/'; - } else { + } + else { auto nid = OBJ_txt2nid(type.c_str()); if (nid == NID_undef) { // skip unknown entries (silently?). - } else { - auto val = // X509_NAME_add_entry_by_NID wants it unsigned: - //NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - reinterpret_cast(&subject[prev]); + } + else { + const auto* val = // X509_NAME_add_entry_by_NID wants it unsigned: + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(&subject[prev]); - auto len = i - prev; + int len = i - prev; - if ( X509_NAME_add_entry_by_NID(name.get(), nid, - MBSTRING_ASC, //NOLINT(hicpp-signed-bitwise) is macro - val, len, -1, 0) - == 0 ) + if (X509_NAME_add_entry_by_NID( + name.get(), nid, + MBSTRING_ASC, // NOLINT(hicpp-signed-bitwise) is macro + val, len, -1, 0) == 0) { std::string errmsg{"subject2name error: cannot add "}; - errmsg += "/" + type +"="+ subject.substr(prev, len) +"\n"; + errmsg += "/" + type + "=" + subject.substr(prev, len) + "\n"; ERR_print_errors_cb(print_error, &errmsg); throw std::runtime_error(errmsg); } } chr = '='; } - prev = i+1; + prev = i + 1; } return name; } - -void selfsigned(const EVP_PKEY_ptr & pkey, const int days, - const std::string & subject, const std::string & crtpath, +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(); + auto* x509 = X509_new(); if (x509 == nullptr) { std::string errmsg{"selfsigned error: cannot create X509 structure\n"}; @@ -337,8 +323,7 @@ void selfsigned(const EVP_PKEY_ptr & pkey, const int days, throw std::runtime_error(errmsg); } - auto freeX509_and_throw = [&x509](const std::string & what) - { + 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"; @@ -346,19 +331,25 @@ void selfsigned(const EVP_PKEY_ptr & pkey, const int days, throw std::runtime_error(errmsg); }; - if (X509_set_version(x509, 2) == 0) { freeX509_and_throw("version"); } + 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_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)) + (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); } + try { + name = subject2name(subject); + } catch (...) { X509_free(x509); throw; @@ -372,9 +363,11 @@ void selfsigned(const EVP_PKEY_ptr & pkey, const int days, freeX509_and_throw("issuer"); } - auto bignum = BN_new(); + auto* bignum = BN_new(); - if (bignum == nullptr) { freeX509_and_throw("serial (creating big number struct)"); } + 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) { @@ -393,22 +386,19 @@ void selfsigned(const EVP_PKEY_ptr & pkey, const int days, freeX509_and_throw("signing digest"); } - BIO * bio = crtpath.empty() ? - _BIO_new_fp(stdout, use_pem) : - BIO_new_file(crtpath.c_str(), (use_pem ? "w" : "wb")); + BIO* bio = crtpath.empty() ? _BIO_new_fp(stdout, use_pem) + : 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); + len = use_pem ? PEM_write_bio_X509(bio, x509) : i2d_X509_bio(bio, x509); BIO_free_all(bio); } X509_free(x509); - if (len==0) { + if (len == 0) { std::string errmsg{"selfsigned error: cannot write certificate to "}; errmsg += crtpath.empty() ? "stdout" : crtpath; errmsg += "\n"; @@ -417,5 +407,4 @@ void selfsigned(const EVP_PKEY_ptr & pkey, const int days, } } - #endif diff --git a/net/nginx-util/src/px5g.cpp b/net/nginx-util/src/px5g.cpp index e2a073d35..4edfb3b8e 100644 --- a/net/nginx-util/src/px5g.cpp +++ b/net/nginx-util/src/px5g.cpp @@ -1,66 +1,53 @@ -#include "px5g-openssl.hpp" +#include #include #include +#include #include #include -#include - - -class argv_view { // TODO(pst): use std::span when available. - -private: - - std::basic_string_view data; - -public: - - argv_view(const argv_view &) = delete; - - - argv_view(argv_view &&) = delete; - +#include "px5g-openssl.hpp" - auto operator=(const argv_view &) -> argv_view & = delete; +class argv_view { // TODO(pst): use std::span when available. + private: + std::basic_string_view data; - auto operator=(argv_view &&) -> argv_view & = delete; + public: + argv_view(const argv_view&) = delete; + argv_view(argv_view&&) = delete; - argv_view(const char ** argv, int argc) : - data{argv, static_cast(argc)} {} + auto operator=(const argv_view&) -> argv_view& = delete; + auto operator=(argv_view &&) -> argv_view& = delete; - inline auto operator[] (size_t pos) const -> std::string_view - { return std::string_view{data[pos]}; } + argv_view(const char** argv, int argc) : data{argv, static_cast(argc)} {} + inline auto operator[](size_t pos) const -> std::string_view + { + return std::string_view{data[pos]}; + } [[nodiscard]] inline constexpr auto size() const noexcept -> size_t - { return data.size(); } - + { + return data.size(); + } ~argv_view() = default; - }; - static const auto default_validity = 30; +auto checkend(const argv_view& argv) -> int; -auto checkend(const argv_view & argv) -> int; - - -void eckey(const argv_view & argv); - - -void rsakey(const argv_view & argv); +void eckey(const argv_view& argv); +void rsakey(const argv_view& argv); -void selfsigned(const argv_view & argv); +void selfsigned(const argv_view& argv); - -inline auto parse_int(const std::string_view & arg) -> int +inline auto parse_int(const std::string_view& arg) -> int { - size_t pos; + size_t pos = 0; int ret = stoi(std::string{arg}, &pos); if (pos < arg.size()) { throw std::runtime_error("number has trailing char"); @@ -68,28 +55,35 @@ inline auto parse_int(const std::string_view & arg) -> int return ret; } - -inline auto parse_curve(const std::string_view & name) -> int +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; } + 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 +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()) { @@ -98,24 +92,26 @@ auto checkend(const argv_view & argv) -> int 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"); + 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 "< int seconds = static_cast(num); - if (num!=static_cast(seconds)) { + if (num != static_cast(seconds)) { auto errmsg = std::string{"checkend error: time too big "}; errmsg += argv[i]; throw std::runtime_error(errmsg); @@ -132,23 +128,23 @@ auto checkend(const argv_view & argv) -> int } bool valid = checkend(crtpath, seconds, use_pem); - std::cout<<"Certificate will"<<(valid ? " not " : " ")<<"expire"<= argv.size()) { @@ -156,25 +152,25 @@ void eckey(const argv_view & argv) } 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"); + if (argv[i] == keypath) { + std::cerr << "eckey warning: repeated same -out file\n"; + } + else { + throw std::runtime_error("eckey error: more than one -out file"); } } keypath = argv[i]; } - else if (argv[i][0]=='-') { - std::cerr<<"eckey warning: skipping option "<= argv.size()) { @@ -206,29 +203,31 @@ void rsakey(const argv_view & argv) } if (!keypath.empty()) { - if (argv[i]==keypath) { - std::cerr<<"rsakey warning: repeated -out file"<= argv.size()) { - throw std::runtime_error - ("selfsigned error: -newkey misses algorithm option"); + throw std::runtime_error("selfsigned error: -newkey misses algorithm option"); } static constexpr auto rsa_prefix = std::string_view{"rsa:"}; - if (argv[i]=="ec") { + if (argv[i] == "ec") { use_rsa = false; - } else if (argv[i].rfind(rsa_prefix, 0) == 0) { + } + else if (argv[i].rfind(rsa_prefix, 0) == 0) { use_rsa = true; try { keysize = parse_int(argv[i].substr(rsa_prefix.size())); - } catch (...) { + } + catch (...) { std::string errmsg{"selfsigned error: invalid keysize "}; errmsg += argv[i].substr(4); std::throw_with_nested(std::runtime_error(errmsg)); } - } else { + } + else { throw std::runtime_error("selfsigned error: invalid algorithm"); } } - else if (argv[i]=="-pkeyopt") { + else if (argv[i] == "-pkeyopt") { ++i; if (i >= argv.size()) { - throw std::runtime_error - ("selfsigned error: -pkeyopt misses value"); + throw std::runtime_error("selfsigned error: -pkeyopt misses value"); } - static constexpr auto curve_prefix = - std::string_view{"ec_paramgen_curve:"}; + 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"); @@ -312,60 +312,57 @@ void selfsigned(const argv_view & argv) curve = parse_curve(argv[i].substr(curve_prefix.size())); } - else if (argv[i]=="-keyout") { + else if (argv[i] == "-keyout") { ++i; if (i >= argv.size()) { - throw std::runtime_error - ("selfsigned error: -keyout misses path"); + 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"); + 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") { + else if (argv[i] == "-out") { ++i; if (i >= argv.size()) { - throw std::runtime_error - ("selfsigned error: -out misses filename"); + 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"); + 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") { + else if (argv[i] == "-subj") { ++i; if (i >= argv.size()) { - throw std::runtime_error - ("selfsigned error: -subj misses value"); + 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"); + if (argv[i] == subject) { + std::cerr << "selfsigned warning: repeated same -subj\n"; + } + else { + throw std::runtime_error("selfsigned error: more than one -subj value"); } } @@ -373,7 +370,7 @@ void selfsigned(const argv_view & argv) } else { - std::cerr<<"selfsigned warning: skipping option "< int +auto main(int argc, const char** argv) -> int { auto args = argv_view{argv, argc}; auto cmds = std::array{ std::array{"checkend", - " [-der] [-in certificate_path] [seconds_remaining]" - }, - std::array{"eckey", - " [-der] [-out key_path] [curve_name]" - }, - std::array{"rsakey", - " [-der] [-out key_path] [-3] [key_size]" - }, - std::array{"selfsigned", + " [-der] [-in certificate_path] [seconds_remaining]"}, + std::array{"eckey", " [-der] [-out key_path] [curve_name]"}, + std::array{"rsakey", " [-der] [-out key_path] [-3] [key_size]"}, + std::array{ + "selfsigned", " [-der] [-keyout key_path] [-out certificate_path]" " [-newkey ec|rsa:key_size] [-pkeyopt ec_paramgen_curve:name]" - " [-days validity] [-subj /C=.../ST=.../L=.../O=.../CN=.../... ]" - }, + " [-days validity] [-subj /C=.../ST=.../L=.../O=.../CN=.../... ]"}, }; try { - if (argc < 2) { throw std::runtime_error("error: no argument"); } + if (argc < 2) { + throw std::runtime_error("error: no argument"); + } - if (args[1]==cmds[0][0]) {return checkend(args);} + if (args[1] == cmds[0][0]) { + return checkend(args); + } - if (args[1]==cmds[1][0]) { eckey(args); } + if (args[1] == cmds[1][0]) { + eckey(args); + } - else if (args[1]==cmds[2][0]) { rsakey(args); } + else if (args[1] == cmds[2][0]) { + rsakey(args); + } - else if (args[1]==cmds[3][0]) { selfsigned(args); } + else if (args[1] == cmds[3][0]) { + selfsigned(args); + } - else { throw std::runtime_error("error: argument not recognized"); } + 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"; - } + catch (const std::exception& e) { + auto usage = std::accumulate( + cmds.begin(), cmds.end(), std::string{"usage: \n"}, + [=](const auto& use, const auto& cmd) { + return use + std::string{4, ' '} + *argv + " " + cmd[0] + cmd[1] + "\n"; + }); - std::cerr< void - { - std::cerr< 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); @@ -443,7 +450,7 @@ auto main(int argc, const char ** argv) -> int } catch (...) { - std::cerr<<*argv<<" unknown error."< #include +#include #include #include #include - namespace rgx { /* partially implement the std::regex interface using PCRE for performance * (=> pass "match" as non-const reference) */ - 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 - - +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: - + 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) + {} -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; } - + [[nodiscard]] auto virtual code() const -> regex_constants::error_type; }; - +[[nodiscard]] auto regex_error::code() const -> regex_constants::error_type +{ + return errcode; +} class regex { - -private: - + private: int errcode = 0; - const char * errptr = nullptr; + const char* errptr = nullptr; int erroffset = 0; - pcre * const re = nullptr; + pcre* const re = nullptr; - static const std::array errcode_pcre2regex; + static const std::array errcode_pcre2regex; static const auto BASE = 10; - -public: - + public: inline regex() = default; + inline regex(const regex&) = delete; - inline regex(const regex &) = delete; - - - inline regex(regex &&) = default; - - - inline auto operator=(const regex &) -> regex & = delete; + inline regex(regex&&) = default; + inline auto operator=(const regex&) -> regex& = delete; - inline auto operator=(regex &&) -> regex & = delete; + inline auto operator=(regex &&) -> regex& = delete; + explicit regex(const std::string& str) : regex(str.c_str()) {} - explicit regex(const std::string & str) - : re{ pcre_compile2(str.c_str(), 0, &errcode, &errptr, &erroffset,nullptr) } + explicit regex(const char* const str) + : re{pcre_compile2(str, 0, &errcode, &errptr, &erroffset, nullptr)} { - if (re==nullptr) { + if (re == nullptr) { std::string what = std::string("regex error: ") + errptr + '\n'; - what += " '" + str + "'\n"; + what += " '" + std::string{str} + "'\n"; what += " " + std::string(erroffset, ' ') + '^'; throw regex_error(errcode_pcre2regex.at(errcode), what.c_str()); } } + ~regex() + { + if (re != nullptr) { + pcre_free(re); + } + } - ~regex() { if (re != nullptr) { pcre_free(re); } } - - - inline auto operator()() const -> const pcre * { return 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, //NOLINT(google-runtime-references) - const regex & rgx); // match std::regex interface. - - -private: + smatch& match, // NOLINT(google-runtime-references) + const regex& rgx); // match std::regex interface. + private: std::string::const_iterator begin; std::string::const_iterator end; - std::vector vec{}; + std::vector vec{}; int n = 0; - -public: - - [[nodiscard]] inline auto position(int i=0) const { - return (i<0 || i>=n) ? std::string::npos : vec[2*i]; + 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]] 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]; + [[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 format(const std::string & fmt) const; - - - [[nodiscard]] auto size() const -> int { return n; } - - - [[nodiscard]] inline auto empty() const { return n<0; } - + [[nodiscard]] auto size() const -> int + { + return n; + } - [[nodiscard]] inline auto ready() const { return !vec.empty(); } + [[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); -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, //NOLINT(google-runtime-references) - const regex & rgx); // match std::regex interface. +auto regex_replace(const std::string& subj, const regex& rgx, const std::string& insert); +inline auto regex_search(const std::string& subj, + smatch& match, // NOLINT(google-runtime-references) + const regex& rgx); // match std::regex interface. auto regex_search(std::string::const_iterator begin, std::string::const_iterator end, - smatch & match, //NOLINT(google-runtime-references) - const regex & rgx); // match std::regex interface. - - + smatch& match, // NOLINT(google-runtime-references) + const regex& rgx); // match std::regex interface. // ------------------------- implementation: ---------------------------------- - -inline auto regex_search(const std::string & subj, const regex & rgx) +inline auto regex_search(const std::string& subj, const regex& rgx) { - if (rgx()==nullptr) { + 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; + int n = + pcre_exec(rgx(), nullptr, subj.c_str(), static_cast(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) + smatch& match, + const regex& rgx) { - if (rgx()==nullptr) { + 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); + sz = 3 * (sz + 1); match.vec.reserve(sz); - const char * subj = &*begin; - size_t len = &*end - subj; + const char* subj = &*begin; + int len = static_cast(&*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; } + 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) +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 { +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); + size_t pos = 0; + while ((pos = fmt.find('$', index)) != std::string::npos) { + ret.append(fmt, index, pos - index); index = pos + 1; char chr = fmt[index++]; - switch(chr) { - - case '&': // match + switch (chr) { + case '&': // match ret += str(0); break; - case '`': // prefix - ret.append(begin, begin+vec[0]); + case '`': // prefix + ret.append(begin, begin + vec[0]); break; - case '\'': // suffix - ret.append(begin+vec[1], end); + case '\'': // suffix + ret.append(begin + vec[1], end); break; default: - if (isdigit(chr) != 0) { // one or two digits => submatch: + if (isdigit(chr) != 0) { // one or two digits => submatch: int num = chr - '0'; chr = fmt[index]; - if (isdigit(chr) != 0) { // second digit: + if (isdigit(chr) != 0) { // second digit: ++index; static const auto base = 10; - num = num*base + chr - '0'; + num = num * base + chr - '0'; } ret += str(num); break; - } //else: + } // else: ret += '$'; [[fallthrough]]; - case '$': // escaped + case '$': // escaped ret += chr; } } @@ -300,20 +281,16 @@ auto smatch::format(const std::string & fmt) const { return ret; } - -auto regex_replace(const std::string & subj, - const regex & rgx, - const std::string & insert) +auto regex_replace(const std::string& subj, const regex& rgx, const std::string& insert) { - if (rgx()==nullptr) { + 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); + for (smatch match; regex_search(pos, subj.end(), match, rgx); pos += match.position(0) + match.length(0)) { ret.append(pos, pos + match.position(0)); @@ -324,11 +301,8 @@ auto regex_replace(const std::string & subj, return ret; } - - // ------------ There is only the translation table below : ------------------- - const std::array regex::errcode_pcre2regex = { // 0 no error regex_constants::error_type::_enum_error_last, @@ -444,7 +418,8 @@ const std::array regex::errcode_pcre2regex = { 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 + // 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, @@ -501,11 +476,8 @@ const std::array regex::errcode_pcre2regex = { // 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 + regex_constants::error_stack}; +} // namespace rgx #endif diff --git a/net/nginx-util/src/test-nginx-util-root.sh b/net/nginx-util/src/test-nginx-util-root.sh index 0ce8c9428..eb8ca56f1 100644 --- a/net/nginx-util/src/test-nginx-util-root.sh +++ b/net/nginx-util/src/test-nginx-util-root.sh @@ -4,6 +4,57 @@ PRINT_PASSED=2 NGINX_UTIL="/usr/bin/nginx-util" +ORIG=".original-test-nginx-util-root" + +mkdir -p /tmp/.uci/ + +uci commit nginx || { printf "Error invoking: uci commit\n Exit."; exit 2; } + + +pst_exit() { + printf "\nExit: Recovering original settings ... " + + uci revert nginx + + cd "/etc/config/" && rm "nginx" && mv "nginx.${ORIG}" "nginx" || + printf "\n%s: not moved %s to %s\n" "/etc/config/" "nginx${ORIG}" "nginx" + + cd "/etc/crontabs/" && rm "root" && mv "root${ORIG}" "root" || + printf "\n%s: not moved %s to %s\n" "/etc/crontabs/" "root${ORIG}" "root" + + cd "$(dirname "${CONF_DIR}")" && rm -r "${CONF_DIR}" && + mv "$(basename "${CONF_DIR}")${ORIG}" "$(basename "${CONF_DIR}")" || + printf "\n%s: not moved %s to %s\n" "$(dirname "${CONF_DIR}")" \ + "$(basename "${CONF_DIR}")${ORIG}" "$(basename "${CONF_DIR}")" + + printf "done.\n" + + exit "$1" +} + + +mkdir -p "/etc/config/" && touch "/etc/config/nginx" + +cd "/etc/config/" && [ ! -e "nginx${ORIG}" ] && cp "nginx" "nginx.${ORIG}" || { + printf "\n%s: not copied %s to %s\n" "/etc/config/" "nginx" "nginx${ORIG}" + pst_exit 3 +} + +uci set nginx.global.uci_enable=1 + + +mkdir -p "/etc/crontabs/" && touch "/etc/crontabs/root" + +cd "/etc/crontabs/" && [ ! -e "root${ORIG}" ] && mv "root" "root${ORIG}" || { + printf "\n%s: not moved %s to %s\n" "/etc/crontabs/" "root${ORIG}" "root" + pst_exit 4 +} + +touch "/etc/crontabs/root" + + +# ---------------------------------------------------------------------------- + __esc_newlines() { echo "${1}" | sed -E 's/$/\\n/' | tr -d '\n' | sed -E 's/\\n$/\n/' } @@ -33,6 +84,32 @@ _echo_sed() { echo "" | sed -E "c${1}" } + +fileauto="# This file is re-created when Nginx starts." + +setpoint_init_lan() { + echo "${fileauto}" + + sed -n -E '/^\s*#UCI_HTTP_CONFIG\s*$/q;p' "${UCI_CONF}.template" + + local rhs="\t}\n\n\tserver { #see uci show 'nginx.\1'" + uci -n export nginx \ + | sed -E -e "s/'//g" \ + -e '/^\s*package\s+nginx\s*$/d' \ + -e '/^\s*config\s+main\s/d' \ + -e "s/^\s*config\s+server\s+(.*)$/$rhs/g" \ + -e 's/^\s*list\s/\t\t/g' \ + -e 's/^\s*option\s/\t\t/g' \ + -e 's/^\s*uci_listen_locally\s+/\t\tlisten 127.0.0.1:/g' \ + -e '/^\s*uci_/d' \ + -e '/^$/d' -e "s/[^'\n]$/&;/g" \ + | sed "1,2d" + printf "\t}\n\n" + + sed -E '1,/^\s*#UCI_HTTP_CONFIG\s*$/ d' "${UCI_CONF}.template" +} + + setpoint_add_ssl() { local indent="\n$1" local name="$2" @@ -40,13 +117,13 @@ setpoint_add_ssl() { [ "${name}" = "${LAN_NAME}" ] && default=".default" local prefix="${CONF_DIR}${name}" - local CONF="$(grep -vE "$(_regex "${NGX_INCLUDE}" \ - "${LAN_LISTEN}${default}")" "${prefix}.sans" 2>/dev/null)" local ADDS="" - echo "${CONF}" \ - | grep -qE "$(_regex "${NGX_INCLUDE}" "${LAN_SSL_LISTEN}${default}")" \ - || ADDS="${ADDS}${indent}$(_sed_rhs "${NGX_INCLUDE}" \ - "${LAN_SSL_LISTEN}${default}")" + local CONF + CONF="$(sed -E \ + -e "s/$(_regex "${NGX_INCLUDE}" "${LAN_LISTEN}${default}")/$1$(\ + _sed_rhs "${NGX_INCLUDE}" "${LAN_SSL_LISTEN}${default}")/g" \ + -e "s/^(\s*listen\s+)([^:]*:|\[[^]]*\]:)?80(\s|$|;)/\1\2443 ssl\3/g" \ + "${prefix}.sans" 2>/dev/null)" echo "${CONF}" | grep -qE "$(_regex "${NGX_SSL_CRT}" "${prefix}")" \ || ADDS="${ADDS}${indent}$(_sed_rhs "${NGX_SSL_CRT}" "${prefix}")" echo "${CONF}" | grep -qE "$(_regex "${NGX_SSL_KEY}" "${prefix}")" \ @@ -82,6 +159,18 @@ test_setpoint() { } +test_existence() { + if [ "$2" -eq "0" ] + then + [ ! -f "$1" ] && echo "$1 missing!" && + [ "${PRINT_PASSED}" -gt 1 ] && pst_exit 1 + else + [ -f "$1" ] && echo "$1 existing!" && + [ "${PRINT_PASSED}" -gt 1 ] && pst_exit 1 + fi +} + + test() { eval "$1 2>/dev/null >/dev/null" if [ "$?" -eq "$2" ] @@ -90,14 +179,19 @@ test() { && printf "%-72s%-1s\n" "$1" "2>/dev/null >/dev/null (-> $2?) passed." else printf "%-72s%-1s\n" "$1" "2>/dev/null >/dev/null (-> $2?) failed!!!" - [ "${PRINT_PASSED}" -gt 1 ] && exit 1 + [ "${PRINT_PASSED}" -gt 0 ] && printf "\n### Snip:\n" && eval "$1" + [ "${PRINT_PASSED}" -gt 0 ] && printf "### Snap.\n" + [ "${PRINT_PASSED}" -gt 1 ] && pst_exit 1 fi } + [ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting %s get_env ...\n" "${NGINX_UTIL}" + eval $("${NGINX_UTIL}" get_env) +test '[ -n "${UCI_CONF}" ]' 0 test '[ -n "${NGINX_CONF}" ]' 0 test '[ -n "${CONF_DIR}" ]' 0 test '[ -n "${LAN_NAME}" ]' 0 @@ -106,13 +200,26 @@ test '[ -n "${LAN_SSL_LISTEN}" ]' 0 test '[ -n "${SSL_SESSION_CACHE_ARG}" ]' 0 test '[ -n "${SSL_SESSION_TIMEOUT_ARG}" ]' 0 test '[ -n "${ADD_SSL_FCT}" ]' 0 +test '[ -n "${MANAGE_SSL}" ]' 0 + +mkdir -p "$(dirname "${LAN_LISTEN}")" + +mkdir -p "${CONF_DIR}" + +cd "$(dirname "${CONF_DIR}")" && [ ! -e "$(basename "${CONF_DIR}")${ORIG}" ] && +mv "$(basename "${CONF_DIR}")" "$(basename "${CONF_DIR}")${ORIG}" || +{ + printf "\n%s: not moved %s to %s\n" "$(dirname "${CONF_DIR}")" \ + "$(basename "${CONF_DIR}")" "$(basename "${CONF_DIR}")${ORIG}" + pst_exit 3 +} [ "$PRINT_PASSED" -gt 0 ] && printf "\nPrepare files in %s ...\n" "${CONF_DIR}" mkdir -p "${CONF_DIR}" -cd "${CONF_DIR}" || exit 2 +cd "${CONF_DIR}" || pst_exit 2 NGX_INCLUDE="include '\$';" NGX_SERVER_NAME="server_name * '\$' *;" @@ -140,6 +247,24 @@ server { EOF CONFS="${CONFS} minimal:0" +cat > listens.sans < normal.sans < acme.sans < more_server.sans < more_names.sans <"/etc/crontabs/root" +test '"${NGINX_UTIL}" add_ssl _lan' 0 +echo "postfix" >>"/etc/crontabs/root" +test_setpoint "/etc/crontabs/root" "prefix +3 3 12 12 * ${NGINX_UTIL} 'check_ssl' +postfix" + +test '"${NGINX_UTIL}" del_ssl _lan' 0 +test_setpoint "/etc/crontabs/root" "prefix +3 3 12 12 * ${NGINX_UTIL} 'check_ssl' +postfix" + +test '"${NGINX_UTIL}" check_ssl' 0 +test_setpoint "/etc/crontabs/root" "prefix +postfix" + +test '"${NGINX_UTIL}" add_ssl _lan' 0 +test_setpoint "/etc/crontabs/root" "prefix +postfix +3 3 12 12 * ${NGINX_UTIL} 'check_ssl'" + +rm -f "/etc/crontabs/root" + + +[ "$PRINT_PASSED" -gt 0 ] && printf '\n\t-"-\t(legacy) ... \n' + +echo -n "prefix" >"/etc/crontabs/root" +cp "minimal.sans" "minimal.conf" + +test '"${NGINX_UTIL}" add_ssl minimal' 0 +echo "postfix" >>"/etc/crontabs/root" +test_setpoint "/etc/crontabs/root" "prefix +3 3 12 12 * ${NGINX_UTIL} 'add_ssl' 'minimal' +postfix" + +test '"${NGINX_UTIL}" del_ssl minimal' 0 +test_setpoint "/etc/crontabs/root" "prefix +postfix" + +rm -f "/etc/crontabs/root" + + + [ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting %s init_lan ...\n" "${NGINX_UTIL}" -mkdir -p "$(dirname "${LAN_LISTEN}")" -cp "${LAN_NAME}.sans" "${LAN_NAME}.conf" +rm -f "${LAN_NAME}.conf" "_redirect2ssl.conf" "${UCI_ADDED}.conf" +rm -f "$(readlink "${UCI_CONF}")" test '"${NGINX_UTIL}" init_lan' 0 +test_setpoint "${UCI_CONF}" "$(setpoint_init_lan)" +test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'" -[ "$PRINT_PASSED" -gt 0 ] && printf "\nSetup files in %s ...\n" "${CONF_DIR}" +[ "$PRINT_PASSED" -gt 0 ] && printf '\n\t-"-\twith temporary UCI config ... \n' -for conf in ${CONFS} -do test 'setpoint_add_ssl " " '"${conf%:*}" "${conf#*:}" -done +UCI_ADDED="$(uci add nginx server)" && +uci set nginx.@server[-1].server_name='temp' && +uci add_list nginx.@server[-1].listen='81 default_server' && +uci add_list nginx.@server[-1].listen='80' && +echo "UCI: nginx.${UCI_ADDED} added." + +rm -f "${LAN_NAME}.conf" "_redirect2ssl.conf" "${UCI_ADDED}.conf" +rm -f "$(readlink "${UCI_CONF}")" + +test '"${NGINX_UTIL}" init_lan' 0 +test_setpoint "${UCI_CONF}" "$(setpoint_init_lan)" +test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'" + + +[ "$PRINT_PASSED" -gt 0 ] && printf '\n\t-"-\t(legacy) ... \n' + +cp "${LAN_NAME}.sans" "${LAN_NAME}.conf" +touch "_redirect2ssl.conf" "${UCI_ADDED}.conf" +rm -f "$(readlink "${UCI_CONF}")" +test '"${NGINX_UTIL}" init_lan' 0 + +skipped() { + printf "\t# skipped UCI server 'nginx.%s'" "$1" + printf " as it could conflict with: %s%s.conf\n\n" "${CONF_DIR}" "$1" +} +rhs="$(skipped "$LAN_NAME" && skipped _redirect2ssl && skipped "${UCI_ADDED}")" +sed -E -e "s/^\t#UCI_HTTP_CONFIG$/$(__esc_sed_rhs "$rhs")\n/" \ + -e 's/\\n/\n/g' -e "1i${fileauto}" "${UCI_CONF}.template" >"uci.setpoint" + +test_setpoint "${UCI_CONF}" "$(cat "uci.setpoint")" +test_setpoint "/etc/crontabs/root" "" -test 'setpoint_add_ssl "\t" tab' 0 # fixes wrong indentation. [ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting %s add_ssl ...\n" "${NGINX_UTIL}" -cp different_name.sans different_name.with test '[ "${ADD_SSL_FCT}" = "add_ssl" ] ' 0 +rm -f "${LAN_NAME}.conf" "_redirect2ssl.conf" "${UCI_ADDED}.conf" +rm -f "$(readlink "${UCI_CONF}")" +test 'uci set nginx._lan.uci_manage_ssl="self-signed"' 0 +"${NGINX_UTIL}" del_ssl "${LAN_NAME}" 2>/dev/null +test_setpoint "/etc/crontabs/root" "" +test_existence "${LAN_NAME}.crt" 1 +test_existence "${LAN_NAME}.key" 1 +test '"${NGINX_UTIL}" add_ssl '"${UCI_ADDED}"' acme \ + '"${CONF_DIR}${UCI_ADDED}.crt"' '"${CONF_DIR}${UCI_ADDED}.key"' ' 0 +test_setpoint "/etc/crontabs/root" "" +test_existence "${UCI_ADDED}.crt" 1 +test_existence "${UCI_ADDED}.key" 1 +test '"${NGINX_UTIL}" add_ssl '"${LAN_NAME}" 0 +test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'" +test_existence "${LAN_NAME}.crt" 0 +test_existence "${LAN_NAME}.key" 0 +test '"${NGINX_UTIL}" add_ssl '"${LAN_NAME}" 0 +test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'" +test '"${NGINX_UTIL}" add_ssl inexistent' 1 +test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'" +test '"${NGINX_UTIL}" init_lan' 0 +test_setpoint "${UCI_CONF}" "$(setpoint_init_lan)" +test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'check_ssl'" +test_existence "${UCI_ADDED}.crt" 1 +test_existence "${UCI_ADDED}.key" 1 +test_existence "${LAN_NAME}.crt" 0 +test_existence "${LAN_NAME}.key" 0 + + +[ "$PRINT_PASSED" -gt 0 ] && printf '\n\t-"-\t(legacy) ... \n' + +cp different_name.sans different_name.with + +cp "/etc/crontabs/root" "cron.setpoint" for conf in ${CONFS}; do name="${conf%:*}" + [ "${name}" = "acme" ] && continue + [ "${name}" = "different_name" ] || + echo "3 3 12 12 * ${NGINX_UTIL} 'add_ssl' '${name}'" >>"cron.setpoint" cp "${name}.sans" "${name}.conf" test '"${NGINX_UTIL}" add_ssl '"${name}" "${conf#*:}" test_setpoint "${name}.conf" "$(cat "${name}.with")" + test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")" + [ "${name}" = "different_name" ] || test_existence "${name}.crt" 0 + [ "${name}" = "different_name" ] || test_existence "${name}.key" 0 done +cp acme.sans acme.conf +test '"${NGINX_UTIL}" add_ssl acme acme /path/to/crt /path/to/key' 0 +test_setpoint "acme.conf" "$(cat "acme.with")" +test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")" +test_existence "acme.crt" 1 +test_existence "acme.key" 1 + + + [ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting %s del_ssl ...\n" "${NGINX_UTIL}" -sed -i "/server {/a\\ include '${LAN_LISTEN}';" minimal.sans + +sed -E -e 's/443 ssl/80/' -e '/[^2]ssl/d' "/etc/config/nginx" >"config.setpoint" + +cp "/etc/crontabs/root" "cron.setpoint" +rm -f "${LAN_NAME}.conf" "_redirect2ssl.conf" "${UCI_ADDED}.conf" +test '"${NGINX_UTIL}" del_ssl '"${LAN_NAME}" 0 +test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")" +test_existence "${LAN_NAME}.crt" 1 +test_existence "${LAN_NAME}.key" 1 +test '"${NGINX_UTIL}" del_ssl '"${LAN_NAME}" 1 +test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")" + +rm -f "$(readlink "${UCI_CONF}")" +sed -E "/$(__esc_regex "'check_ssl'")/d" "/etc/crontabs/root" >"cron.setpoint" +test '"${NGINX_UTIL}" init_lan' 0 +test_setpoint "${UCI_CONF}" "$(setpoint_init_lan)" +test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")" + +touch "${UCI_ADDED}.crt" "${UCI_ADDED}.key" +test '"${NGINX_UTIL}" del_ssl "'${UCI_ADDED}'" acme' 0 +test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")" +test_existence "${UCI_ADDED}.crt" 0 +test_existence "${UCI_ADDED}.key" 0 + +test '"${NGINX_UTIL}" del_ssl inexistent' 1 +test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")" + +test_setpoint "/etc/config/nginx" "$(cat "config.setpoint")" +test '"${NGINX_UTIL}" add_ssl "'${UCI_ADDED}'" acme \ + '"${CONF_DIR}${UCI_ADDED}.crt"' '"${CONF_DIR}${UCI_ADDED}.key"' ' 0 +test '"${NGINX_UTIL}" add_ssl "'$(uci get "nginx.${UCI_ADDED}.server_name")'"' 0 +test '"${NGINX_UTIL}" del_ssl "'$(uci get "nginx.${UCI_ADDED}.server_name")'"' 0 +rm -f "$(readlink "${UCI_CONF}")" +sed -E "/$(__esc_regex "'check_ssl'")/d" "/etc/crontabs/root" >"cron.setpoint" +test '"${NGINX_UTIL}" init_lan' 0 +test_setpoint "${UCI_CONF}" "$(setpoint_init_lan)" +test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")" +test_existence "${UCI_ADDED}.crt" 1 +test_existence "${UCI_ADDED}.key" 1 + + +[ "$PRINT_PASSED" -gt 0 ] && printf '\n\t-"-\t(legacy) ... \n' for conf in ${CONFS}; do name="${conf%:*}" + [ "${name}" = "acme" ] && continue + sed -E "/$(__esc_regex "'${name}'")/d" "/etc/crontabs/root" >"cron.setpoint" + touch "${name}.crt" "${name}.key" cp "${name}.with" "${name}.conf" test '"${NGINX_UTIL}" del_ssl '"${name}" "${conf#*:}" test_setpoint "${name}.conf" "$(cat "${name}.sans")" + test_setpoint "/etc/crontabs/root" "$(cat "cron.setpoint")" + [ "${name}" = "different_name" ] && rm "${name}.crt" "${name}.key" + test_existence "${name}.crt" 1 + test_existence "${name}.key" 1 done +test_setpoint "/etc/crontabs/root" "" + +test '"${NGINX_UTIL}" del_ssl acme acme' 0 +test_existence "acme.crt" 1 +test_existence "acme.key" 1 + +cp acme.with acme.conf +touch acme.crt acme.key +echo "3 3 12 12 * ${NGINX_UTIL} 'add_ssl' 'acme'" >>"/etc/crontabs/root" +test '"${NGINX_UTIL}" del_ssl acme acme' 0 +test_setpoint "acme.conf" "$(cat "acme.sans")" +test_setpoint "/etc/crontabs/root" "3 3 12 12 * ${NGINX_UTIL} 'add_ssl' 'acme'" +test_existence "acme.crt" 0 +test_existence "acme.key" 0 +"${NGINX_UTIL}" del_ssl acme 2>/dev/null +test_setpoint "/etc/crontabs/root" "" +test_existence "acme.crt" 1 +test_existence "acme.key" 1 + + +[ "$PRINT_PASSED" -gt 0 ] && printf "\nTesting without UCI ... \n" + +rm -f "$(readlink "${UCI_CONF}")" + +test 'uci set nginx.global.uci_enable=0' 0 + +test '"${NGINX_UTIL}" init_lan' 0 + +test '[ -e "$(readlink '"${UCI_CONF}"')" ]' 1 + +cp "${LAN_NAME}.sans" "${LAN_NAME}.conf" +test '"${NGINX_UTIL}" add_ssl '"${LAN_NAME}" 0 +test '"${NGINX_UTIL}" add_ssl '"${LAN_NAME}" 0 +test '"${NGINX_UTIL}" del_ssl '"${LAN_NAME}" 0 +test '"${NGINX_UTIL}" del_ssl '"${LAN_NAME}" 0 + +test 'rm "${LAN_NAME}.conf"' 0 +test '"${NGINX_UTIL}" add_ssl '"${LAN_NAME}" 1 +test '"${NGINX_UTIL}" del_ssl '"${LAN_NAME}" 1 + + + +pst_exit 0 diff --git a/net/nginx-util/src/test-nginx-util.sh b/net/nginx-util/src/test-nginx-util.sh index 787906350..6f8d07259 100755 --- a/net/nginx-util/src/test-nginx-util.sh +++ b/net/nginx-util/src/test-nginx-util.sh @@ -14,8 +14,17 @@ TMPROOT="$(mktemp -d "/tmp/test-nginx-util-XXXXXX")" ln -s /bin "${TMPROOT}/bin" -mkdir -p "${TMPROOT}/usr/bin/" +mkdir -p "${TMPROOT}/etc/crontabs/" + +mkdir -p "${TMPROOT}/etc/config/" +cp "./config-nginx-ssl" "${TMPROOT}/etc/config/nginx" +mkdir -p "${TMPROOT}/etc/nginx/" +cp "./uci.conf.template" "${TMPROOT}/etc/nginx/uci.conf.template" +ln -s "${TMPROOT}/var/lib/nginx/uci.conf" "${TMPROOT}/etc/nginx/uci.conf" + +mkdir -p "${TMPROOT}/usr/bin/" +cp "/usr/local/bin/uci" "${TMPROOT}/usr/bin/" cp "./test-nginx-util-root.sh" "${TMPROOT}/usr/bin/" diff --git a/net/nginx-util/src/ubus-cxx.cpp b/net/nginx-util/src/ubus-cxx.cpp index 7f65813ca..5fa29c7d1 100644 --- a/net/nginx-util/src/ubus-cxx.cpp +++ b/net/nginx-util/src/ubus-cxx.cpp @@ -2,130 +2,118 @@ #include "ubus-cxx.hpp" - inline void example_for_checking_if_there_is_a_key() { if (ubus::call("service", "list").filter("cron")) { - std::cout<<"Cron is active (with or without instances) "<(t); - std::cout<<"["<(t); + std::cout << "[" << blobmsg_get_string(x) << "] "; } - for (auto t : lan_status.filter("ipv4-address", "").filter("address")) { - //NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - auto x = const_cast(t); - std::cout<(t); + std::cout << blobmsg_get_string(x) << " "; } - std::cout< int - { return blobmsg_add_string(buf, "config", "nginx"); }; - for (auto t : ubus::call("uci", "get", set_arg).filter("values")) { - //NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - auto x = const_cast(t); - std::cout< int { return blobmsg_add_string(buf, "config", "nginx"); }; + for (const auto* t : ubus::call("uci", "get", set_arg).filter("values")) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + auto* x = const_cast(t); + std::cout << blobmsg_get_string(x) << "\n"; } } - inline void example_for_exploring() { ubus::strings keys{"ipv4-address", "", ""}; - for (auto t : ubus::call("network.interface.lan", "status").filter(keys)) { - //NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - auto x = const_cast(t); - std::cout<(t); + 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< void - { +{ // 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< void { + std::cout << std::endl; bool first = true; - for (; it!=end; ++it) { - //NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) - auto attr = const_cast(*it); - if (first) { first = false; } - else { std::cout<<",\n"; } - std::cout<(*it); + if (first) { + first = false; + } + else { + std::cout << ",\n"; + } + std::cout << std::string(depth, '\t'); std::string name = blobmsg_name(attr); - if (!name.empty()) { std::cout<<"\""< int { - +auto main() -> int +{ try { example_for_checking_if_there_is_a_key(); @@ -140,9 +128,13 @@ auto main() -> int { return 0; } - catch (const std::exception & e) { std::cerr< #include +#include #include #include #include @@ -13,7 +13,6 @@ #include #endif - namespace ubus { static constexpr int call_timeout = 500; @@ -22,12 +21,13 @@ using msg_ptr = std::shared_ptr; using strings = std::vector; +inline auto concat(strings dest) +{ + return dest; +} -inline auto concat(strings dest) { return dest; } - - -template -inline auto concat(strings dest, strings src, Strings ...more) +template +inline auto concat(strings dest, strings src, Strings... more) { dest.reserve(dest.size() + src.size()); dest.insert(std::end(dest), std::make_move_iterator(std::begin(src)), @@ -35,182 +35,162 @@ inline auto concat(strings dest, strings src, Strings ...more) return concat(std::move(dest), std::move(more)...); } - -template -inline auto concat(strings dest, S src, Strings ...more) +template +inline auto concat(strings dest, S src, Strings... more) { dest.emplace_back(std::move(src)); return concat(std::move(dest), std::move(more)...); } - - class iterator { - -private: - - const strings & keys; + private: + const strings& keys; const size_t n = 0; size_t i = 0; - const blob_attr * pos = nullptr; + const blob_attr* pos = nullptr; std::unique_ptr cur{}; - iterator * parent = nullptr; + iterator* parent = nullptr; size_t rem = 0; - [[nodiscard]] inline auto matches() const -> bool { - return (keys[i].empty() || blobmsg_name(cur->pos)==keys[i]); + 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} + explicit iterator(iterator* par) + : keys{par->keys}, n{par->n}, pos{par->pos}, cur{this}, parent{par} { - if (pos!=nullptr) { + if (pos != nullptr) { rem = blobmsg_data_len(pos); - pos = static_cast(blobmsg_data(pos)); + pos = static_cast(blobmsg_data(pos)); } } - -public: - - explicit iterator(const blob_attr * msg, const strings & filter={""}) - : keys{filter}, n{keys.size()-1}, pos{msg}, cur{this} + public: + explicit iterator(const blob_attr* msg, const strings& key_filter = {""}) + : keys{key_filter}, n{keys.size() - 1}, pos{msg}, cur{this} { - if (pos!=nullptr) { + if (pos != nullptr) { rem = blobmsg_data_len(pos); - pos = static_cast(blobmsg_data(pos)); + pos = static_cast(blobmsg_data(pos)); - if (rem==0) { pos = nullptr; } - else if (i!=n || !matches()) { ++*this; } + if (rem == 0) { + pos = nullptr; + } + else if (i != n || !matches()) { + ++*this; + } } } + inline iterator(iterator&&) noexcept = default; - inline iterator(iterator &&) noexcept = default; - - - inline iterator(const iterator &) = delete; - + inline iterator(const iterator&) = delete; - inline auto operator=(const iterator &) -> iterator & = delete; + inline auto operator=(const iterator&) -> iterator& = delete; + inline auto operator=(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); } - + inline auto operator*() + { + return cur->pos; + } - auto operator++() -> iterator &; + inline auto operator!=(const iterator& rhs) + { + return (cur->rem != rhs.cur->rem || cur->pos != rhs.cur->pos); + } + auto operator++() -> iterator&; inline ~iterator() - { if (cur.get()==this) { static_cast(cur.release()); } } - + { + if (cur.get() == this) { + static_cast(cur.release()); + } + } }; - - class message { - -private: - - const msg_ptr msg{}; // initialized by callback. + private: + const msg_ptr msg{}; // initialized by callback. const strings keys{}; + public: + inline explicit message(msg_ptr message_ptr, strings key_filter = {""}) + : msg{std::move(message_ptr)}, keys{std::move(key_filter)} + {} -public: - - inline explicit message(msg_ptr message_ptr, strings filter={""}) - : msg{std::move(message_ptr)}, keys{std::move(filter)} {} - + inline message(message&&) = default; - inline message(message &&) = default; + inline message(const message&) = delete; + inline auto operator=(message &&) -> message& = delete; - inline message(const message &) = delete; - - - inline auto operator=(message &&) -> message & = delete; - - - inline auto operator=(const message &) -> message & = delete; - + inline auto operator=(const message&) -> message& = delete; [[nodiscard]] inline auto begin() const -> iterator - { return iterator{msg.get(), keys}; } - + { + return iterator{msg.get(), keys}; + } [[nodiscard]] inline auto end() const -> iterator - { return iterator{nullptr, keys}; } - - - inline explicit operator bool() const { return begin()!=end(); } + { + return iterator{nullptr, keys}; + } + inline explicit operator bool() const + { + return begin() != end(); + } - template - auto filter(Strings ...filter) + template + auto filter(Strings... key_filter) { strings both{}; - if (keys.size()!=1 || !keys[0].empty()) { both = keys; } - both = concat(std::move(both), std::move(filter)...); + if (keys.size() != 1 || !keys[0].empty()) { + both = keys; + } + both = concat(std::move(both), std::move(key_filter)...); return std::move(message{msg, std::move(both)}); } - inline ~message() = default; - }; - - class lock_shared_resources { - -private: - + private: static std::mutex inuse; + public: + inline lock_shared_resources() + { + inuse.lock(); + } -public: - - - inline lock_shared_resources() { inuse.lock(); } - - - inline lock_shared_resources(lock_shared_resources &&) noexcept = default; - - - inline lock_shared_resources(const lock_shared_resources &) = delete; - - - inline auto operator=(const lock_shared_resources &) -> auto & = delete; + inline lock_shared_resources(lock_shared_resources&&) noexcept = default; + inline lock_shared_resources(const lock_shared_resources&) = delete; - inline auto operator=(lock_shared_resources &&) -> auto && = delete; + inline auto operator=(const lock_shared_resources&) -> auto& = delete; + inline auto operator=(lock_shared_resources &&) -> auto&& = delete; - //NOLINTNEXTLINE(readability-convert-member-functions-to-static) - inline auto get_context() -> ubus_context * // is member to enforce inuse. + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + inline auto get_context() -> ubus_context* // is member to enforce inuse. { - static auto ubus_freeing = [] (ubus_context * ctx) { ubus_free(ctx); }; - static std::unique_ptr - lazy_ctx{ubus_connect(nullptr), ubus_freeing}; + static auto ubus_freeing = [](ubus_context* ctx) { ubus_free(ctx); }; + static std::unique_ptr lazy_ctx{ubus_connect(nullptr), + ubus_freeing}; - if (!lazy_ctx) { // it could be available on a later call: + if (!lazy_ctx) { // it could be available on a later call: lazy_ctx.reset(ubus_connect(nullptr)); @@ -222,82 +202,79 @@ public: return lazy_ctx.get(); } - - //NOLINTNEXTLINE(readability-convert-member-functions-to-static) - inline auto get_blob_buf() -> blob_buf * // is member to enforce inuse. + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + inline auto get_blob_buf() -> blob_buf* // is member to enforce inuse. { static blob_buf buf; - static auto blob_buf_freeing = [] (blob_buf * b) { blob_buf_free(b); }; + static auto blob_buf_freeing = [](blob_buf* b) { blob_buf_free(b); }; static std::unique_ptr - created_to_free_on_the_end_of_life{&buf, blob_buf_freeing}; + created_to_free_on_the_end_of_life{&buf, blob_buf_freeing}; blob_buf_init(&buf, 0); return &buf; } - - inline ~lock_shared_resources() { inuse.unlock(); } - + inline ~lock_shared_resources() + { + inuse.unlock(); + } }; +template +auto call(const char* path, const char* method, F set_arguments, int timeout = call_timeout) + -> message; -template -auto call(const char * path, const char * method, F set_arguments, - int timeout=call_timeout) -> message; - - -inline auto call(const char * path, const char * method, - int timeout=call_timeout) -> message -{ return call(path, method, [](blob_buf * /*buf*/) { return 0; }, timeout); } - - -inline auto call(const char * path, int timeout=call_timeout) -> message -{ return call(path, "", timeout); } - - +inline auto call(const char* path, const char* method, int timeout = call_timeout) -> message +{ + return call( + path, method, [](blob_buf* /*buf*/) { return 0; }, timeout); +} +inline auto call(const char* path, int timeout = call_timeout) -> message +{ + return call(path, "", timeout); +} // ------------------------- implementation: ---------------------------------- - std::mutex lock_shared_resources::inuse; - -inline auto iterator::operator++() -> iterator & +inline auto iterator::operator++() -> iterator& { - for(;;) { - #ifndef NDEBUG - std::cout<')<<" look for "<pos)<') << " 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) - && ipos)>0 ) - { //immmerge: + if ((id == BLOBMSG_TYPE_TABLE || id == BLOBMSG_TYPE_ARRAY) && i < n && matches() && + blobmsg_data_len(cur->pos) > 0) + { // immmerge: ++i; - auto tmp = cur.release(); + auto* tmp = cur.release(); - struct new_iterator : public iterator // use private constructor: - { explicit new_iterator(iterator * par) : iterator{par} {} }; + struct new_iterator : public iterator // use private constructor: + { + explicit new_iterator(iterator* par) : iterator{par} {} + }; cur = std::make_unique(tmp); - - } else { + } + 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; } + if (cur->rem > 0 && len <= cur->rem && len >= sizeof(blob_attr)) { + break; + } - //emerge: - auto tmp = cur->parent; + // emerge: + auto* tmp = cur->parent; if (tmp == nullptr) { cur->pos = nullptr; @@ -309,67 +286,69 @@ inline auto iterator::operator++() -> iterator & --i; } } - if (i==n && matches()) { return *cur; } + if (i == n && matches()) { + return *cur; + } } } - -template -inline auto call(const char * path, const char * method, F set_arguments, - int timeout) -> message +template +inline auto call(const char* path, const char* method, F set_arguments, int timeout) -> message { - auto shared = lock_shared_resources{}; - auto ctx = shared.get_context(); + auto* ctx = shared.get_context(); - uint32_t id; + uint32_t id = 0; int err = ubus_lookup_id(ctx, path, &id); - if (err==0) { // call + if (err == 0) { // call ubus_request request{}; - auto buf = shared.get_blob_buf(); + auto* buf = shared.get_blob_buf(); err = set_arguments(buf); - if (err==0) { + if (err == 0) { err = ubus_invoke_async(ctx, id, method, buf->head, &request); } - if (err==0) { - + if (err == 0) { msg_ptr message_ptr; /* Cannot capture message_ptr, the lambda would be another type. - * Pass a location where to save the message as priv pointer when - * invoking and get it back here: - */ + * Pass a location where to save the message as priv pointer when + * invoking and get it back here: + */ request.priv = &message_ptr; - request.data_cb = - [](ubus_request * req, int /*type*/, blob_attr * msg) - { - if (req==nullptr || msg==nullptr) { return; } + request.data_cb = [](ubus_request* req, int /*type*/, blob_attr* msg) { + if (req == nullptr || msg == nullptr) { + return; + } - auto saved = static_cast(req->priv); - if (saved==nullptr || *saved) { return; } + auto* saved = static_cast(req->priv); + if (saved == nullptr || *saved) { + return; + } saved->reset(blob_memdup(msg), free); - if (!*saved) { throw std::bad_alloc(); } + if (!*saved) { + throw std::bad_alloc(); + } }; err = ubus_complete_request(ctx, &request, timeout); - if (err==0) { return message{message_ptr}; } + if (err == 0) { + return message{message_ptr}; + } } } std::string errmsg = "ubus::call error: cannot invoke"; - errmsg += " (" + std::to_string(err) + ") " + path + " " + method; + errmsg += " (" + std::to_string(err) + ") " + path + " " + method; throw std::runtime_error(errmsg); } - -} // namespace ubus - +} // namespace ubus #endif diff --git a/net/nginx-util/src/uci-cxx.cpp b/net/nginx-util/src/uci-cxx.cpp new file mode 100644 index 000000000..c8805482b --- /dev/null +++ b/net/nginx-util/src/uci-cxx.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +#include "uci-cxx.hpp" + +auto main() -> int +{ + uci::element p = uci::package{"nginx"}; + std::cout << "package " << p.name() << "\n\n"; + for (auto s : p) { + std::cout << "config " << s.type() << " '" << s.name() << "'\n"; + for (auto o : s) { + for (auto i : o) { + std::cout << "\t" << o.type() << " " << o.name() << " '" << i.name() << "'\n"; + } + } + std::cout << "\n"; + } +} diff --git a/net/nginx-util/src/uci-cxx.hpp b/net/nginx-util/src/uci-cxx.hpp new file mode 100644 index 000000000..4598890d9 --- /dev/null +++ b/net/nginx-util/src/uci-cxx.hpp @@ -0,0 +1,474 @@ +#ifndef _UCI_CXX_HPP +#define _UCI_CXX_HPP + +#include +#include +#include +#include +#include +#include + +namespace uci { + +template +class iterator { // like uci_foreach_element_safe. + + private: + const uci_ptr& _ptr; + + uci_element* _it = nullptr; + + uci_element* _next = nullptr; + + // wrapper for clang-tidy + inline auto _list_to_element(const uci_list* cur) -> uci_element* + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast) + return list_to_element(cur); // macro casting container=pointer-offset. + } + + public: + inline explicit iterator(const uci_ptr& ptr, const uci_list* cur) + : _ptr{ptr}, _it{_list_to_element(cur)} + { + _next = _list_to_element(_it->list.next); + } + + inline iterator(iterator&&) noexcept = default; + + inline iterator(const iterator&) = delete; + + inline auto operator=(const iterator&) -> iterator& = delete; + + inline auto operator=(iterator &&) -> iterator& = delete; + + auto operator*() -> T + { + return T{_ptr, _it}; + } + + inline auto operator!=(const iterator& rhs) -> bool + { + return (&_it->list != &rhs._it->list); + } + + inline auto operator++() -> iterator& + { + _it = _next; + _next = _list_to_element(_next->list.next); + return *this; + } + + inline ~iterator() = default; +}; + +class locked_context { + private: + static std::mutex inuse; + + public: + inline locked_context() + { + inuse.lock(); + } + + inline locked_context(locked_context&&) noexcept = default; + + inline locked_context(const locked_context&) = delete; + + inline auto operator=(const locked_context&) -> locked_context& = delete; + + inline auto operator=(locked_context &&) -> locked_context& = delete; + + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + inline auto get() -> uci_context* // is member to enforce inuse + { + static auto free_ctx = [](uci_context* ctx) { uci_free_context(ctx); }; + static std::unique_ptr lazy_ctx{uci_alloc_context(), + free_ctx}; + + if (!lazy_ctx) { // it could be available on a later call: + lazy_ctx.reset(uci_alloc_context()); + if (!lazy_ctx) { + throw std::runtime_error("uci error: cannot allocate context"); + } + } + + return lazy_ctx.get(); + } + + inline ~locked_context() + { + inuse.unlock(); + } +}; + +template +class element { + private: + uci_list* _begin = nullptr; + + uci_list* _end = nullptr; + + uci_ptr _ptr{}; + + protected: + [[nodiscard]] inline auto ptr() -> uci_ptr& + { + return _ptr; + } + + [[nodiscard]] inline auto ptr() const -> const uci_ptr& + { + return _ptr; + } + + void init_begin_end(uci_list* begin, uci_list* end) + { + _begin = begin; + _end = end; + } + + inline explicit element(const uci_ptr& pre, uci_element* last) : _ptr{pre} + { + _ptr.last = last; + } + + inline explicit element() = default; + + public: + inline element(element&&) noexcept = default; + + inline element(const element&) = delete; + + inline auto operator=(const element&) -> element& = delete; + + inline auto operator=(element &&) -> element& = delete; + + auto operator[](std::string_view key) const -> T; + + [[nodiscard]] inline auto name() const -> std::string + { + return _ptr.last->name; + } + + void rename(const char* value) const; + + void commit() const; + + [[nodiscard]] inline auto begin() const -> iterator + { + return iterator{_ptr, _begin}; + } + + [[nodiscard]] inline auto end() const -> iterator + { + return iterator{_ptr, _end}; + } + + inline ~element() = default; +}; + +class section; + +class option; + +class item; + +class package : public element
{ + public: + inline package(const uci_ptr& pre, uci_element* last) : element{pre, last} + { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic,cppcoreguidelines-pro-type-cstyle-cast) + ptr().p = uci_to_package(ptr().last); // macro casting pointer-offset. + ptr().package = ptr().last->name; + + auto* end = &ptr().p->sections; + auto* begin = end->next; + init_begin_end(begin, end); + } + + explicit package(const char* name); + + auto set(const char* key, const char* type) const -> section; +}; + +class section : public element