diff --git a/ur/fortinet.vpn.v6.sh b/ur/fortinet.vpn.v6.sh new file mode 100755 index 0000000..adb03e5 --- /dev/null +++ b/ur/fortinet.vpn.v6.sh @@ -0,0 +1,368 @@ +#!/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}" = "inactive" ] || [ "${systemd_forticlient_status}" = "failed" ]; 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