Mercurial > crates > nonstick
view src/module.rs @ 73:ac6881304c78
Do conversations, along with way too much stuff.
This implements conversations, along with all the memory management
brouhaha that goes along with it. The conversation now lives directly
on the handle rather than being a thing you have to get from it
and then call manually. It Turns Out this makes things a lot easier!
I guess we reorganized things again. For the last time. For real.
I promise.
This all passes ASAN, so it seems Pretty Good!
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Thu, 05 Jun 2025 03:41:38 -0400 |
parents | 47eb242a4f88 |
children | c7c596e6388f |
line wrap: on
line source
//! Functions and types useful for implementing a PAM module. // Temporarily allowed until we get the actual conversation functions hooked up. #![allow(dead_code)] use crate::constants::{ErrorCode, Flags, Result}; use crate::handle::PamHandleModule; use std::ffi::CStr; /// 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. /// /// 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<T: PamHandleModule> { // Functions for auth modules. /// Authenticate the user. /// /// 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 [`PamModuleOnly::get_authtok`], /// and verify them against something. /// /// 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(())`. /// /// Sensible error codes to return include: /// /// - [`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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } /// Perform "account management". /// /// 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) /// /// 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 Module Writer's Guide entry for `pam_sm_acct_mgmt`][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 user should be allowed to log in, return `Ok(())`. /// /// Sensible error codes to return include: /// /// - [`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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } /// 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: /// /// - [`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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } // 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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } // 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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } /// 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 T, 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: /// /// ```no_run /// use nonstick::{Flags, OwnedLibPamHandle, PamModule, PamHandleModule, Result as PamResult, pam_hooks}; /// use std::ffi::CStr; /// # fn main() {} /// /// struct MyPamModule; /// pam_hooks!(MyPamModule); /// /// impl<T: PamHandleModule> PamModule<T> for MyPamModule { /// fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { /// let password = handle.get_authtok(Some("what's your password?"))?; /// // You should use a Conversation to communicate with the user /// // instead of writing to the console, but this is just an example. /// eprintln!("If you say your password is {password:?}, who am I to disagree?"); /// Ok(()) /// } /// /// fn account_management(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { /// let username = handle.get_user(None)?; /// eprintln!("Hello {username:?}! I trust you unconditionally!"); /// 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, LibPamHandle, 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 { if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { let args = extract_argv(argc, argv); ErrorCode::result_to_c(super::$ident::account_management(handle, args, flags)) } else { ErrorCode::Ignore as c_int } } #[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 { if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { let args = extract_argv(argc, argv); ErrorCode::result_to_c(super::$ident::authenticate(handle, args, flags)) } else { ErrorCode::Ignore as c_int } } #[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 { if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { let args = extract_argv(argc, argv); ErrorCode::result_to_c(super::$ident::change_authtok(handle, args, flags)) } else { ErrorCode::Ignore as c_int } } #[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 { if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { let args = extract_argv(argc, argv); ErrorCode::result_to_c(super::$ident::close_session(handle, args, flags)) } else { ErrorCode::Ignore as c_int } } #[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); if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { ErrorCode::result_to_c(super::$ident::open_session(handle, args, flags)) } else { ErrorCode::Ignore as c_int } } #[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); if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { ErrorCode::result_to_c(super::$ident::set_credentials(handle, args, flags)) } else { ErrorCode::Ignore as c_int } } /// 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)] mod tests { // Compile-time test that the `pam_hooks` macro compiles. use super::super::{PamHandleModule, PamModule}; struct Foo; impl<T: PamHandleModule> PamModule<T> for Foo {} pam_hooks!(Foo); }