diff options
| author | Clément Zrounba <6691770+clement-z@users.noreply.github.com> | 2021-01-16 17:43:23 +0100 |
|---|---|---|
| committer | Clément Zrounba <6691770+clement-z@users.noreply.github.com> | 2021-01-16 17:43:23 +0100 |
| commit | a84e279a0b4dea4fe074dd6ff3f87997ce242e5f (patch) | |
| tree | 5380207e8eb5751e7fbf6508f27eb2f24e136ec8 | |
| parent | 70243e9868b1a8819e3762eb49a05c8ac69ee68c (diff) | |
| download | proxy-tcp-ssh-a84e279a0b4dea4fe074dd6ff3f87997ce242e5f.tar.gz proxy-tcp-ssh-a84e279a0b4dea4fe074dd6ff3f87997ce242e5f.zip | |
Full refactor (too bad for the clean history :))
| -rwxr-xr-x | main.sh | 245 |
1 files changed, 183 insertions, 62 deletions
@@ -1,76 +1,192 @@ #!/bin/bash function usage() { - echo "SYNOPSYS - $0 [OPTIONS] + echo \ +"SYNOPSYS + $0 (-S|--ssh-host) SSHHOST (-t|--tunnel) TUNNELSPEC [OPTIONS] DESCRIPTION Spawn TCP tunnels between your computer and a remote SSH server. If the connection fail for some reason, the script will try to re-create the tunnels automatically every TIMEOUT seconds. OPTIONS - -S,--server=HOST - Set the tunnel host. HOST can be either the IP or hostname of the - server to tunnel to, as seen from the ssh host. - - -t, --tunnel=TUNNELPORT - Specify the ports to tunnel. - - -i, --interface=IF - Specify the interface to listen on. - - -H,--host=SSHHOST + -S,--ssh-host SSHHOST Set the ssh host. SSHHOST can be either the full hostname as you would - specify it on the ssh command-line (e.g. user@example.com or hostname + specify it on the ssh command-line (e.g. [user@]myserver.com or hostname if it is set up in your ssh config). - -p, --port=SSHPORT + -t,--tunnel TUNNELSPEC + Specify the ports to tunnel as a comma-separated list of either single + ports or port pairs (e.g. -t 443,80:8080 will establish tunnels + local:443-remote:443 and local:8080-remote:80). Note that you have to + be a privileged user (i.e. a user with the CAP_NET_BIND_SERVICE + capability) to be able to bind to ports below 1024. + + -p,--ssh-port SSHPORT /!\\ Not implemented Set the ssh remote port if different from the default. + -H,--host HOST + Set the tunnel host. HOST can be either the IP or hostname of the + server to tunnel to, as seen from the ssh host. Defaults to localhost. + + -i,--interface IF /!\\ Not implemented + Specify the local interface to bind to. Defaults to localhost. Setting + it to 0.0.0.0 will make the tunnel available to other clients on your + network (depending on your firewall rules). + -f Fork the script to the background. -h,--help Print this help and exit. +EXAMPLES + Todo +AUTHOR + Clément Zrounba " } -# Help argument -if [[ " $@" = *" -h"* || " $@" = *" --help"* ]]; then +function parse_args() { + ARG_POS_PARAMS="" + while (( "$#" )); do + case "$1" in + -f|--fork) + ARG_FORK=0 + shift + ;; + -h|--help) + ARG_HELP=0 + shift + ;; + -H|--host) + if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then + ARG_HOST=$2 + shift 2; + else + echo "Error: Argument for $1 is missing" >&2 + exit 1 + fi + ;; + -S|--ssh-host) + if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then + ARG_SSHHOST=$2 + shift 2; + else + echo "Error: Argument for $1 is missing" >&2 + exit 1 + fi + ;; + -S*) + if [ -n "${1:2}" ]; then + ARG_SSHHOST=${1:2} + shift 1; + else + echo "Error: Option not understood: $1" >&2 + exit 1 + fi + ;; + --ssh-host=*) + if [ -n "${1:2}" ]; then + ARG_SSHHOST=${1:11} + shift 1; + else + echo "Error: Option not understood: $1" >&2 + exit 1 + fi + ;; + -t|--tunnel) + if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then + ARG_TUNNELSPEC=$2 + shift 2; + else + echo "Error: Argument for $1 is missing" >&2 + exit 1 + fi + ;; + -*) + echo "Error: Unsupported flag $1" >&2 + exit 1 + ;; + *) # preserve positional arguments + ARG_POS_PARAMS="$ARG_POS_PARAMS $1" + shift + ;; + esac + done + + # Check SSHHOST is given + if [ -z "$ARG_SSHHOST" ]; then + echo "Error: no ssh host specified" >&2 + exit 1 + fi + + # Check TUNNELSPEC is given + if [ -z "$ARG_TUNNELSPEC" ]; then + echo "Error: no tunnel(s) specified" >&2 + exit 1 + fi +} + +function check_low_port_cap() +{ + [ $(id -u) == 0 ] && echo "yes" && return + # Check capability as well ? + + echo "no" +} + +function parse_tunnel_spec() { + local tunnel_spec=(${1//,/ }) + local can_access_low_local_ports=$(check_low_port_cap) + + declare -ag _TUNNEL_PORTS_LOCAL + declare -ag _TUNNEL_PORTS_REMOTE + + for tunnel_spec_entry in ${tunnel_spec[*]}; do + local ports=(${tunnel_spec_entry/:/ }) + + local local_port=${ports[0]} + local remote_port=${ports[1]:-$local_port} + + echo "Local endpoint $BIND_IF:$local_port will be tunnelled to remote endpoint $TUNNEL_HOST:$remote_port" + + if [ $local_port -lt 1024 ] && [ $can_access_low_local_ports == "no" ]; then + echo "Error: local port below 1024 specified but running as an unprivileged user" >&2 + exit 1 + fi + + _TUNNEL_PORTS_LOCAL+=($local_port) + _TUNNEL_PORTS_REMOTE+=($remote_port) + done +} + +# Parse command line arguments into ARG_* variables +parse_args $@ + +# Make positional parameters accessible through $1, $2, ... +eval set -- "$ARG_POS_PARAMS" + +# Print help and exit if requested +if [ 0 == "$ARG_HELP" ]; then usage exit 0 fi -#if [[ -z "$@" || " $@" != *" --ok"* ]]; then -# echo 'This script will only do something if `--ok` is provided' -# echo '' -# usage -# echo '' -# echo 'Nothing has been done. Exiting.' -# exit 1 -#fi +# Assign values or defaults to other parameters from ARG_* variables +SSH_HOST=${ARG_SSHHOST:-} +BIND_IF=${ARG_BIND_IF:-localhost} +TUNNEL_HOST=${ARG_HOST:-localhost} +SSH_PORT=${ARG_SSHPORT:-22} +DO_FORK=$([ "$ARG_FORK" == "0" ] && echo "yes" || echo "no") + +# Parse tunnel args into _TUNNEL_PORTS_* variables +parse_tunnel_spec $ARG_TUNNELSPEC +TUNNEL_PORTS_LOCAL=(${_TUNNEL_PORTS_LOCAL[*]}) +TUNNEL_PORTS_REMOTE=(${_TUNNEL_PORTS_REMOTE[*]}) # Logfile location # comment to use stdout/stderr -LOGFILE=proxy.log -LOGFILE_ERR=proxy.log - -# Local bind address -# - Use 127.0.0.1 for local computer only; -# - Use 0.0.0.0 to give access to other computers (but be careful who can -# reach your computer in that case, if you do not have a firewall, your LAN -# (at least) normally has access to all your ports (most routers, through -# NAT, provide basic firewall functionality)) -#LOCAL_BIND_ADDR=0.0.0.0 -LOCAL_BIND_ADDR=127.0.0.1 - -# License host and ports -REMOTE_LICENSE_HOST=licence.inl90.ec-lyon.fr -DASHBOARD_PORT=8095 -FLEX_PORT=27011 -LUMERICAL_VENDOR_PORT=42128 - -# ssh host -HOST=sshgate +#LOGFILE=proxy.log +#LOGFILE_ERR=proxy.log # set default log to stdout/stderr LOGFILE=${LOGFILE:-/dev/stdout} @@ -79,32 +195,37 @@ LOGFILE_ERR=${LOGFILE_ERR:-/dev/stderr} function start_and_monitor_tunnels() { # This function takes port numbers as arguments and creates as many TCP # tunnels like so: - # LOCAL_BIND_ADDR:$port <--> HOST <--> REMOTE_LICENSE_HOST:$port + # BIND_IF:$local_port <--> SSH_HOST <--> TUNNEL_HOST:$remote_port # If the tunnel fails or ssh exits, we either: # - try again after a timeout if exit code is not 0 # - exit if exit code is 0 # NOTE: for now we just **always** try again after timeout - local ports=$@ - local timeout_sec=10 + local timeout_sec=${1:-10} + + local ntunnels=${#TUNNEL_PORTS_LOCAL[*]} declare -a ssh_tunnel_opt - for port in ${ports}; do + for i in `seq 0 $(($ntunnels - 1))`; do + local local_port=${TUNNEL_PORTS_LOCAL[$i]} + local remote_port=${TUNNEL_PORTS_REMOTE[$i]} + ssh_tunnel_opt+=("-L") - ssh_tunnel_opt+=("${LOCAL_BIND_ADDR}:${port}:${REMOTE_LICENSE_HOST}:${port}") + ssh_tunnel_opt+=("${BIND_IF}:${local_port}:${TUNNEL_HOST}:${remote_port}") done while true; do # Try establishing the tunnel - echo "Starting tunnel for ports ${ports[@]}" + echo -n "Starting tunnel... " ssh -N \ - ${ssh_tunnel_opt[@]} \ + ${ssh_tunnel_opt[*]} \ -o "ServerAliveInterval 10" \ -o "ServerAliveCountMax 3" \ -o "ExitOnForwardFailure yes" \ - ${HOST} & + ${SSH_HOST} & local pid="$!" # make sure to kill ssh when killed/parent exits trap "kill '$pid'" EXIT + echo "OK" wait local exit_code="$?" @@ -122,11 +243,13 @@ function start_and_monitor_tunnels() { } # Start the tunnels and redirect outputs -echo "Setting up tunnels. -Logs will be redirected to: - stdout --> ${LOGFILE} - stderr --> ${LOGFILE_ERR} -" +if [ ${LOGFILE} != "/dev/stdout" ] || [ ${LOGFILE_ERR} != "/dev/stderr" ]; then + echo "\ + Logs will be redirected to: + stdout --> ${LOGFILE} + stderr --> ${LOGFILE_ERR} + " +fi # In the following block, the following redirections are set up: # - 777 goes to stdout of current script (and not global /dev/stdout) @@ -134,7 +257,7 @@ Logs will be redirected to: # - 2 goes to $LOGFILE_ERR { # Enable trace - set -x + #set -x # Set up CTRL-C callback (write to stdout of script, not log) trap_ctrl_c_callback=" @@ -143,7 +266,5 @@ Logs will be redirected to: " trap "${trap_ctrl_c_callback}" SIGINT - ports=(${DASHBOARD_PORT} ${FLEX_PORT} ${LUMERICAL_VENDOR_PORT}) - start_and_monitor_tunnels ${ports[@]} - + start_and_monitor_tunnels } 777>&1 1>>${LOGFILE} 2>>${LOGFILE_ERR} |
