# HG changeset patch # User Paul Fisher # Date 1751904703 14400 # Node ID 4b3a5095f68c37a0d214bedbc69a8085f1bd50ab # Parent 4d7333337569856664e9be6b796a47911012b3db Move libpam-sys helpers into their own library. - Renames libpam-sys-helpers to libpam-sys-consts. - Moves libpam-sys-helpers::helpers into libpam-sys-helpers, which moves them completely out of libpam-sys's dependency chain. - Moves the aliases from libpam-sys into libpam-sys::aliases. diff -r 4d7333337569 -r 4b3a5095f68c Cargo.lock --- a/Cargo.lock Sun Jul 06 19:23:02 2025 -0400 +++ b/Cargo.lock Mon Jul 07 12:11:43 2025 -0400 @@ -9,12 +9,6 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -71,14 +65,22 @@ version = "0.1.0" dependencies = [ "libc", + "libpam-sys-consts", "libpam-sys-helpers", ] [[package]] +name = "libpam-sys-consts" +version = "0.1.0" +dependencies = [ + "libc", +] + +[[package]] name = "libpam-sys-helpers" version = "0.1.0" dependencies = [ - "libc", + "libpam-sys-consts", ] [[package]] @@ -88,23 +90,14 @@ checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] name = "nonstick" version = "0.0.8-alpha0" dependencies = [ "bitflags", "libc", "libpam-sys", + "libpam-sys-consts", "libpam-sys-helpers", - "memoffset", "num_enum", ] diff -r 4d7333337569 -r 4b3a5095f68c Cargo.toml --- a/Cargo.toml Sun Jul 06 19:23:02 2025 -0400 +++ b/Cargo.toml Mon Jul 07 12:11:43 2025 -0400 @@ -40,10 +40,10 @@ [dependencies] bitflags = "2.9.0" libc = "0.2" -memoffset = "0.9.1" num_enum = "0.7.3" libpam-sys = { optional = true, path = "libpam-sys" } libpam-sys-helpers = { path = "libpam-sys/libpam-sys-helpers" } +libpam-sys-consts = { path = "libpam-sys/libpam-sys-consts" } [build-dependencies] -libpam-sys-helpers = { path = "libpam-sys/libpam-sys-helpers" } +libpam-sys-consts = { path = "libpam-sys/libpam-sys-consts" } diff -r 4d7333337569 -r 4b3a5095f68c build.rs --- a/build.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/build.rs Mon Jul 07 12:11:43 2025 -0400 @@ -1,4 +1,4 @@ -use libpam_sys_helpers::pam_impl; +use libpam_sys_consts::pam_impl; fn main() { pam_impl::enable_pam_impl_cfg() diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/Cargo.lock --- a/libpam-sys/Cargo.lock Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/Cargo.lock Mon Jul 07 12:11:43 2025 -0400 @@ -219,14 +219,22 @@ version = "0.1.0" dependencies = [ "libc", + "libpam-sys-consts", "libpam-sys-helpers", ] [[package]] +name = "libpam-sys-consts" +version = "0.1.0" +dependencies = [ + "libc", +] + +[[package]] name = "libpam-sys-helpers" version = "0.1.0" dependencies = [ - "libc", + "libpam-sys-consts", ] [[package]] @@ -237,7 +245,7 @@ "ctest", "libc", "libpam-sys", - "libpam-sys-helpers", + "libpam-sys-consts", "proc-macro2", "quote", "syn", diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/Cargo.toml --- a/libpam-sys/Cargo.toml Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/Cargo.toml Mon Jul 07 12:11:43 2025 -0400 @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["libpam-sys-helpers", "libpam-sys-test"] +members = ["libpam-sys-consts", "libpam-sys-helpers", "libpam-sys-test"] [workspace.package] version = "0.1.0" @@ -23,8 +23,13 @@ [dependencies] libc = "0.2" +libpam-sys-consts = { path = "libpam-sys-consts" } + +[target.'cfg(doc)'.dependencies] +libpam-sys-helpers = { path = "libpam-sys-helpers" } + +[dev-dependencies] libpam-sys-helpers = { path = "libpam-sys-helpers" } [build-dependencies] -libc = "0.2" -libpam-sys-helpers = { path = "libpam-sys-helpers" } +libpam-sys-consts = { path = "libpam-sys-consts" } diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/README.md --- a/libpam-sys/README.md Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/README.md Mon Jul 07 12:11:43 2025 -0400 @@ -11,7 +11,7 @@ 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). +All build-time configuration is performed by the build script of the [`libpam-sys-consts` crate](https://crates.io/crates/libpam-sys-consts). Each implementation exports all the functionality available in its respective PAM library. `XSso` exports only what is in the [X/SSO specification][xsso]. diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/build.rs --- a/libpam-sys/build.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/build.rs Mon Jul 07 12:11:43 2025 -0400 @@ -1,6 +1,4 @@ -#![allow(unexpected_cfgs)] - -use libpam_sys_helpers::pam_impl; +use libpam_sys_consts::pam_impl; fn main() { println!("cargo:rustc-link-lib=pam"); diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-consts/Cargo.toml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-consts/Cargo.toml Mon Jul 07 12:11:43 2025 -0400 @@ -0,0 +1,15 @@ +[package] +name = "libpam-sys-consts" +description = "Constants exported by libpam and implementation detection." +version.workspace = true +authors.workspace = true +repository.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[dependencies] +libc = "0.2" + +[build-dependencies] +libc = "0.2" \ No newline at end of file diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-consts/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-consts/README.md Mon Jul 07 12:11:43 2025 -0400 @@ -0,0 +1,60 @@ +# `libpam-sys-consts`: Constants for LibPAM + +This crate does two primary things: + +- Detects which implementation of LibPAM the current machine uses (as part of the build script), and exports that information to downstream crates. +- Exports the constants specific to that version of LibPAM. + These are located in the `constants` module. + +## Handling different PAM implmementations + +Different PAM implementations have different constants and some different behaviors. +If you need to change your library's 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, more different 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 diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-consts/build.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-consts/build.rs Mon Jul 07 12:11:43 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); + +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 { + 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()); + } + } +} diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-consts/src/constants.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-consts/src/constants.rs Mon Jul 07 12:11:43 2025 -0400 @@ -0,0 +1,317 @@ +//! 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)] + +/// 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; + + #[derive(Copy, Clone, Debug, PartialEq, Eq)] + #[repr(i32)] + pub enum pam_modutil_redirect_fd { + PAM_MODUTIL_IGNORE_FD, + PAM_MODUTIL_PIPE_FD, + PAM_MODUTIL_NULL_FD, + } + + impl From for i32 { + fn from(value: pam_modutil_redirect_fd) -> Self { + value as Self + } + } + + impl TryFrom for pam_modutil_redirect_fd { + type Error = i32; + fn try_from(value: i32) -> Result { + match value { + 0..=2 => Ok(unsafe { *(&value as *const i32).cast() }), + other => Err(other), + } + } + } + + 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; + + define!( + /// A flag for `__pam_get_authtok`. + PAM_PROMPT = 1; + PAM_HANDLE = 2; + ); +} diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-consts/src/lib.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-consts/src/lib.rs Mon Jul 07 12:11:43 2025 -0400 @@ -0,0 +1,44 @@ +pub mod constants; + +/// 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::*; diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-consts/src/pam_impl.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-consts/src/pam_impl.rs Mon Jul 07 12:11:43 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 { + vec![$(Self::$item),*] + } + + /// Attempts to parse the enum from the string. For internal use. + fn try_from(value: &str) -> Result { + 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_consts::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(",") + ) +} diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-helpers/Cargo.toml --- a/libpam-sys/libpam-sys-helpers/Cargo.toml Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/libpam-sys-helpers/Cargo.toml Mon Jul 07 12:11:43 2025 -0400 @@ -1,5 +1,6 @@ [package] name = "libpam-sys-helpers" +description = "Cross-platform helpers for libpam-sys." version.workspace = true authors.workspace = true repository.workspace = true @@ -8,8 +9,5 @@ license.workspace = true readme = "README.md" -[dependencies] -libc = "0.2" - [build-dependencies] -libc = "0.2" \ No newline at end of file +libpam-sys-consts = { path = "../libpam-sys-consts" } \ No newline at end of file diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-helpers/README.md --- a/libpam-sys/libpam-sys-helpers/README.md Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/libpam-sys-helpers/README.md Mon Jul 07 12:11:43 2025 -0400 @@ -1,57 +1,5 @@ -# `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; +# `libpam-sys-helpers`: Cross-platform helpers for `libpam-sys` -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 -} +This provides memory-management helpers for `libpam-sys`. -#[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 +This does not actually depend upon `libpam-sys`; it only uses `libpam-sys-consts`. \ No newline at end of file diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-helpers/build.rs --- a/libpam-sys/libpam-sys-helpers/build.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/libpam-sys-helpers/build.rs Mon Jul 07 12:11:43 2025 -0400 @@ -1,96 +1,6 @@ -#![allow(unexpected_cfgs)] - -use std::ffi::{c_void, CString}; -use std::ptr::NonNull; -use std::{env, fs}; - -include!("src/pam_impl.rs"); +use libpam_sys_consts::pam_impl; 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:?}) }} - " - ) + println!("cargo:rustc-link-lib=pam"); + pam_impl::enable_pam_impl_cfg(); } - -struct LibPam(NonNull); - -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 { - 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()); - } - } -} diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-helpers/src/constants.rs --- a/libpam-sys/libpam-sys-helpers/src/constants.rs Sun Jul 06 19:23:02 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,317 +0,0 @@ -//! 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)] - -/// 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; - - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - #[repr(i32)] - pub enum pam_modutil_redirect_fd { - PAM_MODUTIL_IGNORE_FD, - PAM_MODUTIL_PIPE_FD, - PAM_MODUTIL_NULL_FD, - } - - impl From for i32 { - fn from(value: pam_modutil_redirect_fd) -> Self { - value as Self - } - } - - impl TryFrom for pam_modutil_redirect_fd { - type Error = i32; - fn try_from(value: i32) -> Result { - match value { - 0..=2 => Ok(unsafe { *(&value as *const i32).cast() }), - other => Err(other), - } - } - } - - 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; - - define!( - /// A flag for `__pam_get_authtok`. - PAM_PROMPT = 1; - PAM_HANDLE = 2; - ); -} diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-helpers/src/lib.rs --- a/libpam-sys/libpam-sys-helpers/src/lib.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/libpam-sys-helpers/src/lib.rs Mon Jul 07 12:11:43 2025 -0400 @@ -1,51 +1,592 @@ -#![doc = include_str!("../README.md")] -//! -//! ## Current implementation -//! -//! This documentation was built based on the -#![doc = concat!("**", env!("LIBPAMSYS_IMPL"), "** implementation.")] +//! This package contains helpers to deal with memory management and +//! annoying type stuff in `libpam-sys` (and LibPAM in general). + +use std::error::Error; +use std::marker::{PhantomData, PhantomPinned}; +use std::mem::ManuallyDrop; +use std::ptr::NonNull; +use std::{any, fmt, mem, ptr, slice}; +// Type aliases: -pub mod constants; -pub mod memory; +// Memory management -/// Information about the PAM implementation you're using right now. +/// A pointer-to-pointer-to-message container for PAM's conversation callback. /// -/// 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. +/// 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: /// -/// ## Always available +/// ```text +/// points to ┌────────────┐ ╔═ Message[] ═╗ +/// messages ┄┄┄┄┄┄┄┄┄┄> │ *messages ┄┼┄┄┄┄┄> ║ style ║ +/// └────────────┘ ║ data ┄┄┄┄┄┄┄╫┄┄> ... +/// ╟─────────────╢ +/// ║ style ║ +/// ║ data ┄┄┄┄┄┄┄╫┄┄> ... +/// ╟─────────────╢ +/// ║ ... ║ +/// ``` /// -/// [`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 +/// whereas Linux-PAM uses an `**argv`-style pointer-to-array-of-pointers: /// -/// Use [`enable_pam_impl_cfg`] in your `build.rs` to generate custom `#[cfg]`s -/// for conditional compilation based on PAM implementation. +/// ```text +/// points to ┌──────────────┐ ╔═ Message ═╗ +/// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ +/// │ messages[1] ┄┼┄┄┄╮ ║ data ┄┄┄┄┄╫┄┄> ... +/// │ ... │ ┆ ╚═══════════╝ +/// ┆ +/// ┆ ╔═ Message ═╗ +/// ╰┄┄> ║ style ║ +/// ║ data ┄┄┄┄┄╫┄┄> ... +/// ╚═══════════╝ +/// ``` /// -/// This will set the current `pam_impl` as well as registering all known -/// PAM implementations with `rustc-check-cfg` to get cfg-checking. +/// 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: /// -/// The names that appear in the `cfg` variables are the same as the values -/// in the [`PamImpl`] enum. +/// ```text +/// points to ┌──────────────┐ ╔═ Message[] ═╗ +/// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ +/// │ messages[1] ┄┼┄┄╮ ║ data ┄┄┄┄┄┄┄╫┄┄> ... +/// │ ... │ ┆ ╟─────────────╢ +/// ╰┄> ║ style ║ +/// ║ data ┄┄┄┄┄┄┄╫┄┄> ... +/// ╟─────────────╢ +/// ║ ... ║ /// /// ``` -/// #[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() -/// } +#[derive(Debug)] +pub struct PtrPtrVec { + data: Vec, + 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 Send for PtrPtrVec where Vec: Send {} +unsafe impl Sync for PtrPtrVec where Vec: Sync {} + +impl PtrPtrVec { + /// Takes ownership of the given Vec and creates a vec of pointers to it. + pub fn new(data: Vec) -> Self { + let start = data.as_ptr(); + // We do this slightly tricky little dance to satisfy Miri: + // + // A pointer extracted from a reference can only legally access + // that reference's memory. This means that if we say: + // pointers[0] = &data[0] as *const T; + // we can't traverse through pointers[0] to reach data[1], + // we can only use pointers[1]. + // + // However, if we use the start-of-vec pointer from the `data` vector, + // its "provenance"* is valid for the entire array (even if the address + // of the pointer is the same). This avoids some behavior which is + // technically undefined. While the CPU sees no difference between + // those two pointers, the compiler is allowed to make optimizations + // based on that provenance (even if, in this case, it isn't likely + // to do so). + // + // data.as_ptr() points here, and is valid for the whole Vec. + // ┃ + // ┠─────────────────╮ + // ┌─────┬─────┬─────┐ + // data │ [0] │ [1] │ [2] │ + // └─────┴─────┴─────┘ + // ┠─────╯ ┊ + // ┃ ┊ ┊ + // (&data[0] as *const T) points to the same place, but is valid + // only for that 0th element. + // ┊ ┊ + // ┠─────╯ + // ┃ + // (&data[1] as *const T) points here, and is only valid + // for that element. + // + // We only have to do this for pointers[0] because only that pointer + // is used for accessing elements other than data[0] (in XSSO). + // + // * "provenance" is kind of like if every pointer in your program + // remembered where it came from and, based on that, it had an implied + // memory range it was valid for, separate from its address. + // https://doc.rust-lang.org/std/ptr/#provenance + // (It took a long time for me to understand this.) + let mut pointers = Vec::with_capacity(data.len()); + // Ensure the 0th pointer has provenance from the entire vec + // (even though it's numerically identical to &data[0] as *const T). + pointers.push(start); + // The 1st and everything thereafter only need to have the provenance + // of their own memory. + pointers.extend(data[1..].iter().map(|r| r as *const T)); + Self { data, pointers } + } + + /// Gives you back your Vec. + pub fn into_inner(self) -> Vec { + self.data + } + + /// Gets a pointer-to-pointer suitable for passing into the Conversation. + pub fn as_ptr(&self) -> *const *const Dest { + Self::assert_size::(); + 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 + where + T: 'a, + { + Self::assert_size::(); + 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 + where + T: 'a, + { + Self::assert_size::(); + 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 + 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() { + assert_eq!( + mem::size_of::(), + mem::size_of::(), + "type {t} is not the size of {that}", + t = any::type_name::(), + that = any::type_name::(), + ); + } +} + +/// 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 232 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. /// -/// // This will give you a warning since "UnknownImpl" is not in the cfg. -/// #[cfg(not(pam_impl = "UnknownImpl"))] -/// fn do_something() { -/// // ... -/// } -/// ``` +/// For an implementation example, see the implementation of this trait +/// for [`Vec`]. +#[allow(clippy::wrong_self_convention)] +pub trait Buffer { + /// Allocates a buffer of `len` elements, filled with the default. + fn allocate(len: usize) -> Self; + + fn as_ptr(this: &Self) -> *const u8; + + /// 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(this: &mut Self, len: usize) -> &mut [u8]; + + /// Consumes this ownership and returns a pointer to the start of the arena. + fn into_ptr(this: Self) -> NonNull; + + /// "Adopts" the memory at the given pointer, taking it under management. + /// + /// Running the operation: + /// + /// ``` + /// # use libpam_sys_helpers::Buffer; + /// # fn test(bytes: usize) { + /// let owner = OwnerType::allocate(bytes); + /// let ptr = OwnerType::into_ptr(owner); + /// 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, bytes: usize) -> Self; +} + +impl Buffer for Vec { + fn allocate(bytes: usize) -> Self { + vec![0; bytes] + } + + fn as_ptr(this: &Self) -> *const u8 { + Vec::as_ptr(this) + } + + unsafe fn as_mut_slice(this: &mut Self, bytes: usize) -> &mut [u8] { + &mut this[..bytes] + } + + fn into_ptr(this: Self) -> NonNull { + let mut me = ManuallyDrop::new(this); + // SAFETY: a Vec is guaranteed to have a nonzero pointer. + unsafe { NonNull::new_unchecked(me.as_mut_ptr()) } + } + + unsafe fn from_ptr(ptr: NonNull, 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 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, +} + +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: &[u8], data_type: 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 unsafe fn total_bytes(this: *const Self) -> usize { + let header = this.as_ref().unwrap_unchecked(); + u32::from_be_bytes(header.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] { + slice::from_raw_parts(ptr.cast(), Self::total_bytes(ptr).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) -> (&'a [u8], u8) { + let header: &Self = ptr.as_ref().unwrap_unchecked(); + (&Self::buffer_of(ptr)[5..], header.data_type) + } + + /// Zeroes out the data of this payload. + /// + /// # Safety + /// + /// - The pointer must point to a valid `BinaryPayload`. + /// - The binary payload must not be used in the future, + /// since its length metadata is gone and so its buffer is unknown. + pub unsafe fn zero(ptr: *mut Self) { + let size = Self::total_bytes(ptr); + let ptr: *mut u8 = ptr.cast(); + for x in 0..size { + ptr::write_volatile(ptr.byte_add(x), mem::zeroed()) + } + } +} + +/// A binary message owned by some storage. /// -/// 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::*; +/// This is an owned, memory-managed version of [`BinaryPayload`]. +/// The `O` type manages the memory where the payload lives. +/// [`Vec`] 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); + +impl OwnedBinaryPayload { + /// 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: &[u8], type_: u8) -> Result { + 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 { Buffer::as_mut_slice(&mut buf, total_len) }, + data, + type_, + ); + 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(Buffer::as_ptr(&self.0).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 { + Buffer::as_ptr(&self.0).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 { + Buffer::into_ptr(self.0).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::>::from_ptr` is not allowed. + pub unsafe fn from_ptr(ptr: NonNull) -> Self { + Self(O::from_ptr( + ptr.cast(), + BinaryPayload::total_bytes(ptr.as_ptr()), + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ptr; + + type VecPayload = OwnedBinaryPayload>; + + #[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!((&[0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..], 0xff), 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 (data, typ) = ( + &[0, 1, 1, 8, 9, 9, 9, 8, 8, 1, 9, 9, 9, 1, 1, 9, 7, 2, 5, 3][..], + 112, + ); + let payload = VecPayload::new(data, typ).unwrap(); + assert_eq!((data, typ), payload.contents()); + let ptr = payload.into_ptr(); + let payload = unsafe { VecPayload::from_ptr(ptr) }; + assert_eq!((data, typ), 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(&data, 5).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::(); + } + + #[allow(deprecated)] + #[test] + #[should_panic] + fn test_iter_xsso_wrong_size() { + unsafe { + let _ = PtrPtrVec::::iter_over_xsso::(ptr::null(), 1); + } + } + + #[allow(deprecated)] + #[test] + #[should_panic] + fn test_iter_linux_wrong_size() { + unsafe { + let _ = PtrPtrVec::::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() { + // These boxes are larger than a single pointer because we want to + // make sure they're not accidentally allocated adjacently + // in such a way that it's compatible with X/SSO. + // + // a pointer to (&str, i32) can be treated as a pointer to (&str). + #[repr(C)] + struct pair(&'static str, i32); + let boxes = vec![ + Box::new(pair("a", 1)), + Box::new(pair("b", 2)), + Box::new(pair("c", 3)), + Box::new(pair("D", 4)), + ]; + let ptr: *const *const &str = boxes.as_ptr().cast(); + let got: Vec<&str> = unsafe { PtrPtrVec::iter_over_linux(ptr, 4) } + .cloned() + .collect(); + assert_eq!(vec!["a", "b", "c", "D"], got); + + // On the other hand, we explicitly want these to be adjacent. + let nums = [-1i8, 2, 3]; + let ptr = nums.as_ptr(); + let got: Vec = unsafe { PtrPtrVec::iter_over_xsso(&ptr, 3) } + .cloned() + .collect(); + assert_eq!(vec![255, 2, 3], got); + } +} diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-helpers/src/memory.rs --- a/libpam-sys/libpam-sys-helpers/src/memory.rs Sun Jul 06 19:23:02 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,588 +0,0 @@ -//! 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, ptr, 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 { - data: Vec, - 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 Send for PtrPtrVec where Vec: Send {} -unsafe impl Sync for PtrPtrVec where Vec: Sync {} - -impl PtrPtrVec { - /// Takes ownership of the given Vec and creates a vec of pointers to it. - pub fn new(data: Vec) -> Self { - let start = data.as_ptr(); - // We do this slightly tricky little dance to satisfy Miri: - // - // A pointer extracted from a reference can only legally access - // that reference's memory. This means that if we say: - // pointers[0] = &data[0] as *const T; - // we can't traverse through pointers[0] to reach data[1], - // we can only use pointers[1]. - // - // However, if we use the start-of-vec pointer from the `data` vector, - // its "provenance"* is valid for the entire array (even if the address - // of the pointer is the same). This avoids some behavior which is - // technically undefined. While the CPU sees no difference between - // those two pointers, the compiler is allowed to make optimizations - // based on that provenance (even if, in this case, it isn't likely - // to do so). - // - // data.as_ptr() points here, and is valid for the whole Vec. - // ┃ - // ┠─────────────────╮ - // ┌─────┬─────┬─────┐ - // data │ [0] │ [1] │ [2] │ - // └─────┴─────┴─────┘ - // ┠─────╯ ┊ - // ┃ ┊ ┊ - // (&data[0] as *const T) points to the same place, but is valid - // only for that 0th element. - // ┊ ┊ - // ┠─────╯ - // ┃ - // (&data[1] as *const T) points here, and is only valid - // for that element. - // - // We only have to do this for pointers[0] because only that pointer - // is used for accessing elements other than data[0] (in XSSO). - // - // * "provenance" is kind of like if every pointer in your program - // remembered where it came from and, based on that, it had an implied - // memory range it was valid for, separate from its address. - // https://doc.rust-lang.org/std/ptr/#provenance - // (It took a long time for me to understand this.) - let mut pointers = Vec::with_capacity(data.len()); - // Ensure the 0th pointer has provenance from the entire vec - // (even though it's numerically identical to &data[0] as *const T). - pointers.push(start); - // The 1st and everything thereafter only need to have the provenance - // of their own memory. - pointers.extend(data[1..].iter().map(|r| r as *const T)); - Self { data, pointers } - } - - /// Gives you back your Vec. - pub fn into_inner(self) -> Vec { - self.data - } - - /// Gets a pointer-to-pointer suitable for passing into the Conversation. - pub fn as_ptr(&self) -> *const *const Dest { - Self::assert_size::(); - 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 - where - T: 'a, - { - Self::assert_size::(); - 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 - where - T: 'a, - { - Self::assert_size::(); - 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 - 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() { - assert_eq!( - mem::size_of::(), - mem::size_of::(), - "type {t} is not the size of {that}", - t = any::type_name::(), - that = any::type_name::(), - ); - } -} - -/// 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 232 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`]. -#[allow(clippy::wrong_self_convention)] -pub trait Buffer { - /// Allocates a buffer of `len` elements, filled with the default. - fn allocate(len: usize) -> Self; - - fn as_ptr(this: &Self) -> *const u8; - - /// 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(this: &mut Self, len: usize) -> &mut [u8]; - - /// Consumes this ownership and returns a pointer to the start of the arena. - fn into_ptr(this: Self) -> NonNull; - - /// "Adopts" the memory at the given pointer, taking it under management. - /// - /// Running the operation: - /// - /// ``` - /// # use libpam_sys_helpers::memory::Buffer; - /// # fn test(bytes: usize) { - /// let owner = OwnerType::allocate(bytes); - /// let ptr = OwnerType::into_ptr(owner); - /// 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, bytes: usize) -> Self; -} - -impl Buffer for Vec { - fn allocate(bytes: usize) -> Self { - vec![0; bytes] - } - - fn as_ptr(this: &Self) -> *const u8 { - Vec::as_ptr(this) - } - - unsafe fn as_mut_slice(this: &mut Self, bytes: usize) -> &mut [u8] { - &mut this[..bytes] - } - - fn into_ptr(this: Self) -> NonNull { - let mut me = ManuallyDrop::new(this); - // SAFETY: a Vec is guaranteed to have a nonzero pointer. - unsafe { NonNull::new_unchecked(me.as_mut_ptr()) } - } - - unsafe fn from_ptr(ptr: NonNull, 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 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, -} - -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: &[u8], data_type: 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 unsafe fn total_bytes(this: *const Self) -> usize { - let header = this.as_ref().unwrap_unchecked(); - u32::from_be_bytes(header.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] { - slice::from_raw_parts(ptr.cast(), Self::total_bytes(ptr).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) -> (&'a [u8], u8) { - let header: &Self = ptr.as_ref().unwrap_unchecked(); - (&Self::buffer_of(ptr)[5..], header.data_type) - } - - /// Zeroes out the data of this payload. - /// - /// # Safety - /// - /// - The pointer must point to a valid `BinaryPayload`. - /// - The binary payload must not be used in the future, - /// since its length metadata is gone and so its buffer is unknown. - pub unsafe fn zero(ptr: *mut Self) { - let size = Self::total_bytes(ptr); - let ptr: *mut u8 = ptr.cast(); - for x in 0..size { - ptr::write_volatile(ptr.byte_add(x), mem::zeroed()) - } - } -} - -/// 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`] 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); - -impl OwnedBinaryPayload { - /// 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: &[u8], type_: u8) -> Result { - 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 { Buffer::as_mut_slice(&mut buf, total_len) }, - data, - type_, - ); - 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(Buffer::as_ptr(&self.0).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 { - Buffer::as_ptr(&self.0).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 { - Buffer::into_ptr(self.0).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::>::from_ptr` is not allowed. - pub unsafe fn from_ptr(ptr: NonNull) -> Self { - Self(O::from_ptr( - ptr.cast(), - BinaryPayload::total_bytes(ptr.as_ptr()), - )) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::ptr; - - type VecPayload = OwnedBinaryPayload>; - - #[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!((&[0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..], 0xff), 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 (data, typ) = ( - &[0, 1, 1, 8, 9, 9, 9, 8, 8, 1, 9, 9, 9, 1, 1, 9, 7, 2, 5, 3][..], - 112, - ); - let payload = VecPayload::new(data, typ).unwrap(); - assert_eq!((data, typ), payload.contents()); - let ptr = payload.into_ptr(); - let payload = unsafe { VecPayload::from_ptr(ptr) }; - assert_eq!((data, typ), 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(&data, 5).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::(); - } - - #[allow(deprecated)] - #[test] - #[should_panic] - fn test_iter_xsso_wrong_size() { - unsafe { - let _ = PtrPtrVec::::iter_over_xsso::(ptr::null(), 1); - } - } - - #[allow(deprecated)] - #[test] - #[should_panic] - fn test_iter_linux_wrong_size() { - unsafe { - let _ = PtrPtrVec::::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() { - // These boxes are larger than a single pointer because we want to - // make sure they're not accidentally allocated adjacently - // in such a way that it's compatible with X/SSO. - // - // a pointer to (&str, i32) can be treated as a pointer to (&str). - #[repr(C)] - struct pair(&'static str, i32); - let boxes = vec![ - Box::new(pair("a", 1)), - Box::new(pair("b", 2)), - Box::new(pair("c", 3)), - Box::new(pair("D", 4)), - ]; - let ptr: *const *const &str = boxes.as_ptr().cast(); - let got: Vec<&str> = unsafe { PtrPtrVec::iter_over_linux(ptr, 4) } - .cloned() - .collect(); - assert_eq!(vec!["a", "b", "c", "D"], got); - - // On the other hand, we explicitly want these to be adjacent. - let nums = [-1i8, 2, 3]; - let ptr = nums.as_ptr(); - let got: Vec = unsafe { PtrPtrVec::iter_over_xsso(&ptr, 3) } - .cloned() - .collect(); - assert_eq!(vec![255, 2, 3], got); - } -} diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-helpers/src/pam_impl.rs --- a/libpam-sys/libpam-sys-helpers/src/pam_impl.rs Sun Jul 06 19:23:02 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -// 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 { - vec![$(Self::$item),*] - } - - /// Attempts to parse the enum from the string. For internal use. - fn try_from(value: &str) -> Result { - 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(",") - ) -} diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-test/Cargo.toml --- a/libpam-sys/libpam-sys-test/Cargo.toml Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/libpam-sys-test/Cargo.toml Mon Jul 07 12:11:43 2025 -0400 @@ -14,7 +14,7 @@ bindgen = "0.72.0" ctest = "0.4.11" libpam-sys = { path = ".." } -libpam-sys-helpers = { path = "../libpam-sys-helpers" } +libpam-sys-consts = { path = "../libpam-sys-consts" } proc-macro2 = "1.0.95" quote = "1.0.40" syn = { version = "2.0.104", features = ["full"] } diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/libpam-sys-test/build.rs --- a/libpam-sys/libpam-sys-test/build.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/libpam-sys-test/build.rs Mon Jul 07 12:11:43 2025 -0400 @@ -1,6 +1,6 @@ use bindgen::MacroTypeVariation; -use libpam_sys_helpers::pam_impl::PamImpl; -use libpam_sys_helpers::{pam_impl, pam_impl_name}; +use libpam_sys_consts::pam_impl::PamImpl; +use libpam_sys_consts::{pam_impl, pam_impl_name}; use proc_macro2::{Group, Ident, TokenStream, TokenTree}; use quote::{format_ident, ToTokens}; use std::path::Path; @@ -184,12 +184,15 @@ TokenTree::Group(Group::new(g.delimiter(), deconstify(g.stream(), mut_token))) .into_token_stream() } + // Remove all 'consts' from the file and replace them with 'mut'. TokenTree::Ident(id) if id == "const" => mut_token.into_token_stream(), other => other.into_token_stream(), } })) } +enum ItemOr {} + fn test_file(name: impl AsRef) -> String { format!("{}/{}", env::var("OUT_DIR").unwrap(), name.as_ref()) } diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/src/_doc.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/src/_doc.rs Mon Jul 07 12:11:43 2025 -0400 @@ -0,0 +1,1 @@ +pub(crate) use cfg_pam_impl; diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/src/aliases.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/src/aliases.rs Mon Jul 07 12:11:43 2025 -0400 @@ -0,0 +1,48 @@ +//! Convenience aliases for complex types in PAM. + +use super::{pam_conv, pam_handle, pam_message, pam_response, pam_set_data}; +use std::ffi::{c_int, c_void}; + +/// The type of [`pam_conv::conv`]. +/// +/// The exact format of `messages` varies between Linux-PAM and other +/// implementations. See `libpam_sys_helpers::PtrPtrVec` for details +/// (and a workaround). +/// +/// ```no_run +/// use libpam_sys::pam_conv; +/// use libpam_sys::aliases::ConversationCallback; +/// 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 c_void, +) -> c_int; + +/// Alias for the callback to [`pam_set_data`]. +/// +/// ```no_run +/// # use std::ffi::CString; +/// use libpam_sys::pam_set_data; +/// use libpam_sys::aliases::CleanupCallback; +/// # 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); diff -r 4d7333337569 -r 4b3a5095f68c libpam-sys/src/lib.rs --- a/libpam-sys/src/lib.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/libpam-sys/src/lib.rs Mon Jul 07 12:11:43 2025 -0400 @@ -1,50 +1,40 @@ -//! `libpam-sys` provides low-level access to `libpam`. +//! `libpam-sys` provides low-level access to LibPAM. //! -//! Everything in here is directly as exported from the `libpam` library or -//! its header files, with limited exceptions: +//! Everything in here is directly as exported from the LibPAM library or +//! its header files, with two exceptions: //! -//! - 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. +//! - The [`pam_impl`] submodule (and the associated [`pam_impl_name!`] macro), +//! which can be used to detect the current PAM implementation. +//! - The [`aliases`] submodule, which contains convenient aliases +//! for callback types used in libpam, so you don't have to type +//! `unsafe extern "C" fn(this is so long)` all the time. //! #![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). +//! For more information about configuration, see the documentation of +//! [`libpam-sys-consts`](https://crates.io/crates/libpam-sys-consts). #![allow(non_camel_case_types)] #![allow(unused_imports)] +pub mod aliases; #[doc(inline)] -pub use libpam_sys_helpers::constants::*; +pub use libpam_sys_consts::constants::*; #[doc(inline)] -pub use libpam_sys_helpers::{pam_impl, pam_impl_name}; +pub use libpam_sys_consts::{pam_impl, pam_impl_name}; 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); +pub struct pam_handle { + _value: (), + _marker: PhantomData<(PhantomPinned, *mut c_void)>, +} impl fmt::Debug for pam_handle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -52,57 +42,6 @@ } } -/// 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 { @@ -110,9 +49,9 @@ num_msg: c_int, msg: *const *const pam_message, resp: *mut *mut pam_response, - appdata: *mut AppData, + appdata: *mut c_void, ) -> c_int, - pub appdata_ptr: *mut AppData, + pub appdata_ptr: *mut c_void, } /// A message sent into a PAM conversation. diff -r 4d7333337569 -r 4b3a5095f68c src/constants.rs --- a/src/constants.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/src/constants.rs Mon Jul 07 12:11:43 2025 -0400 @@ -15,7 +15,7 @@ /// any PAM implementations. Applications should always use the symbolic value /// and not a magic number.** mod pam_constants { - pub use libpam_sys_helpers::constants::*; + pub use libpam_sys_consts::constants::*; macro_rules! define { ($(#[$attr:meta])* $($name:ident = $value:expr;)+) => { diff -r 4d7333337569 -r 4b3a5095f68c src/libpam/answer.rs --- a/src/libpam/answer.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/src/libpam/answer.rs Mon Jul 07 12:11:43 2025 -0400 @@ -4,7 +4,7 @@ use crate::libpam::memory; use crate::libpam::memory::{CHeapBox, CHeapPayload, CHeapString, Immovable}; use crate::{ErrorCode, Result}; -use libpam_sys_helpers::memory::BinaryPayload; +use libpam_sys_helpers::BinaryPayload; use std::ffi::{c_int, c_void, CStr, OsStr}; use std::mem::ManuallyDrop; use std::ops::{Deref, DerefMut}; diff -r 4d7333337569 -r 4b3a5095f68c src/libpam/conversation.rs --- a/src/libpam/conversation.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/src/libpam/conversation.rs Mon Jul 07 12:11:43 2025 -0400 @@ -5,9 +5,9 @@ use crate::libpam::question::Question; use crate::ErrorCode; use crate::Result; -use libpam_sys::{AppData, ConversationCallback}; -use libpam_sys_helpers::memory::PtrPtrVec; -use std::ffi::c_int; +use libpam_sys::aliases::ConversationCallback; +use libpam_sys_helpers::PtrPtrVec; +use std::ffi::{c_int, c_void}; use std::iter; use std::ptr::NonNull; use std::result::Result as StdResult; @@ -37,7 +37,7 @@ count: c_int, questions: *const *const libpam_sys::pam_message, answers: *mut *mut libpam_sys::pam_response, - me: *mut AppData, + me: *mut c_void, ) -> c_int { let internal = || { // Collect all our pointers diff -r 4d7333337569 -r 4b3a5095f68c src/libpam/handle.rs --- a/src/libpam/handle.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/src/libpam/handle.rs Mon Jul 07 12:11:43 2025 -0400 @@ -10,7 +10,7 @@ use crate::libpam::{items, memory}; use crate::logging::{Level, Location}; use crate::{Conversation, EnvironMap, Flags, ModuleClient, Transaction}; -use libpam_sys_helpers::constants; +use libpam_sys_consts::constants; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::cell::Cell; use std::ffi::{c_char, c_int, CString, OsStr, OsString}; diff -r 4d7333337569 -r 4b3a5095f68c src/libpam/memory.rs --- a/src/libpam/memory.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/src/libpam/memory.rs Mon Jul 07 12:11:43 2025 -0400 @@ -1,6 +1,6 @@ //! Things for dealing with memory. -use libpam_sys_helpers::memory::{Buffer, OwnedBinaryPayload}; +use libpam_sys_helpers::{Buffer, OwnedBinaryPayload}; use std::ffi::{c_char, CStr, CString, OsStr, OsString}; use std::marker::{PhantomData, PhantomPinned}; use std::mem::ManuallyDrop; diff -r 4d7333337569 -r 4b3a5095f68c src/libpam/question.rs --- a/src/libpam/question.rs Sun Jul 06 19:23:02 2025 -0400 +++ b/src/libpam/question.rs Mon Jul 07 12:11:43 2025 -0400 @@ -5,7 +5,7 @@ use crate::libpam::memory; use crate::ErrorCode; use crate::Result; -use libpam_sys_helpers::memory as pam_mem; +use libpam_sys_helpers as libpam_sys_helpers; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::ffi::{c_int, c_void, CStr, OsStr}; use std::os::unix::ffi::OsStrExt; @@ -81,7 +81,7 @@ unsafe fn binary_data(&self) -> (&[u8], u8) { self.data .as_ref() - .map(|data| pam_mem::BinaryPayload::contents(data.as_ptr().cast())) + .map(|data| libpam_sys_helpers::BinaryPayload::contents(data.as_ptr().cast())) .unwrap_or_default() } } @@ -135,7 +135,7 @@ Style::BinaryPrompt => self .data .as_mut() - .map(|p| pam_mem::BinaryPayload::zero(p.as_ptr().cast())), + .map(|p| libpam_sys_helpers::BinaryPayload::zero(p.as_ptr().cast())), #[cfg(feature = "linux-pam-ext")] Style::RadioType => self .data @@ -187,8 +187,8 @@ } #[cfg(feature = "linux-pam-ext")] -impl From for ErrorCode { - fn from(_: pam_mem::TooBigError) -> Self { +impl From for ErrorCode { + fn from(_: libpam_sys_helpers::TooBigError) -> Self { ErrorCode::BufferError } }