Skip to main content

max / alloy_tui

Pick pam-sys + thin in-crate wrapper for the lockscreen auth path Resolves open question #2 in crates/alloy_lockscreen/README.md. The choice was not pam-client vs pam-sys but "use a dormant crate with the right API" vs "own the wrapper ourselves on top of pam-sys." Taking the second. - The only Rust precedent for security-critical PAM is greetd, and greetd uses pam-sys directly even though pam-client exists — greetd's authors note pam-client's higher-level abstractions aren't worth the awkwardness around pam_set_item. Same shape for the lockscreen. - pam-client has been dormant since 2022 (single maintainer). For the only auth path the user has back into their session, owning ~200 lines of stable FFI wrapper is cheaper than depending on unmaintained upstream code. PAM's app-side ABI has been stable for 20+ years, so "owning the wrapper" doesn't imply ongoing maintenance cost. - Audit story is cleaner: every line on the auth path lives in alloy_lockscreen. No third-party code between the user's keystrokes and pam_authenticate. Wrapper shape (deferred to implementation): a Result-returning Context::new(service, conversation) that mirrors pam-client's API (MPL-2.0 — we can copy the shape without taking the dep), with a ConversationHandler trait that owns an mpsc::Receiver so the egui input handler feeds the auth task on a worker. Service name alloy-lock, registered at /etc/pam.d/alloy-lock. Two open questions remain: process model (single-process is the default) and lockscreen-process crash recovery.
Co-Authored-By
Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-26 19:52 UTC
Signed with PGP, not checked
Commit: db0eb44bf74e36f47193bde1fc1e09c72eb59a54
Parent: c69b4de
1 file changed, +2 insertions, -1 deletion
@@ -66,11 +66,12 @@
66 66 | Wayland client | **`smithay-client-toolkit`** | First-class `session_lock` module encodes ext-session-lock-v1's lifecycle (must wait for `locked`, must `unlock` before drop) as types. Upstream `examples/session_lock.rs` is a working starting point. Multi-maintainer Smithay org vs wayrs's single maintainer. Dep-tree premium (~20 crates over wayrs) doesn't matter for a compile-once binary. |
67 67 | egui rendering | **`egui` + `egui_glow`** (not `eframe`) | We need to own the Wayland connection, which means we can't use winit (which eframe wraps). egui_glow lets us submit egui paint to GL contexts we manage. |
68 68 | GL context | **`glutin`** (or `glow` directly if SCTK exposes EGL helpers) | Decision deferred until first protocol code lands; pick after seeing what SCTK gives us for EGL surface creation. |
69 + | PAM | **`pam-sys`** + a thin in-crate wrapper (`src/pam/`) | The only Rust-PAM consumer with real security-critical precedent is greetd, which uses `pam-sys` directly even though `pam-client` exists — pam-client's higher-level abstractions aren't worth the awkwardness around `pam_set_item`. pam-client is also dormant since 2022; for the only auth path the user has back into the session, owning ~200 lines of stable FFI wrapper is cheaper than depending on an unmaintained upstream. The wrapper exposes a `Result`-returning `Context::new(service, conversation)` mirroring pam-client's shape (which we can copy — MPL-2.0 is GPLv3-compat — without taking the dep), with a `ConversationHandler` trait that owns an `mpsc::Receiver` so the egui input handler feeds the auth task. Service name: `alloy-lock`, registered at `/etc/pam.d/alloy-lock`. |
69 70
70 71 ## Open questions blocking implementation
71 72
72 73 1. ~~**Wayland protocol crate.**~~ Resolved: smithay-client-toolkit. See above.
73 - 2. **PAM crate.** `pam` (canonical bindings, may be stale), `pam-client` (newer, more ergonomic), or vendoring a thin FFI ourselves. PAM is a small enough API surface that vendoring is reasonable if no current crate is well-maintained.
74 + 2. ~~**PAM crate.**~~ Resolved: pam-sys + thin in-crate wrapper. See Stack picks above.
74 75 3. **Process model.** Single-process binding all outputs, or one process per output. Single-process is simpler and matches how swaylock works; one-process-per-output gets us isolation if PAM blocks. Default: single-process.
75 76 4. **Recovery path.** If the lockscreen process crashes while holding the session lock, the compositor stays locked and the user is stranded. ext-session-lock-v1 has explicit semantics for this; document the chosen recovery path (compositor-side fallback, watchdog, intentional crash-to-greeter) before shipping.
76 77