From b16ea8ba5d2a908164c99c4761915e5e439e7107 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Thu, 23 Apr 2015 12:48:03 +0100 Subject: [PATCH 1/1] Initial version. Organization: Straylight/Edgeware From: Mark Wooding --- .ssh/.gitignore | 4 ++ .ssh/Makefile | 3 + .ssh/keys/.gitignore | 1 + .ssh/sshsvc.conf | 12 ++++ bin/init | 79 +++++++++++++++++++++++++++ bin/outbound | 127 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 226 insertions(+) create mode 100644 .ssh/.gitignore create mode 100644 .ssh/Makefile create mode 100644 .ssh/keys/.gitignore create mode 100644 .ssh/sshsvc.conf create mode 100755 bin/init create mode 100755 bin/outbound diff --git a/.ssh/.gitignore b/.ssh/.gitignore new file mode 100644 index 0000000..32fb549 --- /dev/null +++ b/.ssh/.gitignore @@ -0,0 +1,4 @@ +authorized_keys +id_* +known_hosts +config diff --git a/.ssh/Makefile b/.ssh/Makefile new file mode 100644 index 0000000..be15e1b --- /dev/null +++ b/.ssh/Makefile @@ -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 index 0000000..2fa7496 --- /dev/null +++ b/.ssh/keys/.gitignore @@ -0,0 +1 @@ +*.pub diff --git a/.ssh/sshsvc.conf b/.ssh/sshsvc.conf new file mode 100644 index 0000000..266ac50 --- /dev/null +++ b/.ssh/sshsvc.conf @@ -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 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 + +## 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 index 0000000..cad1526 --- /dev/null +++ b/bin/outbound @@ -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 -- [mdw]