#![allow(clippy::needless_doctest_main)]
use std::env;
use std::env::VarError;
use std::ffi::c_void;
use std::ptr::NonNull;
macro_rules! self_aware_enum {
(
$(#[$enumeta:meta])*
$viz:vis enum $name:ident {
$(
$(#[$itemeta:meta])*
$item:ident,
)*
}
) => {
$(#[$enumeta])*
$viz enum $name {
$(
$(#[$itemeta])*
$item,
)*
}
#[allow(dead_code)]
impl $name {
pub(crate) fn items() -> Vec<Self> {
vec![$(Self::$item),*]
}
pub(crate) fn try_from(value: &str) -> Result<Self, String> {
match value {
$(stringify!($item) => Ok(Self::$item),)*
_ => Err(value.into()),
}
}
}
};
}
self_aware_enum! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PamImpl {
LinuxPam,
OpenPam,
Sun,
XSso,
}
}
#[allow(clippy::needless_doctest_main)]
pub fn enable_pam_impl_cfg() {
println!("{}", pam_impl_cfg_string())
}
pub fn pam_impl_cfg_string() -> String {
generate_cfg(build_target_impl())
}
fn generate_cfg(pam_impl: Option<PamImpl>) -> String {
let impls: Vec<_> = PamImpl::items()
.into_iter()
.map(|i| format!(r#""{i:?}""#))
.collect();
let mut lines = vec![
format!(
"cargo:rustc-check-cfg=cfg(pam_impl, values({impls}))",
impls = impls.join(",")
),
"cargo:rustc-cfg=pam_impl".into(),
];
if let Some(pam_impl) = pam_impl {
lines.push("cargo:rustc-cfg=pam_impl".into());
lines.push(format!("cargo:rustc-cfg=pam_impl=\"{pam_impl:?}\""));
}
lines.join("\n")
}
enum Detect {
TargetDefault,
Installed,
Specified(PamImpl),
}
const INSTALLED: &str = "__installed__";
pub fn build_target_impl() -> Option<PamImpl> {
let detection = match env::var("LIBPAMSYS_IMPL").as_deref() {
Ok("") | Err(VarError::NotPresent) => Detect::TargetDefault,
Ok(INSTALLED) => Detect::Installed,
Ok(val) => Detect::Specified(PamImpl::try_from(val).unwrap_or_else(|_| {
panic!(
"unknown PAM implementation {val:?}. \
valid LIBPAMSYS_IMPL values are {:?}, \
{INSTALLED:?} to use the currently-installed version, \
or unset to use the OS default",
PamImpl::items()
)
})),
Err(other) => panic!("Couldn't detect PAM version: {other}"),
};
match detection {
Detect::TargetDefault => env::var("CARGO_CFG_TARGET_OS")
.ok()
.as_deref()
.and_then(os_default),
Detect::Installed => currently_installed(),
Detect::Specified(other) => Some(other),
}
}
pub fn os_default(target_os: &str) -> Option<PamImpl> {
match target_os {
"linux" => Some(PamImpl::LinuxPam),
"macos" | "freebsd" | "netbsd" | "dragonfly" | "openbsd" => Some(PamImpl::OpenPam),
"illumos" | "solaris" => Some(PamImpl::Sun),
_ => None,
}
}
pub fn currently_installed() -> Option<PamImpl> {
LibPam::open().map(|lib| {
if lib.has(b"pam_syslog\0") {
PamImpl::LinuxPam
} else if lib.has(b"_openpam_log\0") {
PamImpl::OpenPam
} else if lib.has(b"__pam_get_authtok\0") {
PamImpl::Sun
} else {
PamImpl::XSso
}
})
}
struct LibPam(NonNull<c_void>);
impl LibPam {
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: &[u8]) -> bool {
let symbol = unsafe { libc::dlsym(self.0.as_ptr(), name.as_ptr().cast()) };
!symbol.is_null()
}
}
impl Drop for LibPam {
fn drop(&mut self) {
unsafe {
libc::dlclose(self.0.as_ptr());
}
}
}