#!/bin/sh # nosignal-sudo-toggle — time-boxed passwordless sudo ("sudoless" mode). # # enable : grant the human user NOPASSWD: ALL for WINDOW_MIN minutes, then # auto-revert. Requires root (run via `sudo`); you authenticate ONCE. # disable: revert now (remove the drop-in, cancel the timer). Requires root, # but inside an active window `sudo -n` works without a password. # status : print active/inactive (+minutes remaining). Needs NO root — reads a # world-readable stamp, so the panel toggle can poll it. # # Safety: # - the sudoers drop-in is validated with `visudo -cf` BEFORE it is installed; # a malformed file is never moved into place (a broken sudoers locks you out). # - auto-revert is a root-owned transient systemd timer (no password needed). # - a boot-time oneshot (nosignal-sudoless-boot-clean.service) removes the # drop-in on every boot, so a reboot during the window can never leave # passwordless sudo enabled (transient timers don't survive reboot). set -eu DROPIN=/etc/sudoers.d/01-nosignal-sudoless STAMP=/run/nosignal-sudoless.stamp # tmpfs: world-readable, cleared on reboot WINDOW_MIN=15 REVERT_UNIT=nosignal-sudoless-revert SELF=/usr/local/bin/nosignal-sudo-toggle die() { echo "nosignal-sudo-toggle: $*" >&2; exit 1; } need_root() { [ "$(id -u)" -eq 0 ] || die "must run as root (use: sudo $SELF $1)"; } target_user() { # who to grant: the invoking human (via sudo), never root if [ -n "${SUDO_USER:-}" ] && [ "$SUDO_USER" != "root" ]; then printf '%s\n' "$SUDO_USER"; return 0 fi # fallback: the lowest normal login account (uid 1000-60000) with a shell awk -F: '$3>=1000 && $3<60000 && $7 !~ /(nologin|false)$/ {print $1; exit}' /etc/passwd } cancel_timer() { # Stop ONLY the .timer (disarm a pending revert). NEVER stop the .service: # when disable() is invoked BY that service (the timer fired), stopping it # would SIGTERM us mid-run — which is exactly the bug that left the drop-in # in place. reset-failed both is harmless cleanup. systemctl stop "${REVERT_UNIT}.timer" 2>/dev/null || true systemctl reset-failed "${REVERT_UNIT}.timer" "${REVERT_UNIT}.service" 2>/dev/null || true } cmd_enable() { need_root enable user=$(target_user) [ -n "$user" ] || die "could not determine target user" tmp=$(mktemp /tmp/nosignal-sudoless.XXXXXX) || die "mktemp failed" trap 'rm -f "$tmp"' EXIT cat > "$tmp" </dev/null 2>&1 || die "sudoers validation failed; aborting (no change made)" install -m 0440 -o root -g root "$tmp" "$DROPIN" rm -f "$tmp"; trap - EXIT date +%s > "$STAMP"; chmod 0644 "$STAMP" # arm the auto-revert (fresh 15-min window each enable) cancel_timer systemd-run --quiet --unit="$REVERT_UNIT" --on-active="${WINDOW_MIN}min" \ --description="Revert NoSignal timed passwordless sudo" \ "$SELF" disable >/dev/null 2>&1 \ || echo "WARNING: could not arm auto-revert timer; run 'sudo $SELF disable' manually" >&2 echo "sudoless ENABLED for $user — ${WINDOW_MIN} min (auto-reverts; reboot also clears it)" } cmd_enable_tui() { # Runs as the human user inside the floating prompt terminal (class # nosignal-sudo). Drives the SINGLE password prompt for `enable`, then keeps # the window readable on failure so the user sees why — the old flow let the # terminal vanish on failure, which read as "the switch is broken". # -k : ignore any cached/stale credential so the prompt is always fresh and # a pam_faillock lockout surfaces here instead of silently reverting. printf '\n Enable passwordless sudo for %s minutes.\n\n' "$WINDOW_MIN" if sudo -k -p " [sudo] password for %p: " "$SELF" enable; then printf '\n Done — closing.\n' sleep 1 return 0 fi # reached only when sudo failed (wrong password, or faillock lockout) printf '\n Could not enable passwordless sudo.\n' printf ' If you mistyped a few times the account may be briefly locked\n' printf ' (~10 min by pam_faillock). Wait and retry, or from a TTY:\n' printf ' su - then faillock --user %s --reset\n' "$(id -un)" printf '\n Press Enter to close. ' read -r _ || true return 1 } cmd_disable() { need_root disable # who held the grant — needed to clear their cached sudo ticket below u=$(awk '/NOPASSWD/{print $1; exit}' "$DROPIN" 2>/dev/null || true) # Remove the grant FIRST. If we're running from inside the revert service, # cancel_timer used to stop our own unit and kill us before this line ran, # leaving the drop-in in place. Drop the privilege before touching units. rm -f "$DROPIN" "$STAMP" sync # Invalidate the user's cached sudo credential too: sudo caches an auth # ticket (~15 min, timestamp_timeout) independent of the drop-in, so without # this a sudo run just before revert would stay passwordless past the window. [ -n "$u" ] && rm -f "/run/sudo/ts/$u" 2>/dev/null || true cancel_timer echo "sudoless DISABLED — password required again" } cmd_status() { if [ -f "$STAMP" ]; then start=$(cat "$STAMP" 2>/dev/null || echo 0) now=$(date +%s) left=$(( start + WINDOW_MIN*60 - now )) [ "$left" -lt 0 ] && left=0 echo "active $(( (left + 59) / 60 ))" # "active " else echo "inactive 0" fi } case "${1:-}" in enable) cmd_enable ;; enable-tui) cmd_enable_tui ;; disable) cmd_disable ;; status) cmd_status ;; *) echo "usage: nosignal-sudo-toggle {enable|enable-tui|disable|status}" >&2; exit 2 ;; esac