#!/bin/sh
set -eu

fail() {
  logger -t wayavpn-helper -- "failure: $1" 2>/dev/null || true
  echo "WayaVPN helper: $1" >&2
  exit 1
}

[ "$(id -u)" -eq 0 ] || fail "administrator authorization is required"

CALLER_UID="${PKEXEC_UID:-${SUDO_UID:-}}"
case "$CALLER_UID" in
  ''|*[!0-9]*) fail "could not identify the requesting user" ;;
esac
[ "$CALLER_UID" -gt 0 ] || fail "refusing an invalid requesting user"

PASSWD_ENTRY="$(getent passwd "$CALLER_UID" || true)"
[ -n "$PASSWD_ENTRY" ] || fail "requesting user was not found"
CALLER_NAME="$(printf '%s' "$PASSWD_ENTRY" | cut -d: -f1)"
CALLER_GID="$(printf '%s' "$PASSWD_ENTRY" | cut -d: -f4)"
CALLER_HOME="$(printf '%s' "$PASSWD_ENTRY" | cut -d: -f6)"
CALLER_GROUP="$(getent group "$CALLER_GID" | cut -d: -f1)"
[ -n "$CALLER_NAME" ] && [ -n "$CALLER_GROUP" ] && [ -n "$CALLER_HOME" ] || fail "requesting user details are incomplete"

prepare_tun() {
  if [ ! -c /dev/net/tun ]; then
    [ -e /sys/class/misc/tun ] || fail "the Linux TUN driver is unavailable"
    mkdir -p /dev/net
    mknod /dev/net/tun c 10 200
    chmod 0666 /dev/net/tun
  fi
}

ACTION="${1:-}"
if [ "$ACTION" = "setup" ]; then
  case "$CALLER_NAME" in *[!A-Za-z0-9_.-]*) fail "requesting username is invalid" ;; esac

  if ! command -v openvpn >/dev/null 2>&1; then
    command -v apt-get >/dev/null 2>&1 || fail "OpenVPN is missing and no supported package manager was found"
    DEBIAN_FRONTEND=noninteractive apt-get update
    DEBIAN_FRONTEND=noninteractive apt-get install -y openvpn
  fi

  install -d -m 0755 /usr/libexec
  SOURCE_PATH="$(readlink -f "$0")"
  INSTALLED_PATH="$(readlink -f /usr/libexec/wayavpn-openvpn-helper 2>/dev/null || true)"
  if [ "$SOURCE_PATH" != "$INSTALLED_PATH" ]; then
    install -o root -g root -m 0755 "$0" /usr/libexec/wayavpn-openvpn-helper
  else
    chown root:root /usr/libexec/wayavpn-openvpn-helper
    chmod 0755 /usr/libexec/wayavpn-openvpn-helper
  fi
  SUDOERS_TMP="$(mktemp)"
  printf '%s ALL=(root) NOPASSWD: /usr/libexec/wayavpn-openvpn-helper *\n' "$CALLER_NAME" > "$SUDOERS_TMP"
  chmod 0440 "$SUDOERS_TMP"
  visudo -cf "$SUDOERS_TMP" >/dev/null || { rm -f "$SUDOERS_TMP"; fail "could not validate privileged helper authorization"; }
  install -o root -g root -m 0440 "$SUDOERS_TMP" /etc/sudoers.d/wayavpn
  rm -f "$SUDOERS_TMP"
  prepare_tun
  logger -t wayavpn-helper -- "setup complete for uid $CALLER_UID" 2>/dev/null || true
  exit 0
fi

IPV6_STATE_FILE="/run/wayavpn-ipv6-$CALLER_UID.state"
if [ "$ACTION" = "ipv6-disable" ]; then
  if [ ! -f "$IPV6_STATE_FILE" ]; then
    {
      printf 'all=%s\n' "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6)"
      printf 'default=%s\n' "$(cat /proc/sys/net/ipv6/conf/default/disable_ipv6)"
    } > "$IPV6_STATE_FILE"
    chown root:root "$IPV6_STATE_FILE"
    chmod 0600 "$IPV6_STATE_FILE"
  fi
  sysctl -q -w net.ipv6.conf.all.disable_ipv6=1
  sysctl -q -w net.ipv6.conf.default.disable_ipv6=1
  [ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6)" = "1" ] || fail "IPv6 could not be disabled"
  logger -t wayavpn-helper -- "IPv6 disabled for app session uid $CALLER_UID" 2>/dev/null || true
  exit 0
fi

if [ "$ACTION" = "ipv6-restore" ]; then
  if [ -f "$IPV6_STATE_FILE" ]; then
    ALL_VALUE="$(sed -n 's/^all=//p' "$IPV6_STATE_FILE")"
    DEFAULT_VALUE="$(sed -n 's/^default=//p' "$IPV6_STATE_FILE")"
    case "$ALL_VALUE:$DEFAULT_VALUE" in 0:0|0:1|1:0|1:1) ;; *) fail "saved IPv6 state is invalid" ;; esac
    sysctl -q -w "net.ipv6.conf.all.disable_ipv6=$ALL_VALUE"
    sysctl -q -w "net.ipv6.conf.default.disable_ipv6=$DEFAULT_VALUE"
    rm -f "$IPV6_STATE_FILE"
  fi
  logger -t wayavpn-helper -- "IPv6 state restored for app session uid $CALLER_UID" 2>/dev/null || true
  exit 0
fi

OPENVPN_BIN=""
for candidate in /usr/sbin/openvpn /usr/local/sbin/openvpn; do
  if [ -x "$candidate" ]; then
    OPENVPN_BIN="$candidate"
    break
  fi
done
[ -n "$OPENVPN_BIN" ] || fail "OpenVPN is not installed"

find_tun_interface() {
  for item in /sys/class/net/tun*; do
    [ -e "$item" ] || continue
    basename "$item"
    return 0
  done
  return 1
}

if [ "$ACTION" = "dns-apply" ]; then
  shift
  [ "$#" -gt 0 ] || fail "no DNS servers were supplied"
  DNS_ARGS=""
  for dns in "$@"; do
    case "$dns" in
      *[!0-9.]*|'') fail "invalid DNS server" ;;
    esac
    DNS_ARGS="$DNS_ARGS $dns"
  done
  TUN_INTERFACE="$(find_tun_interface || true)"
  [ -n "$TUN_INTERFACE" ] || fail "active VPN interface was not found"
  command -v resolvectl >/dev/null 2>&1 || fail "systemd-resolved tools are unavailable"
  # DNS_ARGS contains only validated IPv4 characters.
  # shellcheck disable=SC2086
  resolvectl dns "$TUN_INTERFACE" $DNS_ARGS
  resolvectl domain "$TUN_INTERFACE" '~.'
  resolvectl default-route "$TUN_INTERFACE" yes
  resolvectl flush-caches
  DNS_STATUS="$(resolvectl dns "$TUN_INTERFACE")"
  for dns in "$@"; do
    printf '%s\n' "$DNS_STATUS" | grep -F -- "$dns" >/dev/null || fail "DNS verification failed"
  done
  logger -t wayavpn-helper -- "DNS applied to $TUN_INTERFACE" 2>/dev/null || true
  exit 0
fi

if [ "$ACTION" = "dns-reset" ]; then
  if command -v resolvectl >/dev/null 2>&1; then
    for item in /sys/class/net/tun*; do
      [ -e "$item" ] || continue
      resolvectl revert "$(basename "$item")" || true
    done
    resolvectl flush-caches || true
  fi
  logger -t wayavpn-helper -- "DNS reset complete" 2>/dev/null || true
  exit 0
fi

if [ "$ACTION" = "stop" ]; then
  PID="${2:-}"
  case "$PID" in ''|*[!0-9]*) fail "invalid VPN process" ;; esac
  [ -r "/proc/$PID/cmdline" ] || exit 0
  CMDLINE="$(tr '\000' ' ' < "/proc/$PID/cmdline")"
  case "$CMDLINE" in
    *openvpn*"$CALLER_HOME/.config/WayaVPN/server-"*.ovpn*) kill -TERM "$PID" ;;
    *) fail "refusing to stop an unrelated process" ;;
  esac
  exit 0
fi

[ "$ACTION" = "start" ] || fail "unsupported action"
CONFIG_PATH="$(readlink -f "${2:-}" 2>/dev/null || true)"
CREDS_PATH="$(readlink -f "${3:-}" 2>/dev/null || true)"

case "$CONFIG_PATH" in "$CALLER_HOME/.config/WayaVPN/"server-*.ovpn) ;; *) fail "invalid VPN profile path" ;; esac
case "$CREDS_PATH" in /tmp/WayaVPN/creds-*.txt) ;; *) fail "invalid credential path" ;; esac
[ -f "$CONFIG_PATH" ] && [ ! -L "$CONFIG_PATH" ] || fail "VPN profile is not a regular file"
[ -f "$CREDS_PATH" ] && [ ! -L "$CREDS_PATH" ] || fail "VPN credentials are not a regular file"
[ "$(stat -c %u "$CONFIG_PATH")" = "$CALLER_UID" ] || fail "VPN profile ownership is invalid"
[ "$(stat -c %u "$CREDS_PATH")" = "$CALLER_UID" ] || fail "VPN credential ownership is invalid"
[ "$(stat -c %a "$CREDS_PATH")" = "600" ] || fail "VPN credential permissions are invalid"

prepare_tun

logger -t wayavpn-helper -- "starting OpenVPN for uid $CALLER_UID with profile $(basename "$CONFIG_PATH")" 2>/dev/null || true

exec "$OPENVPN_BIN" \
  --config "$CONFIG_PATH" \
  --auth-user-pass "$CREDS_PATH" \
  --auth-nocache \
  --user "$CALLER_NAME" \
  --group "$CALLER_GROUP" \
  --verb 3
