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 <jeffery.to@gmail.com>
This commit is contained in:
Jeffery To 2019-01-29 21:21:20 +08:00
parent effc8b5bf8
commit 8bb3eba3c2
3 changed files with 330 additions and 106 deletions

View file

@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=stunnel PKG_NAME:=stunnel
PKG_VERSION:=5.49 PKG_VERSION:=5.49
PKG_RELEASE:=1 PKG_RELEASE:=2
PKG_LICENSE:=GPL-2.0+ PKG_LICENSE:=GPL-2.0+
PKG_MAINTAINER:=Florian Eckert <fe@dev.tdt.de> PKG_MAINTAINER:=Florian Eckert <fe@dev.tdt.de>

View file

@ -1,180 +1,402 @@
#!/bin/sh /etc/rc.common #!/bin/sh /etc/rc.common
# Copyright (C) 2006-2008 OpenWrt.org # Copyright (C) 2006-2008 OpenWrt.org
# Copyright (C) 2019 Jeffery To
START=90 START=90
USE_PROCD=1 USE_PROCD=1
PID_FILE="/var/run/stunnel.pid" PID_FILE="/var/run/stunnel/stunnel.pid"
CONF_FILE="/tmp/stunnel.conf" CONF_FILE="/var/etc/stunnel.conf"
BIN="/usr/bin/stunnel" BIN="/usr/bin/stunnel"
SERVICE_SECTION_FOUND=0 CONF_FILE_CREATED=
HAVE_ALT_CONF_FILE=
SERVICE_SECTION_FOUND=
global_defs() { validate_globals_section() {
local debug compression 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 validate_service_section() {
[ -z "$alt_config_file" ] || return 0 uci_load_validate stunnel service "$1" "$2" \
'enabled:bool:1' \
\
'setgid:or(string,uinteger)' \
'setuid:or(string,uinteger)' \
;
}
# Set default settings validate_service_options() {
printf "foreground = yes\n" >> "$CONF_FILE" uci_load_validate stunnel "$1" "$2" "$3" \
printf "pid = %s\n" "$PID_FILE" >> "$CONF_FILE" 'accept_host:host' \
printf "syslog = yes\n" >> "$CONF_FILE" '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' validate_globals_section_service_options() {
printf "debug = %s\n" "$debug" >> "$CONF_FILE" validate_service_options globals "$@"
}
config_get compression 'globals' compression validate_service_section_service_options() {
[ -z "$compression" ] || printf "compression = %s\n" "$compression" >> "$CONF_FILE" validate_service_options service "$@"
} }
print_options() { print_options() {
local config=$1 local _opt
shift local _value
for opt in "$@"; do for _opt in $*; do
local $opt eval "_value=\$$_opt"
local value [ -z "$_value" ] || echo "$_opt = $_value" >> "$CONF_FILE"
local is_boolean=0 done
}
if [ "${opt:0:5}" == "bool_" ]; then print_bool_options() {
opt="${opt:5}" local _opt
is_boolean=1 local _bool
fi local _value
for _opt in $*; do
config_get "value" "$config" "$opt" eval "_bool=\$$_opt"
[ -z "$value" ] || { [ -z "$_bool" ] || {
if [ "$value" = '1' ] && [ "$is_boolean" -eq "1" ]; then _value=no
value="yes" [ "$_bool" != 1 ] || _value=yes
elif [ "$value" = '0' ] && [ "$is_boolean" -eq "1" ] ; then echo "$_opt = $_value" >> "$CONF_FILE"
value="no"
fi
printf "%s = %s\n" "$opt" "$value" >> "$CONF_FILE"
} }
done done
} }
print_list() { print_lists_map() {
local config=$1 local _opt
shift local _values
for opt in "$@"; do local _value
local $opt for _opt in $*; do
local elements eval "_values=\$$_opt"
config_get "elements" "$config" "$opt" for _value in $_values; do
for element in $elements; do echo "$_opt = $_value" >> "$CONF_FILE"
printf "%s = %s\n" "$opt" "$element" >> "$CONF_FILE"
done done
done done
} }
print_list_colon() { print_lists_reduce() {
local config=$1 local _delim="$1"
local value local _opt
local _value
local _values
local _v
shift shift
for opt in "$@"; do for _opt in $*; do
local $opt _value=
local elements eval "_values=\$$_opt"
config_get "elements" "$config" "$opt" for _v in $_values; do
for element in $elements; do _value=$_value$_delim$_v
value="${value}:${element}"
done done
printf "%s = %s\n" "$opt" "${value#*:}" >> "$CONF_FILE" _value=${_value#$_delim}
[ -z "$_value" ] || echo "$_opt = $_value" >> "$CONF_FILE"
done done
} }
service_section() { print_host_port() {
local cfg="$1" local _opt
local accept_host accept_port enabled 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' print_optional_host_port() {
[ ${enabled} -gt 0 ] || return 0 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 print_global_options() {
printf "\n" >> "$CONF_FILE" print_options \
printf "[%s]\n" "$cfg" >> "$CONF_FILE" compression \
EGD \
engine \
engineCtrl \
log \
output \
RNDbytes \
RNDfile \
RNDoverwrite \
;
config_get accept_host "$cfg" accept_host 'localhost' print_bool_options \
config_get accept_port "$cfg" accept_port syslog \
printf "accept = %s:%s\n" "$accept_host" "$accept_port" >> "$CONF_FILE" ;
print_options "$cfg" CApath \ print_lists_reduce , \
engineDefault \
;
}
print_service_options() {
[ "$2" = 0 ] || {
echo "validation failed"
return 1
}
print_options \
CAfile \ CAfile \
CApath \
cert \ cert \
CRLpath \
CRLfile \ CRLfile \
CRLpath \
curve \ curve \
logId \
debug \ debug \
logId \
engineId \ engineId \
engineNum \ engineNum \
exec \
execArgs \
failover \ failover \
ident \ ident \
include \
key \ key \
local \ local \
OCSP \
protocol \
protocolAuthentication \
protocolDomain \
protocolPassword \
protocolUsername \
PSKidentity \ PSKidentity \
PSKsecrets \ PSKsecrets \
service \
sessionCacheSize \
sessionCacheTimeout \
setgid \
setuid \
sslVersion \ sslVersion \
stack \
TIMEOUTbusy \ TIMEOUTbusy \
TIMEOUTclose \ TIMEOUTclose \
TIMEOUTconnect \ TIMEOUTconnect \
TIMEOUTidle \ TIMEOUTidle \
bool_delay \ transparent \
bool_libwrap \ ;
bool_reset \
bool_requireCert \
bool_verifyChain \
bool_verifyPeer \
bool_client
print_list "$cfg" checkEmail \ print_bool_options \
client \
delay \
OCSPaia \
OCSPnonce \
pty \
renegotiation \
requireCert \
reset \
retry \
verifyChain \
verifyPeer \
;
print_lists_map \
checkEmail \
checkHost \ checkHost \
checkIP \ checkIP \
config \
connect \ connect \
options OCSPflag \
options \
sni \
socket \
;
print_list_colon "$cfg" ciphers print_lists_reduce : \
ciphers \
;
print_host_port \
protocolHost \
sessiond \
;
print_optional_host_port \
accept \
redirect \
;
} }
process_config() { create_conf_file() {
local alt_config_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
}
}
rm -f "$CONF_FILE" global_defs() {
local pid_dir
# First line [ "$2" = 0 ] || {
printf "; STunnel configuration file generated by uci\n" > "$CONF_FILE" echo "validation failed"
printf "; Written %s\n\n" "$(date +'%c')" >> "$CONF_FILE" return 1
}
[ -f /etc/config/stunnel ] || return 0 # If the first globals section has alt_config_file, don't process any more globals
[ -z "$HAVE_ALT_CONF_FILE" ] || return 0
config_load stunnel # If "alt_config_file" specified in the first globals section, use that instead
global_defs [ -z "$alt_config_file" ] || [ -n "$CONF_FILE_CREATED" ] || {
# If "alt_config_file" specified, use that instead
[ -n "$alt_config_file" ] && [ -f "$alt_config_file" ] && {
rm -f "$CONF_FILE"
# Symlink "alt_config_file" since it's a bit easier and safer # Symlink "alt_config_file" since it's a bit easier and safer
ln -s "$alt_config_file" "$CONF_FILE" 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 SERVICE_SECTION_FOUND=1
CONF_FILE_CREATED=1
HAVE_ALT_CONF_FILE=1
return 0 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() { 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() { start_service() {
process_config 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!"
return 1
}
if [ "$SERVICE_SECTION_FOUND" = 1 ]; then
procd_open_instance procd_open_instance
procd_set_param command "$BIN" procd_set_param command "$BIN"
procd_append_param command "$CONF_FILE" procd_append_param command "$CONF_FILE"
procd_set_param respawn procd_set_param respawn
procd_set_param file "$CONF_FILE" procd_set_param file "$CONF_FILE"
procd_close_instance procd_close_instance
else
logger -t stunnel -p daemon.info "No uci service section enabled or found!"
fi
} }

View file

@ -1,8 +1,10 @@
config globals 'globals' config globals 'globals'
option alt_config_file '/etc/stunnel/stunnel.conf' #option alt_config_file '/etc/stunnel/stunnel.conf'
option debug '5' option setuid 'nobody'
option setgid 'nogroup'
config service 'dummy' config service 'dummy'
option enabled '1'
option client '1' option client '1'
option accept_host 'localhost' option accept_host 'localhost'
option accept_port '6000' option accept_port '6000'