From dcea0ba9346ba1489998545e4101730a1c8f23c9 Mon Sep 17 00:00:00 2001 From: nevaforget Date: Fri, 7 Aug 2026 13:03:41 +0200 Subject: [PATCH] Match nologin shells by file name, not full path The nologin filter compared against fixed FHS paths, so accounts whose shell lives elsewhere passed through. On NixOS the 32 nixbld build users have UIDs in the 30001-30032 range and a shell at /run/current-system/sw/bin/nologin, and were listed as login candidates. Comparing the file name instead covers every distribution layout. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/users.rs | 44 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9092e0..f5d190d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,7 @@ dependencies = [ [[package]] name = "moongreet" -version = "0.10.1" +version = "0.10.2" dependencies = [ "gdk-pixbuf", "gdk4", diff --git a/Cargo.toml b/Cargo.toml index c1db339..1f96a2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "moongreet" -version = "0.10.1" +version = "0.10.2" edition = "2024" description = "A greetd greeter for Wayland with GTK4 and Layer Shell" license = "MIT" diff --git a/src/users.rs b/src/users.rs index 56f4c93..3736d13 100644 --- a/src/users.rs +++ b/src/users.rs @@ -11,13 +11,12 @@ const DEFAULT_PASSWD: &str = "/etc/passwd"; const DEFAULT_ACCOUNTSSERVICE_DIR: &str = "/var/lib/AccountsService/icons"; const GRESOURCE_PREFIX: &str = "/dev/moonarch/moongreet"; -/// Shells that indicate a user cannot log in. -const NOLOGIN_SHELLS: &[&str] = &[ - "/usr/sbin/nologin", - "/sbin/nologin", - "/bin/false", - "/usr/bin/nologin", -]; +/// Shell file names that indicate a user cannot log in. +/// +/// Matched on the file name rather than the full path: distributions place +/// these binaries in different locations, and on NixOS the path is +/// /run/current-system/sw/bin/nologin. +const NOLOGIN_SHELLS: &[&str] = &["nologin", "false"]; /// Represents a system user suitable for login. #[derive(Debug, Clone)] @@ -73,7 +72,11 @@ pub fn get_users(passwd_path: Option<&Path>) -> Vec { if !(MIN_UID..=MAX_UID).contains(&uid) { continue; } - if NOLOGIN_SHELLS.contains(&shell) { + let shell_name = Path::new(shell) + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or(shell); + if NOLOGIN_SHELLS.contains(&shell_name) { continue; } // Path traversal prevention @@ -194,6 +197,31 @@ mod tests { assert!(users.is_empty()); } + #[test] + fn skip_nologin_users_at_any_path() { + let dir = tempfile::tempdir().unwrap(); + let path = make_passwd( + dir.path(), + "nixbld1:x:30001:30000:Nix build user 1:/var/empty:/run/current-system/sw/bin/nologin\n\ + svc:x:1001:1001::/var/empty:/nix/store/abc123-shadow-4.17.4/bin/nologin\n\ + disabled:x:1002:1002::/home/disabled:/run/current-system/sw/bin/false\n", + ); + let users = get_users(Some(&path)); + assert!(users.is_empty()); + } + + #[test] + fn keep_login_shell_with_similar_name() { + let dir = tempfile::tempdir().unwrap(); + let path = make_passwd( + dir.path(), + "alice:x:1000:1000:Alice:/home/alice:/run/current-system/sw/bin/zsh\n", + ); + let users = get_users(Some(&path)); + assert_eq!(users.len(), 1); + assert_eq!(users[0].username, "alice"); + } + #[test] fn skip_users_with_slash_in_name() { let dir = tempfile::tempdir().unwrap();