Mercurial > crates > nonstick
diff src/module.rs @ 64:bbe84835d6db v0.0.5
More organization; add lots of docs.
- moves `PamHandle` to its own module, since it will be used
by both modules and clients.
- adds a ton of documentation to the `PamModule` trait
and reorders methods to most-interesting-first.
- adds more flag values from pam_modules.h.
- other misc cleanup.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Thu, 22 May 2025 01:52:32 -0400 |
parents | 05cc2c27334f |
children |
line wrap: on
line diff
--- a/src/module.rs Wed May 21 23:19:43 2025 -0400 +++ b/src/module.rs Thu May 22 01:52:32 2025 -0400 @@ -1,203 +1,234 @@ //! 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::handle::PamHandle; +use std::ffi::CStr; -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. +/// A trait for a PAM module to implement. +/// +/// The default implementations of all these hooks tell PAM to ignore them +/// (i.e., behave as if this module does not exist) by returning [`ErrorCode::Ignore`]. +/// Override any functions you wish to handle in your module. +/// After implementing this trait, use the [`pam_hooks!`](crate::pam_hooks!) macro +/// to make the functions available to PAM. /// -/// 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()); - } -} +/// For more information, see [`pam(3)`’s root manual page][manpage] +/// and the [PAM Module Writer’s Guide][mwg]. +/// +/// [manpage]: https://www.man7.org/linux/man-pages/man3/pam.3.html +/// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html +#[allow(unused_variables)] +pub trait PamModule { + // Functions for auth modules. -/// 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. + /// Authenticate the user. /// - /// See the [`pam_get_data` manual page]( - /// https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html). - /// - /// # Safety + /// This is probably the first thing you want to implement. + /// In most cases, you will want to get the user and password, + /// using [`PamHandle::get_user`] and [`PamHandle::get_authtok`], + /// and verify them against something. /// - /// The data stored under the provided key must be of type `T` otherwise the - /// behaviour of this function is undefined. + /// See [the Module Writer's Guide entry for `pam_sm_authenticate`][mwg] + /// for more information. + /// + /// # Valid flags + /// + /// This function may be called with the following flags set: + /// + /// - [`Flags::SILENT`] + /// - [`Flags::DISALLOW_NULL_AUTHTOK`] + /// + /// # Returns + /// + /// If the password check was successful, return `Ok(())`. /// - /// 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. + /// Sensible error codes to return include: /// - /// 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) + /// - [`ErrorCode::AuthenticationError`]: Generic authentication error + /// (like an incorrect password). + /// - [`ErrorCode::CredentialsInsufficient`]: The application does not have + /// sufficient credentials to authenticate the user. + /// - [`ErrorCode::AuthInfoUnavailable`]: The module was not able to access + /// the authentication information, for instance due to a network failure. + /// - [`ErrorCode::UserUnknown`]: The supplied username is not known by this service. + /// - [`ErrorCode::MaxTries`]: The user has tried authenticating too many times. + /// They should not try again. + /// + /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-auth.html#mwg-pam_sm_authenticate + fn authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { + Err(ErrorCode::Ignore) } - /// Retrieves a value that has been set, possibly by the pam client. - /// This is particularly useful for getting a `PamConv` reference. + /// Perform "account management". /// - /// These items are *references to PAM memory* - /// which are *owned by the conversation*. + /// When PAM calls this function, the user has already been authenticated + /// by an authentication module (either this one or some other module). + /// This hook can check for other things, for instance: + /// + /// - Date/time (keep your kids off the computer at night) + /// - Remote host (only let employees log in from the office) /// - /// 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`. + /// You can also check things like, e.g., password expiration, + /// and alert that the user change it before continuing, + /// or really do whatever you want. /// - /// 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. + /// See [the Module Writer's Guide entry for `pam_sm_acct_mgmt`][mwg] + /// for more information. /// - /// This is really a specialization of `get_item`. + /// + /// # Valid flags /// - /// 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 function may be called with the following flags set: + /// + /// - [`Flags::SILENT`] + /// - [`Flags::DISALLOW_NULL_AUTHTOK`] /// - /// This is really a specialization of `get_item`. + /// # Returns + /// + /// If the user should be allowed to log in, return `Ok(())`. + /// + /// Sensible error codes to return include: /// - /// 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<()> { + /// - [`ErrorCode::AccountExpired`]: The user's account has expired. + /// - [`ErrorCode::AuthenticationError`]: Generic authentication error. + /// - [`ErrorCode::NewAuthTokRequired`]: The user's authentication token has expired. + /// PAM will ask the user to set a new authentication token, which may be handled by + /// this module in [`Self::change_authtok`]. + /// - [`ErrorCode::PermissionDenied`]: This one is pretty self-explanatory. + /// - [`ErrorCode::UserUnknown`]: The supplied username is not known by this service. + /// + /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-acct.html#mwg-pam_sm_acct_mgmt + fn account_management(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. + /// Set credentials on this session. + /// + /// If an authentication module knows more about the user than just + /// their authentication token, then it uses this function to provide + /// that information to the application. It should only be called after + /// authentication but before a session is established. + /// + /// See [the Module Writer's Guide entry for `pam_sm_setcred`][mwg] + /// for more information. + /// + /// # Valid flags + /// + /// This function may be called with the following flags set: /// - /// 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<()> { + /// - [`Flags::SILENT`] + /// - [`Flags::ESTABLISH_CREDENTIALS`]: Initialize credentials for the user. + /// - [`Flags::DELETE_CREDENTIALS`]: Delete the credentials associated with this module. + /// - [`Flags::REINITIALIZE_CREDENTIALS`]: Re-initialize credentials for this user. + /// - [`Flags::REFRESH_CREDENTIALS`]: Extend the lifetime of the user's credentials. + /// + /// # Returns + /// + /// If credentials were set successfully, return `Ok(())`. + /// + /// Sensible error codes to return include: + /// + /// - [`ErrorCode::CredentialsUnavailable`]: The credentials cannot be retrieved. + /// - [`ErrorCode::CredentialsExpired`]: The credentials have expired. + /// - [`ErrorCode::CredentialsError`]: Some other error occurred when setting credentials. + /// - [`ErrorCode::UserUnknown`]: The supplied username is not known by this service. + /// + /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-auth.html#mwg-pam_sm_setcred + fn set_credentials(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<()> { + // Function for chauthtok modules. + + /// Called to set or reset the user's authentication token. + /// + /// PAM calls this function twice in succession. + /// 1. The first time, [`Flags::PRELIMINARY_CHECK`] will be set. + /// If the new token is acceptable, return success; + /// if not, return [`ErrorCode::TryAgain`] to re-prompt the user. + /// 2. After the preliminary check succeeds, [`Flags::UPDATE_AUTHTOK`] + /// will be set. On this call, actually update the stored auth token. + /// + /// See [the Module Writer's Guide entry for `pam_sm_chauthtok`][mwg] + /// for more information. + /// + /// # Valid flags + /// + /// This function may be called with the following flags set: + /// + /// - [`Flags::SILENT`] + /// - [`Flags::CHANGE_EXPIRED_AUTHTOK`]: This module should only change + /// any expired passwords, and leave non-expired passwords alone. + /// If present, it _must_ be combined with one of the following. + /// - [`Flags::PRELIMINARY_CHECK`]: Don't actually change the password, + /// just check if the new one is valid. + /// - [`Flags::UPDATE_AUTHTOK`]: Do actually change the password. + /// + /// # Returns + /// + /// If the authentication token was changed successfully + /// (or the check passed), return `Ok(())`. + /// + /// Sensible error codes to return include: + /// + /// - [`ErrorCode::AuthTokError`]: The service could not get the authentication token. + /// - [`ErrorCode::AuthTokRecoveryError`]: The service could not get the old token. + /// - [`ErrorCode::AuthTokLockBusy`]: The password cannot be changed because + /// the authentication token is currently locked. + /// - [`ErrorCode::AuthTokDisableAging`]: Aging (expiration) is disabled. + /// - [`ErrorCode::PermissionDenied`]: What it says on the tin. + /// - [`ErrorCode::TryAgain`]: When the preliminary check is unsuccessful, + /// ask the user for a new authentication token. + /// - [`ErrorCode::UserUnknown`]: The supplied username is not known by this service. + /// + /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-chauthtok.html#mwg-pam_sm_chauthtok + fn change_authtok(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<()> { + // Functions for session modules. + + /// Called when a session is opened. + /// + /// See [the Module Writer's Guide entry for `pam_sm_open_session`][mwg] + /// for more information. + /// + /// # Valid flags + /// + /// The only valid flag is [`Flags::SILENT`]. + /// + /// # Returns + /// + /// If the session was opened successfully, return `Ok(())`. + /// + /// A sensible error code to return is: + /// + /// - [`ErrorCode::SessionError`]: Cannot make an entry for this session. + /// + /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-session.html#mwg-pam_sm_open_session + fn 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<()> { + /// Called when a session is being terminated. + /// + /// See [the Module Writer's Guide entry for `pam_sm_close_session`][mwg] + /// for more information. + /// + /// # Valid flags + /// + /// The only valid flag is [`Flags::SILENT`]. + /// + /// # Returns + /// + /// If the session was closed successfully, return `Ok(())`. + /// + /// A sensible error code to return is: + /// + /// - [`ErrorCode::SessionError`]: Cannot remove an entry for this session. + /// + /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-session.html#mwg-pam_sm_close_session + fn close_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } } @@ -212,24 +243,26 @@ /// /// Here is full example of a PAM module that would authenticate and authorize everybody: /// -/// ``` +/// ```no_run /// use nonstick::{Flags, PamHandle, PamModule, Result as PamResult, pam_hooks}; /// use std::ffi::CStr; +/// # fn main() {} /// -/// # 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!"); +/// fn authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { +/// let password = handle.get_authtok(Some("what's your password?"))?; +/// eprintln!("If you say your password is {:?}, who am I to disagree!", password.unsecure()); /// Ok(()) /// } /// -/// fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { -/// eprintln!("Everybody is authenticated!"); +/// fn account_management(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { +/// let username = handle.get_user(None)?; +/// // You should use a Conversation to communicate with the user +/// // instead of writing to the console, but this is just an example. +/// eprintln!("Hello {username}! I trust you unconditionally!"); /// Ok(()) /// } /// } @@ -249,7 +282,11 @@ 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)) + ErrorCode::result_to_c(super::$ident::account_management( + &mut pamh.into(), + args, + flags, + )) } #[no_mangle] @@ -260,11 +297,7 @@ 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, - )) + ErrorCode::result_to_c(super::$ident::authenticate(&mut pamh.into(), args, flags)) } #[no_mangle] @@ -275,7 +308,7 @@ 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)) + ErrorCode::result_to_c(super::$ident::change_authtok(&mut pamh.into(), args, flags)) } #[no_mangle] @@ -286,11 +319,7 @@ 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, - )) + ErrorCode::result_to_c(super::$ident::close_session(&mut pamh.into(), args, flags)) } #[no_mangle] @@ -301,11 +330,7 @@ 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, - )) + ErrorCode::result_to_c(super::$ident::open_session(&mut pamh.into(), args, flags)) } #[no_mangle] @@ -316,7 +341,11 @@ 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)) + ErrorCode::result_to_c(super::$ident::set_credentials( + &mut pamh.into(), + args, + flags, + )) } /// Turns `argc`/`argv` into a [Vec] of [CStr]s.