From 9d3877d446080b02d83d0a46770684915b99e04f Mon Sep 17 00:00:00 2001 From: Kiril Zyapkov Date: Wed, 9 Dec 2015 23:43:42 +0200 Subject: [PATCH] sshtunnel: switch to procd This changeset removes the shell wrapper the package used previously, and uses the instance-management abilities of procd to track ssh processes. Many fixes and improvements were integrated from the package maintainer's branch at https://github.com/nunojpg/packages/tree/sshtunnel Signed-off-by: Kiril Zyapkov --- net/sshtunnel/Makefile | 9 +- net/sshtunnel/files/sshtunnel.init | 297 +++++++++++++++-------------- net/sshtunnel/files/sshtunnel.sh | 21 -- 3 files changed, 157 insertions(+), 170 deletions(-) delete mode 100644 net/sshtunnel/files/sshtunnel.sh diff --git a/net/sshtunnel/Makefile b/net/sshtunnel/Makefile index 48cdb79df0..b277e22c5c 100644 --- a/net/sshtunnel/Makefile +++ b/net/sshtunnel/Makefile @@ -1,16 +1,15 @@ # -# Copyright (C) 2010-2014 OpenWrt.org +# Copyright (C) 2010-2015 OpenWrt.org # Copyright (C) 2010 segal.di.ubi.pt # # This is free software, licensed under the GNU General Public License v2. # See /LICENSE for more information. -# include $(TOPDIR)/rules.mk PKG_NAME:=sshtunnel -PKG_VERSION:=3 -PKG_RELEASE:=3 +PKG_VERSION:=4 +PKG_RELEASE:=1 PKG_LICENSE:=GPL-2.0+ PKG_MAINTAINER:=Nuno Goncalves @@ -39,8 +38,6 @@ endef define Package/sshtunnel/install $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_BIN) ./files/sshtunnel.init $(1)/etc/init.d/sshtunnel - $(INSTALL_DIR) $(1)/usr/bin - $(INSTALL_BIN) ./files/sshtunnel.sh $(1)/usr/bin/ $(INSTALL_DIR) $(1)/etc/config $(INSTALL_DATA) ./files/uci_sshtunnel $(1)/etc/config/sshtunnel endef diff --git a/net/sshtunnel/files/sshtunnel.init b/net/sshtunnel/files/sshtunnel.init index 482ec67784..a6d9aa78ca 100644 --- a/net/sshtunnel/files/sshtunnel.init +++ b/net/sshtunnel/files/sshtunnel.init @@ -1,18 +1,30 @@ #!/bin/sh /etc/rc.common +# +# Copyright (C) 2010-2015 OpenWrt.org +# Copyright (C) 2010 segal.di.ubi.pt +# START=99 STOP=01 +USE_PROCD=1 -PIDFILE="/tmp/run/sshtunnel" +PROG=/usr/bin/ssh +_log() { + logger -p daemon.info -t sshtunnel "$@" +} + +_err() { + logger -p daemon.err -t sshtunnel "$@" +} append_params() { - local p; local v; local args; + local p v args for p in $*; do eval "v=\$$p" [ -n "$v" ] && args="$args -o $p=$v" done - + ARGS_options="${args# *}" } @@ -24,179 +36,178 @@ append_string() { eval "$varname=\$new" } +validate_server_section() { + uci_validate_section sshtunnel server "$1" \ + 'user:string(1)' \ + 'hostname:host' \ + 'port:port' \ + 'retrydelay:min(1):60' \ + 'PKCS11Provider:file' \ + 'CheckHostIP:or("yes", "no")' \ + 'Compression:or("yes", "no")' \ + 'CompressionLevel:range(1,9)' \ + 'IdentityFile:file' \ + 'LogLevel:or("QUIET", "FATAL", "ERROR", "INFO", "VERBOSE", "DEBUG", "DEBUG1", "DEBUG2", "DEBUG3"):INFO' \ + 'ServerAliveCountMax:min(1)' \ + 'ServerAliveInterval:min(1)' \ + 'StrictHostKeyChecking:or("yes", "no")' \ + 'TCPKeepAlive:or("yes", "no")' \ + 'VerifyHostKeyDNS:or("yes", "no")' +} + +validate_tunnelR_section() { + uci_validate_section sshtunnel tunnelR "$1" \ + 'remoteaddress:or(host, "*"):*' \ + 'remoteport:port' \ + 'localaddress:host' \ + 'localport:port' +} + +validate_tunnelL_section() { + uci_validate_section sshtunnel tunnelL "$1" \ + 'remoteaddress:host' \ + 'remoteport:port' \ + 'localaddress:or(host, "*"):*' \ + 'localport:port' +} + +validate_tunnelD_section() { + uci_validate_section sshtunnel tunnelD "$1" \ + 'localaddress:or(host, "*"):*' \ + 'localport:port' +} + +validate_tunnelW_section() { + uci_validate_section sshtunnel tunnelW "$1" \ + 'vpntype:or("ethernet", "point-to-point"):point-to-point' \ + 'localdev:or("any", min(1))' \ + 'remotedev:or("any", min(1))' +} + load_tunnelR() { - config_get section_server $1 server - [ "$server" = "$section_server" ] || return 0 # continue to read next section if this is not for the current server - let count++ # count nr of valid sections to make sure there are at least one - - config_get remoteaddress $1 remoteaddress "*" - config_get remoteport $1 remoteport - config_get localaddress $1 localaddress - config_get localport $1 localport - - [ "$remoteport" -gt 0 ] || append_string "error" "[tunnelR: $1]remoteport must be a positive integer" "; " - [ "$localport" -gt 0 ] || append_string "error" "[tunnelR: $1]localport must be a positive integer" "; " - [ -n "$error" ] && return 1 + config_get section_server "$1" "server" + + # continue to read next section if this is not for the current server + [ "$server" = "$section_server" ] || return 0 + + # validate and load this remote tunnel config + local remoteaddress remoteport localaddress localport + validate_tunnelR_section "$1" || { _err "tunnelR ${1}: validation failed"; return 1; } + + [ -n "$remoteport" -a -n "$localport" -a -n "$remoteaddress" ] || { _err "tunnelR ${1}: missing required options"; return 1; } + # count nr of valid sections to make sure there are at least one + let count++ + + _log "tunnelR at ${server}: -R $remoteaddress:$remoteport:$localaddress:$localport" append_string "ARGS_tunnels" "-R $remoteaddress:$remoteport:$localaddress:$localport" } load_tunnelL() { - config_get section_server $1 server - [ "$server" = "$section_server" ] || return 0 # continue to read next section if this is not for the current server - let count++ # count nr of valid sections to make sure there are at least one + config_get section_server "$1" "server" + + # continue to read next section if this is not for the current server + [ "$server" = "$section_server" ] || return 0 - config_get localaddress $1 localaddress "*" - config_get localport $1 localport - config_get remoteaddress $1 remoteaddress - config_get remoteport $1 remoteport + # validate and load this remote tunnel config + local remoteaddress remoteport localaddress localport + validate_tunnelL_section "$1" || { _err "tunnelL ${1}: validation failed"; return 1; } - [ "$remoteport" -gt 0 ] || append_string "error" "[tunnelL: $1]remoteport must be a positive integer" "; " - [ "$localport" -gt 0 ] || append_string "error" "[tunnelL: $1]localport must be a positive integer" "; " - [ -n "$error" ] && return 1 + [ -n "$remoteport" -a -n "$localport" -a -n "$remoteaddress" ] || { _err "tunnelL ${1}: missing required options"; return 1; } + # count nr of valid sections to make sure there are at least one + let count++ + + _log "tunnelL at ${server}: -L $localaddress:$localport:$remoteaddress:$remoteport" append_string "ARGS_tunnels" "-L $localaddress:$localport:$remoteaddress:$remoteport" } load_tunnelD() { - config_get section_server $1 server - [ "$server" = "$section_server" ] || return 0 # continue to read next section if this is not for the current server - let count++ # count nr of valid sections to make sure there are at least one + config_get section_server "$1" "server" + + # continue to read next section if this is not for the current server + [ "$server" = "$section_server" ] || return 0 - config_get localaddress $1 localaddress "*" - config_get localport $1 localport + # validate and load this remote tunnel config + local localaddress localport + validate_tunnelD_section "$1" || { _err "tunnelD ${1}: validation failed"; return 1; } - [ "$localport" -gt 0 ] || append_string "error" "[tunnelD: $1]localport must be a positive integer" "; " - [ -n "$error" ] && return 1 + [ -n "$localport" ] || { _err "tunnelD ${1}: missing localport"; return 1; } + # count nr of valid sections to make sure there are at least one + let count++ + + _log "proxy via ${server}: -D $localaddress:$localport" append_string "ARGS_tunnels" "-D $localaddress:$localport" } load_tunnelW() { - config_get section_server $1 server - [ "$server" = "$section_server" ] || return 0 # continue to read next section if this is not for the current server - let count++ # count nr of valid sections to make sure there are at least one + config_get section_server "$1" "server" + + # continue to read next section if this is not for the current server + [ "$server" = "$section_server" ] || return 0 + + # validate and load this remote tunnel config + local localdev remotedev vpntype + validate_tunnelW_section "$1" || { _err "tunnelW ${1}: validation failed"; return 1; } + + [ -n "$vpntype" -a -n "$localdev" -a -n "$remotedev" ] || { _err "tunnelW $1: missing or bad options"; return 1; } - config_get localdev $1 localdev "*" - config_get remotedev $1 remotedev "*" - config_get vpntype $1 vpntype "*" + [ "$user" == "root" ] || { _err "tunnelW ${1}: root is required for tun"; return 1; } - [ "$vpntype" == "ethernet" ] || [ "$vpntype" == "point-to-point" ] || append_string "error" "[tunnelW: $1] vpntype must be \"ethernet\" (tap) or \"pointopoint\" (tun)" "; " - [ "$localdev" == "any" ] || [ "$localdev" -ge 0 ] || append_string "error" "[tunnelW: $1] localdev must be an integer or \"any\"" "; " - [ "$remotedev" == "any" ] || [ "$remotedev" -ge 0 ] || append_string "error" "[tunnelW: $1] remotedev must be an integer or \"any\"" "; " - [ "$user" == "root" ] || logger -p user.warn -t "sshtunnel" "warning: root is required unless the tunnel device has been created manually" - [ -n "$error" ] && return 1 + # count nr of valid sections to make sure there are at least one + let count++ - append_string "ARGS_tunnels" "-w $localdev:$remotedev -o Tunnel=$vpntype" + _log "tunnelW to ${server}: -w $localdev:$remotedev -o Tunnel=$vpntype" + append_string "ARGS_tunnels" "-w $localdev:$remotedev -o Tunnel=$vpntype" } load_server() { server="$1" + local user hostname port retrydelay PKCS11Provider CheckHostIP Compression \ + CompressionLevel IdentityFile LogLevel ServerAliveCountMax \ + ServerAliveInterval StrictHostKeyChecking TCPKeepAlive VerifyHostKeyDNS - config_get user $1 user - config_get hostname $1 hostname - config_get port $1 port "22" - config_get retrydelay $1 retrydelay "60" - config_get PKCS11Provider $1 PKCS11Provider - config_get CheckHostIP $1 CheckHostIP - config_get Compression $1 Compression - config_get CompressionLevel $1 CompressionLevel - config_get IdentityFile $1 IdentityFile - config_get LogLevel $1 LogLevel - config_get ServerAliveCountMax $1 ServerAliveCountMax - config_get ServerAliveInterval $1 ServerAliveInterval - config_get StrictHostKeyChecking $1 StrictHostKeyChecking - config_get TCPKeepAlive $1 TCPKeepAlive - config_get VerifyHostKeyDNS $1 VerifyHostKeyDNS - - error="" - [ -n "$user" ] \ - || append_string "error" "user is not set" "; " - [ -n "$hostname" ] \ - || append_string "error" "hostname is not set" "; " - [ "$retrydelay" -ge 1 ] \ - || append_string "error" "retrydelay must be a positive integer" "; " - [ -z "$PKCS11Provider" -o -f "$PKCS11Provider" ] \ - || append_string "error" "PKCS11Provider must be a pkcs11 shared library accessible" "; " - [ -z "$CheckHostIP" -o "$CheckHostIP"="yes" -o "$CheckHostIP"="no" ] \ - || append_string "error" "CheckHostIP must be 'yes' or 'no'" "; " - [ -z "$Compression" -o "$Compression"="yes" -o "$Compression"="no" ] \ - || append_string "error" "Compression must be 'yes' or 'no'" "; " - [ -z "$CompressionLevel" ] || [ "$CompressionLevel" -ge 1 -a "$CompressionLevel" -le 9 ] \ - || append_string "error" "CompressionLevel must be between 1 and 9" "; " - [ -z "$IdentityFile" -o -f "$IdentityFile" ] \ - || append_string "error" "IdentityFile $IdentityFile not accessible" "; " - [ -z "$LogLevel" -o "$LogLevel" = "QUIET" -o "$LogLevel" = "FATAL" -o "$LogLevel" = "ERROR" -o \ - "$LogLevel" = "INFO" -o "$LogLevel" = "VERBOSE" -o "$LogLevel" = "DEBUG" -o \ - "$LogLevel" = "DEBUG1" -o "$LogLevel" = "DEBUG2" -o "$LogLevel" = "DEBUG3" ] \ - || append_string "error" "LogLevel is invalid" "; " - [ -z "$ServerAliveCountMax" ] || [ "$ServerAliveCountMax" -ge 1 ] \ - || append_string "error" "ServerAliveCountMax must be greater or equal than 1" "; " - [ -z "$ServerAliveInterval" ] || [ "$ServerAliveInterval" -ge 0 ] \ - || append_string "error" "ServerAliveInterval must be greater or equal than 0" "; " - [ -z "$StrictHostKeyChecking" -o "$StrictHostKeyChecking" = "yes" -o "$StrictHostKeyChecking" = "ask" -o "$StrictHostKeyChecking" = "no" ] \ - || append_string "error" "StrictHostKeyChecking must be 'yes', 'ask' or 'no'" "; " - [ -z "$TCPKeepAlive" -o "$TCPKeepAlive" = "yes" -o "$TCPKeepAlive" = "no" ] \ - || append_string "error" "TCPKeepAlive must be 'yes' or 'no'" "; " - [ -z "$VerifyHostKeyDNS" -o "$VerifyHostKeyDNS" = "yes" -o "$VerifyHostKeyDNS" = "no" ] \ - || append_string "error" "VerifyHostKeyDNS must be 'yes' or 'no'" "; " - - [ -n "$error" ] && { logger -p user.err -t "sshtunnel" "tunnels to $server not started - $error"; return; } - + validate_server_section "$server" || { _err "server ${server}: validation failed"; return 1; } ARGS="" ARGS_options="" - ARGS_tunnels="" - + ARGS_tunnels="" count=0 - config_foreach load_tunnelR tunnelR && config_foreach load_tunnelL tunnelL && config_foreach load_tunnelD tunnelD - [ -n "$error" ] && { logger -p user.err -t "sshtunnel" "tunnels to $server not started - $error"; return; } - [ "$count" -eq 0 ] && { logger -p user.err -t "sshtunnel" "tunnels to $server not started - no tunnels defined"; return; } - append_params CheckHostIP Compression CompressionLevel IdentityFile LogLevel PKCS11Provider ServerAliveCountMax ServerAliveInterval StrictHostKeyChecking TCPKeepAlive VerifyHostKeyDNS + config_foreach load_tunnelR "tunnelR" + config_foreach load_tunnelL "tunnelL" + config_foreach load_tunnelD "tunnelD" + config_foreach load_tunnelW "tunnelW" + [ "$count" -eq 0 ] && { _err "tunnels to ${server} not started - no tunnels defined"; return 1; } + + append_params CheckHostIP Compression CompressionLevel IdentityFile \ + LogLevel PKCS11Provider ServerAliveCountMax ServerAliveInterval \ + StrictHostKeyChecking TCPKeepAlive VerifyHostKeyDNS + ARGS="$ARGS_options -o ExitOnForwardFailure=yes -o BatchMode=yes -nN $ARGS_tunnels -p $port $user@$hostname" - /usr/bin/sshtunnel.sh "$ARGS" "$retrydelay" "$server" & - echo $! >> "${PIDFILE}.pids" - logger -p user.info -t "sshtunnel" "started tunnels to $server (pid=$!;retrydelay=$retrydelay)" -} - -stop() { - if [ -f "$PIDFILE".pids ] - then - logger -p user.info -t "sshtunnel" "stopping all tunnels" - - while read pid - do - kill "$pid" # kill mother process first - - [ -f "${PIDFILE}_${pid}.pid" ] && { # if ssh was running, kill it also (mother process could be in retry wait) - start-stop-daemon -K -p "${PIDFILE}_${pid}.pid" - rm "${PIDFILE}_${pid}.pid" - } - - logger -p daemon.info -t "sshtunnel[$pid]" "tunnel stopped" - - done < "${PIDFILE}.pids" - - rm "${PIDFILE}.pids" - - logger -p user.info -t "sshtunnel" "all tunnels stopped" - else - logger -p user.info -t "sshtunnel" "no tunnels running" - fi -} - -start() { - [ -f "${PIDFILE}.pids" ] && stop - - config_load sshtunnel - if [ -n "$(uci show sshtunnel.@server[0])" ] # at least one server section exists - then - logger -p user.info -t "sshtunnel" "starting all tunnels" - config_foreach load_server server - logger -p user.info -t "sshtunnel" "all tunnels started" - else - logger -p user.info -t "sshtunnel" "no servers defined" - fi + procd_open_instance "$server" + procd_set_param command "$PROG" $ARGS + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_set_param respawn 0 "$retrydelay" 1 + procd_close_instance +} + +start_service() { + config_load "sshtunnel" + config_foreach load_server "server" +} + +service_triggers() { + procd_add_reload_trigger "sshtunnel" + + procd_open_validate + validate_server_section + validate_tunnelR_section + validate_tunnelL_section + validate_tunnelD_section + validate_tunnelW_section + procd_close_validate } diff --git a/net/sshtunnel/files/sshtunnel.sh b/net/sshtunnel/files/sshtunnel.sh deleted file mode 100644 index 0ff18a6a29..0000000000 --- a/net/sshtunnel/files/sshtunnel.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -PIDFILE="/tmp/run/sshtunnel" - -args="$1" -retrydelay="$2" -server="$3" - -while true -do - logger -p daemon.info -t "sshtunnel[$$][$server]" "connection started" - - start-stop-daemon -S -p "${PIDFILE}_${$}.pid" -mx ssh -- $args &>/tmp/log/sshtunnel_$$ - - logger -p daemon.err -t "sshtunnel[$$][$server]" < /tmp/log/sshtunnel_$$ - rm /tmp/log/sshtunnel_$$ - logger -p daemon.info -t "sshtunnel[$$][$server]" "ssh exited with code $?, retrying in $retrydelay seconds" - rm "${PIDFILE}_${$}.pid" - - sleep "$retrydelay" & wait -done -- 2.30.2