chiark / gitweb /
bin/outbound: Change how we wait for SSH tunnels to end.
[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 clobber () {
19
20   ## Shut down an existing connection if there is one.
21   if [ -S "$host.ctrl" ]; then
22     runssh -Oexit "$host" >/dev/null 2>/dev/null || :
23   fi
24
25   ## If there's still a socket, then work out what to do.
26   if [ -e "$host.ctrl" ]; then
27
28     ## If the connection's still running then we have a problem.
29     if runssh -Ocheck "$host" >/dev/null 2>/dev/null; then
30       echo >&2 "$0: failed to kill existing connection to $host"
31       exit 2
32     fi
33
34     ## Remove the stale socket.
35     rm -f "$host.ctrl"
36   fi
37 }
38
39 stopit () {
40
41   ## Initial shutdown protocol.
42   writefile "$host.state" stopping
43   if [ -f "$host.pid" ]; then kill $(cat "$host.pid") 2>/dev/null || :; fi
44   rm -f "$host.pid"
45
46   ## Clobber the existing connection, if there is one.
47   clobber
48
49   ## Update the state.
50   rm -f "$host.state" "$host.pid"
51 }
52
53 daemon () {
54
55   ## There doesn't seem to be a better way of getting this. :-(
56   read pid <"$host.daemonpipe"
57   rm -f "$host.daemonpipe"
58
59   ## Set up shop.
60   trap 'rm -f "$host.pid"; stopit' EXIT INT TERM
61   writefile "$host.pid" "$pid"
62
63   ## Initial delay.
64   delay=0
65
66   ## Not waiting on a pipe yet.
67   kidcat=nil
68
69   ## Keep the connection up for as long as we can.
70   while [ -f "$host.pid" ]; do
71
72     ## Maybe back off before trying another connection.
73     case $delay in
74       0)
75         delay=1
76         ;;
77       *)
78         writefile "$host.state" \
79           "wait until $(date -d+${delay}sec +"%Y-%m-%d %H:%M:%S %z")"
80         sleep $delay
81         delay=$(( 2*$delay ))
82         if [ $delay -gt 120 ]; then delay=120; fi
83         ;;
84     esac
85
86     ## Prepare a pipe so that we can wait for SSH to finish.  This is a
87     ## rotten hack.
88     case $kidcat in
89       nil) ;;
90       *) kill $kidcat >/dev/null 2>&1 || :; kidcat=nil ;;
91     esac
92     rm -f "$host.pipe"; mkfifo -m600 "$host.pipe"
93     cat $host.pipe >/dev/null& kidcat=$!
94
95     ## Start a new connection.
96     writefile "$host.state" starting
97     if ! runssh -MNnf "$host" >"$host.pipe"; then continue; fi
98     if ! runssh -Ocheck "$host" >/dev/null 2>&1; then
99       echo "connection to $host apparently stillborn"
100       continue
101     fi
102     writefile "$host.state" connected
103     delay=0
104
105     ## Wait until it gets torn down.
106     wait $kidcat >/dev/null 2>&1 || :
107     rm -f "$host.pipe"
108     clobber
109     writefile "$host.state" disconnected
110   done
111 }
112
113 startit () {
114
115   ## If there's already a connection then we have nothing to do.
116   if runssh -Ocheck "$host" >/dev/null 2>/dev/null; then
117     echo >&2 "$0: already connected to $host"
118     exit 0
119   fi
120
121   ## Start a daemon which makes connections for us.  This is remarkably
122   ## tricky.
123   rm -f "$host.daemonpipe"; mkfifo -m600 "$host.daemonpipe"
124   { daemon& echo $! >"$host.daemonpipe"; } 2>&1 | logger -pdaemon.notice&
125 }
126
127 ## Main dispatch.
128 case "$op" in
129   start) startit ;;
130   stop) stopit ;;
131   restart) stopit; startit ;;
132   status)
133     if [ -f "$host.state" ]
134     then cat "$host.state"
135     else echo "down"
136     fi
137     ;;
138   *)
139     echo >&2 "usage: $0 {start|stop|restart|status} HOST"
140     exit 1
141     ;;
142 esac