changeset 176:0730f5f2ee2a

Turn `libpam-sys-consts` back into `libpam-sys-impls`. This moves the constants into `libpam-sys` and makes `libpam-sys-impls` responsible solely for detecting the current PAM implementation.
author Paul Fisher <paul@pfish.zone>
date Wed, 30 Jul 2025 17:53:31 -0400
parents e30775c80b49
children 9925fa14021b
files Cargo.lock Cargo.toml build.rs libpam-sys/Cargo.lock libpam-sys/Cargo.toml libpam-sys/README.md libpam-sys/build.rs libpam-sys/libpam-sys-consts/Cargo.toml libpam-sys/libpam-sys-consts/README.md libpam-sys/libpam-sys-consts/build.rs libpam-sys/libpam-sys-consts/src/constants.rs libpam-sys/libpam-sys-consts/src/lib.rs libpam-sys/libpam-sys-consts/src/pam_impl.rs libpam-sys/libpam-sys-helpers/Cargo.toml libpam-sys/libpam-sys-helpers/README.md libpam-sys/libpam-sys-helpers/build.rs libpam-sys/libpam-sys-impls/Cargo.toml libpam-sys/libpam-sys-impls/README.md libpam-sys/libpam-sys-impls/build.rs libpam-sys/libpam-sys-impls/src/lib.rs libpam-sys/libpam-sys-impls/src/pam_impl.rs libpam-sys/libpam-sys-test/Cargo.toml libpam-sys/libpam-sys-test/build.rs libpam-sys/src/constants.rs libpam-sys/src/ffi.rs libpam-sys/src/lib.rs src/constants.rs src/lib.rs src/libpam/handle.rs
diffstat 29 files changed, 1186 insertions(+), 1196 deletions(-) [+]
line wrap: on
line diff
--- a/Cargo.lock	Wed Jul 30 14:57:12 2025 -0400
+++ b/Cargo.lock	Wed Jul 30 17:53:31 2025 -0400
@@ -65,22 +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",
+ "libpam-sys-impls",
 ]
 
 [[package]]
 name = "libpam-sys-helpers"
 version = "0.1.0"
 dependencies = [
- "libpam-sys-consts",
+ "libpam-sys-impls",
+]
+
+[[package]]
+name = "libpam-sys-impls"
+version = "0.1.0"
+dependencies = [
+ "libc",
 ]
 
 [[package]]
@@ -96,8 +96,8 @@
  "bitflags",
  "libc",
  "libpam-sys",
- "libpam-sys-consts",
  "libpam-sys-helpers",
+ "libpam-sys-impls",
  "num_enum",
 ]
 
--- a/Cargo.toml	Wed Jul 30 14:57:12 2025 -0400
+++ b/Cargo.toml	Wed Jul 30 17:53:31 2025 -0400
@@ -27,7 +27,7 @@
 #
 # This will fail if you have extensions enabled that are not compatible
 # with your system's PAM.
-link = ["dep:libpam-sys"]
+link = ["dep:libc", "dep:libpam-sys", "dep:libpam-sys-helpers"]
 
 # Extensions to PAM that are shared by Linux-PAM and OpenPAM
 # (i.e., most PAM installations).
@@ -44,11 +44,11 @@
 
 [dependencies]
 bitflags = "2.9.0"
-libc = "0.2"
+libc = { optional = true, version = "0.2" }
 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" }
+libpam-sys-helpers = { optional = true, path = "libpam-sys/libpam-sys-helpers" }
+libpam-sys-impls = { path = "libpam-sys/libpam-sys-impls" }
 
 [build-dependencies]
-libpam-sys-consts = { path = "libpam-sys/libpam-sys-consts" }
+libpam-sys-impls = { path = "libpam-sys/libpam-sys-impls" }
--- a/build.rs	Wed Jul 30 14:57:12 2025 -0400
+++ b/build.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -1,3 +1,3 @@
 fn main() {
-    libpam_sys_consts::pam_impl::enable_pam_impl_cfg()
+    libpam_sys_impls::enable_pam_impl_cfg()
 }
--- a/libpam-sys/Cargo.lock	Wed Jul 30 14:57:12 2025 -0400
+++ b/libpam-sys/Cargo.lock	Wed Jul 30 17:53:31 2025 -0400
@@ -219,22 +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",
+ "libpam-sys-impls",
 ]
 
 [[package]]
 name = "libpam-sys-helpers"
 version = "0.1.0"
 dependencies = [
- "libpam-sys-consts",
+ "libpam-sys-impls",
+]
+
+[[package]]
+name = "libpam-sys-impls"
+version = "0.1.0"
+dependencies = [
+ "libc",
 ]
 
 [[package]]
@@ -245,7 +245,7 @@
  "ctest",
  "libc",
  "libpam-sys",
- "libpam-sys-consts",
+ "libpam-sys-impls",
  "proc-macro2",
  "quote",
  "syn",
--- a/libpam-sys/Cargo.toml	Wed Jul 30 14:57:12 2025 -0400
+++ b/libpam-sys/Cargo.toml	Wed Jul 30 17:53:31 2025 -0400
@@ -1,6 +1,6 @@
 [workspace]
 resolver = "2"
-members = ["libpam-sys-consts", "libpam-sys-helpers", "libpam-sys-test"]
+members = ["libpam-sys-impls", "libpam-sys-helpers", "libpam-sys-test"]
 
 [workspace.package]
 version = "0.1.0"
@@ -32,10 +32,10 @@
 
 [dependencies]
 libc = "0.2"
-libpam-sys-consts = { path = "libpam-sys-consts" }
+libpam-sys-impls = { path = "libpam-sys-impls" }
 
 [target.'cfg(doc)'.dependencies]
 libpam-sys-helpers = { path = "libpam-sys-helpers" }
 
 [build-dependencies]
-libpam-sys-consts = { path = "libpam-sys-consts" }
+libpam-sys-impls = { path = "libpam-sys-impls" }
--- a/libpam-sys/README.md	Wed Jul 30 14:57:12 2025 -0400
+++ b/libpam-sys/README.md	Wed Jul 30 17:53:31 2025 -0400
@@ -13,7 +13,7 @@
 
 This crate automatically chooses the appropriate PAM implementation you are most likely to need installed based on the target OS.
 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-consts` crate](https://crates.io/crates/libpam-sys-consts).
+All build-time configuration is performed by the build script of the [`libpam-sys-impls` crate](https://crates.io/crates/libpam-sys-impls).
 
 Normally, this crate exports all functionality available in the selected PAM library.
 `XSso` exports only the subset of the [X/SSO specification][xsso] supported by both OpenPAM and Sun PAM.
@@ -43,7 +43,7 @@
 fn some_func() { /* Linux-PAM / OpenPAM implementation */ }
 ```
 
-Further documentation on this is available in `libpam-sys-consts`.
+Further documentation on this is available in `libpam-sys-impls`.
 
 ## Testing
 
--- a/libpam-sys/build.rs	Wed Jul 30 14:57:12 2025 -0400
+++ b/libpam-sys/build.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -1,6 +1,4 @@
-use libpam_sys_consts::pam_impl;
-
 fn main() {
     println!("cargo:rustc-link-lib=pam");
-    pam_impl::enable_pam_impl_cfg();
+    libpam_sys_impls::enable_pam_impl_cfg();
 }
--- a/libpam-sys/libpam-sys-consts/Cargo.toml	Wed Jul 30 14:57:12 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-[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
-
-
-[package.metadata.docs.rs]
-default-target = "x86_64-unknown-linux-gnu"
-
-targets = [
-    "x86_64-apple-darwin",
-    "x86_64-unknown-freebsd",
-    "x86_64-unknown-illumos",
-]
-
-[dependencies]
-libc = "0.2"
-
-[build-dependencies]
-libc = "0.2"
\ No newline at end of file
--- a/libpam-sys/libpam-sys-consts/README.md	Wed Jul 30 14:57:12 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,70 +0,0 @@
-# `libpam-sys-consts`: Constants for LibPAM
-
-This is mostly a backend crate for [libpam-sys](https://crates.io/crates/libpam-sys/).
-That crate re-exports pretty much everything we provide.
-In most cases, you can just use that instead of depending upon this directly.
-
-This crate does two primary things:
-
-- Detects which implementation of LibPAM to use (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 implementations
-
-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
-
-You can match on the current PAM implementation at runtime.
-All known PAM implementations are in the `PamImpl` enumeration, and `PamImpl::CURRENT` is set to the current implementation.
-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_consts::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
-
-Known implementations of PAM are listed in the `PamImpl` enum, and your currently installed implementation is automatically detected.
-
-If you need to configure this, you can override it **at build time** with the `LIBPAMSYS_IMPL` environment variable:
-
-- Unset or empty (the default): Use the version of PAM most commonly found on the target OS.
-  If we don't know what kind of PAM is usually installed on this OS, we fall back to `__installed__`.
-- `__installed__`: Looks at the PAM library installed on the current machine.
-  If none is recognized, falls back to `XSso`.
-- The name of a `PamImpl` entry: The named PAM implementation.
-  For instance, `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
--- a/libpam-sys/libpam-sys-consts/build.rs	Wed Jul 30 14:57:12 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-#![allow(unexpected_cfgs)]
-
-use std::ffi::{c_void, CString};
-use std::ptr::NonNull;
-use std::{env, fs};
-
-include!("src/pam_impl.rs");
-
-/// The strategy to use to detect PAM.
-enum Detect {
-    /// Use the default PAM implementation based on the OS,
-    /// or the currently-installed version if the OS is not recognized.
-    TargetDefault,
-    /// Detect the installed implementation.
-    Installed,
-    /// Use the named version of PAM.
-    Specified(PamImpl),
-}
-
-const INSTALLED: &str = "__installed__";
-
-fn main() {
-    let detection = match option_env!("LIBPAMSYS_IMPL") {
-        Some("") | None => Detect::TargetDefault,
-        Some(INSTALLED) => Detect::Installed,
-        Some(val) => Detect::Specified(PamImpl::try_from(val).unwrap_or_else(|_| {
-            panic!(
-                "unknown PAM implementation {val:?}. \
-                valid LIBPAMSYS_IMPLs are {:?}, \
-                {INSTALLED:?} to use the currently-installed version, \
-                or unset to use the OS default",
-                PamImpl::items()
-            )
-        })),
-    };
-    let pam_impl = match detection {
-        Detect::TargetDefault => LibPam::target_default(),
-        Detect::Installed => LibPam::probe_detect(),
-        Detect::Specified(other) => other,
-    };
-    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 {
-    /// Guess the PAM implementation based on the current OS.
-    fn target_default() -> PamImpl {
-        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 {
-            Self::probe_detect()
-        }
-    }
-
-    /// Look at the currently-installed LibPAM.
-    fn probe_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;
-            }
-        }
-        // idk
-        PamImpl::XSso
-    }
-
-    fn open() -> Option<Self> {
-        let dlopen = |s: &[u8]| unsafe { libc::dlopen(s.as_ptr().cast(), libc::RTLD_LAZY) };
-        NonNull::new(dlopen(b"libpam.so\0"))
-            .or_else(|| NonNull::new(dlopen(b"libpam.dylib\0")))
-            .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());
-        }
-    }
-}
--- a/libpam-sys/libpam-sys-consts/src/constants.rs	Wed Jul 30 14:57:12 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,316 +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;
-);
-
-/// A flag for `pam_authenticate`.
-pub const PAM_DISALLOW_NULL_AUTHTOK: i32 = 0x1;
-
-#[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_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<pam_modutil_redirect_fd> for i32 {
-        fn from(value: pam_modutil_redirect_fd) -> Self {
-            value as Self
-        }
-    }
-
-    impl TryFrom<i32> for pam_modutil_redirect_fd {
-        type Error = i32;
-        fn try_from(value: i32) -> Result<Self, Self::Error> {
-            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;
-
-    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;
-    );
-}
--- a/libpam-sys/libpam-sys-consts/src/lib.rs	Wed Jul 30 14:57:12 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-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.
-///
-/// ```
-/// // Your package's build.rs:
-///
-/// use libpam_sys_consts::pam_impl;
-/// // also available at libpam_sys::pam_impl
-///
-/// fn main() {
-///     pam_impl::enable_pam_impl_cfg();
-///     // whatever else you do in your build script.
-/// }
-/// ```
-///
-/// 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.
-///
-/// ```ignore
-/// #[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::*;
--- a/libpam-sys/libpam-sys-consts/src/pam_impl.rs	Wed Jul 30 14:57:12 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<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_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(",")
-    )
-}
--- a/libpam-sys/libpam-sys-helpers/Cargo.toml	Wed Jul 30 14:57:12 2025 -0400
+++ b/libpam-sys/libpam-sys-helpers/Cargo.toml	Wed Jul 30 17:53:31 2025 -0400
@@ -10,4 +10,4 @@
 readme = "README.md"
 
 [build-dependencies]
-libpam-sys-consts = { path = "../libpam-sys-consts" }
\ No newline at end of file
+libpam-sys-impls = { path = "../libpam-sys-impls" }
\ No newline at end of file
--- a/libpam-sys/libpam-sys-helpers/README.md	Wed Jul 30 14:57:12 2025 -0400
+++ b/libpam-sys/libpam-sys-helpers/README.md	Wed Jul 30 17:53:31 2025 -0400
@@ -2,4 +2,4 @@
 
 This provides memory-management helpers for `libpam-sys`.
 
-This does not actually depend upon `libpam-sys`; it only uses `libpam-sys-consts`.
\ No newline at end of file
+This does not actually depend upon `libpam-sys`; it only uses `libpam-sys-impls`.
\ No newline at end of file
--- a/libpam-sys/libpam-sys-helpers/build.rs	Wed Jul 30 14:57:12 2025 -0400
+++ b/libpam-sys/libpam-sys-helpers/build.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -1,6 +1,3 @@
-use libpam_sys_consts::pam_impl;
-
 fn main() {
-    println!("cargo:rustc-link-lib=pam");
-    pam_impl::enable_pam_impl_cfg();
+    libpam_sys_impls::enable_pam_impl_cfg();
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpam-sys/libpam-sys-impls/Cargo.toml	Wed Jul 30 17:53:31 2025 -0400
@@ -0,0 +1,25 @@
+[package]
+name = "libpam-sys-impls"
+description = "Detects the current implementation of LibPAM."
+version.workspace = true
+authors.workspace = true
+repository.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+license.workspace = true
+
+
+[package.metadata.docs.rs]
+default-target = "x86_64-unknown-linux-gnu"
+
+targets = [
+    "x86_64-apple-darwin",
+    "x86_64-unknown-freebsd",
+    "x86_64-unknown-illumos",
+]
+
+[dependencies]
+libc = "0.2"
+
+[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-impls/README.md	Wed Jul 30 17:53:31 2025 -0400
@@ -0,0 +1,66 @@
+# `libpam-sys-impls`: LibPAM library detection
+
+This crate detects what implementation of LibPAM should be used, as part of the build script, and exports that information to downstream crates.
+
+This is mostly a backend crate for [libpam-sys](https://crates.io/crates/libpam-sys/).
+That crate re-exports pretty much everything we provide.
+In most cases, you can just use that instead of depending upon this directly.
+
+## Usage
+
+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
+
+You can match on the current PAM implementation at runtime.
+All known PAM implementations are in the `PamImpl` enumeration, and `PamImpl::CURRENT` is set to the current implementation.
+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_impls::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
+
+Known implementations of PAM are listed in the `PamImpl` enum, and your currently installed implementation is automatically detected.
+
+If you need to configure this, you can override it **at build time** with the `LIBPAMSYS_IMPL` environment variable:
+
+- Unset or empty (the default): Use the version of PAM most commonly found on the target OS.
+  If we don't know what kind of PAM is usually installed on this OS, we fall back to `__installed__`.
+- `__installed__`: Looks at the PAM library installed on the current machine.
+  If none is recognized, falls back to `XSso`.
+- The name of a `PamImpl` entry: The named PAM implementation.
+  For instance, `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-impls/build.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -0,0 +1,124 @@
+#![allow(unexpected_cfgs)]
+
+use std::ffi::{c_void, CString};
+use std::ptr::NonNull;
+use std::{env, fs};
+
+include!("src/pam_impl.rs");
+
+/// The strategy to use to detect PAM.
+enum Detect {
+    /// Use the default PAM implementation based on the OS,
+    /// or the currently-installed version if the OS is not recognized.
+    TargetDefault,
+    /// Detect the installed implementation.
+    Installed,
+    /// Use the named version of PAM.
+    Specified(PamImpl),
+}
+
+const INSTALLED: &str = "__installed__";
+
+fn main() {
+    let detection = match option_env!("LIBPAMSYS_IMPL") {
+        Some("") | None => Detect::TargetDefault,
+        Some(INSTALLED) => Detect::Installed,
+        Some(val) => Detect::Specified(PamImpl::try_from(val).unwrap_or_else(|_| {
+            panic!(
+                "unknown PAM implementation {val:?}. \
+                valid LIBPAMSYS_IMPLs are {:?}, \
+                {INSTALLED:?} to use the currently-installed version, \
+                or unset to use the OS default",
+                PamImpl::items()
+            )
+        })),
+    };
+    let pam_impl = match detection {
+        Detect::TargetDefault => LibPam::target_default(),
+        Detect::Installed => LibPam::probe_detect(),
+        Detect::Specified(other) => other,
+    };
+    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 {
+    /// Guess the PAM implementation based on the current OS.
+    fn target_default() -> PamImpl {
+        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 {
+            Self::probe_detect()
+        }
+    }
+
+    /// Look at the currently-installed LibPAM.
+    fn probe_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;
+            }
+        }
+        // idk
+        PamImpl::XSso
+    }
+
+    fn open() -> Option<Self> {
+        let dlopen = |s: &[u8]| unsafe { libc::dlopen(s.as_ptr().cast(), libc::RTLD_LAZY) };
+        NonNull::new(dlopen(b"libpam.so\0"))
+            .or_else(|| NonNull::new(dlopen(b"libpam.dylib\0")))
+            .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-impls/src/lib.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -0,0 +1,53 @@
+//! 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.
+//!
+//! ```
+//! // Your package's build.rs:
+//!
+//! fn main() {
+//!     // Also available at libpam_sys::pam_impl::enable_pam_impl_cfg().
+//!     libpam_sys_impls::enable_pam_impl_cfg();
+//!     // whatever else you do in your build script.
+//! }
+//! ```
+//!
+//! 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.
+//!
+//! ```ignore
+//! #[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"), "`.")]
+
+mod pam_impl;
+
+#[doc(inline)]
+pub use pam_impl::*;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpam-sys/libpam-sys-impls/src/pam_impl.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -0,0 +1,112 @@
+// 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
+/// fn main() {
+///     libpam_sys_impls::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	Wed Jul 30 14:57:12 2025 -0400
+++ b/libpam-sys/libpam-sys-test/Cargo.toml	Wed Jul 30 17:53:31 2025 -0400
@@ -14,7 +14,7 @@
 bindgen = "0.72.0"
 ctest = "0.4.11"
 libpam-sys = { path = ".." }
-libpam-sys-consts = { path = "../libpam-sys-consts" }
+libpam-sys-impls = { path = "../libpam-sys-impls" }
 proc-macro2 = "1.0.95"
 quote = "1.0.40"
 syn = { version = "2.0.104", features = ["full"] }
--- a/libpam-sys/libpam-sys-test/build.rs	Wed Jul 30 14:57:12 2025 -0400
+++ b/libpam-sys/libpam-sys-test/build.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -1,6 +1,6 @@
 use bindgen::MacroTypeVariation;
-use libpam_sys_consts::pam_impl::PamImpl;
-use libpam_sys_consts::{pam_impl, pam_impl_name};
+use libpam_sys_impls::pam_impl_name;
+use libpam_sys_impls::PamImpl;
 use proc_macro2::{Group, Ident, TokenStream, TokenTree};
 use quote::{format_ident, ToTokens};
 use std::path::Path;
@@ -12,7 +12,7 @@
 const REDIR_FD: &str = "pam_modutil_redirect_fd";
 
 fn main() {
-    pam_impl::enable_pam_impl_cfg();
+    libpam_sys_impls::enable_pam_impl_cfg();
     let config = match PamImpl::CURRENT {
         PamImpl::LinuxPam => TestConfig {
             headers: vec![
@@ -151,7 +151,7 @@
     test.define("const", Some(""));
 
     // Also replace all the `const`s with `mut`s in the ffi.rs file.
-    let file_contents = include_str!("../src/lib.rs");
+    let file_contents = include_str!("../src/ffi.rs");
     let deconsted_file = test_file("ffi.rs");
     remove_consts(file_contents, &deconsted_file);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpam-sys/src/constants.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -0,0 +1,316 @@
+//! 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;
+);
+
+/// A flag for `pam_authenticate`.
+pub const PAM_DISALLOW_NULL_AUTHTOK: i32 = 0x1;
+
+#[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_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<pam_modutil_redirect_fd> for i32 {
+        fn from(value: pam_modutil_redirect_fd) -> Self {
+            value as Self
+        }
+    }
+
+    impl TryFrom<i32> for pam_modutil_redirect_fd {
+        type Error = i32;
+        fn try_from(value: i32) -> Result<Self, Self::Error> {
+            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;
+
+    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;
+    );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpam-sys/src/ffi.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -0,0 +1,438 @@
+use std::ffi::{c_char, c_int, c_uint, c_void};
+use std::fmt;
+use std::marker::{PhantomData, PhantomPinned};
+
+/// 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 {
+    _value: (),
+    _marker: PhantomData<(PhantomPinned, *mut c_void)>,
+}
+
+impl fmt::Debug for pam_handle {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "pam_handle({self:p}")
+    }
+}
+
+/// Used by PAM to communicate between the module and the application.
+#[repr(C)]
+#[derive(Debug)]
+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 c_void,
+    ) -> c_int,
+    pub appdata_ptr: *mut c_void,
+}
+
+/// A message sent into a PAM conversation.
+#[repr(C)]
+#[derive(Debug)]
+pub struct pam_message {
+    pub msg_style: c_int,
+    pub msg: *const c_char,
+}
+
+/// A response returned from a PAM conversation.
+#[repr(C)]
+#[derive(Debug)]
+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(any(pam_impl = "OpenPam", pam_impl = "Sun"))]
+#[derive(Debug)]
+#[repr(C)]
+pub struct pam_repository {
+    pub type_: *mut c_char,
+    pub scope: *mut c_void,
+    pub scope_len: usize,
+}
+
+// These are the functions specified in X/SSO. Everybody exports them.
+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: *const 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.
+
+    /// 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,
+        type_: 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: super::constants::pam_modutil_redirect_fd,
+        redirect_stdout: super::constants::pam_modutil_redirect_fd,
+        redirect_stderr: super::constants::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 c_void,
+    ) -> 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 c_void,
+    ) -> c_int;
+}
+
+#[cfg(pam_impl = "Sun")]
+extern "C" {
+    pub fn __pam_get_authtok(
+        pamh: *mut pam_handle,
+        source: c_int,
+        type_: c_int,
+        prompt: *const c_char,
+        authtok: *mut *mut c_char,
+    ) -> c_int;
+
+    pub fn __pam_log(priority: c_int, format: *const c_char, ...);
+}
--- a/libpam-sys/src/lib.rs	Wed Jul 30 14:57:12 2025 -0400
+++ b/libpam-sys/src/lib.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -13,450 +13,16 @@
 //! 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 documentation of
-//! [`libpam-sys-consts`](https://crates.io/crates/libpam-sys-consts).
+//! [`libpam-sys-impls`](https://crates.io/crates/libpam-sys-impls).
 #![allow(non_camel_case_types)]
 #![allow(unused_imports)]
 
 pub mod aliases;
-#[doc(inline)]
-pub use libpam_sys_consts::constants::*;
+mod constants;
+mod ffi;
 #[doc(inline)]
-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};
-
-/// 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 {
-    _value: (),
-    _marker: PhantomData<(PhantomPinned, *mut c_void)>,
-}
-
-impl fmt::Debug for pam_handle {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "pam_handle({self:p}")
-    }
-}
-
-/// Used by PAM to communicate between the module and the application.
-#[repr(C)]
-#[derive(Debug)]
-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 c_void,
-    ) -> c_int,
-    pub appdata_ptr: *mut c_void,
-}
-
-/// A message sent into a PAM conversation.
-#[repr(C)]
-#[derive(Debug)]
-pub struct pam_message {
-    pub msg_style: c_int,
-    pub msg: *const c_char,
-}
-
-/// A response returned from a PAM conversation.
-#[repr(C)]
-#[derive(Debug)]
-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(any(pam_impl = "OpenPam", pam_impl = "Sun"))]
-#[derive(Debug)]
-#[repr(C)]
-pub struct pam_repository {
-    pub type_: *mut c_char,
-    pub scope: *mut c_void,
-    pub scope_len: usize,
-}
-
-// These are the functions specified in X/SSO. Everybody exports them.
-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: *const 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.
-
-    /// 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,
-        type_: 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 c_void,
-    ) -> 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 c_void,
-    ) -> c_int;
-}
-
-#[cfg(pam_impl = "Sun")]
-extern "C" {
-    pub fn __pam_get_authtok(
-        pamh: *mut pam_handle,
-        source: c_int,
-        type_: c_int,
-        prompt: *const c_char,
-        authtok: *mut *mut c_char,
-    ) -> c_int;
-
-    pub fn __pam_log(priority: c_int, format: *const c_char, ...);
-}
+pub use crate::{constants::*, ffi::*};
+#[doc(inline)]
+pub use libpam_sys_impls as pam_impl;
+#[doc(inline)]
+pub use libpam_sys_impls::pam_impl_name;
--- a/src/constants.rs	Wed Jul 30 14:57:12 2025 -0400
+++ b/src/constants.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -412,7 +412,6 @@
     #[cfg(not(feature = "openpam-ext"))]
     const BAD_CONST: ErrorCode = ErrorCode::SystemError;
 
-
     pub(crate) fn result_from(ret: i32) -> Result<()> {
         match ret {
             0 => Ok(()),
--- a/src/lib.rs	Wed Jul 30 14:57:12 2025 -0400
+++ b/src/lib.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -83,7 +83,6 @@
 //! the address space of the calling application. To implement a module,
 //! create a `dylib` crate and implement a [`PamModule`], and export it
 //! using the [`pam_export!`] macro.
-//!
 //! ```toml
 //! ## Your Cargo.toml
 //! [package]
@@ -93,7 +92,6 @@
 //! [lib]
 //! crate-type = ["cdylib"]
 //! ```
-//!
 //! ```
 //! // Your lib.rs
 //!
@@ -127,7 +125,7 @@
 //!     // features.
 //! }
 //! ```
-//!
+//! 
 //! This gets built into a library like `pam_samename.so`. By installing this
 //! into your PAM library directory and configuring PAM to use it in
 //! the authentication stack (beyond the scope of this documentation), it will
@@ -158,6 +156,7 @@
 //!   - [OpenPAM admin documentation][bsdpam8]
 //!   - [Illumos pam.conf documentation][sunpam5]
 //! - [The original PAM specification][spec] (mostly of historical interest)
+#![doc = ""]
 #![doc = crate::_doc::man7!(man7pam8: 8 pam)]
 #![doc = crate::_doc::manbsd!(bsdpam8: 8 pam)]
 #![doc = crate::_doc::mansun!(sunpam5: 5 "pam.conf")]
@@ -218,4 +217,4 @@
     handle::{ModuleClient, PamShared, Transaction},
     module::PamModule,
 };
-use libpam_sys_consts::pam_impl_name;
+use libpam_sys_impls::pam_impl_name;
--- a/src/libpam/handle.rs	Wed Jul 30 14:57:12 2025 -0400
+++ b/src/libpam/handle.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -138,6 +138,7 @@
     ///
     /// On other platforms, this is no different than letting the transaction
     /// end on its own.
+    #[doc = ""]
     #[doc = man7!(3 pam_end)]
     pub fn end_silent(self) {
         #[cfg(pam_impl = "LinuxPam")]