cvpn/1

383 lines
12 KiB
Bash

#!/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