Mercurial > crates > nonstick
view src/module.rs @ 62:d83623951070
Further improve docs and put `conv` behind a feature gate.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Wed, 21 May 2025 23:10:09 -0400 |
parents | 05cc2c27334f |
children | bbe84835d6db |
line wrap: on
line source
//! Functions and types useful for implementing a PAM module. use crate::constants::{ErrorCode, Flags, Result}; use crate::items::{Item, ItemType}; use crate::memory; use libc::c_char; use secure_string::SecureString; use std::ffi::{c_int, CStr, CString}; use crate::pam_ffi; /// Function called at the end of a PAM session that is called to clean up /// a value previously provided to PAM in a `pam_set_data` call. /// /// You should never call this yourself. extern "C" fn cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) { unsafe { let _data: Box<T> = Box::from_raw(c_data.cast()); } } /// An opaque structure pointing to a PAM handle. #[repr(transparent)] pub struct PamHandle(*mut libc::c_void); impl PamHandle { /// Gets some value, identified by `key`, that has been set by the module /// previously. /// /// See the [`pam_get_data` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html). /// /// # Safety /// /// The data stored under the provided key must be of type `T` otherwise the /// behaviour of this function is undefined. /// /// The data, if present, is owned by the current PAM conversation. pub unsafe fn get_data<T>(&self, key: &str) -> Result<Option<&T>> { let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; let mut ptr: *const libc::c_void = std::ptr::null(); ErrorCode::result_from(pam_ffi::pam_get_data(self.0, c_key.as_ptr(), &mut ptr))?; match ptr.is_null() { true => Ok(None), false => { let typed_ptr = ptr.cast(); Ok(Some(&*typed_ptr)) } } } /// Stores a value that can be retrieved later with `get_data`. /// The conversation takes ownership of the data. /// /// See the [`pam_set_data` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html). pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()> { let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; let ret = unsafe { pam_ffi::pam_set_data( self.0, c_key.as_ptr(), Box::into_raw(data).cast(), cleanup::<T>, ) }; ErrorCode::result_from(ret) } /// Retrieves a value that has been set, possibly by the pam client. /// This is particularly useful for getting a `PamConv` reference. /// /// These items are *references to PAM memory* /// which are *owned by the conversation*. /// /// See the [`pam_get_item` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html). pub fn get_item<T: crate::items::Item>(&self) -> Result<Option<T>> { let mut ptr: *const libc::c_void = std::ptr::null(); let out = unsafe { let ret = pam_ffi::pam_get_item(self.0, T::type_id().into(), &mut ptr); ErrorCode::result_from(ret)?; let typed_ptr: *const T::Raw = ptr.cast(); match typed_ptr.is_null() { true => None, false => Some(T::from_raw(typed_ptr)), } }; Ok(out) } /// Sets an item in the pam context. It can be retrieved using `get_item`. /// /// See the [`pam_set_item` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html). pub fn set_item<T: Item>(&mut self, item: T) -> Result<()> { let ret = unsafe { pam_ffi::pam_set_item(self.0, T::type_id().into(), item.into_raw().cast()) }; ErrorCode::result_from(ret) } /// Retrieves the name of the user who is authenticating or logging in. /// /// This is really a specialization of `get_item`. /// /// See the [`pam_get_user` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html). pub fn get_user(&self, prompt: Option<&str>) -> Result<String> { let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = std::ptr::null_mut(); let ret = unsafe { pam_ffi::pam_get_user(self.0, &mut output, memory::prompt_ptr(prompt.as_ref())) }; ErrorCode::result_from(ret)?; memory::copy_pam_string(output) } /// Retrieves the authentication token from the user. /// /// This is really a specialization of `get_item`. /// /// See the [`pam_get_authtok` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html). pub fn get_authtok(&self, prompt: Option<&str>) -> Result<SecureString> { let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = std::ptr::null_mut(); let res = unsafe { pam_ffi::pam_get_authtok( self.0, ItemType::AuthTok.into(), &mut output, memory::prompt_ptr(prompt.as_ref()), ) }; ErrorCode::result_from(res)?; memory::copy_pam_string(output).map(SecureString::from) } } impl From<*mut libc::c_void> for PamHandle { /// Wraps an internal Handle pointer. fn from(value: *mut libc::c_void) -> Self { Self(value) } } /// Trait representing what a PAM module can do. /// /// By default, all the functions in this trait are ignored. /// Implement any functions you wish to handle in your module. /// After implementing this trait, use the [crate::pam_hooks!] macro /// to export your functions. /// /// For more information, see [`pam(3)`’s root manual page][manpage] /// and the [PAM Module Writer’s Guide][module-guide]. /// /// [manpage]: https://www.man7.org/linux/man-pages/man3/pam.3.html /// [module-guide]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html #[allow(unused_variables)] pub trait PamModule { /// This function performs the task of establishing whether the user is permitted to gain access at /// this time. It should be understood that the user has previously been validated by an /// authentication module. This function checks for other things. Such things might be: the time of /// day or the date, the terminal line, remote hostname, etc. This function may also determine /// things like the expiration on passwords, and respond that the user change it before continuing. fn acct_mgmt(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } /// This function performs the task of authenticating the user. fn sm_authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } /// This function is used to (re-)set the authentication token of the user. /// /// The PAM library calls this function twice in succession. The first time with /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is /// (possibly) changed. fn sm_chauthtok(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } /// This function is called to terminate a session. fn sm_close_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } /// This function is called to commence a session. fn sm_open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } /// This function performs the task of altering the credentials of the user with respect to the /// corresponding authorization scheme. Generally, an authentication module may have access to more /// information about a user than their authentication token. This function is used to make such /// information available to the application. It should only be called after the user has been /// authenticated but before a session has been established. fn sm_setcred(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } } /// Generates the dynamic library entry points for a [PamModule] implementation. /// /// Calling `pam_hooks!(SomeType)` on a type that implements [PamModule] will /// generate the exported `extern "C"` functions that PAM uses to call into /// your module. /// /// ## Examples: /// /// Here is full example of a PAM module that would authenticate and authorize everybody: /// /// ``` /// use nonstick::{Flags, PamHandle, PamModule, Result as PamResult, pam_hooks}; /// use std::ffi::CStr; /// /// # fn main() {} /// struct MyPamModule; /// pam_hooks!(MyPamModule); /// /// impl PamModule for MyPamModule { /// fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { /// // You should use a Conversation to communicate with the user /// // instead of writing to the console, but this is just an example. /// eprintln!("Everybody is authorized!"); /// Ok(()) /// } /// /// fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { /// eprintln!("Everybody is authenticated!"); /// Ok(()) /// } /// } /// ``` #[macro_export] macro_rules! pam_hooks { ($ident:ident) => { mod _pam_hooks_scope { use std::ffi::{c_char, c_int, CStr}; use $crate::{ErrorCode, Flags, PamModule}; #[no_mangle] extern "C" fn pam_sm_acct_mgmt( pamh: *mut libc::c_void, flags: Flags, argc: c_int, argv: *const *const c_char, ) -> c_int { let args = extract_argv(argc, argv); ErrorCode::result_to_c(super::$ident::acct_mgmt(&mut pamh.into(), args, flags)) } #[no_mangle] extern "C" fn pam_sm_authenticate( pamh: *mut libc::c_void, flags: Flags, argc: c_int, argv: *const *const c_char, ) -> c_int { let args = extract_argv(argc, argv); ErrorCode::result_to_c(super::$ident::sm_authenticate( &mut pamh.into(), args, flags, )) } #[no_mangle] extern "C" fn pam_sm_chauthtok( pamh: *mut libc::c_void, flags: Flags, argc: c_int, argv: *const *const c_char, ) -> c_int { let args = extract_argv(argc, argv); ErrorCode::result_to_c(super::$ident::sm_chauthtok(&mut pamh.into(), args, flags)) } #[no_mangle] extern "C" fn pam_sm_close_session( pamh: *mut libc::c_void, flags: Flags, argc: c_int, argv: *const *const c_char, ) -> c_int { let args = extract_argv(argc, argv); ErrorCode::result_to_c(super::$ident::sm_close_session( &mut pamh.into(), args, flags, )) } #[no_mangle] extern "C" fn pam_sm_open_session( pamh: *mut libc::c_void, flags: Flags, argc: c_int, argv: *const *const c_char, ) -> c_int { let args = extract_argv(argc, argv); ErrorCode::result_to_c(super::$ident::sm_open_session( &mut pamh.into(), args, flags, )) } #[no_mangle] extern "C" fn pam_sm_setcred( pamh: *mut libc::c_void, flags: Flags, argc: c_int, argv: *const *const c_char, ) -> c_int { let args = extract_argv(argc, argv); ErrorCode::result_to_c(super::$ident::sm_setcred(&mut pamh.into(), args, flags)) } /// Turns `argc`/`argv` into a [Vec] of [CStr]s. /// /// # Safety /// /// We use this only with arguments we get from `libpam`, which we kind of have to trust. fn extract_argv<'a>(argc: c_int, argv: *const *const c_char) -> Vec<&'a CStr> { (0..argc) .map(|o| unsafe { CStr::from_ptr(*argv.offset(o as isize) as *const c_char) }) .collect() } } }; } #[cfg(test)] pub mod test { use crate::module::PamModule; struct Foo; impl PamModule for Foo {} pam_hooks!(Foo); }