#![allow(unexpected_cfgs)]
use std::ffi::{c_void, CString};
use std::ptr::NonNull;
use std::{env, fs};
include!("src/pam_impl.rs");
enum Detect {
TargetDefault,
Installed,
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));
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 {
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()
}
}
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;
}
}
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());
}
}
}