Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
370 changes: 47 additions & 323 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 4 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,19 @@ edition = "2024"
[dependencies]
anyhow = "1.0.100"
atomic_enum = "0.3.0"
cairo-rs = { version = "0.22.0", features = ["png"] }
cairo-rs = { version = "0.21.5", features = ["png"] }
chrono = "0.4.42"
clap = { version = "4.5.53", features = ["derive"] }
clap_complete = "4.5.64"
config = "0.15.18"
dirs = "6.0.0"
gdk-pixbuf = { version = "0.21.5", optional = true }
nix = { version = "0.31.3", features = ["event", "fs", "mman", "process", "time"] }
pam-rs = "0.9.5"
pango = { version = "0.22.0", optional = true }
pangocairo = { version = "0.22.0", optional = true }
nix = { version = "0.31.3", features = ["event", "fs", "mman", "process", "time", "user"] }
pango = { version = "0.21.5", optional = true }
pangocairo = { version = "0.21.5", optional = true }
serde = { version = "1.0.228", features = [ "derive" ] }
tracing = "0.1.41"
tracing-subscriber = "0.3.20"
uzers = "0.12.1"
wayland-client = "0.31.11"
wayland-protocols = { version = "0.32.9", features = ["client", "staging"] }
xkbcommon = "0.9.0"
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ compile with the latest stable Rust, I haven't tested older versions.
In addition, you'll need development libraries for the following, which can
probably be installed via your system package manager:

- clang
- glib
- gdk-pixbuf (optional, provides support for non-PNG image formats)
- pam
- pam (except openbsd)
- cairo
- pango (optional, provides support for system font loading)
- xkbcommon
Expand All @@ -37,6 +36,10 @@ gdk-pixbuf and pango are both optional, and are enabled with the features
`gdk-pixbuf` and `pango` respectively. These features are enabled by default,
you will need to disable them if you do not plan on using these libraries.

On OpenBSD targets, BSD Authentication is natively supported and will be
selected as the authentication backend automatically, PAM is not required
in this case.

With all of that, you should just be able to clone this repository, and run:

```sh
Expand Down
4 changes: 4 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ fn main() {

println!("cargo:rerun-if-env-changed=NLOCK_VERSION");
println!("cargo:rerun-if-env-changed=NLOCK_COMMIT");

if cfg!(not(target_os = "openbsd")) {
println!("cargo:rustc-link-lib=pam");
}
}
2 changes: 0 additions & 2 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@
rustPackages.clippy

cairo
clang
gdk-pixbuf
glib
libxkbcommon
Expand All @@ -71,7 +70,6 @@
];

RUST_SRC_PATH = rustPlatform.rustLibSrc;
LIBCLANG_PATH = "${clang.cc.lib}/lib";
};
});
};
Expand Down
3 changes: 0 additions & 3 deletions nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
rustPlatform,
installShellFiles,
cairo,
clang,
gdk-pixbuf,
glib,
libxkbcommon,
Expand Down Expand Up @@ -32,7 +31,6 @@ rustPlatform.buildRustPackage {

nativeBuildInputs = [
installShellFiles
clang
pkg-config
];

Expand All @@ -52,7 +50,6 @@ rustPlatform.buildRustPackage {
--fish <($out/bin/nlock completions fish)
'';

LIBCLANG_PATH = "${clang.cc.lib}/lib";
NLOCK_COMMIT = "${shortRev}"; # used to generate version string

meta = with lib; {
Expand Down
67 changes: 24 additions & 43 deletions src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026, Nathan Gill

use std::{os::fd::AsFd, sync::Arc};
use std::{os::fd::AsFd, sync::Arc, thread::JoinHandle};

use anyhow::{Result, anyhow};
use atomic_enum::atomic_enum;
use nix::{
errno::Errno,
poll::{PollFd, PollFlags, PollTimeout},
};
use pam_rs::{Client, PamFlag};
use tracing::{debug, warn};
use zeroize::Zeroizing;

use crate::{comm::PipeCommChannel, config::NLockConfig};
use crate::{auth_sys::AuthClient, comm::PipeCommChannel};

pub struct AuthChannel {
pub request: PipeCommChannel<String>,
Expand All @@ -38,43 +37,15 @@ pub enum AuthState {
Fail,
}

pub struct AuthConfig {
#[cfg(target_os = "linux")]
pub allow_empty: bool,
}

impl AuthConfig {
#[cfg_attr(not(target_os = "linux"), allow(unused_variables))]
pub fn new(config: &NLockConfig) -> Self {
Self {
#[cfg(target_os = "linux")]
allow_empty: config.general.pwd_allow_empty,
}
}
}

#[cfg_attr(not(target_os = "linux"), allow(unused_variables))]
fn authenticate(config: &AuthConfig, username: &str, password: Zeroizing<String>) -> Result<()> {
let mut client = Client::with_password("nlock")?;
client
.conversation_mut()
.set_credentials(username, password.as_str());

#[cfg_attr(not(target_os = "linux"), allow(unused_mut))]
let mut flags = PamFlag::None;

#[cfg(target_os = "linux")]
if !config.allow_empty {
flags = PamFlag::Disallow_Null_AuthTok;
}

client.authenticate(flags)?;

fn authenticate(client: &mut AuthClient, password: Zeroizing<String>) -> Result<()> {
client.set_password(password);
client.authenticate()?;
Ok(())
}

/// Handle an authentication request, returning a value to indicate success
fn handle_auth_request(config: &AuthConfig, auth_comm: Arc<AuthChannel>, username: &str) -> bool {
fn handle_auth_request(client: &mut AuthClient, auth_comm: Arc<AuthChannel>) -> bool {
let pwd = match auth_comm.request.read().map(Zeroizing::new) {
Ok(p) => p,
Err(e) => {
Expand All @@ -83,7 +54,7 @@ fn handle_auth_request(config: &AuthConfig, auth_comm: Arc<AuthChannel>, usernam
}
};

match authenticate(config, username, pwd) {
match authenticate(client, pwd) {
Ok(()) => true,
Err(e) => {
warn!("Auth failed: {e}");
Expand All @@ -92,12 +63,7 @@ fn handle_auth_request(config: &AuthConfig, auth_comm: Arc<AuthChannel>, usernam
}
}

pub fn run_auth_loop(config: AuthConfig, auth_comm: Arc<AuthChannel>) -> Result<()> {
let username = uzers::get_current_username().ok_or(anyhow!("Current user does not exist"))?;
let username = username.to_string_lossy().to_string();

debug!("Running authenticator for '{username}'");

fn auth_loop(mut client: AuthClient, auth_comm: Arc<AuthChannel>) -> Result<()> {
let mut success = false;

loop {
Expand All @@ -116,7 +82,7 @@ pub fn run_auth_loop(config: AuthConfig, auth_comm: Arc<AuthChannel>) -> Result<

// auth was requested for a password
if events[0].any().unwrap_or_default() && !success {
success = handle_auth_request(&config, auth_comm.clone(), &username);
success = handle_auth_request(&mut client, auth_comm.clone());

// dump auth result in response pipe
if let Err(e) = auth_comm.response.write(success) {
Expand All @@ -131,3 +97,18 @@ pub fn run_auth_loop(config: AuthConfig, auth_comm: Arc<AuthChannel>) -> Result<

Ok(())
}

pub fn setup_auth(auth_comm: Arc<AuthChannel>) -> Result<JoinHandle<()>> {
let client = AuthClient::new("nlock")?;

let handle = std::thread::spawn({
move || {
if let Err(e) = auth_loop(client, auth_comm) {
warn!("Error in auth thread: {e}");
}
debug!("Auth thread exited");
}
});

Ok(handle)
}
Loading
Loading