#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
use super::{AuthResult, Authenticator};
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
use pam_client::{Context, Flag, conv_mock::Conversation};
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
pub struct PamAuthenticator {
service_name: String,
}
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
impl PamAuthenticator {
pub fn new() -> Result<Self, String> {
if !std::path::Path::new("/etc/pam.d").exists() {
return Err("PAM configuration directory not found".to_string());
}
Ok(Self {
service_name: "login".to_string(),
})
}
}
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))]
impl Authenticator for PamAuthenticator {
fn is_available(&self) -> bool {
#[cfg(target_os = "linux")]
{
std::path::Path::new("/lib/x86_64-linux-gnu/libpam.so.0").exists()
|| std::path::Path::new("/lib64/libpam.so.0").exists()
|| std::path::Path::new("/usr/lib/libpam.so").exists()
|| std::path::Path::new("/usr/lib/x86_64-linux-gnu/libpam.so.0").exists()
|| std::path::Path::new("/lib/libpam.so.0").exists()
}
#[cfg(target_os = "freebsd")]
{
std::path::Path::new("/usr/lib/libpam.so").exists()
|| std::path::Path::new("/usr/lib/libpam.so.6").exists()
}
#[cfg(target_os = "netbsd")]
{
std::path::Path::new("/usr/lib/libpam.so").exists()
|| std::path::Path::new("/usr/lib/libpam.so.3").exists()
}
}
fn authenticate(&self, username: &str, password: &str) -> AuthResult {
let conversation = Conversation::with_credentials(username, password);
let mut context = match Context::new(&self.service_name, Some(username), conversation) {
Ok(ctx) => ctx,
Err(e) => {
return AuthResult::SystemError(format!("PAM initialization failed: {}", e));
}
};
match context.authenticate(Flag::NONE) {
Ok(()) => AuthResult::Success,
Err(_) => AuthResult::Failure("Invalid username or password".to_string()),
}
}
fn get_current_username(&self) -> Option<String> {
std::env::var("USER")
.or_else(|_| std::env::var("LOGNAME"))
.ok()
.or_else(|| {
unsafe {
let uid = libc::getuid();
let pw = libc::getpwuid(uid);
if !pw.is_null() {
let name = std::ffi::CStr::from_ptr((*pw).pw_name);
return name.to_str().ok().map(|s| s.to_string());
}
}
None
})
}
fn system_name(&self) -> &'static str {
"PAM"
}
}
#[cfg(all(
test,
any(target_os = "linux", target_os = "freebsd", target_os = "netbsd")
))]
mod tests {
use super::*;
#[test]
fn test_pam_new() {
let result = PamAuthenticator::new();
let _ = result;
}
#[test]
fn test_get_username() {
if let Ok(auth) = PamAuthenticator::new() {
let username = auth.get_current_username();
assert!(username.is_some() || std::env::var("USER").is_err());
}
}
}