1
0
mirror of https://git.openwrt.org/feed/packages.git synced 2024-06-16 12:23:59 +02:00

openfortivpn: Use netifd for script rather than init script

By using the netifd for open fortivpn we are able to set up multiple
VPN connections and manage them through the netifd toolset.

This also adds support for binding an openfortivpn client to a given
interface, in which case when that interface comes online, the vpn
will be initiated via a hotplug script.

This is a breaking commit and configurations will need to be migrated
from openfortivpn.config into the /etc/config/networks.

Example configuration via /etc/config/network:

config interface 'ftvpn'
        option proto 'openfortivpn'
        option server 'example.com'
        option username 'USERNAME'
        option password 'PASSWORD'
        # optional arguments follow
        option local_ip '192.0.5.1'
        option port '443'
        option iface_name 'wan'
        option trusted_cert 'CERT_HASH'
        option set_dns '0'
        option pppd_use_peerdns '0'
        option metric '10'

Signed-off-by: Aaron Goodman <aaronjg@stanford.edu>
This commit is contained in:
Aaron Goodman 2020-05-22 18:57:42 -04:00
parent 4f727bcc02
commit 9b0fce23d1
7 changed files with 358 additions and 96 deletions

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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
}

View File

@ -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

View File

@ -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<prompt>\fR]
[\fB\-\-otp\-delay=\fI<delay>\fR]
[\fB\-\-realm=\fI<realm>\fR]
+[\fB\-\-ifname=\fI<interface>\fR]
[\fB\-\-set\-routes=<bool>\fR]
[\fB\-\-no\-routes\fR]
[\fB\-\-set\-dns=<bool>\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<interface>\fR
+Bind the connection to the specified network interface.
+.TP
\fB\-\-set\-routes=\fI<bool>\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=<string> Set the pppd interface name, if supported by pppd.\n" \
" --pppd-ipparam=<string> 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=<name> Move most pppd options from pppd cmdline to\n" \
" /etc/ppp/peers/<name> and invoke pppd with\n" \
-" 'call <name>'\n"
+" 'call <name>'.\n"
#elif HAVE_USR_SBIN_PPP
#define PPPD_USAGE \
" [--ppp-system=<system>]\n"
#define PPPD_HELP \
" --ppp-system=<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 [<host>[:<port>]] [-u <user>] [-p <pass>]\n" \
-" [--pinentry=<program>]\n" \
-" [--realm=<realm>] [--otp=<otp>] [--otp-delay=<delay>]\n" \
-" [--otp-prompt=<prompt>] [--set-routes=<0|1>]\n" \
+" [--otp=<otp>] [--otp-delay=<delay>] [--otp-prompt=<prompt>]\n" \
+" [--pinentry=<program>] [--realm=<realm>]\n" \
+" [--ifname=<ifname>] [--set-routes=<0|1>]\n" \
" [--half-internet-routes=<0|1>] [--set-dns=<0|1>]\n" \
PPPD_USAGE \
" " RESOLVCONF_USAGE "[--ca-file=<file>]\n" \
" [--user-cert=<file>] [--user-key=<file>]\n" \
-" [--trusted-cert=<digest>] [--use-syslog]\n" \
+" [--use-syslog] [--trusted-cert=<digest>]\n" \
" [--persistent=<interval>] [-c <file>] [-v|-q]\n" \
" openfortivpn --help\n" \
" openfortivpn --version\n" \
@@ -115,10 +115,11 @@ PPPD_USAGE \
" -u <user>, --username=<user> VPN account username.\n" \
" -p <pass>, --password=<pass> VPN account password.\n" \
" -o <otp>, --otp=<otp> One-Time-Password.\n" \
-" --otp-prompt=<prompt> Search for the OTP prompt starting with this string\n" \
+" --otp-prompt=<prompt> Search for the OTP prompt starting with this string.\n" \
" --otp-delay=<delay> Wait <delay> seconds before sending the OTP.\n" \
-" --pinentry=<program> Use the program to supply a secret instead of asking for it\n" \
+" --pinentry=<program> Use the program to supply a secret instead of asking for it.\n" \
" --realm=<realm> Use specified authentication realm.\n" \
+" --ifname=<interface> 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=<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");