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>);