#!/bin/sh
# XTeleport privileged tunnel helper for Linux.
#
# This is the Linux counterpart of the macOS Packet Tunnel controller. The GUI
# never runs awg-quick/wg-quick as root directly; it invokes this helper via
# pkexec, and the polkit action online.xteleport.tunnel (auth_admin_keep)
# authorizes exactly this program. Switching servers therefore does not ask
# for the administrator password on every up/down cycle.
#
# The helper only manages xt-* tunnels created by XTeleport and refuses
# configurations that would execute shell hooks as root.
#
#   xteleport-tunnel up   <config.conf> [awg-quick|wg-quick]
#   xteleport-tunnel down <config.conf> [awg-quick|wg-quick]
#   xteleport-tunnel reset [config-dir]
set -eu

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH

die() {
  echo "xteleport-tunnel: $*" >&2
  exit 1
}

usage() {
  echo "usage: xteleport-tunnel up|down <config.conf> [awg-quick|wg-quick]" >&2
  echo "       xteleport-tunnel reset [config-dir]" >&2
  exit 2
}

interface_name() {
  base=$(basename -- "$1")
  printf '%s\n' "${base%.conf}"
}

validate_config() {
  conf="$1"
  case "$conf" in
    /*) ;;
    *) die "configuration path must be absolute: $conf" ;;
  esac
  [ -f "$conf" ] || die "configuration not found: $conf"
  case "$conf" in
    *.conf) ;;
    *) die "configuration must end with .conf: $conf" ;;
  esac
  iface=$(interface_name "$conf")
  case "$iface" in
    xt-*) ;;
    *) die "only xt-* tunnels are managed by this helper: $iface" ;;
  esac
  [ "${#iface}" -le 15 ] || die "interface name is too long: $iface"
  if grep -Eiq '^[[:space:]]*(PreUp|PostUp|PreDown|PostDown|SaveConfig)[[:space:]]*=' "$conf"; then
    die "configuration contains shell hooks and was rejected: $conf"
  fi
}

is_amnezia_config() {
  grep -Eiq '^[[:space:]]*(Jc|Jmin|Jmax|S1|S2|S3|S4|H1|H2|H3|H4|I1|I2|I3|I4|I5)[[:space:]]*=' "$1"
}

select_tool() {
  conf="$1"
  requested="${2:-}"
  case "$requested" in
    awg-quick|wg-quick)
      command -v "$requested" >/dev/null 2>&1 || die "$requested is not installed"
      printf '%s\n' "$requested"
      return
      ;;
    "") ;;
    *) die "unsupported tunnel tool: $requested" ;;
  esac
  if is_amnezia_config "$conf"; then
    command -v awg-quick >/dev/null 2>&1 || die "AmneziaWG configuration requires awg-quick (amneziawg-tools)"
    printf 'awg-quick\n'
    return
  fi
  for tool in wg-quick awg-quick; do
    if command -v "$tool" >/dev/null 2>&1; then
      printf '%s\n' "$tool"
      return
    fi
  done
  die "install wireguard-tools (wg-quick) or amneziawg-tools (awg-quick)"
}

tunnel_up() {
  conf="$1"
  validate_config "$conf"
  tool=$(select_tool "$conf" "${2:-}")
  iface=$(interface_name "$conf")
  # Replace a stale interface left behind by a crashed GUI instead of failing
  # with "already exists".
  if ip link show dev "$iface" >/dev/null 2>&1; then
    "$tool" down "$conf" >/dev/null 2>&1 || ip link delete dev "$iface" 2>/dev/null || true
  fi
  exec "$tool" up "$conf"
}

tunnel_down() {
  conf="$1"
  validate_config "$conf"
  iface=$(interface_name "$conf")
  if ! ip link show dev "$iface" >/dev/null 2>&1; then
    # Nothing to do; treat an already-removed tunnel as success.
    exit 0
  fi
  tool=$(select_tool "$conf" "${2:-}")
  exec "$tool" down "$conf"
}

tunnel_reset() {
  dir="${1:-/tmp/XTeleport}"
  status=0
  if [ -d "$dir" ]; then
    for conf in "$dir"/xt-*.conf; do
      [ -f "$conf" ] || continue
      iface=$(interface_name "$conf")
      case "$iface" in xt-*) ;; *) continue ;; esac
      if ip link show dev "$iface" >/dev/null 2>&1; then
        tool=$(select_tool "$conf" "" 2>/dev/null || true)
        if [ -n "$tool" ] && "$tool" down "$conf" >/dev/null 2>&1; then
          :
        elif ! ip link delete dev "$iface" 2>/dev/null; then
          echo "xteleport-tunnel: failed to remove $iface" >&2
          status=1
        fi
      fi
    done
  fi
  # Interfaces whose configuration file has already been deleted.
  for iface in $(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | cut -d@ -f1 | grep '^xt-' || true); do
    ip link delete dev "$iface" 2>/dev/null || { echo "xteleport-tunnel: failed to remove $iface" >&2; status=1; }
  done
  exit "$status"
}

[ "$#" -ge 1 ] || usage
command="$1"
shift
case "$command" in
  up)
    [ "$#" -ge 1 ] && [ "$#" -le 2 ] || usage
    tunnel_up "$@"
    ;;
  down)
    [ "$#" -ge 1 ] && [ "$#" -le 2 ] || usage
    tunnel_down "$@"
    ;;
  reset)
    [ "$#" -le 1 ] || usage
    tunnel_reset "$@"
    ;;
  *)
    usage
    ;;
esac
