changeset 134:6c1e1bdb4164

Use standard #[cfg] directives rather than custom proc macros. Instead of having to do a bunch of custom parsing and other logic that tools often choke on, this change introduces an easy way to depend upon custom #[cfg]s provided by the libpam-sys crate.
author Paul Fisher <paul@pfish.zone>
date Thu, 03 Jul 2025 11:03:36 -0400
parents 32b2a545ca3e
children b52594841480
files Cargo.lock Cargo.toml build.rs libpam-sys/Cargo.toml libpam-sys/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-test/Cargo.toml libpam-sys/libpam-sys-test/build.rs libpam-sys/src/constants.rs libpam-sys/src/ffi.rs libpam-sys/src/helpers.rs libpam-sys/src/lib.rs libpam-sys/src/pam_impl.rs src/libpam/handle.rs src/logging.rs
diffstat 18 files changed, 278 insertions(+), 698 deletions(-) [+]
line wrap: on
line diff
--- a/Cargo.lock	Wed Jul 02 03:33:09 2025 -0400
+++ b/Cargo.lock	Thu Jul 03 11:03:36 2025 -0400
@@ -35,12 +35,12 @@
  "itertools",
  "log",
  "prettyplease",
- "proc-macro2 1.0.95",
- "quote 1.0.40",
+ "proc-macro2",
+ "quote",
  "regex",
  "rustc-hash",
  "shlex",
- "syn 2.0.104",
+ "syn",
 ]
 
 [[package]]
@@ -142,29 +142,6 @@
 ]
 
 [[package]]
-name = "dlopen"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71e80ad39f814a9abe68583cd50a2d45c8a67561c3361ab8da240587dda80937"
-dependencies = [
- "dlopen_derive",
- "lazy_static",
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "dlopen_derive"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f236d9e1b1fbd81cea0f9cbdc8dcc7e8ebcd80e6659cd7cb2ad5f6c05946c581"
-dependencies = [
- "libc",
- "quote 0.6.13",
- "syn 0.15.44",
-]
-
-[[package]]
 name = "either"
 version = "1.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -186,7 +163,7 @@
  "libc",
  "serde",
  "term",
- "unicode-xid 0.2.6",
+ "unicode-xid",
 ]
 
 [[package]]
@@ -210,7 +187,7 @@
  "log",
  "serde",
  "serde_json",
- "unicode-xid 0.2.6",
+ "unicode-xid",
 ]
 
 [[package]]
@@ -237,12 +214,6 @@
 checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
 
 [[package]]
-name = "heck"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-
-[[package]]
 name = "indexmap"
 version = "2.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -274,12 +245,6 @@
 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
 
 [[package]]
-name = "lazy_static"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
-
-[[package]]
 name = "libc"
 version = "0.2.174"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -300,20 +265,7 @@
 version = "0.1.0"
 dependencies = [
  "libc",
- "libpam-sys-impls",
  "num_enum",
- "strum",
-]
-
-[[package]]
-name = "libpam-sys-impls"
-version = "0.0.1"
-dependencies = [
- "dlopen",
- "proc-macro2 1.0.95",
- "quote 1.0.40",
- "strum",
- "syn 2.0.104",
 ]
 
 [[package]]
@@ -324,10 +276,9 @@
  "ctest",
  "libc",
  "libpam-sys",
- "libpam-sys-impls",
- "proc-macro2 1.0.95",
- "quote 1.0.40",
- "syn 2.0.104",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -381,13 +332,11 @@
 name = "nonstick"
 version = "0.0.8-alpha0"
 dependencies = [
- "bindgen",
  "bitflags 2.9.1",
  "libc",
  "libpam-sys",
  "memoffset",
  "num_enum",
- "regex",
 ]
 
 [[package]]
@@ -417,9 +366,9 @@
 checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
 dependencies = [
  "proc-macro-crate",
- "proc-macro2 1.0.95",
- "quote 1.0.40",
- "syn 2.0.104",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -428,8 +377,8 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
 dependencies = [
- "proc-macro2 1.0.95",
- "syn 2.0.104",
+ "proc-macro2",
+ "syn",
 ]
 
 [[package]]
@@ -443,15 +392,6 @@
 
 [[package]]
 name = "proc-macro2"
-version = "0.4.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
-dependencies = [
- "unicode-xid 0.1.0",
-]
-
-[[package]]
-name = "proc-macro2"
 version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
@@ -461,20 +401,11 @@
 
 [[package]]
 name = "quote"
-version = "0.6.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
-dependencies = [
- "proc-macro2 0.4.30",
-]
-
-[[package]]
-name = "quote"
 version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
 dependencies = [
- "proc-macro2 1.0.95",
+ "proc-macro2",
 ]
 
 [[package]]
@@ -581,9 +512,9 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
 dependencies = [
- "proc-macro2 1.0.95",
- "quote 1.0.40",
- "syn 2.0.104",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -605,46 +536,13 @@
 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
-name = "strum"
-version = "0.27.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
-dependencies = [
- "strum_macros",
-]
-
-[[package]]
-name = "strum_macros"
-version = "0.27.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
-dependencies = [
- "heck",
- "proc-macro2 1.0.95",
- "quote 1.0.40",
- "rustversion",
- "syn 2.0.104",
-]
-
-[[package]]
-name = "syn"
-version = "0.15.44"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
-dependencies = [
- "proc-macro2 0.4.30",
- "quote 0.6.13",
- "unicode-xid 0.1.0",
-]
-
-[[package]]
 name = "syn"
 version = "2.0.104"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
 dependencies = [
- "proc-macro2 1.0.95",
- "quote 1.0.40",
+ "proc-macro2",
+ "quote",
  "unicode-ident",
 ]
 
@@ -694,9 +592,9 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
 dependencies = [
- "proc-macro2 1.0.95",
- "quote 1.0.40",
- "syn 2.0.104",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -705,9 +603,9 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
 dependencies = [
- "proc-macro2 1.0.95",
- "quote 1.0.40",
- "syn 2.0.104",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
@@ -744,12 +642,6 @@
 
 [[package]]
 name = "unicode-xid"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
-
-[[package]]
-name = "unicode-xid"
 version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
--- a/Cargo.toml	Wed Jul 02 03:33:09 2025 -0400
+++ b/Cargo.toml	Thu Jul 03 11:03:36 2025 -0400
@@ -1,5 +1,5 @@
 [workspace]
-members = ["libpam-sys", "libpam-sys/libpam-sys-impls", "libpam-sys/libpam-sys-test", "testharness"]
+members = ["libpam-sys", "libpam-sys/libpam-sys-test", "testharness"]
 resolver = "2"
 
 [workspace.package]
@@ -44,8 +44,5 @@
 num_enum = "0.7.3"
 libpam-sys = { path = "libpam-sys" }
 
-[dev-dependencies]
-regex = "1.11.1"
-
 [build-dependencies]
-bindgen = "0.72.0"
+libpam-sys = { path = "libpam-sys" }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/build.rs	Thu Jul 03 11:03:36 2025 -0400
@@ -0,0 +1,5 @@
+use libpam_sys::pam_impl;
+
+fn main() {
+    println!("{}", pam_impl::enable_pam_impl_cfg())
+}
--- a/libpam-sys/Cargo.toml	Wed Jul 02 03:33:09 2025 -0400
+++ b/libpam-sys/Cargo.toml	Thu Jul 03 11:03:36 2025 -0400
@@ -14,9 +14,7 @@
 
 [dependencies]
 libc = "0.2"
-libpam-sys-impls = { path = "libpam-sys-impls" }
 num_enum = "0.7.4"
 
 [build-dependencies]
-libpam-sys-impls = { path = "libpam-sys-impls" }
-strum = { version = "0.27.1", features = ["derive"] }
+libc = "0.2"
\ No newline at end of file
--- a/libpam-sys/build.rs	Wed Jul 02 03:33:09 2025 -0400
+++ b/libpam-sys/build.rs	Thu Jul 03 11:03:36 2025 -0400
@@ -1,13 +1,93 @@
-use libpam_sys_impls::__pam_impl_enum__;
-use strum::{EnumIter, IntoEnumIterator};
+#![allow(unexpected_cfgs)]
 
-__pam_impl_enum__!(#[derive(EnumIter)]);
+use std::env;
+use std::ffi::{c_void, CString};
+use std::fs;
+use std::ptr::NonNull;
+
+include!("src/pam_impl.rs");
 
 fn main() {
     println!("cargo:rustc-link-lib=pam");
-    let pam_impl_strs: Vec<_> = PamImpl::iter().map(|e| format!("\"{:?}\"", e)).collect();
-    let pam_impls = pam_impl_strs.join(",");
-    // We use this for ctest. Don't do what we've done; just use cfg_pam_impl.
-    println!("cargo:rustc-check-cfg=cfg(_hack_impl, values({pam_impls}))");
-    println!("cargo:rustc-cfg=_hack_impl=\"{:?}\"", PamImpl::CURRENT);
+
+    let pam_impl = match option_env!("LIBPAMSYS_IMPL") {
+        // The default option: Guess what PAM impl we're using based on OS.
+        None | Some("") => detect(),
+        Some(other) => match PamImpl::try_from(other) {
+            Ok(i) => i,
+            Err(_) => {
+                panic!(
+                    "unknown PAM implementation {other:?}. valid LIBPAMSYS_IMPLs are {:?}, or unset to detect", PamImpl::items()
+                )
+            }
+        },
+    };
+    println!("{}", checkcfg());
+    let impl_str = format!("{pam_impl:?}");
+    println!("{}", implcfg(&impl_str));
+    println!("cargo:rustc-env=LIBPAMSYS_IMPL={impl_str}");
+    fs::write(
+        format!("{}/pam_impl_const.rs", env::var("OUT_DIR").unwrap()),
+        format!(
+            "\
+            impl PamImpl {{\
+            /// The implementation of libpam this was built for (`{impl_str}`).
+            pub const CURRENT: Self = Self::{impl_str};\
+            /// String version for internal consumption.
+            const CURRENT_STR: &'static str = {impl_str:?};\
+            }}
+            "
+        ),
+    )
+    .unwrap();
 }
+
+fn detect() -> PamImpl {
+    if let Some(lib) = LibPam::open() {
+        if lib.has("pam_syslog") {
+            return PamImpl::LinuxPam;
+        } else if lib.has("_openpam_log") {
+            return PamImpl::OpenPam;
+        } else if lib.has("__pam_get_authtok") {
+            return PamImpl::Sun;
+        }
+    }
+    if cfg!(target_os = "linux") {
+        PamImpl::LinuxPam
+    } else if cfg!(any(
+        target_os = "macos",
+        target_os = "freebsd",
+        target_os = "netbsd",
+        target_os = "dragonfly",
+        target_os = "openbsd",
+    )) {
+        PamImpl::OpenPam
+    } else if cfg!(any(target_os = "illumos", target_os = "solaris")) {
+        PamImpl::Sun
+    } else {
+        PamImpl::XSso
+    }
+}
+
+struct LibPam(NonNull<c_void>);
+
+impl LibPam {
+    fn open() -> Option<Self> {
+        NonNull::new(unsafe { libc::dlopen(b"libpam.so\0".as_ptr().cast(), libc::RTLD_LAZY) })
+            .map(Self)
+    }
+
+    fn has(&self, name: &str) -> bool {
+        let name = CString::new(name).unwrap();
+        let symbol = unsafe { libc::dlsym(self.0.as_ptr(), name.as_ptr()) };
+        !symbol.is_null()
+    }
+}
+
+impl Drop for LibPam {
+    fn drop(&mut self) {
+        unsafe {
+            libc::dlclose(self.0.as_ptr());
+        }
+    }
+}
--- a/libpam-sys/libpam-sys-impls/Cargo.toml	Wed Jul 02 03:33:09 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-[package]
-name = "libpam-sys-impls"
-description = "Macros for use in libpam-sys."
-version = "0.0.1"
-rust-version.workspace = true
-edition.workspace = true
-readme = "README.md"
-
-[lib]
-proc-macro = true
-
-
-[build-dependencies]
-dlopen = "0.1.8"
-proc-macro2 = "1.0.95"
-quote = "1.0.40"
-strum = { version = "0.27.1", features = ["derive"] }
-
-
-[dependencies]
-quote = "1.0.40"
-syn = {  version = "2.0.104", default-features = false, features = ["parsing"] }
-proc-macro2 = "1.0.95"
--- a/libpam-sys/libpam-sys-impls/README.md	Wed Jul 02 03:33:09 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-# `libpam-sys-impls`
-
-This crate is an internal implementation detail of the [`libpam-sys`] crate.
-Its one public-facing macro is documented there.
-
-[`libpam-sys`]: https://docs.rs/libpam-sys
\ No newline at end of file
--- a/libpam-sys/libpam-sys-impls/build.rs	Wed Jul 02 03:33:09 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-//! This absurd build script basically sets up everything for libpam-sys-impl.
-//!
-//!  1. It's the definition site for the [`PamImpl`] enum, which then gets
-//!     output to the `OUT_DIR/pam_impl_enum.rs` file for parsing/inclusion
-//!     into the `__pam_impl_enum__` macro.
-//!  2. It detects the current PAM implementation and sets an env var for
-//!     the macros in `libpam-sys-impl`.
-//!
-//! We need to have this detection here for `#[cfg_pam_impl]` to work properly
-//! when exported to downstream crates.
-
-use dlopen::raw::Library;
-use proc_macro2::TokenStream;
-use quote::quote;
-use std::ffi::c_void;
-use std::{env, fs};
-use strum::{EnumIter, EnumString, IntoEnumIterator};
-
-const DETECT: &str = "__detect__";
-
-fn main() {
-    let pam_impl = match option_env!("LIBPAMSYS_IMPL") {
-        // The default option: Guess what PAM impl we're using based on OS.
-        None => {
-            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
-            }
-        }
-        Some(DETECT) => {
-            // Detect the library based on the symbols in libpam.so.
-            let lib = Library::open("libpam.so").unwrap();
-            if symbol_exists(&lib, "pam_syslog") {
-                PamImpl::LinuxPam
-            } else if symbol_exists(&lib, "_openpam_log") {
-                PamImpl::OpenPam
-            } else if symbol_exists(&lib, "__pam_get_authtok") {
-                PamImpl::Sun
-            } else {
-                // If all else fails, assume the bare minimum.
-                PamImpl::XSso
-            }
-        }
-        Some(other) => match PamImpl::try_from(other) {
-            Ok(i) => i,
-            Err(_) => {
-                let valid: Vec<_> = PamImpl::iter().collect();
-                panic!(
-                    "unknown PAM implementation {other:?}. valid LIBPAMSYS_IMPLs are {valid:?}, or use {DETECT:?}"
-                )
-            }
-        },
-    };
-    fs::write(
-        format!("{}/pam_impl_enum.rs", env::var("OUT_DIR").unwrap()),
-        PamImpl::enum_tokens().to_string(),
-    )
-    .unwrap();
-    println!("cargo:rustc-env=LIBPAMSYS_IMPL={pam_impl:?}");
-}
-
-fn symbol_exists(lib: &Library, symbol: &str) -> bool {
-    unsafe { lib.symbol::<*mut c_void>(symbol) }.is_ok()
-}
-
-/// This defines a local enum with an `enum_tokens()` method that can spit out
-/// its own contents.
-macro_rules! self_aware_enum {
-    (
-        $(#here[$here:meta])*
-        $(#[$attr:meta])*
-        $name:ident {
-            $($tt:tt)*
-        }
-    ) => {
-        $(#[$here])*
-        $(#[$attr])*
-        pub enum $name {
-            $($tt)*
-        }
-
-        impl $name {
-            fn enum_tokens() -> TokenStream {
-                quote!(
-                    $(#[$attr])*
-                    pub enum $name {
-                        $($tt)*
-                    }
-                )
-            }
-        }
-    }
-}
-
-self_aware_enum!(
-    #here[derive(EnumString, strum::Display, EnumIter)]
-    /// The PAM implementations supported by `libpam-sys`.
-    #[derive(Clone, Copy, Debug, PartialEq)]
-    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,
-    }
-);
--- a/libpam-sys/libpam-sys-impls/src/lib.rs	Wed Jul 02 03:33:09 2025 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,287 +0,0 @@
-//! Internal-use macros for [`libpam-sys`]. Please don't use them.
-//!
-//! The build script detects what the current PAM implementation is,
-//! and then these macros generate code based on that.
-//!
-//! [`libpam-sys`]: https://docs.rs/libpam-sys
-
-use proc_macro as pm;
-use proc_macro2::{Delimiter, Group, Literal, Span, TokenStream, TokenTree};
-use quote::{format_ident, quote};
-use std::fmt::Display;
-use std::str::FromStr;
-use syn::Lit;
-
-// For documentation on this, see the `libpam-sys` crate.
-#[proc_macro_attribute]
-pub fn cfg_pam_impl(attr: pm::TokenStream, item: pm::TokenStream) -> pm::TokenStream {
-    Predicate::parse(attr.into(), Some(Span::call_site()))
-        .map(|p| {
-            if p.matches(pam_impl_str()) {
-                item
-            } else {
-                pm::TokenStream::new()
-            }
-        })
-        .unwrap_or_else(|e| syn::Error::from(e).into_compile_error().into())
-}
-
-/// Outputs the `PamImpl` enum and `LIBPAMSYS_IMPL` constant.
-/// For use only in `libpam-sys`.
-///
-/// The tokens passed into the macro are pasted immediately before the enum.
-#[proc_macro]
-pub fn __pam_impl_enum__(data: pm::TokenStream) -> pm::TokenStream {
-    let variant = format_ident!("{}", pam_impl_str());
-    TokenStream::from_iter([
-        data.into(),
-        TokenStream::from_str(include_str!(concat!(env!("OUT_DIR"), "/pam_impl_enum.rs"))).unwrap(),
-        quote!(
-            impl PamImpl {
-                #[doc = concat!("The PAM implementation this was built for (currently `", stringify!(#variant), "`).")]
-                pub const CURRENT: Self = Self::#variant;
-            }
-        ),
-    ]).into()
-}
-
-/// The name of the PAM implementation. For use only in `libpam-sys`.
-#[proc_macro]
-pub fn __pam_impl_name__(data: pm::TokenStream) -> pm::TokenStream {
-    if !data.is_empty() {
-        panic!("pam_impl_name! does not take any input")
-    }
-    pm::TokenTree::Literal(pm::Literal::string(pam_impl_str())).into()
-}
-
-fn pam_impl_str() -> &'static str {
-    env!("LIBPAMSYS_IMPL")
-}
-
-#[derive(Debug)]
-enum Error {
-    WithSpan(syn::Error),
-    WithoutSpan(String),
-}
-
-impl Error {
-    fn new<D: Display>(span: Option<Span>, msg: D) -> Self {
-        match span {
-            Some(span) => syn::Error::new(span, msg).into(),
-            None => Self::WithoutSpan(msg.to_string()),
-        }
-    }
-}
-
-impl From<syn::Error> for Error {
-    fn from(value: syn::Error) -> Self {
-        Self::WithSpan(value)
-    }
-}
-
-impl From<String> for Error {
-    fn from(value: String) -> Self {
-        Self::WithoutSpan(value)
-    }
-}
-
-impl From<Error> for syn::Error {
-    fn from(value: Error) -> Self {
-        match value {
-            Error::WithSpan(e) => e,
-            Error::WithoutSpan(s) => syn::Error::new(Span::call_site(), s),
-        }
-    }
-}
-
-type Result<T> = std::result::Result<T, Error>;
-
-#[derive(Debug)]
-enum Predicate {
-    Literal(String),
-    Any(Vec<String>),
-    Not(Box<Predicate>),
-}
-
-impl Predicate {
-    fn matches(&self, value: &str) -> bool {
-        match self {
-            Self::Literal(literal) => value == literal,
-            Self::Not(pred) => !pred.matches(value),
-            Self::Any(options) => options.iter().any(|s| s == value),
-        }
-    }
-
-    fn parse(stream: TokenStream, span: Option<Span>) -> Result<Self> {
-        let mut iter = stream.into_iter();
-        let pred = match iter.next() {
-            None => return error(span, "a PAM implementation predicate must be provided"),
-            Some(TokenTree::Literal(lit)) => Self::Literal(Self::string_lit(lit)?),
-            Some(TokenTree::Ident(id)) => {
-                let next = Self::parens(iter.next(), span)?;
-                match id.to_string().as_str() {
-                    "not" => Self::Not(Box::new(Self::parse(next.stream(), Some(next.span()))?)),
-                    "any" => Self::Any(Self::parse_any(next)?),
-                    _ => return unexpected(&id.into(), "\"not\" or \"any\""),
-                }
-            }
-            Some(other) => return unexpected(&other, "\"not\", \"any\", or a string literal"),
-        };
-        // Check for anything after. We only allow a comma and nothing else.
-        if maybe_comma(iter.next())? {
-            if let Some(next) = iter.next() {
-                return unexpected(&next, "nothing");
-            }
-        }
-        Ok(pred)
-    }
-
-    fn parens(tree: Option<TokenTree>, mut span: Option<Span>) -> Result<Group> {
-        if let Some(tree) = tree {
-            span = Some(tree.span());
-            if let TokenTree::Group(g) = tree {
-                if g.delimiter() == Delimiter::Parenthesis {
-                    return Ok(g);
-                }
-            }
-        }
-        Err(Error::new(span, "expected function-call syntax"))
-    }
-
-    fn parse_any(g: Group) -> Result<Vec<String>> {
-        let mut output = Vec::new();
-        let mut iter = g.stream().into_iter();
-        loop {
-            match iter.next() {
-                None => break,
-                Some(TokenTree::Literal(lit)) => {
-                    output.push(Self::string_lit(lit)?);
-                    if !maybe_comma(iter.next())? {
-                        break;
-                    }
-                }
-                Some(other) => return unexpected(&other, "string literal"),
-            }
-        }
-        Ok(output)
-    }
-
-    fn string_lit(lit: Literal) -> Result<String> {
-        let tree: TokenTree = lit.clone().into();
-        match syn::parse2::<Lit>(tree.into())? {
-            Lit::Str(s) => Ok(s.value()),
-            _ => unexpected(&lit.into(), "string literal"),
-        }
-    }
-}
-
-fn error<T, M: Display>(span: Option<Span>, message: M) -> Result<T> {
-    Err(Error::new(span, message))
-}
-
-fn unexpected<T>(tree: &TokenTree, want: &str) -> Result<T> {
-    error(
-        Some(tree.span()),
-        format!("expected {want}; got unexpected token {tree}"),
-    )
-}
-
-fn maybe_comma(next: Option<TokenTree>) -> Result<bool> {
-    match next {
-        None => Ok(false),
-        Some(tree) => {
-            if let TokenTree::Punct(p) = &tree {
-                if p.as_char() == ',' {
-                    return Ok(true);
-                }
-            }
-            unexpected(&tree, "',' or ')'")
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    fn parse(tree: TokenStream) -> Predicate {
-        Predicate::parse(tree, None).unwrap()
-    }
-
-    #[test]
-    fn test_parse() {
-        macro_rules! cases {
-            ($(($($i:tt)*)),* $(,)?) => { [ $( quote!($($i)*) ),* ] };
-        }
-
-        let good = cases![
-            ("this"),
-            (any("this", "that", "the other")),
-            (not("the bees")),
-            (not(any("of", "those"))),
-            (not(not("saying it"))),
-            (any("trailing", "comma", "allowed",)),
-            ("even on a singleton",),
-            (not("forbidden here either",)),
-            (not(not(any("this", "is", "stupid"),),),),
-        ];
-        for tree in good {
-            parse(tree);
-        }
-        let bad = cases![
-            (),
-            (wrong),
-            (wheel::of::fortune),
-            ("invalid", "syntax"),
-            (any(any)),
-            (any),
-            (not),
-            (not(any)),
-            ("too many commas",,),
-            (any("too", "many",, "commas")),
-            (not("the commas",,,)),
-            (9),
-            (any("123", 8)),
-            (not(666)),
-        ];
-        for tree in bad {
-            Predicate::parse(tree, None).unwrap_err();
-        }
-    }
-
-    #[test]
-    fn test_match() {
-        macro_rules! cases {
-            ($(($e:expr, ($($i:tt)*))),* $(,)?) => {
-                [$(($e, quote!($($i)*))),*]
-            }
-        }
-        let matching = cases![
-            ("Sun", (any("Sun", "OpenPam"))),
-            ("OpenPam", (any("Sun", "OpenPam"))),
-            ("LinuxPam", (not("OpenPam"))),
-            ("XSso", (not("OpenPam"))),
-            ("Other", (not(any("This", "That")))),
-            ("OpenPam", (not(not("OpenPam")))),
-            ("Anything", (not(any()))),
-        ];
-        for (good, tree) in matching {
-            let pred = parse(tree);
-            assert!(pred.matches(good))
-        }
-
-        let nonmatching = cases![
-            ("LinuxPam", (not("LinuxPam"))),
-            ("Sun", ("LinuxPam")),
-            ("OpenPam", (any("LinuxPam", "Sun"))),
-            ("One", (not(any("One", "Another")))),
-            ("Negatory", (not(not("Affirmative")))),
-            ("XSso", ("OpenPam")),
-            ("OpenPam", ("XSso")),
-        ];
-        for (bad, tree) in nonmatching {
-            let pred = parse(tree);
-            assert!(!pred.matches(bad))
-        }
-    }
-}
--- a/libpam-sys/libpam-sys-test/Cargo.toml	Wed Jul 02 03:33:09 2025 -0400
+++ b/libpam-sys/libpam-sys-test/Cargo.toml	Thu Jul 03 11:03:36 2025 -0400
@@ -13,7 +13,7 @@
 [build-dependencies]
 bindgen = "0.72.0"
 ctest = "0.4.11"
-libpam-sys-impls = { path = "../libpam-sys-impls" }
+libpam-sys = { path = ".." }
 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 02 03:33:09 2025 -0400
+++ b/libpam-sys/libpam-sys-test/build.rs	Thu Jul 03 11:03:36 2025 -0400
@@ -1,5 +1,5 @@
 use bindgen::MacroTypeVariation;
-use libpam_sys_impls::__pam_impl_enum__;
+use libpam_sys::pam_impl::PamImpl;
 use proc_macro2::{Group, Ident, TokenStream, TokenTree};
 use quote::{format_ident, ToTokens};
 use std::path::Path;
@@ -8,9 +8,6 @@
 use std::{env, fs};
 use syn::{Item, ItemConst};
 
-// We're using the macro directly so we can match exhaustively.
-__pam_impl_enum__!();
-
 const REDIR_FD: &str = "pam_modutil_redirect_fd";
 
 fn main() {
@@ -49,6 +46,7 @@
             headers: vec!["\"xsso_pam_appl.h\""],
             ..Default::default()
         },
+        other => panic!("PAM implementation {other:?} is not yet tested"),
     };
     generate_const_test(&config);
     generate_ctest(&config);
--- a/libpam-sys/src/constants.rs	Wed Jul 02 03:33:09 2025 -0400
+++ b/libpam-sys/src/constants.rs	Thu Jul 03 11:03:36 2025 -0400
@@ -3,8 +3,6 @@
 //! These constants are tested on a per-platform basis by `libpam-sys-test`'s
 //! `test_constants.rs`.
 
-use libpam_sys_impls::cfg_pam_impl;
-
 /// Macro to make defining a bunch of constants way easier.
 macro_rules! define {
     ($(#[$attr:meta])* $($name:ident = $value:expr);+$(;)?) => {
@@ -70,9 +68,9 @@
     PAM_MAX_RESP_SIZE = 512;
 );
 
-#[cfg_pam_impl("LinuxPam")]
+#[cfg(pam_impl = "LinuxPam")]
 pub use linux_pam::*;
-#[cfg_pam_impl("LinuxPam")]
+#[cfg(pam_impl = "LinuxPam")]
 mod linux_pam {
     c_enum!(
         /// An error return code.
@@ -147,9 +145,9 @@
     pub const PAM_MODUTIL_NGROUPS: i32 = 64;
 }
 
-#[cfg_pam_impl(any("OpenPam", "Sun", "XSso"))]
+#[cfg(any(pam_impl = "OpenPam", pam_impl = "Sun", pam_impl = "XSso"))]
 pub use xsso_shared::*;
-#[cfg_pam_impl(any("OpenPam", "Sun", "XSso"))]
+#[cfg(any(pam_impl = "OpenPam", pam_impl = "Sun", pam_impl = "XSso"))]
 mod xsso_shared {
     c_enum!(
         /// An error return code.
@@ -206,9 +204,9 @@
     );
 }
 
-#[cfg_pam_impl("OpenPam")]
+#[cfg(pam_impl = "OpenPam")]
 pub use openpam::*;
-#[cfg_pam_impl("OpenPam")]
+#[cfg(pam_impl = "OpenPam")]
 mod openpam {
     c_enum!(
         /// An error return code.
@@ -266,9 +264,9 @@
 }
 
 /// Constants exclusive to Illumos.
-#[cfg_pam_impl("Sun")]
+#[cfg(pam_impl = "Sun")]
 pub use sun::*;
-#[cfg_pam_impl("Sun")]
+#[cfg(pam_impl = "Sun")]
 mod sun {
     /// The total number of PAM error codes.
     pub const PAM_TOTAL_ERRNUM: i32 = 28;
--- a/libpam-sys/src/ffi.rs	Wed Jul 02 03:33:09 2025 -0400
+++ b/libpam-sys/src/ffi.rs	Thu Jul 03 11:03:36 2025 -0400
@@ -115,7 +115,7 @@
 }
 
 /// Definition of the PAM_XAUTHDATA item. Compatible with `xcb_auth_info_t`.
-#[cfg(_hack_impl = "LinuxPam")]
+#[cfg(pam_impl = "LinuxPam")]
 #[repr(C)]
 pub struct pam_xauth_data {
     namelen: c_int,
@@ -124,7 +124,7 @@
     data: *mut c_char,
 }
 
-#[cfg(_hack_impl = "LinuxPam")]
+#[cfg(pam_impl = "LinuxPam")]
 #[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
 #[repr(i32)]
 pub enum pam_modutil_redirect_fd {
@@ -133,10 +133,10 @@
     PAM_MODUTIL_NULL_FD,
 }
 
-#[cfg(_hack_impl = "LinuxPam")]
+#[cfg(pam_impl = "LinuxPam")]
 pub use pam_modutil_redirect_fd::*;
 
-#[cfg(_hack_impl = "LinuxPam")]
+#[cfg(pam_impl = "LinuxPam")]
 #[derive(Debug)]
 #[repr(C)]
 pub struct pam_modutil_privs {
@@ -148,7 +148,7 @@
     is_dropped: c_int,
 }
 
-#[cfg(_hack_impl = "OpenPam")]
+#[cfg(pam_impl = "OpenPam")]
 pub type pam_func_t = unsafe extern "C" fn(
     handle: *mut pam_handle,
     flags: c_int,
@@ -156,7 +156,7 @@
     argv: *const *const c_char,
 ) -> c_int;
 
-#[cfg(_hack_impl = "OpenPam")]
+#[cfg(pam_impl = "OpenPam")]
 #[derive(Debug)]
 #[repr(C)]
 pub struct pam_module {
@@ -165,7 +165,7 @@
     dlh: *mut c_void,
 }
 
-#[cfg(_hack_impl = "OpenPam")]
+#[cfg(pam_impl = "OpenPam")]
 #[derive(Debug)]
 #[repr(C)]
 pub struct pam_repository {
@@ -175,6 +175,7 @@
 }
 
 // These are the functions specified in X/SSO. Everybody exports them.
+#[link(name = "pam")]
 extern "C" {
     /// Account validation.
     pub fn pam_acct_mgmt(pamh: *mut pam_handle, flags: c_int) -> c_int;
@@ -272,11 +273,11 @@
     pub fn pam_strerror(pamh: *const pam_handle, error_number: c_int) -> *mut c_char;
 }
 
-// We use `_hack_impl` because ctest loses its mind
+// We use `pam_impl` because ctest loses its mind
 // when it comes across the `cfg_pam_impl` macro.
 // This is a custom cfg variable set in our build.rs. Don't do this; just use
 // cfg_pam_impl.
-#[cfg(any(_hack_impl = "LinuxPam", _hack_impl = "OpenPam"))]
+#[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(
@@ -296,7 +297,7 @@
 
 }
 
-#[cfg(_hack_impl = "LinuxPam")]
+#[cfg(pam_impl = "LinuxPam")]
 extern "C" {
     pub fn pam_fail_delay(pamh: *mut pam_handle, musec_delay: c_uint) -> c_int;
 
@@ -401,7 +402,7 @@
     ) -> *mut c_char;
 }
 
-#[cfg(_hack_impl = "OpenPam")]
+#[cfg(pam_impl = "OpenPam")]
 extern "C" {
     pub fn openpam_borrow_cred(pamh: *mut pam_handle, passwd: *const libc::passwd) -> c_int;
 
--- a/libpam-sys/src/helpers.rs	Wed Jul 02 03:33:09 2025 -0400
+++ b/libpam-sys/src/helpers.rs	Thu Jul 03 11:03:36 2025 -0400
@@ -1,7 +1,6 @@
 //! This module contains a few non-required helpers to deal with some of the
 //! more annoying memory management in the PAM API.
 
-use super::cfg_pam_impl;
 use std::error::Error;
 use std::marker::{PhantomData, PhantomPinned};
 use std::mem::ManuallyDrop;
@@ -133,30 +132,6 @@
         slice::from_raw_parts(*ptr_ptr.cast(), count).iter()
     }
 
-    #[cfg_pam_impl("LinuxPam")]
-    unsafe fn _iter_over<'a, Src>(
-        ptr_ptr: *const *const Src,
-        count: usize,
-    ) -> impl Iterator<Item = &'a T>
-    where
-        T: 'a,
-    {
-        #[allow(deprecated)]
-        Self::iter_over_linux(ptr_ptr, count)
-    }
-
-    #[cfg_pam_impl(not("LinuxPam"))]
-    unsafe fn _iter_over<'a, Src>(
-        ptr_ptr: *const *const Src,
-        count: usize,
-    ) -> impl Iterator<Item = &'a T>
-    where
-        T: 'a,
-    {
-        #[allow(deprecated)]
-        Self::iter_over_xsso(ptr_ptr, count)
-    }
-
     /// Iterates over a PAM message list appropriate to your system's impl.
     ///
     /// This selects the correct pointer/array structure to use for a message
@@ -166,6 +141,7 @@
     ///
     /// `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,
@@ -173,7 +149,10 @@
     where
         T: 'a,
     {
-        Self::_iter_over(ptr_ptr, count)
+        #[cfg(pam_impl = "LinuxPam")]
+        return Self::iter_over_linux(ptr_ptr, count);
+        #[cfg(not(pam_impl = "LinuxPam"))]
+        return Self::iter_over_xsso(ptr_ptr, count);
     }
 
     fn assert_size<That>() {
--- a/libpam-sys/src/lib.rs	Wed Jul 02 03:33:09 2025 -0400
+++ b/libpam-sys/src/lib.rs	Thu Jul 03 11:03:36 2025 -0400
@@ -2,54 +2,12 @@
 //!
 //! ## PAM implementation
 //!
-#![doc = concat!("This documentation was built for the **", __pam_impl_name__!(), "** implementation.")]
-
-use libpam_sys_impls::{__pam_impl_enum__, __pam_impl_name__};
-
-/// A `cfg`-like attribute macro for code specific to one PAM implementation.
-///
-/// Different versions of PAM export different functions and have some
-/// meaningful internal implementation differences, like the way `pam_conv`
-/// is handled (see [the Linux-PAM man page for details][man7]).
-///
-/// This macro will let you figure out which PAM you're compiling
-/// (and eventually running) against so you can make those critical changes.
-///
-/// The implementation names are the same as those in the [`PamImpl`] enum.
-///
-/// ```
-/// use libpam_sys::cfg_pam_impl;
-///
-/// #[cfg_pam_impl("Sun")]
-/// fn do_something() { /* Illumos/Solaris-only code */ }
-///
-/// #[cfg_pam_impl(not("Sun"))]
-/// fn do_something() { /* non-Illumos code */ }
-///
-/// #[cfg_pam_impl(any("LinuxPam", "XSso"))]
-/// fn do_something_else() { /* Linux-PAM or X/SSO-spec PAM */ }
-///
-/// #[cfg_pam_impl(not(any("Sun", "OpenPam")))]
-/// fn do_a_third_thing() { /* Neither Sun nor OpenPAM */ }
-///
-/// #[cfg_pam_impl(any())]
-/// fn this_will_never_build() { /* why would you do this? */ }
-///
-/// #[cfg_pam_impl(not(any()))]
-/// fn this_will_always_build() { /* I, sure, whatever, you do you. */ }
-/// ```
-///
-/// [man7]: https://man7.org/linux/man-pages/man3/pam_conv.3.html
-#[doc(inline)]
-pub use libpam_sys_impls::cfg_pam_impl;
+#![doc = concat!("This documentation was built for the **", env!("LIBPAMSYS_IMPL"), "** implementation.")]
 
 mod constants;
 mod ffi;
 pub mod helpers;
+pub mod pam_impl;
 
 #[doc(inline)]
-pub use crate::{constants::*, ffi::*};
-
-// Looking for the actual code defining this enum?
-// It's in the build.rs file for libpam_sys_impls.
-__pam_impl_enum__!(#[non_exhaustive]);
+pub use crate::{constants::*, ffi::*, pam_impl::*};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpam-sys/src/pam_impl.rs	Thu Jul 03 11:03:36 2025 -0400
@@ -0,0 +1,121 @@
+/// An enum that knows its own values.
+macro_rules! self_aware_enum {
+        (
+            $(#[$enumeta:meta])*
+            $viz:vis enum $name:ident {
+                $(
+                    $(#[$itemeta:meta])*
+                    $item:ident,
+                )*
+            }
+        ) => {
+            $(#[$enumeta])*
+            $viz enum $name {
+                $(
+                    $(#[$itemeta])*
+                    $item,
+                )*
+            }
+
+            // The implementations in this block are private for now
+            // to avoid putting a contract into the public API.
+            #[allow(dead_code)]
+            impl $name {
+                /// Iterator over the items in the enum. For internal use.
+                fn items() -> Vec<Self> {
+                    vec![$(Self::$item),*]
+                }
+
+                /// Attempts to parse the enum from the string. For internal use.
+                fn try_from(value: &str) -> Result<Self, String> {
+                    match value {
+                        $(stringify!($item) => Ok(Self::$item),)*
+                        _ => Err(value.into()),
+                    }
+                }
+            }
+        };
+    }
+
+self_aware_enum! {
+    /// The PAM implementations supported by `libpam-sys`.
+    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+    #[cfg_attr(pam_impl, non_exhaustive)]
+    pub enum PamImpl {
+        /// [Linux-PAM] is provided by most Linux distributions.
+        ///
+        /// [Linux-PAM]: https://github.com/linux-pam/linux-pam
+        LinuxPam,
+        /// [OpenPAM] is used by most BSDs, including Mac OS X.
+        ///
+        /// [OpenPAM]: https://git.des.dev/OpenPAM/OpenPAM
+        OpenPam,
+        /// Illumos and Solaris use a derivative of [Sun's implementation][sun].
+        ///
+        /// [sun]: https://code.illumos.org/plugins/gitiles/illumos-gate/+/refs/heads/master/usr/src/lib/libpam
+        Sun,
+        /// Only the functionality and constants in [the PAM spec].
+        ///
+        /// [the PAM spec]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm
+        XSso,
+    }
+}
+
+#[cfg(pam_impl)]
+include!(concat!(env!("OUT_DIR"), "/pam_impl_const.rs"));
+
+/// Generates `cargo` directives for build scripts to enable `cfg(pam_impl)`.
+///
+/// Print this in your `build.rs` script to be able to use the custom `pam_impl`
+/// configuration directive.
+///
+/// ```
+/// // build.rs
+/// use libpam_sys::pam_impl;
+/// fn main() {
+///     println!("{}", pam_impl::enable_pam_impl_cfg())
+///
+///     // Whatever other stuff you do in your build.rs.
+/// }
+/// ```
+///
+/// This will set the current `pam_impl` as well as registering all known
+/// PAM implementations with `rustc-check-cfg` so you can easily use them:
+///
+/// ```
+/// #[cfg(pam_impl = "OpenPam")]
+/// fn openpam_specific_func(handle: *const libpam_sys::pam_handle) {
+///     let environ = libpam_sys::pam_getenvlist(handle);
+///     // ...
+///     libpam_sys::openpam_free_envlist()
+/// }
+///
+/// // This will give you a warning since "UnknownImpl" is not in the cfg.
+/// #[cfg(not(pam_impl = "UnknownImpl"))]
+/// fn do_something() {
+///     // ...
+/// }
+/// ```
+#[cfg(pam_impl)]
+pub fn enable_pam_impl_cfg() -> String {
+    format!("{}\n{}", checkcfg(), implcfg(PamImpl::CURRENT_STR))
+}
+
+/// Generates the `cargo:rustc-check-cfg` directive to print in build scripts.
+fn checkcfg() -> String {
+    let impls: Vec<_> = PamImpl::items()
+        .into_iter()
+        .map(|i| format!(r#""{i:?}""#))
+        .collect();
+    format!(
+        "\
+            cargo:rustc-check-cfg=cfg(pam_impl)\n\
+            cargo:rustc-check-cfg=cfg(pam_impl, values({impls}))\
+        ",
+        impls = impls.join(",")
+    )
+}
+
+fn implcfg(name: &str) -> String {
+    format!("cargo:rustc-cfg=pam_impl\ncargo:rustc-cfg=pam_impl={name:?}")
+}
--- a/src/libpam/handle.rs	Wed Jul 02 03:33:09 2025 -0400
+++ b/src/libpam/handle.rs	Thu Jul 03 11:03:36 2025 -0400
@@ -14,7 +14,6 @@
 use std::cell::Cell;
 use std::ffi::{c_char, c_int, CString};
 
-use libpam_sys::cfg_pam_impl;
 use std::ptr;
 use std::ptr::NonNull;
 
@@ -279,7 +278,7 @@
 }
 
 impl PamHandleModule for LibPamHandle {
-    #[cfg_pam_impl(any("LinuxPam", "OpenPam"))]
+    #[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))]
     fn authtok(&mut self, prompt: Option<&str>) -> Result<String> {
         let prompt = memory::option_cstr(prompt)?;
         let mut output: *const c_char = ptr::null_mut();
@@ -299,7 +298,7 @@
             .unwrap_or(Err(ErrorCode::ConversationError))
     }
 
-    #[cfg_pam_impl(not(any("LinuxPam", "OpenPam")))]
+    #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))]
     fn authtok(&mut self, prompt: Option<&str>) -> Result<String> {
         Err(ErrorCode::ConversationError)
     }
--- a/src/logging.rs	Wed Jul 02 03:33:09 2025 -0400
+++ b/src/logging.rs	Thu Jul 03 11:03:36 2025 -0400
@@ -15,16 +15,14 @@
 //! and may even itself implement `log::Log`, but that interface is not exposed
 //! to the generic PAM user.
 
-use libpam_sys::cfg_pam_impl;
-
-#[cfg_pam_impl("OpenPam")]
+#[cfg(pam_impl = "OpenPam")]
 mod levels {
     pub const ERROR: i32 = libpam_sys::PAM_LOG_ERROR;
     pub const WARN: i32 = libpam_sys::PAM_LOG_NOTICE;
     pub const INFO: i32 = libpam_sys::PAM_LOG_VERBOSE;
     pub const DEBUG: i32 = libpam_sys::PAM_LOG_DEBUG;
 }
-#[cfg_pam_impl(not("OpenPam"))]
+#[cfg(not(pam_impl = "OpenPam"))]
 mod levels {
     pub const ERROR: i32 = libc::LOG_ERR;
     pub const WARN: i32 = libc::LOG_WARNING;
@@ -170,7 +168,7 @@
 /// # let userinfo_url = "https://zombo.com/";
 /// nonstick::debug!(pam_handle, "making HTTP GET request to {userinfo_url}");
 /// // Will log a message like
-/// // making HTTP GET request to https://zombo.com/"
+/// //     "making HTTP GET request to https://zombo.com/"
 /// // at DEBUG level on syslog.
 /// # }
 /// ```