#!/bin/bash
# Turns the contest restrictions off and on, for maintenance. Root only, so
# it is used through sudo from the administrator account (icpc).
#
#   sudo contest-restrictions status
#   sudo contest-restrictions report [SINCE]   (refused attempts, default: today)
#   sudo contest-restrictions off [all|firewall|usb|network]
#   sudo contest-restrictions on  [all|firewall|usb|network]
#
#   firewall  firewall, web proxy, DNS filter (/disable_firewall.sh, /enable_firewall.sh)
#   usb       USBGuard, USB disks and phones for the team account
#   network   network settings (Wi-Fi, hotspot, ...) for the team account
#
# "off" stays in effect after a reboot, until "on".
set -u
DISABLED=/etc/contest-restrictions/disabled
USB_FILES="/etc/polkit-1/rules.d/10-acpc-no-removable-media.rules /etc/udev/rules.d/72-acpc-no-phone-access.rules"
NETWORK_FILES="/etc/polkit-1/rules.d/10-acpc-no-network-changes.rules"

usage() {
    sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//'
    exit 2
}

[ "$(id -u)" -eq 0 ] || { echo "Run it with sudo, from the administrator account." >&2; exit 1; }
mkdir -p "$DISABLED"
chmod 700 /etc/contest-restrictions

# Moves rule files out of the way (off) or back (on)
move_rules() {
    action=$1
    shift
    for f in "$@"; do
        if [ "$action" = off ] && [ -f "$f" ]; then
            mv "$f" "$DISABLED/"
        elif [ "$action" = on ] && [ -f "$DISABLED/$(basename "$f")" ]; then
            mv "$DISABLED/$(basename "$f")" "$f"
        fi
    done
}

firewall() {
    if [ "$1" = off ]; then
        /disable_firewall.sh
    else
        /enable_firewall.sh
        /refresh_rules.sh
    fi
}

usb() {
    if [ "$1" = off ]; then
        systemctl disable --now usbguard.service
        # USBGuard leaves the controllers refusing new devices when it stops
        for f in /sys/bus/usb/devices/usb*/authorized_default; do
            [ -e "$f" ] && echo 1 > "$f"
        done
        move_rules off $USB_FILES
        udevadm control --reload
        echo "USB devices allowed (unplug and plug again a device refused before)."
        echo "USB disks and phones can be used from the team account."
    else
        # Removable media mounted while the restriction was off
        findmnt -rno TARGET | grep '^/run/media/' | while read -r m; do
            umount "$m" && echo "Unmounted $m"
        done
        move_rules on $USB_FILES
        udevadm control --reload
        systemctl enable --now usbguard.service
        echo "USB restrictions on. The devices plugged in right now stay allowed until they are unplugged."
    fi
}

network() {
    if [ "$1" = off ]; then
        move_rules off $NETWORK_FILES
        echo "The team account can change the network settings."
    else
        move_rules on $NETWORK_FILES
        echo "Network settings restricted to the administrators."
    fi
}

state() {
    if [ "$1" = yes ]; then echo "on"; else echo "OFF"; fi
}

status() {
    fw=no; systemctl is-active --quiet firewall.service && fw=yes
    us=no; systemctl is-active --quiet usbguard.service && [ -f /etc/polkit-1/rules.d/10-acpc-no-removable-media.rules ] && us=yes
    nw=no; [ -f /etc/polkit-1/rules.d/10-acpc-no-network-changes.rules ] && nw=yes
    echo "firewall  $(state $fw)"
    echo "usb       $(state $us)"
    echo "network   $(state $nw)"
}

# What the machine refused: web sites, DNS names, USB devices
report() {
    since=${1:-today}
    echo "== Web sites refused by the proxy (since $since)"
    journalctl -t contest_proxy --since "$since" --no-pager -o cat 2>/dev/null \
        | awk '$3 != "200" || $4 == "-" {print $2}' | sort | uniq -c | sort -rn | head -40
    echo
    echo "== DNS names refused by the filter"
    # Names that Firefox, VS Code, the IDEs and Kattis pages look up on their
    # own in the background
    background='(^|\.)(mozilla\.(com|org|net)|firefox-portal-detection\.com|googleapis\.com|gvt1\.com|microsoft\.com|vscode-cdn\.net|vscode-unpkg\.net|exp-tas\.com|openh264\.org|ipv4only\.arpa|sentry\.io|cloudflare-dns\.com|jetbrains\.com|ubuntu\.com|canonical\.com|snapcraft\.io)$'
    names=$(journalctl -u contest-dns --since "$since" --no-pager -o cat 2>/dev/null \
        | sed -n 's/^config \(.*\) is NXDOMAIN$/\1/p')
    printf '%s\n' "$names" | grep -v -E "$background" | grep . | sort | uniq -c | sort -rn | head -40
    echo "   ($(printf '%s\n' "$names" | grep -c -E "$background") lookups by Firefox, VS Code and the IDEs in the background, not listed)"
    echo
    echo "== USB devices rejected"
    journalctl -u usbguard --since "$since" --no-pager -o short 2>/dev/null \
        | grep "target.new='reject'" \
        | sed -E 's/^([A-Za-z]+ +[0-9]+ [0-9:]+).* name "([^"]*)".*/\1  \2/' | tail -20
}

case "${1:-}" in
    status) status ;;
    report) report "${2:-today}" ;;
    on|off)
        what=${2:-all}
        case "$what" in
            all) firewall "$1"; usb "$1"; network "$1" ;;
            firewall|usb|network) "$what" "$1" ;;
            *) usage ;;
        esac
        echo
        status
        ;;
    *) usage ;;
esac
