#!/bin/bash
#
# air-zerotier — strict allowlist wrapper around /usr/sbin/zerotier-cli for
# the `cast` service user. Invoked via sudo from cast-server; sudoers
# restricts that service user to exactly this path (/etc/sudoers.d/air-vpn).
#
# Why a wrapper?
# --------------
# zerotier-cli has subcommands that can change cluster membership, set
# moons/planets, and dump secrets. We only want to permit the read-only
# query and the join/leave operations cast-server actually performs, with
# tightly validated arguments. sudoers cannot express argv constraints
# without enumerating every accepted variant; this wrapper centralises the
# allowlist instead.
#
# Subcommand allowlist:
#   listnetworks   — accepts only `-j` (json output, what cast-server reads).
#   join <NWID>    — NWID must be exactly 16 lowercase hex chars.
#   leave <NWID>   — NWID must be exactly 16 lowercase hex chars.
#   info           — no extra arguments permitted.
#   status         — alias for info; no extra arguments permitted.
#
# Anything else (set, controller, moon, peers, dump, ...) is refused.
#
# The final argv is echoed to stderr before exec for audit purposes.
#

set -euo pipefail

ZEROTIER=/usr/sbin/zerotier-cli
PROG=air-zerotier

die() {
    echo "${PROG}: $*" >&2
    exit 2
}

is_nwid() {
    [[ "$1" =~ ^[0-9a-f]{16}$ ]]
}

if [ $# -lt 1 ]; then
    die "missing subcommand"
fi

subcmd=$1
shift

case "${subcmd}" in
    listnetworks)
        # cast-server invokes `zerotier-cli listnetworks -j`. Pin it.
        if [ $# -ne 1 ] || [ "${1:-}" != "-j" ]; then
            die "subcommand 'listnetworks' only accepts the single arg '-j'"
        fi
        ;;
    join|leave)
        if [ $# -ne 1 ]; then
            die "subcommand '${subcmd}' requires exactly one network ID argument"
        fi
        if ! is_nwid "$1"; then
            die "network ID must be 16 lowercase hex characters"
        fi
        ;;
    info|status)
        if [ $# -ne 0 ]; then
            die "subcommand '${subcmd}' takes no arguments"
        fi
        ;;
    *)
        die "subcommand '${subcmd}' not permitted"
        ;;
esac

# Audit trail: log the exact argv we are about to exec.
#
# Per-arg printf '%q' so a value with spaces or shell metas is escaped
# instead of word-splitting against the log line — see air-tailscale for
# the longer rationale.
{
    printf '%s: exec %s %s' "${PROG}" "${ZEROTIER}" "${subcmd}"
    if [ "$#" -gt 0 ]; then
        printf ' %q' "$@"
    fi
    printf '\n'
} >&2

exec "${ZEROTIER}" "${subcmd}" "$@"
