Mercurial > crates > nonstick
changeset 108:e97534be35e3
Make some proc macros for doing cfg-like stuff for PAM impls.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sat, 28 Jun 2025 00:34:45 -0400 |
parents | 49c6633f6fd2 |
children | bb465393621f |
files | Cargo.lock Cargo.toml build.rs libpam-sys/Cargo.toml libpam-sys/README.md libpam-sys/build.rs libpam-sys/libpam-sys-impls/Cargo.toml libpam-sys/libpam-sys-impls/build.rs libpam-sys/libpam-sys-impls/src/lib.rs libpam-sys/src/constants.rs libpam-sys/src/lib.rs src/constants.rs src/libpam/answer.rs src/libpam/pam_ffi.rs src/libpam/question.rs src/logging.rs testharness/Cargo.toml testharness/container/Containerfile.orig |
diffstat | 18 files changed, 542 insertions(+), 234 deletions(-) [+] |
line wrap: on
line diff
--- a/Cargo.lock Thu Jun 26 22:42:32 2025 -0400 +++ b/Cargo.lock Sat Jun 28 00:34:45 2025 -0400 @@ -145,6 +145,12 @@ checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] name = "home" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -217,9 +223,21 @@ [[package]] name = "libpam-sys" -version = "0.0.8-alpha0" +version = "0.1.0" dependencies = [ "bindgen 0.69.5", + "libpam-sys-impls", +] + +[[package]] +name = "libpam-sys-impls" +version = "0.0.1" +dependencies = [ + "bindgen 0.72.0", + "proc-macro2", + "quote", + "strum", + "syn", ] [[package]] @@ -272,6 +290,7 @@ "bindgen 0.72.0", "bitflags", "libc", + "libpam-sys", "memoffset", "num_enum", "regex", @@ -473,6 +492,28 @@ 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", + "quote", + "rustversion", + "syn", +] + +[[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index"
--- a/Cargo.toml Thu Jun 26 22:42:32 2025 -0400 +++ b/Cargo.toml Sat Jun 28 00:34:45 2025 -0400 @@ -1,5 +1,5 @@ [workspace] -members = ["libpam-sys", "testharness"] +members = ["libpam-sys", "libpam-sys/libpam-sys-impls", "testharness"] resolver = "2" [workspace.package] @@ -29,10 +29,10 @@ # with your system's PAM. link = [] -# Enable this to get access to Linux-PAM extensions. -linux-pam-extensions = [] -# Enable this to get access to OpenPAM extensions. -openpam-extensions = [] +basic-ext = [] +illumos-ext = [] +linux-pam-ext = [] +openpam-ext = [] # This feature exists only for testing. test-install = [] @@ -42,6 +42,7 @@ libc = "0.2.97" memoffset = "0.9.1" num_enum = "0.7.3" +libpam-sys = { path = "libpam-sys" } [dev-dependencies] regex = "1.11.1"
--- a/build.rs Thu Jun 26 22:42:32 2025 -0400 +++ b/build.rs Sat Jun 28 00:34:45 2025 -0400 @@ -11,7 +11,7 @@ .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .blocklist_type("pam_handle") .blocklist_type("pam_conv") - .allowlist_var(".*") + .blocklist_var(".*") .allowlist_function("pam_start") .allowlist_function("pam_[gs]et_item") .allowlist_function("pam_get_(user|authtok)")
--- a/libpam-sys/Cargo.toml Thu Jun 26 22:42:32 2025 -0400 +++ b/libpam-sys/Cargo.toml Sat Jun 28 00:34:45 2025 -0400 @@ -2,18 +2,14 @@ name = "libpam-sys" description = "Low-level bindings for PAM (Pluggable Authentication Modules)" links = "pam" -version.workspace = true +version = "0.1.0" authors.workspace = true repository.workspace = true edition.workspace = true rust-version.workspace = true -[features] -default = ["basic-ext"] -use-system-headers = [] -basic-ext = [] -linux-pam-ext = [] -openpam-ext = [] +[dependencies] +libpam-sys-impls = { path = "libpam-sys-impls" } [build-dependencies] -bindgen = "0.69.5" \ No newline at end of file +bindgen = "0.69.5"
--- a/libpam-sys/README.md Thu Jun 26 22:42:32 2025 -0400 +++ b/libpam-sys/README.md Sat Jun 28 00:34:45 2025 -0400 @@ -2,17 +2,17 @@ This crate provides low-level access to PAM. -## Options +## Configuration -With no features at all enabled, only the functions specified by the [X/SSO PAM specification][xsso] are available. +By default, this crate guesses your system's PAM implementation based upon your OS. -- `extras` (enabled by default): Common extensions to PAM, shared by both OpenPAM and Linux-PAM. - This includes, most notably, `pam_get_authtok`. - *Illumos does not provide this.* -- `illumos-ext`: Extensions provided only by Illumos. -- `linux-pam-ext`: Extensions provided only by Linux-PAM. -- `openpam-ext`: Extensions provided by OpenPAM. -- `use-system-headers`: Source constants from your PAM system headers. +- Linux: `LinuxPam` +- BSDs, including Mac OS: `OpenPam` +- Illumos/Solaris: `Illumos` +- Unknown: `OpenPamMinimal` + +Each implementation exports all the functionality available in its respective PAM library. +`OpenPamMinimal` is a subset that includes only the functions available in the spec, and the constants shared in common between OpenPAM and Illumos' implementation. ## References
--- a/libpam-sys/build.rs Thu Jun 26 22:42:32 2025 -0400 +++ b/libpam-sys/build.rs Sat Jun 28 00:34:45 2025 -0400 @@ -1,143 +1,3 @@ -use bindgen::MacroTypeVariation; -use std::error::Error; -use std::fmt::{Debug, Display, Formatter}; -use std::path::PathBuf; -use std::{env, fs}; - -enum PamImpl { - Illumos, - LinuxPam, - OpenPam, -} - -#[derive(Debug)] -struct InvalidEnum(String); - -impl Display for InvalidEnum { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "invalid PAM impl {:?}", self.0) - } -} - -impl Error for InvalidEnum {} - -impl TryFrom<&str> for PamImpl { - type Error = InvalidEnum; - fn try_from(value: &str) -> Result<Self, Self::Error> { - Ok(match value { - "illumos" => Self::Illumos, - "linux-pam" => Self::LinuxPam, - "openpam" => Self::OpenPam, - other => return Err(InvalidEnum(other.to_owned())), - }) - } -} - -impl Debug for PamImpl { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt( - match self { - Self::Illumos => "illumos", - Self::LinuxPam => "linux-pam", - Self::OpenPam => "openpam", - }, - f, - ) - } -} - fn main() { println!("cargo:rustc-link-lib=pam"); - let out_file = PathBuf::from(env::var("OUT_DIR").unwrap()).join("constants.rs"); - let pam_impl = do_detection(); - - if cfg!(feature = "use-system-headers") { - let builder = bindgen::Builder::default() - .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - .blocklist_function(".*") - .blocklist_type(".*") - .allowlist_var(".*") - .default_macro_constant_type(MacroTypeVariation::Unsigned); - - let builder = match pam_impl { - PamImpl::Illumos => builder.header_contents( - "illumos.h", - "\ - #include <security/pam_appl.h> - #include <security/pam_modules.h> - ", - ), - PamImpl::LinuxPam => builder.header_contents( - "linux-pam.h", - "\ - #include <security/_pam_types.h> - #include <security/pam_appl.h> - #include <security/pam_ext.h> - #include <security/pam_modules.h> - ", - ), - PamImpl::OpenPam => builder.header_contents( - "openpam.h", - "\ - #include <security/pam_types.h> - #include <security/openpam.h> - #include <security/pam_appl.h> - #include <security/pam_constants.h> - ", - ), - }; - let bindings = builder.generate().unwrap(); - bindings.write_to_file(out_file).unwrap(); - } else { - // Just write empty data to the file to avoid conditional compilation - // shenanigans. - fs::write(out_file, "").unwrap(); - } } - -fn do_detection() -> PamImpl { - println!(r#"cargo:rustc-check-cfg=cfg(pam_impl, values("illumos", "linux-pam", "openpam"))"#); - let pam_impl = _detect_internal(); - println!("cargo:rustc-cfg=pam_impl={pam_impl:?}"); - pam_impl -} - -fn _detect_internal() -> PamImpl { - if let Some(pam_impl) = option_env!("LIBPAMSYS_PAM_IMPL") { - pam_impl.try_into().unwrap() - } else if cfg!(feature = "use-system-headers") { - // Detect which impl it is from system headers. - if header_exists("security/_pam_types.h") { - PamImpl::LinuxPam - } else if header_exists("security/openpam.h") { - PamImpl::OpenPam - } else if header_exists("security/pam_appl.h") { - PamImpl::Illumos - } else { - panic!("could not detect PAM implementation") - } - } else { - // Otherwise, guess what PAM impl we're using based on the OS. - 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 { - PamImpl::Illumos - } - } -} - -fn header_exists(header: &str) -> bool { - bindgen::Builder::default() - .blocklist_item(".*") - .header_contents("header.h", &format!("#include <{header}>")) - .generate() - .is_ok() -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-impls/Cargo.toml Sat Jun 28 00:34:45 2025 -0400 @@ -0,0 +1,20 @@ +[package] +name = "libpam-sys-impls" +description = "Macros for use in libpam-sys." +version = "0.0.1" +edition = "2021" + +[lib] +proc-macro = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +bindgen = "0.72.0" +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"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-impls/build.rs Sat Jun 28 00:34:45 2025 -0400 @@ -0,0 +1,60 @@ +use strum::EnumString; + +fn main() { + let pam_impl = match option_env!("LIBPAMSYS_IMPL") { + // The default option: Guess what PAM impl we're using based on OS. + None => { + // Otherwise, guess what PAM impl we're using based on the OS. + 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()) { + PamImpl::Illumos + } else { + PamImpl::MinimalOpenPam + } + } + Some("_detect") => { + // Detect which impl it is from system headers. + if header_exists("security/_pam_types.h") { + PamImpl::LinuxPam + } else if header_exists("security/openpam.h") { + PamImpl::OpenPam + } else if header_exists("security/pam_appl.h") { + // We figure we're *probably* on Illumos or something like that. + PamImpl::Illumos + } else { + // If all else fails, assume the bare minimum. + PamImpl::MinimalOpenPam + } + } + Some(other) => match PamImpl::try_from(other) { + Ok(i) => i, + Err(_) => panic!("unknown PAM implementation {other:?}") + } + }; + println!("cargo:rustc-env=LIBPAMSYS_IMPL={pam_impl:?}"); +} + +#[derive(Debug, EnumString)] +enum PamImpl { + Illumos, + LinuxPam, + OpenPam, + MinimalOpenPam, +} + +fn header_exists(header: &str) -> bool { + bindgen::Builder::default() + .blocklist_item(".*") + .header_contents("header.h", &format!("#include <{header}>")) + .generate() + .is_ok() +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/libpam-sys-impls/src/lib.rs Sat Jun 28 00:34:45 2025 -0400 @@ -0,0 +1,321 @@ +use proc_macro as pm; +use proc_macro2::{Delimiter, Group, Literal, Span, TokenStream, TokenTree}; +use quote::{format_ident, quote}; +use std::fmt::Display; +use syn::Lit; + +/// 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]). +/// +/// ``` +/// # use libpam_sys_impls::cfg_pam_impl; +/// #[cfg_pam_impl("illumos")] +/// fn do_something() { /* illumos-only code */ } +/// +/// #[cfg_pam_impl(not("illumos"))] +/// fn do_something() { /* non-illumos code */ } +/// +/// #[cfg_pam_impl(any("linux-pam", "openpam"))] +/// fn do_something_else() { /* Linux-PAM or OpenPAM */ } +/// +/// #[cfg_pam_impl(not(any("illumos", "openpam")))] +/// fn do_a_third_thing() { /* Neither Illumos nor OpenPAM */ } +/// +/// #[cfg_pam_impl(any())] +/// fn this_will_never_build() { /* why would you do this? */ } +/// ``` +/// +/// [man7]: https://man7.org/linux/man-pages/man3/pam_conv.3.html +#[proc_macro_attribute] +pub fn cfg_pam_impl(attr: pm::TokenStream, item: pm::TokenStream) -> pm::TokenStream { + eprintln!("Got TokenStream: {:?}", attr); + Predicate::parse(attr.into(), None) + .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()) +} + +#[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 ')'") + } + } +} + +/// A proc macro that outputs the PAM implementation macro and const. +#[proc_macro] +pub fn pam_impl_enum(data: pm::TokenStream) -> pm::TokenStream { + if !data.is_empty() { panic!("unexpected stuff in pam_impl_enum!()") } + + let variant = format_ident!("{}", pam_impl_str()); + + quote!( + /// The PAM implementations supported by `libpam-sys`. + #[non_exhaustive] + #[derive(Clone, Copy, Debug, PartialEq)] + pub enum PamImpl { + /// [Linux-PAM] is provided by most Linux implementations. + /// + /// [Linux-PAM]: https://github.com/linux-pam/linux-pam + LinuxPam, + /// [OpenPAM] is used by most BSD distributions, including Mac OS X. + /// + /// [OpenPAM]: https://git.des.dev/OpenPAM/OpenPAM + OpenPam, + /// [Illumos PAM] is used on Illumos and Solaris systems. + /// + /// [Illumos PAM]: https://code.illumos.org/plugins/gitiles/illumos-gate/+/refs/heads/master/usr/src/lib/libpam + Illumos, + /// Only the functionality in [the PAM spec], + /// with OpenPAM/Illumos constants. + /// + /// [the PAM spec]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm + MinimalOpenPam, + } + + #[doc = concat!("This version of libpam-sys was built for **", stringify!(#variant), "**.")] + pub const LIBPAMSYS_IMPL: PamImpl = PamImpl::#variant; + ).into() +} + +/// String literal of the name of the PAM implementation this was built for. +/// +/// The value is the string value of `libpamsys::PamImpl`. +#[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") +} + +#[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![ + ("Illumos", (any("Illumos", "OpenPam"))), + ("LinuxPam", (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"))), + ("Illumos", ("LinuxPam")), + ("OpenPam", (any("LinuxPam", "Illumos"))), + ("One", (not(any("One", "Another")))), + ("Negatory", (not(not("Affirmative")))), + ]; + for (bad, tree) in nonmatching { + let pred = parse(tree); + assert!(!pred.matches(bad)) + } + } +}
--- a/libpam-sys/src/constants.rs Thu Jun 26 22:42:32 2025 -0400 +++ b/libpam-sys/src/constants.rs Sat Jun 28 00:34:45 2025 -0400 @@ -1,17 +1,20 @@ //! All the constants. -#![allow(dead_code)] + +// We have to enable these otherwise we get lit up with warnings +// during conditional compilation. +#![allow(dead_code, unused_imports)] /// 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: u32 = $value;)+ - ); - }; - (@meta $m:tt $($i:item)+) => { define!(@expand $($m $i)+); }; - (@expand $({ $(#[$m:meta])* } $i:item)+) => {$($(#[$m])* $i)+}; - } + ($(#[$attr:meta])* $($name:ident = $value:expr);+$(;)?) => { + define!( + @meta { $(#[$attr])* } + $(pub const $name: u32 = $value;)+ + ); + }; + (@meta $m:tt $($i:item)+) => { define!(@expand $($m $i)+); }; + (@expand $({ $(#[$m:meta])* } $i:item)+) => {$($(#[$m])* $i)+}; +} #[cfg(feature = "use-system-headers")] pub use system_headers::*; @@ -55,7 +58,7 @@ pub use super::linux_pam::*; #[cfg(not(pam_impl = "linux-pam"))] - pub use super::shared::*; + pub use super::illumos_openpam::*; #[cfg(pam_impl = "illumos")] pub use super::illumos::*; @@ -66,7 +69,7 @@ /// Constants extracted from PAM header files. mod system_headers { - include!(concat!(env!("OUT_DIR"), "/constants.rs")); + // include!(concat!(env!("OUT_DIR"), "/constants.rs")); } /// Constants used by Linux-PAM. @@ -114,8 +117,13 @@ 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; ); define!( @@ -138,7 +146,7 @@ } /// Constants shared between Illumos and OpenPAM. -mod shared { +mod illumos_openpam { define!( /// An error code. PAM_OPEN_ERR = 1;
--- a/libpam-sys/src/lib.rs Thu Jun 26 22:42:32 2025 -0400 +++ b/libpam-sys/src/lib.rs Sat Jun 28 00:34:45 2025 -0400 @@ -1,22 +1,25 @@ #![doc = include_str!("../README.md")] //! -//! ## Version info -//! -//! This documentation was built with the following configuration: -//! -#![cfg_attr(pam_impl = "illumos", doc = "- PAM implementation: Illumos")] -#![cfg_attr(pam_impl = "linux-pam", doc = "- PAM implementation: Linux-PAM")] -#![cfg_attr(pam_impl = "openpam", doc = "- PAM implementation: OpenPAM")] +//! ## PAM implementation //! -#![cfg_attr(feature = "basic-ext", doc = "- Includes common extensions")] -#![cfg_attr(feature = "linux-pam-ext", doc = "- Includes Linux-PAM extensions")] -#![cfg_attr(feature = "openpam-ext", doc = "- Includes OpenPAM extensions")] -#![cfg_attr( - feature = "use-system-headers", - doc = "- Built with system header files" -)] +#![doc = concat!("This documentation was built for the **", pam_impl_name!(), "** implementation.")] + mod constants; + +libpam_sys_impls::pam_impl_enum!(); + +#[doc(inline)] +pub use libpam_sys_impls::{cfg_pam_impl, pam_impl_name}; + pub mod helpers; #[doc(inline)] pub use constants::*; + +#[cfg(test)] +mod tests { + #[test] + fn test() { + panic!("The pam impl is {:?}", super::LIBPAMSYS_IMPL); + } +}
--- a/src/constants.rs Thu Jun 26 22:42:32 2025 -0400 +++ b/src/constants.rs Sat Jun 28 00:34:45 2025 -0400 @@ -192,11 +192,13 @@ Ignore = pam_ffi::PAM_IGNORE, Abort = pam_ffi::PAM_ABORT, AuthTokExpired = pam_ffi::PAM_AUTHTOK_EXPIRED, + #[cfg(feature = "basic-ext")] ModuleUnknown = pam_ffi::PAM_MODULE_UNKNOWN, + #[cfg(feature = "basic-ext")] BadItem = pam_ffi::PAM_BAD_ITEM, - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] ConversationAgain = pam_ffi::PAM_CONV_AGAIN, - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] Incomplete = pam_ffi::PAM_INCOMPLETE, } @@ -246,8 +248,8 @@ fn test_enums() { assert_eq!(Ok(()), ErrorCode::result_from(0)); assert_eq!( - pam_ffi::PAM_BAD_ITEM as i32, - ErrorCode::result_to_c::<()>(Err(ErrorCode::BadItem)) + pam_ffi::PAM_SESSION_ERR as i32, + ErrorCode::result_to_c::<()>(Err(ErrorCode::SessionError)) ); assert_eq!( Err(ErrorCode::Abort),
--- a/src/libpam/answer.rs Thu Jun 26 22:42:32 2025 -0400 +++ b/src/libpam/answer.rs Sat Jun 28 00:34:45 2025 -0400 @@ -248,7 +248,7 @@ } } - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] fn test_round_trip_linux() { use crate::conv::{BinaryData, BinaryQAndA, RadioQAndA}; let binary_msg = {
--- a/src/libpam/pam_ffi.rs Thu Jun 26 22:42:32 2025 -0400 +++ b/src/libpam/pam_ffi.rs Sat Jun 28 00:34:45 2025 -0400 @@ -103,6 +103,8 @@ } } +pub use libpam_sys::*; + type pam_handle = LibPamHandle; type pam_conv = LibPamConversation<'static>;
--- a/src/libpam/question.rs Thu Jun 26 22:42:32 2025 -0400 +++ b/src/libpam/question.rs Sat Jun 28 00:34:45 2025 -0400 @@ -1,6 +1,6 @@ //! Data and types dealing with PAM messages. -#[cfg(feature = "linux-pam-extensions")] +#[cfg(feature = "linux-pam-ext")] use crate::conv::{BinaryQAndA, RadioQAndA}; use crate::conv::{ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA}; use crate::libpam::conversation::OwnedMessage; @@ -188,13 +188,13 @@ /// An informational message. TextInfo = pam_ffi::PAM_TEXT_INFO, /// Yes/No/Maybe conditionals. A Linux-PAM extension. - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] RadioType = pam_ffi::PAM_RADIO_TYPE, /// For server–client non-human interaction. /// /// NOT part of the X/Open PAM specification. /// A Linux-PAM extension. - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] BinaryPrompt = pam_ffi::PAM_BINARY_PROMPT, } @@ -236,13 +236,13 @@ Message::Prompt(p) => alloc(Style::PromptEchoOn, p.question()), Message::Error(p) => alloc(Style::ErrorMsg, p.question()), Message::Info(p) => alloc(Style::TextInfo, p.question()), - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] Message::RadioPrompt(p) => alloc(Style::RadioType, p.question()), - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] Message::BinaryPrompt(p) => Ok((Style::BinaryPrompt, unsafe { CHeapBox::cast(CBinaryData::alloc(p.question())?) })), - #[cfg(not(feature = "linux-pam-extensions"))] + #[cfg(not(feature = "linux-pam-ext"))] Message::RadioPrompt(_) | Message::BinaryPrompt(_) => Err(ErrorCode::ConversationError), }?; Ok(Self { @@ -261,12 +261,12 @@ // in the Question. If it's not a supported format, we skip it. if let Ok(style) = Style::try_from(self.style) { let _ = match style { - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] Style::BinaryPrompt => self .data .as_ref() .map(|p| CBinaryData::zero_contents(CHeapBox::as_ptr(p).cast())), - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] Style::RadioType => self .data .as_ref() @@ -301,9 +301,9 @@ Style::PromptEchoOn => Self::Prompt(QAndA::new(question.string_data()?)), Style::ErrorMsg => Self::Error(ErrorMsg::new(question.string_data()?)), Style::TextInfo => Self::Info(InfoMsg::new(question.string_data()?)), - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] Style::RadioType => Self::RadioPrompt(RadioQAndA::new(question.string_data()?)), - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] Style::BinaryPrompt => Self::BinaryPrompt(BinaryQAndA::new(question.binary_data())), } }; @@ -353,7 +353,7 @@ } #[test] - #[cfg(not(feature = "linux-pam-extensions"))] + #[cfg(not(feature = "linux-pam-ext"))] fn no_linux_extensions() { use crate::conv::{BinaryQAndA, RadioQAndA}; <$typ>::new(&[ @@ -363,7 +363,7 @@ } #[test] - #[cfg(feature = "linux-pam-extensions")] + #[cfg(feature = "linux-pam-ext")] fn linux_extensions() { let interrogation = Box::pin(<$typ>::new(&[ BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(),
--- a/src/logging.rs Thu Jun 26 22:42:32 2025 -0400 +++ b/src/logging.rs Sat Jun 28 00:34:45 2025 -0400 @@ -15,33 +15,20 @@ //! and may even itself implement `log::Log`, but that interface is not exposed //! to the generic PAM user. -#[cfg(feature = "link")] +#[cfg(all(feature = "link", pam_impl = "openpam"))] mod levels { - pub use internal::*; - #[cfg(pam_impl = "linux-pam")] - mod internal { - use crate::libpam::pam_ffi; - pub const ERROR: u32 = pam_ffi::LOG_ERR; - pub const WARN: u32 = pam_ffi::LOG_WARNING; - pub const INFO: u32 = pam_ffi::LOG_INFO; - pub const DEBUG: u32 = pam_ffi::LOG_DEBUG; - } - #[cfg(pam_impl = "openpam")] - mod internal { - use crate::libpam::pam_ffi; - pub const ERROR: u32 = pam_ffi::PAM_LOG_ERROR; - pub const WARN: u32 = pam_ffi::PAM_LOG_NOTICE; - pub const INFO: u32 = pam_ffi::PAM_LOG_VERBOSE; - pub const DEBUG: u32 = pam_ffi::PAM_LOG_DEBUG; - } + use crate::libpam::pam_ffi; + pub const ERROR: u32 = pam_ffi::PAM_LOG_ERROR; + pub const WARN: u32 = pam_ffi::PAM_LOG_NOTICE; + pub const INFO: u32 = pam_ffi::PAM_LOG_VERBOSE; + pub const DEBUG: u32 = pam_ffi::PAM_LOG_DEBUG; } - -#[cfg(not(feature = "link"))] +#[cfg(not(all(feature = "link", pam_impl = "openpam")))] mod levels { - pub const ERROR: u32 = 2255887; - pub const WARN: u32 = 7265000; - pub const INFO: u32 = 7762323; - pub const DEBUG: u32 = 8675309; + pub const ERROR: u32 = libc::LOG_ERR as u32; + pub const WARN: u32 = libc::LOG_WARNING as u32; + pub const INFO: u32 = libc::LOG_INFO as u32; + pub const DEBUG: u32 = libc::LOG_DEBUG as u32; } /// An entry to be added to the log.
--- a/testharness/Cargo.toml Thu Jun 26 22:42:32 2025 -0400 +++ b/testharness/Cargo.toml Sat Jun 28 00:34:45 2025 -0400 @@ -11,8 +11,9 @@ crate-type = ["cdylib"] [features] -linux-pam-extensions = ["nonstick/linux-pam-extensions"] -openpam-extensions = ["nonstick/openpam-extensions"] +illumos-ext = ["nonstick/illumos-ext"] +linux-pam-ext = ["nonstick/linux-pam-ext"] +openpam-ext = ["nonstick/openpam-ext"] test-install = [] [dependencies]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/testharness/container/Containerfile.orig Sat Jun 28 00:34:45 2025 -0400 @@ -0,0 +1,6 @@ +FROM docker.io/debian:stable + +RUN apt-get -y update \ + && apt-get -y upgrade \ + && apt-get install -y cargo mercurial wget build-essential \ + && apt-get clean \ No newline at end of file