Initial version.
authorMark Wooding <mdw@distorted.org.uk>
Thu, 23 Apr 2015 11:48:03 +0000 (12:48 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Thu, 23 Apr 2015 20:02:29 +0000 (21:02 +0100)
.ssh/.gitignore [new file with mode: 0644]
.ssh/Makefile [new file with mode: 0644]
.ssh/keys/.gitignore [new file with mode: 0644]
.ssh/sshsvc.conf [new file with mode: 0644]
bin/init [new file with mode: 0755]
bin/outbound [new file with mode: 0755]

diff --git a/.ssh/.gitignore b/.ssh/.gitignore
new file mode 100644 (file)
index 0000000..32fb549
--- /dev/null
@@ -0,0 +1,4 @@
+authorized_keys
+id_*
+known_hosts
+config
diff --git a/.ssh/Makefile b/.ssh/Makefile
new file mode 100644 (file)
index 0000000..be15e1b
--- /dev/null
@@ -0,0 +1,3 @@
+### -*-makefile-*-
+authorized_keys: sshsvc.conf keys/ $(wildcard keys/*)
+       sshsvc-mkauthkeys
diff --git a/.ssh/keys/.gitignore b/.ssh/keys/.gitignore
new file mode 100644 (file)
index 0000000..2fa7496
--- /dev/null
@@ -0,0 +1 @@
+*.pub
diff --git a/.ssh/sshsvc.conf b/.ssh/sshsvc.conf
new file mode 100644 (file)
index 0000000..266ac50
--- /dev/null
@@ -0,0 +1,12 @@
+### -*-sh-*-
+
+allow_port_forwarding=yes
+cmd=/bin/false
+
+make_key_line () {
+  user=$1
+  addrs=$(echo "$user" | sed "y/%,/: /")
+  line=""
+  for a in $addrs; do line="${line:+$line,}permitopen=\"$a\""; done
+  echo "$line"
+}
diff --git a/bin/init b/bin/init
new file mode 100755 (executable)
index 0000000..86e8538
--- /dev/null
+++ b/bin/init
@@ -0,0 +1,79 @@
+#! /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
diff --git a/bin/outbound b/bin/outbound
new file mode 100755 (executable)
index 0000000..cad1526
--- /dev/null
@@ -0,0 +1,127 @@
+#! /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