#!/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/stunnel.pid" CONF_FILE="/var/etc/stunnel.conf" BIN="/usr/bin/stunnel" CONF_FILE_CREATED= HAVE_ALT_CONF_FILE= SERVICE_SECTION_FOUND= 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' \ ; } validate_service_section() { uci_load_validate stunnel service "$1" "$2" \ 'enabled:bool:1' \ \ 'setgid:or(string,uinteger)' \ 'setuid:or(string,uinteger)' \ ; } 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' \ ; } validate_globals_section_service_options() { validate_service_options globals "$@" } validate_service_section_service_options() { validate_service_options service "$@" } print_options() { 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_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_lists_reduce() { local _delim="$1" local _opt local _value local _values local _v shift for _opt in $*; do _value= eval "_values=\$$_opt" for _v in $_values; do _value=$_value$_delim$_v done _value=${_value#$_delim} [ -z "$_value" ] || echo "$_opt = $_value" >> "$CONF_FILE" done } 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 } 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 } print_global_options() { print_options \ compression \ EGD \ engine \ engineCtrl \ log \ output \ RNDbytes \ RNDfile \ RNDoverwrite \ ; print_bool_options \ syslog \ ; print_lists_reduce , \ engineDefault \ ; } print_service_options() { [ "$2" = 0 ] || { echo "validation failed" return 1 } print_options \ CAfile \ CApath \ cert \ CRLfile \ CRLpath \ curve \ 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 \ transparent \ ; print_bool_options \ client \ delay \ OCSPaia \ OCSPnonce \ pty \ renegotiation \ requireCert \ reset \ retry \ verifyChain \ verifyPeer \ ; print_lists_map \ checkEmail \ checkHost \ checkIP \ config \ connect \ OCSPflag \ options \ sni \ socket \ ; print_lists_reduce : \ ciphers \ ; print_host_port \ protocolHost \ sessiond \ ; print_optional_host_port \ accept \ redirect \ ; } 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 } } global_defs() { local pid_dir [ "$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 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 hopefully knows what they are doing SERVICE_SECTION_FOUND=1 CONF_FILE_CREATED=1 HAVE_ALT_CONF_FILE=1 return 0 } 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_open_validate validate_globals_section validate_globals_section_service_options validate_service_section validate_service_section_service_options procd_close_validate } start_service() { 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 } 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 }