Mercurial > crates > nonstick
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. /// # } /// ```