#!/usr/bin/env bash . ${HOME}/code/bashtools/bashtools.sh if [[ -z $HAVE_BASHTOOLS ]]; then echo "ERROR: bashtools not installed download from https://git.nethack.net/rob/bashtools" >/dev/stderr exit 1 fi CONFDIR=${HOME}/.vpn LOGFILE=${CONFDIR}/log VPNPIDFILE=${CONFDIR}/vpnpid DEFAULTCONF=${CONFDIR}/config CONFFILE=${DEFAULTCONF} cp -p "$LOGFILE" "${LOGFILE}.0" cp /dev/null "$LOGFILE" err=0 OPENCONNECT=$(which openconnect 2>/dev/null) if [[ $? -ne 0 ]]; then error "Can't find openconnect binary" err=$((err + 1)) fi VPNSLICE=$(which vpn-slice 2>/dev/null) if [[ $? -ne 0 ]]; then error "Can't find vpn-slice binary" err=$((err + 1)) fi if [[ $ree -ne 0 ]]; then exit 1 fi mkdir -p "${CONFDIR}" function alias_to_cmd() { local input="$1" local output="$input" case $input in "l") output="list";; "ls") output="list";; "start") output="on";; "u") output="on";; "up") output="on";; "stop") output="off";; "d") output="off";; "down") output="off";; "check") output="status";; "stat") output="status";; "info") output="status";; "i") output="status";; esac echo "$output" } function load_config() { local CONFLINES line thisprofile thisuser thispw thisgroup thisroutes thisservercert DEFAULTPROFILE="" if [[ -e ${CONFFILE} ]]; then DEFAULTPROFILE=$(grep '^#default:' "${CONFFILE}" | sed 's/^#default://') CONFLINES=$(egrep -v "^#" ${CONFFILE} 2>/dev/null | awk NF) nvpns=0 while read line; do [[ $line =~ ^# ]] && continue thisprofile=$(cut -d, -f1 <<< "${line}") thisuser=$(cut -d, -f2 <<< "${line}") thispw=$(cut -d, -f3 <<< "${line}") thisgroup=$(cut -d, -f4 <<< "${line}") thisvpntype=$(cut -d, -f5 <<< "${line}") thisserver=$(cut -d, -f6 <<< "${line}") thisroutes=$(cut -d, -f7 <<< "${line}") thisservercert=$(cut -d, -f8 <<< "${line}") case $thisvpntype in "") thisvpntype="anyconnect"; "cisco") thisvpntype="anyconnect"; "bigip") thisvpntype="anyconnect"; esac if [[ -z $thisserver ]]; then error "Missing server IP for profile '$thisprofile'" cecho -s "$RED" "^bBad line:^p $line" exit 1 fi profile[$nvpns]="${thisprofile}" user[$nvpns]="${thisuser}" pw[$nvpns]="${thispw}" group[$nvpns]="${thisgroup}" vpntype[$nvpns]="${thisvpntype}" server[$nvpns]="${thisserver}" routes[$nvpns]="${thisroutes}" servercert[$nvpns]="${thisservercert}" nvpns=$((nvpns + 1)) done <<< "${CONFLINES}" else error "Config file '$CONFFILE' not found" return 1 fi return 0 } function usage() { echo "$0 [OPTIONS] on|off|status|list [vpnname]" echo echo "Wrapper for openconnect to support multiple VPN profiles." echo echo " -h show this help" echo " -p profile Use selected connection profile" echo " -c file Use selected config file (default is ${DEFAULTCONF})" echo echo "Config file format:" echo " #Specify default profile like this:" echo " #default:myvpn2" echo " #Profile,Username,Password,VPNGroup,VPNType,ServerIP,VPNRoutes,ServerCert(script will obtain this and auto-update config file)" echo " myvpn1,username_1,password_1,vpngroup_1,anyconnect,3.3.3.3,10.0.0.0/24 192.168.0.0/24," echo " myvpn2,username_2,password_2,vpngroup_2,anyconnect,1.1.1.1,172.16.0.0/12," echo " myvpn3,username_3,password_3,,f5,1.1.1.1,172.16.0.0/12," echo } function get_profile_id() { #1=profile_to_check local x lookfor="$1" for x in ${!profile[@]}; do if [[ ${profile[$x],,} == ${lookfor,,} ]]; then echo "${x}" return 0 fi done error "No credentials found for the VPN profile ^b${PROFILE}^p." >&2 csecho "$RED" "Add a line of this format to ^b${CONFFILE}^p:" >&2 csecho "$RED" "${PROFILE},^iusername^p,^ipassword^p[,^igroup]^p" >&2 return 1 } function get_vpn_status() { # populates vprofile vuser vstatus vserver vvpntype, return 0 if vpn up local rv=0 vpnpid pname vprofile="" vuser="" vstatus="Disconnected" vserver="" vvpntype="" if [[ -e $VPNPIDFILE ]]; then vpnpid=$(cat $VPNPIDFILE 2>/dev/null) pname=$(ps -p $vpnpid -o command="" 2>/dev/null) vstatus="Connected" vserver=$(echo "$pname" | awk '{ print $NF }') vuser=$(echo "$pname" | sed 's/^.*-u //;s/ .*//g') vvpntype=$(echo "$pname" | sed 's/^.*--protocol=//;s/ .*//g') else rv=1 fi if [[ $vstatus == "Connected" ]]; then # find matching profile for x in ${!profile[@]}; do if [[ $vserver == ${server[$x]} ]]; then vprofile="${profile[$x]}" [[ -z $vuser ]] && vuser=${user[$x]} [[ -z $vvpntype ]] && vvpntype=${vvpntype[$x]} fi done fi return $rv } PROFILE="" ARGS="hp:" while getopts "$ARGS" i; do case "$i" in p) profile="$OPTARG"; exit 1; ;; h) usage; exit 1; ;; *) error "invalid argument: $i"; usage; ;; esac done shift $((OPTIND - 1)) if [[ $# -lt 1 ]]; then usage exit 1 fi cmd=$(alias_to_cmd "$1") load_config || exit 1 usingdefault=0 PROFILE="$2" if [[ -z $PROFILE ]]; then if [[ -n ${DEFAULTPROFILE} ]]; then PROFILE="${DEFAULTPROFILE}" usingdefault=1 fi fi if [[ $cmd == "list" ]]; then F="%s,%s,%s,%s,%s,%s\n" table=$( ( printf "${F}" "Profile" "Username" "Password" "Group" "Server" "Type" for x in ${!profile[@]}; do p="${pw[$x]:0:2}....${pw[$x]: (-2)}" prof="${profile[$x]}" [[ $prof == $DEFAULTPROFILE ]] && prof="*${prof}" printf "${F}" "${prof}" "${user[$x]}" "${p}" "${group[$x]:-(n/a)}" "${server[$x]:-(n/a)}" "${vpntype[$x]}" done ) | column -s, -t ) table=$(echo "$table" | sed "s/^\(Profile.*\)$/^b^u\1^p/;s/\(n\/a\)/^i\1^p/g") csecho "$WHITE" "$table" echo echo "(* = default VPN)" exit 0 elif [[ $cmd == "on" ]]; then [[ $usingdefault -eq 1 ]] && inform "[using default VPN: ^b$DEFAULTPROFILE^p]" get_vpn_status if [[ $vstatus == "Connected" ]]; then error "VPN profile $vprofile is already connected" exit 1 fi if [[ -z ${PROFILE} ]]; then error "No VPN profile provided and no default VPN profile set." csecho "$RED" "Either specify on commandline, or add this to ^b${CONFFILE}^p:" csecho "$RED" "#default:^ivpn_name^p" exit 1 fi id=$(get_profile_id "${PROFILE}") if [[ -n $id ]]; then curprofile=${profile[$id]} curuser=${user[$id]} curpw=${pw[$id]} curgroup=${group[$id]} curvpntype=${vpntype[$id]} curserver=${server[$id]} curservercert=${servercert[$id]} curroutes=${routes[$id]} else error "Could not determine VPN ID for profile ^b${PROFILE}^p." exit 1 fi if [[ -n $curgroup ]]; then grouparg="--authgroup=$curgroup" else grouparg="" fi if [[ -z $curservercert ]]; then inform "No server certificate is defined for ^b$curprofile^p." notify "Trying to obtain server certificate" foundcert=$(sudo ${OPENCONNECT} --non-inter --protocol=anyconnect -u "$curuser" $grouparg "$curserver" 2>&1 | grep -- --servercert | awk '{ print $NF }') if [[ -n $foundcert ]]; then ok inform "Got server certificate '^b${foundcert}^p'" notify "Updating configuration file ^b$CONFFILE^p" bakfile="${CONFFILE}".backup prevlines=$(cat "$CONFFILE" | awk NF | wc -l | bc) newconfig=$(cat "${CONFFILE}" | awk -v p="$curprofile" -v c="$foundcert" -F, '{ OFS=","; if ($1 == p) { $8 = c; } print }') newlines=$(echo "$newconfig" | awk NF | wc -l | bc) err="" if [[ $newlines -ne $prevlines ]]; then err="line count different" elif ! grep -q pin-sha256 <<<"$newconfig"; then err="new config missing server cert" fi if [[ -z $err ]]; then ok cp -p "${CONFFILE}" "${bakfile}" echo "$newconfig" > ${CONFFILE} curservercert="$foundcert" else fail error "Regenerated config file seems wrong ($err)" cecho -s "$RED" "Current config:" cecho -s "$RED" "$(cat "$CONFFILE")" | sed 's/^/ /' echo cecho -s "$RED" "Newly generated config:" cecho -s "$RED" "$newconfig" | sed 's/^/ /' echo exit 1 fi else fail exit 1 fi fi nstr="Connecting to ^b${PROFILE}^p as user ^b${curuser}^p" [[ -n $curgroup ]] && nstr="${nstr} group ${curgroup}" [[ -n $curserver ]] && nstr="${nstr} server ${curserver}" notify "${nstr}" rm -f "$VPNPIDFILE" if [[ -n $curroutes ]]; then printf '%s' "$curpw" | sudo ${OPENCONNECT} --background --non-inter --protocol=$vpntype -u "$curuser" --passwd-on-stdin $grouparg -s "$VPNSLICE $curroutes" --servercert "$curservercert" "$curserver" >"${LOGFILE}" 2>&1 else printf '%s' "$curpw" | sudo ${OPENCONNECT} --background --non-inter --protocol=$vpntype -u "$curuser" --passwd-on-stdin $grouparg --servercert "$curservercert" "$curserver" >"${LOGFILE}" 2>&1 fi rv=$? res=$(cat "$LOGFILE") if [[ $rv -eq 0 ]]; then cpid=$(pgrep openconnect) if [[ $? -eq 0 ]]; then echo "$cpid" > "$VPNPIDFILE" else rv=1 fi fi if [[ $rv -eq 0 ]]; then ok "connected" else fail error "Connection failed, logs are below:" cecho -s "$RED" "$res" | sed 's/^/ /' fi elif [[ $cmd == "status" ]]; then BASECOL="$ORANGE" notify "Checking VPN status" get_vpn_status [[ $? -eq 0 ]] && ok || fail if [[ $vstatus == "Connected" ]]; then col="$GREEN" else col="$RED" fi csecho -n "$BASECOL" "VPN status: ${col}${BOLD}${vstatus}^p" if [[ ${vstatus,,} =~ "connected" ]]; then [[ -n $vserver ]] && csecho "$BASECOL" " to ^b${vprofile}^p (${vvpntype}:${vuser}@${vserver})" || echo fi elif [[ $cmd == "off" ]]; then rv=1 get_vpn_status if [[ $vstatus == "Connected" ]]; then notify "Disconnecting from ${vvpntype} VPN ^b${vprofile}^p" errstring="" errdata="" thepid=$(cat $VPNPIDFILE) if [[ -n $thepid ]]; then sudo kill $thepid sleep 2 # give it some time res=$(ps -p $thepid 2>&1) if [[ $? -ne 0 ]]; then rm -f "${VPNPIDFILE}" rv=0 else errstring="could not kill pid $thepid:" errdata="$res" rv=1 fi else errstring="Cannot determine current VPN PID to kill." rv=1 fi if [[ $rv -eq 0 ]]; then ok else fail error "$errstring" [[ -n $errdata ]] && cecho -s "$RED" "$errdata" | sed 's/^/ /' fi else fail error "All VPNs are already disconnected" fi else usage exit 1 fi