#[cfg(target_os = "linux")]
use std::ffi::{CStr, CString};
#[cfg(target_os = "linux")]
use std::os::raw::{c_char, c_int, c_void};
#[cfg(target_os = "linux")]
use std::ptr;
#[cfg(target_os = "linux")]
const PAM_SUCCESS: c_int = 0;
#[cfg(target_os = "linux")]
const PAM_PROMPT_ECHO_OFF: c_int = 1;
#[cfg(target_os = "linux")]
const PAM_PROMPT_ECHO_ON: c_int = 2;
#[cfg(target_os = "linux")]
const PAM_CONV_ERR: c_int = 19;
#[cfg(target_os = "linux")]
const PAM_BUF_ERR: c_int = 5;
#[cfg(target_os = "linux")]
#[repr(C)]
struct PamMessage {
msg_style: c_int,
msg: *const c_char,
}
#[cfg(target_os = "linux")]
#[repr(C)]
struct PamResponse {
resp: *mut c_char,
resp_retcode: c_int,
}
#[cfg(target_os = "linux")]
#[repr(C)]
struct PamConv {
conv: Option<
unsafe extern "C" fn(
c_int,
*mut *const PamMessage,
*mut *mut PamResponse,
*mut c_void,
) -> c_int,
>,
appdata_ptr: *mut c_void,
}
#[cfg(target_os = "linux")]
type PamHandle = c_void;
#[cfg(target_os = "linux")]
type FnPamStart = unsafe extern "C" fn(
*const c_char,
*const c_char,
*const PamConv,
*mut *mut PamHandle,
) -> c_int;
#[cfg(target_os = "linux")]
type FnPamAuthenticate = unsafe extern "C" fn(*mut PamHandle, c_int) -> c_int;
#[cfg(target_os = "linux")]
type FnPamAcctMgmt = unsafe extern "C" fn(*mut PamHandle, c_int) -> c_int;
#[cfg(target_os = "linux")]
type FnPamEnd = unsafe extern "C" fn(*mut PamHandle, c_int) -> c_int;
#[cfg(target_os = "linux")]
type FnPamStrerror = unsafe extern "C" fn(*mut PamHandle, c_int) -> *const c_char;
#[cfg(target_os = "linux")]
unsafe extern "C" fn pam_conversation_cb(
num_msg: c_int,
msg: *mut *const PamMessage,
resp: *mut *mut PamResponse,
appdata_ptr: *mut c_void,
) -> c_int {
if num_msg <= 0 || num_msg > 32 || msg.is_null() || resp.is_null() || appdata_ptr.is_null() {
return PAM_CONV_ERR;
}
let password_cstr = &*(appdata_ptr as *const CString);
let resp_arr = libc::calloc(num_msg as usize, std::mem::size_of::<PamResponse>()) as *mut PamResponse;
if resp_arr.is_null() {
return PAM_BUF_ERR;
}
for i in 0..num_msg as isize {
let msg_ptr = *msg.offset(i);
if msg_ptr.is_null() {
continue;
}
let style = (*msg_ptr).msg_style;
if style == PAM_PROMPT_ECHO_OFF || style == PAM_PROMPT_ECHO_ON {
let resp_str = libc::strdup(password_cstr.as_ptr());
(*resp_arr.offset(i)).resp = resp_str;
(*resp_arr.offset(i)).resp_retcode = 0;
} else {
(*resp_arr.offset(i)).resp = ptr::null_mut();
(*resp_arr.offset(i)).resp_retcode = 0;
}
}
*resp = resp_arr;
PAM_SUCCESS
}
#[cfg(target_os = "linux")]
pub fn authenticate(service: &str, username: &str, password: &str) -> Result<(), String> {
unsafe {
let lib_names = [
"libpam.so.0",
"libpam.so",
"/usr/lib/libpam.so.0",
"/usr/lib64/libpam.so.0",
"/lib/x86_64-linux-gnu/libpam.so.0",
"/usr/lib/x86_64-linux-gnu/libpam.so.0",
"/lib64/libpam.so.0",
"/lib/libpam.so.0",
];
let mut handle = ptr::null_mut();
for name in &lib_names {
let c_name = match CString::new(*name) {
Ok(c) => c,
Err(_) => continue,
};
handle = libc::dlopen(c_name.as_ptr(), libc::RTLD_NOW);
if !handle.is_null() {
break;
}
}
if handle.is_null() {
return Err("PAM shared library (libpam.so.0) not found on system".to_string());
}
let pam_start_sym = libc::dlsym(handle, b"pam_start\0".as_ptr() as *const c_char);
let pam_authenticate_sym = libc::dlsym(handle, b"pam_authenticate\0".as_ptr() as *const c_char);
let pam_acct_mgmt_sym = libc::dlsym(handle, b"pam_acct_mgmt\0".as_ptr() as *const c_char);
let pam_end_sym = libc::dlsym(handle, b"pam_end\0".as_ptr() as *const c_char);
let pam_strerror_sym = libc::dlsym(handle, b"pam_strerror\0".as_ptr() as *const c_char);
if pam_start_sym.is_null() || pam_authenticate_sym.is_null() || pam_end_sym.is_null() {
libc::dlclose(handle);
return Err("Failed to resolve PAM symbols from libpam.so.0".to_string());
}
let pam_start: FnPamStart = std::mem::transmute(pam_start_sym);
let pam_authenticate: FnPamAuthenticate = std::mem::transmute(pam_authenticate_sym);
let pam_acct_mgmt: Option<FnPamAcctMgmt> = if !pam_acct_mgmt_sym.is_null() {
Some(std::mem::transmute(pam_acct_mgmt_sym))
} else {
None
};
let pam_end: FnPamEnd = std::mem::transmute(pam_end_sym);
let pam_strerror: Option<FnPamStrerror> = if !pam_strerror_sym.is_null() {
Some(std::mem::transmute(pam_strerror_sym))
} else {
None
};
let c_service = CString::new(service).map_err(|e| e.to_string())?;
let c_user = CString::new(username).map_err(|e| e.to_string())?;
let c_pass = CString::new(password).map_err(|e| e.to_string())?;
let conv = PamConv {
conv: Some(pam_conversation_cb),
appdata_ptr: &c_pass as *const CString as *mut c_void,
};
let mut pamh: *mut PamHandle = ptr::null_mut();
let status = pam_start(c_service.as_ptr(), c_user.as_ptr(), &conv, &mut pamh);
if status != PAM_SUCCESS || pamh.is_null() {
libc::dlclose(handle);
return Err(format!("pam_start failed with status {}", status));
}
let auth_status = pam_authenticate(pamh, 0);
let mut final_status = auth_status;
if auth_status == PAM_SUCCESS {
if let Some(acct_mgmt) = pam_acct_mgmt {
let acct_status = acct_mgmt(pamh, 0);
if acct_status != PAM_SUCCESS {
final_status = acct_status;
}
}
}
let err_msg = if final_status != PAM_SUCCESS {
if let Some(strerror_fn) = pam_strerror {
let err_ptr = strerror_fn(pamh, final_status);
if !err_ptr.is_null() {
CStr::from_ptr(err_ptr).to_string_lossy().to_string()
} else {
format!("PAM authentication error code {}", final_status)
}
} else {
format!("PAM authentication error code {}", final_status)
}
} else {
String::new()
};
pam_end(pamh, final_status);
libc::dlclose(handle);
if final_status == PAM_SUCCESS {
Ok(())
} else {
Err(err_msg)
}
}
}
#[cfg(not(target_os = "linux"))]
pub fn authenticate(_service: &str, _username: &str, _password: &str) -> Result<(), String> {
Err("PAM authentication is only supported on Linux".to_string())
}