Skip to content

Commit

Permalink
build: Configure login.defs location and defaults
Browse files Browse the repository at this point in the history
Add support for a different location of `/etc/login.defs` file and
override the assumed defaults if the file is not present.
  • Loading branch information
max-ishere committed Oct 19, 2024
1 parent 4d0c091 commit 1c24c05
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 39 deletions.
30 changes: 23 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
41 changes: 39 additions & 2 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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: <https://github.com/rharish101/ReGreet/issues/89>
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",
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
63 changes: 35 additions & 28 deletions src/sysutil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -39,12 +37,26 @@ pub struct SysUtil {
}

impl SysUtil {
pub fn new() -> IOResult<Self> {
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<Self> {
let path = (*LOGIN_DEFS_PATHS).iter().try_for_each(|path| {
if let Ok(true) = AsRef::<Path>::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:?}");

Expand All @@ -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();

Expand Down Expand Up @@ -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<SessionMap> {
fn init_sessions() -> io::Result<SessionMap> {
let mut found_session_names = HashSet::new();
let mut sessions = HashMap::new();

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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),
}
}

Expand Down Expand Up @@ -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<u64> {
NormalUser::parse_number(num)
}
Expand Down

0 comments on commit 1c24c05

Please sign in to comment.