--- /dev/null
+#! /bin/sh
+
+### BEGIN INIT INFO
+# Provides: tunnel
+# Required-Start: $remote_fs $syslog
+# Required-Stop: $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Outbound SSH tunnels
+# Description: This script starts or stops the outbound SSH tunnels
+# maintained by the `tunnel' user.
+### END INIT INFO
+
+# Author: Mark Wooding <mdw@distorted.org.uk>
+
+## Initial configuration.
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="outbound SSH tunnels"
+TUNUSER=tunnel
+RUN=/var/run/$TUNUSER
+if [ -f /etc/default/tunnel ]; then . /etc/default/tunnel; fi
+: ${TUNHOME=$(getent passwd "$TUNUSER" | cut -d: -f6)}
+: ${TUNGROUP=$(id -g "$TUNUSER")}
+if [ ! -x "$TUNHOME/bin/outbound" ]; then exit 0; fi
+: ${tunnels=$(sed -n \
+ '/^Host[[:space:]]\+\([^[:space:]*]\|[^[:space:]].*[^[:space:]]\)[[:space:]]*$/s//\1/p' \
+ "$TUNHOME/.ssh/config")}
+
+## Scan the command-line.
+case "$#" in
+ 0) op=none ;;
+ 1) op=$1; shift; set -- $tunnels ;;
+ *) op=$1; shift ;;
+esac
+
+## Make sure that the runtime state directory exists. If not, create it with
+## sensible permissions. Don't override permissions if it already exists,
+## because presumably the administrator has fiddled them deliberately.
+if [ ! -d "$RUN" ]; then
+ mkdir -m755 "$RUN"
+ chown "$TUNUSER:$TUNGROUP" "$RUN"
+fi
+cd "$RUN"
+
+## Utility to run the per-host script.
+run_outbound () { sudo -u"$TUNUSER" "$TUNHOME/bin/outbound" "$@"; }
+
+## Utilities for doing things to individual hosts.
+start () { run_outbound start "$1"; }
+stop () { run_outbound stop "$1"; }
+restart () { stop "$1"; start "$1"; }
+
+## Higher-order iterator to process a list of hosts.
+foreach () {
+ whatting=$1 what=$2; shift 2
+ echo -n "$whatting $DESC:"
+ for i in "$@"; do
+ $what "$i"
+ echo -n " $i"
+ done
+ echo "."
+}
+
+## Main dispatch.
+case $op in
+ start) foreach "Starting" start "$@" ;;
+ stop) foreach "Stopping" stop "$@" ;;
+ restart | force-reload) foreach "Restarting" restart "$@" ;;
+ status)
+ for i in "$@"; do
+ echo -n "$i: "
+ run_outbound status "$i"
+ done
+ ;;
+ *)
+ echo >&2 "usage: $0 {start|stop|restart|status} [HOST ...]"
+ exit 1
+ ;;
+esac
--- /dev/null
+#! /bin/sh
+
+set -e
+case $# in
+ 2) ;;
+ *) echo >&2 "usage: $0 {start|stop|restart|status} HOST"; exit 1 ;;
+esac
+op=$1 host=$2
+
+writefile () {
+ file=$1; shift
+ echo "$*" >"$file.new"
+ mv "$file.new" "$file"
+}
+
+runssh () { ssh -T -oControlPath="./$host.ctrl" "$@"; }
+
+stopit () {
+
+ ## Initial shutdown protocol.
+ writefile "$host.state" stopping
+ if [ -f "$host.pid" ]; then kill $(cat "$host.pid") 2>/dev/null || :; fi
+ rm -f "$host.pid"
+
+ ## Shut down an existing connection if there is one.
+ if [ -S "$host.ctrl" ]; then
+ runssh -Oexit "$host" >/dev/null 2>/dev/null || :
+ fi
+
+ ## If there's still a socket, then work out what to do.
+ if [ -e "$host.ctrl" ]; then
+
+ ## If the connection's still running then we have a problem.
+ if runssh -Ocheck "$host" >/dev/null 2>/dev/null; then
+ echo >&2 "$0: failed to kill existing connection to $host"
+ exit 2
+ fi
+
+ ## Remove the stale socket.
+ rm -f "$host.ctrl"
+ fi
+
+ ## Update the state.
+ rm -f "$host.state" "$host.pid"
+}
+
+daemon () {
+
+ ## There doesn't seem to be a better way of getting this. :-(
+ read pid <"$host.daemonpipe"
+ rm -f "$host.daemonpipe"
+
+ ## Set up shop.
+ trap 'rm -f "$host.pid"; stopit' EXIT INT TERM
+ writefile "$host.pid" "$pid"
+
+ ## Initial delay.
+ delay=0
+
+ ## Keep the connection up for as long as we can.
+ while [ -f "$host.pid" ]; do
+
+ ## Maybe back off before trying another connection.
+ case $delay in
+ 0)
+ delay=1
+ ;;
+ *)
+ writefile "$host.state" \
+ "wait until $(date -d+${delay}sec +"%Y-%m-%d %H:%M:%S %z")"
+ sleep $delay
+ delay=$(( 2*$delay ))
+ if [ $delay -gt 120 ]; then delay=120; fi
+ ;;
+ esac
+
+ ## Start a new connection.
+ writefile "$host.state" starting
+ if ! runssh -MNnf "$host" >/dev/null; then continue; fi
+ if ! runssh -Ocheck "$host" >/dev/null 2>&1; then
+ echo "connection to $host apparently stillborn"
+ continue
+ fi
+ writefile "$host.state" connected
+ delay=0
+
+ ## Wait until it gets torn down. The chicanery with a pipe is because
+ ## the ssh process will continue until either it gets disconnected from
+ ## the server or stdin closes -- so we have to arrange that stdin doesn't
+ ## close. Thanks to Richard Kettlewell for the suggestion.
+ rm -f "$host.pipe"; mkfifo -m400 "$host.pipe"
+ runssh -N "$host" >/dev/null <"$host.pipe" || :
+ rm -f "$host.pipe"
+ writefile "$host.state" disconnected
+ done
+}
+
+startit () {
+
+ ## If there's already a connection then we have nothing to do.
+ if runssh -Ocheck "$host" >/dev/null 2>/dev/null; then
+ echo >&2 "$0: already connected to $host"
+ exit 0
+ fi
+
+ ## Start a daemon which makes connections for us. This is remarkably
+ ## tricky.
+ rm -f "$host.daemonpipe"; mkfifo -m600 "$host.daemonpipe"
+ { daemon& echo $! >"$host.daemonpipe"; } 2>&1 | logger -pdaemon.notice&
+}
+
+## Main dispatch.
+case "$op" in
+ start) startit ;;
+ stop) stopit ;;
+ restart) stopit; startit ;;
+ status)
+ if [ -f "$host.state" ]
+ then cat "$host.state"
+ else echo "down"
+ fi
+ ;;
+ *)
+ echo >&2 "usage: $0 {start|stop|restart|status} HOST"
+ exit 1
+ ;;
+esac