feat: complete session preferences and PAM fixture gates
This commit is contained in:
@@ -14,90 +14,61 @@ use crate::{
|
||||
error::{Error, ResultExt},
|
||||
};
|
||||
|
||||
/// An account that can be offered by the login screen.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Identity {
|
||||
/// Canonical username.
|
||||
pub username: String,
|
||||
/// Numeric user ID.
|
||||
pub uid: u32,
|
||||
/// Primary group ID.
|
||||
pub gid: u32,
|
||||
/// Home directory.
|
||||
pub home: PathBuf,
|
||||
/// Login shell.
|
||||
pub shell: PathBuf,
|
||||
}
|
||||
|
||||
/// Effective account state determined from the login shell and shadow aging data.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AccountState {
|
||||
/// Account can be offered for login.
|
||||
Active,
|
||||
/// Password is disabled by the shadow lock marker.
|
||||
Locked,
|
||||
/// Password must be changed before login can continue.
|
||||
PasswordExpired,
|
||||
/// Account expiration date has elapsed.
|
||||
AccountExpired,
|
||||
/// Password inactivity period has elapsed.
|
||||
AccountInactive,
|
||||
/// Login shell explicitly disallows interactive sessions.
|
||||
NoLoginShell,
|
||||
/// Shadow data was not available or could not be parsed.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Non-sensitive password and account aging information.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct AccountStatus {
|
||||
/// Effective state used by the chooser and diagnostics.
|
||||
pub state: AccountState,
|
||||
/// Day the password was last changed, if present.
|
||||
pub password_changed_day: Option<i64>,
|
||||
/// Day the password expires, if the policy specifies one.
|
||||
pub password_expiry_day: Option<i64>,
|
||||
/// Day the account expires, if specified.
|
||||
pub account_expiry_day: Option<i64>,
|
||||
/// Day after which an expired password disables the account.
|
||||
pub inactive_after_day: Option<i64>,
|
||||
}
|
||||
|
||||
impl AccountStatus {
|
||||
/// Returns whether this account may be presented as an active login choice.
|
||||
pub const fn is_login_allowed(self) -> bool {
|
||||
matches!(self.state, AccountState::Active)
|
||||
}
|
||||
}
|
||||
|
||||
/// An identity paired with its non-sensitive account policy state.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AccountRecord {
|
||||
/// Discovered NSS identity.
|
||||
pub identity: Identity,
|
||||
/// Current local account status.
|
||||
pub status: AccountStatus,
|
||||
}
|
||||
|
||||
/// A currently logged-in process obtained from UTMPX.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ActiveSession {
|
||||
/// Login name associated with the session.
|
||||
pub username: String,
|
||||
/// UTMPX line, such as `tty2` or `pts/0`.
|
||||
pub terminal: String,
|
||||
/// Session leader PID when reported by UTMPX.
|
||||
pub pid: libc::pid_t,
|
||||
/// Remote host, if supplied by the session producer.
|
||||
pub host: Option<String>,
|
||||
}
|
||||
|
||||
/// Runtime session-management backend detected on this host.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SessionBackend {
|
||||
/// systemd-logind runtime markers are available.
|
||||
SystemdLogind,
|
||||
/// UTMPX is available as a conservative fallback.
|
||||
Utmpx,
|
||||
}
|
||||
|
||||
@@ -113,15 +84,10 @@ struct ShadowEntry {
|
||||
/// A non-authoritative account lockout summary.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct FaillockStatus {
|
||||
/// Whether the faillock helper returned a usable result.
|
||||
pub available: bool,
|
||||
/// Number of observed failed attempts.
|
||||
pub failures: u32,
|
||||
/// Configured failure threshold, when discoverable.
|
||||
pub max_tries: Option<u32>,
|
||||
/// Remaining attempts before the advisory threshold.
|
||||
pub remaining: Option<u32>,
|
||||
/// Whether the advisory threshold has been reached.
|
||||
pub locked_out: bool,
|
||||
}
|
||||
|
||||
@@ -291,12 +257,8 @@ pub fn faillock_status(username: &str, config: &AccountsConfig) -> Result<Faillo
|
||||
Ok(faillock_status_from_output(&output.stdout, max_tries))
|
||||
}
|
||||
|
||||
/// Converts stable helper output into an advisory lockout status.
|
||||
///
|
||||
/// This parser accepts both the legacy `count timestamp` format and modern
|
||||
/// rows containing a username prefix or timestamp columns. It intentionally
|
||||
/// treats malformed output as zero failures instead of making authentication
|
||||
/// fail open or fail closed.
|
||||
/// Parses the legacy count format and current rows with usernames or timestamps.
|
||||
/// Malformed output reports zero failures and does not control authentication.
|
||||
pub fn faillock_status_from_output(output: &[u8], max_tries: Option<u32>) -> FaillockStatus {
|
||||
let failures = parse_failure_count(output);
|
||||
let remaining = max_tries.map(|limit| limit.saturating_sub(failures));
|
||||
|
||||
Reference in New Issue
Block a user