diff --git a/net/openfortivpn/Makefile b/net/openfortivpn/Makefile index 1ae52eb80..d81897995 100644 --- a/net/openfortivpn/Makefile +++ b/net/openfortivpn/Makefile @@ -32,7 +32,7 @@ define Package/openfortivpn CATEGORY:=Network TITLE:=Fortinet SSL VPN client URL:=https://github.com/adrienverge/openfortivpn - DEPENDS:=+ppp +libopenssl + DEPENDS:=+ppp +libopenssl +resolveip endef define Package/openfortivpn/description @@ -50,19 +50,16 @@ CONFIGURE_ARGS += \ TARGET_LDFLAGS += -Wl,--gc-sections,--as-needed -define Package/openfortivpn/conffiles -/etc/config/openfortivpn -endef - define Package/openfortivpn/install $(INSTALL_DIR) \ $(1)/usr/sbin \ - $(1)/etc/config \ - $(1)/etc/init.d + $(1)/lib/netifd/proto \ + $(1)/etc/hotplug.d/iface $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/openfortivpn $(1)/usr/sbin/ - $(INSTALL_DATA) ./files/openfortivpn.config $(1)/etc/config/openfortivpn - $(INSTALL_BIN) ./files/openfortivpn.init $(1)/etc/init.d/openfortivpn + $(INSTALL_BIN) ./files/openfortivpn-wrapper $(1)/usr/sbin/ + $(INSTALL_BIN) ./files/openfortivpn.sh $(1)/lib/netifd/proto/ + $(INSTALL_BIN) ./files/14-openforticlient $(1)/etc/hotplug.d/iface/ endef $(eval $(call BuildPackage,openfortivpn)) diff --git a/net/openfortivpn/files/14-openforticlient b/net/openfortivpn/files/14-openforticlient new file mode 100644 index 000000000..336e05a9c --- /dev/null +++ b/net/openfortivpn/files/14-openforticlient @@ -0,0 +1,18 @@ +#!/bin/sh +. /usr/share/libubox/jshn.sh +[ "$ACTION" != ifup ] && exit + +networks=$(uci show network | sed "s/network.\([^.]*\).proto='openfortivpn'/\1/;t;d") +for i in $networks; do + iface=$(uci get "network.${i}.iface_name") + iface_success=$? + [ $? -eq 0 ] && [ $INTERFACE == "$iface" ] && { + logger -t "openfortivpnhotplug" "$ACTION on $INTERFACE to bring up $i" + json_load "$(ifstatus $i)" + json_get_var autostart autostart + [ "$autostart" -eq 0 ] && { + logger -t "openfortivpnhotplug" "auto-start was false. bringing $i up" + ubus call network.interface up "{ \"interface\" : \"$i\" }" + } + } +done diff --git a/net/openfortivpn/files/openfortivpn-wrapper b/net/openfortivpn/files/openfortivpn-wrapper new file mode 100755 index 000000000..a64d94d83 --- /dev/null +++ b/net/openfortivpn/files/openfortivpn-wrapper @@ -0,0 +1,13 @@ +#!/bin/sh + +# This script wraps openfortivpn in order to obtain the password +# file from cmd and to daemonize + +# $1 password file +# $2... are passed to openconnect + +test -z "$1" && exit 1 + +pwfile=$1 +shift +exec /usr/sbin/openfortivpn "$@" < $pwfile \ No newline at end of file diff --git a/net/openfortivpn/files/openfortivpn.config b/net/openfortivpn/files/openfortivpn.config deleted file mode 100644 index 108e3eb7e..000000000 --- a/net/openfortivpn/files/openfortivpn.config +++ /dev/null @@ -1,12 +0,0 @@ -config service 'openfortivpn' - option 'enabled' '0' - option 'host' 'vpn-gateway' - option 'port' '10443' - option 'set_routes' '0' - option 'set_dns' '0' - option 'pppd_use_peerdns' '0' - option 'username' 'foo' - option 'password' 'bar' -config 'certs' -# example X509 certificate sha256 sum, trust only defined one(s)! - option 'trusted_cert' 'e46d4aff08ba6914e64daa85bc6112a422fa7ce16631bff0b592a28556f993db' diff --git a/net/openfortivpn/files/openfortivpn.init b/net/openfortivpn/files/openfortivpn.init deleted file mode 100644 index e9fdc20d5..000000000 --- a/net/openfortivpn/files/openfortivpn.init +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/sh /etc/rc.common - -START=99 -USE_PROCD=1 -BIN=/usr/sbin/openfortivpn -CONFIG=/var/etc/openfortivpn.config - - -validate_certs_section() { - uci_load_validate openfortivpn certs "$1" "$2" \ - 'trusted_cert:string' -} - -validate_openfortivpn_section() { - uci_load_validate openfortivpn service "$1" "$2" \ - 'enabled:uinteger' \ - 'host:string' \ - 'port:uinteger' \ - 'username:string' \ - 'password:string' \ - 'set_routes:uinteger' \ - 'set_dns:uinteger' \ - 'pppd_use_peerdns:uinteger' -} - -setup_certs() { - [ "$2" = 0 ] || { - echo "validation failed" - return 1 - } - - [ -n "$trusted_cert" ] || return 0 - echo "trusted-cert = $trusted_cert" >> $CONFIG -} - -setup_config() { - [ "$2" = 0 ] || { - echo "validation failed" - return 1 - } - - [ "$enabled" -eq 0 ] && return 1 - - mkdir -p /var/etc - echo '# auto-generated config file from /etc/config/openfortivpn' > $CONFIG - - [ -n "$host" ] && echo "host = $host" >> $CONFIG - [ -n "$port" ] && echo "port = $port" >> $CONFIG - [ -n "$username" ] && echo "username = $username" >> $CONFIG - [ -n "$password" ] && echo "password = $password" >> $CONFIG - [ -n "$set_routes" ] && echo "set-routes = $set_routes" >> $CONFIG - [ -n "$set_dns" ] && echo "set-dns = $set_dns" >> $CONFIG - [ -n "$pppd_use_peerdns" ] && echo "pppd-use-peerdns = $pppd_use_peerdns" >> $CONFIG - return 0 -} - -start_service() { - config_load openfortivpn - validate_openfortivpn_section openfortivpn setup_config || return - config_foreach validate_certs_section certs setup_certs - - procd_open_instance - procd_set_param stderr 1 - procd_set_param command $BIN -c $CONFIG --use-syslog - procd_close_instance -} - -service_triggers () { - procd_add_reload_trigger "openfortivpn" - - procd_open_validate - validate_openfortivpn_section - validate_certs_section - procd_close_validate -} diff --git a/net/openfortivpn/files/openfortivpn.sh b/net/openfortivpn/files/openfortivpn.sh new file mode 100755 index 000000000..d69575230 --- /dev/null +++ b/net/openfortivpn/files/openfortivpn.sh @@ -0,0 +1,139 @@ +#!/bin/sh +. /lib/functions.sh +. ../netifd-proto.sh +init_proto "$@" + +append_args() { + while [ $# -gt 0 ]; do + append cmdline "'${1//\'/\'\\\'\'}'" + shift + done +} + +proto_openfortivpn_init_config() { + proto_config_add_string "server" + proto_config_add_int "port" + proto_config_add_string "iface_name" + proto_config_add_string "local_ip" + proto_config_add_string "username" + proto_config_add_string "password" + proto_config_add_string "trusted_cert" + proto_config_add_int "set_dns" + proto_config_add_int "pppd_use_peerdns" + proto_config_add_int "metric" + no_device=1 + available=1 +} + +proto_openfortivpn_setup() { + local config="$1" + + json_get_vars host server port iface_name local_ip username password trusted_cert set_dns pppd_use_peerdns metric + + ifname="vpn-$config" + + logger -t openfortivpn "$config: initializing..." + + [ -n "$iface_name" ] && { + json_load "$(ifstatus $iface_name)" + json_get_var iface_device_name device + json_get_var iface_device_up up + } + + logger -t "openfortivpn" "$config: $iface_name is status $iface_device_up" + [ "$iface_device_up" -eq 1 ] || { + logger -t "openfortivpn" "$config: $iface_name is not up $iface_device_up" + proto_notify_error "$config" "$iface_name is not up $iface_device_up" + proto_block_restart "$config" + exit 1 + } + + + server_ip=$(resolveip -t 10 "$server") + + [ $? -eq 0 ] || { + logger -t "openfortivpn" "$config: failed to resolve server ip for $server" + sleep 10 + proto_notify_error "$config" "failed to resolve server ip for $server" + proto_setup_failed "$config" + exit 1 + } + + [ -n $iface_name ] && { + ping -I $iface_device_name -c 1 -w 10 $server_ip > /dev/null 2>&1 || { + logger -t "openfortivpn" "$config: failed to ping $server_ip on $iface_device_name" + sleep 10 + proto_notify_error "$config" "failed to ping $server_ip on $iface_device_name" + proto_setup_failed "$config" + exit 1 + } + } + + for ip in $(resolveip -t 10 "$server"); do + logger -t "openfortivpn" "$config: adding host dependency for $ip on $iface_name at $config" + proto_add_host_dependency "$config" "$ip" "$iface_name" + done + + + + [ -n "$port" ] && port=":$port" + + append_args "$server$port" --pppd-ifname="$ifname" --use-syslog -c /dev/null + append_args "--set-dns=$set_dns" + append_args "--no-routes" + append_args "--pppd-use-peerdns=$pppd_use_peerdns" + + [ -n "$iface_name" ] && { + append_args "--ifname=$iface_device_name" + } + + [ -n "$trusted_cert" ] && append_args "--trusted-cert=$trusted_cert" + [ -n "$username" ] && append_args -u "$username" + [ -n "$password" ] && { + umask 077 + mkdir -p /var/etc + pwfile="/var/etc/openfortivpn-$config.passwd" + echo "$password" > "$pwfile" + } + + [ -n "$local_ip" ] || local_ip=192.0.2.1 + mkdir -p '/etc/ppp/peers' + callfile="/etc/ppp/peers/$config" + echo "115200 +:$local_ip +noipdefault +noaccomp +noauth +default-asyncmap +nopcomp +receive-all +defaultroute +nodetach +ipparam $config +lcp-max-configure 40 +ip-up-script /lib/netifd/ppp-up +ip-down-script /lib/netifd/ppp-down +mru 1354" > $callfile + append_args "--pppd-call=$config" + + proto_export INTERFACE="$ifname" + logger -t openfortivpn "$config: executing 'openfortivpn $cmdline'" + logger -t openfortivpn "$config: metric is $metric" + + eval "proto_run_command '$config' /usr/sbin/openfortivpn-wrapper '$pwfile' $cmdline" + +} + +proto_openfortivpn_teardown() { + local config="$1" + + pwfile="/var/etc/openfortivpn-$config.passwd" + callfile="/etc/ppp/peers/$config" + + rm -f $pwfile + rm -f $callfile + logger -t openfortivpn "$config: bringing down openfortivpn" + proto_kill_command "$config" 2 +} + +add_protocol openfortivpn diff --git a/net/openfortivpn/patches/010-bind-iface.patch b/net/openfortivpn/patches/010-bind-iface.patch new file mode 100644 index 000000000..76bc8c84f --- /dev/null +++ b/net/openfortivpn/patches/010-bind-iface.patch @@ -0,0 +1,182 @@ +--- a/doc/openfortivpn.1.in ++++ b/doc/openfortivpn.1.in +@@ -12,6 +12,7 @@ openfortivpn \- Client for PPP+SSL VPN t + [\fB\-\-otp\-prompt=\fI\fR] + [\fB\-\-otp\-delay=\fI\fR] + [\fB\-\-realm=\fI\fR] ++[\fB\-\-ifname=\fI\fR] + [\fB\-\-set\-routes=\fR] + [\fB\-\-no\-routes\fR] + [\fB\-\-set\-dns=\fR] +@@ -83,6 +84,9 @@ no wait (this is the default). + Connect to the specified authentication realm. Defaults to empty, which + is usually what you want. + .TP ++\fB\-\-ifname=\fI\fR ++Bind the connection to the specified network interface. ++.TP + \fB\-\-set\-routes=\fI\fR, \fB\-\-no-routes\fR + Set if openfortivpn should try to configure IP routes through the VPN when + tunnel is up. If used multiple times, the last one takes priority. +--- a/src/config.c ++++ b/src/config.c +@@ -50,6 +50,7 @@ const struct vpn_config invalid_cfg = { + .otp_delay = -1, + .pinentry = NULL, + .realm = {'\0'}, ++ .iface_name = {'\0'}, + .set_routes = -1, + .set_dns = -1, + .pppd_use_peerdns = -1, +@@ -490,6 +491,8 @@ void merge_config(struct vpn_config *dst + } + if (src->realm[0]) + strcpy(dst->realm, src->realm); ++ if (src->iface_name[0]) ++ strcpy(dst->iface_name, src->iface_name); + if (src->set_routes != invalid_cfg.set_routes) + dst->set_routes = src->set_routes; + if (src->set_dns != invalid_cfg.set_dns) +--- a/src/config.h ++++ b/src/config.h +@@ -86,6 +86,7 @@ struct vpn_config { + char *otp_prompt; + unsigned int otp_delay; + char *pinentry; ++ char iface_name[FIELD_SIZE + 1]; + char realm[FIELD_SIZE + 1]; + + int set_routes; +--- a/src/main.c ++++ b/src/main.c +@@ -51,16 +51,16 @@ + " resolver and routes directly.\n" \ + " --pppd-ifname= Set the pppd interface name, if supported by pppd.\n" \ + " --pppd-ipparam= Provides an extra parameter to the ip-up, ip-pre-up\n" \ +-" and ip-down scripts. See man (8) pppd\n" \ ++" and ip-down scripts. See man (8) pppd.\n" \ + " --pppd-call= Move most pppd options from pppd cmdline to\n" \ + " /etc/ppp/peers/ and invoke pppd with\n" \ +-" 'call '\n" ++" 'call '.\n" + #elif HAVE_USR_SBIN_PPP + #define PPPD_USAGE \ + " [--ppp-system=]\n" + #define PPPD_HELP \ + " --ppp-system= Connect to the specified system as defined in\n" \ +-" /etc/ppp/ppp.conf\n" ++" /etc/ppp/ppp.conf.\n" + #else + #error "Neither HAVE_USR_SBIN_PPPD nor HAVE_USR_SBIN_PPP have been defined." + #endif +@@ -69,7 +69,7 @@ + #define RESOLVCONF_USAGE \ + "[--use-resolvconf=<0|1>] " + #define RESOLVCONF_HELP \ +-" --use-resolvconf=[01] If possible use resolvconf to update /etc/resolv.conf\n" ++" --use-resolvconf=[01] If possible use resolvconf to update /etc/resolv.conf.\n" + #else + #define RESOLVCONF_USAGE "" + #define RESOLVCONF_HELP "" +@@ -77,14 +77,14 @@ + + #define usage \ + "Usage: openfortivpn [[:]] [-u ] [-p ]\n" \ +-" [--pinentry=]\n" \ +-" [--realm=] [--otp=] [--otp-delay=]\n" \ +-" [--otp-prompt=] [--set-routes=<0|1>]\n" \ ++" [--otp=] [--otp-delay=] [--otp-prompt=]\n" \ ++" [--pinentry=] [--realm=]\n" \ ++" [--ifname=] [--set-routes=<0|1>]\n" \ + " [--half-internet-routes=<0|1>] [--set-dns=<0|1>]\n" \ + PPPD_USAGE \ + " " RESOLVCONF_USAGE "[--ca-file=]\n" \ + " [--user-cert=] [--user-key=]\n" \ +-" [--trusted-cert=] [--use-syslog]\n" \ ++" [--use-syslog] [--trusted-cert=]\n" \ + " [--persistent=] [-c ] [-v|-q]\n" \ + " openfortivpn --help\n" \ + " openfortivpn --version\n" \ +@@ -115,10 +115,11 @@ PPPD_USAGE \ + " -u , --username= VPN account username.\n" \ + " -p , --password= VPN account password.\n" \ + " -o , --otp= One-Time-Password.\n" \ +-" --otp-prompt= Search for the OTP prompt starting with this string\n" \ ++" --otp-prompt= Search for the OTP prompt starting with this string.\n" \ + " --otp-delay= Wait seconds before sending the OTP.\n" \ +-" --pinentry= Use the program to supply a secret instead of asking for it\n" \ ++" --pinentry= Use the program to supply a secret instead of asking for it.\n" \ + " --realm= Use specified authentication realm.\n" \ ++" --ifname= Bind to interface.\n" \ + " --set-routes=[01] Set if openfortivpn should configure routes\n" \ + " when tunnel is up.\n" \ + " --no-routes Do not configure routes, same as --set-routes=0.\n" \ +@@ -127,7 +128,7 @@ PPPD_USAGE \ + " --set-dns=[01] Set if openfortivpn should add DNS name servers\n" \ + " and domain search list in /etc/resolv.conf.\n" \ + " If installed resolvconf is used for the update.\n" \ +-" --no-dns Do not reconfigure DNS, same as --set-dns=0\n" \ ++" --no-dns Do not reconfigure DNS, same as --set-dns=0.\n" \ + " --ca-file= Use specified PEM-encoded certificate bundle\n" \ + " instead of system-wide store to verify the gateway\n" \ + " certificate.\n" \ +@@ -199,6 +200,7 @@ int main(int argc, char **argv) + .otp_delay = 0, + .pinentry = NULL, + .realm = {'\0'}, ++ .iface_name = {'\0'}, + .set_routes = 1, + .set_dns = 1, + .use_syslog = 0, +@@ -245,6 +247,7 @@ int main(int argc, char **argv) + {"otp", required_argument, NULL, 'o'}, + {"otp-prompt", required_argument, NULL, 0}, + {"otp-delay", required_argument, NULL, 0}, ++ {"ifname", required_argument, NULL, 0}, + {"set-routes", required_argument, NULL, 0}, + {"no-routes", no_argument, &cli_cfg.set_routes, 0}, + {"half-internet-routes", required_argument, NULL, 0}, +@@ -427,6 +430,12 @@ int main(int argc, char **argv) + break; + } + if (strcmp(long_options[option_index].name, ++ "ifname") == 0) { ++ strncpy(cli_cfg.iface_name, optarg, FIELD_SIZE); ++ cli_cfg.iface_name[FIELD_SIZE] = '\0'; ++ break; ++ } ++ if (strcmp(long_options[option_index].name, + "set-routes") == 0) { + int set_routes = strtob(optarg); + +--- a/src/tunnel.c ++++ b/src/tunnel.c +@@ -523,12 +523,28 @@ static int tcp_connect(struct tunnel *tu + int ret, handle; + struct sockaddr_in server; + char *env_proxy; ++ const int iface_len = strnlen(tunnel->config->iface_name, IFNAMSIZ); + + handle = socket(AF_INET, SOCK_STREAM, 0); ++ + if (handle == -1) { + log_error("socket: %s\n", strerror(errno)); + goto err_socket; + } ++ if (iface_len == IFNAMSIZ) { ++ log_error("socket: Too long iface name"); ++ goto err_socket; ++ } ++ if (iface_len > 0) { ++ ret = setsockopt(handle, SOL_SOCKET, SO_BINDTODEVICE, ++ tunnel->config->iface_name, iface_len); ++ if (ret) { ++ log_error("socket: setting interface name failed with error: %d", ++ errno); ++ goto err_socket; ++ } ++ } ++ + env_proxy = getenv("https_proxy"); + if (env_proxy == NULL) + env_proxy = getenv("HTTPS_PROXY");