#!/bin/sh
# INCY VPN privileged helper.
#
# Centralised entry point for every operation that needs CAP_NET_ADMIN —
# start the tunnel binary, set up policy routing, tear it all down. Lives at
# /opt/incy/bin/incy-tunnel-helper (path baked into the polkit policy file)
# so pkexec invocations always use the same fully-qualified path. That's
# what lets us keep the auth-admin-keep cache: polkit caches by
# (uid × command path × parameters), so identical paths on consecutive
# connect/disconnect calls reuse the same authorisation for ~5 minutes.
#
# Usage:
#   incy-tunnel-helper start <tunnel-binary> <config-yml> <tun-device> <fwmark> <routing-table>
#   incy-tunnel-helper cleanup <tun-device> <fwmark> <routing-table>
#
# Exit codes:
#   0   ok
#   1   bad arguments
#   2   tunnel binary missing or not executable
#   3   TUN interface didn't appear in 2s
#   *   tunnel binary's own exit code (start subcommand)

set -u

cmd="${1:-}"

case "$cmd" in
  start)
    binary="${2:-}"
    config="${3:-}"
    tun_device="${4:-}"
    fwmark="${5:-}"
    routing_table="${6:-}"
    if [ -z "$binary" ] || [ -z "$config" ] || [ -z "$tun_device" ] || [ -z "$fwmark" ] || [ -z "$routing_table" ]; then
      echo "incy-tunnel-helper: usage: start <binary> <config> <tun-device> <fwmark> <routing-table>" >&2
      exit 1
    fi
    if [ ! -x "$binary" ]; then
      # Bundled binary may have lost +x after copy — fix it before bailing.
      chmod +x "$binary" 2>/dev/null
      if [ ! -x "$binary" ]; then
        echo "incy-tunnel-helper: $binary is not executable" >&2
        exit 2
      fi
    fi

    # Cleanup any stale rules from a previous crash.
    ip rule del not fwmark "$fwmark" table "$routing_table" 2>/dev/null || true
    ip route del default dev "$tun_device" table "$routing_table" 2>/dev/null || true
    ip -6 rule del not fwmark "$fwmark" table "$routing_table" 2>/dev/null || true
    ip -6 route del default dev "$tun_device" table "$routing_table" 2>/dev/null || true

    # Launch the tunnel in the foreground so pkexec stays alive — when the
    # JVM kills us, the kernel reaps the tunnel as a child.
    "$binary" "$config" &
    tunnel_pid=$!

    # Wait up to 2s for the TUN device to appear.
    i=0
    while [ "$i" -lt 20 ]; do
      if [ -d "/sys/class/net/$tun_device" ]; then break; fi
      sleep 0.1
      i=$((i + 1))
    done
    if [ ! -d "/sys/class/net/$tun_device" ]; then
      kill "$tunnel_pid" 2>/dev/null
      exit 3
    fi

    # Install policy routing rules.
    ip route replace default dev "$tun_device" table "$routing_table"
    ip rule add not fwmark "$fwmark" table "$routing_table" 2>/dev/null || true
    ip -6 route replace default dev "$tun_device" table "$routing_table" 2>/dev/null || true
    ip -6 rule add not fwmark "$fwmark" table "$routing_table" 2>/dev/null || true

    echo "TUNNEL_PID=$tunnel_pid"

    # Block on the tunnel process — when we exit, pkexec exits, the JVM's
    # process handle goes inert, and the JVM knows the tunnel is gone.
    wait "$tunnel_pid"
    ;;

  cleanup)
    tun_device="${2:-}"
    fwmark="${3:-}"
    routing_table="${4:-}"
    if [ -z "$tun_device" ] || [ -z "$fwmark" ] || [ -z "$routing_table" ]; then
      echo "incy-tunnel-helper: usage: cleanup <tun-device> <fwmark> <routing-table>" >&2
      exit 1
    fi
    ip rule del not fwmark "$fwmark" table "$routing_table" 2>/dev/null || true
    ip route del default dev "$tun_device" table "$routing_table" 2>/dev/null || true
    ip -6 rule del not fwmark "$fwmark" table "$routing_table" 2>/dev/null || true
    ip -6 route del default dev "$tun_device" table "$routing_table" 2>/dev/null || true
    exit 0
    ;;

  *)
    echo "incy-tunnel-helper: unknown command '$cmd'" >&2
    echo "Usage:" >&2
    echo "  incy-tunnel-helper start <binary> <config> <tun-device> <fwmark> <routing-table>" >&2
    echo "  incy-tunnel-helper cleanup <tun-device> <fwmark> <routing-table>" >&2
    exit 1
    ;;
esac
