diff libpam-sys/libpam-sys-impls/build.rs @ 176:0730f5f2ee2a

Turn `libpam-sys-consts` back into `libpam-sys-impls`. This moves the constants into `libpam-sys` and makes `libpam-sys-impls` responsible solely for detecting the current PAM implementation.
author Paul Fisher <paul@pfish.zone>
date Wed, 30 Jul 2025 17:53:31 -0400
parents libpam-sys/libpam-sys-consts/build.rs@46e8ce5cd5d1
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpam-sys/libpam-sys-impls/build.rs	Wed Jul 30 17:53:31 2025 -0400
@@ -0,0 +1,124 @@
+#![allow(unexpected_cfgs)]
+
+use std::ffi::{c_void, CString};
+use std::ptr::NonNull;
+use std::{env, fs};
+
+include!("src/pam_impl.rs");
+
+/// The strategy to use to detect PAM.
+enum Detect {
+    /// Use the default PAM implementation based on the OS,
+    /// or the currently-installed version if the OS is not recognized.
+    TargetDefault,
+    /// Detect the installed implementation.
+    Installed,
+    /// Use the named version of PAM.
+    Specified(PamImpl),
+}
+
+const INSTALLED: &str = "__installed__";
+
+fn main() {
+    let detection = match option_env!("LIBPAMSYS_IMPL") {
+        Some("") | None => Detect::TargetDefault,
+        Some(INSTALLED) => Detect::Installed,
+        Some(val) => Detect::Specified(PamImpl::try_from(val).unwrap_or_else(|_| {
+            panic!(
+                "unknown PAM implementation {val:?}. \
+                valid LIBPAMSYS_IMPLs are {:?}, \
+                {INSTALLED:?} to use the currently-installed version, \
+                or unset to use the OS default",
+                PamImpl::items()
+            )
+        })),
+    };
+    let pam_impl = match detection {
+        Detect::TargetDefault => LibPam::target_default(),
+        Detect::Installed => LibPam::probe_detect(),
+        Detect::Specified(other) => other,
+    };
+    let impl_str = format!("{pam_impl:?}");
+    println!("{}", generate_cfg(&impl_str));
+    // We set this environment variable to substitute into docstrings.
+    println!("cargo:rustc-env=LIBPAMSYS_IMPL={impl_str}");
+    fs::write(
+        format!("{}/pam_impl_const.rs", env::var("OUT_DIR").unwrap()),
+        generate_consts(&impl_str),
+    )
+    .unwrap();
+}
+
+fn generate_consts(impl_str: &str) -> String {
+    format!(
+        "\
+impl PamImpl {{
+/// The implementation of libpam this was built for (`{impl_str}`).
+pub const CURRENT: Self = Self::{impl_str};
+}}
+
+/// String name of [`PamImpl::CURRENT`], for substituting into docs.
+#[macro_export]
+macro_rules! pam_impl_name {{ () => ({impl_str:?}) }}
+        "
+    )
+}
+
+struct LibPam(NonNull<c_void>);
+
+impl LibPam {
+    /// Guess the PAM implementation based on the current OS.
+    fn target_default() -> PamImpl {
+        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 {
+            Self::probe_detect()
+        }
+    }
+
+    /// Look at the currently-installed LibPAM.
+    fn probe_detect() -> PamImpl {
+        if let Some(lib) = Self::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;
+            }
+        }
+        // idk
+        PamImpl::XSso
+    }
+
+    fn open() -> Option<Self> {
+        let dlopen = |s: &[u8]| unsafe { libc::dlopen(s.as_ptr().cast(), libc::RTLD_LAZY) };
+        NonNull::new(dlopen(b"libpam.so\0"))
+            .or_else(|| NonNull::new(dlopen(b"libpam.dylib\0")))
+            .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());
+        }
+    }
+}