Mercurial > crates > nonstick
changeset 90:f6186e41399b
Miscellaneous fixes and cleanup:
- Rename `get_user` to `username` and `get_authtok` to `authtok`.
- Use pam_strerror for error messages.
- Add library linkage to build.rs (it was missing???).
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sat, 14 Jun 2025 09:30:16 -0400 |
parents | dd3e9c4bcde3 |
children | 039aae9a01f7 |
files | build.rs src/constants.rs src/handle.rs src/libpam/handle.rs src/libpam/module.rs src/libpam/pam_ffi.rs src/module.rs |
diffstat | 7 files changed, 68 insertions(+), 60 deletions(-) [+] |
line wrap: on
line diff
--- a/build.rs Fri Jun 13 05:22:48 2025 -0400 +++ b/build.rs Sat Jun 14 09:30:16 2025 -0400 @@ -4,6 +4,7 @@ fn main() { if cfg!(feature = "link") { + println!("cargo::rustc-link-lib=pam"); println!("cargo::rustc-check-cfg=cfg(pam_impl, values(\"linux-pam\",\"openpam\"))"); let common_builder = bindgen::Builder::default() .merge_extern_blocks(true) @@ -16,7 +17,7 @@ .allowlist_function("pam_get_user") .allowlist_function("pam_get_authtok") .allowlist_function("pam_end") - .dynamic_link_require_all(true) + .allowlist_function("pam_strerror") .default_macro_constant_type(MacroTypeVariation::Unsigned); let linux_builder = common_builder
--- a/src/constants.rs Fri Jun 13 05:22:48 2025 -0400 +++ b/src/constants.rs Sat Jun 14 09:30:16 2025 -0400 @@ -9,7 +9,10 @@ use bitflags::bitflags; use libc::c_int; use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::error::Error; use std::ffi::c_uint; +use std::fmt; +use std::fmt::{Display, Formatter}; use std::result::Result as StdResult; /// Arbitrary values for PAM constants when not linking against system PAM. @@ -77,6 +80,10 @@ PAM_TRY_AGAIN = 552, PAM_USER_UNKNOWN = 553 ); + + fn strerror(val: c_uint) -> Option<&'static str> { + None + } } bitflags! { @@ -140,79 +147,59 @@ /// For more detailed information, see /// `/usr/include/security/_pam_types.h`. #[allow(non_camel_case_types, dead_code)] -#[derive(Copy, Clone, Debug, PartialEq, thiserror::Error, TryFromPrimitive, IntoPrimitive)] +#[derive(Copy, Clone, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)] #[non_exhaustive] // C might give us anything! #[repr(u32)] pub enum ErrorCode { - #[error("dlopen() failure when dynamically loading a service module")] OpenError = pam_ffi::PAM_OPEN_ERR, - #[error("symbol not found")] SymbolError = pam_ffi::PAM_SYMBOL_ERR, - #[error("error in service module")] ServiceError = pam_ffi::PAM_SERVICE_ERR, - #[error("system error")] SystemError = pam_ffi::PAM_SYSTEM_ERR, - #[error("memory buffer error")] BufferError = pam_ffi::PAM_BUF_ERR, - #[error("permission denied")] PermissionDenied = pam_ffi::PAM_PERM_DENIED, - #[error("authentication failure")] AuthenticationError = pam_ffi::PAM_AUTH_ERR, - #[error("cannot access authentication data due to insufficient credentials")] CredentialsInsufficient = pam_ffi::PAM_CRED_INSUFFICIENT, - #[error("underlying authentication service cannot retrieve authentication information")] AuthInfoUnavailable = pam_ffi::PAM_AUTHINFO_UNAVAIL, - #[error("user not known to the underlying authentication module")] UserUnknown = pam_ffi::PAM_USER_UNKNOWN, - #[error("retry limit reached; do not attempt further")] MaxTries = pam_ffi::PAM_MAXTRIES, - #[error("new authentication token required")] NewAuthTokRequired = pam_ffi::PAM_NEW_AUTHTOK_REQD, - #[error("user account has expired")] AccountExpired = pam_ffi::PAM_ACCT_EXPIRED, - #[error("cannot make/remove an entry for the specified session")] SessionError = pam_ffi::PAM_SESSION_ERR, - #[error("underlying authentication service cannot retrieve user credentials")] CredentialsUnavailable = pam_ffi::PAM_CRED_UNAVAIL, - #[error("user credentials expired")] CredentialsExpired = pam_ffi::PAM_CRED_EXPIRED, - #[error("failure setting user credentials")] CredentialsError = pam_ffi::PAM_CRED_ERR, - #[error("no module-specific data is present")] NoModuleData = pam_ffi::PAM_NO_MODULE_DATA, - #[error("conversation error")] ConversationError = pam_ffi::PAM_CONV_ERR, - #[error("authentication token manipulation error")] AuthTokError = pam_ffi::PAM_AUTHTOK_ERR, - #[error("authentication information cannot be recovered")] AuthTokRecoveryError = pam_ffi::PAM_AUTHTOK_RECOVERY_ERR, - #[error("authentication token lock busy")] AuthTokLockBusy = pam_ffi::PAM_AUTHTOK_LOCK_BUSY, - #[error("authentication token aging disabled")] AuthTokDisableAging = pam_ffi::PAM_AUTHTOK_DISABLE_AGING, - #[error("preliminary password check failed")] TryAgain = pam_ffi::PAM_TRY_AGAIN, - #[error("ignore underlying account module, regardless of control flag")] Ignore = pam_ffi::PAM_IGNORE, - #[error("critical error; this module should fail now")] Abort = pam_ffi::PAM_ABORT, - #[error("authentication token has expired")] AuthTokExpired = pam_ffi::PAM_AUTHTOK_EXPIRED, - #[error("module is not known")] ModuleUnknown = pam_ffi::PAM_MODULE_UNKNOWN, - #[error("bad item passed to pam_[whatever]_item")] BadItem = pam_ffi::PAM_BAD_ITEM, #[cfg(feature = "linux-pam-extensions")] - #[error("conversation function is event-driven and data is not available yet")] ConversationAgain = pam_ffi::PAM_CONV_AGAIN, #[cfg(feature = "linux-pam-extensions")] - #[error("call this function again to complete authentication stack")] Incomplete = pam_ffi::PAM_INCOMPLETE, } /// A PAM-specific Result type with an [ErrorCode] error. pub type Result<T> = StdResult<T, ErrorCode>; +impl Display for ErrorCode { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match pam_ffi::strerror((*self).into()) { + Some(err) => f.write_str(err), + None => self.fmt_internal(f), + } + } +} + +impl Error for ErrorCode {} + impl ErrorCode { /// Converts this [Result] into a C-compatible result code. pub fn result_to_c<T>(value: Result<T>) -> c_int { @@ -230,6 +217,11 @@ value => Err((value as u32).try_into().unwrap_or(Self::SystemError)), } } + + /// A basic Display implementation for if we don't link against PAM. + fn fmt_internal(self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "PAM error: {self:?} ({n})", n = self as c_uint) + } } /// Returned when text that should not have any `\0` bytes in it does.
--- a/src/handle.rs Fri Jun 13 05:22:48 2025 -0400 +++ b/src/handle.rs Sat Jun 14 09:30:16 2025 -0400 @@ -74,36 +74,36 @@ /// # use nonstick::PamShared; /// # fn _doc(handle: &mut impl PamShared) -> Result<(), Box<dyn std::error::Error>> { /// // Get the username using the default prompt. - /// let user = handle.get_user(None)?; + /// let user = handle.username(None)?; /// // Get the username using a custom prompt. /// // If this were actually called right after the above, /// // both user and user_2 would have the same value. - /// let user_2 = handle.get_user(Some("who ARE you even???"))?; + /// let user_2 = handle.username(Some("who ARE you even???"))?; /// # Ok(()) /// # } /// ``` /// /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_user - fn get_user(&mut self, prompt: Option<&str>) -> Result<&str>; + fn username(&mut self, prompt: Option<&str>) -> Result<&str>; trait_item!( /// The identity of the user for whom service is being requested. /// - /// Unlike [`get_user`](Self::get_user), this will simply get + /// Unlike [`username`](Self::username), this will simply get /// the current state of the user item, and not request the username. - /// While PAM usually sets this automatically in the `get_user` call, + /// While PAM usually sets this automatically in the `username` call, /// it may be changed by a module during the PAM transaction. /// Applications should check it after each step of the PAM process. get = user_item, item = "PAM_USER", - see = Self::get_user + see = Self::username ); trait_item!( /// Sets the identity of the logging-in user. /// /// Usually this will be set during the course of - /// a [`get_user`](Self::get_user) call, but you may set it manually + /// a [`username`](Self::username) call, but you may set it manually /// or change it during the PAM process. set = set_user_item, item = "PAM_USER", @@ -198,7 +198,7 @@ /// Gets the user's authentication token (e.g., password). /// /// This is usually set automatically when - /// [`get_authtok`](PamHandleModule::get_authtok) is called, + /// [`authtok`](PamHandleModule::authtok) is called, /// but can be manually set. set = set_authtok_item, item = "PAM_AUTHTOK", @@ -248,28 +248,28 @@ /// # use nonstick::handle::PamHandleModule; /// # fn _doc(handle: &mut impl PamHandleModule) -> Result<(), Box<dyn std::error::Error>> { /// // Get the user's password using the default prompt. - /// let pass = handle.get_authtok(None)?; + /// let pass = handle.authtok(None)?; /// // Get the user's password using a custom prompt. - /// let pass = handle.get_authtok(Some("Reveal your secrets!"))?; + /// let pass = handle.authtok(Some("Reveal your secrets!"))?; /// Ok(()) /// # } /// ``` /// /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item - fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str>; + fn authtok(&mut self, prompt: Option<&str>) -> Result<&str>; trait_item!( /// Gets the user's authentication token (e.g., password). /// /// This is normally set automatically by PAM when calling - /// [`get_authtok`](Self::get_authtok), but can be set explicitly. + /// [`authtok`](Self::authtok), but can be set explicitly. /// - /// Like `get_authtok`, this should only ever be called + /// Like `authtok`, this should only ever be called /// by *authentication* and *password-change* PAM modules. get = authtok_item, item = "PAM_AUTHTOK", - see = Self::get_authtok + see = Self::authtok ); trait_item!(
--- a/src/libpam/handle.rs Fri Jun 13 05:22:48 2025 -0400 +++ b/src/libpam/handle.rs Sat Jun 14 09:30:16 2025 -0400 @@ -8,6 +8,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::cell::Cell; use std::ffi::{c_char, c_int}; +use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::ptr; @@ -27,9 +28,10 @@ } /// An owned PAM handle. -pub struct OwnedLibPamHandle { +pub struct OwnedLibPamHandle<'a> { handle: HandleWrap, last_return: Cell<Result<()>>, + _conversation_lifetime: PhantomData<&'a mut ()>, } // TODO: pam_authenticate - app @@ -42,7 +44,7 @@ // pam_getenv - shared // pam_getenvlist - shared -impl Drop for OwnedLibPamHandle { +impl Drop for OwnedLibPamHandle<'_> { /// Closes the PAM session on an owned PAM handle. /// /// See the [`pam_end` manual page][man] for more information. @@ -72,7 +74,7 @@ } impl PamShared for LibPamHandle { - fn get_user(&mut self, prompt: Option<&str>) -> Result<&str> { + fn username(&mut self, prompt: Option<&str>) -> Result<&str> { let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = ptr::null(); let ret = unsafe { @@ -114,7 +116,7 @@ } impl PamHandleModule for LibPamHandle { - fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str> { + fn authtok(&mut self, prompt: Option<&str>) -> Result<&str> { let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = ptr::null_mut(); // SAFETY: We're calling this with known-good values. @@ -221,8 +223,8 @@ result.as_ref().map(drop).map_err(|&e| e) } -impl PamShared for OwnedLibPamHandle { - delegate!(fn get_user(&mut self, prompt: Option<&str>) -> Result<&str>); +impl PamShared for OwnedLibPamHandle<'_> { + delegate!(fn username(&mut self, prompt: Option<&str>) -> Result<&str>); delegate!(get = user_item, set = set_user_item); delegate!(get = service, set = set_service); delegate!(get = user_prompt, set = set_user_prompt);
--- a/src/libpam/module.rs Fri Jun 13 05:22:48 2025 -0400 +++ b/src/libpam/module.rs Sat Jun 14 09:30:16 2025 -0400 @@ -22,7 +22,7 @@ /// /// 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?"))?; +/// let password = handle.authtok(Some("what's your password?"))?; /// let response = /// format!("If you say your password is {password:?}, who am I to disagree?"); /// handle.info_msg(&response); @@ -30,7 +30,7 @@ /// } /// /// fn account_management(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { -/// let username = handle.get_user(None)?; +/// let username = handle.username(None)?; /// let response = format!("Hello {username}! I trust you unconditionally."); /// handle.info_msg(&response); /// Ok(())
--- a/src/libpam/pam_ffi.rs Fri Jun 13 05:22:48 2025 -0400 +++ b/src/libpam/pam_ffi.rs Sat Jun 14 09:30:16 2025 -0400 @@ -3,8 +3,9 @@ #![allow(non_camel_case_types)] use crate::libpam::memory::Immovable; -use std::ffi::{c_int, c_uint, c_void}; +use std::ffi::{c_int, c_uint, c_void, CStr}; use std::marker::PhantomData; +use std::ptr; /// An opaque structure that a PAM handle points to. #[repr(C)] @@ -28,7 +29,7 @@ #[derive(Debug)] pub struct Answer { /// Pointer to the data returned in an answer. - /// For most answers, this will be a [`CStr`](std::ffi::CStr), + /// For most answers, this will be a [`CStr`], /// but for [`BinaryQAndA`](crate::conv::BinaryQAndA)s (a Linux-PAM extension), /// this will be [`CBinaryData`](crate::libpam::memory::CBinaryData). /// @@ -53,7 +54,7 @@ pub style: c_uint, /// A description of the data requested. /// - /// For most requests, this will be an owned [`CStr`](std::ffi::CStr), + /// For most requests, this will be an owned [`CStr`], /// but for requests with style `PAM_BINARY_PROMPT`, /// this will be `CBinaryData` (a Linux-PAM extension). pub data: *mut c_void, @@ -90,6 +91,18 @@ pub _marker: Immovable, } +/// Gets a string version of an error message. +pub fn strerror(code: c_uint) -> Option<&'static str> { + // SAFETY: Every single PAM implementation I can find (Linux-PAM, OpenPAM, + // Solaris, etc.) returns a static string and ignores the handle value. + let strerror = unsafe { pam_strerror(ptr::null_mut(), code as c_int) }; + if strerror.is_null() { + None + } else { + unsafe { CStr::from_ptr(strerror) }.to_str().ok() + } +} + type pam_handle = LibPamHandle; type pam_conv = LibPamConversation<'static>;
--- a/src/module.rs Fri Jun 13 05:22:48 2025 -0400 +++ b/src/module.rs Sat Jun 14 09:30:16 2025 -0400 @@ -28,8 +28,8 @@ /// /// This is probably the first thing you want to implement. /// In most cases, you will want to get the user and password, - /// using [`PamShared::get_user`](crate::PamShared::get_user) - /// and [`PamHandleModule::get_authtok`], + /// using [`PamShared::username`](crate::PamShared::username) + /// and [`PamHandleModule::authtok`], /// and verify them against something. /// /// See [the Module Writer's Guide entry for `pam_sm_authenticate`][mwg]