Mercurial > crates > nonstick
diff src/module.rs @ 60:05cc2c27334f
The Big Refactor: clean up docs and exports.
- Brings the most important symbols in the library to the root
with `pub use` statements.
- Expands and updates documentation.
- Rearranges things extensively to make the external interface nicer
and make the structure easier to understand.
- Renames a few things (e.g. `Result`).
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Wed, 21 May 2025 19:00:51 -0400 |
parents | 3f4a77aa88be |
children | bbe84835d6db |
line wrap: on
line diff
--- a/src/module.rs Wed May 21 00:27:18 2025 -0400 +++ b/src/module.rs Wed May 21 19:00:51 2025 -0400 @@ -1,73 +1,28 @@ -//! Functions for use in pam modules. +//! Functions and types useful for implementing a PAM module. -use crate::constants::{ErrorCode, Flags, PamResult}; +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}; -/// Opaque type, used as a pointer when making pam API calls. -/// -/// A module is invoked via an external function such as `pam_sm_authenticate`. -/// Such a call provides a pam handle pointer. The same pointer should be given -/// as an argument when making API calls. -#[repr(C)] -pub struct PamHandle { - _data: [u8; 0], -} - -#[link(name = "pam")] -extern "C" { - fn pam_get_data( - pamh: *const PamHandle, - module_data_name: *const c_char, - data: &mut *const libc::c_void, - ) -> c_int; - - fn pam_set_data( - pamh: *const PamHandle, - module_data_name: *const c_char, - data: *const libc::c_void, - cleanup: extern "C" fn( - pamh: *const PamHandle, - data: *mut libc::c_void, - error_status: c_int, - ), - ) -> c_int; - - fn pam_get_item( - pamh: *const PamHandle, - item_type: c_int, - item: &mut *const libc::c_void, - ) -> c_int; - - fn pam_set_item(pamh: *mut PamHandle, item_type: c_int, item: *const libc::c_void) -> c_int; - - fn pam_get_user( - pamh: *const PamHandle, - user: &mut *const c_char, - prompt: *const c_char, - ) -> c_int; - - fn pam_get_authtok( - pamh: *const PamHandle, - item_type: c_int, - data: &mut *const c_char, - prompt: *const c_char, - ) -> c_int; - -} +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 PamHandle, c_data: *mut libc::c_void, _: c_int) { +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. @@ -75,20 +30,16 @@ /// See the [`pam_get_data` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html). /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - /// /// # 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) -> PamResult<Option<&T>> { + 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_get_data(self, c_key.as_ptr(), &mut ptr))?; + ErrorCode::result_from(pam_ffi::pam_get_data(self.0, c_key.as_ptr(), &mut ptr))?; match ptr.is_null() { true => Ok(None), false => { @@ -103,15 +54,11 @@ /// /// See the [`pam_set_data` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> PamResult<()> { + 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_set_data( - self, + pam_ffi::pam_set_data( + self.0, c_key.as_ptr(), Box::into_raw(data).cast(), cleanup::<T>, @@ -128,14 +75,10 @@ /// /// See the [`pam_get_item` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> { + 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_get_item(self, T::type_id().into(), &mut ptr); + 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() { @@ -150,12 +93,9 @@ /// /// See the [`pam_set_item` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn set_item<T: Item>(&mut self, item: T) -> PamResult<()> { - let ret = unsafe { pam_set_item(self, T::type_id().into(), item.into_raw().cast()) }; + 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) } @@ -165,16 +105,14 @@ /// /// See the [`pam_get_user` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> { - let prompt = option_cstr(prompt)?; + 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_get_user(self, &mut output, prompt_ptr(prompt.as_ref())) }; + let ret = unsafe { + pam_ffi::pam_get_user(self.0, &mut output, memory::prompt_ptr(prompt.as_ref())) + }; ErrorCode::result_from(ret)?; - copy_pam_string(output) + memory::copy_pam_string(output) } /// Retrieves the authentication token from the user. @@ -183,75 +121,54 @@ /// /// See the [`pam_get_authtok` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<SecureString> { - let prompt = option_cstr(prompt)?; + 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_get_authtok( - self, + pam_ffi::pam_get_authtok( + self.0, ItemType::AuthTok.into(), &mut output, - prompt_ptr(prompt.as_ref()), + memory::prompt_ptr(prompt.as_ref()), ) }; ErrorCode::result_from(res)?; - copy_pam_string(output).map(SecureString::from) + memory::copy_pam_string(output).map(SecureString::from) } } -/// Safely converts a `&str` option to a `CString` option. -fn option_cstr(prompt: Option<&str>) -> PamResult<Option<CString>> { - prompt - .map(CString::new) - .transpose() - .map_err(|_| ErrorCode::ConversationError) -} - -/// The pointer to the prompt CString, or null if absent. -pub(crate) fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { - match prompt { - Some(c_str) => c_str.as_ptr(), - None => std::ptr::null(), +impl From<*mut libc::c_void> for PamHandle { + /// Wraps an internal Handle pointer. + fn from(value: *mut libc::c_void) -> Self { + Self(value) } } -/// Creates an owned copy of a string that is returned from a -/// <code>pam_get_<var>whatever</var></code> function. -pub(crate) fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> { - // We really shouldn't get a null pointer back here, but if we do, return nothing. - if result_ptr.is_null() { - return Ok(String::new()); - } - let bytes = unsafe { CStr::from_ptr(result_ptr) }; - bytes - .to_str() - .map(String::from) - .map_err(|_| ErrorCode::ConversationError) -} - -/// Provides functions that are invoked by the entrypoints generated by the -/// [`pam_hooks!` macro](../macro.pam_hooks.html). +/// 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. /// -/// All hooks are ignored by PAM dispatch by default given the default return value of `PAM_IGNORE`. -/// Override any functions that you want to handle with your module. See [PAM’s root manual page]( -/// https://www.man7.org/linux/man-pages/man3/pam.3.html). +/// 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 PamHooks { +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) -> PamResult<()> { + 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) -> PamResult<()> { + fn sm_authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } @@ -261,17 +178,17 @@ /// `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) -> PamResult<()> { + 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) -> PamResult<()> { + 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) -> PamResult<()> { + fn sm_open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } @@ -280,7 +197,148 @@ /// 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) -> PamResult<()> { + 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); +}