diff --git a/Cargo.lock b/Cargo.lock index fd27f71..74b257c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,18 +265,19 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "const_format" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] name = "const_format_proc_macros" -version = "0.2.32" +version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" dependencies = [ "proc-macro2", "quote", @@ -879,11 +880,26 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "konst" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330f0e13e6483b8c34885f7e6c9f19b1a7bd449c673fbb948a51c99d66ef74f4" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" @@ -1199,7 +1215,6 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" name = "regreet" version = "0.1.1" dependencies = [ - "anyhow", "chrono", "clap", "const_format", @@ -1208,6 +1223,7 @@ dependencies = [ "glob", "greetd_ipc", "gtk4", + "lazy_static", "lru", "pwd", "regex", diff --git a/Cargo.toml b/Cargo.toml index 9bb65cd..d0fac97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,15 +14,15 @@ license = "GPL-3.0-or-later" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.89" chrono = { version = "0.4.22", default-features = false } clap = { version = "4.1.4", features = ["derive"] } -const_format = "0.2.26" +const_format = { version = "0.2.33", features = ["rust_1_64"] } derivative = "2.2.0" file-rotate = "0.7.2" glob = "0.3.0" greetd_ipc = { version = "0.9.0", features = ["tokio-codec"] } gtk4 = "0.5" +lazy_static = "1.5.0" lru = "0.9.0" pwd = "1.4.0" regex = "1.7.1" diff --git a/src/constants.rs b/src/constants.rs index ef442d6..08afd68 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -4,8 +4,6 @@ //! Stores constants that can be configured at compile time -use const_format::concatcp; - /// Get an environment variable during compile time, else return a default. macro_rules! env_or { ($name:expr, $default:expr) => { @@ -49,6 +47,45 @@ pub const POWEROFF_CMD: &str = env_or!("POWEROFF_CMD", "poweroff"); /// Default greeting message pub const GREETING_MSG: &str = "Welcome back!"; +/// `:`-separated search path for `login.defs` file. +/// +/// By default this file is at `/etc/login.defs`, however some distros (e.g. Tumbleweed) move it to other locations. +/// +/// See: +pub const LOGIN_DEFS_PATHS: &[&str] = { + const ENV: &str = env_or!("LOGIN_DEFS_PATHS", "/etc/login.defs:/usr/etc/login.defs"); + &str_split!(ENV, ':') +}; + +lazy_static! { + /// Override the default `UID_MIN` in `login.defs`. If the string cannot be parsed at runtime, the value is `1_000`. + /// + /// This is not meant as a configuration facility. Only override this value if it's a different default in the + /// `passwd` suite. + pub static ref LOGIN_DEFS_UID_MIN: u64 = { + const DEFAULT: u64 = 1_000; + const ENV: &str = env_or!("LOGIN_DEFS_UID_MIN", formatcp!("{DEFAULT}")); + + ENV.parse() + .map_err(|e| error!("Failed to parse LOGIN_DEFS_UID_MIN='{ENV}': {e}. This is a compile time mistake!")) + .unwrap_or(DEFAULT) + }; + + /// Override the default `UID_MAX` in `login.defs`. If the string cannot be parsed at runtime, the value is + /// `60_000`. + /// + /// This is not meant as a configuration facility. Only override this value if it's a different default in the + /// `passwd` suite. + pub static ref LOGIN_DEFS_UID_MAX: u64 = { + const DEFAULT: u64 = 60_000; + const ENV: &str = env_or!("LOGIN_DEFS_UID_MAX", formatcp!("{DEFAULT}")); + + ENV.parse() + .map_err(|e| error!("Failed to parse LOGIN_DEFS_UID_MAX='{ENV}': {e}. This is a compile time mistake!")) + .unwrap_or(DEFAULT) + }; +} + /// Directories separated by `:`, containing desktop files for X11/Wayland sessions pub const SESSION_DIRS: &str = env_or!( "SESSION_DIRS", diff --git a/src/main.rs b/src/main.rs index b755dbe..945bb1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,10 @@ use crate::gui::{Greeter, GreeterInit}; #[macro_use] extern crate tracing; +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate const_format; #[cfg(test)] #[macro_use] diff --git a/src/sysutil.rs b/src/sysutil.rs index ad8ddc3..a51611f 100644 --- a/src/sysutil.rs +++ b/src/sysutil.rs @@ -4,21 +4,19 @@ //! Helper for system utilities like users and sessions -use std::collections::HashMap; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::env; -use std::fs::read; -use std::fs::read_to_string; -use std::io::Result as IOResult; +use std::fs::{read, read_to_string}; +use std::io; +use std::ops::ControlFlow; use std::path::Path; use std::str::from_utf8; -use anyhow::Context; use glob::glob; use pwd::Passwd; use regex::Regex; -use crate::constants::SESSION_DIRS; +use crate::constants::{LOGIN_DEFS_PATHS, LOGIN_DEFS_UID_MAX, LOGIN_DEFS_UID_MIN, SESSION_DIRS}; /// XDG data directory variable name (parent directory for X11/Wayland sessions) const XDG_DIR_ENV_VAR: &str = "XDG_DATA_DIRS"; @@ -39,12 +37,26 @@ pub struct SysUtil { } impl SysUtil { - pub fn new() -> IOResult { - let normal_user = read_to_string(NormalUser::PATH) - .with_context(|| format!("Failed to read `{}`", NormalUser::PATH)) - .map(|text| NormalUser::parse_login_defs(&text)) - .map_err(|err| warn!("{err}")) - .unwrap_or_default(); + pub fn new() -> io::Result { + let path = (*LOGIN_DEFS_PATHS).iter().try_for_each(|path| { + if let Ok(true) = AsRef::::as_ref(&path).try_exists() { + ControlFlow::Break(path) + } else { + ControlFlow::Continue(()) + } + }); + + let normal_user = match path { + ControlFlow::Break(path) => read_to_string(path) + .map_err(|err| warn!("Failed to read '{path}': {err}")) + .map(|text| NormalUser::parse_login_defs(&text)) + .unwrap_or_default(), + ControlFlow::Continue(()) => { + warn!("`login.defs` file not found in these paths: {LOGIN_DEFS_PATHS:?}",); + + NormalUser::default() + } + }; debug!("{normal_user:?}"); @@ -59,7 +71,7 @@ impl SysUtil { /// Get the list of regular users. /// /// These are defined as a list of users with UID between `UID_MIN` and `UID_MAX`. - fn init_users(normal_user: NormalUser) -> IOResult<(UserMap, ShellMap)> { + fn init_users(normal_user: NormalUser) -> io::Result<(UserMap, ShellMap)> { let mut users = HashMap::new(); let mut shells = HashMap::new(); @@ -108,7 +120,7 @@ impl SysUtil { /// /// These are defined as either X11 or Wayland session desktop files stored in specific /// directories. - fn init_sessions() -> IOResult { + fn init_sessions() -> io::Result { let mut found_session_names = HashSet::new(); let mut sessions = HashMap::new(); @@ -299,21 +311,16 @@ struct NormalUser { impl Default for NormalUser { fn default() -> Self { Self { - min_uid: Self::MIN_DEFAULT, - max_uid: Self::MAX_DEFAULT, + min_uid: *LOGIN_DEFS_UID_MIN, + max_uid: *LOGIN_DEFS_UID_MAX, } } } impl NormalUser { - /// Path to a file that can be parsed by [`Self::parse_login_defs`]. - pub const PATH: &'static str = "/etc/login.defs"; - - const MIN_DEFAULT: u64 = 1_000; - const MAX_DEFAULT: u64 = 60_000; - - /// Parses the [`Self::PATH`] file content and looks for `UID_MIN` and `UID_MAX` definitions. If a definition is - /// missing or causes parsing errors, the default values [`Self::MIN_DEFAULT`] and [`Self::MAX_DEFAULT`] are used. + /// Parses the `login.defs` file content and looks for `UID_MIN` and `UID_MAX` definitions. If a definition is + /// missing or causes parsing errors, the default values [`struct@LOGIN_DEFS_UID_MIN`] and + /// [`struct@LOGIN_DEFS_UID_MAX`] are used. /// /// This parser is highly specific to parsing the 2 required values, thus it focuses on doing the least amout of /// compute required to extracting them. @@ -350,8 +357,8 @@ impl NormalUser { } Self { - min_uid: min.unwrap_or(Self::MIN_DEFAULT), - max_uid: max.unwrap_or(Self::MAX_DEFAULT), + min_uid: min.unwrap_or(*LOGIN_DEFS_UID_MIN), + max_uid: max.unwrap_or(*LOGIN_DEFS_UID_MAX), } } @@ -440,7 +447,7 @@ mod tests { #[test_case("0x" => None; "0x isn't a hex number")] #[test_case("10" => Some(10); "decimal")] #[test_case("0777" => Some(0o777); "octal")] - #[test_case("0xDeadBeef" => Some(0xDeadBeef); "hex")] + #[test_case("0xDeadBeef" => Some(0xdead_beef); "hex")] fn parse_number(num: &str) -> Option { NormalUser::parse_number(num) }