Mercurial > crates > nonstick
view 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 source
//! Functions and types useful for implementing a PAM module. use crate::constants::{ErrorCode, Flags, Result}; use crate::handle::PamHandle; 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 { // 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 [`PamHandle::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 PamHandle, 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 PamHandle, 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 PamHandle, 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 PamHandle, 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 PamHandle, 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 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: /// /// ```no_run /// 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 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 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(()) /// } /// } /// ``` #[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::account_management( &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::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::change_authtok(&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::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::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::set_credentials( &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); }