#!/bin/sh # # Purpose {{{ # This script will try to manage VPN with forticlient v6 # 1. Start a VPN will # a. Ensure forticlient-scheduler systemd unit is started. # b. Try to connect to VPN with profile name. # 2. Stop a VPN will # a. Disconnect from the VPN. # b. Stop and disable forticlient-scheduler systemd unit. # c. Restart systemd-resolved to ensure to have correct DNS resolvers. # # 2023-01-24 # }}} # Flags {{{ ## Exit on error {{{ set -o errexit ## }}} ## Exit on unset var {{{ ### Use "${VARNAME-}" to test a var that may not have been set set -o nounset ## }}} ## Pipeline command is treated as failed {{{ ### Not available in POSIX sh − https://github.com/koalaman/shellcheck/wiki/SC3040 #set -o pipefail ## }}} ## Help with debugging {{{ ### Call the script by prefixing it with "TRACE=1 ./script.sh" if [ "${TRACE-0}" -eq 1 ]; then set -o xtrace; fi ## }}} # }}} # Vars {{{ PROGNAME=$(basename "${0}"); readonly PROGNAME PROGDIR=$(readlink --canonicalize-missing $(dirname "${0}")); readonly PROGDIR ARGS="${*}"; readonly ARGS readonly NBARGS="${#}" [ -z "${DEBUG-}" ] && DEBUG=1 ## Export DEBUG for sub-script export DEBUG ## Default values for some vars readonly REQUEST_STATUS_DEFAULT="start" readonly VPN_PROFILE_NAME_DEFAULT="Universite Rennes 1" VPN_USER_DEFAULT=$(whoami) ; readonly VPN_USER_DEFAULT readonly FORTICLIENT_NEW_VERSION="7.0.0.0" ## Colors readonly PURPLE='\033[1;35m' readonly RED='\033[0;31m' readonly RESET='\033[0m' readonly COLOR_DEBUG="${PURPLE}" # }}} usage() { # {{{ cat <<- HELP usage: $PROGNAME [-d|-h|-p|-u] Try to easily manage VPN with forticlient. EXAMPLES : - Start VPN with default profife name and default user ${PROGNAME} - Stop the VPN ${PROGNAME} stop - Start VPN with specific VPN user ${PROGNAME} --user my_lambda_username start - Start VPN with specific VPN profile name ${PROGNAME} --profile "My University name" start OPTIONS : -d,--debug Enable debug messages. -h,--help Print this help message. -p,--profile Define VPN profile name to use. Default : ${VPN_PROFILE_NAME_DEFAULT} -u,--user Define VPN user name to use. Default : ${VPN_USER_DEFAULT} For a first connection, try to start \`forticlient gui\` first to configure EMS and to check profile name. HELP } # }}} debug_message() { # {{{ local_debug_message="${1}" ## Print message if DEBUG is enable (=0) [ "${DEBUG}" -eq "0" ] && printf '\e[1;35m%-6b\e[m\n' "DEBUG − ${PROGNAME} : ${local_debug_message}" unset local_debug_message return 0 } # }}} error_message() { # {{{ local_error_message="${1}" local_error_code="${2}" ## Print message printf '%b\n' "ERROR − ${PROGNAME} : ${RED}${local_error_message}${RESET}" >&2 unset local_error_message exit "${local_error_code:=66}" } # }}} is_command_available() { # {{{ local_command_available_cmd="${1}" debug_prefix="${2:-}" ## Return False by default return_command_available="1" if [ "$(command -v ${local_command_available_cmd})" ]; then debug_message "${debug_prefix}is_command_available − \ ${RED}${local_command_available_cmd}${COLOR_DEBUG} seems present on this host." return_command_available="0" else debug_message "${debug_prefix}is_command_available − \ ${RED}${local_command_available_cmd}${COLOR_DEBUG} is not available on this host." return_command_available="1" fi unset local_command_available_cmd unset debug_prefix return "${return_command_available}" } # }}} is_version_greater_than() { # {{{ first_value="${1}" value_to_compare="${2}" ## Return False by default return_is_version_greater_than="1" debug_message "is_version_greater_than − \ Is first value (${first_value}) greater than the second value (${value_to_compare})." if printf '%s\n' "${first_value}" "${value_to_compare}" | sort --check=quiet --version-sort; then debug_message "is_version_greater_than − ${first_value} <= ${value_to_compare} ." return_is_version_greater_than="1" else debug_message "is_version_greater_than − ${first_value} > ${value_to_compare} ." return_is_version_greater_than="0" fi unset first_value unset value_to_compare return "${return_is_version_greater_than}" } # }}} define_vars() { # {{{ ## If request_status wasn't defined (argument) {{{ if [ -z "${request_status-}" ]; then ## Use default value readonly request_status="${REQUEST_STATUS_DEFAULT}" fi ## }}} ## If vpn_profile_name wasn't defined (argument) {{{ if [ -z "${vpn_profile_name-}" ]; then ## Use default value readonly vpn_profile_name="${VPN_PROFILE_NAME_DEFAULT}" fi ## }}} ## If vpn_user wasn't defined (argument) {{{ if [ -z "${vpn_user-}" ]; then ## Use default value readonly vpn_user="${VPN_USER_DEFAULT}" fi ## }}} ## Try to get forticlient version {{{ is_command_available "dpkg" "| " \ && forticlient_current_version=$(dpkg --list -- forticlient | awk '/^ii *forticlient/ {print $3}' | sed 's/.:\(.*\)-.*/\1/') ## }}} } # }}} main() { # {{{ debug_message "--- MAIN BEGIN" ## If forticlient command is not available {{{ ### Exit with error is_command_available "forticlient" "| " \ || error_message "forticlient is not in PATH, ensure the package is installed." 01 ## }}} ## Define all vars define_vars debug_message "| Define vars" ## If forticlient version is 7 or above {{{ ### Exit with error is_version_greater_than "${forticlient_current_version}" "${FORTICLIENT_NEW_VERSION}" \ && error_message "Forticlient installed version (${forticlient_current_version}) seems newest than expected (<${FORTICLIENT_NEW_VERSION}). Please see fortinet.vpn.v7.sh script instead." 02 ## }}} ## If the VPN must be stopped {{{ if [ "${request_status}" = "stop" ]; then debug_message "-- Disconnect VPN BEGIN" ### If the VPN is still connected {{{ forticlient_status=$(/opt/forticlient/fortivpn status | head --lines=1 | sed 's/Status: \(.*\)/\1/' || error_message "Error while requesting current VPN status." 22) if [ "${forticlient_status}" = "Connected" ]; then debug_message "| VPN is ${RED}${forticlient_status}${COLOR_DEBUG}, try to disconnect it…" ### Try to disconnect /opt/forticlient/fortivpn disconnect > /dev/null \ || error_message "Error when disconnecting VPN." 11 fi ### }}} ### Stop the systemd service {{{ debug_message "| Try to stop and disable forticlient-scheduler.service unit" sudo systemctl stop forticlient-scheduler.service \ || error_message "Error while stopping forticlient-scheduler.service unit" 12 ### Ensure to disable it sudo systemctl disable forticlient-scheduler.service \ || error_message "Error while disabling forticlient-scheduler.service unit" 13 ### }}} ### Ensure to restart DNS resolver {{{ debug_message "| Try to restart systemd-resolved.service unit to have correct DNS resolvers." sudo systemctl restart systemd-resolved.service \ || error_message "Error while restart systemd-resolved.service unit" 14 ### }}} debug_message "-- Disconnect VPN END" fi ## }}} ## If the VPN must be started {{{ if [ "${request_status}" = "start" ]; then debug_message "-- Connect VPN BEGIN" ### If forticlient-scheduler.service unit is not started {{{ systemd_forticlient_status=$(systemctl show --property ActiveState --value forticlient-scheduler.service || error_message "Error while requesting forticlient-scheduler.service unit status." 21) if [ "${systemd_forticlient_status}" != "active" ]; then debug_message "| Try to start forticlient-scheduler.service unit." sudo systemctl restart forticlient-scheduler.service \ || error_message "Error while (re)starting forticlient-scheduler.service unit" 22 fi ### }}} ### If forticlient-scheduler.service unit is started {{{ systemd_forticlient_status=$(systemctl show --property ActiveState --value forticlient-scheduler.service || error_message "Error while requesting forticlient-scheduler.service unit status." 21) if [ "${systemd_forticlient_status}" = "active" ]; then debug_message "| forticlient-scheduler.service unit is ${RED}started${COLOR_DEBUG}." fi ### }}} ### If the VPN is not connected {{{ forticlient_status=$(/opt/forticlient/fortivpn status | head --lines=1 | sed 's/Status: \(.*\)/\1/' || error_message "Error while requesting current VPN status." 22) if [ "${forticlient_status}" = "Not Running" ]; then ### If script was started from a launcher {{{ if [ -n "${DISPLAY-}" ] && [ "${TERM}" = "linux" ]; then ### Try to launch a new TERM_EMULATOR to ask the password "${TERM_EMULATOR}" -e /opt/forticlient/fortivpn connect "${vpn_profile_name}" --password --user="${vpn_user}" \ || error_message "Error when connecting to VPN profile (${vpn_profile_name})." 24 ### }}} ### Else we consider it was started from CLI {{{ else ### Try to connect /opt/forticlient/fortivpn connect "${vpn_profile_name}" --password --user="${vpn_user}" \ || error_message "Error when connecting to VPN profile (${vpn_profile_name})." 25 fi ### }}} fi ### }}} ### If the VPN is connected {{{ forticlient_status=$(/opt/forticlient/fortivpn status | head --lines=1 | sed 's/Status: \(.*\)/\1/' || error_message "Error while requesting current VPN status." 22) if [ "${forticlient_status}" = "Connected" ]; then debug_message "| VPN is ${RED}${forticlient_status}${COLOR_DEBUG}." fi ### }}} debug_message "-- Connect VPN END" fi ## }}} debug_message "--- MAIN END" } # }}} # Manage arguments # {{{ # This code can't be in a function due to argument management if [ ! "${NBARGS}" -eq "0" ]; then manage_arg="0" ## If the first argument ask for help (h|help|-h|-help|-*h|-*help) if printf -- '%s' "${1-}" | grep --quiet --extended-regexp -- "^-*h(elp)?$"; then usage exit 0 fi # Parse all argument one by one while printf -- '%s' "${1-}" | grep --quiet -- "."; do case "${1}" in -d|--debug ) ## debug DEBUG=0 debug_message "--- Manage argument BEGIN" ;; --start|start ) ## Define request_status to start ## Define var readonly request_status="start" ;; --stop|stop ) ## Define request_status to stop ## Define var readonly request_status="stop" ;; -p|--profile ) ## Define vpn_profile_name with given arg ## Move to the next argument shift ## Define var readonly vpn_profile_name="${1}" ;; -u|--user ) ## Define vpn_user with given arg ## Move to the next argument shift ## Define var readonly vpn_user="${1}" ;; * ) ## unknow option printf '%b\n' "${RED}Invalid option: ${1}${RESET}" printf '%b\n' "---" usage exit 1 ;; esac debug_message "| ${RED}${1}${COLOR_DEBUG} option managed." ## Move to the next argument shift manage_arg=$((manage_arg+1)) done debug_message "| ${RED}${manage_arg}${COLOR_DEBUG} argument(s) successfully managed." else debug_message "| No arguments/options to manage." fi debug_message "--- Manage argument END" # }}} main exit 0