view libpam-sys/libpam-sys-consts/build.rs @ 158:d5b7b28d754e

Add `__TARGET_DEFAULT__` PamImpl and set up for docsrs build. Also fixes some formatting stuff.
author Paul Fisher <paul@pfish.zone>
date Sat, 12 Jul 2025 17:17:37 -0400
parents 4b3a5095f68c
children 09dff285ff5e
line wrap: on
line source

#![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 {
    /// Automatically detect PAM, using the installed implementation if present
    /// or the OS default if not.
    Auto,
    /// Use the default PAM implementation based on the OS.
    TargetDefault,
    /// Use the named version of PAM.
    Specified(PamImpl),
}

const TARGET_DEFAULT: &str = "__TARGET_DEFAULT__";

fn main() {
    let detection = match option_env!("LIBPAMSYS_IMPL") {
        None | Some("") => match option_env!("DOCS_RS") {
            // docs.rs cross-compiles, so we don't want to look at
            // its currently-installed PAM; instead we want to use the OS.
            Some(_) => Detect::TargetDefault,
            // In other cases, just auto-detect the actual installed library.
            None => Detect::Auto,
        },
        Some(TARGET_DEFAULT) => Detect::TargetDefault,
        Some(val) => Detect::Specified(PamImpl::try_from(val).unwrap_or_else(|_| {
            panic!(
                "unknown PAM implementation {val:?}. \
                valid LIBPAMSYS_IMPLs are {:?}, \
                {TARGET_DEFAULT:?} to use the OS default, \
                or unset to detect",
                PamImpl::items()
            )
        })),
    };
    let pam_impl = match detection {
        Detect::Auto => LibPam::probe_detect(),
        Detect::TargetDefault => LibPam::os_default(),
        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 {
    /// Look at the currently-installed LibPAM, or use [`Self::os_default`]
    /// if absent.
    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;
            }
        }
        Self::os_default()
    }

    /// Guess the PAM implementation based on the current OS.
    fn os_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 {
            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());
        }
    }
}