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.
This commit is contained in:
2026-08-07 13:03:41 +02:00
parent 86d0f5e56d
commit dcea0ba934
3 changed files with 38 additions and 10 deletions
Generated
+1 -1
View File
@@ -575,7 +575,7 @@ dependencies = [
[[package]]
name = "moongreet"
version = "0.10.1"
version = "0.10.2"
dependencies = [
"gdk-pixbuf",
"gdk4",
+1 -1
View File
@@ -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"
+36 -8
View File
@@ -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<User> {
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();