diff --git a/net/openconnect/Makefile b/net/openconnect/Makefile index 9ba580c50..1e1ba66aa 100644 --- a/net/openconnect/Makefile +++ b/net/openconnect/Makefile @@ -20,7 +20,7 @@ include $(INCLUDE_DIR)/package.mk define Package/openconnect SECTION:=net CATEGORY:=Network - DEPENDS:=+libxml2 +libopenssl +kmod-tun +vpnc-scripts + DEPENDS:=+libxml2 +libopenssl +kmod-tun +resolveip +!PACKAGE_netifd:vpnc-scripts TITLE:=VPN client for Cisco's AnyConnect SSL VPN URL:=http://www.infradead.org/openconnect/ SUBMENU:=VPN @@ -35,13 +35,26 @@ define Package/openconnect/description endef CONFIGURE_ARGS+=--disable-shared +ifdef CONFIG_PACKAGE_netifd + CONFIGURE_ARGS += --with-vpnc-script=/lib/netifd/vpnc-script -define Package/openconnect/install - $(INSTALL_DIR) $(1)/lib/network - $(INSTALL_BIN) ./files/openconnect.sh $(1)/lib/network/ + define Package/openconnect/install + $(INSTALL_DIR) $(1)/lib/netifd/proto + $(INSTALL_BIN) ./files/openconnect.sh $(1)/lib/netifd/proto/ + $(INSTALL_BIN) ./files/vpnc-script $(1)/lib/netifd/ $(INSTALL_DIR) $(1)/usr/sbin - $(INSTALL_BIN) ./files/run-openconnect $(1)/usr/sbin/ $(INSTALL_BIN) $(PKG_BUILD_DIR)/openconnect $(1)/usr/sbin/ -endef + endef +else + CONFIGURE_ARGS += --with-vpnc-script=/etc/vpnc/vpnc-script + + define Package/openconnect/install + $(INSTALL_DIR) $(1)/lib/network + $(INSTALL_BIN) ./files.old/openconnect.sh $(1)/lib/network/ + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) ./files.old/run-openconnect $(1)/usr/sbin/ + $(INSTALL_BIN) $(PKG_BUILD_DIR)/openconnect $(1)/usr/sbin/ + endef +endif $(eval $(call BuildPackage,openconnect)) diff --git a/net/openconnect/files.old/openconnect.sh b/net/openconnect/files.old/openconnect.sh new file mode 100755 index 000000000..13221abaf --- /dev/null +++ b/net/openconnect/files.old/openconnect.sh @@ -0,0 +1,39 @@ +find_gw() { + route -n | awk '$1 == "0.0.0.0" { print $2; exit }' +} + +scan_openconnect() { + config_set "$1" device "vpn-$1" +} + +stop_interface_openconnect() { + local config="$1" + local lock="/var/lock/openconnect-$config" + + uci_set_state network "$config" up 0 + + lock "$lock" + + SERVICE_PID_FILE="/var/run/openconnect-${config}.pid" \ + SERVICE_SIG=HUP service_stop /bin/sh + + remove_dns "$config" + + lock -u "$lock" +} + +setup_interface_openconnect() { + local config="$2" + + /sbin/insmod tun 2>&- >&- + + # creating the tunnel below will trigger a net subsystem event + # prevent it from touching or iface by disabling .auto here + uci_set_state network "$config" ifname "vpn-$config" + uci_set_state network "$config" auto 0 + uci_set_state network "$config" up 1 + + SERVICE_PID_FILE="/var/run/openconnect-${config}.pid" \ + SERVICE_WRITE_PID=1 SERVICE_DAEMONIZE=1 \ + service_start /usr/sbin/run-openconnect $config +} diff --git a/net/openconnect/files/run-openconnect b/net/openconnect/files.old/run-openconnect similarity index 100% rename from net/openconnect/files/run-openconnect rename to net/openconnect/files.old/run-openconnect diff --git a/net/openconnect/files/openconnect.sh b/net/openconnect/files/openconnect.sh index 13221abaf..f99b34ea1 100755 --- a/net/openconnect/files/openconnect.sh +++ b/net/openconnect/files/openconnect.sh @@ -1,39 +1,56 @@ -find_gw() { - route -n | awk '$1 == "0.0.0.0" { print $2; exit }' +#!/bin/sh +. /etc/functions.sh +. ../netifd-proto.sh +init_proto "$@" + +proto_openconnect_init_config() { + proto_config_add_string "server" + proto_config_add_int "port" + proto_config_add_string "username" + proto_config_add_string "cookie" + proto_config_add_string "password" + no_device=1 + available=1 } -scan_openconnect() { - config_set "$1" device "vpn-$1" -} - -stop_interface_openconnect() { +proto_openconnect_setup() { local config="$1" - local lock="/var/lock/openconnect-$config" - uci_set_state network "$config" up 0 + json_get_vars server port username cookie password - lock "$lock" + grep -q tun /proc/modules || insmod tun - SERVICE_PID_FILE="/var/run/openconnect-${config}.pid" \ - SERVICE_SIG=HUP service_stop /bin/sh + serv_addr= + for ip in $(resolveip -t 5 "$server"); do + proto_add_host_dependency "$config" "$server" + serv_addr=1 + done + [ -n "$serv_addr" ] || { + echo "Could not resolve server address" + sleep 5 + proto_setup_failed "$config" + exit 1 + } - remove_dns "$config" + [ -n "$port" ] && port=":$port" - lock -u "$lock" + cmdline="$server$port -i vpn-$config --no-cert-check --non-inter --syslog --script /lib/netifd/vpnc-script" + + [ -n "$cookie" ] && append cmdline "-C $cookie" + [ -n "$username" ] && append cmdline "-u $username" + [ -n "$password" ] && { + umask 077 + pwfile="/var/run/openconnect-$config.passwd" + echo "$password" > "$pwfile" + append cmdline "--passwd-file=$pwfile" + } + + proto_export INTERFACE="$config" + proto_run_command "$config" /usr/sbin/openconnect $cmdline } -setup_interface_openconnect() { - local config="$2" - - /sbin/insmod tun 2>&- >&- - - # creating the tunnel below will trigger a net subsystem event - # prevent it from touching or iface by disabling .auto here - uci_set_state network "$config" ifname "vpn-$config" - uci_set_state network "$config" auto 0 - uci_set_state network "$config" up 1 - - SERVICE_PID_FILE="/var/run/openconnect-${config}.pid" \ - SERVICE_WRITE_PID=1 SERVICE_DAEMONIZE=1 \ - service_start /usr/sbin/run-openconnect $config +proto_openconnect_teardown() { + proto_kill_command "$config" } + +add_protocol openconnect diff --git a/net/openconnect/files/vpnc-script b/net/openconnect/files/vpnc-script new file mode 100755 index 000000000..4d12d7e20 --- /dev/null +++ b/net/openconnect/files/vpnc-script @@ -0,0 +1,156 @@ +#!/bin/sh +# List of parameters passed through environment +#* reason -- why this script was called, one of: pre-init connect disconnect +#* VPNGATEWAY -- vpn gateway address (always present) +#* TUNDEV -- tunnel device (always present) +#* INTERNAL_IP4_ADDRESS -- address (always present) +#* INTERNAL_IP4_MTU -- mtu (often unset) +#* INTERNAL_IP4_NETMASK -- netmask (often unset) +#* INTERNAL_IP4_NETMASKLEN -- netmask length (often unset) +#* INTERNAL_IP4_NETADDR -- address of network (only present if netmask is set) +#* INTERNAL_IP4_DNS -- list of dns servers +#* INTERNAL_IP4_NBNS -- list of wins servers +#* INTERNAL_IP6_ADDRESS -- IPv6 address +#* INTERNAL_IP6_NETMASK -- IPv6 netmask +#* INTERNAL_IP6_DNS -- IPv6 list of dns servers +#* CISCO_DEF_DOMAIN -- default domain name +#* CISCO_BANNER -- banner from server +#* CISCO_SPLIT_INC -- number of networks in split-network-list +#* CISCO_SPLIT_INC_%d_ADDR -- network address +#* CISCO_SPLIT_INC_%d_MASK -- subnet mask (for example: 255.255.255.0) +#* CISCO_SPLIT_INC_%d_MASKLEN -- subnet masklen (for example: 24) +#* CISCO_SPLIT_INC_%d_PROTOCOL -- protocol (often just 0) +#* CISCO_SPLIT_INC_%d_SPORT -- source port (often just 0) +#* CISCO_SPLIT_INC_%d_DPORT -- destination port (often just 0) +#* CISCO_IPV6_SPLIT_INC -- number of networks in IPv6 split-network-list +#* CISCO_IPV6_SPLIT_INC_%d_ADDR -- IPv6 network address +#* CISCO_IPV6_SPLIT_INC_$%d_MASKLEN -- IPv6 subnet masklen + +# FIXMEs: + +# Section A: route handling + +# 1) The 3 values CISCO_SPLIT_INC_%d_PROTOCOL/SPORT/DPORT are currently being ignored +# In order to use them, we'll probably need os specific solutions +# * Linux: iptables -t mangle -I PREROUTING -j ROUTE --oif $TUNDEV +# This would be an *alternative* to changing the routes (and thus 2) and 3) +# shouldn't be relevant at all) +# 2) There are two different functions to set routes: generic routes and the +# default route. Why isn't the defaultroute handled via the generic route case? +# 3) In the split tunnel case, all routes but the default route might get replaced +# without getting restored later. We should explicitely check and save them just +# like the defaultroute +# 4) Replies to a dhcp-server should never be sent into the tunnel + +# Section B: Split DNS handling + +# 1) Maybe dnsmasq can do something like that +# 2) Parse dns packets going out via tunnel and redirect them to original dns-server + +do_connect() { + if [ -n "$CISCO_BANNER" ]; then + echo "Connect Banner:" + echo "$CISCO_BANNER" | while read LINE ; do echo "|" "$LINE" ; done + echo + fi + + proto_init_update "$TUNDEV" 1 + + if [ -n "$INTERNAL_IP4_MTU" ]; then + MTU=$INTERNAL_IP4_MTU + fi + + if [ -z "$MTU" ]; then + MTU=1412 + fi + + proto_add_ipv4_address "$INTERNAL_IP4_ADDRESS" 32 "" "$INTERNAL_IP4_ADDRESS" + + if [ -n "$INTERNAL_IP4_NETMASKLEN" ]; then + proto_add_ipv4_route "$INTERNAL_IP4_NETADDR" "$INTERNAL_IP4_NETMASKLEN" + fi + + # If the netmask is provided, it contains the address _and_ netmask + if [ -n "$INTERNAL_IP6_ADDRESS" ] && [ -z "$INTERNAL_IP6_NETMASK" ]; then + INTERNAL_IP6_NETMASK="$INTERNAL_IP6_ADDRESS/128" + fi + + if [ -n "$INTERNAL_IP6_NETMASK" ]; then + addr="${INTERNAL_IP6_NETMASK%%/*}" + mask="${INTERNAL_IP6_NETMASK##*/}" + [[ "$addr" != "$mask" ]] && proto_add_ipv6_address "$addr" "$mask" + fi + + [ -n "$INTERNAL_IP4_DNS" ] && proto_add_dns_server "$INTERNAL_IP4_DNS" + [ -n "$CISCO_DEF_DOMAIN" ] && proto_add_dns_search "$CISCO_DEF_DOMAIN" + + if [ -n "$CISCO_SPLIT_INC" ]; then + i=0 + while [ $i -lt $CISCO_SPLIT_INC ] ; do + eval NETWORK="\${CISCO_SPLIT_INC_${i}_ADDR}" + eval NETMASK="\${CISCO_SPLIT_INC_${i}_MASK}" + eval NETMASKLEN="\${CISCO_SPLIT_INC_${i}_MASKLEN}" + if [ $NETWORK != "0.0.0.0" ]; then + proto_add_ipv4_route "$NETWORK" "$NETMASKLEN" + else + proto_add_ipv4_route "0.0.0.0" 0 + fi + i=$(($i + 1)) + done + elif [ -n "$INTERNAL_IP4_ADDRESS" ]; then + proto_add_ipv4_route "0.0.0.0" 0 + fi + if [ -n "$CISCO_IPV6_SPLIT_INC" ]; then + i=0 + while [ $i -lt $CISCO_IPV6_SPLIT_INC ] ; do + eval NETWORK="\${CISCO_IPV6_SPLIT_INC_${i}_ADDR}" + eval NETMASKLEN="\${CISCO_IPV6_SPLIT_INC_${i}_MASKLEN}" + if [ $NETMASKLEN -lt 128 ]; then + proto_add_ipv6_route "$NETWORK" "$NETMASKLEN" + else + proto_add_ipv6_route "::0" 0 + fi + i=$(($i + 1)) + done + elif [ -n "$INTERNAL_IP6_NETMASK" -o -n "$INTERNAL_IP6_ADDRESS" ]; then + proto_add_ipv6_route "::0" 0 + fi + proto_send_update "$INTERFACE" +} + +do_disconnect() { + proto_init_update "$TUNDEV" 0 + proto_send_update "$INTERFACE" +} + +#### Main + +if [ -z "$reason" ]; then + echo "this script must be called from vpnc" 1>&2 + exit 1 +fi +if [ -z "$INTERFACE" ]; then + echo "this script must be called for an active interface" + exit 1 +fi + +. /lib/netifd/netifd-proto.sh + +case "$reason" in + pre-init) + ;; + connect) + do_connect + ;; + disconnect) + do_disconnect + ;; + reconnect) + ;; + *) + echo "unknown reason '$reason'. Maybe vpnc-script is out of date" 1>&2 + exit 1 + ;; +esac + +exit 0 diff --git a/net/openconnect/patches/100-passwd_file.patch b/net/openconnect/patches/100-passwd_file.patch new file mode 100644 index 000000000..3e074d54e --- /dev/null +++ b/net/openconnect/patches/100-passwd_file.patch @@ -0,0 +1,100 @@ +--- a/main.c ++++ b/main.c +@@ -77,6 +77,7 @@ enum { + OPT_CAFILE, + OPT_COOKIEONLY, + OPT_COOKIE_ON_STDIN, ++ OPT_COOKIE_FILE, + OPT_CSD_USER, + OPT_CSD_WRAPPER, + OPT_DISABLE_IPV6, +@@ -91,6 +92,7 @@ enum { + OPT_NO_PROXY, + OPT_PIDFILE, + OPT_PASSWORD_ON_STDIN, ++ OPT_PASSWORD_FILE, + OPT_PRINTCOOKIE, + OPT_RECONNECT_TIMEOUT, + OPT_SERVERCERT, +@@ -139,7 +141,9 @@ static struct option long_options[] = { + OPTION("queue-len", 1, 'Q'), + OPTION("xmlconfig", 1, 'x'), + OPTION("cookie-on-stdin", 0, OPT_COOKIE_ON_STDIN), ++ OPTION("cookie-file", 1, OPT_COOKIE_FILE), + OPTION("passwd-on-stdin", 0, OPT_PASSWORD_ON_STDIN), ++ OPTION("passwd-file", 1, OPT_PASSWORD_FILE), + OPTION("no-passwd", 0, OPT_NO_PASSWD), + OPTION("reconnect-timeout", 1, OPT_RECONNECT_TIMEOUT), + OPTION("dtls-ciphers", 1, OPT_DTLS_CIPHERS), +@@ -177,6 +181,7 @@ static void usage(void) + printf(" -K, --key-type=TYPE %s\n", _("Private key type (PKCS#12 / TPM / PEM)")); + printf(" -C, --cookie=COOKIE %s\n", _("Use WebVPN cookie COOKIE")); + printf(" --cookie-on-stdin %s\n", _("Read cookie from standard input")); ++ printf(" --cookie-file=FILE %s\n", _("Read cookie from a file")); + printf(" -d, --deflate %s\n", _("Enable compression (default)")); + printf(" -D, --no-deflate %s\n", _("Disable compression")); + printf(" --force-dpd=INTERVAL %s\n", _("Set minimum Dead Peer Detection interval")); +@@ -217,6 +222,7 @@ static void usage(void) + printf(" --no-cert-check %s\n", _("Do not require server SSL cert to be valid")); + printf(" --non-inter %s\n", _("Do not expect user input; exit if it is required")); + printf(" --passwd-on-stdin %s\n", _("Read password from standard input")); ++ printf(" --passwd-file=FILE %s\n", _("Read password from a file")); + printf(" --reconnect-timeout %s\n", _("Connection retry timeout in seconds")); + printf(" --servercert=FINGERPRINT %s\n", _("Server's certificate SHA1 fingerprint")); + printf(" --useragent=STRING %s\n", _("HTTP header User-Agent: field")); +@@ -226,15 +232,28 @@ static void usage(void) + exit(1); + } + +-static void read_stdin(char **string) ++static void read_file(const char *file, char **string) + { + char *c = malloc(100); ++ FILE *f; ++ ++ if (file) { ++ f = fopen(file, "r"); ++ if (!f) { ++ fprintf(stderr, _("Failed to open password file\n")); ++ exit(1); ++ } ++ } else { ++ file = "stdin"; ++ f = stdin; ++ } ++ + if (!c) { +- fprintf(stderr, _("Allocation failure for string from stdin\n")); ++ fprintf(stderr, _("Allocation failure for string from %s\n"), file); + exit(1); + } +- if (!fgets(c, 100, stdin)) { +- perror(_("fgets (stdin)")); ++ if (!fgets(c, 100, f)) { ++ perror(_("fgets")); + exit(1); + } + +@@ -332,14 +351,20 @@ int main(int argc, char **argv) + cookieonly = 2; + break; + case OPT_COOKIE_ON_STDIN: +- read_stdin(&vpninfo->cookie); ++ optarg = NULL; ++ /* fall through */ ++ case OPT_COOKIE_FILE: ++ read_file(optarg, &vpninfo->cookie); + /* If the cookie is empty, ignore it */ + if (! *vpninfo->cookie) { + vpninfo->cookie = NULL; + } + break; + case OPT_PASSWORD_ON_STDIN: +- read_stdin(&vpninfo->password); ++ optarg = NULL; ++ /* fall through */ ++ case OPT_PASSWORD_FILE: ++ read_file(optarg, &vpninfo->password); + break; + case OPT_NO_PASSWD: + vpninfo->nopasswd = 1;