chiark / gitweb /
Initial version.
[tunneluser] / bin / outbound
1 #! /bin/sh
2
3 set -e
4 case $# in
5   2) ;;
6   *) echo >&2 "usage: $0 {start|stop|restart|status} HOST"; exit 1 ;;
7 esac
8 op=$1 host=$2
9
10 writefile () {
11   file=$1; shift
12   echo "$*" >"$file.new"
13   mv "$file.new" "$file"
14 }
15
16 runssh () { ssh -T -oControlPath="./$host.ctrl" "$@"; }
17
18 stopit () {
19
20   ## Initial shutdown protocol.
21   writefile "$host.state" stopping
22   if [ -f "$host.pid" ]; then kill $(cat "$host.pid") 2>/dev/null || :; fi
23   rm -f "$host.pid"
24
25   ## Shut down an existing connection if there is one.
26   if [ -S "$host.ctrl" ]; then
27     runssh -Oexit "$host" >/dev/null 2>/dev/null || :
28   fi
29
30   ## If there's still a socket, then work out what to do.
31   if [ -e "$host.ctrl" ]; then
32
33     ## If the connection's still running then we have a problem.
34     if runssh -Ocheck "$host" >/dev/null 2>/dev/null; then
35       echo >&2 "$0: failed to kill existing connection to $host"
36       exit 2
37     fi
38
39     ## Remove the stale socket.
40     rm -f "$host.ctrl"
41   fi
42
43   ## Update the state.
44   rm -f "$host.state" "$host.pid"
45 }
46
47 daemon () {
48
49   ## There doesn't seem to be a better way of getting this. :-(
50   read pid <"$host.daemonpipe"
51   rm -f "$host.daemonpipe"
52
53   ## Set up shop.
54   trap 'rm -f "$host.pid"; stopit' EXIT INT TERM
55   writefile "$host.pid" "$pid"
56
57   ## Initial delay.
58   delay=0
59
60   ## Keep the connection up for as long as we can.
61   while [ -f "$host.pid" ]; do
62
63     ## Maybe back off before trying another connection.
64     case $delay in
65       0)
66         delay=1
67         ;;
68       *)
69         writefile "$host.state" \
70           "wait until $(date -d+${delay}sec +"%Y-%m-%d %H:%M:%S %z")"
71         sleep $delay
72         delay=$(( 2*$delay ))
73         if [ $delay -gt 120 ]; then delay=120; fi
74         ;;
75     esac
76
77     ## Start a new connection.
78     writefile "$host.state" starting
79     if ! runssh -MNnf "$host" >/dev/null; then continue; fi
80     if ! runssh -Ocheck "$host" >/dev/null 2>&1; then
81       echo "connection to $host apparently stillborn"
82       continue
83     fi
84     writefile "$host.state" connected
85     delay=0
86
87     ## Wait until it gets torn down.  The chicanery with a pipe is because
88     ## the ssh process will continue until either it gets disconnected from
89     ## the server or stdin closes -- so we have to arrange that stdin doesn't
90     ## close.  Thanks to Richard Kettlewell for the suggestion.
91     rm -f "$host.pipe"; mkfifo -m400 "$host.pipe"
92     runssh -N "$host" >/dev/null <"$host.pipe" || :
93     rm -f "$host.pipe"
94     writefile "$host.state" disconnected
95   done
96 }
97
98 startit () {
99
100   ## If there's already a connection then we have nothing to do.
101   if runssh -Ocheck "$host" >/dev/null 2>/dev/null; then
102     echo >&2 "$0: already connected to $host"
103     exit 0
104   fi
105
106   ## Start a daemon which makes connections for us.  This is remarkably
107   ## tricky.
108   rm -f "$host.daemonpipe"; mkfifo -m600 "$host.daemonpipe"
109   { daemon& echo $! >"$host.daemonpipe"; } 2>&1 | logger -pdaemon.notice&
110 }
111
112 ## Main dispatch.
113 case "$op" in
114   start) startit ;;
115   stop) stopit ;;
116   restart) stopit; startit ;;
117   status)
118     if [ -f "$host.state" ]
119     then cat "$host.state"
120     else echo "down"
121     fi
122     ;;
123   *)
124     echo >&2 "usage: $0 {start|stop|restart|status} HOST"
125     exit 1
126     ;;
127 esac