#!/usr/bin/env bash
_scriptname="semiautossh-tunnel"
set -u
set -e

############################################################################
# semiautossh-tunnel: Attempts to mimic autossh, for systems where that    #
#                     application is not installed.                        #
#                                                                          #
#            WARNING: If you change this, never, ever, rely on other files #
#                     from the Cathedral environment. This script is       #
#                     supposed to be able to function independently.       #
############################################################################

foreground=false
declare -i delay=5

function printusage
{
cat - >&2 <<EOF

Usage: ${_scriptname} [options] [-- <ssh arguments>]
       "--" will, in most cases, be needed to separate the arguments passed
       to ssh from the ones used internally by this script. Use it!

Options:
  -p <port>
    Specify which remote port to monitor. For this to make sense, the
    <ssh arguments> *should* include an instruction to forward that remote
    port to somewhere.
  -f
    Run in foreground. Prevents semiautossh-tunnel from dropping to the background.
    Useful for debugging purposes.
  -d
    How long to wait before checking that the background process still runs.
    By default, if "-f" is not specified, this script will start a background
    command, sleep $delay seconds, then verify it's running. This option
    changes that delay. Set to 0 to disable the check.

Example: semiautossh-tunnel -p 2222 -d 10 -- foo@bar.com -p 22 -R 2222:localhost:22
         Connects as foo, to port 22 on bar.com, forwarding remote port 2222 to
         local port 22, and also monitoring that 2222 is open on the remote host.
         Will also wait 10 seconds (-d 10) before assuming the background process
         started successfully.

EOF
}

OPTIND=1
while getopts ":fp:d:" opt; do
	case "$opt" in
		f) foreground=true ;;
		p) port=$OPTARG ;;
		d) delay=$OPTARG ;;
		[?])
			echo "Error: Unknown option -${OPTARG}" >&2
			printusage
			exit 1
		;;
		:)
			echo "Error: Option -${OPTARG} requires an argument" >&2
			printusage
			exit 1
		;;
	esac
done
shift $((OPTIND-1))
if [[ "${1:-}" = "--" ]]; then shift; fi

if [[ -n "${port:-}" ]] &&
	(
		[[ "$port" == *[!0-9]* ]] ||
		(( "${port:-0}" < 0 )) ||
		(( "${port:-0}" > 65535 ))
	); then
	echo "The remote monitor port must be an integer, (N >= 0) && (N <= 65535)" >&2
	exit 1
fi

if (( $# == 0 )); then
	printusage
	exit 0
fi

if ! $foreground; then
	nohup ${_scriptname} -f ${port:+-p $port} "--" "$@" &>/dev/null &
	pid=$!
	if (( delay > 0 )); then
		while true; do
			sleep 1
			if ! kill -0 $pid &>/dev/null; then
				echo "failed to start background process!" >&2
				echo "Check your arguments, perhaps with \"-f\"." >&2
				exit 1
			fi
			(( --delay )) || break
		done
	fi
	echo "Background process started. PID: $pid"
	disown
	exit 0
fi

function killssh
{
	if (( sshpid != 0 )) && kill -0 "$sshpid" 2>/dev/null; then
		echo "Killing SSH ($sshpid)"
        kill -SIGTERM "$sshpid" 2>/dev/null || :
    fi
	sshpid=0
}

function cleanup
{
	local code=$?
	trap : HUP INT TERM EXIT
	set +e
	killssh
	exit $code
}

echo "Script PID : $$"
while true; do
	sshpid=0
	trap cleanup HUP INT TERM EXIT
	while read -rt 60 line; do
		if (( sshpid == 0 )); then
			sshpid=${line}
			echo "SSH PID    : $sshpid"
			continue
		fi
		if [[ "$line" != *[!0-9]* ]]; then
			echo "Connection open (${line})"
		else
			echo "${line}"
		fi
	done < <(
				ssh -o "KeepAlive yes" -q -g "$@" "\
				echo 'Authenticated'; \
				function fail \
				{ \
					echo 'Forwarded port CLOSED!'; \
					echo 'Bailing!'; \
					exit 1; \
				}; \
				while true; do \
					date +%s; \
					${port:+"netstat -ntl | awk '{print \$4}' | grep -q \":${port}\$\" && echo 'Forwarded port OK' || fail; "} \
					sleep 10; \
				done\
				" &
				echo $!
				echo "Opening connection"
			)
	echo "Disconnected"
	killssh
	sleep 10 || exit 0
done