diff --git a/Cargo.lock b/Cargo.lock index 4f82ff6..f08b239 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - [[package]] name = "android_system_properties" version = "0.1.5" @@ -90,7 +81,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -101,7 +92,7 @@ checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -116,26 +107,6 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "lazy_static", - "lazycell", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.117", -] - [[package]] name = "bitflags" version = "2.10.0" @@ -162,23 +133,23 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "cairo-rs" -version = "0.22.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" +checksum = "b01fe135c0bd16afe262b6dea349bd5ea30e6de50708cec639aae7c5c14cc7e4" dependencies = [ "bitflags", "cairo-sys-rs", - "glib 0.22.3", + "glib", "libc", ] [[package]] name = "cairo-sys-rs" -version = "0.22.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54" +checksum = "06c28280c6b12055b5e39e4554271ae4e6630b27c0da9148c4cf6485fc6d245c" dependencies = [ - "glib-sys 0.22.3", + "glib-sys", "libc", "system-deps", ] @@ -193,15 +164,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-expr" version = "0.20.4" @@ -237,16 +199,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", -] - [[package]] name = "clap" version = "4.5.53" @@ -287,7 +239,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -437,12 +389,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - [[package]] name = "encoding_rs" version = "0.8.35" @@ -531,7 +477,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -561,8 +507,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "debb0d39e3cdd84626edfd54d6e4a6ba2da9a0ef2e796e691c4e9f8646fda00c" dependencies = [ "gdk-pixbuf-sys", - "gio 0.21.5", - "glib 0.21.4", + "gio", + "glib", "libc", ] @@ -572,9 +518,9 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd95ad50b9a3d2551e25dd4f6892aff0b772fe5372d84514e9d0583af60a0ce7" dependencies = [ - "gio-sys 0.21.2", - "glib-sys 0.21.2", - "gobject-sys 0.21.2", + "gio-sys", + "glib-sys", + "gobject-sys", "libc", "system-deps", ] @@ -610,25 +556,8 @@ dependencies = [ "futures-core", "futures-io", "futures-util", - "gio-sys 0.21.2", - "glib 0.21.4", - "libc", - "pin-project-lite", - "smallvec", -] - -[[package]] -name = "gio" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816b6743c46b217aa8fba679095ac6f2162fd53259dc8f186fcdbff9c555db03" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys 0.22.0", - "glib 0.22.3", + "gio-sys", + "glib", "libc", "pin-project-lite", "smallvec", @@ -640,21 +569,8 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" dependencies = [ - "glib-sys 0.21.2", - "gobject-sys 0.21.2", - "libc", - "system-deps", - "windows-sys", -] - -[[package]] -name = "gio-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1" -dependencies = [ - "glib-sys 0.22.3", - "gobject-sys 0.22.0", + "glib-sys", + "gobject-sys", "libc", "system-deps", "windows-sys", @@ -672,30 +588,10 @@ dependencies = [ "futures-executor", "futures-task", "futures-util", - "gio-sys 0.21.2", - "glib-macros 0.21.4", - "glib-sys 0.21.2", - "gobject-sys 0.21.2", - "libc", - "memchr", - "smallvec", -] - -[[package]] -name = "glib" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039f93465ac17e6cb02d16f16572cd3e43a77e736d5ecc461e71b9c9c5c0569c" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "glib-macros 0.22.2", - "glib-sys 0.22.3", - "gobject-sys 0.22.0", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", "libc", "memchr", "smallvec", @@ -711,19 +607,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "glib-macros" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda575994e3689b1bc12f89c3df621ead46ff292623b76b4710a3a5b79be54bb" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -736,40 +620,13 @@ dependencies = [ "system-deps", ] -[[package]] -name = "glib-sys" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eb23a616a3dbc7fc15bbd26f58756ff0b04c8a894df3f0680cd21011db6a642" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - [[package]] name = "gobject-sys" version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" dependencies = [ - "glib-sys 0.21.2", - "libc", - "system-deps", -] - -[[package]] -name = "gobject-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18eda93f09d3778f38255b231b17ef67195013a592c91624a4daf8bead875565" -dependencies = [ - "glib-sys 0.22.3", + "glib-sys", "libc", "system-deps", ] @@ -850,15 +707,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.15" @@ -892,12 +740,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.186" @@ -941,12 +783,6 @@ dependencies = [ "libc", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "nix" version = "0.31.3" @@ -973,30 +809,18 @@ dependencies = [ "dirs", "gdk-pixbuf", "nix", - "pam-rs", "pango", "pangocairo", "serde", "time", "tracing", "tracing-subscriber", - "uzers 0.12.1", "wayland-client", "wayland-protocols", "xkbcommon", "zeroize", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1049,72 +873,38 @@ dependencies = [ "hashbrown 0.14.5", ] -[[package]] -name = "pam-rs" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c9882f4752b64f1bf4cdbbc88c98fdae37c224e184e250992ffe741b762cbd1" -dependencies = [ - "libc", - "memchr", - "pam-rs-macros", - "pam-sys", - "uzers 0.11.3", -] - -[[package]] -name = "pam-rs-macros" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb687e22bbb768f1aaf54d8afbbc4cd38e73228c1f102ab686ad5c077f061409" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "pam-sys" -version = "1.0.0-alpha5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce9484729b3e52c0bacdc5191cb6a6a5f31ef4c09c5e4ab1209d3340ad9e997b" -dependencies = [ - "bindgen", - "libc", -] - [[package]] name = "pango" -version = "0.22.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25d8f224eddef627b896d2f7b05725b3faedbd140e0e8343446f0d34f34238ee" +checksum = "52d1d85e2078077a065bb7fc072783d5bcd4e51b379f22d67107d0a16937eb69" dependencies = [ - "gio 0.22.2", - "glib 0.22.3", + "gio", + "glib", "libc", "pango-sys", ] [[package]] name = "pango-sys" -version = "0.22.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd111a20ca90fedf03e09c59783c679c00900f1d8491cca5399f5e33609d5d6" +checksum = "b4f06627d36ed5ff303d2df65211fc2e52ba5b17bf18dd80ff3d9628d6e06cfd" dependencies = [ - "glib-sys 0.22.3", - "gobject-sys 0.22.0", + "glib-sys", + "gobject-sys", "libc", "system-deps", ] [[package]] name = "pangocairo" -version = "0.22.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f15369c787b1cc59a5b86eff6afffd5a9716c5beb4969d20b307cebfe7e407" +checksum = "b36c5c84304072939d860595d9bda2a797d3bd6f7215e20b8ccd0e72d84da8c8" dependencies = [ "cairo-rs", - "glib 0.22.3", + "glib", "libc", "pango", "pangocairo-sys", @@ -1122,12 +912,12 @@ dependencies = [ [[package]] name = "pangocairo-sys" -version = "0.22.0" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95cb73468373b9e568abb1afbaf5b42fe6ab9128fc41b5f2adbf69451c3c77f" +checksum = "eadbb01ad38be76e0d37e329d40ba0f3f9ef261d7b84b05201d7a0f14f819406" dependencies = [ "cairo-sys-rs", - "glib-sys 0.22.3", + "glib-sys", "libc", "pango-sys", "system-deps", @@ -1169,7 +959,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1253,35 +1043,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "regex" -version = "1.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" - [[package]] name = "ron" version = "0.8.1" @@ -1304,12 +1065,6 @@ dependencies = [ "ordered-multimap", ] -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustix" version = "1.1.2" @@ -1374,7 +1129,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1443,17 +1198,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.117" @@ -1501,7 +1245,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1623,7 +1367,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1697,26 +1441,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uzers" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d283dc7e8c901e79e32d077866eaf599156cbf427fffa8289aecc52c5c3f63" -dependencies = [ - "libc", - "log", -] - -[[package]] -name = "uzers" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df81ff504e7d82ad53e95ed1ad5b72103c11253f39238bcc0235b90768a97dd" -dependencies = [ - "libc", - "log", -] - [[package]] name = "valuable" version = "0.1.1" @@ -1764,7 +1488,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -1786,7 +1510,7 @@ checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1878,7 +1602,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1889,7 +1613,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6dfe6e9..a1e7621 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 2692f7c..2e3eee4 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/build.rs b/build.rs index ce6ea70..e4d8088 100644 --- a/build.rs +++ b/build.rs @@ -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"); + } } diff --git a/doc/cli.md b/doc/cli.md index aa96af4..654d3c7 100644 --- a/doc/cli.md +++ b/doc/cli.md @@ -37,8 +37,7 @@ The following correspond directly to configuration options. See - `--fit-to-content `, resize the input box to fit password - `--frame-radius `, sets the border radius of the frame - `--frame-border `, sets the border width of the frame -- `--allow-empty-password `, validate empty passwords, this option is - only supported on Linux targets +- `--allow-empty-password `, validate empty passwords - `--hide-cursor `, hide the mouse cursor - `--bg-type `, sets the background type - `--image-path `, path to a background image diff --git a/examples/default.toml b/examples/default.toml index 75e93ca..9d5f4eb 100644 --- a/examples/default.toml +++ b/examples/default.toml @@ -4,7 +4,6 @@ [general] hideCursor = true # hide the mouse cursor backgroundType = "color" # background type "color", or "image" -# this option is only supported on Linux targets allowEmptyPassword = false # allow a blank password to be validated # Colors section configures, well, colors. diff --git a/flake.nix b/flake.nix index 644a514..dc17974 100644 --- a/flake.nix +++ b/flake.nix @@ -61,7 +61,6 @@ rustPackages.clippy cairo - clang gdk-pixbuf glib libxkbcommon @@ -71,7 +70,6 @@ ]; RUST_SRC_PATH = rustPlatform.rustLibSrc; - LIBCLANG_PATH = "${clang.cc.lib}/lib"; }; }); }; diff --git a/nix/default.nix b/nix/default.nix index dd504d3..b7539b9 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -3,7 +3,6 @@ rustPlatform, installShellFiles, cairo, - clang, gdk-pixbuf, glib, libxkbcommon, @@ -32,7 +31,6 @@ rustPlatform.buildRustPackage { nativeBuildInputs = [ installShellFiles - clang pkg-config ]; @@ -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; { diff --git a/src/args.rs b/src/args.rs index 5e5f6f0..96a5c5f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -102,7 +102,6 @@ pub struct NLockArgs { /// Validate empty passwords #[arg(long)] - #[cfg(target_os = "linux")] pub pwd_allow_empty: Option, /// Hide the mouse cursor #[arg(long)] diff --git a/src/auth.rs b/src/auth.rs index fdf6dfe..9e09fb2 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,7 +1,7 @@ // 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; @@ -9,15 +9,14 @@ 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, config::NLockConfig}; pub struct AuthChannel { pub request: PipeCommChannel, - pub response: PipeCommChannel, + pub response: PipeCommChannel, pub stop: PipeCommChannel, } @@ -31,74 +30,68 @@ impl AuthChannel { } } -#[atomic_enum] -pub enum AuthState { - Idle, - Success, - Fail, -} - +#[derive(Debug)] pub struct AuthConfig { - #[cfg(target_os = "linux")] - pub allow_empty: bool, + pwd_allow_empty: bool, } -impl AuthConfig { - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] - pub fn new(config: &NLockConfig) -> Self { +impl From<&NLockConfig> for AuthConfig { + fn from(value: &NLockConfig) -> Self { Self { - #[cfg(target_os = "linux")] - allow_empty: config.general.pwd_allow_empty, + pwd_allow_empty: value.general.pwd_allow_empty, } } } -#[cfg_attr(not(target_os = "linux"), allow(unused_variables))] -fn authenticate(config: &AuthConfig, username: &str, password: Zeroizing) -> 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)?; +#[atomic_enum] +#[derive(PartialEq)] +pub enum AuthState { + Idle, + Success, + Fail, +} +#[cfg_attr(not(target_os = "linux"), allow(unused_variables))] +fn authenticate(client: &mut AuthClient, password: Zeroizing) -> 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, username: &str) -> bool { +fn handle_auth_request( + config: &AuthConfig, + client: &mut AuthClient, + auth_comm: Arc, +) -> AuthState { let pwd = match auth_comm.request.read().map(Zeroizing::new) { Ok(p) => p, Err(e) => { warn!("Auth comm error: {e}"); - return false; + return AuthState::Fail; } }; - match authenticate(config, username, pwd) { - Ok(()) => true, + if !config.pwd_allow_empty && pwd.is_empty() { + debug!("Auth request ignored, password is empty"); + return AuthState::Idle; + } + + match authenticate(client, pwd) { + Ok(()) => AuthState::Success, Err(e) => { warn!("Auth failed: {e}"); - false + AuthState::Fail } } } -pub fn run_auth_loop(config: AuthConfig, auth_comm: Arc) -> 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}'"); - - let mut success = false; +fn auth_loop( + config: AuthConfig, + mut client: AuthClient, + auth_comm: Arc, +) -> Result<()> { + let mut state = AuthState::Idle; loop { let req_fd = PollFd::new(auth_comm.request.rx().as_fd(), PollFlags::POLLIN); @@ -115,11 +108,11 @@ pub fn run_auth_loop(config: AuthConfig, auth_comm: Arc) -> Result< } // auth was requested for a password - if events[0].any().unwrap_or_default() && !success { - success = handle_auth_request(&config, auth_comm.clone(), &username); + if events[0].any().unwrap_or_default() && state != AuthState::Success { + state = handle_auth_request(&config, &mut client, auth_comm.clone()); // dump auth result in response pipe - if let Err(e) = auth_comm.response.write(success) { + if let Err(e) = auth_comm.response.write(state) { warn!("Failed to write auth response: {e}"); } } @@ -131,3 +124,18 @@ pub fn run_auth_loop(config: AuthConfig, auth_comm: Arc) -> Result< Ok(()) } + +pub fn setup_auth(config: AuthConfig, auth_comm: Arc) -> Result> { + let client = AuthClient::new("nlock")?; + + let handle = std::thread::spawn({ + move || { + if let Err(e) = auth_loop(config, client, auth_comm) { + warn!("Error in auth thread: {e}"); + } + debug!("Auth thread exited"); + } + }); + + Ok(handle) +} diff --git a/src/auth_sys.rs b/src/auth_sys.rs new file mode 100644 index 0000000..6509e0f --- /dev/null +++ b/src/auth_sys.rs @@ -0,0 +1,367 @@ +#[cfg(not(target_os = "openbsd"))] +mod pam_backend { + use std::{ + ffi::{CStr, CString}, + os::raw::{c_char, c_int, c_void}, + }; + + use anyhow::{Result, anyhow}; + use nix::libc::{self, size_t}; + use tracing::{debug, warn}; + use zeroize::Zeroizing; + + use crate::auth_sys::get_current_username; + + const PAM_PROMPT_ECHO_OFF: c_int = 1; + const PAM_PROMPT_ECHO_ON: c_int = 2; + const PAM_ERROR_MSG: c_int = 3; + const PAM_TEXT_INFO: c_int = 4; + + const PAM_SUCCESS: c_int = 0; + const PAM_BUF_ERR: c_int = 5; + const PAM_CONV_ERR: c_int = 19; + + #[repr(C)] + struct pam_handle_t { + _private: [u8; 0], + } + + #[repr(C)] + struct pam_message { + msg_style: c_int, + msg: *const c_char, + } + + #[repr(C)] + struct pam_response { + resp: *mut c_char, + resp_retcode: c_int, + } + + #[repr(C)] + struct pam_conv { + conv: Option< + unsafe extern "C" fn( + num_msg: c_int, + msg: *mut *const pam_message, + resp: *mut *mut pam_response, + appdata_ptr: *mut c_void, + ) -> c_int, + >, + appdata_ptr: *mut c_void, + } + + unsafe extern "C" { + fn pam_acct_mgmt(pamh: *mut pam_handle_t, flags: c_int) -> c_int; + fn pam_authenticate(pamh: *mut pam_handle_t, flags: c_int) -> c_int; + fn pam_end(pamh: *mut pam_handle_t, pam_status: c_int) -> c_int; + fn pam_start( + service_name: *const c_char, + user: *const c_char, + pam_conversation: *const pam_conv, + pamh: *mut *mut pam_handle_t, + ) -> c_int; + fn pam_strerror(pamh: *const pam_handle_t, errnum: c_int) -> *const c_char; + } + + struct ConvState { + password: Option>, + } + + pub struct AuthClient { + user: String, + conv: pam_conv, + pamh: *mut pam_handle_t, + pamres: c_int, + conv_state: Box, + } + + impl AuthClient { + pub fn new(service: S) -> Result + where + S: AsRef, + { + debug!("using PAM backend"); + + let user = get_current_username()?; + + debug!("setting up auth for '{}'", user); + + let mut conv_state = Box::new(ConvState { password: None }); + + let appdata_ptr = conv_state.as_mut() as *mut ConvState as *mut c_void; + + let mut s = Self { + user, + conv: pam_conv { + conv: Some(Self::handle_conversation), + appdata_ptr, + }, + pamh: std::ptr::null_mut(), + pamres: PAM_SUCCESS, + conv_state, + }; + + s.start(service)?; + + Ok(s) + } + + fn handle_pam_error(&mut self) -> Result<()> { + if self.pamres == PAM_SUCCESS { + return Ok(()); + } + + unsafe { + let err = pam_strerror(self.pamh as *const pam_handle_t, self.pamres); + let err = CStr::from_ptr(err).to_string_lossy().to_string(); + self.clear_password(); // always invalidate password if something fails + Err(anyhow!("pam error: {err}")) + } + } + + fn start(&mut self, service: S) -> Result<()> + where + S: AsRef, + { + unsafe { + let user = CString::new(self.user.as_str())?; + let service = CString::new(service.as_ref())?; + self.pamres = pam_start( + service.as_ptr(), + user.as_ptr(), + &self.conv as *const pam_conv, + &mut self.pamh as *mut *mut pam_handle_t, + ); + self.handle_pam_error()?; + + debug!("started new pam conversation"); + + Ok(()) + } + } + + pub fn set_password(&mut self, password: Zeroizing) { + self.conv_state.password = Some(password); + } + + pub fn clear_password(&mut self) { + self.conv_state.password = None; + } + + pub fn authenticate(&mut self) -> Result<()> { + if self.conv_state.password.is_none() { + return Err(anyhow!("password has not been set")); + } + + unsafe { + self.pamres = pam_authenticate(self.pamh, 0); + self.handle_pam_error()?; + self.pamres = pam_acct_mgmt(self.pamh, 0); + self.handle_pam_error()?; + } + + self.clear_password(); + Ok(()) + } + + unsafe extern "C" fn handle_conversation( + num_msg: c_int, + msg: *mut *const pam_message, + resp: *mut *mut pam_response, + appdata_ptr: *mut c_void, + ) -> c_int { + if appdata_ptr.is_null() { + return PAM_BUF_ERR; + } + if num_msg <= 0 || msg.is_null() || resp.is_null() { + return PAM_CONV_ERR; + } + + let conv_state = unsafe { &mut *(appdata_ptr as *mut ConvState) }; + + let responses = unsafe { + libc::calloc( + num_msg as size_t, + std::mem::size_of::() as size_t, + ) as *mut pam_response + }; + if responses.is_null() { + return PAM_BUF_ERR; + } + + for i in 0..num_msg as usize { + let message = unsafe { *msg.add(i) }; + + if message.is_null() { + return PAM_CONV_ERR; + } + + match unsafe { (*message).msg_style } { + PAM_PROMPT_ECHO_ON | PAM_PROMPT_ECHO_OFF => { + let Some(password) = &conv_state.password else { + continue; + }; + + // easier to zero a raw buffer than a cstring + let mut buf = Zeroizing::new(password.as_bytes().to_vec()); + buf.push(0); + + let dup = unsafe { libc::strdup(buf.as_ptr() as *const c_char) }; + if dup.is_null() { + unsafe { + libc::free(responses as *mut c_void); + } + return PAM_BUF_ERR; + } + + unsafe { + (*responses.add(i)).resp = dup; + (*responses.add(i)).resp_retcode = 0; + } + + conv_state.password = None; + } + + PAM_TEXT_INFO | PAM_ERROR_MSG => {} + + _ => { + unsafe { + libc::free(responses as *mut c_void); + } + return PAM_CONV_ERR; + } + } + } + + unsafe { + *resp = responses; + } + + PAM_SUCCESS + } + } + + impl Drop for AuthClient { + fn drop(&mut self) { + unsafe { + self.pamres = pam_end(self.pamh, self.pamres); + if let Err(e) = self.handle_pam_error() { + warn!("pam_end failed: {e}"); + } + debug!("pam conversation closed"); + } + } + } + + // # Safety + // Assumed PAM handle can be safely moved across threads + unsafe impl Send for AuthClient {} +} + +#[cfg(target_os = "openbsd")] +mod bsdauth_backend { + use std::os::raw::{c_char, c_int}; + + use anyhow::{Result, anyhow}; + use nix::unistd::{Group, setgid}; + use tracing::debug; + use zeroize::Zeroizing; + + use crate::auth_sys::get_current_username; + + unsafe extern "C" { + fn auth_userokay( + name: *mut c_char, + style: *mut c_char, + service: *mut c_char, + password: *mut c_char, + ) -> c_int; + } + + pub struct AuthClient { + user: String, + service: String, + password: Option>, + } + + impl AuthClient { + pub fn new(service: S) -> Result + where + S: AsRef, + { + debug!("using BSD Authentication backend"); + + let user = get_current_username()?; + + debug!("setting up auth for '{}'", user); + + let Some(authg) = Group::from_name("auth")? else { + return Err(anyhow!("'auth' group does not exist?")); + }; + + // ensure we are setgid auth first + if let Err(e) = setgid(authg.gid) { + return Err(anyhow!( + "nlock was compiled with BSD Authentication, but is not setgid auth: {e}" + )); + } + + Ok(Self { + user, + service: service.as_ref().to_string(), + password: None, + }) + } + + pub fn set_password(&mut self, password: Zeroizing) { + self.password = Some(password); + } + + pub fn clear_password(&mut self) { + self.password = None; + } + + pub fn authenticate(&mut self) -> Result<()> { + let Some(password) = self.password.take() else { + return Err(anyhow!("password has not been set")); + }; + + let mut user = self.user.clone().into_bytes(); + user.push(0); + let mut service = self.service.clone().into_bytes(); + service.push(0); + let mut buf = Zeroizing::new(password.as_bytes().to_vec()); + buf.push(0); + + let res = unsafe { + auth_userokay( + user.as_mut_ptr() as *mut c_char, + std::ptr::null_mut(), + service.as_mut_ptr() as *mut c_char, + buf.as_mut_ptr() as *mut c_char, + ) + }; + + // 0=fail, anything else=success + if res == 0 { + return Err(anyhow!("authentication failure")); + } + + Ok(()) + } + } +} + +pub fn get_current_username() -> anyhow::Result { + let uid = nix::unistd::getuid(); + let Some(user) = nix::unistd::User::from_uid(uid)? else { + return Err(anyhow::anyhow!("who the f**k are you? (uid {:?})", uid)); + }; + Ok(user.name) +} + +#[cfg(target_os = "openbsd")] +pub use bsdauth_backend::*; +#[cfg(not(target_os = "openbsd"))] +pub use pam_backend::*; diff --git a/src/comm.rs b/src/comm.rs index 6ad672d..4ae4afb 100644 --- a/src/comm.rs +++ b/src/comm.rs @@ -6,6 +6,8 @@ use std::{ use anyhow::{Result, anyhow}; use nix::errno::Errno; +use crate::auth::AuthState; + /// A one-way pipe based communication channel pub struct PipeCommChannel { tx: OwnedFd, @@ -106,6 +108,16 @@ impl AsBytes for bool { } } +impl AsBytes for AuthState { + fn as_bytes(&self) -> &[u8] { + match self { + Self::Idle => &[0u8], + Self::Success => &[1u8], + Self::Fail => &[2u8], + } + } +} + pub trait FromBytes { /// Convert from a bytes-like representation of the object fn from_bytes(bytes: &[u8]) -> Option @@ -137,3 +149,20 @@ impl FromBytes for bool { } } } + +impl FromBytes for AuthState { + fn from_bytes(bytes: &[u8]) -> Option + where + Self: Sized, + { + if bytes.len() != 1 { + return None; + } + match bytes[0] { + 0u8 => Some(Self::Idle), + 1u8 => Some(Self::Success), + 2u8 => Some(Self::Fail), + _ => None, + } + } +} diff --git a/src/config.rs b/src/config.rs index c38795e..ebed676 100644 --- a/src/config.rs +++ b/src/config.rs @@ -350,7 +350,6 @@ fn default_frame_radius() -> f64 { #[derive(Deserialize)] #[serde(deny_unknown_fields)] pub struct NLockConfigGeneral { - #[cfg(target_os = "linux")] #[serde(default = "default_pwd_allow_empty", rename = "allowEmptyPassword")] pub pwd_allow_empty: bool, @@ -364,7 +363,6 @@ pub struct NLockConfigGeneral { impl Default for NLockConfigGeneral { fn default() -> Self { Self { - #[cfg(target_os = "linux")] pwd_allow_empty: default_pwd_allow_empty(), hide_cursor: default_hide_cursor(), bg_type: default_bg_type(), @@ -374,14 +372,12 @@ impl Default for NLockConfigGeneral { impl LoadArgOverrides for NLockConfigGeneral { fn load_arg_overrides(&mut self, args: &NLockArgs) { - #[cfg(target_os = "linux")] set_if_some!(self.pwd_allow_empty, args.pwd_allow_empty); set_if_some!(self.hide_cursor, args.hide_cursor); set_if_some!(self.bg_type, args.bg_type); } } -#[cfg(target_os = "linux")] fn default_pwd_allow_empty() -> bool { false } diff --git a/src/event.rs b/src/event.rs index acba7bc..ddc7e69 100644 --- a/src/event.rs +++ b/src/event.rs @@ -75,13 +75,16 @@ impl NLockState { wayland_sock_ready = true; } EventType::AuthStateChanged => match self.auth_comm.response.read() { - Ok(true) => { + Ok(AuthState::Idle) => { + // request was ignored by backend, do nothing + } + Ok(AuthState::Success) => { // auth was successful, set flags for exit self.auth_state.store(AuthState::Success, Ordering::Relaxed); self.running.store(false, Ordering::Relaxed); self.state_changed.store(true, Ordering::Relaxed); } - Ok(false) => { + Ok(AuthState::Fail) => { // auth failed, set fail state self.auth_state.store(AuthState::Fail, Ordering::Relaxed); self.state_changed.store(true, Ordering::Relaxed); diff --git a/src/main.rs b/src/main.rs index 16a4244..5ceeac9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ pub mod args; pub mod auth; +pub mod auth_sys; pub mod buffer; pub mod cairo_ext; pub mod comm; @@ -29,7 +30,7 @@ use wayland_client::Connection; use crate::{ args::run_cli, - auth::{AuthChannel, AuthConfig, run_auth_loop}, + auth::{AuthChannel, AuthConfig, setup_auth}, config::NLockConfig, state::NLockState, }; @@ -45,7 +46,7 @@ fn start(config: NLockConfig) -> Result<()> { let display = conn.display(); let auth_comm = Arc::new(AuthChannel::new()?); - let auth_config = AuthConfig::new(&config); + let auth_config: AuthConfig = (&config).into(); let mut state = NLockState::new(config, display, auth_comm.clone())?; @@ -75,16 +76,8 @@ fn start(config: NLockConfig) -> Result<()> { bail!("Missing ExtSessionLockManagerV1"); } - // spawn authenticator loop in another thread - std::thread::spawn({ - let auth_comm = auth_comm.clone(); - move || { - if let Err(e) = run_auth_loop(auth_config, auth_comm) { - warn!("Error in auth thread: {e}"); - } - debug!("Auth thread exited"); - } - }); + // spawn the auth thread + let handle = setup_auth(auth_config, auth_comm.clone())?; state.lock(&qh); @@ -99,6 +92,8 @@ fn start(config: NLockConfig) -> Result<()> { if let Err(e) = auth_comm.stop.write(true) { warn!("Failed to stop auth loop: {e}"); + } else { + let _ = handle.join(); } Ok(())