use std::ffi::{CStr, CString}; use std::ptr; const PAM_PROMPT_ECHO_OFF: libc::c_int = 1; const PAM_PROMPT_ECHO_ON: libc::c_int = 2; const PAM_ERROR_MSG: libc::c_int = 3; const PAM_TEXT_INFO: libc::c_int = 4; const PAM_SUCCESS: libc::c_int = 0; #[repr(C)] struct PamMessage { msg_style: libc::c_int, msg: *const libc::c_char, } #[repr(C)] struct PamResponse { resp: *mut libc::c_char, resp_retcode: libc::c_int, } type PamConvFn = unsafe extern "C" fn( num_msg: libc::c_int, msg: *mut *const PamMessage, resp: *mut *mut PamResponse, appdata_ptr: *mut libc::c_void, ) -> libc::c_int; #[repr(C)] struct PamConv { conv: PamConvFn, appdata_ptr: *mut libc::c_void, } type PamHandle = libc::c_void; extern "C" { fn pam_start( service_name: *const libc::c_char, user: *const libc::c_char, pam_conversation: *const PamConv, pamh: *mut *mut PamHandle, ) -> libc::c_int; fn pam_authenticate(pamh: *mut PamHandle, flags: libc::c_int) -> libc::c_int; fn pam_end(pamh: *mut PamHandle, pam_status: libc::c_int) -> libc::c_int; } pub trait Conversation { fn prompt_echo(&mut self, msg: &CStr) -> Result; fn prompt_blind(&mut self, msg: &CStr) -> Result; fn info(&mut self, msg: &CStr); fn error(&mut self, msg: &CStr); } pub struct Client { handle: *mut PamHandle, // Box the conversation so the pointer in PamConv remains stable. conversation: Box, } unsafe extern "C" fn converse( num_msg: libc::c_int, msg: *mut *const PamMessage, resp: *mut *mut PamResponse, appdata_ptr: *mut libc::c_void, ) -> libc::c_int { let conv = &mut *(appdata_ptr as *mut C); let count = num_msg as usize; let responses = libc::calloc(count, std::mem::size_of::()) as *mut PamResponse; if responses.is_null() { return libc::ENOMEM; } for i in 0..count { let message = &*(*msg.add(i)); let c_msg = CStr::from_ptr(message.msg); match message.msg_style { PAM_PROMPT_ECHO_ON => match conv.prompt_echo(c_msg) { Ok(s) => (*responses.add(i)).resp = libc::strdup(s.as_ptr()), Err(_) => { free_responses(responses, count); return PAM_SUCCESS + 1; } }, PAM_PROMPT_ECHO_OFF => match conv.prompt_blind(c_msg) { Ok(s) => (*responses.add(i)).resp = libc::strdup(s.as_ptr()), Err(_) => { free_responses(responses, count); return PAM_SUCCESS + 1; } }, PAM_TEXT_INFO => conv.info(c_msg), PAM_ERROR_MSG => conv.error(c_msg), _ => {} } } *resp = responses; PAM_SUCCESS } unsafe fn free_responses(resp: *mut PamResponse, count: usize) { for i in 0..count { let r = &mut *resp.add(i); if !r.resp.is_null() { libc::free(r.resp as *mut libc::c_void); } } libc::free(resp as *mut libc::c_void); } #[derive(Debug)] pub struct PamError(pub libc::c_int); pub type PamResult = Result; impl Client { pub fn with_conversation(service: &str, conversation: C) -> PamResult { let mut conversation = Box::new(conversation); let c_service = CString::new(service).map_err(|_| PamError(1))?; let pam_conv = PamConv { conv: converse::, appdata_ptr: &mut *conversation as *mut C as *mut libc::c_void, }; let mut handle: *mut PamHandle = ptr::null_mut(); let ret = unsafe { pam_start( c_service.as_ptr(), ptr::null(), &pam_conv, &mut handle, ) }; if ret != PAM_SUCCESS { return Err(PamError(ret)); } Ok(Client { handle, conversation, }) } pub fn authenticate(&mut self) -> PamResult<()> { let ret = unsafe { pam_authenticate(self.handle, 0) }; if ret == PAM_SUCCESS { Ok(()) } else { Err(PamError(ret)) } } } impl Drop for Client { fn drop(&mut self) { if !self.handle.is_null() { unsafe { pam_end(self.handle, PAM_SUCCESS); } } } }