Index: vpn/vpn ================================================================== --- vpn/vpn +++ vpn/vpn @@ -1,10 +1,10 @@ #!/usr/bin/env bash # [ʞ] vpn # ~ lexi hale # ® affero general public license -# $ vpn (join | part | info | clean | key) +# $ vpn (join | part | info | clean | key | pid) # : vim:ft=bash # vpn is a simple wrapper around openvpn to make it suitable # for everyday use. it takes two arguments: the vpn, and # what to do with it. @@ -14,10 +14,11 @@ # - vpn key : automatically provisions the client # with RSA keys from and creates # a configuration file if one does not # already exist # - vpn part : closes an active connection to +# - vpn pid : print the pid of an active connection # - vpn help: display this text # # a number of environment variables affect the behavior of # vpn. these are listed in the source with their defaults # and an explantion of their function. if you do not wish @@ -36,10 +37,31 @@ # # (it should go without saying, but ensure you understand # the security implications before editing sudoers on a # multiuser machine or one that is directly exposed to the # internet.) +# +# this script is designed to automatically handle +# certificate-based authentication. if there is a file named +# "ca.crt" in the config folder for a script, the script +# will look for files named $vpn_cn.crt and $vpn_cn.key, +# which are automatically passed to openvpn (you don't need +# to name them in the config file; this feature is designed +# to enable the syncing of config files across multiple +# devices each with their own certificate for the host. it +# can also automatically download the necessary certificate +# files from the server using ssh (scp); to enable this +# feature, $vpn_srv_keydir must be set to the location of +# the "pki" directory on the server. users may then invoke +# `vpn key $server-url` to create a default configuration +# and provision the device. $vpn_cn should probably be +# set to your hostname (the default) or a device-unique +# identifier issued by your organization. + +# TODO set up `key` mode so that it is able to accept +# paths from the command line as well as in the +# $vpn_srv_keydir environment variable. _text_=$LINENO param(){ eval $1=\${$1:-$2}; } @@ -74,11 +96,12 @@ # the user who should own all files and processes # created and destroyed by vpn param vpn_pidbox $TMPDIR/pid.$USER # a directory for storing pids in. this should be chmod - # 700 and owned by the user invoking vpn, ideally + # 700 and owned by the user invoking vpn, ideally on a + # tmpfs param vpn_bin openvpn # the binary to use. if openvpn is not in your path, # enter its absolute path here @@ -89,11 +112,12 @@ # the name of the executable # thus ends the admin-configurable portion of this script. # abandon all hope, ye who enter here -err(){ echo -e "\e[1;31merror:\e[m" $* ; exit 1; } +err(){ echo -e "\e[1;31merror:\e[m" $* >&2; exit 1; } +warn(){ echo -e "\e[1;33mwarn:\e[m " $* >&2; } assert(){ msg=$1; shift; test ! $@ && err $msg; } test "$1" == help || assert "incorrect number of arguments" $# -eq 2 act=$1; target=$2 @@ -144,10 +168,21 @@ # make sure a private pid directory exists test ! -e $vpn_pidbox && mkdir -p $vpn_pidbox chmod 700 $vpn_pidbox + # make sure a private base directory exists + test ! -e $vpn_basedir && { + mkdir -p $vpn_basedir && { + chmod 700 $USER $vpn_pidbox + } || { + err '$vpn_basedir is not set or points to a nonexistent directory you cannot create.' + } + } + + test -e $vpn_global || err "global configuration file $vpn_global does not exist or is not in $vpn_basedir" + # check and see if we're using automatic # host certificates; tell openvpn if so test -e $vpnd/ca.crt && { hostcert=$vpnd/$vpn_cn cmd=( @@ -170,47 +205,51 @@ ( part | down | disc* | stop | p | d ) test -e $pidfile && { goodpid && { kill $(cat $pidfile) && rm -f $pidfile } || { - echo -e "\e[1;33mwarn:\e[m pidfile exists but does not name an openvpn process" - echo " → removing pidfile for safety" + warn 'pidfile exists but does not name an openvpn process' + echo ' → removing pidfile for safety' >&2 rm -f $pidfile } } || { - echo -ne "\e[1mvpn $target\e[m is not up" + echo -ne "\e[1mvpn $target\e[m is not up" >&2 } ;; # clean up dirty pidfiles ( clean | wipe | clear | fix | tidy ) clean=1 ;& # return profile status ( info | stat* | detail* | i ) - echo -ne "\e[1mvpn $target:\e[m " + echo -ne "\e[1mvpn $target:\e[m " >&2 test ! -e $pidfile && { - echo -ne "\e[31mno\e[m current connection" + echo -ne "\e[31mno\e[m current connection" >&2 pids=($(pidof openvpn)) let pidc=${#pids} ((pidc > 0)) && { - echo ", but $pidc vpn instances are running" + echo ", but $pidc vpn instances are running" >&2 exit 2 } || { echo; exit 0; } } || { - echo -n "pidfile exists" + echo -n "pidfile exists" >&2 proc=$(ps ho fname $(cat $pidfile)) (($? != 0)) && { - echo ", but there are no processes with that pid" + echo ", but there are no processes with that pid" >&2 stale; exit 1 } || test "$proc" == "openvpn" && { - echo -e " and openvpn is \e[32mrunning\e[m" - exit 0 + echo -e " and openvpn is \e[32mrunning\e[m" >&2 } || { - echo -e " but named process is \e[31mnot a vpn instance!\e[m" + echo -e " but named process is \e[31mnot a vpn instance!\e[m" >&2 stale; exit 1 } } ;; + ( help ) head -n $(expr $_text_ - 2) $vpn_script | tail -n $(expr $_text_ - 2); exit 255;; - ( * ) err "action must be one of: join | part | info | clean" ;; + ( pid ) test -e $pidfile && cat $pidfile || + err "no pidfile exists for \e[1m$target\e[m; are you sure you're connected" ;; + + ( * ) err "action must be one of: join | part | info | clean | key | pid | help" ;; esac +exit 0