Mercurial > crates > nonstick
changeset 131:a632a8874131
Get all the Linux-PAM functions into libpam-sys, and get tests right.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Wed, 02 Jul 2025 02:24:21 -0400 |
parents | 80c07e5ab22f |
children | 0b6a17f8c894 |
files | Cargo.lock Cargo.toml libpam-sys/Cargo.toml libpam-sys/build.rs libpam-sys/libpam-sys-test/Cargo.toml libpam-sys/libpam-sys-test/build.rs libpam-sys/libpam-sys-test/tests/ctest.rs libpam-sys/libpam-sys-test/tests/runner.rs libpam-sys/src/constants.rs libpam-sys/src/ffi.rs src/constants.rs src/libpam/handle.rs |
diffstat | 12 files changed, 240 insertions(+), 63 deletions(-) [+] |
line wrap: on
line diff
--- a/Cargo.lock Tue Jul 01 06:11:43 2025 -0400 +++ b/Cargo.lock Wed Jul 02 02:24:21 2025 -0400 @@ -299,7 +299,9 @@ name = "libpam-sys" version = "0.1.0" dependencies = [ + "libc", "libpam-sys-impls", + "num_enum", "strum", ]
--- a/Cargo.toml Tue Jul 01 06:11:43 2025 -0400 +++ b/Cargo.toml Wed Jul 02 02:24:21 2025 -0400 @@ -39,7 +39,7 @@ [dependencies] bitflags = "2.9.0" -libc = "0.2.97" +libc = "0.2" memoffset = "0.9.1" num_enum = "0.7.3" libpam-sys = { path = "libpam-sys" }
--- a/libpam-sys/Cargo.toml Tue Jul 01 06:11:43 2025 -0400 +++ b/libpam-sys/Cargo.toml Wed Jul 02 02:24:21 2025 -0400 @@ -13,7 +13,9 @@ helpers = [] [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" }
--- a/libpam-sys/build.rs Tue Jul 01 06:11:43 2025 -0400 +++ b/libpam-sys/build.rs Wed Jul 02 02:24:21 2025 -0400 @@ -8,9 +8,6 @@ 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(_private_pam_impl_hack, values({pam_impls}))"); - println!( - "cargo:rustc-cfg=_private_pam_impl_hack=\"{:?}\"", - PamImpl::CURRENT - ); + println!("cargo:rustc-check-cfg=cfg(_hack_impl, values({pam_impls}))"); + println!("cargo:rustc-cfg=_hack_impl=\"{:?}\"", PamImpl::CURRENT); }
--- a/libpam-sys/libpam-sys-test/Cargo.toml Tue Jul 01 06:11:43 2025 -0400 +++ b/libpam-sys/libpam-sys-test/Cargo.toml Wed Jul 02 02:24:21 2025 -0400 @@ -7,7 +7,7 @@ readme = "README.md" [dependencies] -libc = "0.2.174" +libc = "0.2" libpam-sys = { path = ".." } [build-dependencies]
--- a/libpam-sys/libpam-sys-test/build.rs Tue Jul 01 06:11:43 2025 -0400 +++ b/libpam-sys/libpam-sys-test/build.rs Wed Jul 02 02:24:21 2025 -0400 @@ -6,11 +6,13 @@ use std::process::Command; use std::str::FromStr; use std::{env, fs}; -use syn::{Item, ItemConst, Type, TypePath}; +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() { let config = match PamImpl::CURRENT { PamImpl::LinuxPam => TestConfig { @@ -19,7 +21,9 @@ "<security/pam_appl.h>", "<security/pam_ext.h>", "<security/pam_modules.h>", + "<security/pam_modutil.h>", ], + allow_types: vec![REDIR_FD], ignore_consts: vec![ "__LINUX_PAM__", "__LINUX_PAM_MINOR__", @@ -39,12 +43,10 @@ }, PamImpl::Sun => TestConfig { headers: vec!["<security/pam_appl.h>", "<security/pam_modules.h>"], - block_headers: vec!["sys/.*"], ..Default::default() }, PamImpl::XSso => TestConfig { headers: vec!["\"xsso_pam_appl.h\""], - ignore_consts: vec!["PAM_CRED_PRELIM_CHECK"], ..Default::default() }, }; @@ -57,22 +59,28 @@ .header_contents("_.h", &config.header_contents()) .merge_extern_blocks(true) .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - .blocklist_type(".*") - .blocklist_function(".*") - .allowlist_var(".*") + .allowlist_var("(OPEN)?PAM_.*") .default_macro_constant_type(MacroTypeVariation::Signed); - for hdr in config.block_headers.iter() { - builder = builder.blocklist_file(".*?/".to_owned() + hdr) + + for &typ in config.allow_types.iter() { + builder = builder.allowlist_type(typ); } - let generated = builder.generate().unwrap().to_string(); - let file = syn::parse_file(&generated).unwrap(); + let generated = builder.generate().unwrap(); + generated.write_to_file(test_file("bindgen.rs")).unwrap(); + let file = syn::parse_file(&generated.to_string()).unwrap(); let mut tests = vec![ - "use libpam_sys::*;".into(), - "#[allow(deprecated, overflowing_literals)]".into(), - "fn main() {".into(), + "\ + #[allow(dead_code, non_camel_case_types, non_upper_case_globals)] + mod generated { + include!(\"bindgen.rs\"); + } + #[allow(deprecated, overflowing_literals)] + fn main() { + " + .into(), format!( - "assert_eq!(PamImpl::CURRENT, PamImpl::{:?});", + "assert_eq!(libpam_sys::PamImpl::CURRENT, libpam_sys::PamImpl::{:?});", PamImpl::CURRENT ), ]; @@ -86,18 +94,14 @@ None } }) - .filter(|item| config.should_check_const(item)) - .cloned() - .map(|mut item| { - item.ty = Box::new(Type::Path(TypePath { - qself: None, - path: format_ident!("i32").into(), - })); - format!( - "assert_eq!({tokens}, {name});", - tokens = item.expr.to_token_stream(), - name = item.ident - ) + .filter(|&item| config.should_check_const(item)) + .map(|item| { + let name = item.ident.to_string(); + if let Some(stripped) = name.strip_prefix(&format!("{REDIR_FD}_")) { + format!("assert_eq!(generated::{name} as i32, libpam_sys::{REDIR_FD}::{stripped}.into());") + } else { + format!("assert_eq!(generated::{name}, libpam_sys::{name});") + } }), ); tests.push("}".into()); @@ -108,8 +112,9 @@ fn generate_ctest(config: &TestConfig) { let mut test = ctest::TestGenerator::new(); + test.cfg("_hack_impl", Some(&format!("{:?}", PamImpl::CURRENT))); - for header in config.headers.iter() { + for &header in config.headers.iter() { if header.starts_with('"') { test.include(env::var("CARGO_MANIFEST_DIR").unwrap()); } @@ -118,17 +123,17 @@ // These are opaque structs. test.skip_struct(|name| matches!(name, "pam_handle" | "AppData")); test.skip_type(|name| matches!(name, "ConversationCallback" | "CleanupCallback")); - test.type_name(|name, _is_struct, is_union| { + test.type_name(|name, is_struct, is_union| { assert!(!is_union); // we scabbin' - match name { - "pam_handle" => "struct pam_handle", - "pam_conv" => "struct pam_conv", - "pam_message" => "struct pam_message", - "pam_response" => "struct pam_response", - "AppData" => "void", - other => other, + match (name, is_struct) { + ("AppData", _) => "void".into(), + (REDIR_FD, _) => format!("enum {REDIR_FD}"), + ("passwd", _) => "struct passwd".into(), + ("group", _) => "struct group".into(), + ("spwd", _) => "struct spwd".into(), + (name, true) => format!("struct {name}"), + (other, false) => other.into(), } - .into() }); // @@ -186,7 +191,7 @@ #[derive(Default)] struct TestConfig { headers: Vec<&'static str>, - block_headers: Vec<&'static str>, + allow_types: Vec<&'static str>, ignore_consts: Vec<&'static str>, }
--- a/libpam-sys/libpam-sys-test/tests/ctest.rs Tue Jul 01 06:11:43 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -#![allow(unused_imports, clippy::all)] -use libpam_sys::*; - -include!(concat!(env!("OUT_DIR"), "/ctest.rs"));
--- a/libpam-sys/libpam-sys-test/tests/runner.rs Tue Jul 01 06:11:43 2025 -0400 +++ b/libpam-sys/libpam-sys-test/tests/runner.rs Wed Jul 02 02:24:21 2025 -0400 @@ -1,1 +1,22 @@ -include!(concat!(env!("OUT_DIR"), "/constant_test.rs")); +#![allow(unused_imports)] + +mod constants { + include!(concat!(env!("OUT_DIR"), "/constant_test.rs")); + + #[test] + fn test_constants() { + main() + } +} + +#[allow(clippy::all)] +mod ctest { + use libpam_sys::*; + include!(concat!(env!("OUT_DIR"), "/ctest.rs")); + + #[test] + fn test_c() { + eprintln!("Running a test!"); + main(); + } +}
--- a/libpam-sys/src/constants.rs Tue Jul 01 06:11:43 2025 -0400 +++ b/libpam-sys/src/constants.rs Wed Jul 02 02:24:21 2025 -0400 @@ -143,6 +143,8 @@ PAM_RADIO_TYPE = 5; PAM_BINARY_PROMPT = 7; ); + + pub const PAM_MODUTIL_NGROUPS: i32 = 64; } #[cfg_pam_impl(any("OpenPam", "Sun", "XSso"))]
--- a/libpam-sys/src/ffi.rs Tue Jul 01 06:11:43 2025 -0400 +++ b/libpam-sys/src/ffi.rs Wed Jul 02 02:24:21 2025 -0400 @@ -1,6 +1,12 @@ +//! The actual PAM FFI bindings. +//! +//! They live in this specific file rather than lib.rs because otherwise +//! ctest gets very upset about some of the macros we use. #![allow(non_camel_case_types)] +#![allow(unused_imports)] -use std::ffi::{c_char, c_int, c_void}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::ffi::{c_char, c_int, c_uint, c_void}; use std::fmt; use std::marker::{PhantomData, PhantomPinned}; @@ -14,7 +20,7 @@ impl fmt::Debug for ExtremelyUnsafe { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("ExtremelyUnsafe") + write!(f, "ExtremelyUnsafe({self:p})") } } @@ -26,7 +32,7 @@ impl fmt::Debug for pam_handle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "PamHandle({self:p}") + write!(f, "pam_handle({self:p}") } } @@ -108,6 +114,40 @@ pub resp_retcode: c_int, } +/// Definition of the PAM_XAUTHDATA item. Compatible with `xcb_auth_info_t`. +#[cfg(_hack_impl = "LinuxPam")] +#[repr(C)] +pub struct pam_xauth_data { + namelen: c_int, + name: *mut c_char, + datalen: c_int, + data: *mut c_char, +} + +#[cfg(_hack_impl = "LinuxPam")] +#[derive(Copy, Clone, Debug, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] +#[repr(i32)] +pub enum pam_modutil_redirect_fd { + PAM_MODUTIL_IGNORE_FD, + PAM_MODUTIL_PIPE_FD, + PAM_MODUTIL_NULL_FD, +} + +#[cfg(_hack_impl = "LinuxPam")] +pub use pam_modutil_redirect_fd::*; + +#[cfg(_hack_impl = "LinuxPam")] +#[derive(Debug)] +#[repr(C)] +pub struct pam_modutil_privs { + grplist: *mut libc::gid_t, + number_of_groups: c_int, + allocated: c_int, + old_gid: libc::gid_t, + old_uid: libc::uid_t, + is_dropped: c_int, +} + // These are the functions specified in X/SSO. Everybody exports them. extern "C" { /// Account validation. @@ -206,16 +246,130 @@ pub fn pam_strerror(pamh: *const pam_handle, error_number: c_int) -> *mut c_char; } -// We use `_private_pam_impl_hack` because ctest loses its mind +// We use `_hack_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(_private_pam_impl_hack = "LinuxPam", _private_pam_impl_hack = "OpenPam"))] +#[cfg(any(_hack_impl = "LinuxPam", _hack_impl = "OpenPam"))] extern "C" { + /// Gets `PAM_AUTHTOK`, or asks the user if that is unset. pub fn pam_get_authtok( pamh: *mut pam_handle, - x: c_int, - token: *mut *const c_char, + item: c_int, + authtok: *mut *const c_char, prompt: *const c_char, ) -> c_int; } + +#[cfg(_hack_impl = "LinuxPam")] +extern "C" { + pub fn pam_fail_delay(pamh: *mut pam_handle, musec_delay: c_uint) -> c_int; + + /// Start a PAM transaction based on configuration in the given directory. + pub fn pam_start_confdir( + service_name: *const c_char, + user: *const c_char, + pam_conversation: *mut pam_conv, + confdir: *const c_char, + pamh: *mut *mut pam_handle, + ) -> c_int; + + // We don't export the v-variants of the formatting functions. + + pub fn pam_syslog(pamh: *const pam_handle, priority: c_int, fmt: *const c_char, ...); + + pub fn pam_prompt( + pamh: *const pam_handle, + style: c_int, + response: *mut *mut c_char, + fmt: *const c_char, + ... + ) -> c_int; + + pub fn pam_get_authtok_noverify( + pamh: *const pam_handle, + authtok: *mut *const c_char, + prompt: *const c_char, + ) -> c_int; + + pub fn pam_get_authtok_verify( + pamh: *const pam_handle, + authtok: *mut *const c_char, + prompt: *const c_char, + ) -> c_int; + + // pam_modutil also lives in libpam for Linux. + + pub fn pam_modutil_check_user_in_passwd( + pamh: *mut pam_handle, + user_name: *const c_char, + file_name: *const c_char, + ) -> c_int; + + pub fn pam_modutil_getpwnam(pamh: *mut pam_handle, user: *const c_char) -> *mut libc::passwd; + + pub fn pam_modutil_getpwuid(pamh: *mut pam_handle, uid: libc::uid_t) -> *mut libc::passwd; + + pub fn pam_modutil_getgrnam(pamh: *mut pam_handle, group: *const c_char) -> *mut libc::group; + + pub fn pam_modutil_getgrgid(pamh: *mut pam_handle, gid: libc::gid_t) -> *mut libc::group; + + pub fn pam_modutil_getspnam(pamh: *mut pam_handle, user: *const c_char) -> *mut libc::spwd; + + pub fn pam_modutil_user_in_group_nam_nam( + pamh: *mut pam_handle, + user: *const c_char, + group: *const c_char, + ) -> c_int; + pub fn pam_modutil_user_in_group_nam_gid( + pamh: *mut pam_handle, + user: *const c_char, + group: libc::gid_t, + ) -> c_int; + + pub fn pam_modutil_user_in_group_uid_nam( + pamh: *mut pam_handle, + user: libc::uid_t, + group: *const c_char, + ) -> c_int; + + pub fn pam_modutil_user_in_group_uid_gid( + pamh: *mut pam_handle, + user: libc::uid_t, + group: libc::gid_t, + ) -> c_int; + + pub fn pam_modutil_getlogin(pamh: *mut pam_handle) -> *const c_char; + + pub fn pam_modutil_read(fd: c_int, buffer: *mut c_char, count: c_int) -> c_int; + + pub fn pam_modutil_write(fd: c_int, buffer: *const c_char, count: c_int) -> c_int; + + pub fn pam_modutil_audit_write( + pamh: *mut pam_handle, + typ: c_int, + message: *const c_char, + retval: c_int, + ) -> c_int; + + pub fn pam_modutil_drop_priv( + pamh: *mut pam_handle, + p: *mut pam_modutil_privs, + pw: *const libc::passwd, + ) -> c_int; + + pub fn pam_modutil_regain_priv(pamh: *mut pam_handle, p: *mut pam_modutil_privs) -> c_int; + + pub fn pam_modutil_sanitize_helper_fds( + pamh: *mut pam_handle, + redirect_stdin: pam_modutil_redirect_fd, + redirect_stdout: pam_modutil_redirect_fd, + redirect_stderr: pam_modutil_redirect_fd, + ) -> c_int; + + pub fn pam_modutil_search_key( + pamh: *mut pam_handle, + file_name: *const c_char, + key: *const c_char, + ) -> *mut c_char; +}
--- a/src/constants.rs Tue Jul 01 06:11:43 2025 -0400 +++ b/src/constants.rs Wed Jul 02 02:24:21 2025 -0400 @@ -6,12 +6,12 @@ use crate::{linklist, man7, manbsd, xsso}; use bitflags::bitflags; -use std::ffi::c_int; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::error::Error; +use std::ffi::c_int; +use std::fmt; use std::fmt::{Display, Formatter}; use std::result::Result as StdResult; -use std::fmt; /// Values for constants not provided by certain PAM implementations. /// @@ -32,7 +32,6 @@ (@expand $({ $(#[$m:meta])* } $i:item)+) => {$($(#[$m])* $i)+}; } - define!( /// A fictitious constant for testing purposes. #[cfg(not(feature = "link"))] @@ -56,7 +55,6 @@ PAM_CONV_AGAIN = 517; PAM_INCOMPLETE = 518; ); - } bitflags! { @@ -204,7 +202,7 @@ /// A basic Display implementation for if we don't link against PAM. fn fmt_internal(self, f: &mut Formatter<'_>) -> fmt::Result { - let n : c_int = self.into(); + let n: c_int = self.into(); write!(f, "PAM error: {self:?} ({n})") } } @@ -212,8 +210,8 @@ /// Gets a string version of an error message. #[cfg(feature = "link")] pub fn strerror(code: c_int) -> Option<&'static str> { + use std::ffi::CStr; use std::ptr; - use std::ffi::CStr; // SAFETY: PAM impls don't care about the PAM handle and always return // static strings. let strerror = unsafe { libpam_sys::pam_strerror(ptr::null(), code as c_int) };
--- a/src/libpam/handle.rs Tue Jul 01 06:11:43 2025 -0400 +++ b/src/libpam/handle.rs Wed Jul 02 02:24:21 2025 -0400 @@ -14,9 +14,9 @@ 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; -use libpam_sys::cfg_pam_impl; /// Owner for a PAM handle. pub struct LibPamHandle(pub NonNull<libpam_sys::pam_handle>);