diff --git a/contrib/ifup-systemd-resolved.sh b/contrib/ifup-systemd-resolved.sh new file mode 100755 index 00000000..47d422aa --- /dev/null +++ b/contrib/ifup-systemd-resolved.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +# ------------------------------------------------------------------------------- +# LICENSE: +# +# 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 . +# ------------------------------------------------------------------------------- + +# This is an `ifup` script to be used for integrating openfortivpn and +# systemd-resolved. When the network interface goes up, the DNS server information +# will be added to `systemd-resolved` without modifying /etc/resolve.conf. +# +# This script is largely based on the main script from the `update-systemd-resolved` +# package, see: https://github.com/jonathanio/update-systemd-resolved + +DBUS_DEST="org.freedesktop.resolve1" +DBUS_NODE="/org/freedesktop/resolve1" + +SCRIPT_NAME="${BASH_SOURCE[0]##*/}" + +log() { + logger -s -t "$SCRIPT_NAME" "$@" +} + +for level in emerg err warning info debug; do + printf -v functext -- '%s() { log -p user.%s -- "$@" ; }' "$level" "$level" + eval "$functext" +done + +get_link_info() { + dev="$1" + shift + + link='' + link="$(ip link show dev "$dev")" || return $? + + echo "$dev" "${link%%:*}" +} + +busctl_call() { + # Preserve busctl's exit status + busctl call "$DBUS_DEST" "$DBUS_NODE" "${DBUS_DEST}.Manager" "$@" || { + local -i status=$? + emerg "'busctl' exited with status $status" + return $status + } +} + +up() { + local link="$1" + shift + local if_index="$1" + shift + + local -a dns_servers=() dns_domain=() dns_search=() dns_routed=() + local -i dns_server_count=0 dns_domain_count=0 dns_search_count=0 dns_routed_count=0 + local dns_sec="" + + for address in ${DNS_SERVERS}; do + (( dns_server_count += 1 )) + dns_servers+=(2 4 ${address//./ }) + done + + for domain in ${DNS_SUFFIX}; do + (( dns_search_count += 1 )) + dns_search+=("${domain}" false) + done + + if [[ "${#dns_servers[*]}" -gt 0 ]]; then + busctl_params=("$if_index" "$dns_server_count" "${dns_servers[@]}") + info "SetLinkDNS(${busctl_params[*]})" + busctl_call SetLinkDNS 'ia(iay)' "${busctl_params[@]}" || return $? + fi + + if [[ "${#dns_domain[*]}" -gt 0 \ + || "${#dns_search[*]}" -gt 0 \ + || "${#dns_routed[*]}" -gt 0 ]]; then + dns_count=$((dns_domain_count+dns_search_count+dns_routed_count)) + busctl_params=("$if_index" "$dns_count") + if [[ "${#dns_domain[*]}" -gt 0 ]]; then + busctl_params+=("${dns_domain[@]}") + fi + if [[ "${#dns_search[*]}" -gt 0 ]]; then + busctl_params+=("${dns_search[@]}") + fi + if [[ "${#dns_routed[*]}" -gt 0 ]]; then + busctl_params+=("${dns_routed[@]}") + fi + info "SetLinkDomains(${busctl_params[*]})" + busctl_call SetLinkDomains 'ia(sb)' "${busctl_params[@]}" || return $? + fi + + if [[ -n "${dns_sec}" ]]; then + if [[ "${dns_sec}" == "default" ]]; then + # We need to provide an empty string to use the default settings + info "SetLinkDNSSEC($if_index '')" + busctl_call SetLinkDNSSEC 'is' "$if_index" "" || return $? + else + info "SetLinkDNSSEC($if_index ${dns_sec})" + busctl_call SetLinkDNSSEC 'is' "$if_index" "${dns_sec}" || return $? + fi + fi +} + +dev=${NET_DEVICE} +read -r link if_index _ < <(get_link_info "$dev") +up "$link" "$if_index" +systemd-resolve --flush-caches diff --git a/src/config.c b/src/config.c index 9c6d3c5d..8c38be50 100644 --- a/src/config.c +++ b/src/config.c @@ -292,6 +292,9 @@ int load_config(struct vpn_config *cfg, const char *filename) } else if (strcmp(key, "realm") == 0) { strncpy(cfg->realm, val, REALM_SIZE); cfg->realm[REALM_SIZE] = '\0'; + } else if (strcmp(key, "ifup-script") == 0) { + strncpy(cfg->ifup_script, val, MAXPATHLEN - 1); + cfg->ifup_script[MAXPATHLEN] = '\0'; } else if (strcmp(key, "set-dns") == 0) { int set_dns = strtob(val); diff --git a/src/config.h b/src/config.h index eaf7f825..0e3316f5 100644 --- a/src/config.h +++ b/src/config.h @@ -18,6 +18,7 @@ #ifndef OPENFORTIVPN_CONFIG_H #define OPENFORTIVPN_CONFIG_H +#include #include #include @@ -97,6 +98,7 @@ struct vpn_config { char *pinentry; char iface_name[IF_NAMESIZE]; char realm[REALM_SIZE + 1]; + char ifup_script[MAXPATHLEN + 1]; char sni[GATEWAY_HOST_SIZE + 1]; int set_routes; diff --git a/src/tunnel.c b/src/tunnel.c index 71d8446d..97b3dff7 100644 --- a/src/tunnel.c +++ b/src/tunnel.c @@ -105,13 +105,39 @@ static int ofv_append_varr(struct ofv_varr *p, const char *x) return 0; } +static int ipv4_run_ifup_script(struct tunnel *tunnel) +{ + char ns[32]; + + setenv("NET_DEVICE", tunnel->ppp_iface, 0); + + ns[0] = '\0'; + + if (tunnel->ipv4.ns1_addr.s_addr != 0) + strncat(ns, inet_ntoa(tunnel->ipv4.ns1_addr), 15); + + if (tunnel->ipv4.ns2_addr.s_addr != 0) { + strcpy(ns, " "); + strncat(ns, inet_ntoa(tunnel->ipv4.ns2_addr), 15); + } + + setenv("DNS_SERVERS", ns, 0); + + if (tunnel->ipv4.dns_suffix != NULL) + setenv("DNS_SUFFIX", tunnel->ipv4.dns_suffix, 0); + else + setenv("DNS_SUFFIX", "", 0); + + return system(tunnel->config->ifup_script); +} + static int on_ppp_if_up(struct tunnel *tunnel) { + int ret; + log_info("Interface %s is UP.\n", tunnel->ppp_iface); if (tunnel->config->set_routes) { - int ret; - log_info("Setting new routes...\n"); ret = ipv4_set_tunnel_routes(tunnel); @@ -125,6 +151,13 @@ static int on_ppp_if_up(struct tunnel *tunnel) ipv4_add_nameservers_to_resolv_conf(tunnel); } + if (tunnel->config->ifup_script) { + log_info("Running `ifup` script...\n"); + ret = ipv4_run_ifup_script(tunnel); + if (ret != 0) + log_warn("The `ifup` script failed. Please check your logs.\n"); + } + log_info("Tunnel is up and running.\n"); #if HAVE_SYSTEMD