Mercurial > crates > nonstick
changeset 136:efbc235f01d3
Separate libpam-sys-helpers from libpam-sys.
This separates the parts of libpam-sys that don't need linking against libpam
from the parts that do need to link against libpam.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Thu, 03 Jul 2025 14:28:04 -0400 |
parents | b52594841480 |
children | 88627c057709 |
files | Cargo.lock Cargo.toml build.rs libpam-sys/Cargo.lock libpam-sys/Cargo.toml libpam-sys/LICENSE libpam-sys/README.md libpam-sys/build.rs libpam-sys/libpam-sys-helpers/Cargo.toml libpam-sys/libpam-sys-helpers/LICENSE libpam-sys/libpam-sys-helpers/README.md libpam-sys/libpam-sys-helpers/build.rs libpam-sys/libpam-sys-helpers/src/constants.rs libpam-sys/libpam-sys-helpers/src/lib.rs libpam-sys/libpam-sys-helpers/src/memory.rs libpam-sys/libpam-sys-helpers/src/pam_impl.rs libpam-sys/libpam-sys-test/Cargo.toml libpam-sys/libpam-sys-test/build.rs libpam-sys/libpam-sys-test/tests/runner.rs libpam-sys/src/constants.rs libpam-sys/src/ffi.rs libpam-sys/src/helpers.rs libpam-sys/src/lib.rs libpam-sys/src/pam_impl.rs src/constants.rs src/libpam/conversation.rs src/logging.rs testharness/Cargo.toml |
diffstat | 28 files changed, 1940 insertions(+), 1620 deletions(-) [+] |
line wrap: on
line diff
--- a/Cargo.lock Thu Jul 03 11:14:49 2025 -0400 +++ b/Cargo.lock Thu Jul 03 14:28:04 2025 -0400 @@ -3,6 +3,12 @@ version = 3 [[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -15,6 +21,18 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] +name = "cargo_metadata" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e3374c604fb39d1a2f35ed5e4a4e30e60d01fab49446e08f1b3e9a90aef202" +dependencies = [ + "semver", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -37,6 +55,12 @@ ] [[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -47,6 +71,15 @@ version = "0.1.0" dependencies = [ "libc", + "libpam-sys-helpers", + "num_enum", +] + +[[package]] +name = "libpam-sys-helpers" +version = "0.1.0" +dependencies = [ + "libc", "num_enum", ] @@ -72,11 +105,22 @@ "bitflags", "libc", "libpam-sys", + "libpam-sys-helpers", "memoffset", "num_enum", ] [[package]] +name = "nonstick-testharness" +version = "0.0.8-alpha0" +dependencies = [ + "anyhow", + "nonstick", + "test-cdylib", + "thiserror", +] + +[[package]] name = "num_enum" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -132,6 +176,60 @@ checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -143,6 +241,47 @@ ] [[package]] +name = "test-cdylib" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8f41b1f729f5ff5177beab62e5a9251e318df8386e260ab3c944cff502ee78d" +dependencies = [ + "cargo_metadata", + "serde", + "serde_json", + "toml", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index"
--- a/Cargo.toml Thu Jul 03 11:14:49 2025 -0400 +++ b/Cargo.toml Thu Jul 03 14:28:04 2025 -0400 @@ -1,13 +1,24 @@ +[workspace] +members = ["testharness"] +exclude = ["libpam-sys"] + +[workspace.package] +license = "MIT" +version = "0.0.8-alpha0" +authors = ["Paul Fisher <paul@pfish.zone>"] +edition = "2021" +rust-version = "1.75.0" + [package] name = "nonstick" description = "PAM bindings for Rust" readme = "README.md" keywords = ["pam", "ffi", "linux", "authentication"] -license = "MIT" -version = "0.0.8-alpha0" -authors = ["Paul Fisher <paul@pfish.zone>"] -edition = "2021" -rust-version = "1.75.0" +license.workspace = true +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true [features] default = ["link", "basic-ext"] @@ -16,7 +27,7 @@ # # This will fail if you have extensions enabled that are not compatible # with your system's PAM. -link = [] +link = ["libpam-sys"] basic-ext = [] linux-pam-ext = [] @@ -31,7 +42,8 @@ libc = "0.2" memoffset = "0.9.1" num_enum = "0.7.3" -libpam-sys = { path = "libpam-sys" } +libpam-sys = { optional = true, path = "libpam-sys" } +libpam-sys-helpers = { path = "libpam-sys/libpam-sys-helpers" } [build-dependencies] -libpam-sys = { path = "libpam-sys" } +libpam-sys-helpers = { path = "libpam-sys/libpam-sys-helpers" }
--- a/build.rs Thu Jul 03 11:14:49 2025 -0400 +++ b/build.rs Thu Jul 03 14:28:04 2025 -0400 @@ -1,5 +1,5 @@ -use libpam_sys::pam_impl; +use libpam_sys_helpers::pam_impl; fn main() { - println!("{}", pam_impl::enable_pam_impl_cfg()) + pam_impl::enable_pam_impl_cfg() }
--- a/libpam-sys/Cargo.lock Thu Jul 03 11:14:49 2025 -0400 +++ b/libpam-sys/Cargo.lock Thu Jul 03 14:28:04 2025 -0400 @@ -241,6 +241,15 @@ version = "0.1.0" dependencies = [ "libc", + "libpam-sys-helpers", + "num_enum", +] + +[[package]] +name = "libpam-sys-helpers" +version = "0.1.0" +dependencies = [ + "libc", "num_enum", ] @@ -252,6 +261,7 @@ "ctest", "libc", "libpam-sys", + "libpam-sys-helpers", "proc-macro2", "quote", "syn",
--- a/libpam-sys/Cargo.toml Thu Jul 03 11:14:49 2025 -0400 +++ b/libpam-sys/Cargo.toml Thu Jul 03 14:28:04 2025 -0400 @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["libpam-sys-test"] +members = ["libpam-sys-helpers", "libpam-sys-test"] [workspace.package] version = "0.1.0" @@ -8,6 +8,7 @@ repository = "https://hg.pfish.zone/crates/nonstick/" edition = "2021" rust-version = "1.75.0" +license = "MIT" [package] name = "libpam-sys" @@ -18,14 +19,13 @@ repository.workspace = true edition.workspace = true rust-version.workspace = true - -[features] -default = ["helpers"] -helpers = [] +license.workspace = true [dependencies] +libpam-sys-helpers = { path = "libpam-sys-helpers" } libc = "0.2" num_enum = "0.7.4" [build-dependencies] -libc = "0.2" \ No newline at end of file +libc = "0.2" +libpam-sys-helpers = { path = "libpam-sys-helpers" }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/LICENSE Thu Jul 03 14:28:04 2025 -0400 @@ -0,0 +1,20 @@ +Copyright 2025 Paul Fisher + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +
--- a/libpam-sys/README.md Thu Jul 03 11:14:49 2025 -0400 +++ b/libpam-sys/README.md Thu Jul 03 14:28:04 2025 -0400 @@ -5,27 +5,17 @@ If you're looking for a nice, safe, Rusty API to PAM, may I recommend [nonstick][nonstick]? -## Configuration - -By default, this crate guesses your system's PAM implementation based upon your OS. +## PAM implementations -- Linux: `LinuxPam` -- BSDs, including Mac OS: `OpenPam` -- Illumos/Solaris: `Sun` -- Unknown: `XSso` +This crate detects the PAM implementation you have installed, or guesses based on the OS if that's unavailable. +Supported PAM implementations are defined in the `pam_impl::PamImpl` enum. + +You can also explicitly specify the PAM implementation you want (if not detected correctly) by setting the `LIBPAMSYS_IMPL` environment variable **at build time**. +All build-time configuration is performed by the build script of the [`libpam-sys-helpers` crate](https://crates.io/crates/libpam-sys-helpers). Each implementation exports all the functionality available in its respective PAM library. `XSso` exports only what is in the [X/SSO specification][xsso]. -## Cargo Features - -The `helpers` feature (optional, but on by default) exports two helpers for PAM memory management. - -- A struct for managing the difference in memory management between Linux-PAM and all other implementations. -- A struct for handling the Linux-PAM–specific binary data payload structure. - -Neither are directly referenced elsewhere, and both allow you to bring your own storage abstractions. - ## Testing Tests are mostly run through `libpam-sys-test`, which lives in the crate's workspace in its repository (along with [nonstick]).
--- a/libpam-sys/build.rs Thu Jul 03 11:14:49 2025 -0400 +++ b/libpam-sys/build.rs Thu Jul 03 14:28:04 2025 -0400 @@ -1,93 +1,8 @@ #![allow(unexpected_cfgs)] -use std::env; -use std::ffi::{c_void, CString}; -use std::fs; -use std::ptr::NonNull; - -include!("src/pam_impl.rs"); +use libpam_sys_helpers::pam_impl; fn main() { println!("cargo:rustc-link-lib=pam"); - - let pam_impl = match option_env!("LIBPAMSYS_IMPL") { - // The default option: Guess what PAM impl we're using based on OS. - None | Some("") => detect(), - Some(other) => match PamImpl::try_from(other) { - Ok(i) => i, - Err(_) => { - panic!( - "unknown PAM implementation {other:?}. valid LIBPAMSYS_IMPLs are {:?}, or unset to detect", PamImpl::items() - ) - } - }, - }; - println!("{}", checkcfg()); - let impl_str = format!("{pam_impl:?}"); - println!("{}", implcfg(&impl_str)); - println!("cargo:rustc-env=LIBPAMSYS_IMPL={impl_str}"); - fs::write( - format!("{}/pam_impl_const.rs", env::var("OUT_DIR").unwrap()), - format!( - "\ - impl PamImpl {{\ - /// The implementation of libpam this was built for (`{impl_str}`). - pub const CURRENT: Self = Self::{impl_str};\ - /// String version for internal consumption. - const CURRENT_STR: &'static str = {impl_str:?};\ - }} - " - ), - ) - .unwrap(); + pam_impl::enable_pam_impl_cfg(); } - -fn detect() -> PamImpl { - if let Some(lib) = LibPam::open() { - if lib.has("pam_syslog") { - return PamImpl::LinuxPam; - } else if lib.has("_openpam_log") { - return PamImpl::OpenPam; - } else if lib.has("__pam_get_authtok") { - return PamImpl::Sun; - } - } - if cfg!(target_os = "linux") { - PamImpl::LinuxPam - } else if cfg!(any( - target_os = "macos", - target_os = "freebsd", - target_os = "netbsd", - target_os = "dragonfly", - target_os = "openbsd", - )) { - PamImpl::OpenPam - } else if cfg!(any(target_os = "illumos", target_os = "solaris")) { - PamImpl::Sun - } else { - PamImpl::XSso - } -} - -struct LibPam(NonNull<c_void>); - -impl LibPam { - fn open() -> Option<Self> { - NonNull::new(unsafe { libc::dlopen(b"libpam.so\0".as_ptr().cast(), libc::RTLD_LAZY) }) - .map(Self) - } - - fn has(&self, name: &str) -> bool { - let name = CString::new(name).unwrap(); - let symbol = unsafe { libc::dlsym(self.0.as_ptr(), name.as_ptr()) }; - !symbol.is_null() - } -} - -impl Drop for LibPam { - fn drop(&mut self) { - unsafe { - libc::dlclose(self.0.as_ptr()); - } - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-helpers/Cargo.toml Thu Jul 03 14:28:04 2025 -0400 @@ -0,0 +1,16 @@ +[package] +name = "libpam-sys-helpers" +version.workspace = true +authors.workspace = true +repository.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +readme = "README.md" + +[dependencies] +libc = "0.2" +num_enum = "0.7" + +[build-dependencies] +libc = "0.2" \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-helpers/LICENSE Thu Jul 03 14:28:04 2025 -0400 @@ -0,0 +1,20 @@ +Copyright 2025 Paul Fisher + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-helpers/README.md Thu Jul 03 14:28:04 2025 -0400 @@ -0,0 +1,57 @@ +# `libpam-sys-helpers`: cross-platform tools for libpam + +This crate contains tools for `libpam` that **don't directly link to `libpam.so`**. +This allows for creation of `libpam` abstractions (e.g. test doubles) that don't require libpam to be present if they don't link directly into it. + +## Handling PAM implementations + +Different PAM implementations have different constants and some different behaviors. +If you need to change your behavior based on PAM implementation, there are a few ways to do so. + +### Constants + +The current PAM implementation is available in `PamImpl::CURRENT`. +This is present as a string literal macro in `pam_impl_name!`. + +### Conditional compilation + +This package provides custom `#[cfg]`s to compile based on the current PAM implementation. + +First, **enable custom `#[cfg]`s in your build.rs**: + +```rust +// build.rs +use libpam_sys_helpers::pam_impl; + +fn main() { + pam_impl::enable_pam_impl_cfg(); + + // everything else you do at build time +} +``` + +This will then allow you to use the `pam_impl` configuration variable at compile time: + +```rust +#[cfg(pam_impl = "LinuxPam")] +fn handle_pam() { + // do things in a Linux-PAM specific way +} + +#[cfg(not(pam_impl = "LinuxPam"))] +fn handle_pam() { + // do things in another way +} +``` + +## Configuration + +By default, this crate automatically detects your libpam version. +Known implementations are listed in the `PamImpl` enum. + +You can override this **at build time** by setting the `LIBPAMSYS_IMPL` environment variable to one of those names. +For example, `LIBPAMSYS_IMPL=OpenPam cargo build` will build this library for OpenPAM. + +## MSRV + +This library supports **Rust 1.75**, as the version currently (July 2025) available in Debian Trixie and Ubuntu 24.04 LTS. \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-helpers/build.rs Thu Jul 03 14:28:04 2025 -0400 @@ -0,0 +1,96 @@ +#![allow(unexpected_cfgs)] + +use std::ffi::{c_void, CString}; +use std::ptr::NonNull; +use std::{env, fs}; + +include!("src/pam_impl.rs"); + +fn main() { + let pam_impl = match option_env!("LIBPAMSYS_IMPL") { + // The default option: Guess what PAM impl we're using based on OS. + None | Some("") => LibPam::detect(), + Some(other) => match PamImpl::try_from(other) { + Ok(i) => i, + Err(_) => { + panic!( + "unknown PAM implementation {other:?}. valid LIBPAMSYS_IMPLs are {:?}, or unset to detect", PamImpl::items() + ) + } + }, + }; + let impl_str = format!("{pam_impl:?}"); + println!("{}", generate_cfg(&impl_str)); + // We set this environment variable to substitute into docstrings. + println!("cargo:rustc-env=LIBPAMSYS_IMPL={impl_str}"); + fs::write( + format!("{}/pam_impl_const.rs", env::var("OUT_DIR").unwrap()), + generate_consts(&impl_str), + ) + .unwrap(); +} + +fn generate_consts(impl_str: &str) -> String { + format!( + "\ +impl PamImpl {{ +/// The implementation of libpam this was built for (`{impl_str}`). +pub const CURRENT: Self = Self::{impl_str}; +}} + +/// String name of [`PamImpl::CURRENT`], for substituting into docs. +#[macro_export] +macro_rules! pam_impl_name {{ () => ({impl_str:?}) }} + " + ) +} + +struct LibPam(NonNull<c_void>); + +impl LibPam { + fn detect() -> PamImpl { + if let Some(lib) = Self::open() { + if lib.has("pam_syslog") { + return PamImpl::LinuxPam; + } else if lib.has("_openpam_log") { + return PamImpl::OpenPam; + } else if lib.has("__pam_get_authtok") { + return PamImpl::Sun; + } + } + if cfg!(target_os = "linux") { + PamImpl::LinuxPam + } else if cfg!(any( + target_os = "macos", + target_os = "freebsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "openbsd", + )) { + PamImpl::OpenPam + } else if cfg!(any(target_os = "illumos", target_os = "solaris")) { + PamImpl::Sun + } else { + PamImpl::XSso + } + } + + fn open() -> Option<Self> { + NonNull::new(unsafe { libc::dlopen(b"libpam.so\0".as_ptr().cast(), libc::RTLD_LAZY) }) + .map(Self) + } + + fn has(&self, name: &str) -> bool { + let name = CString::new(name).unwrap(); + let symbol = unsafe { libc::dlsym(self.0.as_ptr(), name.as_ptr()) }; + !symbol.is_null() + } +} + +impl Drop for LibPam { + fn drop(&mut self) { + unsafe { + libc::dlclose(self.0.as_ptr()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-helpers/src/constants.rs Thu Jul 03 14:28:04 2025 -0400 @@ -0,0 +1,298 @@ +//! All of `libpam`'s constants. +//! +//! These constants are tested on a per-platform basis by `libpam-sys-test`'s +//! `test_constants.rs`. + +#![allow(non_camel_case_types)] +#![allow(unused_imports)] +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +/// Macro to make defining a bunch of constants way easier. +macro_rules! define { + ($(#[$attr:meta])* $($name:ident = $value:expr);+$(;)?) => { + define!( + @meta { $(#[$attr])* } + $(pub const $name: i32 = $value;)+ + ); + }; + (@meta $m:tt $($i:item)+) => { define!(@expand $($m $i)+); }; + (@expand $({ $(#[$m:meta])* } $i:item)+) => {$($(#[$m])* $i)+}; +} + +/// Macro to make defining C-style enums way easier. +macro_rules! c_enum { + ($(#[$attr:meta])* $($name:ident $(= $value:expr)?,)*) => { + c_enum!( + (0) + $(#[$attr])* + $($name $(= $value)?,)* + ); + }; + (($n:expr) $(#[$attr:meta])* $name:ident, $($rest:ident $(= $rv:expr)?,)*) => { + $(#[$attr])* pub const $name: i32 = $n; + c_enum!(($n + 1) $(#[$attr])* $($rest $(= $rv)?,)*); + }; + (($n:expr) $(#[$attr:meta])* $name:ident = $value:expr, $($rest:ident $(= $rv:expr)?,)*) => { + $(#[$attr])* pub const $name: i32 = $value; + c_enum!(($value + 1) $(#[$attr])* $($rest $(= $rv)?,)*); + }; + (($n:expr) $(#[$attr:meta])*) => {}; +} + +// There are a few truly universal constants. +// They are defined here directly. +/// The successful return code. +pub const PAM_SUCCESS: i32 = 0; + +c_enum!( + /// An item type. + PAM_SERVICE = 1, + PAM_USER, + PAM_TTY, + PAM_RHOST, + PAM_CONV, + PAM_AUTHTOK, + PAM_OLDAUTHTOK, + PAM_RUSER, + PAM_USER_PROMPT, +); + +c_enum!( + /// A message style. + PAM_PROMPT_ECHO_OFF = 1, + PAM_PROMPT_ECHO_ON, + PAM_ERROR_MSG, + PAM_TEXT_INFO, +); + +define!( + /// Maximum size of PAM conversation elements (suggested). + PAM_MAX_NUM_MSG = 32; + PAM_MAX_MSG_SIZE = 512; + PAM_MAX_RESP_SIZE = 512; +); + +#[cfg(pam_impl = "LinuxPam")] +pub use linux_pam::*; +#[cfg(pam_impl = "LinuxPam")] +mod linux_pam { + use super::{IntoPrimitive, TryFromPrimitive}; + c_enum!( + /// An error return code. + PAM_OPEN_ERR = 1, + PAM_SYMBOL_ERR, + PAM_SERVICE_ERR, + PAM_SYSTEM_ERR, + PAM_BUF_ERR, + PAM_PERM_DENIED, + PAM_AUTH_ERR, + PAM_CRED_INSUFFICIENT, + PAM_AUTHINFO_UNAVAIL, + PAM_USER_UNKNOWN, + PAM_MAXTRIES, + PAM_NEW_AUTHTOK_REQD, + PAM_ACCT_EXPIRED, + PAM_SESSION_ERR, + PAM_CRED_UNAVAIL, + PAM_CRED_EXPIRED, + PAM_CRED_ERR, + PAM_NO_MODULE_DATA, + PAM_CONV_ERR, + PAM_AUTHTOK_ERR, + PAM_AUTHTOK_RECOVERY_ERR, + PAM_AUTHTOK_LOCK_BUSY, + PAM_AUTHTOK_DISABLE_AGING, + PAM_TRY_AGAIN, + PAM_IGNORE, + PAM_ABORT, + PAM_AUTHTOK_EXPIRED, + PAM_MODULE_UNKNOWN, + PAM_BAD_ITEM, + PAM_CONV_AGAIN, + PAM_INCOMPLETE, + _PAM_RETURN_VALUES, + ); + + define!( + /// A flag value. + PAM_SILENT = 0x8000; + PAM_DISALLOW_NULL_AUTHTOK = 0x0001; + PAM_ESTABLISH_CRED = 0x0002; + PAM_DELETE_CRED = 0x0004; + PAM_REINITIALIZE_CRED = 0x0008; + PAM_REFRESH_CRED = 0x0010; + + PAM_CHANGE_EXPIRED_AUTHTOK = 0x0020; + + PAM_PRELIM_CHECK = 0x4000; + PAM_UPDATE_AUTHTOK = 0x2000; + PAM_DATA_REPLACE = 0x20000000; + ); + + c_enum!( + /// An item type (Linux-only). + PAM_FAIL_DELAY = 10, + PAM_XDISPLAY, + PAM_XAUTHDATA, + PAM_AUTHTOK_TYPE, + ); + + /// To suppress messages in the item cleanup function. + pub const PAM_DATA_SILENT: i32 = 0x40000000; + + // Message styles + define!( + /// A message style. + PAM_RADIO_TYPE = 5; + PAM_BINARY_PROMPT = 7; + ); + + pub const PAM_MODUTIL_NGROUPS: i32 = 64; + + #[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] + #[repr(i32)] + pub enum pam_modutil_redirect_fd { + PAM_MODUTIL_IGNORE_FD, + PAM_MODUTIL_PIPE_FD, + PAM_MODUTIL_NULL_FD, + } + + pub use pam_modutil_redirect_fd::*; +} + +#[cfg(any(pam_impl = "OpenPam", pam_impl = "Sun", pam_impl = "XSso"))] +pub use xsso_shared::*; +#[cfg(any(pam_impl = "OpenPam", pam_impl = "Sun", pam_impl = "XSso"))] +mod xsso_shared { + c_enum!( + /// An error return code. + PAM_OPEN_ERR = 1, + PAM_SYMBOL_ERR, + PAM_SERVICE_ERR, + PAM_SYSTEM_ERR, + PAM_BUF_ERR, + PAM_CONV_ERR, + PAM_PERM_DENIED, + PAM_MAXTRIES, + PAM_AUTH_ERR, + PAM_NEW_AUTHTOK_REQD, + PAM_CRED_INSUFFICIENT, + PAM_AUTHINFO_UNAVAIL, + PAM_USER_UNKNOWN, + PAM_CRED_UNAVAIL, + PAM_CRED_EXPIRED, + PAM_CRED_ERR, + PAM_ACCT_EXPIRED, + PAM_AUTHTOK_EXPIRED, + PAM_SESSION_ERR, + PAM_AUTHTOK_ERR, + PAM_AUTHTOK_RECOVERY_ERR, + PAM_AUTHTOK_LOCK_BUSY, + PAM_AUTHTOK_DISABLE_AGING, + PAM_NO_MODULE_DATA, + PAM_IGNORE, + PAM_ABORT, + PAM_TRY_AGAIN, + ); + // While `PAM_MODULE_UNKNOWN` and `PAM_DOMAIN_UNKNOWN` are in X/SSO, + // Sun doesn't use them so we're omitting them here. + + /// A general flag for PAM operations. + pub const PAM_SILENT: i32 = 0x80000000u32 as i32; + + /// A flag for `pam_authenticate`. + pub const PAM_DISALLOW_NULL_AUTHTOK: i32 = 0b1; + + define!( + /// A flag for `pam_setcred`. + PAM_ESTABLISH_CRED = 0b0001; + PAM_DELETE_CRED = 0b0010; + PAM_REINITIALIZE_CRED = 0b0100; + PAM_REFRESH_CRED = 0b1000; + ); + + define!( + /// A flag for `pam_sm_chauthtok`. + PAM_PRELIM_CHECK = 0b0001; + PAM_UPDATE_AUTHTOK = 0b0010; + PAM_CHANGE_EXPIRED_AUTHTOK = 0b0100; + ); +} + +#[cfg(pam_impl = "OpenPam")] +pub use openpam::*; +#[cfg(pam_impl = "OpenPam")] +mod openpam { + c_enum!( + /// An error return code. + PAM_MODULE_UNKNOWN = 28, + PAM_DOMAIN_UNKNOWN, + PAM_BAD_HANDLE, + PAM_BAD_ITEM, + PAM_BAD_FEATURE, + PAM_BAD_CONSTANT, + ); + /// The total number of PAM error codes (including success). + pub const PAM_NUM_ERRORS: i32 = 34; + + c_enum!( + /// An item type. + PAM_REPOSITORY = 10, + PAM_AUTHTOK_PROMPT, + PAM_OLDAUTHTOK_PROMPT, + PAM_HOST, + ); + /// The total number of PAM items. + pub const PAM_NUM_ITEMS: i32 = 14; + + c_enum!( + /// An optional OpenPAM feature. + OPENPAM_RESTRICT_SERVICE_NAME, + OPENPAM_VERIFY_POLICY_FILE, + OPENPAM_RESTRICT_MODULE_NAME, + OPENPAM_VERIFY_MODULE_FILE, + OPENPAM_FALLBACK_TO_OTHER, + ); + /// The number of optional OpenPAM features. + pub const OPENPAM_NUM_FEATURES: i32 = 5; + + c_enum!( + /// Log level. + PAM_LOG_LIBDEBUG = -1, + PAM_LOG_DEBUG, + PAM_LOG_VERBOSE, + PAM_LOG_NOTICE, + PAM_LOG_ERROR, + ); + + c_enum!( + /// PAM primitives. + PAM_SM_AUTHENTICATE, + PAM_SM_SETCRED, + PAM_SM_ACCT_MGMT, + PAM_SM_OPEN_SESSION, + PAM_SM_CLOSE_SESSION, + PAM_SM_CHAUTHTOK, + ); + /// The number of PAM primitives. + pub const PAM_NUM_PRIMITIVES: i32 = 6; +} + +/// Constants exclusive to Illumos. +#[cfg(pam_impl = "Sun")] +pub use sun::*; +#[cfg(pam_impl = "Sun")] +mod sun { + /// The total number of PAM error codes. + pub const PAM_TOTAL_ERRNUM: i32 = 28; + + c_enum!( + /// An item type. + PAM_REPOSITORY = 10, + PAM_RESOURCE, + PAM_AUSER, + ); + + /// A flag for `pam_chauthtok`. + pub const PAM_NO_AUTHTOK_CHECK: i32 = 0b1000; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-helpers/src/lib.rs Thu Jul 03 14:28:04 2025 -0400 @@ -0,0 +1,51 @@ +#![doc = include_str!("../README.md")] +//! +//! ## Current implementation +//! +//! This documentation was built based on the +#![doc = concat!("**", env!("LIBPAMSYS_IMPL"), "** implementation.")] + +pub mod constants; +pub mod memory; + +/// Information about the PAM implementation you're using right now. +/// +/// This module contains constants and values that can be used at build-script, +/// compile, and run time to determine what PAM implementation you're using. +/// +/// ## Always available +/// +/// [`PamImpl::CURRENT`] will tell you what version of PAM you're using. +/// It can be imported in any Rust code, from build scripts to runtime. +/// +/// ## Compile time +/// +/// Use [`enable_pam_impl_cfg`] in your `build.rs` to generate custom `#[cfg]`s +/// for conditional compilation based on PAM implementation. +/// +/// This will set the current `pam_impl` as well as registering all known +/// PAM implementations with `rustc-check-cfg` to get cfg-checking. +/// +/// The names that appear in the `cfg` variables are the same as the values +/// in the [`PamImpl`] enum. +/// +/// ``` +/// #[cfg(pam_impl = "OpenPam")] +/// fn openpam_specific_func(handle: *const libpam_sys::pam_handle) { +/// let environ = libpam_sys::pam_getenvlist(handle); +/// // ... +/// libpam_sys::openpam_free_envlist() +/// } +/// +/// // This will give you a warning since "UnknownImpl" is not in the cfg. +/// #[cfg(not(pam_impl = "UnknownImpl"))] +/// fn do_something() { +/// // ... +/// } +/// ``` +/// +/// The [`pam_impl_name!`] macro will expand to this same value, currently +#[doc = concat!("`", env!("LIBPAMSYS_IMPL"), "`.")] +pub mod pam_impl; +#[doc(inline)] +pub use pam_impl::*;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-helpers/src/memory.rs Thu Jul 03 14:28:04 2025 -0400 @@ -0,0 +1,510 @@ +//! Helpers to deal with annoying memory management in the PAM API. +//! +//! + +use std::error::Error; +use std::marker::{PhantomData, PhantomPinned}; +use std::mem::ManuallyDrop; +use std::ptr::NonNull; +use std::{any, fmt, mem, slice}; + +/// A pointer-to-pointer-to-message container for PAM's conversation callback. +/// +/// The PAM conversation callback requires a pointer to a pointer of +/// `pam_message`s. Linux-PAM handles this differently than all other +/// PAM implementations (including the X/SSO PAM standard). +/// +/// X/SSO appears to specify a pointer-to-pointer-to-array: +/// +/// ```text +/// points to ┌────────────┐ ╔═ Message[] ═╗ +/// messages ┄┄┄┄┄┄┄┄┄┄> │ *messages ┄┼┄┄┄┄┄> ║ style ║ +/// └────────────┘ ║ data ┄┄┄┄┄┄┄╫┄┄> ... +/// ╟─────────────╢ +/// ║ style ║ +/// ║ data ┄┄┄┄┄┄┄╫┄┄> ... +/// ╟─────────────╢ +/// ║ ... ║ +/// ``` +/// +/// whereas Linux-PAM uses an `**argv`-style pointer-to-array-of-pointers: +/// +/// ```text +/// points to ┌──────────────┐ ╔═ Message ═╗ +/// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ +/// │ messages[1] ┄┼┄┄┄╮ ║ data ┄┄┄┄┄╫┄┄> ... +/// │ ... │ ┆ ╚═══════════╝ +/// ┆ +/// ┆ ╔═ Message ═╗ +/// ╰┄┄> ║ style ║ +/// ║ data ┄┄┄┄┄╫┄┄> ... +/// ╚═══════════╝ +/// ``` +/// +/// Because the `messages` remain owned by the application which calls into PAM, +/// we can solve this with One Simple Trick: make the intermediate list point +/// into the same array: +/// +/// ```text +/// points to ┌──────────────┐ ╔═ Message[] ═╗ +/// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ +/// │ messages[1] ┄┼┄┄╮ ║ data ┄┄┄┄┄┄┄╫┄┄> ... +/// │ ... │ ┆ ╟─────────────╢ +/// ╰┄> ║ style ║ +/// ║ data ┄┄┄┄┄┄┄╫┄┄> ... +/// ╟─────────────╢ +/// ║ ... ║ +/// +/// ``` +#[derive(Debug)] +pub struct PtrPtrVec<T> { + data: Vec<T>, + pointers: Vec<*const T>, +} + +// Since this is a wrapper around a Vec with no dangerous functionality*, +// this can be Send and Sync provided the original Vec is. +// +// * It will only become unsafe when the user dereferences a pointer or sends it +// to an unsafe function. +unsafe impl<T> Send for PtrPtrVec<T> where Vec<T>: Send {} +unsafe impl<T> Sync for PtrPtrVec<T> where Vec<T>: Sync {} + +impl<T> PtrPtrVec<T> { + /// Takes ownership of the given Vec and creates a vec of pointers to it. + pub fn new(data: Vec<T>) -> Self { + let pointers: Vec<_> = data.iter().map(|r| r as *const T).collect(); + Self { data, pointers } + } + + /// Gives you back your Vec. + pub fn into_inner(self) -> Vec<T> { + self.data + } + + /// Gets a pointer-to-pointer suitable for passing into the Conversation. + pub fn as_ptr<Dest>(&self) -> *const *const Dest { + Self::assert_size::<Dest>(); + self.pointers.as_ptr().cast::<*const Dest>() + } + + /// Iterates over a Linux-PAM–style pointer-to-array-of-pointers. + /// + /// # Safety + /// + /// `ptr_ptr` must be a valid pointer to an array of pointers, + /// there must be at least `count` valid pointers in the array, + /// and each pointer in that array must point to a valid `T`. + #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"] + #[allow(dead_code)] + pub unsafe fn iter_over_linux<'a, Src>( + ptr_ptr: *const *const Src, + count: usize, + ) -> impl Iterator<Item = &'a T> + where + T: 'a, + { + Self::assert_size::<Src>(); + slice::from_raw_parts(ptr_ptr.cast::<&T>(), count) + .iter() + .copied() + } + + /// Iterates over an X/SSO–style pointer-to-pointer-to-array. + /// + /// # Safety + /// + /// You must pass a valid pointer to a valid pointer to an array, + /// there must be at least `count` elements in the array, + /// and each value in that array must be a valid `T`. + #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"] + #[allow(dead_code)] + pub unsafe fn iter_over_xsso<'a, Src>( + ptr_ptr: *const *const Src, + count: usize, + ) -> impl Iterator<Item = &'a T> + where + T: 'a, + { + Self::assert_size::<Src>(); + slice::from_raw_parts(*ptr_ptr.cast(), count).iter() + } + + /// Iterates over a PAM message list appropriate to your system's impl. + /// + /// This selects the correct pointer/array structure to use for a message + /// that was given to you by your system. + /// + /// # Safety + /// + /// `ptr_ptr` must point to a valid message list, there must be at least + /// `count` messages in the list, and all messages must be a valid `Src`. + #[allow(deprecated)] + pub unsafe fn iter_over<'a, Src>( + ptr_ptr: *const *const Src, + count: usize, + ) -> impl Iterator<Item = &'a T> + where + T: 'a, + { + #[cfg(pam_impl = "LinuxPam")] + return Self::iter_over_linux(ptr_ptr, count); + #[cfg(not(pam_impl = "LinuxPam"))] + return Self::iter_over_xsso(ptr_ptr, count); + } + + fn assert_size<That>() { + debug_assert_eq!( + mem::size_of::<T>(), + mem::size_of::<That>(), + "type {t} is not the size of {that}", + t = any::type_name::<T>(), + that = any::type_name::<That>(), + ); + } +} + +/// Error returned when attempting to allocate a buffer that is too big. +/// +/// This is specifically used in [`OwnedBinaryPayload`] when you try to allocate +/// a message larger than 2<sup>32</sup> bytes. +#[derive(Debug, PartialEq)] +pub struct TooBigError { + pub size: usize, + pub max: usize, +} + +impl Error for TooBigError {} + +impl fmt::Display for TooBigError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "can't allocate a message of {size} bytes (max {max})", + size = self.size, + max = self.max + ) + } +} + +/// A trait wrapping memory management. +/// +/// This is intended to allow you to bring your own allocator for +/// [`OwnedBinaryPayload`]s. +/// +/// For an implementation example, see the implementation of this trait +/// for [`Vec`]. +pub trait Buffer<T: Default> { + /// Allocates a buffer of `len` elements, filled with the default. + fn allocate(len: usize) -> Self; + + fn as_ptr(&self) -> *const T; + + /// Returns a slice view of `size` elements of the given memory. + /// + /// # Safety + /// + /// The caller must not request more elements than are allocated. + unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T]; + + /// Consumes this ownership and returns a pointer to the start of the arena. + fn into_ptr(self) -> NonNull<T>; + + /// "Adopts" the memory at the given pointer, taking it under management. + /// + /// Running the operation: + /// + /// ``` + /// # use libpam_sys_helpers::memory::Buffer; + /// # fn test<T: Default, OwnerType: Buffer<T>>(bytes: usize) { + /// let owner = OwnerType::allocate(bytes); + /// let ptr = owner.into_ptr(); + /// let owner = unsafe { OwnerType::from_ptr(ptr, bytes) }; + /// # } + /// ``` + /// + /// must be a no-op. + /// + /// # Safety + /// + /// The pointer must be valid, and the caller must provide the exact size + /// of the given arena. + unsafe fn from_ptr(ptr: NonNull<T>, bytes: usize) -> Self; +} + +impl<T: Default> Buffer<T> for Vec<T> { + fn allocate(bytes: usize) -> Self { + (0..bytes).map(|_| Default::default()).collect() + } + + fn as_ptr(&self) -> *const T { + Vec::as_ptr(self) + } + + unsafe fn as_mut_slice(&mut self, bytes: usize) -> &mut [T] { + debug_assert!(bytes <= self.len()); + Vec::as_mut(self) + } + + fn into_ptr(self) -> NonNull<T> { + let mut me = ManuallyDrop::new(self); + // SAFETY: a Vec is guaranteed to have a nonzero pointer. + unsafe { NonNull::new_unchecked(me.as_mut_ptr()) } + } + + unsafe fn from_ptr(ptr: NonNull<T>, bytes: usize) -> Self { + Vec::from_raw_parts(ptr.as_ptr(), bytes, bytes) + } +} + +/// The structure of the "binary message" payload for the `PAM_BINARY_PROMPT` +/// extension from Linux-PAM. +pub struct BinaryPayload { + /// The total byte size of the message, including this header, + /// as a u32 in network byte order (big endian). + pub total_bytes_u32be: [u8; 4], + /// A tag used to provide some kind of hint as to what the data is. + /// Its meaning is undefined. + pub data_type: u8, + /// Where the data itself would start, used as a marker to make this + /// not [`Unpin`] (since it is effectively an intrusive data structure + /// pointing to immediately after itself). + pub _marker: PhantomData<PhantomPinned>, +} + +impl BinaryPayload { + /// The most data it's possible to put into a [`BinaryPayload`]. + pub const MAX_SIZE: usize = (u32::MAX - 5) as usize; + + /// Fills in the provided buffer with the given data. + /// + /// This uses [`copy_from_slice`](slice::copy_from_slice) internally, + /// so `buf` must be exactly 5 bytes longer than `data`, or this function + /// will panic. + pub fn fill(buf: &mut [u8], data_type: u8, data: &[u8]) { + let ptr: *mut Self = buf.as_mut_ptr().cast(); + // SAFETY: We're given a slice, which always has a nonzero pointer. + let me = unsafe { ptr.as_mut().unwrap_unchecked() }; + me.total_bytes_u32be = u32::to_be_bytes(buf.len() as u32); + me.data_type = data_type; + buf[5..].copy_from_slice(data) + } + + /// The total storage needed for the message, including header. + pub fn total_bytes(&self) -> usize { + u32::from_be_bytes(self.total_bytes_u32be) as usize + } + + /// Gets the total byte buffer of the BinaryMessage stored at the pointer. + /// + /// The returned data slice is borrowed from where the pointer points to. + /// + /// # Safety + /// + /// - The pointer must point to a valid `BinaryPayload`. + /// - The borrowed data must not outlive the pointer's validity. + pub unsafe fn buffer_of<'a>(ptr: *const Self) -> &'a [u8] { + let header: &Self = ptr.as_ref().unwrap_unchecked(); + slice::from_raw_parts(ptr.cast(), header.total_bytes().max(5)) + } + + /// Gets the contents of the BinaryMessage stored at the given pointer. + /// + /// The returned data slice is borrowed from where the pointer points to. + /// This is a cheap operation and doesn't do *any* copying. + /// + /// We don't take a `&self` reference here because accessing beyond + /// the range of the `Self` data (i.e., beyond the 5 bytes of `self`) + /// is undefined behavior. Instead, you have to pass a raw pointer + /// directly to the data. + /// + /// # Safety + /// + /// - The pointer must point to a valid `BinaryPayload`. + /// - The borrowed data must not outlive the pointer's validity. + pub unsafe fn contents<'a>(ptr: *const Self) -> (u8, &'a [u8]) { + let header: &Self = ptr.as_ref().unwrap_unchecked(); + (header.data_type, &Self::buffer_of(ptr)[5..]) + } +} + +/// A binary message owned by some storage. +/// +/// This is an owned, memory-managed version of [`BinaryPayload`]. +/// The `O` type manages the memory where the payload lives. +/// [`Vec<u8>`] is one such manager and can be used when ownership +/// of the data does not need to transit through PAM. +#[derive(Debug)] +pub struct OwnedBinaryPayload<Owner: Buffer<u8>>(Owner); + +impl<O: Buffer<u8>> OwnedBinaryPayload<O> { + /// Allocates a new OwnedBinaryPayload. + /// + /// This will return a [`TooBigError`] if you try to allocate too much + /// (more than [`BinaryPayload::MAX_SIZE`]). + pub fn new(data_type: u8, data: &[u8]) -> Result<Self, TooBigError> { + let total_len: u32 = (data.len() + 5).try_into().map_err(|_| TooBigError { + size: data.len(), + max: BinaryPayload::MAX_SIZE, + })?; + let total_len = total_len as usize; + let mut buf = O::allocate(total_len); + // SAFETY: We just allocated this exact size. + BinaryPayload::fill(unsafe { buf.as_mut_slice(total_len) }, data_type, data); + Ok(Self(buf)) + } + + /// The contents of the buffer. + pub fn contents(&self) -> (u8, &[u8]) { + unsafe { BinaryPayload::contents(self.as_ptr()) } + } + + /// The total bytes needed to store this, including the header. + pub fn total_bytes(&self) -> usize { + unsafe { BinaryPayload::buffer_of(self.0.as_ptr().cast()).len() } + } + + /// Unwraps this into the raw storage backing it. + pub fn into_inner(self) -> O { + self.0 + } + + /// Gets a const pointer to the start of the message's buffer. + pub fn as_ptr(&self) -> *const BinaryPayload { + self.0.as_ptr().cast() + } + + /// Consumes ownership of this message and converts it to a raw pointer + /// to the start of the message. + /// + /// To clean this up, you should eventually pass it into [`Self::from_ptr`] + /// with the same `O` ownership type. + pub fn into_ptr(self) -> NonNull<BinaryPayload> { + self.0.into_ptr().cast() + } + + /// Takes ownership of the given pointer. + /// + /// # Safety + /// + /// You must provide a valid pointer, allocated by (or equivalent to one + /// allocated by) [`Self::new`]. For instance, passing a pointer allocated + /// by `malloc` to `OwnedBinaryPayload::<Vec<u8>>::from_ptr` is not allowed. + pub unsafe fn from_ptr(ptr: NonNull<BinaryPayload>) -> Self { + Self(O::from_ptr(ptr.cast(), ptr.as_ref().total_bytes())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ptr; + + type VecPayload = OwnedBinaryPayload<Vec<u8>>; + + #[test] + fn test_binary_payload() { + let simple_message = &[0u8, 0, 0, 16, 0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let empty = &[0u8; 5]; + + assert_eq!((0xff, &[0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..]), unsafe { + BinaryPayload::contents(simple_message.as_ptr().cast()) + }); + assert_eq!((0x00, &[][..]), unsafe { + BinaryPayload::contents(empty.as_ptr().cast()) + }); + } + + #[test] + fn test_owned_binary_payload() { + let (typ, data) = ( + 112, + &[0, 1, 1, 8, 9, 9, 9, 8, 8, 1, 9, 9, 9, 1, 1, 9, 7, 2, 5, 3][..], + ); + let payload = VecPayload::new(typ, data).unwrap(); + assert_eq!((typ, data), payload.contents()); + let ptr = payload.into_ptr(); + let payload = unsafe { VecPayload::from_ptr(ptr) }; + assert_eq!((typ, data), payload.contents()); + } + + #[test] + #[ignore] + fn test_owned_too_big() { + let data = vec![0xFFu8; 0x1_0000_0001]; + assert_eq!( + TooBigError { + max: 0xffff_fffa, + size: 0x1_0000_0001 + }, + VecPayload::new(5, &data).unwrap_err() + ) + } + + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn test_new_wrong_size() { + let bad_vec = vec![0; 19]; + let msg = PtrPtrVec::new(bad_vec); + let _ = msg.as_ptr::<u64>(); + } + + #[allow(deprecated)] + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn test_iter_xsso_wrong_size() { + unsafe { + let _ = PtrPtrVec::<u8>::iter_over_xsso::<f64>(ptr::null(), 1); + } + } + + #[allow(deprecated)] + #[cfg(debug_assertions)] + #[test] + #[should_panic] + fn test_iter_linux_wrong_size() { + unsafe { + let _ = PtrPtrVec::<u128>::iter_over_linux::<()>(ptr::null(), 1); + } + } + + #[allow(deprecated)] + #[test] + fn test_right_size() { + let good_vec = vec![(1u64, 2u64), (3, 4), (5, 6)]; + let ptr = good_vec.as_ptr(); + let msg = PtrPtrVec::new(good_vec); + let msg_ref: *const *const (i64, i64) = msg.as_ptr(); + assert_eq!(unsafe { *msg_ref }, ptr.cast()); + + let linux_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_linux(msg_ref, 3) } + .cloned() + .collect(); + let xsso_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_xsso(msg_ref, 3) } + .cloned() + .collect(); + assert_eq!(vec![(1, 2), (3, 4), (5, 6)], linux_result); + assert_eq!(vec![(1, 2), (3, 4), (5, 6)], xsso_result); + drop(msg) + } + + #[allow(deprecated)] + #[test] + fn test_iter_ptr_ptr() { + let strs = vec![Box::new("a"), Box::new("b"), Box::new("c"), Box::new("D")]; + let ptr: *const *const &str = strs.as_ptr().cast(); + let got: Vec<&str> = unsafe { PtrPtrVec::iter_over_linux(ptr, 4) } + .cloned() + .collect(); + assert_eq!(vec!["a", "b", "c", "D"], got); + + let nums = [-1i8, 2, 3]; + let ptr = nums.as_ptr(); + let got: Vec<u8> = unsafe { PtrPtrVec::iter_over_xsso(&ptr, 3) } + .cloned() + .collect(); + assert_eq!(vec![255, 2, 3], got); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-helpers/src/pam_impl.rs Thu Jul 03 14:28:04 2025 -0400 @@ -0,0 +1,113 @@ +// This file is include!d directly by `../build.rs`, so its doc comment +// is found in lib.rs. + +/// An enum that knows its own values. +macro_rules! self_aware_enum { + ( + $(#[$enumeta:meta])* + $viz:vis enum $name:ident { + $( + $(#[$itemeta:meta])* + $item:ident, + )* + } + ) => { + $(#[$enumeta])* + $viz enum $name { + $( + $(#[$itemeta])* + $item, + )* + } + + // The implementations in this block are private for now + // to avoid putting a contract into the public API. + #[allow(dead_code)] + impl $name { + /// Iterator over the items in the enum. For internal use. + fn items() -> Vec<Self> { + vec![$(Self::$item),*] + } + + /// Attempts to parse the enum from the string. For internal use. + fn try_from(value: &str) -> Result<Self, String> { + match value { + $(stringify!($item) => Ok(Self::$item),)* + _ => Err(value.into()), + } + } + } + }; +} + +self_aware_enum! { + /// The PAM implementations supported by `libpam-sys`. + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + #[cfg_attr(pam_impl, non_exhaustive)] + pub enum PamImpl { + /// [Linux-PAM] is provided by most Linux distributions. + /// + /// [Linux-PAM]: https://github.com/linux-pam/linux-pam + LinuxPam, + /// [OpenPAM] is used by most BSDs, including Mac OS X. + /// + /// [OpenPAM]: https://git.des.dev/OpenPAM/OpenPAM + OpenPam, + /// Illumos and Solaris use a derivative of [Sun's implementation][sun]. + /// + /// [sun]: https://code.illumos.org/plugins/gitiles/illumos-gate/+/refs/heads/master/usr/src/lib/libpam + Sun, + /// Only the functionality and constants in [the PAM spec]. + /// + /// [the PAM spec]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm + XSso, + } +} + +// This generated file contains: +// - pam_impl_name! +// - PamImpl::CURRENT +#[cfg(pam_impl)] +include!(concat!(env!("OUT_DIR"), "/pam_impl_const.rs")); + +#[allow(clippy::needless_doctest_main)] +/// Generates `cargo` directives for build scripts to enable `cfg(pam_impl)`. +/// +/// Print this in your `build.rs` script to be able to use the custom `pam_impl` +/// configuration directive. +/// +/// ``` +/// // build.rs +/// use libpam_sys_helpers::pam_impl; +/// fn main() { +/// pam_impl::enable_pam_impl_cfg(); +/// +/// // Whatever other stuff you do in your build.rs. +/// } +/// ``` +#[cfg(pam_impl)] +pub fn enable_pam_impl_cfg() { + println!("{}", pam_impl_cfg_string()) +} + +/// Generates the `cargo:` directives to print in build scripts. +#[cfg(pam_impl)] +pub fn pam_impl_cfg_string() -> String { + generate_cfg(pam_impl_name!()) +} + +fn generate_cfg(name: &str) -> String { + let impls: Vec<_> = PamImpl::items() + .into_iter() + .map(|i| format!(r#""{i:?}""#)) + .collect(); + format!( + "\ +cargo:rustc-check-cfg=cfg(pam_impl) +cargo:rustc-check-cfg=cfg(pam_impl, values({impls})) +cargo:rustc-cfg=pam_impl +cargo:rustc-cfg=pam_impl={name:?} + ", + impls = impls.join(",") + ) +}
--- a/libpam-sys/libpam-sys-test/Cargo.toml Thu Jul 03 11:14:49 2025 -0400 +++ b/libpam-sys/libpam-sys-test/Cargo.toml Thu Jul 03 14:28:04 2025 -0400 @@ -14,6 +14,7 @@ bindgen = "0.72.0" ctest = "0.4.11" libpam-sys = { path = ".." } +libpam-sys-helpers = { path = "../libpam-sys-helpers" } proc-macro2 = "1.0.95" quote = "1.0.40" syn = { version = "2.0.104", features = ["full"] }
--- a/libpam-sys/libpam-sys-test/build.rs Thu Jul 03 11:14:49 2025 -0400 +++ b/libpam-sys/libpam-sys-test/build.rs Thu Jul 03 14:28:04 2025 -0400 @@ -1,5 +1,6 @@ use bindgen::MacroTypeVariation; -use libpam_sys::pam_impl::PamImpl; +use libpam_sys_helpers::pam_impl::PamImpl; +use libpam_sys_helpers::{pam_impl, pam_impl_name}; use proc_macro2::{Group, Ident, TokenStream, TokenTree}; use quote::{format_ident, ToTokens}; use std::path::Path; @@ -11,6 +12,7 @@ const REDIR_FD: &str = "pam_modutil_redirect_fd"; fn main() { + pam_impl::enable_pam_impl_cfg(); let config = match PamImpl::CURRENT { PamImpl::LinuxPam => TestConfig { headers: vec![ @@ -67,8 +69,7 @@ let generated = builder.generate().unwrap(); generated.write_to_file(test_file("bindgen.rs")).unwrap(); let file = syn::parse_file(&generated.to_string()).unwrap(); - let mut tests = vec![ - "\ + let mut tests = vec!["\ #[allow(dead_code, non_camel_case_types, non_upper_case_globals)] mod generated { include!(\"bindgen.rs\"); @@ -78,7 +79,7 @@ " .into(), format!( - "assert_eq!(libpam_sys::PamImpl::CURRENT, libpam_sys::PamImpl::{:?});", + "assert_eq!(libpam_sys::pam_impl::PamImpl::CURRENT, libpam_sys::pam_impl::PamImpl::{:?});", PamImpl::CURRENT ), ]; @@ -110,7 +111,7 @@ fn generate_ctest(config: &TestConfig) { let mut test = ctest::TestGenerator::new(); - test.cfg("_hack_impl", Some(&format!("{:?}", PamImpl::CURRENT))); + test.cfg("pam_impl", Some(pam_impl_name!())); for &header in config.headers.iter() { if header.starts_with('"') { @@ -143,7 +144,7 @@ test.define("const", Some("")); // Also replace all the `const`s with `mut`s in the ffi.rs file. - let file_contents = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../src/ffi.rs")); + let file_contents = include_str!("../src/lib.rs"); let deconsted_file = test_file("ffi.rs"); remove_consts(file_contents, &deconsted_file);
--- a/libpam-sys/libpam-sys-test/tests/runner.rs Thu Jul 03 11:14:49 2025 -0400 +++ b/libpam-sys/libpam-sys-test/tests/runner.rs Thu Jul 03 14:28:04 2025 -0400 @@ -17,7 +17,6 @@ #[test] fn test_c() { - eprintln!("Running a test!"); main(); } }
--- a/libpam-sys/src/constants.rs Thu Jul 03 11:14:49 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -//! All the constants. -//! -//! These constants are tested on a per-platform basis by `libpam-sys-test`'s -//! `test_constants.rs`. - -/// Macro to make defining a bunch of constants way easier. -macro_rules! define { - ($(#[$attr:meta])* $($name:ident = $value:expr);+$(;)?) => { - define!( - @meta { $(#[$attr])* } - $(pub const $name: i32 = $value;)+ - ); - }; - (@meta $m:tt $($i:item)+) => { define!(@expand $($m $i)+); }; - (@expand $({ $(#[$m:meta])* } $i:item)+) => {$($(#[$m])* $i)+}; -} - -/// Macro to make defining C-style enums way easier. -macro_rules! c_enum { - ($(#[$attr:meta])* $($name:ident $(= $value:expr)?,)*) => { - c_enum!( - (0) - $(#[$attr])* - $($name $(= $value)?,)* - ); - }; - (($n:expr) $(#[$attr:meta])* $name:ident, $($rest:ident $(= $rv:expr)?,)*) => { - $(#[$attr])* pub const $name: i32 = $n; - c_enum!(($n + 1) $(#[$attr])* $($rest $(= $rv)?,)*); - }; - (($n:expr) $(#[$attr:meta])* $name:ident = $value:expr, $($rest:ident $(= $rv:expr)?,)*) => { - $(#[$attr])* pub const $name: i32 = $value; - c_enum!(($value + 1) $(#[$attr])* $($rest $(= $rv)?,)*); - }; - (($n:expr) $(#[$attr:meta])*) => {}; -} - -// There are a few truly universal constants. -// They are defined here directly. -/// The successful return code. -pub const PAM_SUCCESS: i32 = 0; - -c_enum!( - /// An item type. - PAM_SERVICE = 1, - PAM_USER, - PAM_TTY, - PAM_RHOST, - PAM_CONV, - PAM_AUTHTOK, - PAM_OLDAUTHTOK, - PAM_RUSER, - PAM_USER_PROMPT, -); - -c_enum!( - /// A message style. - PAM_PROMPT_ECHO_OFF = 1, - PAM_PROMPT_ECHO_ON, - PAM_ERROR_MSG, - PAM_TEXT_INFO, -); - -define!( - /// Maximum size of PAM conversation elements (suggested). - PAM_MAX_NUM_MSG = 32; - PAM_MAX_MSG_SIZE = 512; - PAM_MAX_RESP_SIZE = 512; -); - -#[cfg(pam_impl = "LinuxPam")] -pub use linux_pam::*; -#[cfg(pam_impl = "LinuxPam")] -mod linux_pam { - c_enum!( - /// An error return code. - PAM_OPEN_ERR = 1, - PAM_SYMBOL_ERR, - PAM_SERVICE_ERR, - PAM_SYSTEM_ERR, - PAM_BUF_ERR, - PAM_PERM_DENIED, - PAM_AUTH_ERR, - PAM_CRED_INSUFFICIENT, - PAM_AUTHINFO_UNAVAIL, - PAM_USER_UNKNOWN, - PAM_MAXTRIES, - PAM_NEW_AUTHTOK_REQD, - PAM_ACCT_EXPIRED, - PAM_SESSION_ERR, - PAM_CRED_UNAVAIL, - PAM_CRED_EXPIRED, - PAM_CRED_ERR, - PAM_NO_MODULE_DATA, - PAM_CONV_ERR, - PAM_AUTHTOK_ERR, - PAM_AUTHTOK_RECOVERY_ERR, - PAM_AUTHTOK_LOCK_BUSY, - PAM_AUTHTOK_DISABLE_AGING, - PAM_TRY_AGAIN, - PAM_IGNORE, - PAM_ABORT, - PAM_AUTHTOK_EXPIRED, - PAM_MODULE_UNKNOWN, - PAM_BAD_ITEM, - PAM_CONV_AGAIN, - PAM_INCOMPLETE, - _PAM_RETURN_VALUES, - ); - - define!( - /// A flag value. - PAM_SILENT = 0x8000; - PAM_DISALLOW_NULL_AUTHTOK = 0x0001; - PAM_ESTABLISH_CRED = 0x0002; - PAM_DELETE_CRED = 0x0004; - PAM_REINITIALIZE_CRED = 0x0008; - PAM_REFRESH_CRED = 0x0010; - - PAM_CHANGE_EXPIRED_AUTHTOK = 0x0020; - - PAM_PRELIM_CHECK = 0x4000; - PAM_UPDATE_AUTHTOK = 0x2000; - PAM_DATA_REPLACE = 0x20000000; - ); - - c_enum!( - /// An item type (Linux-only). - PAM_FAIL_DELAY = 10, - PAM_XDISPLAY, - PAM_XAUTHDATA, - PAM_AUTHTOK_TYPE, - ); - - /// To suppress messages in the item cleanup function. - pub const PAM_DATA_SILENT: i32 = 0x40000000; - - // Message styles - define!( - /// A message style. - PAM_RADIO_TYPE = 5; - PAM_BINARY_PROMPT = 7; - ); - - pub const PAM_MODUTIL_NGROUPS: i32 = 64; -} - -#[cfg(any(pam_impl = "OpenPam", pam_impl = "Sun", pam_impl = "XSso"))] -pub use xsso_shared::*; -#[cfg(any(pam_impl = "OpenPam", pam_impl = "Sun", pam_impl = "XSso"))] -mod xsso_shared { - c_enum!( - /// An error return code. - PAM_OPEN_ERR = 1, - PAM_SYMBOL_ERR, - PAM_SERVICE_ERR, - PAM_SYSTEM_ERR, - PAM_BUF_ERR, - PAM_CONV_ERR, - PAM_PERM_DENIED, - PAM_MAXTRIES, - PAM_AUTH_ERR, - PAM_NEW_AUTHTOK_REQD, - PAM_CRED_INSUFFICIENT, - PAM_AUTHINFO_UNAVAIL, - PAM_USER_UNKNOWN, - PAM_CRED_UNAVAIL, - PAM_CRED_EXPIRED, - PAM_CRED_ERR, - PAM_ACCT_EXPIRED, - PAM_AUTHTOK_EXPIRED, - PAM_SESSION_ERR, - PAM_AUTHTOK_ERR, - PAM_AUTHTOK_RECOVERY_ERR, - PAM_AUTHTOK_LOCK_BUSY, - PAM_AUTHTOK_DISABLE_AGING, - PAM_NO_MODULE_DATA, - PAM_IGNORE, - PAM_ABORT, - PAM_TRY_AGAIN, - ); - // While `PAM_MODULE_UNKNOWN` and `PAM_DOMAIN_UNKNOWN` are in X/SSO, - // Sun doesn't use them so we're omitting them here. - - /// A general flag for PAM operations. - pub const PAM_SILENT: i32 = 0x80000000u32 as i32; - - /// A flag for `pam_authenticate`. - pub const PAM_DISALLOW_NULL_AUTHTOK: i32 = 0b1; - - define!( - /// A flag for `pam_setcred`. - PAM_ESTABLISH_CRED = 0b0001; - PAM_DELETE_CRED = 0b0010; - PAM_REINITIALIZE_CRED = 0b0100; - PAM_REFRESH_CRED = 0b1000; - ); - - define!( - /// A flag for `pam_sm_chauthtok`. - PAM_PRELIM_CHECK = 0b0001; - PAM_UPDATE_AUTHTOK = 0b0010; - PAM_CHANGE_EXPIRED_AUTHTOK = 0b0100; - ); -} - -#[cfg(pam_impl = "OpenPam")] -pub use openpam::*; -#[cfg(pam_impl = "OpenPam")] -mod openpam { - c_enum!( - /// An error return code. - PAM_MODULE_UNKNOWN = 28, - PAM_DOMAIN_UNKNOWN, - PAM_BAD_HANDLE, - PAM_BAD_ITEM, - PAM_BAD_FEATURE, - PAM_BAD_CONSTANT, - ); - /// The total number of PAM error codes (including success). - pub const PAM_NUM_ERRORS: i32 = 34; - - c_enum!( - /// An item type. - PAM_REPOSITORY = 10, - PAM_AUTHTOK_PROMPT, - PAM_OLDAUTHTOK_PROMPT, - PAM_HOST, - ); - /// The total number of PAM items. - pub const PAM_NUM_ITEMS: i32 = 14; - - c_enum!( - /// An optional OpenPAM feature. - OPENPAM_RESTRICT_SERVICE_NAME, - OPENPAM_VERIFY_POLICY_FILE, - OPENPAM_RESTRICT_MODULE_NAME, - OPENPAM_VERIFY_MODULE_FILE, - OPENPAM_FALLBACK_TO_OTHER, - ); - /// The number of optional OpenPAM features. - pub const OPENPAM_NUM_FEATURES: i32 = 5; - - c_enum!( - /// Log level. - PAM_LOG_LIBDEBUG = -1, - PAM_LOG_DEBUG, - PAM_LOG_VERBOSE, - PAM_LOG_NOTICE, - PAM_LOG_ERROR, - ); - - c_enum!( - /// PAM primitives. - PAM_SM_AUTHENTICATE, - PAM_SM_SETCRED, - PAM_SM_ACCT_MGMT, - PAM_SM_OPEN_SESSION, - PAM_SM_CLOSE_SESSION, - PAM_SM_CHAUTHTOK, - ); - /// The number of PAM primitives. - pub const PAM_NUM_PRIMITIVES: i32 = 6; -} - -/// Constants exclusive to Illumos. -#[cfg(pam_impl = "Sun")] -pub use sun::*; -#[cfg(pam_impl = "Sun")] -mod sun { - /// The total number of PAM error codes. - pub const PAM_TOTAL_ERRNUM: i32 = 28; - - c_enum!( - /// An item type. - PAM_REPOSITORY = 10, - PAM_RESOURCE, - PAM_AUSER, - ); - - /// A flag for `pam_chauthtok`. - pub const PAM_NO_AUTHTOK_CHECK: i32 = 0b1000; -}
--- a/libpam-sys/src/ffi.rs Thu Jul 03 11:14:49 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,514 +0,0 @@ -//! The actual PAM FFI bindings. -//! -//! They live in this specific file rather than lib.rs because otherwise -//! ctest gets very upset about some of the macros we use. -#![allow(non_camel_case_types)] -#![allow(unused_imports)] - -use num_enum::{IntoPrimitive, TryFromPrimitive}; -use std::ffi::{c_char, c_int, c_uint, c_void}; -use std::fmt; -use std::marker::{PhantomData, PhantomPinned}; - -/// A marker struct to make whatever it's in `!Sync`, `!Send`, and `!Unpin`. -#[derive(Default, PartialOrd, PartialEq, Ord, Eq)] -#[repr(C)] -struct ExtremelyUnsafe { - _value: (), - _marker: PhantomData<(PhantomPinned, *mut c_void)>, -} - -impl fmt::Debug for ExtremelyUnsafe { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ExtremelyUnsafe({self:p})") - } -} - -/// An opaque structure that PAM uses to communicate. -/// -/// This is only ever returned in pointer form and cannot be constructed. -#[repr(C)] -pub struct pam_handle(ExtremelyUnsafe); - -impl fmt::Debug for pam_handle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "pam_handle({self:p}") - } -} - -/// An opaque structure that is passed through PAM in a conversation. -#[repr(C)] -pub struct AppData(ExtremelyUnsafe); - -impl fmt::Debug for AppData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "AppData({self:p}") - } -} - -/// Just an alias for the type of [`pam_conv::conv`]. -/// -/// For important details about the format of `messages`, -/// see the [`helpers`](crate::helpers) module. -/// -/// ```no_run -/// # use libpam_sys::{ConversationCallback, pam_conv}; -/// fn convo() -> ConversationCallback { -/// // ... -/// # unimplemented!() -/// } -/// let conv = pam_conv{conv: convo(), appdata_ptr: std::ptr::null_mut()}; -/// ``` -pub type ConversationCallback = unsafe extern "C" fn( - num_msg: c_int, - msg: *const *const pam_message, - resp: *mut *mut pam_response, - appdata: *mut AppData, -) -> c_int; - -/// Alias for the callback to [`pam_set_data`](pam_set_data). -/// -/// ```no_run -/// # use std::ffi::CString; -/// use libpam_sys::{CleanupCallback, pam_set_data}; -/// # use libpam_sys::pam_handle; -/// # let handle: *mut pam_handle = std::ptr::null_mut(); -/// # let mut my_data = 100; -/// # let data_ptr = &mut my_data as *mut i32; -/// fn cleanup() -> CleanupCallback { -/// // ... -/// # unimplemented!() -/// } -/// let name = CString::new("name").unwrap(); -/// unsafe { -/// pam_set_data(handle, name.as_ptr().cast_mut(), data_ptr.cast(), cleanup()); -/// } -/// ``` -pub type CleanupCallback = - unsafe extern "C" fn(pamh: *mut pam_handle, data: *mut c_void, pam_end_status: c_int); - -/// Used by PAM to communicate between the module and the application. -#[repr(C)] -pub struct pam_conv { - pub conv: unsafe extern "C" fn( - num_msg: c_int, - msg: *const *const pam_message, - resp: *mut *mut pam_response, - appdata: *mut AppData, - ) -> c_int, - pub appdata_ptr: *mut AppData, -} - -/// A message sent into a PAM conversation. -#[repr(C)] -pub struct pam_message { - pub msg_style: c_int, - pub msg: *const c_char, -} - -/// A response returned from a PAM conversation. -#[repr(C)] -pub struct pam_response { - pub resp: *mut c_char, - /// Completely unused. - pub resp_retcode: c_int, -} - -/// Definition of the PAM_XAUTHDATA item. Compatible with `xcb_auth_info_t`. -#[cfg(pam_impl = "LinuxPam")] -#[repr(C)] -pub struct pam_xauth_data { - namelen: c_int, - name: *mut c_char, - datalen: c_int, - data: *mut c_char, -} - -#[cfg(pam_impl = "LinuxPam")] -#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] -#[repr(i32)] -pub enum pam_modutil_redirect_fd { - PAM_MODUTIL_IGNORE_FD, - PAM_MODUTIL_PIPE_FD, - PAM_MODUTIL_NULL_FD, -} - -#[cfg(pam_impl = "LinuxPam")] -pub use pam_modutil_redirect_fd::*; - -#[cfg(pam_impl = "LinuxPam")] -#[derive(Debug)] -#[repr(C)] -pub struct pam_modutil_privs { - grplist: *mut libc::gid_t, - number_of_groups: c_int, - allocated: c_int, - old_gid: libc::gid_t, - old_uid: libc::uid_t, - is_dropped: c_int, -} - -#[cfg(pam_impl = "OpenPam")] -pub type pam_func_t = unsafe extern "C" fn( - handle: *mut pam_handle, - flags: c_int, - argc: c_int, - argv: *const *const c_char, -) -> c_int; - -#[cfg(pam_impl = "OpenPam")] -#[derive(Debug)] -#[repr(C)] -pub struct pam_module { - path: *mut c_char, - func: [pam_func_t; 6], - dlh: *mut c_void, -} - -#[cfg(pam_impl = "OpenPam")] -#[derive(Debug)] -#[repr(C)] -pub struct pam_repository { - typ: *mut c_char, - scope: *mut c_void, - scope_len: usize, -} - -// These are the functions specified in X/SSO. Everybody exports them. -#[link(name = "pam")] -extern "C" { - /// Account validation. - pub fn pam_acct_mgmt(pamh: *mut pam_handle, flags: c_int) -> c_int; - - /// Authenticate a user. - pub fn pam_authenticate(pamh: *mut pam_handle, flags: c_int) -> c_int; - - // Nobody implements pam_authenticate_secondary. - - /// Manage authentication tokens. - pub fn pam_chauthtok(pamh: *mut pam_handle, flags: c_int) -> c_int; - - /// Close an opened user session. - pub fn pam_close_session(pamh: *mut pam_handle, flags: c_int) -> c_int; - - /// Ends the PAM transaction. - pub fn pam_end(pamh: *mut pam_handle, flags: c_int) -> c_int; - - /// Gets module-specific data. PAM still owns the data. - pub fn pam_get_data( - pamh: *mut pam_handle, - module_data_name: *const c_char, - data: *mut *const c_void, - ) -> c_int; - - /// Gets an environment variable. You own the return value. - pub fn pam_getenv(pamh: *const pam_handle, name: *const c_char) -> *mut c_char; - - /// Gets all the environment variables. You own everything it points to. - pub fn pam_getenvlist(pamh: *const pam_handle) -> *mut *mut c_char; - - /// Get information about the transaction. - /// - /// The item is owned by PAM. - pub fn pam_get_item( - pamh: *const pam_handle, - item_type: c_int, - item: *mut *const c_void, - ) -> c_int; - - // Nobody implements pam_get_mapped_authtok. - // Nobody implements pam_get_mapped_username. - - /// Get the username. PAM owns it. - pub fn pam_get_user( - pamh: *mut pam_handle, - user: *mut *const c_char, - prompt: *const c_char, - ) -> c_int; - - /// Opens a user session. - pub fn pam_open_session(pamh: *mut pam_handle, flags: c_int) -> c_int; - - /// Sets the value of an environment variable. `namevalue` is copied. - pub fn pam_putenv(pamh: *mut pam_handle, namevalue: *const c_char) -> c_int; - - /// Update or delete user credentials. - pub fn pam_setcred(pamh: *mut pam_handle, flags: c_int) -> c_int; - - /// Set module-specific data. PAM will call `cleanup` when completed. - pub fn pam_set_data( - pamh: *mut pam_handle, - module_data_name: *const c_char, - data: *mut c_void, - cleanup: unsafe extern "C" fn( - pamh: *mut pam_handle, - data: *mut c_void, - pam_end_status: c_int, - ), - ) -> c_int; - - /// Set information about the transaction. The `item` is copied. - pub fn pam_set_item(pamh: *mut pam_handle, item_type: c_int, item: *const c_void) -> c_int; - - // Nobody implements pam_set_mapped_authtok. - // Nobody implements pam_set_mapped_username. - - // The pam_sm_whatever functions are prototypes for the functions that - // a PAM module should implement, not symbols provided by PAM. - - // Nobody implements pam_authenticate_secondary. - - /// Starts a PAM transaction. The `conv` may or may not be copied. - pub fn pam_start( - service: *const c_char, - user: *const c_char, - pam_conv: *mut pam_conv, - pamh: *mut *mut pam_handle, - ) -> c_int; - - /// Gets a statically-allocated error string. - /// - /// All implementations of PAM known to this library (Linux-PAM, OpenPAM, - /// and Sun) ignore `pamh` and will accept a null pointer. - pub fn pam_strerror(pamh: *const pam_handle, error_number: c_int) -> *mut c_char; -} - -// We use `pam_impl` because ctest loses its mind -// when it comes across the `cfg_pam_impl` macro. -// This is a custom cfg variable set in our build.rs. Don't do this; just use -// cfg_pam_impl. -#[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))] -extern "C" { - /// Gets `PAM_AUTHTOK`, or asks the user if that is unset. - pub fn pam_get_authtok( - pamh: *mut pam_handle, - item: c_int, - authtok: *mut *const c_char, - prompt: *const c_char, - ) -> c_int; - - pub fn pam_prompt( - pamh: *const pam_handle, - style: c_int, - response: *mut *mut c_char, - fmt: *const c_char, - ... - ) -> c_int; - -} - -#[cfg(pam_impl = "LinuxPam")] -extern "C" { - pub fn pam_fail_delay(pamh: *mut pam_handle, musec_delay: c_uint) -> c_int; - - /// Start a PAM transaction based on configuration in the given directory. - pub fn pam_start_confdir( - service_name: *const c_char, - user: *const c_char, - pam_conversation: *mut pam_conv, - confdir: *const c_char, - pamh: *mut *mut pam_handle, - ) -> c_int; - - // We don't export the v-variants of the formatting functions. - - pub fn pam_syslog(pamh: *const pam_handle, priority: c_int, fmt: *const c_char, ...); - - pub fn pam_get_authtok_noverify( - pamh: *const pam_handle, - authtok: *mut *const c_char, - prompt: *const c_char, - ) -> c_int; - - pub fn pam_get_authtok_verify( - pamh: *const pam_handle, - authtok: *mut *const c_char, - prompt: *const c_char, - ) -> c_int; - - // pam_modutil also lives in libpam for Linux. - - pub fn pam_modutil_check_user_in_passwd( - pamh: *mut pam_handle, - user_name: *const c_char, - file_name: *const c_char, - ) -> c_int; - - pub fn pam_modutil_getpwnam(pamh: *mut pam_handle, user: *const c_char) -> *mut libc::passwd; - - pub fn pam_modutil_getpwuid(pamh: *mut pam_handle, uid: libc::uid_t) -> *mut libc::passwd; - - pub fn pam_modutil_getgrnam(pamh: *mut pam_handle, group: *const c_char) -> *mut libc::group; - - pub fn pam_modutil_getgrgid(pamh: *mut pam_handle, gid: libc::gid_t) -> *mut libc::group; - - pub fn pam_modutil_getspnam(pamh: *mut pam_handle, user: *const c_char) -> *mut libc::spwd; - - pub fn pam_modutil_user_in_group_nam_nam( - pamh: *mut pam_handle, - user: *const c_char, - group: *const c_char, - ) -> c_int; - pub fn pam_modutil_user_in_group_nam_gid( - pamh: *mut pam_handle, - user: *const c_char, - group: libc::gid_t, - ) -> c_int; - - pub fn pam_modutil_user_in_group_uid_nam( - pamh: *mut pam_handle, - user: libc::uid_t, - group: *const c_char, - ) -> c_int; - - pub fn pam_modutil_user_in_group_uid_gid( - pamh: *mut pam_handle, - user: libc::uid_t, - group: libc::gid_t, - ) -> c_int; - - pub fn pam_modutil_getlogin(pamh: *mut pam_handle) -> *const c_char; - - pub fn pam_modutil_read(fd: c_int, buffer: *mut c_char, count: c_int) -> c_int; - - pub fn pam_modutil_write(fd: c_int, buffer: *const c_char, count: c_int) -> c_int; - - pub fn pam_modutil_audit_write( - pamh: *mut pam_handle, - typ: c_int, - message: *const c_char, - retval: c_int, - ) -> c_int; - - pub fn pam_modutil_drop_priv( - pamh: *mut pam_handle, - p: *mut pam_modutil_privs, - pw: *const libc::passwd, - ) -> c_int; - - pub fn pam_modutil_regain_priv(pamh: *mut pam_handle, p: *mut pam_modutil_privs) -> c_int; - - pub fn pam_modutil_sanitize_helper_fds( - pamh: *mut pam_handle, - redirect_stdin: pam_modutil_redirect_fd, - redirect_stdout: pam_modutil_redirect_fd, - redirect_stderr: pam_modutil_redirect_fd, - ) -> c_int; - - pub fn pam_modutil_search_key( - pamh: *mut pam_handle, - file_name: *const c_char, - key: *const c_char, - ) -> *mut c_char; -} - -#[cfg(pam_impl = "OpenPam")] -extern "C" { - pub fn openpam_borrow_cred(pamh: *mut pam_handle, passwd: *const libc::passwd) -> c_int; - - pub fn openpam_subst( - pamh: *const pam_handle, - buf: *mut c_char, - _bufsize: *mut usize, - _template: *const c_char, - ) -> c_int; - - pub fn openpam_free_data(pamh: *mut pam_handle, data: *mut c_void, status: c_int); - - pub fn openpam_free_envlist(_envlist: *mut *mut c_char); - - pub fn openpam_get_option(_pamh: *mut pam_handle, _option: *const c_char) -> *const c_char; - - pub fn openpam_restore_cred(pamh: *mut pam_handle) -> c_int; - - pub fn openpam_set_option( - _pamh: *mut pam_handle, - _option: *const c_char, - _value: *const c_char, - ) -> c_int; - - pub fn pam_error(pamh: *const pam_handle, _fmt: *const c_char, ...) -> c_int; - - pub fn pam_info(_pamh: *const pam_handle, _fmt: *const c_char, ...) -> c_int; - - pub fn openpam_readline( - _f: *mut libc::FILE, - _lineno: *mut c_int, - _lenp: *mut usize, - ) -> *mut c_char; - - pub fn openpam_readlinev( - _f: *mut libc::FILE, - _lineno: *mut c_int, - _lenp: *mut c_int, - ) -> *mut *mut c_char; - - pub fn openpam_readword( - _f: *mut libc::FILE, - _lineno: *mut c_int, - _lenp: *mut usize, - ) -> *mut c_char; - - pub fn openpam_straddch( - _str: *mut *mut c_char, - _sizep: *mut usize, - _lenp: *mut usize, - ch: c_int, - ) -> c_int; - - pub fn openpam_set_feature(_feature: c_int, _onoff: c_int) -> c_int; - - pub fn openpam_get_feature(_feature: c_int, _onoff: *mut c_int) -> c_int; - - pub fn _openpam_log(_level: c_int, _func: *const c_char, _fmt: *const c_char, ...); - - /// A premade conversation function that talks to the TTY. - /// - /// ```no_run - /// # use std::ffi::CString; - /// # use std::ptr; - /// use libpam_sys::*; - /// # let service = CString::new("whatever").unwrap(); - /// # let user = CString::new("whatever").unwrap(); - /// let mut handle: *mut pam_handle = ptr::null_mut(); - /// let mut conv = pam_conv{ - /// conv: openpam_ttyconv, - /// appdata_ptr: ptr::null_mut(), - /// }; - /// let result = unsafe { pam_start( - /// service.as_ptr(), user.as_ptr(), &mut conv, &mut handle - /// ) }; - /// ``` - pub fn openpam_ttyconv( - n: c_int, - _msg: *const *const pam_message, - _resp: *mut *mut pam_response, - _data: *mut AppData, - ) -> c_int; - - pub static mut openpam_ttyconv_timeout: c_int; - - /// A null conversation function. - /// - /// ```no_run - /// # use std::ffi::CString; - /// # use std::ptr; - /// use libpam_sys::*; - /// # let service = CString::new("whatever").unwrap(); - /// # let user = CString::new("whatever").unwrap(); - /// let mut handle: *mut pam_handle = ptr::null_mut(); - /// let mut conv = pam_conv{ - /// conv: openpam_nullconv, - /// appdata_ptr: ptr::null_mut(), - /// }; - /// let result = unsafe { pam_start( - /// service.as_ptr(), user.as_ptr(), &mut conv, &mut handle - /// ) }; - /// ``` - pub fn openpam_nullconv( - n: c_int, - _msg: *const *const pam_message, - _resp: *mut *mut pam_response, - _data: *mut AppData, - ) -> c_int; -}
--- a/libpam-sys/src/helpers.rs Thu Jul 03 11:14:49 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,512 +0,0 @@ -//! This module contains a few non-required helpers to deal with some of the -//! more annoying memory management in the PAM API. - -use std::error::Error; -use std::marker::{PhantomData, PhantomPinned}; -use std::mem::ManuallyDrop; -use std::ptr::NonNull; -use std::{any, fmt, mem, slice}; - -/// A pointer-to-pointer-to-message container for the [conversation callback]. -/// -/// The PAM conversation callback requires a pointer to a pointer of [message]s. -/// Linux-PAM handles this differently than all other PAM implementations -/// (including the X/SSO PAM standard). -/// -/// X/SSO appears to specify a pointer-to-pointer-to-array: -/// -/// ```text -/// points to ┌────────────┐ ╔═ Message[] ═╗ -/// messages ┄┄┄┄┄┄┄┄┄┄> │ *messages ┄┼┄┄┄┄┄> ║ style ║ -/// └────────────┘ ║ data ┄┄┄┄┄┄┄╫┄┄> ... -/// ╟─────────────╢ -/// ║ style ║ -/// ║ data ┄┄┄┄┄┄┄╫┄┄> ... -/// ╟─────────────╢ -/// ║ ... ║ -/// ``` -/// -/// whereas Linux-PAM uses an `**argv`-style pointer-to-array-of-pointers: -/// -/// ```text -/// points to ┌──────────────┐ ╔═ Message ═╗ -/// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ -/// │ messages[1] ┄┼┄┄┄╮ ║ data ┄┄┄┄┄╫┄┄> ... -/// │ ... │ ┆ ╚═══════════╝ -/// ┆ -/// ┆ ╔═ Message ═╗ -/// ╰┄┄> ║ style ║ -/// ║ data ┄┄┄┄┄╫┄┄> ... -/// ╚═══════════╝ -/// ``` -/// -/// Because the `messages` remain owned by the application which calls into PAM, -/// we can solve this with One Simple Trick: make the intermediate list point -/// into the same array: -/// -/// ```text -/// points to ┌──────────────┐ ╔═ Message[] ═╗ -/// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ -/// │ messages[1] ┄┼┄┄╮ ║ data ┄┄┄┄┄┄┄╫┄┄> ... -/// │ ... │ ┆ ╟─────────────╢ -/// ╰┄> ║ style ║ -/// ║ data ┄┄┄┄┄┄┄╫┄┄> ... -/// ╟─────────────╢ -/// ║ ... ║ -/// -/// ``` -/// -/// [conversation callback]: crate::ConversationCallback -/// [message]: crate::pam_message -#[derive(Debug)] -pub struct PtrPtrVec<T> { - data: Vec<T>, - pointers: Vec<*const T>, -} - -// Since this is a wrapper around a Vec with no dangerous functionality*, -// this can be Send and Sync provided the original Vec is. -// -// * It will only become unsafe when the user dereferences a pointer or sends it -// to an unsafe function. -unsafe impl<T> Send for PtrPtrVec<T> where Vec<T>: Send {} -unsafe impl<T> Sync for PtrPtrVec<T> where Vec<T>: Sync {} - -impl<T> PtrPtrVec<T> { - /// Takes ownership of the given Vec and creates a vec of pointers to it. - pub fn new(data: Vec<T>) -> Self { - let pointers: Vec<_> = data.iter().map(|r| r as *const T).collect(); - Self { data, pointers } - } - - /// Gives you back your Vec. - pub fn into_inner(self) -> Vec<T> { - self.data - } - - /// Gets a pointer-to-pointer suitable for passing into the Conversation. - pub fn as_ptr<Dest>(&self) -> *const *const Dest { - Self::assert_size::<Dest>(); - self.pointers.as_ptr().cast::<*const Dest>() - } - - /// Iterates over a Linux-PAM–style pointer-to-array-of-pointers. - /// - /// # Safety - /// - /// `ptr_ptr` must be a valid pointer to an array of pointers, - /// there must be at least `count` valid pointers in the array, - /// and each pointer in that array must point to a valid `T`. - #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"] - #[allow(dead_code)] - pub unsafe fn iter_over_linux<'a, Src>( - ptr_ptr: *const *const Src, - count: usize, - ) -> impl Iterator<Item = &'a T> - where - T: 'a, - { - Self::assert_size::<Src>(); - slice::from_raw_parts(ptr_ptr.cast::<&T>(), count) - .iter() - .copied() - } - - /// Iterates over an X/SSO–style pointer-to-pointer-to-array. - /// - /// # Safety - /// - /// You must pass a valid pointer to a valid pointer to an array, - /// there must be at least `count` elements in the array, - /// and each value in that array must be a valid `T`. - #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"] - #[allow(dead_code)] - pub unsafe fn iter_over_xsso<'a, Src>( - ptr_ptr: *const *const Src, - count: usize, - ) -> impl Iterator<Item = &'a T> - where - T: 'a, - { - Self::assert_size::<Src>(); - slice::from_raw_parts(*ptr_ptr.cast(), count).iter() - } - - /// Iterates over a PAM message list appropriate to your system's impl. - /// - /// This selects the correct pointer/array structure to use for a message - /// that was given to you by your system. - /// - /// # Safety - /// - /// `ptr_ptr` must point to a valid message list, there must be at least - /// `count` messages in the list, and all messages must be a valid `Src`. - #[allow(deprecated)] - pub unsafe fn iter_over<'a, Src>( - ptr_ptr: *const *const Src, - count: usize, - ) -> impl Iterator<Item = &'a T> - where - T: 'a, - { - #[cfg(pam_impl = "LinuxPam")] - return Self::iter_over_linux(ptr_ptr, count); - #[cfg(not(pam_impl = "LinuxPam"))] - return Self::iter_over_xsso(ptr_ptr, count); - } - - fn assert_size<That>() { - debug_assert_eq!( - mem::size_of::<T>(), - mem::size_of::<That>(), - "type {t} is not the size of {that}", - t = any::type_name::<T>(), - that = any::type_name::<That>(), - ); - } -} - -/// Error returned when attempting to allocate a buffer that is too big. -/// -/// This is specifically used in [`OwnedBinaryPayload`] when you try to allocate -/// a message larger than 2<sup>32</sup> bytes. -#[derive(Debug, PartialEq)] -pub struct TooBigError { - pub size: usize, - pub max: usize, -} - -impl Error for TooBigError {} - -impl fmt::Display for TooBigError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "can't allocate a message of {size} bytes (max {max})", - size = self.size, - max = self.max - ) - } -} - -/// A trait wrapping memory management. -/// -/// This is intended to allow you to bring your own allocator for -/// [`OwnedBinaryPayload`]s. -/// -/// For an implementation example, see the implementation of this trait -/// for [`Vec`]. -pub trait Buffer<T: Default> { - /// Allocates a buffer of `len` elements, filled with the default. - fn allocate(len: usize) -> Self; - - fn as_ptr(&self) -> *const T; - - /// Returns a slice view of `size` elements of the given memory. - /// - /// # Safety - /// - /// The caller must not request more elements than are allocated. - unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T]; - - /// Consumes this ownership and returns a pointer to the start of the arena. - fn into_ptr(self) -> NonNull<T>; - - /// "Adopts" the memory at the given pointer, taking it under management. - /// - /// Running the operation: - /// - /// ``` - /// # use libpam_sys::helpers::Buffer; - /// # fn test<T: Default, OwnerType: Buffer<T>>(bytes: usize) { - /// let owner = OwnerType::allocate(bytes); - /// let ptr = owner.into_ptr(); - /// let owner = unsafe { OwnerType::from_ptr(ptr, bytes) }; - /// # } - /// ``` - /// - /// must be a no-op. - /// - /// # Safety - /// - /// The pointer must be valid, and the caller must provide the exact size - /// of the given arena. - unsafe fn from_ptr(ptr: NonNull<T>, bytes: usize) -> Self; -} - -impl<T: Default> Buffer<T> for Vec<T> { - fn allocate(bytes: usize) -> Self { - (0..bytes).map(|_| Default::default()).collect() - } - - fn as_ptr(&self) -> *const T { - Vec::as_ptr(self) - } - - unsafe fn as_mut_slice(&mut self, bytes: usize) -> &mut [T] { - debug_assert!(bytes <= self.len()); - Vec::as_mut(self) - } - - fn into_ptr(self) -> NonNull<T> { - let mut me = ManuallyDrop::new(self); - // SAFETY: a Vec is guaranteed to have a nonzero pointer. - unsafe { NonNull::new_unchecked(me.as_mut_ptr()) } - } - - unsafe fn from_ptr(ptr: NonNull<T>, bytes: usize) -> Self { - Vec::from_raw_parts(ptr.as_ptr(), bytes, bytes) - } -} - -/// The structure of the "binary message" payload for the `PAM_BINARY_PROMPT` -/// extension from Linux-PAM. -pub struct BinaryPayload { - /// The total byte size of the message, including this header, - /// as a u32 in network byte order (big endian). - pub total_bytes_u32be: [u8; 4], - /// A tag used to provide some kind of hint as to what the data is. - /// Its meaning is undefined. - pub data_type: u8, - /// Where the data itself would start, used as a marker to make this - /// not [`Unpin`] (since it is effectively an intrusive data structure - /// pointing to immediately after itself). - pub _marker: PhantomData<PhantomPinned>, -} - -impl BinaryPayload { - /// The most data it's possible to put into a [`BinaryPayload`]. - pub const MAX_SIZE: usize = (u32::MAX - 5) as usize; - - /// Fills in the provided buffer with the given data. - /// - /// This uses [`copy_from_slice`](slice::copy_from_slice) internally, - /// so `buf` must be exactly 5 bytes longer than `data`, or this function - /// will panic. - pub fn fill(buf: &mut [u8], data_type: u8, data: &[u8]) { - let ptr: *mut Self = buf.as_mut_ptr().cast(); - // SAFETY: We're given a slice, which always has a nonzero pointer. - let me = unsafe { ptr.as_mut().unwrap_unchecked() }; - me.total_bytes_u32be = u32::to_be_bytes(buf.len() as u32); - me.data_type = data_type; - buf[5..].copy_from_slice(data) - } - - /// The total storage needed for the message, including header. - pub fn total_bytes(&self) -> usize { - u32::from_be_bytes(self.total_bytes_u32be) as usize - } - - /// Gets the total byte buffer of the BinaryMessage stored at the pointer. - /// - /// The returned data slice is borrowed from where the pointer points to. - /// - /// # Safety - /// - /// - The pointer must point to a valid `BinaryPayload`. - /// - The borrowed data must not outlive the pointer's validity. - pub unsafe fn buffer_of<'a>(ptr: *const Self) -> &'a [u8] { - let header: &Self = ptr.as_ref().unwrap_unchecked(); - slice::from_raw_parts(ptr.cast(), header.total_bytes().max(5)) - } - - /// Gets the contents of the BinaryMessage stored at the given pointer. - /// - /// The returned data slice is borrowed from where the pointer points to. - /// This is a cheap operation and doesn't do *any* copying. - /// - /// We don't take a `&self` reference here because accessing beyond - /// the range of the `Self` data (i.e., beyond the 5 bytes of `self`) - /// is undefined behavior. Instead, you have to pass a raw pointer - /// directly to the data. - /// - /// # Safety - /// - /// - The pointer must point to a valid `BinaryPayload`. - /// - The borrowed data must not outlive the pointer's validity. - pub unsafe fn contents<'a>(ptr: *const Self) -> (u8, &'a [u8]) { - let header: &Self = ptr.as_ref().unwrap_unchecked(); - (header.data_type, &Self::buffer_of(ptr)[5..]) - } -} - -/// A binary message owned by some storage. -/// -/// This is an owned, memory-managed version of [`BinaryPayload`]. -/// The `O` type manages the memory where the payload lives. -/// [`Vec<u8>`] is one such manager and can be used when ownership -/// of the data does not need to transit through PAM. -#[derive(Debug)] -pub struct OwnedBinaryPayload<Owner: Buffer<u8>>(Owner); - -impl<O: Buffer<u8>> OwnedBinaryPayload<O> { - /// Allocates a new OwnedBinaryPayload. - /// - /// This will return a [`TooBigError`] if you try to allocate too much - /// (more than [`BinaryPayload::MAX_SIZE`]). - pub fn new(data_type: u8, data: &[u8]) -> Result<Self, TooBigError> { - let total_len: u32 = (data.len() + 5).try_into().map_err(|_| TooBigError { - size: data.len(), - max: BinaryPayload::MAX_SIZE, - })?; - let total_len = total_len as usize; - let mut buf = O::allocate(total_len); - // SAFETY: We just allocated this exact size. - BinaryPayload::fill(unsafe { buf.as_mut_slice(total_len) }, data_type, data); - Ok(Self(buf)) - } - - /// The contents of the buffer. - pub fn contents(&self) -> (u8, &[u8]) { - unsafe { BinaryPayload::contents(self.as_ptr()) } - } - - /// The total bytes needed to store this, including the header. - pub fn total_bytes(&self) -> usize { - unsafe { BinaryPayload::buffer_of(self.0.as_ptr().cast()).len() } - } - - /// Unwraps this into the raw storage backing it. - pub fn into_inner(self) -> O { - self.0 - } - - /// Gets a const pointer to the start of the message's buffer. - pub fn as_ptr(&self) -> *const BinaryPayload { - self.0.as_ptr().cast() - } - - /// Consumes ownership of this message and converts it to a raw pointer - /// to the start of the message. - /// - /// To clean this up, you should eventually pass it into [`Self::from_ptr`] - /// with the same `O` ownership type. - pub fn into_ptr(self) -> NonNull<BinaryPayload> { - self.0.into_ptr().cast() - } - - /// Takes ownership of the given pointer. - /// - /// # Safety - /// - /// You must provide a valid pointer, allocated by (or equivalent to one - /// allocated by) [`Self::new`]. For instance, passing a pointer allocated - /// by `malloc` to `OwnedBinaryPayload::<Vec<u8>>::from_ptr` is not allowed. - pub unsafe fn from_ptr(ptr: NonNull<BinaryPayload>) -> Self { - Self(O::from_ptr(ptr.cast(), ptr.as_ref().total_bytes())) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::ptr; - - type VecPayload = OwnedBinaryPayload<Vec<u8>>; - - #[test] - fn test_binary_payload() { - let simple_message = &[0u8, 0, 0, 16, 0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - let empty = &[0u8; 5]; - - assert_eq!((0xff, &[0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..]), unsafe { - BinaryPayload::contents(simple_message.as_ptr().cast()) - }); - assert_eq!((0x00, &[][..]), unsafe { - BinaryPayload::contents(empty.as_ptr().cast()) - }); - } - - #[test] - fn test_owned_binary_payload() { - let (typ, data) = ( - 112, - &[0, 1, 1, 8, 9, 9, 9, 8, 8, 1, 9, 9, 9, 1, 1, 9, 7, 2, 5, 3][..], - ); - let payload = VecPayload::new(typ, data).unwrap(); - assert_eq!((typ, data), payload.contents()); - let ptr = payload.into_ptr(); - let payload = unsafe { VecPayload::from_ptr(ptr) }; - assert_eq!((typ, data), payload.contents()); - } - - #[test] - #[ignore] - fn test_owned_too_big() { - let data = vec![0xFFu8; 0x1_0000_0001]; - assert_eq!( - TooBigError { - max: 0xffff_fffa, - size: 0x1_0000_0001 - }, - VecPayload::new(5, &data).unwrap_err() - ) - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn test_new_wrong_size() { - let bad_vec = vec![0; 19]; - let msg = PtrPtrVec::new(bad_vec); - let _ = msg.as_ptr::<u64>(); - } - - #[allow(deprecated)] - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn test_iter_xsso_wrong_size() { - unsafe { - let _ = PtrPtrVec::<u8>::iter_over_xsso::<f64>(ptr::null(), 1); - } - } - - #[allow(deprecated)] - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn test_iter_linux_wrong_size() { - unsafe { - let _ = PtrPtrVec::<u128>::iter_over_linux::<()>(ptr::null(), 1); - } - } - - #[allow(deprecated)] - #[test] - fn test_right_size() { - let good_vec = vec![(1u64, 2u64), (3, 4), (5, 6)]; - let ptr = good_vec.as_ptr(); - let msg = PtrPtrVec::new(good_vec); - let msg_ref: *const *const (i64, i64) = msg.as_ptr(); - assert_eq!(unsafe { *msg_ref }, ptr.cast()); - - let linux_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_linux(msg_ref, 3) } - .cloned() - .collect(); - let xsso_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_xsso(msg_ref, 3) } - .cloned() - .collect(); - assert_eq!(vec![(1, 2), (3, 4), (5, 6)], linux_result); - assert_eq!(vec![(1, 2), (3, 4), (5, 6)], xsso_result); - drop(msg) - } - - #[allow(deprecated)] - #[test] - fn test_iter_ptr_ptr() { - let strs = vec![Box::new("a"), Box::new("b"), Box::new("c"), Box::new("D")]; - let ptr: *const *const &str = strs.as_ptr().cast(); - let got: Vec<&str> = unsafe { PtrPtrVec::iter_over_linux(ptr, 4) } - .cloned() - .collect(); - assert_eq!(vec!["a", "b", "c", "D"], got); - - let nums = [-1i8, 2, 3]; - let ptr = nums.as_ptr(); - let got: Vec<u8> = unsafe { PtrPtrVec::iter_over_xsso(&ptr, 3) } - .cloned() - .collect(); - assert_eq!(vec![255, 2, 3], got); - } -}
--- a/libpam-sys/src/lib.rs Thu Jul 03 11:14:49 2025 -0400 +++ b/libpam-sys/src/lib.rs Thu Jul 03 14:28:04 2025 -0400 @@ -1,13 +1,515 @@ -#![doc = include_str!("../README.md")] +//! `libpam-sys` provides low-level access to `libpam`. //! -//! ## PAM implementation +//! Everything in here is directly as exported from the `libpam` library or +//! its header files, with limited exceptions: //! -#![doc = concat!("This documentation was built for the **", env!("LIBPAMSYS_IMPL"), "** implementation.")] - -mod constants; -mod ffi; -pub mod helpers; -pub mod pam_impl; +//! - The [`pam_impl`] submodule (and the [`pam_impl_name!`] macro) contains +//! tools for detecting the current PAM library. +//! - [`AppData`] is an opaque pointer newtype. +//! - [`ConversationCallback`] and [`CleanupCallback`] are aliases for +//! what are otherwise anonymous function types. +//! +#![doc = concat!("This documentation was built for the **", pam_impl_name!(), "** implementation.")] +//! +//! You can override this **at build time** by setting the `LIBPAMSYS_IMPL` +//! environment variable to one of the values of the [`pam_impl::PamImpl`] enum. +//! +//! For more information about configuration, see [the README](https://crates.io/crate/libpam-sys). +#![allow(non_camel_case_types)] +#![allow(unused_imports)] #[doc(inline)] -pub use crate::{constants::*, ffi::*, pam_impl::*}; +pub use libpam_sys_helpers::constants::*; +#[doc(inline)] +pub use libpam_sys_helpers::{pam_impl, pam_impl_name}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::ffi::{c_char, c_int, c_uint, c_void}; +use std::fmt; +use std::marker::{PhantomData, PhantomPinned}; + +/// A marker struct to make whatever it's in `!Sync`, `!Send`, and `!Unpin`. +#[derive(Default, PartialOrd, PartialEq, Ord, Eq)] +#[repr(C)] +struct ExtremelyUnsafe { + _value: (), + _marker: PhantomData<(PhantomPinned, *mut c_void)>, +} + +impl fmt::Debug for ExtremelyUnsafe { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ExtremelyUnsafe({self:p})") + } +} + +/// An opaque structure that PAM uses to communicate. +/// +/// This is only ever returned in pointer form and cannot be constructed. +#[repr(C)] +pub struct pam_handle(ExtremelyUnsafe); + +impl fmt::Debug for pam_handle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "pam_handle({self:p}") + } +} + +/// An opaque structure that is passed through PAM in a conversation. +#[repr(C)] +pub struct AppData(ExtremelyUnsafe); + +impl fmt::Debug for AppData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "AppData({self:p}") + } +} + +/// Just an alias for the type of [`pam_conv::conv`]. +/// +/// For important details about the format of `messages`, +/// see [`libpam_sys_helpers::memory::PtrPtrVec`]. +/// +/// ```no_run +/// # use libpam_sys::{ConversationCallback, pam_conv}; +/// fn convo() -> ConversationCallback { +/// // ... +/// # unimplemented!() +/// } +/// let conv = pam_conv{conv: convo(), appdata_ptr: std::ptr::null_mut()}; +/// ``` +pub type ConversationCallback = unsafe extern "C" fn( + num_msg: c_int, + msg: *const *const pam_message, + resp: *mut *mut pam_response, + appdata: *mut AppData, +) -> c_int; + +/// Alias for the callback to [`pam_set_data`]. +/// +/// ```no_run +/// # use std::ffi::CString; +/// use libpam_sys::{CleanupCallback, pam_set_data}; +/// # use libpam_sys::pam_handle; +/// # let handle: *mut pam_handle = std::ptr::null_mut(); +/// # let mut my_data = 100; +/// # let data_ptr = &mut my_data as *mut i32; +/// fn cleanup() -> CleanupCallback { +/// // ... +/// # unimplemented!() +/// } +/// let name = CString::new("name").unwrap(); +/// unsafe { +/// pam_set_data(handle, name.as_ptr().cast_mut(), data_ptr.cast(), cleanup()); +/// } +/// ``` +pub type CleanupCallback = + unsafe extern "C" fn(pamh: *mut pam_handle, data: *mut c_void, pam_end_status: c_int); + +/// Used by PAM to communicate between the module and the application. +#[repr(C)] +pub struct pam_conv { + pub conv: unsafe extern "C" fn( + num_msg: c_int, + msg: *const *const pam_message, + resp: *mut *mut pam_response, + appdata: *mut AppData, + ) -> c_int, + pub appdata_ptr: *mut AppData, +} + +/// A message sent into a PAM conversation. +#[repr(C)] +pub struct pam_message { + pub msg_style: c_int, + pub msg: *const c_char, +} + +/// A response returned from a PAM conversation. +#[repr(C)] +pub struct pam_response { + pub resp: *mut c_char, + /// Completely unused. + pub resp_retcode: c_int, +} + +/// Definition of the PAM_XAUTHDATA item. Compatible with `xcb_auth_info_t`. +#[cfg(pam_impl = "LinuxPam")] +#[repr(C)] +pub struct pam_xauth_data { + pub namelen: c_int, + pub name: *mut c_char, + pub datalen: c_int, + pub data: *mut c_char, +} + +#[cfg(pam_impl = "LinuxPam")] +#[derive(Debug)] +#[repr(C)] +pub struct pam_modutil_privs { + pub grplist: *mut libc::gid_t, + pub number_of_groups: c_int, + pub allocated: c_int, + pub old_gid: libc::gid_t, + pub old_uid: libc::uid_t, + pub is_dropped: c_int, +} + +#[cfg(pam_impl = "OpenPam")] +pub type pam_func_t = unsafe extern "C" fn( + handle: *mut pam_handle, + flags: c_int, + argc: c_int, + argv: *const *const c_char, +) -> c_int; + +#[cfg(pam_impl = "OpenPam")] +#[derive(Debug)] +#[repr(C)] +pub struct pam_module { + pub path: *mut c_char, + pub func: [pam_func_t; 6], + pub dlh: *mut c_void, +} + +#[cfg(pam_impl = "OpenPam")] +#[derive(Debug)] +#[repr(C)] +pub struct pam_repository { + pub typ: *mut c_char, + pub scope: *mut c_void, + pub scope_len: usize, +} + +// These are the functions specified in X/SSO. Everybody exports them. +#[link(name = "pam")] +extern "C" { + /// Account validation. + pub fn pam_acct_mgmt(pamh: *mut pam_handle, flags: c_int) -> c_int; + + /// Authenticate a user. + pub fn pam_authenticate(pamh: *mut pam_handle, flags: c_int) -> c_int; + + // Nobody implements pam_authenticate_secondary. + + /// Manage authentication tokens. + pub fn pam_chauthtok(pamh: *mut pam_handle, flags: c_int) -> c_int; + + /// Close an opened user session. + pub fn pam_close_session(pamh: *mut pam_handle, flags: c_int) -> c_int; + + /// Ends the PAM transaction. + pub fn pam_end(pamh: *mut pam_handle, flags: c_int) -> c_int; + + /// Gets module-specific data. PAM still owns the data. + pub fn pam_get_data( + pamh: *mut pam_handle, + module_data_name: *const c_char, + data: *mut *const c_void, + ) -> c_int; + + /// Gets an environment variable. You own the return value. + pub fn pam_getenv(pamh: *const pam_handle, name: *const c_char) -> *mut c_char; + + /// Gets all the environment variables. You own everything it points to. + pub fn pam_getenvlist(pamh: *const pam_handle) -> *mut *mut c_char; + + /// Get information about the transaction. + /// + /// The item is owned by PAM. + pub fn pam_get_item( + pamh: *const pam_handle, + item_type: c_int, + item: *mut *const c_void, + ) -> c_int; + + // Nobody implements pam_get_mapped_authtok. + // Nobody implements pam_get_mapped_username. + + /// Get the username. PAM owns it. + pub fn pam_get_user( + pamh: *mut pam_handle, + user: *mut *const c_char, + prompt: *const c_char, + ) -> c_int; + + /// Opens a user session. + pub fn pam_open_session(pamh: *mut pam_handle, flags: c_int) -> c_int; + + /// Sets the value of an environment variable. `namevalue` is copied. + pub fn pam_putenv(pamh: *mut pam_handle, namevalue: *const c_char) -> c_int; + + /// Update or delete user credentials. + pub fn pam_setcred(pamh: *mut pam_handle, flags: c_int) -> c_int; + + /// Set module-specific data. PAM will call `cleanup` when completed. + pub fn pam_set_data( + pamh: *mut pam_handle, + module_data_name: *const c_char, + data: *mut c_void, + cleanup: unsafe extern "C" fn( + pamh: *mut pam_handle, + data: *mut c_void, + pam_end_status: c_int, + ), + ) -> c_int; + + /// Set information about the transaction. The `item` is copied. + pub fn pam_set_item(pamh: *mut pam_handle, item_type: c_int, item: *const c_void) -> c_int; + + // Nobody implements pam_set_mapped_authtok. + // Nobody implements pam_set_mapped_username. + + // The pam_sm_whatever functions are prototypes for the functions that + // a PAM module should implement, not symbols provided by PAM. + + // Nobody implements pam_authenticate_secondary. + + /// Starts a PAM transaction. The `conv` may or may not be copied. + pub fn pam_start( + service: *const c_char, + user: *const c_char, + pam_conv: *mut pam_conv, + pamh: *mut *mut pam_handle, + ) -> c_int; + + /// Gets a statically-allocated error string. + /// + /// All implementations of PAM known to this library (Linux-PAM, OpenPAM, + /// and Sun) ignore `pamh` and will accept a null pointer. + pub fn pam_strerror(pamh: *const pam_handle, error_number: c_int) -> *mut c_char; +} + +#[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))] +extern "C" { + /// Gets `PAM_AUTHTOK`, or asks the user if that is unset. + pub fn pam_get_authtok( + pamh: *mut pam_handle, + item: c_int, + authtok: *mut *const c_char, + prompt: *const c_char, + ) -> c_int; + + pub fn pam_prompt( + pamh: *const pam_handle, + style: c_int, + response: *mut *mut c_char, + fmt: *const c_char, + ... + ) -> c_int; + +} + +#[cfg(pam_impl = "LinuxPam")] +extern "C" { + pub fn pam_fail_delay(pamh: *mut pam_handle, musec_delay: c_uint) -> c_int; + + /// Start a PAM transaction based on configuration in the given directory. + pub fn pam_start_confdir( + service_name: *const c_char, + user: *const c_char, + pam_conversation: *mut pam_conv, + confdir: *const c_char, + pamh: *mut *mut pam_handle, + ) -> c_int; + + // We don't export the v-variants of the formatting functions. + + pub fn pam_syslog(pamh: *const pam_handle, priority: c_int, fmt: *const c_char, ...); + + pub fn pam_get_authtok_noverify( + pamh: *const pam_handle, + authtok: *mut *const c_char, + prompt: *const c_char, + ) -> c_int; + + pub fn pam_get_authtok_verify( + pamh: *const pam_handle, + authtok: *mut *const c_char, + prompt: *const c_char, + ) -> c_int; + + // pam_modutil also lives in libpam for Linux. + + pub fn pam_modutil_check_user_in_passwd( + pamh: *mut pam_handle, + user_name: *const c_char, + file_name: *const c_char, + ) -> c_int; + + pub fn pam_modutil_getpwnam(pamh: *mut pam_handle, user: *const c_char) -> *mut libc::passwd; + + pub fn pam_modutil_getpwuid(pamh: *mut pam_handle, uid: libc::uid_t) -> *mut libc::passwd; + + pub fn pam_modutil_getgrnam(pamh: *mut pam_handle, group: *const c_char) -> *mut libc::group; + + pub fn pam_modutil_getgrgid(pamh: *mut pam_handle, gid: libc::gid_t) -> *mut libc::group; + + pub fn pam_modutil_getspnam(pamh: *mut pam_handle, user: *const c_char) -> *mut libc::spwd; + + pub fn pam_modutil_user_in_group_nam_nam( + pamh: *mut pam_handle, + user: *const c_char, + group: *const c_char, + ) -> c_int; + pub fn pam_modutil_user_in_group_nam_gid( + pamh: *mut pam_handle, + user: *const c_char, + group: libc::gid_t, + ) -> c_int; + + pub fn pam_modutil_user_in_group_uid_nam( + pamh: *mut pam_handle, + user: libc::uid_t, + group: *const c_char, + ) -> c_int; + + pub fn pam_modutil_user_in_group_uid_gid( + pamh: *mut pam_handle, + user: libc::uid_t, + group: libc::gid_t, + ) -> c_int; + + pub fn pam_modutil_getlogin(pamh: *mut pam_handle) -> *const c_char; + + pub fn pam_modutil_read(fd: c_int, buffer: *mut c_char, count: c_int) -> c_int; + + pub fn pam_modutil_write(fd: c_int, buffer: *const c_char, count: c_int) -> c_int; + + pub fn pam_modutil_audit_write( + pamh: *mut pam_handle, + typ: c_int, + message: *const c_char, + retval: c_int, + ) -> c_int; + + pub fn pam_modutil_drop_priv( + pamh: *mut pam_handle, + p: *mut pam_modutil_privs, + pw: *const libc::passwd, + ) -> c_int; + + pub fn pam_modutil_regain_priv(pamh: *mut pam_handle, p: *mut pam_modutil_privs) -> c_int; + + pub fn pam_modutil_sanitize_helper_fds( + pamh: *mut pam_handle, + redirect_stdin: pam_modutil_redirect_fd, + redirect_stdout: pam_modutil_redirect_fd, + redirect_stderr: pam_modutil_redirect_fd, + ) -> c_int; + + pub fn pam_modutil_search_key( + pamh: *mut pam_handle, + file_name: *const c_char, + key: *const c_char, + ) -> *mut c_char; +} + +#[cfg(pam_impl = "OpenPam")] +extern "C" { + pub fn openpam_borrow_cred(pamh: *mut pam_handle, passwd: *const libc::passwd) -> c_int; + + pub fn openpam_subst( + pamh: *const pam_handle, + buf: *mut c_char, + _bufsize: *mut usize, + _template: *const c_char, + ) -> c_int; + + pub fn openpam_free_data(pamh: *mut pam_handle, data: *mut c_void, status: c_int); + + pub fn openpam_free_envlist(_envlist: *mut *mut c_char); + + pub fn openpam_get_option(_pamh: *mut pam_handle, _option: *const c_char) -> *const c_char; + + pub fn openpam_restore_cred(pamh: *mut pam_handle) -> c_int; + + pub fn openpam_set_option( + _pamh: *mut pam_handle, + _option: *const c_char, + _value: *const c_char, + ) -> c_int; + + pub fn pam_error(pamh: *const pam_handle, _fmt: *const c_char, ...) -> c_int; + + pub fn pam_info(_pamh: *const pam_handle, _fmt: *const c_char, ...) -> c_int; + + pub fn openpam_readline( + _f: *mut libc::FILE, + _lineno: *mut c_int, + _lenp: *mut usize, + ) -> *mut c_char; + + pub fn openpam_readlinev( + _f: *mut libc::FILE, + _lineno: *mut c_int, + _lenp: *mut c_int, + ) -> *mut *mut c_char; + + pub fn openpam_readword( + _f: *mut libc::FILE, + _lineno: *mut c_int, + _lenp: *mut usize, + ) -> *mut c_char; + + pub fn openpam_straddch( + _str: *mut *mut c_char, + _sizep: *mut usize, + _lenp: *mut usize, + ch: c_int, + ) -> c_int; + + pub fn openpam_set_feature(_feature: c_int, _onoff: c_int) -> c_int; + + pub fn openpam_get_feature(_feature: c_int, _onoff: *mut c_int) -> c_int; + + pub fn _openpam_log(_level: c_int, _func: *const c_char, _fmt: *const c_char, ...); + + /// A premade conversation function that talks to the TTY. + /// + /// ```no_run + /// # use std::ffi::CString; + /// # use std::ptr; + /// use libpam_sys::*; + /// # let service = CString::new("whatever").unwrap(); + /// # let user = CString::new("whatever").unwrap(); + /// let mut handle: *mut pam_handle = ptr::null_mut(); + /// let mut conv = pam_conv{ + /// conv: openpam_ttyconv, + /// appdata_ptr: ptr::null_mut(), + /// }; + /// let result = unsafe { pam_start( + /// service.as_ptr(), user.as_ptr(), &mut conv, &mut handle + /// ) }; + /// ``` + pub fn openpam_ttyconv( + n: c_int, + _msg: *const *const pam_message, + _resp: *mut *mut pam_response, + _data: *mut AppData, + ) -> c_int; + + pub static mut openpam_ttyconv_timeout: c_int; + + /// A null conversation function. + /// + /// ```no_run + /// # use std::ffi::CString; + /// # use std::ptr; + /// use libpam_sys::*; + /// # let service = CString::new("whatever").unwrap(); + /// # let user = CString::new("whatever").unwrap(); + /// let mut handle: *mut pam_handle = ptr::null_mut(); + /// let mut conv = pam_conv{ + /// conv: openpam_nullconv, + /// appdata_ptr: ptr::null_mut(), + /// }; + /// let result = unsafe { pam_start( + /// service.as_ptr(), user.as_ptr(), &mut conv, &mut handle + /// ) }; + /// ``` + pub fn openpam_nullconv( + n: c_int, + _msg: *const *const pam_message, + _resp: *mut *mut pam_response, + _data: *mut AppData, + ) -> c_int; +}
--- a/libpam-sys/src/pam_impl.rs Thu Jul 03 11:14:49 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,121 +0,0 @@ -/// An enum that knows its own values. -macro_rules! self_aware_enum { - ( - $(#[$enumeta:meta])* - $viz:vis enum $name:ident { - $( - $(#[$itemeta:meta])* - $item:ident, - )* - } - ) => { - $(#[$enumeta])* - $viz enum $name { - $( - $(#[$itemeta])* - $item, - )* - } - - // The implementations in this block are private for now - // to avoid putting a contract into the public API. - #[allow(dead_code)] - impl $name { - /// Iterator over the items in the enum. For internal use. - fn items() -> Vec<Self> { - vec![$(Self::$item),*] - } - - /// Attempts to parse the enum from the string. For internal use. - fn try_from(value: &str) -> Result<Self, String> { - match value { - $(stringify!($item) => Ok(Self::$item),)* - _ => Err(value.into()), - } - } - } - }; - } - -self_aware_enum! { - /// The PAM implementations supported by `libpam-sys`. - #[derive(Clone, Copy, Debug, PartialEq, Eq)] - #[cfg_attr(pam_impl, non_exhaustive)] - pub enum PamImpl { - /// [Linux-PAM] is provided by most Linux distributions. - /// - /// [Linux-PAM]: https://github.com/linux-pam/linux-pam - LinuxPam, - /// [OpenPAM] is used by most BSDs, including Mac OS X. - /// - /// [OpenPAM]: https://git.des.dev/OpenPAM/OpenPAM - OpenPam, - /// Illumos and Solaris use a derivative of [Sun's implementation][sun]. - /// - /// [sun]: https://code.illumos.org/plugins/gitiles/illumos-gate/+/refs/heads/master/usr/src/lib/libpam - Sun, - /// Only the functionality and constants in [the PAM spec]. - /// - /// [the PAM spec]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm - XSso, - } -} - -#[cfg(pam_impl)] -include!(concat!(env!("OUT_DIR"), "/pam_impl_const.rs")); - -/// Generates `cargo` directives for build scripts to enable `cfg(pam_impl)`. -/// -/// Print this in your `build.rs` script to be able to use the custom `pam_impl` -/// configuration directive. -/// -/// ``` -/// // build.rs -/// use libpam_sys::pam_impl; -/// fn main() { -/// println!("{}", pam_impl::enable_pam_impl_cfg()) -/// -/// // Whatever other stuff you do in your build.rs. -/// } -/// ``` -/// -/// This will set the current `pam_impl` as well as registering all known -/// PAM implementations with `rustc-check-cfg` so you can easily use them: -/// -/// ``` -/// #[cfg(pam_impl = "OpenPam")] -/// fn openpam_specific_func(handle: *const libpam_sys::pam_handle) { -/// let environ = libpam_sys::pam_getenvlist(handle); -/// // ... -/// libpam_sys::openpam_free_envlist() -/// } -/// -/// // This will give you a warning since "UnknownImpl" is not in the cfg. -/// #[cfg(not(pam_impl = "UnknownImpl"))] -/// fn do_something() { -/// // ... -/// } -/// ``` -#[cfg(pam_impl)] -pub fn enable_pam_impl_cfg() -> String { - format!("{}\n{}", checkcfg(), implcfg(PamImpl::CURRENT_STR)) -} - -/// Generates the `cargo:rustc-check-cfg` directive to print in build scripts. -fn checkcfg() -> String { - let impls: Vec<_> = PamImpl::items() - .into_iter() - .map(|i| format!(r#""{i:?}""#)) - .collect(); - format!( - "\ - cargo:rustc-check-cfg=cfg(pam_impl)\n\ - cargo:rustc-check-cfg=cfg(pam_impl, values({impls}))\ - ", - impls = impls.join(",") - ) -} - -fn implcfg(name: &str) -> String { - format!("cargo:rustc-cfg=pam_impl\ncargo:rustc-cfg=pam_impl={name:?}") -}
--- a/src/constants.rs Thu Jul 03 11:14:49 2025 -0400 +++ b/src/constants.rs Thu Jul 03 14:28:04 2025 -0400 @@ -18,8 +18,8 @@ /// **The values of these constants are deliberately selected _not_ to match /// any PAM implementations. Applications should always use the symbolic value /// and not a magic number.** -mod pam_ffi { - pub use libpam_sys::*; +mod pam_constants { + pub use libpam_sys_helpers::constants::*; macro_rules! define { ($(#[$attr:meta])* $($name:ident = $value:expr;)+) => { @@ -34,24 +34,21 @@ define!( /// A fictitious constant for testing purposes. - #[cfg(not(feature = "link"))] - #[cfg_pam_impl(not("OpenPam"))] + #[cfg(not(pam_impl = "OpenPam"))] PAM_BAD_CONSTANT = 513; PAM_BAD_FEATURE = 514; ); define!( /// A fictitious constant for testing purposes. - #[cfg(not(feature = "link"))] - #[cfg_pam_impl(not(any("LinuxPam", "OpenPam")))] + #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))] PAM_BAD_ITEM = 515; PAM_MODULE_UNKNOWN = 516; ); define!( /// A fictitious constant for testing purposes. - #[cfg(not(feature = "link"))] - #[cfg_pam_impl(not("LinuxPam"))] + #[cfg(not(pam_impl = "LinuxPam"))] PAM_CONV_AGAIN = 517; PAM_INCOMPLETE = 518; ); @@ -66,24 +63,24 @@ #[repr(transparent)] pub struct Flags: c_int { /// The module should not generate any messages. - const SILENT = libpam_sys::PAM_SILENT; + const SILENT = pam_constants::PAM_SILENT; /// The module should return [ErrorCode::AuthError] /// if the user has an empty authentication token /// rather than immediately accepting them. - const DISALLOW_NULL_AUTHTOK = libpam_sys::PAM_DISALLOW_NULL_AUTHTOK; + const DISALLOW_NULL_AUTHTOK = pam_constants::PAM_DISALLOW_NULL_AUTHTOK; // Flag used for `set_credentials`. /// Set user credentials for an authentication service. - const ESTABLISH_CREDENTIALS = libpam_sys::PAM_ESTABLISH_CRED; + const ESTABLISH_CREDENTIALS = pam_constants::PAM_ESTABLISH_CRED; /// Delete user credentials associated with /// an authentication service. - const DELETE_CREDENTIALS = libpam_sys::PAM_DELETE_CRED; + const DELETE_CREDENTIALS = pam_constants::PAM_DELETE_CRED; /// Reinitialize user credentials. - const REINITIALIZE_CREDENTIALS = libpam_sys::PAM_REINITIALIZE_CRED; + const REINITIALIZE_CREDENTIALS = pam_constants::PAM_REINITIALIZE_CRED; /// Extend the lifetime of user credentials. - const REFRESH_CREDENTIALS = libpam_sys::PAM_REFRESH_CRED; + const REFRESH_CREDENTIALS = pam_constants::PAM_REFRESH_CRED; // Flags used for password changing. @@ -92,7 +89,7 @@ /// the password service should update all passwords. /// /// This flag is only used by `change_authtok`. - const CHANGE_EXPIRED_AUTHTOK = libpam_sys::PAM_CHANGE_EXPIRED_AUTHTOK; + const CHANGE_EXPIRED_AUTHTOK = pam_constants::PAM_CHANGE_EXPIRED_AUTHTOK; /// This is a preliminary check for password changing. /// The password should not be changed. /// @@ -100,7 +97,7 @@ /// Applications may not use this flag. /// /// This flag is only used by `change_authtok`. - const PRELIMINARY_CHECK = libpam_sys::PAM_PRELIM_CHECK; + const PRELIMINARY_CHECK = pam_constants::PAM_PRELIM_CHECK; /// The password should actuallyPR be updated. /// This and [Self::PRELIMINARY_CHECK] are mutually exclusive. /// @@ -108,7 +105,7 @@ /// Applications may not use this flag. /// /// This flag is only used by `change_authtok`. - const UPDATE_AUTHTOK = libpam_sys::PAM_UPDATE_AUTHTOK; + const UPDATE_AUTHTOK = pam_constants::PAM_UPDATE_AUTHTOK; } } @@ -131,41 +128,41 @@ #[non_exhaustive] // C might give us anything! #[repr(i32)] pub enum ErrorCode { - OpenError = pam_ffi::PAM_OPEN_ERR, - SymbolError = pam_ffi::PAM_SYMBOL_ERR, - ServiceError = pam_ffi::PAM_SERVICE_ERR, - SystemError = pam_ffi::PAM_SYSTEM_ERR, - BufferError = pam_ffi::PAM_BUF_ERR, - PermissionDenied = pam_ffi::PAM_PERM_DENIED, - AuthenticationError = pam_ffi::PAM_AUTH_ERR, - CredentialsInsufficient = pam_ffi::PAM_CRED_INSUFFICIENT, - AuthInfoUnavailable = pam_ffi::PAM_AUTHINFO_UNAVAIL, - UserUnknown = pam_ffi::PAM_USER_UNKNOWN, - MaxTries = pam_ffi::PAM_MAXTRIES, - NewAuthTokRequired = pam_ffi::PAM_NEW_AUTHTOK_REQD, - AccountExpired = pam_ffi::PAM_ACCT_EXPIRED, - SessionError = pam_ffi::PAM_SESSION_ERR, - CredentialsUnavailable = pam_ffi::PAM_CRED_UNAVAIL, - CredentialsExpired = pam_ffi::PAM_CRED_EXPIRED, - CredentialsError = pam_ffi::PAM_CRED_ERR, - NoModuleData = pam_ffi::PAM_NO_MODULE_DATA, - ConversationError = pam_ffi::PAM_CONV_ERR, - AuthTokError = pam_ffi::PAM_AUTHTOK_ERR, - AuthTokRecoveryError = pam_ffi::PAM_AUTHTOK_RECOVERY_ERR, - AuthTokLockBusy = pam_ffi::PAM_AUTHTOK_LOCK_BUSY, - AuthTokDisableAging = pam_ffi::PAM_AUTHTOK_DISABLE_AGING, - TryAgain = pam_ffi::PAM_TRY_AGAIN, - Ignore = pam_ffi::PAM_IGNORE, - Abort = pam_ffi::PAM_ABORT, - AuthTokExpired = pam_ffi::PAM_AUTHTOK_EXPIRED, + OpenError = pam_constants::PAM_OPEN_ERR, + SymbolError = pam_constants::PAM_SYMBOL_ERR, + ServiceError = pam_constants::PAM_SERVICE_ERR, + SystemError = pam_constants::PAM_SYSTEM_ERR, + BufferError = pam_constants::PAM_BUF_ERR, + PermissionDenied = pam_constants::PAM_PERM_DENIED, + AuthenticationError = pam_constants::PAM_AUTH_ERR, + CredentialsInsufficient = pam_constants::PAM_CRED_INSUFFICIENT, + AuthInfoUnavailable = pam_constants::PAM_AUTHINFO_UNAVAIL, + UserUnknown = pam_constants::PAM_USER_UNKNOWN, + MaxTries = pam_constants::PAM_MAXTRIES, + NewAuthTokRequired = pam_constants::PAM_NEW_AUTHTOK_REQD, + AccountExpired = pam_constants::PAM_ACCT_EXPIRED, + SessionError = pam_constants::PAM_SESSION_ERR, + CredentialsUnavailable = pam_constants::PAM_CRED_UNAVAIL, + CredentialsExpired = pam_constants::PAM_CRED_EXPIRED, + CredentialsError = pam_constants::PAM_CRED_ERR, + NoModuleData = pam_constants::PAM_NO_MODULE_DATA, + ConversationError = pam_constants::PAM_CONV_ERR, + AuthTokError = pam_constants::PAM_AUTHTOK_ERR, + AuthTokRecoveryError = pam_constants::PAM_AUTHTOK_RECOVERY_ERR, + AuthTokLockBusy = pam_constants::PAM_AUTHTOK_LOCK_BUSY, + AuthTokDisableAging = pam_constants::PAM_AUTHTOK_DISABLE_AGING, + TryAgain = pam_constants::PAM_TRY_AGAIN, + Ignore = pam_constants::PAM_IGNORE, + Abort = pam_constants::PAM_ABORT, + AuthTokExpired = pam_constants::PAM_AUTHTOK_EXPIRED, #[cfg(feature = "basic-ext")] - ModuleUnknown = pam_ffi::PAM_MODULE_UNKNOWN, + ModuleUnknown = pam_constants::PAM_MODULE_UNKNOWN, #[cfg(feature = "basic-ext")] - BadItem = pam_ffi::PAM_BAD_ITEM, + BadItem = pam_constants::PAM_BAD_ITEM, #[cfg(feature = "linux-pam-ext")] - ConversationAgain = pam_ffi::PAM_CONV_AGAIN, + ConversationAgain = pam_constants::PAM_CONV_AGAIN, #[cfg(feature = "linux-pam-ext")] - Incomplete = pam_ffi::PAM_INCOMPLETE, + Incomplete = pam_constants::PAM_INCOMPLETE, } /// A PAM-specific Result type with an [ErrorCode] error. @@ -235,12 +232,12 @@ fn test_enums() { assert_eq!(Ok(()), ErrorCode::result_from(0)); assert_eq!( - pam_ffi::PAM_SESSION_ERR as i32, + pam_constants::PAM_SESSION_ERR as i32, ErrorCode::result_to_c::<()>(Err(ErrorCode::SessionError)) ); assert_eq!( Err(ErrorCode::Abort), - ErrorCode::result_from(pam_ffi::PAM_ABORT as i32) + ErrorCode::result_from(pam_constants::PAM_ABORT as i32) ); assert_eq!(Err(ErrorCode::SystemError), ErrorCode::result_from(423)); }
--- a/src/libpam/conversation.rs Thu Jul 03 11:14:49 2025 -0400 +++ b/src/libpam/conversation.rs Thu Jul 03 14:28:04 2025 -0400 @@ -6,7 +6,7 @@ use crate::libpam::question::Question; use crate::ErrorCode; use crate::Result; -use libpam_sys::helpers::PtrPtrVec; +use libpam_sys_helpers::memory::PtrPtrVec; use libpam_sys::AppData; use std::ffi::c_int; use std::iter;
--- a/src/logging.rs Thu Jul 03 11:14:49 2025 -0400 +++ b/src/logging.rs Thu Jul 03 14:28:04 2025 -0400 @@ -17,10 +17,12 @@ #[cfg(pam_impl = "OpenPam")] mod levels { - pub const ERROR: i32 = libpam_sys::PAM_LOG_ERROR; - pub const WARN: i32 = libpam_sys::PAM_LOG_NOTICE; - pub const INFO: i32 = libpam_sys::PAM_LOG_VERBOSE; - pub const DEBUG: i32 = libpam_sys::PAM_LOG_DEBUG; + use libpam_sys_helpers::constants; + + pub const ERROR: i32 = constants::PAM_LOG_ERROR; + pub const WARN: i32 = constants::PAM_LOG_NOTICE; + pub const INFO: i32 = constants::PAM_LOG_VERBOSE; + pub const DEBUG: i32 = constants::PAM_LOG_DEBUG; } #[cfg(not(pam_impl = "OpenPam"))] mod levels {
--- a/testharness/Cargo.toml Thu Jul 03 11:14:49 2025 -0400 +++ b/testharness/Cargo.toml Thu Jul 03 14:28:04 2025 -0400 @@ -2,6 +2,7 @@ name = "nonstick-testharness" description = "Automatic test harness for the Nonstick PAM library." publish = false +license.workspace = true version.workspace = true authors.workspace = true edition.workspace = true