#!/bin/bash ############################################################################### # # # IPFire.org - A linux based firewall # # Copyright (C) 2017 IPFire Network Development Team # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # # # ############################################################################### IPSEC_CONNECTION_CONFIG_SETTINGS="\ AUTH_MODE \ DPD_ACTION \ DPD_DELAY \ DPD_TIMEOUT \ INACTIVITY_TIMEOUT \ LOCAL_ADDRESS \ LOCAL_ID \ LOCAL_PREFIX \ MODE \ PEER \ POOLS \ PSK \ REMOTE_ID \ REMOTE_PREFIX \ SECURITY_POLICY \ START_ACTION \ TYPE \ ENABLED \ ZONE" # Default values IPSEC_DEFAULT_AUTH_MODE="PSK" IPSEC_DEFAULT_DPD_ACTION="restart" IPSEC_DEFAULT_DPD_DELAY="30" IPSEC_DEFAULT_DPD_TIMEOUT="120" IPSEC_DEFAULT_ENABLED="true" IPSEC_DEFAULT_INACTIVITY_TIMEOUT="0" IPSEC_DEFAULT_MODE="tunnel" IPSEC_DEFAULT_SECURITY_POLICY="system" IPSEC_DEFAULT_START_ACTION="on-demand" IPSEC_DEFAULT_TYPE="net-to-net" IPSEC_VALID_MODES="transport tunnel" IPSEC_VALID_AUTH_MODES="PSK" cli_ipsec() { local action=${1} shift 1 case "${action}" in connection) cli_ipsec_connection "$@" ;; pool) cli_ipsec_pool "$@" ;; *) error "Unrecognized argument: ${action}" exit ${EXIT_ERROR} ;; esac } cli_ipsec_connection() { if ipsec_connection_exists ${1}; then local connection=${1} local key=${2} key=${key//-/_} shift 2 case "${key}" in authentication|down|disable|dpd|enable|inactivity_timeout|local|mode|peer|pool|remote|security_policy|start_action|up|zone) ipsec_connection_${key} ${connection} "$@" ;; color) color_cli "ipsec-connection" "${connection}" "$@" ;; description) description_cli "ipsec-connection" ${connection} $@ ;; show) cli_ipsec_connection_show "${connection}" exit $? ;; *) error "Unrecognized argument: ${key}" exit ${EXIT_ERROR} ;; esac else local action=${1} shift case "${action}" in new) ipsec_connection_new "$@" ;; destroy) cli_ipsec_connection_destroy "$@" ;; ""|*) if [ -n "${action}" ]; then error "Unrecognized argument: '${action}'" fi exit ${EXIT_ERROR} ;; esac fi } cli_ipsec_connection_destroy() { local connection="${1}" if ! ipsec_connection_destroy "${connection}"; then return ${EXIT_ERROR} fi # Inform strongswan about the changes ipsec_strongswan_load # Configure strongswan autostart ipsec_strongswan_autostart } ipsec_connection_get_color() { # This function return the color of a zone assert [ $# -eq 1 ] local name=${1} color_read "ipsec-connection" ${name} } ipsec_connection_get_description_title() { assert [ $# -eq 1 ] local name=${1} description_title_read $(description_format_filename "ipsec-connection" "${name}") } cli_ipsec_connection_show() { local connection="${1}" # Read the config settings local ${IPSEC_CONNECTION_CONFIG_SETTINGS} if ! ipsec_connection_read_config "${connection}"; then error "Could not read the connection configuration" return ${EXIT_ERROR} fi cli_headline 0 "IPsec VPN Connection: ${connection}" cli_space cli_print_fmt1 1 "Color" "$(cli_color_bar $(ipsec_connection_get_color ${connection}))" cli_print_fmt1 1 "Description" "$(ipsec_connection_get_description_title ${connection})" cli_space # Peer if isset PEER; then cli_print_fmt1 1 "Peer" "${PEER}" fi # Zone if isset ZONE; then cli_print_fmt1 1 "Zone" "${ZONE}" fi # Security Policy cli_print_fmt1 1 "Security Policy" "${SECURITY_POLICY-${IPSEC_DEFAULT_SECURITY_POLICY}}" cli_space cli_headline 2 "Authentication" case "${AUTH_MODE^^}" in PSK) cli_print_fmt1 2 "Mode" "Pre-Shared-Key" if isset PSK; then cli_print_fmt1 2 "Pre-Shared-Key" "****" else cli_print_fmt1 2 "Pre-Shared-Key" "- is not set -" fi ;; X509) : # TODO ;; esac cli_space local i for i in LOCAL REMOTE; do case "${i}" in LOCAL) cli_headline 2 "Local" ;; REMOTE) cli_headline 2 "Remote" ;; esac local id_var="${i}_ID" if [ -n "${!id_var}" ]; then cli_print_fmt1 2 "ID" "${!id_var}" fi local prefix_var="${i}_PREFIX" if isset ${prefix_var}; then cli_headline 3 "Prefix(es)" local prefix for prefix in ${!prefix_var}; do cli_print_fmt1 3 "${prefix}" done fi cli_space done cli_headline 2 "Misc." case "${MODE}" in transport) cli_print_fmt1 2 "Transport Mode" "Transport" ;; tunnel) cli_print_fmt1 2 "Transport Mode" "Tunnel" ;; *) cli_print_fmt1 2 "Transport Mode" "- Unknown -" ;; esac # Inactivity timeout if isset INACTIVITY_TIMEOUT && [ ${INACTIVITY_TIMEOUT} -gt 0 ]; then cli_print_fmt1 2 "Inactivity Timeout" "$(format_time ${INACTIVITY_TIMEOUT})" fi cli_space return ${EXIT_OK} } ipsec_connection_disable() { local connection=${1} if ! ipsec_connection_write_config_key "${connection}" "ENABLED" "false"; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi # Configure strongswan autostart ipsec_strongswan_autostart } ipsec_connection_enable() { local connection=${1} if ! ipsec_connection_write_config_key "${connection}" "ENABLED" "true"; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi # Configure strongswan autostart ipsec_strongswan_autostart } # This function writes all values to a via ${connection} specificated VPN IPsec configuration file ipsec_connection_write_config() { assert [ $# -ge 1 ] local connection="${1}" if ! ipsec_connection_exists "${connection}"; then log ERROR "No such VPN IPsec connection: ${connection}" return ${EXIT_ERROR} fi local path="${NETWORK_IPSEC_CONNS_DIR}/${connection}/settings" if ! settings_write "${path}" ${IPSEC_CONNECTION_CONFIG_SETTINGS}; then log ERROR "Could not write configuration settings for VPN IPsec connection ${connection}" return ${EXIT_ERROR} fi ipsec_reload ${connection} } # This funtion writes the value for one key to a via ${connection} specificated VPN IPsec connection configuration file ipsec_connection_write_config_key() { assert [ $# -ge 3 ] local connection=${1} local key=${2} shift 2 local value="$@" if ! ipsec_connection_exists "${connection}"; then log ERROR "No such VPN ipsec connection: ${connection}" return ${EXIT_ERROR} fi log DEBUG "Set '${key}' to new value '${value}' in VPN ipsec connection '${connection}'" local ${IPSEC_CONNECTION_CONFIG_SETTINGS} # Read the config settings if ! ipsec_connection_read_config "${connection}"; then return ${EXIT_ERROR} fi # Set the key to a new value assign "${key}" "${value}" if ! ipsec_connection_write_config "${connection}"; then return ${EXIT_ERROR} fi return ${EXIT_TRUE} } # Reads one or more keys out of a settings file or all if no key is provided. ipsec_connection_read_config() { assert [ $# -ge 1 ] local connection="${1}" shift 1 if ! ipsec_connection_exists "${connection}"; then log ERROR "No such VPN IPsec connection : ${connection}" return ${EXIT_ERROR} fi local args if [ $# -eq 0 ] && [ -n "${IPSEC_CONNECTION_CONFIG_SETTINGS}" ]; then list_append args ${IPSEC_CONNECTION_CONFIG_SETTINGS} else list_append args "$@" fi local path="${NETWORK_IPSEC_CONNS_DIR}/${connection}/settings" if ! settings_read "${path}" ${args}; then log ERROR "Could not read settings for VPN IPsec connection ${connection}" return ${EXIT_ERROR} fi } # This function checks if a vpn ipsec connection exists # Returns True when yes and false when not ipsec_connection_exists() { assert [ $# -eq 1 ] local connection=${1} local path="${NETWORK_IPSEC_CONNS_DIR}/${connection}" [ -d "${path}" ] && return ${EXIT_TRUE} || return ${EXIT_FALSE} } # Determines if strongswan should be automatically started # when the system boots up. ipsec_strongswan_autostart() { local autostart_needed="false" local connection for connection in $(ipsec_list_connections); do local ENABLED if ! ipsec_connection_read_config "${connection}" "ENABLED"; then log WARNING "Could not read configuation" continue fi if enabled ENABLED; then autostart_needed="true" break fi done # Start strongswan when we need it and when it is not yet enabled if ${autostart_needed}; then if ! service_is_enabled "strongswan"; then service_enable "strongswan" fi if ! service_is_active "strongswan"; then service_start "strongswan" fi # Disable strongswan when we do not need it but it is enabled elif ! ${autostart_needed}; then if service_is_enabled "strongswan"; then service_disable "strongswan" fi if service_is_active "strongswan"; then service_stop "strongswan" fi fi } ipsec_strongswan_load() { # Do nothing if strongswan is not running if ! service_is_active "strongswan"; then return ${EXIT_OK} fi if ! cmd swanctl --load-all; then log ERROR "Could not reload strongswan config" return ${EXIT_ERROR} fi } # Reloads the connection after config changes ipsec_reload() { local connection=${1} local ENABLED if ! ipsec_connection_read_config "${connection}" "ENABLED"; then log ERROR "Could not read configuration for IPsec connection ${connection}" return ${EXIT_ERROR} fi if enabled ENABLED; then if ! ipsec_connection_to_strongswan ${connection}; then log ERROR "Could not generate strongswan config for ${connnection}" return ${EXIT_ERROR} fi else log DEBUG "Deleting strongswan config ${NETWORK_IPSEC_SWANCTL_CONNECTIONS_DIR}/${connection}.conf" unlink "${NETWORK_IPSEC_SWANCTL_CONNECTIONS_DIR}/${connection}.conf" fi ipsec_strongswan_load } # Handle the cli after authentification ipsec_connection_authentication() { if [ ! $# -gt 1 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local cmd=${2} shift 2 case ${cmd} in mode) ipsec_connection_authentication_mode "${connection}" "$@" ;; pre-shared-key) ipsec_connection_authentication_psk "${connection}" "$@" ;; *) log ERROR "Unrecognized argument: ${cmd}" return ${EXIT_ERROR} ;; esac } # Set the authentification mode ipsec_connection_authentication_mode() { if [ ! $# -eq 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local mode=${2} if ! isoneof mode ${IPSEC_VALID_AUTH_MODES}; then log ERROR "Auth mode '${mode}' is invalid" return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "AUTH_MODE" ${mode^^}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi } # Set the psk ipsec_connection_authentication_psk() { if [ ! $# -eq 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local psk=${2} local length=${#psk} if [ ${length} -lt 4 ]; then error "The PSK must be longer than four characters" return ${EXIT_ERROR} fi if [ ${length} -gt 128 ]; then error "The PSK cannot be longer than 128 characters" return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "PSK" "${psk}"; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi return ${EXIT_OK} } ipsec_connection_up() { local connection="${1}" if ! ipsec_connection_exists "${connection}"; then error "No such VPN IPsec connection: ${connection}" return ${EXIT_ERROR} fi # Read configuration options local ZONE if ! ipsec_connection_read_config "${connection}" "ZONE"; then log ERROR "Could not read configuration for IPsec connection ${connection}" return ${EXIT_ERROR} fi if ! [ -f "${NETWORK_IPSEC_SWANCTL_CONNECTIONS_DIR}/${connection}.conf" ]; then log DEBUG "Could not find a swanctl config, generating swanctl config" ipsec_connection_to_strongswan "${connection}" ipsec_strongswan_load fi # Bring up the zone if isset ZONE; then zone_up "${ZONE}" fi cmd swanctl --initiate --child "${connection}" } ipsec_connection_down() { local connection="${1}" if ! ipsec_connection_exists "${connection}"; then error "No such VPN IPsec connection: ${connection}" return ${EXIT_ERROR} fi # Read configuration options local ZONE if ! ipsec_connection_read_config "${connection}" "ZONE"; then log ERROR "Could not read configuration for IPsec connection ${connection}" return ${EXIT_ERROR} fi # Tell strongswan to bring down any tunnels cmd swanctl --terminate --ike "${connection}" # Bring up the zone if isset ZONE; then zone_down "${ZONE}" fi } # Handle the cli after authentification ipsec_connection_dpd() { if [ ! $# -gt 1 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local cmd=${2} shift 2 case ${cmd} in action) ipsec_connection_dpd_action "${connection}" "$@" ;; delay) ipsec_connection_dpd_delay "${connection}" "$@" ;; timeout) ipsec_connection_dpd_timeout "${connection}" "$@" ;; *) log ERROR "Unrecognized argument: ${cmd}" return ${EXIT_ERROR} ;; esac } # Set the default dpd action ipsec_connection_dpd_action() { if [ ! $# -eq 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local action=${2} if ! isoneof action "restart" "clear"; then log ERROR "dpd action '${action}' is invalid" return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "DPD_ACTION" ${action}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi } # Set the dpd delay ipsec_connection_dpd_delay() { if [ ! $# -ge 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} shift 1 local value=$@ if ! isinteger value; then value=$(parse_time "$@") if [ ! $? -eq 0 ]; then log ERROR "Parsing the passed time was not sucessful please check the passed values." return ${EXIT_ERROR} fi fi if [ ${value} -lt 0 ]; then log ERROR "The passed time value must be in the sum greater or equal zero seconds." return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "DPD_DELAY" ${value}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi return ${EXIT_OK} } # Set the dpd timeout ipsec_connection_dpd_timeout() { if [ ! $# -ge 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} shift 1 local value=$@ if ! isinteger value; then value=$(parse_time "$@") if [ ! $? -eq 0 ]; then log ERROR "Parsing the passed time was not sucessful please check the passed values." return ${EXIT_ERROR} fi fi if [ ${value} -le 0 ]; then log ERROR "The passed time value must be in the sum greater or equal zero seconds." return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "DPD_TIMEOUT" ${value}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi return ${EXIT_OK} } # Handle the cli after local ipsec_connection_local() { if [ ! $# -ge 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local cmd=${2} shift 2 case ${cmd} in address) ipsec_connection_local_address "${connection}" "$@" ;; id) ipsec_connection_id "${connection}" "LOCAL" "$@" ;; prefix) ipsec_connection_prefix "${connection}" "LOCAL" "$@" ;; *) log ERROR "Unrecognized argument: ${cmd}" return ${EXIT_ERROR} ;; esac return ${EXIT_OK} } # Set the connection mode ipsec_connection_mode() { if [ ! $# -eq 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local mode=${2} if ! isoneof mode ${IPSEC_VALID_MODES}; then log ERROR "Mode '${mode}' is invalid" return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "MODE" ${mode}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi return ${EXIT_OK} } ipsec_connection_zone() { local connection="${1}" local zone="${2}" shift 2 # Check if we got an argument if ! isset zone; then error "Zone is not set" return ${EXIT_ERROR} fi local ZONE case "${zone}" in -) if ! ipsec_connection_read_config "${connection}" "ZONE"; then log ERROR "Could not read configuration for IPsec connection ${connection}" return ${EXIT_ERROR} fi # Removes zone setting zone="" if isset ZONE; then log INFO "Removing zone ${ZONE} from IPsec connection '${connection}'" fi ;; *) # Check if the zone exists if ! zone_exists "${zone}"; then error "Zone ${zone} does not exist" return ${EXIT_ERROR} fi # Zone must be of type tunnel local hook="$(zone_get_hook "${zone}")" case "${hook}" in ip-tunnel) # We support ip-tunnels ;; *) error "Zones of type ${hook} are not supported" return ${EXIT_ERROR} ;; esac # Check if this zone is alreadz attached to another IPsec connection # XXX log INFO "Adding zone ${zone} to IPsec connection '${connection}'" ;; esac # Save settings if ! ipsec_connection_write_config_key "${connection}" "ZONE" "${zone}"; then error "Could not write configuration settings" return ${EXIT_ERROR} fi return ${EXIT_OK} } # Set the local address ipsec_connection_local_address() { if [ ! $# -eq 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local local_address=${2} if ! ipsec_connection_check_peer ${local_address}; then log ERROR "Local address '${local_address}' is invalid" return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "LOCAL_ADDRESS" ${local_address}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi return ${EXIT_OK} } # Set the peer to connect to ipsec_connection_peer() { if [ ! $# -eq 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local peer=${2} if ! ipsec_connection_check_peer ${peer}; then log ERROR "Peer '${peer}' is invalid" return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "PEER" ${peer}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi return ${EXIT_OK} } #Set the local or remote id ipsec_connection_id() { if [ ! $# -eq 3 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local type=${2} local id=${3} if ! ipsec_connection_check_id ${id}; then log ERROR "Id '${id}' is invalid" return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "${type}_ID" ${id}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi return ${EXIT_OK} } # Set the local or remote prefix ipsec_connection_prefix() { if [ ! $# -ge 3 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local type=${2} shift 2 local _prefix="${type}_PREFIX" local "${_prefix}" if ! ipsec_connection_read_config "${connection}" "${_prefix}"; then return ${EXIT_ERROR} fi # Remove duplicated entries to proceed the list safely assign "${_prefix}" "$(list_unique ${!_prefix} )" local prefixes_added local prefixes_removed local prefixes_set while [ $# -gt 0 ]; do local arg="${1}" case "${arg}" in +*) list_append prefixes_added "${arg:1}" ;; -*) list_append prefixes_removed "${arg:1}" ;; [A-Fa-f0-9]*) list_append prefixes_set "${arg}" ;; *) error "Invalid argument: ${arg}" return ${EXIT_ERROR} ;; esac shift done # Check if the user is trying a mixed operation if ! list_is_empty prefixes_set && (! list_is_empty prefixes_added || ! list_is_empty prefixes_removed); then error "You cannot reset the prefix list and add or remove prefixes at the same time" return ${EXIT_ERROR} fi # Set new prefix list if ! list_is_empty prefixes_set; then # Check if all prefixes are valid local prefix for prefix in ${prefixes_set}; do if ! ip_net_is_valid ${prefix}; then error "Unsupported prefix: ${prefix}" return ${EXIT_ERROR} fi done assign "${_prefix}" "${prefixes_set}" # Perform incremental updates else local prefix # Perform all removals for prefix in ${prefixes_removed}; do if ! list_remove "${_prefix}" ${prefix}; then warning "${prefix} was not on the list and could not be removed" fi done for prefix in ${prefixes_added}; do if ip_net_is_valid ${prefix}; then if ! list_append_unique "${_prefix}" ${prefix}; then warning "${prefix} is already on the prefix list" fi else warning "${prefix} is not a valid IP network and could not be added" fi done fi # Check if the list contain at least one valid prefix if list_is_empty ${_prefix}; then error "Cannot save an empty prefix list" return ${EXIT_ERROR} fi # Save everything if ! ipsec_connection_write_config_key "${connection}" "${_prefix}" ${!_prefix}; then log ERROR "Could not write configuration settings" fi return ${EXIT_OK} } # Set the pools to use ipsec_connection_pool() { if [ ! $# -ge 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} shift local POOLS if ! ipsec_connection_read_config "${connection}" "POOLS"; then return ${EXIT_ERROR} fi # Remove duplicated entries to proceed the list safely assign "POOLS" "$(list_unique ${POOLS})" local pools_added local pools_removed local pools_set while [ $# -gt 0 ]; do local arg="${1}" case "${arg}" in +*) list_append pools_added "${arg:1}" ;; -*) list_append pools_removed "${arg:1}" ;; [A-Za-z0-9]*) list_append pools_set "${arg}" ;; *) error "Invalid argument: ${arg}" return ${EXIT_ERROR} ;; esac shift done # Check if the user is trying a mixed operation if ! list_is_empty pools_set && (! list_is_empty pools_added || ! list_is_empty pools_removed); then error "You cannot reset the pools list and add or remove pools at the same time" return ${EXIT_ERROR} fi # Set new pools list if ! list_is_empty pools_set; then # Check if all pools are valid local pool for pool in ${pools_set}; do if ! ipsec_pool_exists ${pool} || ! ipsec_pool_check_config ${pool}; then error "Pool ${pool} is not valid" return ${EXIT_ERROR} fi done assign "POOLS" "${pools_set}" # Perform incremental updates else local pool # Perform all removals for pool in ${pools_removed}; do if ! list_remove "POOLS" ${pool}; then warning "${pool} was not on the list and could not be removed" fi done for pool in ${pools_added}; do if ipsec_pool_exists ${pool} && ipsec_pool_check_config ${pool}; then if ! list_append_unique "POOLS" ${pool}; then warning "${pool} is already on the prefix list" fi else warning "${pool} is not a valid pool" fi done fi # Check if the list contain at least one valid pool if list_is_empty POOLS; then error "Cannot save an empty pool list" return ${EXIT_ERROR} fi # Save everything if ! ipsec_connection_write_config_key "${connection}" "POOLS" ${POOLS}; then log ERROR "Could not write configuration settings" fi return ${EXIT_OK} } # Handle the cli after remote ipsec_connection_remote() { if [ ! $# -ge 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local cmd=${2} shift 2 case ${cmd} in id) ipsec_connection_id "${connection}" "REMOTE" "$@" ;; prefix) ipsec_connection_prefix "${connection}" "REMOTE" "$@" ;; *) log ERROR "Unrecognized argument: ${cmd}" return ${EXIT_ERROR} ;; esac return ${EXIT_OK} } # Set the inactivity timeout ipsec_connection_inactivity_timeout() { if [ ! $# -ge 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} shift 1 local value=$@ if ! isinteger value; then value=$(parse_time "$@") if [ ! $? -eq 0 ]; then log ERROR "Parsing the passed time was not sucessful please check the passed values." return ${EXIT_ERROR} fi fi if [ ${value} -le 0 ]; then log ERROR "The passed time value must be in the sum greater zero seconds." return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "INACTIVITY_TIMEOUT" ${value}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi return ${EXIT_OK} } # Set the default start action ipsec_connection_start_action() { if [ ! $# -eq 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local action=${2} if ! isoneof action "on-demand" "always-on"; then log ERROR "Start action '${action}' is invalid" return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "START_ACTION" ${action}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi } # Set the security policy to use ipsec_connection_security_policy() { if [ ! $# -eq 2 ]; then log ERROR "Not enough arguments" return ${EXIT_ERROR} fi local connection=${1} local security_policy=${2} if ! vpn_security_policy_exists ${security_policy}; then log ERROR "No such vpn security policy '${security_policy}'" return ${EXIT_ERROR} fi if ! ipsec_connection_write_config_key "${connection}" "SECURITY_POLICY" ${security_policy}; then log ERROR "Could not write configuration settings" return ${EXIT_ERROR} fi } # Check if a id is valid ipsec_connection_check_id() { assert [ $# -eq 1 ] local id=${1} if [[ ${id} =~ ^@[[:alnum:]]+$ ]] || ip_is_valid ${id}; then return ${EXIT_TRUE} else return ${EXIT_FALSE} fi } # Checks if a peer is valid ipsec_connection_check_peer() { assert [ $# -eq 1 ] local peer=${1} # IP addresses are accepted if ip_is_valid ${peer}; then return ${EXIT_TRUE} fi # FQDNs are okay, too if fqdn_is_valid "${peer}"; then return ${EXIT_TRUE} fi # We cannot use anything else return ${EXIT_FALSE} } # This function checks if a VPN IPsec connection name is valid # Allowed are only A-Za-z0-9 ipsec_connection_check_name() { assert [ $# -eq 1 ] local connection=${1} [[ "${connection}" =~ [^[:alnum:]$] ]] } # Function that creates one VPN IPsec connection ipsec_connection_new() { if [ $# -gt 2 ]; then error "Too many arguments" return ${EXIT_ERROR} fi local connection="${1}" local type="${2}" if ! isset connection; then error "Please provide a connection name" return ${EXIT_ERROR} fi # Check for duplicates if ipsec_connection_exists "${connection}"; then error "The VPN IPsec connection ${connection} already exists" return ${EXIT_ERROR} fi # Check if the name of the connection is valid if ipsec_connection_check_name "${connection}"; then error "'${connection}' contains illegal characters" return ${EXIT_ERROR} fi # Set TYPE to default if not set by the user if ! isset type; then type="${IPSEC_DEFAULT_TYPE}" fi if ! isoneof "type" "net-to-net" "host-to-net"; then error "Type is invalid" return ${EXIT_ERROR} fi log DEBUG "Creating VPN IPsec connection ${connection}" if ! mkdir -p "${NETWORK_IPSEC_CONNS_DIR}/${connection}"; then log ERROR "Could not create config directory for ${connection}" return ${EXIT_ERROR} fi local ${IPSEC_CONNECTION_CONFIG_SETTINGS} AUTH_MODE=${IPSEC_DEFAULT_AUTH_MODE} DPD_ACTION=${IPSEC_DEFAULT_DPD_ACTION} DPD_DELAY=${IPSEC_DEFAULT_DPD_DELAY} DPD_TIMEOUT=${IPSEC_DEFAULT_DPD_TIMEOUT} ENABLED=${IPSEC_DEFAULT_ENABLED} MODE=${IPSEC_DEFAULT_MODE} START_ACTION=${IPSEC_DEFAULT_START_ACTION} TYPE="${type}" INACTIVITY_TIMEOUT=${IPSEC_DEFAULT_INACTIVITY_TIMEOUT} SECURITY_POLICY=${IPSEC_DEFAULT_SECURITY_POLICY} if ! ipsec_connection_write_config "${connection}"; then log ERROR "Could not write new config file" return ${EXIT_ERROR} fi # Configure strongswan autostart ipsec_strongswan_autostart } # Function that deletes based on the passed parameters one ore more vpn security policies ipsec_connection_destroy() { local connection for connection in "$@"; do if ! ipsec_connection_exists "${connection}"; then log ERROR "The VPN IPsec connection ${connection} does not exist." continue fi log DEBUG "Deleting VPN IPsec connection ${connection}" # Delete strongswan configuration file file_delete "${NETWORK_IPSEC_SWANCTL_CONNECTIONS_DIR}/${connection}.conf" if ! rm -rf "${NETWORK_IPSEC_CONNS_DIR}/${connection}"; then log ERROR "Deleting the VPN IPsec connection ${connection} was not sucessful" return ${EXIT_ERROR} fi done } # List all ipsec connections ipsec_list_connections() { list_directory "${NETWORK_IPSEC_CONNS_DIR}" } ipsec_connection_to_strongswan() { local connection="${1}" log DEBUG "Generating IPsec configuration for ${connection}" # Read the config settings local ${IPSEC_CONNECTION_CONFIG_SETTINGS} if ! ipsec_connection_read_config "${connection}"; then error "Could not read the connection ${connection}" return ${EXIT_ERROR} fi local path="${NETWORK_IPSEC_SWANCTL_CONNECTIONS_DIR}/${connection}.conf" ( # Write the connection section _ipsec_connection_to_strongswan_connection "${connection}" # Write the secrets section _ipsec_connection_to_strongswan_secrets "${connection}" ) > ${path} } _ipsec_connection_to_strongswan_connection() { local connection="${1}" # Read the security policy local ${VPN_SECURITY_POLICIES_CONFIG_SETTINGS} if ! vpn_security_policies_read_config "${SECURITY_POLICY}"; then return ${EXIT_ERROR} fi # Is DPD enabled? local dpd="false" if isset DPD_DELAY && isinteger DPD_DELAY && [ ${DPD_DELAY} -gt 0 ]; then dpd="true" fi local zone_mode # Are we connected to a zone? if isset ZONE; then # Store MODE local mode="${MODE}" if ! zone_settings_read "${ZONE}" MARK MODE; then log ERROR "Could not read zone settings from ${zone}" return ${EXIT_ERROR} fi local zone_mode="${MODE}" MODE="${mode}" fi # Write configuration header config_header "strongSwan configuration for ${connection}" print_indent 0 "connections {" print_indent 1 "${connection} {" # IKE Version print_indent 2 "# IKE Version" case "${KEY_EXCHANGE^^}" in IKEV1) print_indent 2 "version = 1" ;; # Fall back to IKEv2 for any random values IKEV2|*) print_indent 2 "version = 2" ;; esac print # empty line # Always only keep one connection open at a time print_indent 2 "# Unique IDs" print_indent 2 "unique = replace" print # Local Address print_indent 2 "# Local Address" if isset LOCAL_ADDRESS; then print_indent 2 "local_addrs = ${LOCAL_ADDRESS}" else print_indent 2 "local_addrs = %any" fi print # Remote Address print_indent 2 "# Remote Address" if isset PEER; then print_indent 2 "remote_addrs = ${PEER}" else print_indent 2 "remote_addrs = %any" fi print # IKE Proposals print_indent 2 "# IKE Proposals" print_indent 2 "proposals = $(vpn_security_policies_make_ike_proposal ${SECURITY_POLICY})" print # DPD Settings if enabled dpd; then print_indent 2 "# Dead Peer Detection" print_indent 2 "dpd_delay = ${DPD_DELAY}" if isset DPD_TIMEOUT; then print_indent 2 "dpd_timeout = ${DPD_TIMEOUT}" fi print fi # Fragmentation print_indent 2 "# Fragmentation" print_indent 2 "fragmentation = yes" print # Host-to-Net specific settings case "${TYPE}" in host-to-net) # Pools if isset POOLS; then print_indent 2 "# Pools" print_indent 2 "pools = $(list_join POOLS ", ")" print fi ;; esac # Local print_indent 2 "local {" # Local ID if isset LOCAL_ID; then print_indent 3 "id = ${LOCAL_ID}" fi # Authentication case "${AUTH_MODE}" in PSK) print_indent 3 "auth = psk" ;; esac print_indent 2 "}" print # Remote print_indent 2 "remote {" # Remote ID if isset REMOTE_ID; then print_indent 3 "id = ${REMOTE_ID}" fi # Authentication case "${AUTH_MODE}" in PSK) print_indent 3 "auth = psk" ;; esac print_indent 2 "}" print # Children print_indent 2 "children {" print_indent 3 "${connection} {" print_indent 4 "# ESP Proposals" print_indent 4 "esp_proposals = $(vpn_security_policies_make_esp_proposal ${SECURITY_POLICY})" print # Traffic Selectors case "${MODE},${zone_mode}" in *,gre) print_indent 4 "local_ts = dynamic[gre]" print_indent 4 "remote_ts = dynamic[gre]" ;; *) # Local Prefixes if isset LOCAL_PREFIX; then print_indent 4 "local_ts = $(list_join LOCAL_PREFIX ,)" else print_indent 4 "local_ts = dynamic" fi # Remote Prefixes if isset REMOTE_PREFIX; then print_indent 4 "remote_ts = $(list_join REMOTE_PREFIX ,)" else print_indent 4 "remote_ts = dynamic" fi ;; esac print # Netfilter Marks case "${zone_mode}" in vti) assert isset MARK print_indent 4 "# Netfilter Marks" print_indent 4 "mark_in = ${MARK}" print_indent 4 "mark_out = ${MARK}" print ;; esac # Dead Peer Detection if enabled dpd; then print_indent 4 "# Dead Peer Detection" print_indent 4 "dpd_action = ${DPD_ACTION}" print fi # Rekeying if isset LIFETIME; then print_indent 4 "# Rekey Time" print_indent 4 "rekey_time = ${LIFETIME}" print fi # Updown Script print_indent 4 "updown = ${NETWORK_HELPERS_DIR}/ipsec-updown" print # Mode print_indent 4 "# Mode" case "${MODE}" in transport) print_indent 4 "mode = transport" ;; tunnel) print_indent 4 "mode = tunnel" ;; *) log WARNING "Unsupported IPsec mode: ${mode}" ;; esac print # Compression print_indent 4 "# Compression" if enabled COMPRESSION; then print_indent 4 "ipcomp = yes" else print_indent 4 "ipcomp = no" fi print # Inactivity Timeout if isset INACTIVITY_TIMEOUT; then print_indent 4 "# Inactivity Timeout" print_indent 4 "inactivity = ${INACTIVITY_TIMEOUT}" print fi # Net-to-Net specific settings case "${TYPE}" in net-to-net) # Start Action print_indent 4 "# Start Action" case "${START_ACTION}" in on-demand) print_indent 4 "start_action = trap" print_indent 4 "close_action = trap" ;; wait) print_indent 4 "start_action = none" print_indent 4 "close_action = none" ;; always-on|*) print_indent 4 "start_action = start" print_indent 4 "close_action = start" ;; esac print ;; esac print_indent 3 "}" print_indent 2 "}" print print_indent 1 "}" print_indent 0 "}" print } _ipsec_connection_to_strongswan_secrets() { local connection="${1}" print_indent 0 "secrets {" case "${AUTH_MODE}" in PSK) print_indent 1 "ike {" # Secret print_indent 2 "secret = ${PSK}" # ID if isset REMOTE_ID; then print_indent 2 "id = ${REMOTE_ID}" fi print_indent 1 "}" ;; esac print_indent 0 "}" }