From 2168c3d3f4f257be96ea88541477461399e8fd98 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 00:12:05 +0000 Subject: [PATCH] stoa-doctor: report faillock lockout, locked keyring, greeter auto-sync Three failures that until now were silent: - pam_faillock counting failed logins: at `deny` of them sudo, polkit, the lock screen and greetd all refuse the right password together. - The login keyring locked, or another collection as default: Noctalia seals clipboard pins with a key from the default collection, so pins vanish at the next restart without an error. Also flags a greetd PAM stack missing pam_gnome_keyring. - [shell.greeter_sync] auto_sync = true in Noctalia's GUI state, which wins over config.toml and turns every wallpaper rotation into a polkit password prompt. enable-stoa-greetd.sh: correct the claim that clipboard state under the old default keyring was never persisted. On a real machine it held the user's pins; say where the backup is and which keyring holds its key. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MN4kDREFU6ZpG9wyDPKuhp --- scripts/stoa-doctor.sh | 63 +++++++++++++++++++++++++++++++++++++ setup/enable-stoa-greetd.sh | 22 ++++++------- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/scripts/stoa-doctor.sh b/scripts/stoa-doctor.sh index c86dec4..6b97f2d 100755 --- a/scripts/stoa-doctor.sh +++ b/scripts/stoa-doctor.sh @@ -201,6 +201,69 @@ else echo "[INFO] Stoa polkit rule: not verifiable from this session (no journal access, ${_POLKIT_RULE} not readable)" >> "$LOG" fi +# ── Account locked by pam_faillock? ── +# Every PAM authentication that fails — a wrong password, or a sudo/pkexec +# with no way to prompt — counts against the account, and at `deny` of +# them (Arch default 3) pam_faillock refuses even the right password for +# unlock_time. sudo, polkit, the lock screen and the login screen all fail +# together, and nothing says why. `faillock` lists the counted ("V") ones; +# a user may read their own record. +if command -v faillock &>/dev/null; then + _fl_deny="$(awk -F= '/^[[:space:]]*deny[[:space:]]*=/ { gsub(/[[:space:]]/, "", $2); print $2 }' \ + /etc/security/faillock.conf 2>/dev/null | tail -n1)" + _fl_deny="${_fl_deny:-3}" + if _fl_out="$(faillock --user "$(id -un)" 2>/dev/null)"; then + _fl_count="$(printf '%s\n' "$_fl_out" | awk '$NF == "V"' | wc -l)" + if [ "$_fl_count" -ge "$_fl_deny" ]; then + _fail "Account locked by pam_faillock (${_fl_count} failed attempts) — run: faillock --user $(id -un) --reset" + elif [ "$_fl_count" -gt 0 ]; then + _warn "pam_faillock counted ${_fl_count} of ${_fl_deny} failed logins — at ${_fl_deny} sudo/polkit/login stop accepting the password" + else + _ok "pam_faillock: no failed attempts counted" + fi + fi +fi + +# ── Login keyring unlocked? ── +# Noctalia keeps clipboard pins encrypted, with the key in the Secret +# Service's default collection. pam_gnome_keyring (greetd login) unlocks +# only the collection named "login"; if that one is locked, or another one +# is the default, pins are dropped at the next restart without an error. +_KR_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/keyrings" +if [ -f "${_KR_DIR}/default" ] && [ "$(cat "${_KR_DIR}/default" 2>/dev/null)" != "login" ]; then + _warn "Default keyring is '$(cat "${_KR_DIR}/default")', not 'login' — the login screen does not unlock it, so clipboard pins cannot persist" +fi +if command -v busctl &>/dev/null; then + _kr_locked="$(busctl --user get-property org.freedesktop.secrets \ + /org/freedesktop/secrets/collection/login \ + org.freedesktop.Secret.Collection Locked 2>/dev/null)" + case "$_kr_locked" in + "b false") _ok "Login keyring unlocked" ;; + "b true") _warn "Login keyring is locked — its password differs from yours, or you did not log in through greetd; clipboard pins will not persist" ;; + esac +fi +if systemctl is-enabled greetd.service &>/dev/null \ + && [ -r /etc/pam.d/greetd ] \ + && ! grep -q '^[^#]*pam_gnome_keyring' /etc/pam.d/greetd; then + _warn "/etc/pam.d/greetd does not load pam_gnome_keyring — re-run setup/enable-stoa-greetd.sh" +fi + +# ── Greeter sync on every change? ── +# config.toml sets [shell.greeter_sync] auto_sync = false because each sync +# escalates through pkexec — one polkit password box per wallpaper rotation. +# The GUI toggle writes ~/.local/state/noctalia/settings.toml, which wins +# over config.toml, so turning it on there brings the prompts back. +_NOC_STATE="${XDG_STATE_HOME:-$HOME/.local/state}/noctalia/settings.toml" +# Parsed as TOML: the GUI may write it as a table or as an inline value. +if [ -f "$_NOC_STATE" ] && python3 -c ' +import sys, tomllib +with open(sys.argv[1], "rb") as f: + s = tomllib.load(f) +sys.exit(0 if s.get("shell", {}).get("greeter_sync", {}).get("auto_sync") is True else 1) +' "$_NOC_STATE" 2>/dev/null; then + _warn "Greeter auto-sync is on in ${_NOC_STATE} — polkit asks for a password on every wallpaper change (Settings → Security → Noctalia Greeter)" +fi + if [ "$STOA_WM_NAME" = "niri" ]; then # ── niri config installed? ── # niri falls back to its built-in default config (no Stoa binds, no diff --git a/setup/enable-stoa-greetd.sh b/setup/enable-stoa-greetd.sh index 84a1de2..9948566 100755 --- a/setup/enable-stoa-greetd.sh +++ b/setup/enable-stoa-greetd.sh @@ -283,19 +283,19 @@ _fix_keyring_default() { echo -e " ${O}[✓] Default keyring: '${current}' -> 'login' (the one pam_gnome_keyring actually unlocks).${R}" # Any encrypted Noctalia clipboard history on disk was sealed with a - # storage key stored under the OLD default collection — now - # unreachable, since that collection is no longer what "default" means. - # Noctalia's storage_key_provider refuses to mint a replacement key - # while it sees encrypted data it can't decrypt (a corruption guard), - # so persistence stays permanently broken until that stale state is - # out of the way. Moved aside rather than deleted; whatever was pinned - # under the old broken default was never actually persisted anyway - # (the collection was locked the whole time), so there is nothing - # recoverable in it — this is just being cautious with someone else's - # files rather than assuming that. + # storage key stored under the OLD default collection, which is no + # longer what "default" means. Noctalia's storage_key_provider refuses + # to mint a replacement key while it sees encrypted data it can't + # decrypt (a corruption guard), so persistence stays broken until that + # state is out of the way. Moved aside, not deleted: if the old + # collection was unlocked in the session (it usually was — it was the + # default), the pins in it were really saved, and the backup together + # with that keyring file is what it takes to get them back. if [ -d "$NOCTALIA_CLIPBOARD_STATE" ]; then mv "$NOCTALIA_CLIPBOARD_STATE" "${NOCTALIA_CLIPBOARD_STATE}.bak-$(date +%s)" - echo -e " ${O}[✓] Cleared orphaned encrypted clipboard state so Noctalia can mint a fresh key.${R}" + echo -e " ${O}[✓] Moved the old encrypted clipboard state aside so Noctalia can mint a fresh key.${R}" + echo -e " ${S} Pins pinned before now are in ${NOCTALIA_CLIPBOARD_STATE}.bak-*,${R}" + echo -e " ${S} sealed with a key in the '${current}' keyring — keep both to recover them.${R}" fi }