Differences From
Artifact [0b831e354e]:
1 1 #!/usr/bin/env bash
2 2 # [ʞ] vpn
3 3 # ~ lexi hale <lexi@hale.su>
4 4 # ® affero general public license
5 -# $ vpn (join | part | info | clean | key) <name>
5 +# $ vpn (join | part | info | clean | key | pid) <name>
6 6 # : vim:ft=bash
7 7
8 8 # vpn is a simple wrapper around openvpn to make it suitable
9 9 # for everyday use. it takes two arguments: the vpn, and
10 10 # what to do with it.
11 11 # - vpn join <vpn>: opens a connection to vpn <name>
12 12 # - vpn part <vpn>: closes an active connection to <name>
13 13 # - vpn info <vpn>: reports the status of of a vpn
14 14 # - vpn key <host>: automatically provisions the client
15 15 # with RSA keys from <host> and creates
16 16 # a configuration file if one does not
17 17 # already exist
18 18 # - vpn part <vpn>: closes an active connection to <vpn>
19 +# - vpn pid <vpn>: print the pid of an active connection
19 20 # - vpn help: display this text
20 21 #
21 22 # a number of environment variables affect the behavior of
22 23 # vpn. these are listed in the source with their defaults
23 24 # and an explantion of their function. if you do not wish
24 25 # users to be able to change the behavior of vpn with
25 26 # setenv, you must change the param invokation to a simple
................................................................................
34 35 # pivot to the account that invoked the script after it is
35 36 # done with tasks that require privileged access.
36 37 #
37 38 # (it should go without saying, but ensure you understand
38 39 # the security implications before editing sudoers on a
39 40 # multiuser machine or one that is directly exposed to the
40 41 # internet.)
42 +#
43 +# this script is designed to automatically handle
44 +# certificate-based authentication. if there is a file named
45 +# "ca.crt" in the config folder for a script, the script
46 +# will look for files named $vpn_cn.crt and $vpn_cn.key,
47 +# which are automatically passed to openvpn (you don't need
48 +# to name them in the config file; this feature is designed
49 +# to enable the syncing of config files across multiple
50 +# devices each with their own certificate for the host. it
51 +# can also automatically download the necessary certificate
52 +# files from the server using ssh (scp); to enable this
53 +# feature, $vpn_srv_keydir must be set to the location of
54 +# the "pki" directory on the server. users may then invoke
55 +# `vpn key $server-url` to create a default configuration
56 +# and provision the device. $vpn_cn should probably be
57 +# set to your hostname (the default) or a device-unique
58 +# identifier issued by your organization.
59 +
60 +# TODO set up `key` mode so that it is able to accept
61 +# paths from the command line as well as in the
62 +# $vpn_srv_keydir environment variable.
41 63
42 64 _text_=$LINENO
43 65
44 66 param(){ eval $1=\${$1:-$2}; }
45 67
46 68 param vpn_basedir ~/opt/vpn
47 69 # the directory in which vpn's logfiles are stored,
................................................................................
72 94
73 95 param USER $(whoami)
74 96 # the user who should own all files and processes
75 97 # created and destroyed by vpn
76 98
77 99 param vpn_pidbox $TMPDIR/pid.$USER
78 100 # a directory for storing pids in. this should be chmod
79 - # 700 and owned by the user invoking vpn, ideally
101 + # 700 and owned by the user invoking vpn, ideally on a
102 + # tmpfs
80 103
81 104 param vpn_bin openvpn
82 105 # the binary to use. if openvpn is not in your path,
83 106 # enter its absolute path here
84 107
85 108 param vpn_script $0
86 109 # a path to the executable
................................................................................
87 110
88 111 param vpn_scrname $(basename $vpn_script)
89 112 # the name of the executable
90 113
91 114 # thus ends the admin-configurable portion of this script.
92 115 # abandon all hope, ye who enter here
93 116
94 -err(){ echo -e "\e[1;31merror:\e[m" $* ; exit 1; }
117 +err(){ echo -e "\e[1;31merror:\e[m" $* >&2; exit 1; }
118 +warn(){ echo -e "\e[1;33mwarn:\e[m " $* >&2; }
95 119 assert(){ msg=$1; shift; test ! $@ && err $msg; }
96 120
97 121 test "$1" == help || assert "incorrect number of arguments" $# -eq 2
98 122
99 123 act=$1; target=$2
100 124 vpnd=$vpn_confdir/$target
101 125 conf=$vpnd/conf
................................................................................
142 166 goodpid && err "\e[1m$target\e[m is already up!"
143 167
144 168 # make sure a private pid directory exists
145 169 test ! -e $vpn_pidbox &&
146 170 mkdir -p $vpn_pidbox
147 171 chmod 700 $vpn_pidbox
148 172
173 + # make sure a private base directory exists
174 + test ! -e $vpn_basedir && {
175 + mkdir -p $vpn_basedir && {
176 + chmod 700 $USER $vpn_pidbox
177 + } || {
178 + err '$vpn_basedir is not set or points to a nonexistent directory you cannot create.'
179 + }
180 + }
181 +
182 + test -e $vpn_global || err "global configuration file $vpn_global does not exist or is not in $vpn_basedir"
183 +
149 184 # check and see if we're using automatic
150 185 # host certificates; tell openvpn if so
151 186 test -e $vpnd/ca.crt && {
152 187 hostcert=$vpnd/$vpn_cn
153 188 cmd=(
154 189 --askpass
155 190 --ca $vpnd/ca.crt
................................................................................
168 203
169 204 # kill an existing connection
170 205 ( part | down | disc* | stop | p | d )
171 206 test -e $pidfile && {
172 207 goodpid && {
173 208 kill $(cat $pidfile) && rm -f $pidfile
174 209 } || {
175 - echo -e "\e[1;33mwarn:\e[m pidfile exists but does not name an openvpn process"
176 - echo " → removing pidfile for safety"
210 + warn 'pidfile exists but does not name an openvpn process'
211 + echo ' → removing pidfile for safety' >&2
177 212 rm -f $pidfile
178 213 }
179 214
180 215 } || {
181 - echo -ne "\e[1mvpn $target\e[m is not up"
216 + echo -ne "\e[1mvpn $target\e[m is not up" >&2
182 217 } ;;
183 218
184 219 # clean up dirty pidfiles
185 220 ( clean | wipe | clear | fix | tidy ) clean=1 ;&
186 221 # return profile status
187 222 ( info | stat* | detail* | i )
188 - echo -ne "\e[1mvpn $target:\e[m "
223 + echo -ne "\e[1mvpn $target:\e[m " >&2
189 224 test ! -e $pidfile && {
190 - echo -ne "\e[31mno\e[m current connection"
225 + echo -ne "\e[31mno\e[m current connection" >&2
191 226 pids=($(pidof openvpn))
192 227 let pidc=${#pids}
193 228 ((pidc > 0)) && {
194 - echo ", but $pidc vpn instances are running"
229 + echo ", but $pidc vpn instances are running" >&2
195 230 exit 2
196 231 } || { echo; exit 0; }
197 232 } || {
198 - echo -n "pidfile exists"
233 + echo -n "pidfile exists" >&2
199 234 proc=$(ps ho fname $(cat $pidfile))
200 235 (($? != 0)) && {
201 - echo ", but there are no processes with that pid"
236 + echo ", but there are no processes with that pid" >&2
202 237 stale; exit 1
203 238 } || test "$proc" == "openvpn" && {
204 - echo -e " and openvpn is \e[32mrunning\e[m"
205 - exit 0
239 + echo -e " and openvpn is \e[32mrunning\e[m" >&2
206 240 } || {
207 - echo -e " but named process is \e[31mnot a vpn instance!\e[m"
241 + echo -e " but named process is \e[31mnot a vpn instance!\e[m" >&2
208 242 stale; exit 1
209 243 }
210 244 } ;;
245 +
211 246 ( help ) head -n $(expr $_text_ - 2) $vpn_script |
212 247 tail -n $(expr $_text_ - 2); exit 255;;
213 248
214 - ( * ) err "action must be one of: join | part | info | clean" ;;
249 + ( pid ) test -e $pidfile && cat $pidfile ||
250 + err "no pidfile exists for \e[1m$target\e[m; are you sure you're connected" ;;
251 +
252 + ( * ) err "action must be one of: join | part | info | clean | key | pid | help" ;;
215 253 esac
216 254
255 +exit 0