First version

This commit is contained in:
andy committed 2026-10-15 20:44:05 -03:00
commit 243e92a9dd
84 files changed
+4012

No files matched your search

@@ -0,0 +1,9 @@
// ACPC 2026: the teams may not change the network (Wi-Fi, phone hotspot,
// modem, VPN, ...). Only the administrators (sudo group) keep these actions.
polkit.addRule(function(action, subject) {
if ((action.id.indexOf("org.freedesktop.NetworkManager.") == 0 ||
action.id.indexOf("org.freedesktop.ModemManager1.") == 0) &&
!subject.isInGroup("sudo")) {
return polkit.Result.NO;
}
});
@@ -0,0 +1,14 @@
// ACPC 2026: the teams may not restart, power off, suspend or hibernate the
// machine. A restart would let USBGuard accept a device plugged in before.
// Only the administrators (sudo group) keep these actions.
polkit.addRule(function(action, subject) {
if ((action.id.indexOf("org.freedesktop.login1.reboot") == 0 ||
action.id.indexOf("org.freedesktop.login1.power-off") == 0 ||
action.id.indexOf("org.freedesktop.login1.halt") == 0 ||
action.id.indexOf("org.freedesktop.login1.suspend") == 0 ||
action.id.indexOf("org.freedesktop.login1.hibernate") == 0 ||
action.id.indexOf("org.freedesktop.login1.set-reboot") == 0) &&
!subject.isInGroup("sudo")) {
return polkit.Result.NO;
}
});
@@ -0,0 +1,8 @@
// ACPC 2026: the teams may not mount USB sticks, disks or disk images.
// Only the administrators (sudo group) keep the udisks actions.
polkit.addRule(function(action, subject) {
if (action.id.indexOf("org.freedesktop.udisks2.") == 0 &&
!subject.isInGroup("sudo")) {
return polkit.Result.NO;
}
});
@@ -0,0 +1,7 @@
# ACPC 2026: the desktop user gets no direct access to phones and cameras
# (MTP and PTP, used to copy files), which bypasses the mounting of disks.
# The tags are set by 69-libmtp.rules and 60-libgphoto2, and used by
# 73-seat-late.rules, so this file sits between them.
ENV{ID_MTP_DEVICE}=="1", TAG-="uaccess"
ENV{ID_GPHOTO2}=="1", TAG-="uaccess"
ENV{ID_MEDIA_PLAYER}=="1", TAG-="uaccess"
+8
View File
@@ -0,0 +1,8 @@
# ACPC 2026: the power, reboot and suspend keys do nothing. A long press on
# the power button still cuts the power (hardware), in view of the proctors.
[Login]
HandlePowerKey=ignore
HandleRebootKey=ignore
HandleSuspendKey=ignore
HandleHibernateKey=ignore
HandleLidSwitch=ignore
+2
View File
@@ -0,0 +1,2 @@
# ACPC 2026: no SysRq key combinations (they can restart the machine)
kernel.sysrq = 0
+133
View File
@@ -0,0 +1,133 @@
#!/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
@@ -0,0 +1,41 @@
#!/bin/bash
# Runs as root (contest-restrictions-warning.service). While any contest
# restriction is off (after maintenance with contest-restrictions off), shows
# a red banner across the top of the screen, so that the proctors notice it.
# The banner has no title bar or close button, the team account cannot stop
# this process, and the banner comes back within seconds if its window is
# destroyed. It is redrawn every 15 seconds to stay above the other windows.
export DISPLAY=:0 XAUTHORITY=/var/run/lightdm/root/:0
off() {
! systemctl is-active --quiet firewall.service ||
! systemctl is-active --quiet usbguard.service ||
[ -n "$(ls -A /etc/contest-restrictions/disabled 2>/dev/null)" ]
}
show() {
width=$(xdpyinfo 2>/dev/null | awk '/dimensions/ {split($2, a, "x"); print a[1]}')
[ -n "$width" ] || return
xmessage -geometry "${width}x70+0+0" -buttons "" \
-xrm "*overrideRedirect: true" \
-fg white -bg red3 -fn "-*-helvetica-bold-r-*-*-34-*-*-*-*-*-*-*" \
"CONTEST RESTRICTIONS ARE OFF ON THIS MACHINE - CALL THE STAFF" >/dev/null 2>&1 &
pid=$!
shown=$(date +%s)
}
pid=""
shown=0
while true; do
if off && [ -e "$XAUTHORITY" ]; then
if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
show
elif [ $(( $(date +%s) - shown )) -ge 15 ]; then
old=$pid
show
sleep 1
kill "$old" 2>/dev/null
fi
elif [ -n "$pid" ]; then
kill "$pid" 2>/dev/null
pid=""
fi
sleep 3
done
@@ -0,0 +1,11 @@
[Unit]
Description=Red banner on the screen while a contest restriction is off
After=display-manager.service
[Service]
ExecStart=/usr/local/sbin/contest-restrictions-warning
Restart=always
RestartSec=3
[Install]
WantedBy=graphical.target
+38
View File
@@ -0,0 +1,38 @@
#!/bin/bash
# Manual staff command for permanently erasing the team account's files.
# Stops the desktop, deletes the home and team-owned files on the root
# filesystem, then recreates the home. No backup or undo.
# Never invoke from boot, login, a timer, cron, a playbook or automated tests.
# Run only after explicit staff authorization and verified backups.
# The --now argument is internal to the transient service after confirmation.
set -u
[ "$(id -u)" -eq 0 ] || { echo "Run this script as root (sudo)." >&2; exit 1; }
if [ "${1:-}" != --now ]; then
echo "The files of the team account will be deleted on this machine."
read -rp "Type RESET to continue: " answer
[ "$answer" = RESET ] || { echo "Cancelled."; exit 1; }
# The reset ends the desktop session, and with it this terminal if the
# script was started from the desktop. It runs as a separate service.
systemd-run --unit=reset-team-home --collect --quiet "$(readlink -f "$0")" --now
echo "Reset started. The screen restarts in a few seconds."
exit 0
fi
systemctl stop lightdm
loginctl terminate-user team 2>/dev/null
sleep 2
pkill -KILL -u team 2>/dev/null
sleep 1
for m in /home/team/.vscode/extensions /home/team/.vscode/extensions-root; do
umount "$m" 2>/dev/null
done
rm -rf --one-file-system /home/team
find / -xdev \( -path /proc -o -path /sys -o -path /dev \) -prune -o -user team -print0 2>/dev/null \
| xargs -0 -r rm -rf --one-file-system
mkdir /home/team
cp -a /etc/skel/. /home/team/
chown -R team:teams /home/team
chmod 750 /home/team
systemctl restart team-vscode-extensions.service
systemctl start lightdm
echo "The team account is clean."
+14
View File
@@ -0,0 +1,14 @@
[Unit]
Description=Wait for the clock before the desktop starts (at most 30 seconds)
After=chrony.service firewall.service contest-dns.service contest-proxy.service
Wants=chrony.service
Before=display-manager.service
[Service]
Type=oneshot
# Three checks 10 seconds apart, then the desktop starts anyway
ExecStart=-/usr/bin/chronyc waitsync 3 1
TimeoutStartSec=45
[Install]
WantedBy=multi-user.target