diff options
Diffstat (limited to 'src-repo/src/pam_client.rs')
| -rw-r--r-- | src-repo/src/pam_client.rs | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/src-repo/src/pam_client.rs b/src-repo/src/pam_client.rs new file mode 100644 index 0000000..e0fbcc6 --- /dev/null +++ b/src-repo/src/pam_client.rs @@ -0,0 +1,166 @@ +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<CString, ()>; + fn prompt_blind(&mut self, msg: &CStr) -> Result<CString, ()>; + fn info(&mut self, msg: &CStr); + fn error(&mut self, msg: &CStr); +} + +pub struct Client<C: Conversation> { + handle: *mut PamHandle, + // Box the conversation so the pointer in PamConv remains stable. + conversation: Box<C>, +} + +unsafe extern "C" fn converse<C: Conversation>( + 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::<PamResponse>()) 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<T> = Result<T, PamError>; + +impl<C: Conversation> Client<C> { + pub fn with_conversation(service: &str, conversation: C) -> PamResult<Self> { + let mut conversation = Box::new(conversation); + let c_service = CString::new(service).map_err(|_| PamError(1))?; + + let pam_conv = PamConv { + conv: converse::<C>, + 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<C: Conversation> Drop for Client<C> { + fn drop(&mut self) { + if !self.handle.is_null() { + unsafe { + pam_end(self.handle, PAM_SUCCESS); + } + } + } +} |
