diff --git a/net/openvpn/Makefile b/net/openvpn/Makefile index 7b9a3a84df052b..49956d9f720616 100644 --- a/net/openvpn/Makefile +++ b/net/openvpn/Makefile @@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=openvpn PKG_VERSION:=2.7.1 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_SOURCE_URL:=\ https://build.openvpn.net/downloads/releases/ \ diff --git a/net/openvpn/files/lib/netifd/proto/openvpn.sh b/net/openvpn/files/lib/netifd/proto/openvpn.sh index 2cf5dd718e993a..781d631e3c40da 100755 --- a/net/openvpn/files/lib/netifd/proto/openvpn.sh +++ b/net/openvpn/files/lib/netifd/proto/openvpn.sh @@ -13,6 +13,9 @@ init_proto "$@" } +CONF_DIR="/var/run" +CONF_PREFIX="${CONF_DIR}/openvpn." + # Helper to DRY up repeated option handling in init/setup option_builder() { # option_builder @@ -92,6 +95,7 @@ option_builder() { # Not real config params used by openvpn - only by our proto handler PROTO_BOOLS=' allow_deprecated +defaultroute ipv6 ' @@ -124,11 +128,86 @@ proto_openvpn_init_config() { option_builder add OPENVPN_LIST list } +# rewrite_config_line +# Replace a `config ` line in with `config `. +# Handles absolute path, relative filename, no quotes / single quotes / double quotes, +# and optional trailing whitespace. +rewrite_config_line() { + local dst="$1" + local old="$2" + local new="$3" + local fname="${old##*/}" + + # absolute path, three quote styles + sed -i "s|^\([[:space:]]*config[[:space:]]\+\)${old}[[:space:]]*$|\1${new}|" "$dst" + sed -i "s|^\([[:space:]]*config[[:space:]]\+\)'${old}'[[:space:]]*$|\1${new}|" "$dst" + sed -i "s|^\([[:space:]]*config[[:space:]]\+\)\"${old}\"[[:space:]]*$|\1${new}|" "$dst" + # relative filename, three quote styles + sed -i "s|^\([[:space:]]*config[[:space:]]\+\)${fname}[[:space:]]*$|\1${new}|" "$dst" + sed -i "s|^\([[:space:]]*config[[:space:]]\+\)'${fname}'[[:space:]]*$|\1${new}|" "$dst" + sed -i "s|^\([[:space:]]*config[[:space:]]\+\)\"${fname}\"[[:space:]]*$|\1${new}|" "$dst" +} + +# Recursively copy config files referenced by `config` directives. +# Updates the `config` paths in the destination file to point to the copies. +# Appends all copied files to the global CONFIG_FILES variable. +# +# Usage: copy_config_recursive +# dst_file: the already-copied config file to scan for `config` lines +# visited: pipe-delimited list of source paths already processed (cycle guard) +copy_config_recursive() { + local dst="$1" + local visited="$2" + local ref dst_ref fname + + while IFS= read -r ref; do + # skip empty lines + [ -n "$ref" ] || continue + + # expand relative path to absolute using cd_dir + case "$ref" in + /*) ;; + *) ref="$cd_dir/$ref" ;; + esac + + # cycle guard + case "$visited" in + *"|$ref|"*) continue ;; + esac + + [ -f "$ref" ] || continue + + fname="${ref##*/}" + dst_ref="${CONF_PREFIX}${config}.user_${fname}" + + cp "$ref" "$dst_ref" || { + logger -t "openvpn_$config(proto)" -p daemon.err "failed to copy config '$ref' to '$dst_ref'" + continue + } + + # rewrite the `config` line in the parent file to point to the copy + rewrite_config_line "$dst" "$ref" "$dst_ref" + + # accumulate file list + CONFIG_FILES="$CONFIG_FILES $dst_ref" + + # recurse + copy_config_recursive "$dst_ref" "$visited|$ref|" + + done < "$cp_file" - umask 022 - append exec_params "--askpass $cp_file" - elif [ -n "$askpass" ]; then - append exec_params "--askpass $askpass" - fi - - # combine into --auth-user-pass: - if [ -n "$username" ] || [ -n "$password" ]; then - auth_file="/var/run/openvpn.$config.auth" - umask 077 - printf '%s\n' "${username:-}" "${password:-}" > "$auth_file" - umask 022 - append exec_params "--auth-user-pass $auth_file" - elif [ -n "$auth_user_pass" ]; then - append exec_params "--auth-user-pass $auth_user_pass" - fi - # Testing option # ${tls_exit:+--tls-exit} \ # Check 'script_security' option json_get_var script_security script_security - [ -z "$script_security" ] && script_security=3 + [ -z "$script_security" ] && script_security=2 - # Add default hotplug handling if 'script_security' option is equal '3' - if [ "$script_security" -eq '3' ]; then - local ipv6 + # Add default hotplug handling if 'script_security' option is ge '2' + if [ "$script_security" -ge '2' ]; then local up down route_up route_pre_down local client tls_client tls_server local tls_crypt_v2_verify mode learn_address client_connect local client_crresponse client_disconnect auth_user_pass_verify - logger -t "openvpn(proto)" \ - -p daemon.info "Enabled default hotplug processing, as the openvpn configuration 'script_security' is '3'" - - append exec_params "--setenv INTERFACE $config" - append exec_params "--script-security 3" - json_get_vars up down route_up route_pre_down json_get_vars tls_crypt_v2_verify mode learn_address client_connect json_get_vars client_crresponse client_disconnect auth_user_pass_verify - json_get_vars ipv6 - #default ipv6 is enabled - [ -n "$ipv6" ] || ipv6=1 - append exec_params "--setenv IPV6 '$ipv6'" - json_get_vars ifconfig_noexec route_noexec - [ -z "$ifconfig_noexec" ] && append exec_params "--ifconfig-noexec" - [ -z "$route_noexec" ] && append exec_params "--route-noexec" - append exec_params "--up '/usr/libexec/openvpn-hotplug'" [ -n "$up" ] && append exec_params "--setenv user_up '$up'" - - append exec_params "--down '/usr/libexec/openvpn-hotplug'" [ -n "$down" ] && append exec_params "--setenv user_down '$down'" - - append exec_params "--route-up '/usr/libexec/openvpn-hotplug'" [ -n "$route_up" ] && append exec_params "--setenv user_route_up '$route_up'" - - append exec_params "--route-pre-down '/usr/libexec/openvpn-hotplug'" [ -n "$route_pre_down" ] && append exec_params "--setenv user_route_pre_down '$route_pre_down'" append exec_params "--tls-crypt-v2-verify '/usr/libexec/openvpn-hotplug'" @@ -260,16 +295,97 @@ proto_openvpn_setup() { json_get_var tls_verify tls_verify [ -n "$tls_verify" ] && append exec_params "--setenv user_tls_verify '$tls_verify'" fi - else - logger -t "openvpn(proto)" \ - -p daemon.warn "Default hotplug processing disabled, as the openvpn configuration 'script_security' is less than '3'" fi + # Write first-phase params to conf_file eval "set -- $exec_params" umask 077 printf "%b\n" "${exec_params//--/\\n}" > "$conf_file" umask 022 - proto_run_command "$config" openvpn --config "$conf_file" + + local CONFIG_FILES="$conf_file" + # Copy user config and recursively copy all referenced config files, + # rewriting `config` directives to point to the copies in CONF_DIR. + local user_conf="${CONF_PREFIX}$config.user.conf" + if [ -n "$config_file" -a -e "$config_file" ]; then + cp "$config_file" "$user_conf" || { + logger -t "openvpn_$config(proto)" -p daemon.err "failed to copy config '$config_file'" + return 1 + } + CONFIG_FILES="$CONFIG_FILES $user_conf" + copy_config_recursive "$user_conf" "" + + # Update the `config` reference in conf_file to point to the copied user_conf. + rewrite_config_line "$conf_file" "$config_file" "$user_conf" + fi + + is_openvpn_client() { + grep -qE '^[[:space:]]*remote[[:space:]]+' $CONFIG_FILES && return 0 + } + + local ipv6 defaultroute + exec_params= + + # combine into --askpass: + if [ -n "$cert_password" ]; then + cp_file="${CONF_PREFIX}$config.pass" + umask 077 + printf '%s\n' "${cert_password:-}" > "$cp_file" + umask 022 + append exec_params "--askpass $cp_file" + elif [ -n "$askpass" ]; then + append exec_params "--askpass $askpass" + fi + + # combine into --auth-user-pass: + if [ -n "$username" ] || [ -n "$password" ]; then + auth_file="${CONF_PREFIX}$config.auth" + umask 077 + printf '%s\n' "${username:-}" "${password:-}" > "$auth_file" + umask 022 + append exec_params "--auth-user-pass $auth_file" + elif [ -n "$auth_user_pass" ]; then + append exec_params "--auth-user-pass $auth_user_pass" + fi + + #Always Override Options + append exec_params "--setenv INTERFACE $config" + append exec_params "--script-security 3" + append exec_params "--ifconfig-noexec" + append exec_params "--route-noexec" + append exec_params "--up '/usr/libexec/openvpn-hotplug'" + append exec_params "--down '/usr/libexec/openvpn-hotplug'" + append exec_params "--route-up '/usr/libexec/openvpn-hotplug'" + append exec_params "--route-pre-down '/usr/libexec/openvpn-hotplug'" + append exec_params "--persist-tun" + append exec_params "--persist-key" + + # filter out dup options - applied to all copied config files + sed -i '/^[[:space:]]*script-security[[:space:]]*/s/^/# /' $CONFIG_FILES + sed -i '/^[[:space:]]*ifconfig-noexec[[:space:]]*/s/^/# /' $CONFIG_FILES + sed -i '/^[[:space:]]*route-noexec[[:space:]]*/s/^/# /' $CONFIG_FILES + sed -i '/^[[:space:]]*up[[:space:]]/s/^/# /' $CONFIG_FILES + sed -i '/^[[:space:]]*down[[:space:]]/s/^/# /' $CONFIG_FILES + sed -i '/^[[:space:]]*route-up[[:space:]]/s/^/# /' $CONFIG_FILES + sed -i '/^[[:space:]]*route-pre-down[[:space:]]/s/^/# /' $CONFIG_FILES + sed -i '/^[[:space:]]*persist-tun[[:space:]]*/s/^/# /' $CONFIG_FILES + sed -i '/^[[:space:]]*persist-key[[:space:]]*/s/^/# /' $CONFIG_FILES + + json_get_vars ipv6 defaultroute + #default ipv6 is enabled + [ -n "$ipv6" ] || ipv6=1 + append exec_params "--setenv IPV6 $ipv6" + + if is_openvpn_client; then + append exec_params "--redirect-gateway def1 ipv6" + [ -n "$defaultroute" ] || defaultroute=1 + sed -i '/^[[:space:]]*redirect-gateway[[:space:]]*/s/^/# /' $CONFIG_FILES + else + defaultroute=0 + fi + append exec_params "--setenv DEFAULTROUTE $defaultroute" + + proto_run_command "$config" openvpn $exec_params --config "$conf_file" # last param wins; user provided status or syslog supersedes. } @@ -284,12 +400,14 @@ proto_openvpn_renew() { proto_openvpn_teardown() { local iface="$1" - rm -f \ - "/var/run/openvpn.$iface.conf" \ - "/var/run/openvpn.$iface.pass" \ - "/var/run/openvpn.$iface.auth" \ - "/var/run/openvpn.$iface.status" + proto_kill_command "$iface" + [ -n "$iface" ] && rm -f "${CONF_PREFIX}${iface}."* + + /usr/libexec/openvpn-hotplug cleanup "$iface" + + proto_init_update "*" 0 + proto_send_update "$iface" } [ -n "$INCLUDE_ONLY" ] || { diff --git a/net/openvpn/files/usr/libexec/openvpn-hotplug b/net/openvpn/files/usr/libexec/openvpn-hotplug index 96734f6532af98..9c7f1cd0b6882c 100644 --- a/net/openvpn/files/usr/libexec/openvpn-hotplug +++ b/net/openvpn/files/usr/libexec/openvpn-hotplug @@ -1,5 +1,10 @@ #!/bin/sh +if [ "$1" = "cleanup" ]; then + script_type=cleanup + INTERFACE="$2" +fi + [ -z "$script_type" ] && { logger -t "openvpn(proto)" -p daemon.warn "hotplug: variable 'script_type' not found" exit @@ -10,8 +15,14 @@ exit } +exec 2> >(logger -t "openvpn_$INTERFACE(hotplug)" -p daemon.err) + . /lib/functions.sh . /lib/netifd/netifd-proto.sh +. /usr/share/libubox/jshn.sh + +SERVER_ROUTE_IPV4_CLEANUP_CMD= +SERVER_ROUTE_IPV6_CLEANUP_CMD= mask2prefix() { local mask="$1" @@ -43,19 +54,84 @@ parse_cidr6() { echo "$addr $plen" } +route_path_for_target() { + local family="$1" + local target="$2" + local output key + + route_path_gateway= + route_path_dev= + + [ -n "$target" ] || return 1 + + output="$(ip "-$family" route get "$target" 2>/dev/null)" || return 1 + + set -- $output + while [ "$#" -gt 0 ]; do + key="$1" + case "$key" in + via) + shift + route_path_gateway="$1" + ;; + dev) + shift + route_path_dev="$1" + ;; + esac + [ "$#" -gt 0 ] && shift + done + + [ -n "$route_path_gateway$route_path_dev" ] +} + +server_route_exists() { + local family="$1" + local prefix="$2" + local target="$3" + local output + + [ -n "$target" ] || return 1 + + output="$(ip "-$family" route show "$target/$prefix" 2>/dev/null)" + [ -n "$output" ] +} + +is_openvpn_default_route() +{ + case $1 in + 0.0.0.0/0|\ + 0.0.0.0/1|\ + 128.0.0.0/1|\ + ::/0|\ + ::/1|\ + 8000::/1|\ + ::/3|\ + 2000::/3|\ + 3000::/4|\ + 2000::/4|\ + fc00::/7) + return 0 + esac + return 1 +} + case "$script_type" in up) proto_init_update "$dev" 1 + [ -n "$tun_mtu" ] && /sbin/ip link set dev $dev mtu $tun_mtu + [ -n "$ifconfig_local" ] && proto_add_ipv4_address "$ifconfig_local" "${ifconfig_netmask:-255.255.255.255}" - [ -n "$trusted_ip" ] && { - if [ -n "$route_net_gateway" -a "$route_net_gateway" != "0.0.0.0" ]; then - proto_add_ipv4_route "$trusted_ip" 32 "$route_net_gateway" + if [ -n "$trusted_ip" -a "$DEFAULTROUTE" = "1" ]; then + if route_path_for_target 4 "$trusted_ip" && ! server_route_exists 4 32 "$trusted_ip"; then + ip -4 route add $trusted_ip ${route_path_gateway:+via $route_path_gateway} ${route_path_dev:+dev $route_path_dev} + SERVER_ROUTE_IPV4_CLEANUP_CMD="ip -4 route del $trusted_ip ${route_path_gateway:+via $route_path_gateway} ${route_path_dev:+dev $route_path_dev}" fi - } + fi - [ -n "$route_vpn_gateway" ] && proto_add_ipv4_route "0.0.0.0" 0 "$route_vpn_gateway" + [ "$DEFAULTROUTE" = "1" ] && [ -n "$route_vpn_gateway" ] && proto_add_ipv4_route "0.0.0.0" 0 "$route_vpn_gateway" i=0 while :; do @@ -65,6 +141,8 @@ case "$script_type" in [ -z "$mask" ] && continue plen=$(mask2prefix "$mask") + is_openvpn_default_route "$net/$plen" && continue + proto_add_ipv4_route "$net" "$plen" "$gw" done @@ -76,13 +154,14 @@ case "$script_type" in proto_add_ipv6_address "$v6addr" "$v6plen" fi - [ -n "$trusted_ip6" ] && { - if [ -n "$route_ipv6_gateway" -a "$route_ipv6_gateway" != "::" ]; then - proto_add_ipv6_route "$trusted_ip6" 128 "$route_ipv6_gateway" + if [ -n "$trusted_ip6" -a "$DEFAULTROUTE" = "1" ]; then + if route_path_for_target 6 "$trusted_ip6" && ! server_route_exists 6 128 "$trusted_ip6"; then + ip -6 route add $trusted_ip6 ${route_path_gateway:+via $route_path_gateway} ${route_path_dev:+dev $route_path_dev} + SERVER_ROUTE_IPV6_CLEANUP_CMD="ip -6 route del $trusted_ip6 ${route_path_gateway:+via $route_path_gateway} ${route_path_dev:+dev $route_path_dev}" fi - } + fi - [ -n "$ifconfig_ipv6_remote" ] && proto_add_ipv6_route "::" 0 "$ifconfig_ipv6_remote" + [ "$DEFAULTROUTE" = "1" ] && [ -n "$ifconfig_ipv6_remote" ] && proto_add_ipv6_route "::" 0 "$ifconfig_ipv6_remote" i=0 while :; do @@ -93,12 +172,12 @@ case "$script_type" in read -r v6net v6plen <<-EOF $(parse_cidr6 "$net" 128) EOF + is_openvpn_default_route "$v6net/$v6plen" && continue + proto_add_ipv6_route "$v6net" "$v6plen" "$gw" done fi - [ -n "$tun_mtu" ] && json_add_int mtu "$tun_mtu" - i=0 while :; do i=$((i+1)) @@ -114,13 +193,26 @@ case "$script_type" in esac done - proto_send_update "$INTERFACE" - ;; + proto_add_data + json_add_string "daemon_pid" "$daemon_pid" + json_add_string "ifname" "$dev" + [ -n "$SERVER_ROUTE_IPV4_CLEANUP_CMD" ] && json_add_string "server_route_ipv4_cleanup_cmd" "$SERVER_ROUTE_IPV4_CLEANUP_CMD" + [ -n "$SERVER_ROUTE_IPV6_CLEANUP_CMD" ] && json_add_string "server_route_ipv6_cleanup_cmd" "$SERVER_ROUTE_IPV6_CLEANUP_CMD" + proto_close_data - down) - proto_init_update "$dev" 0 proto_send_update "$INTERFACE" ;; + cleanup) + json_data="$(ubus call network.interface."$INTERFACE" status 2>/dev/null)" + if json_load "$json_data" 2>/dev/null; then + if json_select data 2>/dev/null; then + json_get_var SERVER_ROUTE_IPV4_CLEANUP_CMD server_route_ipv4_cleanup_cmd + json_get_var SERVER_ROUTE_IPV6_CLEANUP_CMD server_route_ipv6_cleanup_cmd + [ -n "$SERVER_ROUTE_IPV4_CLEANUP_CMD" ] && $SERVER_ROUTE_IPV4_CLEANUP_CMD + [ -n "$SERVER_ROUTE_IPV6_CLEANUP_CMD" ] && $SERVER_ROUTE_IPV6_CLEANUP_CMD + fi + fi + ;; esac ACTION="$script_type"