From 8bb3eba3c22f81488f6702ca0c31eeae0441aec0 Mon Sep 17 00:00:00 2001 From: Jeffery To Date: Tue, 29 Jan 2019 21:21:20 +0800 Subject: [PATCH] stunnel: Update init script The reworked init script: * Loads and validates options using uci_validate_section() (through uci_load_validate()) * Allows service options be specified in the globals section * Hard-codes less global options (debug, syslog), as their default values already work * Adds support for almost all options (up to the current package version, 5.49) * Moves the pid file into a subdirectory (/var/run/stunnel) so that it can be created successfully when setuid is used Certain options are omitted: * chroot - requires more setup than the init script can manage * fips, libwrap - disabled at compile-time * iconActive, iconError, iconIdle, taskbar - gui/win32 only * verify - obsolete, verifyChain and/or verifyPeer should be used instead Signed-off-by: Jeffery To --- net/stunnel/Makefile | 2 +- net/stunnel/files/stunnel.init | 434 +++++++++++++++++++++++++-------- net/stunnel/files/stunnel.uci | 6 +- 3 files changed, 333 insertions(+), 109 deletions(-) diff --git a/net/stunnel/Makefile b/net/stunnel/Makefile index e05c1d582..a9736c1a2 100644 --- a/net/stunnel/Makefile +++ b/net/stunnel/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=stunnel PKG_VERSION:=5.49 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_LICENSE:=GPL-2.0+ PKG_MAINTAINER:=Florian Eckert diff --git a/net/stunnel/files/stunnel.init b/net/stunnel/files/stunnel.init index a1772e159..0a2dfa416 100644 --- a/net/stunnel/files/stunnel.init +++ b/net/stunnel/files/stunnel.init @@ -1,180 +1,402 @@ #!/bin/sh /etc/rc.common # Copyright (C) 2006-2008 OpenWrt.org +# Copyright (C) 2019 Jeffery To START=90 USE_PROCD=1 -PID_FILE="/var/run/stunnel.pid" -CONF_FILE="/tmp/stunnel.conf" +PID_FILE="/var/run/stunnel/stunnel.pid" +CONF_FILE="/var/etc/stunnel.conf" BIN="/usr/bin/stunnel" -SERVICE_SECTION_FOUND=0 +CONF_FILE_CREATED= +HAVE_ALT_CONF_FILE= +SERVICE_SECTION_FOUND= -global_defs() { - local debug compression +validate_globals_section() { + uci_load_validate stunnel globals "$1" "$2" \ + 'alt_config_file:file' \ + \ + 'compression:or("deflate","zlib")' \ + 'EGD:string' \ + 'engine:string' \ + 'engineCtrl:string' \ + 'engineDefault:list(or("ALL","CIPHERS","DH","DIGESTS","DSA","ECDH","ECDSA","PKEY","PKEY_ASN1","PKEY_CRYPTO","RAND","RSA"))' \ + 'log:or("append","overwrite")' \ + 'output:string' \ + 'RNDbytes:uinteger' \ + 'RNDfile:string' \ + 'RNDoverwrite:bool' \ + 'setgid:or(string,uinteger)' \ + 'setuid:or(string,uinteger)' \ + 'syslog:bool' \ + ; +} - config_get alt_config_file 'globals' alt_config_file - [ -z "$alt_config_file" ] || return 0 +validate_service_section() { + uci_load_validate stunnel service "$1" "$2" \ + 'enabled:bool:1' \ + \ + 'setgid:or(string,uinteger)' \ + 'setuid:or(string,uinteger)' \ + ; +} - # Set default settings - printf "foreground = yes\n" >> "$CONF_FILE" - printf "pid = %s\n" "$PID_FILE" >> "$CONF_FILE" - printf "syslog = yes\n" >> "$CONF_FILE" +validate_service_options() { + uci_load_validate stunnel "$1" "$2" "$3" \ + 'accept_host:host' \ + 'accept_port:port' \ + 'CAfile:string' \ + 'CApath:string' \ + 'cert:string' \ + 'checkEmail:list(string)' \ + 'checkHost:list(host)' \ + 'checkIP:list(ipaddr)' \ + 'ciphers:list(string)' \ + 'client:bool' \ + 'config:list(string)' \ + 'connect:list(string)' \ + 'CRLfile:string' \ + 'CRLpath:string' \ + 'curve:string' \ + 'debug:or(range(0,7),string)' \ + 'delay:bool' \ + 'engineId:string' \ + 'engineNum:and(uinteger,min(1))' \ + 'exec:string' \ + 'execArgs:string' \ + 'failover:or("prio","rr")' \ + 'ident:string' \ + 'include:directory' \ + 'key:string' \ + 'local:host' \ + 'logId:or("process","sequential","thread","unique")' \ + 'OCSP:string' \ + 'OCSPaia:bool' \ + 'OCSPflag:list(or("NOCASIGN","NOCERTS","NOCHAIN","NOCHECKS","NODELEGATED","NOEXPLICIT","NOINTERN","NOSIGS","NOTIME","NOVERIFY","RESPID_KEY","TRUSTOTHER"))' \ + 'OCSPnonce:bool' \ + 'options:list(string) ' \ + 'protocol:or("cifs","connect","imap","nntp","pgsql","pop3","proxy","smtp","socks")' \ + 'protocolAuthentication:or("basic","login","ntlm","plain")' \ + 'protocolDomain:hostname' \ + 'protocolHost_host:host' \ + 'protocolHost_port:port' \ + 'protocolPassword:string' \ + 'protocolUsername:string' \ + 'PSKidentity:string' \ + 'PSKsecrets:string' \ + 'pty:bool' \ + 'redirect_host:host' \ + 'redirect_port:port' \ + 'renegotiation:bool' \ + 'requireCert:bool' \ + 'reset:bool' \ + 'retry:bool' \ + 'service:string' \ + 'sessionCacheSize:uinteger' \ + 'sessionCacheTimeout:uinteger' \ + 'sessiond_host:host' \ + 'sessiond_port:port' \ + 'sni:list(string)' \ + 'socket:list(string)' \ + 'sslVersion:or("all","SSLv2","SSLv3","TLSv1","TLSv1.1","TLSv1.2")' \ + 'stack:uinteger' \ + 'TIMEOUTbusy:uinteger' \ + 'TIMEOUTclose:uinteger' \ + 'TIMEOUTconnect:uinteger' \ + 'TIMEOUTidle:uinteger' \ + 'transparent:or("both","destination","none","source")' \ + 'verifyChain:bool' \ + 'verifyPeer:bool' \ + ; +} - config_get debug 'globals' debug '5' - printf "debug = %s\n" "$debug" >> "$CONF_FILE" +validate_globals_section_service_options() { + validate_service_options globals "$@" +} - config_get compression 'globals' compression - [ -z "$compression" ] || printf "compression = %s\n" "$compression" >> "$CONF_FILE" +validate_service_section_service_options() { + validate_service_options service "$@" } print_options() { - local config=$1 - shift - for opt in "$@"; do - local $opt - local value - local is_boolean=0 - - if [ "${opt:0:5}" == "bool_" ]; then - opt="${opt:5}" - is_boolean=1 - fi - - config_get "value" "$config" "$opt" - [ -z "$value" ] || { - if [ "$value" = '1' ] && [ "$is_boolean" -eq "1" ]; then - value="yes" - elif [ "$value" = '0' ] && [ "$is_boolean" -eq "1" ] ; then - value="no" - fi - printf "%s = %s\n" "$opt" "$value" >> "$CONF_FILE" + local _opt + local _value + for _opt in $*; do + eval "_value=\$$_opt" + [ -z "$_value" ] || echo "$_opt = $_value" >> "$CONF_FILE" + done +} + +print_bool_options() { + local _opt + local _bool + local _value + for _opt in $*; do + eval "_bool=\$$_opt" + [ -z "$_bool" ] || { + _value=no + [ "$_bool" != 1 ] || _value=yes + echo "$_opt = $_value" >> "$CONF_FILE" } done } -print_list() { - local config=$1 - shift - for opt in "$@"; do - local $opt - local elements - config_get "elements" "$config" "$opt" - for element in $elements; do - printf "%s = %s\n" "$opt" "$element" >> "$CONF_FILE" +print_lists_map() { + local _opt + local _values + local _value + for _opt in $*; do + eval "_values=\$$_opt" + for _value in $_values; do + echo "$_opt = $_value" >> "$CONF_FILE" done done } -print_list_colon() { - local config=$1 - local value +print_lists_reduce() { + local _delim="$1" + local _opt + local _value + local _values + local _v shift - for opt in "$@"; do - local $opt - local elements - config_get "elements" "$config" "$opt" - for element in $elements; do - value="${value}:${element}" + for _opt in $*; do + _value= + eval "_values=\$$_opt" + for _v in $_values; do + _value=$_value$_delim$_v done - printf "%s = %s\n" "$opt" "${value#*:}" >> "$CONF_FILE" + _value=${_value#$_delim} + [ -z "$_value" ] || echo "$_opt = $_value" >> "$CONF_FILE" done } -service_section() { - local cfg="$1" - local accept_host accept_port enabled +print_host_port() { + local _opt + local _host + local _port + for _opt in $*; do + eval "_host=\${${_opt}_host}" + eval "_port=\${${_opt}_port}" + [ -z "$_host" ] || [ -z "$_port" ] || echo "$_opt = $_host:$_port" >> "$CONF_FILE" + done +} - config_get_bool enabled "$cfg" 'enabled' '1' - [ ${enabled} -gt 0 ] || return 0 +print_optional_host_port() { + local _opt + local _host + local _port + local _value + for _opt in $*; do + eval "_host=\${${_opt}_host}" + eval "_port=\${${_opt}_port}" + [ -z "$_port" ] || { + _value=$_port + [ -z "$_host" ] || _value=$_host:$_port + echo "$_opt = $_value" >> "$CONF_FILE" + } + done +} - SERVICE_SECTION_FOUND=1 - printf "\n" >> "$CONF_FILE" - printf "[%s]\n" "$cfg" >> "$CONF_FILE" +print_global_options() { + print_options \ + compression \ + EGD \ + engine \ + engineCtrl \ + log \ + output \ + RNDbytes \ + RNDfile \ + RNDoverwrite \ + ; + + print_bool_options \ + syslog \ + ; - config_get accept_host "$cfg" accept_host 'localhost' - config_get accept_port "$cfg" accept_port - printf "accept = %s:%s\n" "$accept_host" "$accept_port" >> "$CONF_FILE" + print_lists_reduce , \ + engineDefault \ + ; +} + +print_service_options() { + [ "$2" = 0 ] || { + echo "validation failed" + return 1 + } - print_options "$cfg" CApath \ + print_options \ CAfile \ + CApath \ cert \ - CRLpath \ CRLfile \ + CRLpath \ curve \ - logId \ debug \ + logId \ engineId \ engineNum \ + exec \ + execArgs \ failover \ ident \ + include \ key \ local \ + OCSP \ + protocol \ + protocolAuthentication \ + protocolDomain \ + protocolPassword \ + protocolUsername \ PSKidentity \ PSKsecrets \ + service \ + sessionCacheSize \ + sessionCacheTimeout \ + setgid \ + setuid \ sslVersion \ + stack \ TIMEOUTbusy \ TIMEOUTclose \ TIMEOUTconnect \ TIMEOUTidle \ - bool_delay \ - bool_libwrap \ - bool_reset \ - bool_requireCert \ - bool_verifyChain \ - bool_verifyPeer \ - bool_client - - print_list "$cfg" checkEmail \ + transparent \ + ; + + print_bool_options \ + client \ + delay \ + OCSPaia \ + OCSPnonce \ + pty \ + renegotiation \ + requireCert \ + reset \ + retry \ + verifyChain \ + verifyPeer \ + ; + + print_lists_map \ + checkEmail \ checkHost \ checkIP \ + config \ connect \ - options + OCSPflag \ + options \ + sni \ + socket \ + ; - print_list_colon "$cfg" ciphers -} + print_lists_reduce : \ + ciphers \ + ; -process_config() { - local alt_config_file + print_host_port \ + protocolHost \ + sessiond \ + ; - rm -f "$CONF_FILE" + print_optional_host_port \ + accept \ + redirect \ + ; +} - # First line - printf "; STunnel configuration file generated by uci\n" > "$CONF_FILE" - printf "; Written %s\n\n" "$(date +'%c')" >> "$CONF_FILE" +create_conf_file() { + [ -n "$CONF_FILE_CREATED" ] || { + mkdir -p "$(dirname "$CONF_FILE")" + echo "; STunnel configuration file generated by uci" > "$CONF_FILE" + echo "; Written $(date +'%c')" >> "$CONF_FILE" + echo >> "$CONF_FILE" + echo "foreground = quiet" >> "$CONF_FILE" + echo "pid = $PID_FILE" >> "$CONF_FILE" + CONF_FILE_CREATED=1 + } +} - [ -f /etc/config/stunnel ] || return 0 +global_defs() { + local pid_dir - config_load stunnel - global_defs + [ "$2" = 0 ] || { + echo "validation failed" + return 1 + } + + # If the first globals section has alt_config_file, don't process any more globals + [ -z "$HAVE_ALT_CONF_FILE" ] || return 0 - # If "alt_config_file" specified, use that instead - [ -n "$alt_config_file" ] && [ -f "$alt_config_file" ] && { - rm -f "$CONF_FILE" + # If "alt_config_file" specified in the first globals section, use that instead + [ -z "$alt_config_file" ] || [ -n "$CONF_FILE_CREATED" ] || { # Symlink "alt_config_file" since it's a bit easier and safer ln -s "$alt_config_file" "$CONF_FILE" - # Set section found to start service user hopfully knows what you does + # Set section found to start service, user hopefully knows what they are doing SERVICE_SECTION_FOUND=1 + CONF_FILE_CREATED=1 + HAVE_ALT_CONF_FILE=1 return 0 } - config_foreach service_section service + pid_dir="$(dirname "$PID_FILE")" + mkdir -p "$pid_dir" + [ -z "$setuid" ] || chown "$setuid" "$pid_dir" + [ -z "$setgid" ] || chown ":$setgid" "$pid_dir" + + create_conf_file + print_global_options + validate_service_options globals "$1" print_service_options +} + +service_section() { + [ "$2" = 0 ] || { + echo "validation failed" + return 1 + } + + [ "$enabled" = 1 ] || return 0 + + SERVICE_SECTION_FOUND=1 + echo >> "$CONF_FILE" + echo "[$1]" >> "$CONF_FILE" + + validate_service_options service "$1" print_service_options } service_triggers() { - procd_add_reload_trigger "stunnel" + procd_add_reload_trigger stunnel + + procd_open_validate + validate_globals_section + validate_globals_section_service_options + validate_service_section + validate_service_section_service_options + procd_close_validate } start_service() { - process_config - - if [ "$SERVICE_SECTION_FOUND" = 1 ]; then - procd_open_instance - procd_set_param command "$BIN" - procd_append_param command "$CONF_FILE" - procd_set_param respawn - procd_set_param file "$CONF_FILE" - procd_close_instance - else + rm -f "$CONF_FILE" + config_load stunnel + + config_foreach validate_globals_section globals global_defs + + [ -n "$HAVE_ALT_CONF_FILE" ] || { + create_conf_file + config_foreach validate_service_section service service_section + } + + [ -n "$SERVICE_SECTION_FOUND" ] || { logger -t stunnel -p daemon.info "No uci service section enabled or found!" - fi + return 1 + } + + procd_open_instance + procd_set_param command "$BIN" + procd_append_param command "$CONF_FILE" + procd_set_param respawn + procd_set_param file "$CONF_FILE" + procd_close_instance } diff --git a/net/stunnel/files/stunnel.uci b/net/stunnel/files/stunnel.uci index 6fad1c6c7..89633ab99 100644 --- a/net/stunnel/files/stunnel.uci +++ b/net/stunnel/files/stunnel.uci @@ -1,8 +1,10 @@ config globals 'globals' - option alt_config_file '/etc/stunnel/stunnel.conf' - option debug '5' + #option alt_config_file '/etc/stunnel/stunnel.conf' + option setuid 'nobody' + option setgid 'nogroup' config service 'dummy' + option enabled '1' option client '1' option accept_host 'localhost' option accept_port '6000'