Mercurial > crates > nonstick
changeset 118:39760dfc9b3b
Detect PAM library based only on system lib; rename minimal lib to XSso.
Also formats and assorted other cleanup.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 29 Jun 2025 20:13:03 -0400 |
parents | 20f7712a6857 |
children | 476a22db8639 |
files | libpam-sys/README.md libpam-sys/libpam-sys-impls/build.rs libpam-sys/libpam-sys-impls/src/lib.rs libpam-sys/libpam-sys-test/build.rs libpam-sys/src/constants.rs libpam-sys/src/functions.rs libpam-sys/src/helpers.rs libpam-sys/src/lib.rs libpam-sys/src/structs.rs src/_doc.rs src/libpam/handle.rs src/logging.rs |
diffstat | 12 files changed, 216 insertions(+), 138 deletions(-) [+] |
line wrap: on
line diff
--- a/libpam-sys/README.md Sun Jun 29 18:48:14 2025 -0400 +++ b/libpam-sys/README.md Sun Jun 29 20:13:03 2025 -0400 @@ -9,10 +9,10 @@ - Linux: `LinuxPam` - BSDs, including Mac OS: `OpenPam` - Illumos/Solaris: `Sun` -- Unknown: `OpenPamMinimal` +- Unknown: `XSso` Each implementation exports all the functionality available in its respective PAM library. -`OpenPamMinimal` is a subset that includes only the functions available in the spec, and the constants shared in common between OpenPAM and Sun's implementation. +`XSso` exports only what is in the [X/SSO specification][xsso]. ## References
--- a/libpam-sys/libpam-sys-impls/build.rs Sun Jun 29 18:48:14 2025 -0400 +++ b/libpam-sys/libpam-sys-impls/build.rs Sun Jun 29 20:13:03 2025 -0400 @@ -6,12 +6,14 @@ //! 2. It detects the current PAM implementation and sets an env var for //! the macros in `libpam-sys-impl`. +use dlopen::raw::Library; use proc_macro2::TokenStream; use quote::quote; +use std::ffi::c_void; use std::{env, fs}; -use std::ffi::c_void; -use strum::EnumString; -use dlopen::raw::Library; +use strum::{EnumIter, EnumString, IntoEnumIterator}; + +const DETECT: &str = "__detect__"; fn main() { let pam_impl = match option_env!("LIBPAMSYS_IMPL") { @@ -30,27 +32,31 @@ } else if cfg!(any(target_os = "illumos", target_os = "solaris",)) { PamImpl::Sun } else { - PamImpl::MinimalOpenPam + PamImpl::XSso } } - Some("_detect") => { - // Detect what library we're using based on the symbols. + Some(DETECT) => { + // Detect the library based on the symbols in libpam.so. let lib = Library::open("libpam.so").unwrap(); if symbol_exists(&lib, "pam_syslog") { PamImpl::LinuxPam } else if symbol_exists(&lib, "_openpam_log") { PamImpl::OpenPam - } else if header_exists("security/pam_appl.h") { - // We figure we're *probably* on a Sun derivative. + } else if symbol_exists(&lib, "__pam_get_authtok") { PamImpl::Sun } else { // If all else fails, assume the bare minimum. - PamImpl::MinimalOpenPam + PamImpl::XSso } } Some(other) => match PamImpl::try_from(other) { Ok(i) => i, - Err(_) => panic!("unknown PAM implementation {other:?}"), + Err(_) => { + let valid: Vec<_> = PamImpl::iter().collect(); + panic!( + "unknown PAM implementation {other:?}. valid LIBPAMSYS_IMPLs are {valid:?}, or use {DETECT:?}" + ) + } }, }; fs::write( @@ -95,7 +101,7 @@ } self_aware_enum!( - #here[derive(EnumString, strum::Display)] + #here[derive(EnumString, strum::Display, EnumIter)] /// The PAM implementations supported by `libpam-sys`. #[derive(Clone, Copy, Debug, PartialEq)] PamImpl { @@ -111,17 +117,9 @@ /// /// [sun]: https://code.illumos.org/plugins/gitiles/illumos-gate/+/refs/heads/master/usr/src/lib/libpam Sun, - /// Only the functionality in [the PAM spec], with OpenPAM/Sun consts. + /// Only the functionality and constants in [the PAM spec]. /// /// [the PAM spec]: https://pubs.opengroup.org/onlinepubs/8329799/toc.htm - MinimalOpenPam, + XSso, } ); - -fn header_exists(header: &str) -> bool { - bindgen::Builder::default() - .blocklist_item(".*") - .header_contents("header.h", &format!("#include <{header}>")) - .generate() - .is_ok() -}
--- a/libpam-sys/libpam-sys-impls/src/lib.rs Sun Jun 29 18:48:14 2025 -0400 +++ b/libpam-sys/libpam-sys-impls/src/lib.rs Sun Jun 29 20:13:03 2025 -0400 @@ -253,7 +253,7 @@ ("Sun", (any("Sun", "OpenPam"))), ("OpenPam", (any("Sun", "OpenPam"))), ("LinuxPam", (not("OpenPam"))), - ("MinimalOpenPam", (not("OpenPam"))), + ("XSso", (not("OpenPam"))), ("Other", (not(any("This", "That")))), ("OpenPam", (not(not("OpenPam")))), ("Anything", (not(any()))), @@ -269,8 +269,8 @@ ("OpenPam", (any("LinuxPam", "Sun"))), ("One", (not(any("One", "Another")))), ("Negatory", (not(not("Affirmative")))), - ("MinimalOpenPam", ("OpenPam")), - ("OpenPam", ("MinimalOpenPam")), + ("XSso", ("OpenPam")), + ("OpenPam", ("XSso")), ]; for (bad, tree) in nonmatching { let pred = parse(tree);
--- a/libpam-sys/libpam-sys-test/build.rs Sun Jun 29 18:48:14 2025 -0400 +++ b/libpam-sys/libpam-sys-test/build.rs Sun Jun 29 20:13:03 2025 -0400 @@ -18,7 +18,11 @@ "security/pam_ext.h".into(), "security/pam_modules.h".into(), ], - ignore_consts: vec!["__LINUX_PAM__".into(), "__LINUX_PAM_MINOR__".into()], + ignore_consts: vec![ + "__LINUX_PAM__".into(), + "__LINUX_PAM_MINOR__".into(), + "PAM_AUTHTOK_RECOVER_ERR".into(), + ], } }
--- a/libpam-sys/src/constants.rs Sun Jun 29 18:48:14 2025 -0400 +++ b/libpam-sys/src/constants.rs Sun Jun 29 20:13:03 2025 -0400 @@ -39,6 +39,7 @@ // There are a few truly universal constants. // They are defined here directly. +/// The successful return code. pub const PAM_SUCCESS: i32 = 0; c_enum!( @@ -51,6 +52,7 @@ PAM_AUTHTOK, PAM_OLDAUTHTOK, PAM_RUSER, + PAM_USER_PROMPT, ); c_enum!( @@ -73,7 +75,7 @@ #[cfg_pam_impl("LinuxPam")] mod linux_pam { c_enum!( - /// An error code. + /// An error return code. PAM_OPEN_ERR = 1, PAM_SYMBOL_ERR, PAM_SERVICE_ERR, @@ -107,8 +109,6 @@ PAM_INCOMPLETE, _PAM_RETURN_VALUES, ); - /// An error code. - pub const PAM_AUTHTOK_RECOVER_ERR: i32 = 21; define!( /// A flag value. @@ -127,8 +127,7 @@ ); c_enum!( - PAM_USER_PROMPT = 9, - PAM_FAIL_DELAY, + PAM_FAIL_DELAY = 10, PAM_XDISPLAY, PAM_XAUTHDATA, PAM_AUTHTOK_TYPE, @@ -145,12 +144,12 @@ ); } -#[cfg_pam_impl(any("OpenPam", "OpenPamMinimal", "Sun"))] -pub use openpam_sun::*; -#[cfg_pam_impl(any("OpenPam", "OpenPamMinimal", "Sun"))] -mod openpam_sun { +#[cfg_pam_impl(any("OpenPam", "Sun", "XSso"))] +pub use xsso_shared::*; +#[cfg_pam_impl(any("OpenPam", "Sun", "XSso"))] +mod xsso_shared { c_enum!( - /// An error code. + /// An error return code. PAM_OPEN_ERR = 1, PAM_SYMBOL_ERR, PAM_SERVICE_ERR, @@ -179,17 +178,13 @@ PAM_ABORT, PAM_TRY_AGAIN, ); - - define!( - /// An item type. - PAM_USER_PROMPT = 9; - PAM_REPOSITORY = 10; - ); + // While `PAM_MODULE_UNKNOWN` and `PAM_DOMAIN_UNKNOWN` are in X/SSO, + // Sun doesn't use them so we're omitting them here. /// A general flag for PAM operations. pub const PAM_SILENT: i32 = 0x80000000u32 as i32; - /// The password must be non-null. + /// A flag for `pam_authenticate`. pub const PAM_DISALLOW_NULL_AUTHTOK: i32 = 0b1; define!( @@ -201,7 +196,7 @@ ); define!( - /// A flag for `pam_chauthtok`. + /// A flag for `pam_sm_chauthtok`. PAM_PRELIM_CHECK = 0b0001; PAM_UPDATE_AUTHTOK = 0b0010; PAM_CHANGE_EXPIRED_AUTHTOK = 0b0100; @@ -213,7 +208,7 @@ #[cfg_pam_impl("OpenPam")] mod openpam { c_enum!( - /// An error code. + /// An error return code. PAM_MODULE_UNKNOWN = 28, PAM_DOMAIN_UNKNOWN, PAM_BAD_HANDLE, @@ -221,12 +216,13 @@ PAM_BAD_FEATURE, PAM_BAD_CONSTANT, ); - /// The total number of PAM error codes. + /// The total number of PAM error codes (including success). pub const PAM_NUM_ERRORS: i32 = 34; c_enum!( /// An item type. - PAM_AUTHTOK_PROMPT = 11, + PAM_REPOSITORY = 10, + PAM_AUTHTOK_PROMPT, PAM_OLDAUTHTOK_PROMPT, PAM_HOST, ); @@ -262,10 +258,11 @@ /// The total number of PAM error codes. pub const PAM_TOTAL_ERRNUM: i32 = 28; - define!( + c_enum!( /// An item type. - PAM_RESOURCE = 11; - PAM_AUSER = 12; + PAM_REPOSITORY = 10, + PAM_RESOURCE, + PAM_AUSER, ); /// A flag for `pam_chauthtok`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpam-sys/src/functions.rs Sun Jun 29 20:13:03 2025 -0400 @@ -0,0 +1,1 @@ +
--- a/libpam-sys/src/helpers.rs Sun Jun 29 18:48:14 2025 -0400 +++ b/libpam-sys/src/helpers.rs Sun Jun 29 20:13:03 2025 -0400 @@ -1,10 +1,11 @@ //! This module contains a few non-required helpers to deal with some of the //! more annoying memory management in the PAM API. -use crate::structs::BinaryPayload; + use std::error::Error; -use std::fmt; +use std::marker::{PhantomData, PhantomPinned}; use std::mem::ManuallyDrop; use std::ptr::NonNull; +use std::{fmt, slice}; /// Error returned when attempting to allocate a buffer that is too big. /// @@ -99,6 +100,77 @@ } } +/// The structure of the "binary message" payload for the `PAM_BINARY_PROMPT` +/// extension from Linux-PAM. +pub struct BinaryPayload { + /// The total byte size of the message, including this header, + /// as a u32 in network byte order (big endian). + pub total_bytes_u32be: [u8; 4], + /// A tag used to provide some kind of hint as to what the data is. + /// Its meaning is undefined. + pub data_type: u8, + /// Where the data itself would start, used as a marker to make this + /// not [`Unpin`] (since it is effectively an intrusive data structure + /// pointing to immediately after itself). + pub _marker: PhantomData<PhantomPinned>, +} + +impl BinaryPayload { + /// The most data it's possible to put into a [`BinaryPayload`]. + pub const MAX_SIZE: usize = (u32::MAX - 5) as usize; + + /// Fills in the provided buffer with the given data. + /// + /// This uses [`copy_from_slice`](slice::copy_from_slice) internally, + /// so `buf` must be exactly 5 bytes longer than `data`, or this function + /// will panic. + pub fn fill(buf: &mut [u8], data_type: u8, data: &[u8]) { + let ptr: *mut Self = buf.as_mut_ptr().cast(); + // SAFETY: We're given a slice, which always has a nonzero pointer. + let me = unsafe { ptr.as_mut().unwrap_unchecked() }; + me.total_bytes_u32be = u32::to_be_bytes(buf.len() as u32); + me.data_type = data_type; + buf[5..].copy_from_slice(data) + } + + /// The total storage needed for the message, including header. + pub fn total_bytes(&self) -> usize { + u32::from_be_bytes(self.total_bytes_u32be) as usize + } + + /// Gets the total byte buffer of the BinaryMessage stored at the pointer. + /// + /// The returned data slice is borrowed from where the pointer points to. + /// + /// # Safety + /// + /// - The pointer must point to a valid `BinaryPayload`. + /// - The borrowed data must not outlive the pointer's validity. + pub unsafe fn buffer_of<'a>(ptr: *const Self) -> &'a [u8] { + let header: &Self = ptr.as_ref().unwrap_unchecked(); + slice::from_raw_parts(ptr.cast(), header.total_bytes().max(5)) + } + + /// Gets the contents of the BinaryMessage stored at the given pointer. + /// + /// The returned data slice is borrowed from where the pointer points to. + /// This is a cheap operation and doesn't do *any* copying. + /// + /// We don't take a `&self` reference here because accessing beyond + /// the range of the `Self` data (i.e., beyond the 5 bytes of `self`) + /// is undefined behavior. Instead, you have to pass a raw pointer + /// directly to the data. + /// + /// # Safety + /// + /// - The pointer must point to a valid `BinaryPayload`. + /// - The borrowed data must not outlive the pointer's validity. + pub unsafe fn contents<'a>(ptr: *const Self) -> (u8, &'a [u8]) { + let header: &Self = ptr.as_ref().unwrap_unchecked(); + (header.data_type, &Self::buffer_of(ptr)[5..]) + } +} + /// A binary message owned by some storage. /// /// This is an owned, memory-managed version of [`BinaryPayload`]. @@ -132,9 +204,7 @@ /// The total bytes needed to store this, including the header. pub fn total_bytes(&self) -> usize { - unsafe { - BinaryPayload::buffer_of(self.0.as_ptr().cast()).len() - } + unsafe { BinaryPayload::buffer_of(self.0.as_ptr().cast()).len() } } /// Unwraps this into the raw storage backing it.
--- a/libpam-sys/src/lib.rs Sun Jun 29 18:48:14 2025 -0400 +++ b/libpam-sys/src/lib.rs Sun Jun 29 20:13:03 2025 -0400 @@ -7,6 +7,7 @@ use libpam_sys_impls::{__pam_impl_enum__, __pam_impl_name__}; mod constants; +mod functions; pub mod helpers; mod structs; @@ -30,8 +31,8 @@ /// #[cfg_pam_impl(not("Sun"))] /// fn do_something() { /* non-Illumos code */ } /// -/// #[cfg_pam_impl(any("LinuxPam", "MinimalOpenPam"))] -/// fn do_something_else() { /* Linux-PAM or minimal OpenPAM */ } +/// #[cfg_pam_impl(any("LinuxPam", "XSso"))] +/// fn do_something_else() { /* Linux-PAM or X/SSO-spec PAM */ } /// /// #[cfg_pam_impl(not(any("Sun", "OpenPam")))] /// fn do_a_third_thing() { /* Neither Sun nor OpenPAM */ }
--- a/libpam-sys/src/structs.rs Sun Jun 29 18:48:14 2025 -0400 +++ b/libpam-sys/src/structs.rs Sun Jun 29 20:13:03 2025 -0400 @@ -1,76 +1,71 @@ -use core::marker::{PhantomData, PhantomPinned}; -use core::slice; +use std::ffi::{c_int, c_void}; +use std::fmt; +use std::marker::{PhantomData, PhantomPinned}; + +/// A marker struct to make whatever it's in `!Sync`, `!Send`, and `!Unpin`. +#[derive(Default, PartialOrd, PartialEq, Ord, Eq)] +#[repr(transparent)] +struct ExtremelyUnsafe(PhantomData<(PhantomPinned, *mut c_void)>); + +impl fmt::Debug for ExtremelyUnsafe { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ExtremelyUnsafe") + } +} -/// The structure of the "binary message" payload for the `PAM_BINARY_PROMPT` -/// extension from Linux-PAM. -pub struct BinaryPayload { - /// The total byte size of the message, including this header, - /// as a u32 in network byte order (big endian). - pub total_bytes_u32be: [u8; 4], - /// A tag used to provide some kind of hint as to what the data is. - /// Its meaning is undefined. - pub data_type: u8, - /// Where the data itself would start, used as a marker to make this - /// not [`Unpin`] (since it is effectively an intrusive data structure - /// pointing to immediately after itself). - pub _marker: PhantomData<PhantomPinned>, +/// An opaque structure that PAM uses to communicate. +/// +/// This is only ever returned in pointer form and cannot be constructed. +#[repr(C)] +pub struct PamHandle { + _marker: ExtremelyUnsafe, +} + +impl fmt::Debug for PamHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PamHandle({self:p}") + } +} + +/// An opaque structure that is passed through PAM in a conversation. +pub struct AppData { + _marker: ExtremelyUnsafe, } -impl BinaryPayload { - /// The most data it's possible to put into a [`BinaryPayload`]. - pub const MAX_SIZE: usize = (u32::MAX - 5) as usize; - - /// Fills in the provided buffer with the given data. - /// - /// This uses [`copy_from_slice`](slice::copy_from_slice) internally, - /// so `buf` must be exactly 5 bytes longer than `data`, or this function - /// will panic. - pub fn fill(buf: &mut [u8], data_type: u8, data: &[u8]) { - let ptr: *mut Self = buf.as_mut_ptr().cast(); - // SAFETY: We're given a slice, which always has a nonzero pointer. - let me = unsafe { ptr.as_mut().unwrap_unchecked() }; - me.total_bytes_u32be = u32::to_be_bytes(buf.len() as u32); - me.data_type = data_type; - buf[5..].copy_from_slice(data) - } - - /// The total storage needed for the message, including header. - pub fn total_bytes(&self) -> usize { - u32::from_be_bytes(self.total_bytes_u32be) as usize - } - - /// Gets the total byte buffer of the BinaryMessage stored at the pointer. - /// - /// The returned data slice is borrowed from where the pointer points to. - /// - /// # Safety - /// - /// - The pointer must point to a valid `BinaryPayload`. - /// - The borrowed data must not outlive the pointer's validity. - pub unsafe fn buffer_of<'a>(ptr: *const Self) -> &'a [u8] { - let header: &Self = ptr.as_ref().unwrap_unchecked(); - slice::from_raw_parts(ptr.cast(), header.total_bytes().max(5)) - } - - /// Gets the contents of the BinaryMessage stored at the given pointer. - /// - /// The returned data slice is borrowed from where the pointer points to. - /// This is a cheap operation and doesn't do *any* copying. - /// - /// We don't take a `&self` reference here because accessing beyond - /// the range of the `Self` data (i.e., beyond the 5 bytes of `self`) - /// is undefined behavior. Instead, you have to pass a raw pointer - /// directly to the data. - /// - /// # Safety - /// - /// - The pointer must point to a valid `BinaryPayload`. - /// - The borrowed data must not outlive the pointer's validity. - pub unsafe fn contents<'a>(ptr: *const Self) -> (u8, &'a [u8]) { - let header: &Self = ptr.as_ref().unwrap_unchecked(); - ( - header.data_type, - &Self::buffer_of(ptr)[5..] - ) +impl fmt::Debug for AppData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "AppData({self:p}") } } + +/// The callback that PAM uses to get information in a conversation. +/// +/// For important details about the format of `messages`, +/// see the [`helpers`](crate::helpers) module. +pub type ConversationCallback = unsafe extern "C" fn( + num_msg: c_int, + messages: *const *const Message, + responses: &mut *mut Response, + appdata: *const AppData, +) -> c_int; + +/// Used by PAM to communicate between the module and the application. +#[repr(C)] +pub struct Conversation { + pub callback: ConversationCallback, + pub appdata: *const AppData, +} + +/// A message sent into a PAM conversation. +#[repr(C)] +pub struct Message { + pub style: c_int, + pub data: *const c_void, +} + +/// A response returned from a PAM conversation. +#[repr(C)] +pub struct Response { + pub data: *mut c_void, + pub _unused: c_int, +}
--- a/src/_doc.rs Sun Jun 29 18:48:14 2025 -0400 +++ b/src/_doc.rs Sun Jun 29 20:13:03 2025 -0400 @@ -176,4 +176,4 @@ }; } -pub(crate) use {linklist, guide, man7, manbsd, xsso, stdlinks}; \ No newline at end of file +pub(crate) use {guide, linklist, man7, manbsd, stdlinks, xsso};
--- a/src/libpam/handle.rs Sun Jun 29 18:48:14 2025 -0400 +++ b/src/libpam/handle.rs Sun Jun 29 20:13:03 2025 -0400 @@ -8,8 +8,8 @@ use crate::libpam::{memory, pam_ffi}; use crate::logging::{Level, Location}; use crate::{ - Conversation, EnvironMap, Flags, PamHandleApplication, PamHandleModule, guide, linklist, - stdlinks, + guide, linklist, stdlinks, Conversation, EnvironMap, Flags, PamHandleApplication, + PamHandleModule, }; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::cell::Cell; @@ -208,7 +208,12 @@ let func = CString::new(loc.function).unwrap_or(CString::default()); // SAFETY: We're calling this function with a known value. unsafe { - pam_ffi::_openpam_log(level as c_int, func.as_ptr(), "%s\0".as_ptr().cast(), entry.as_ptr()) + pam_ffi::_openpam_log( + level as c_int, + func.as_ptr(), + "%s\0".as_ptr().cast(), + entry.as_ptr(), + ) } } }
--- a/src/logging.rs Sun Jun 29 18:48:14 2025 -0400 +++ b/src/logging.rs Sun Jun 29 20:13:03 2025 -0400 @@ -47,18 +47,23 @@ Debug = levels::DEBUG, } -/// The location of a log entry. +/// The location of a log entry. Use [`location!`](crate::location!) to create this. #[derive(Clone, Copy, Debug, Default)] +#[non_exhaustive] pub struct Location<'a> { pub file: &'a str, pub line: u32, pub function: &'a str, - _more: (), } impl<'a> Location<'a> { + /// Creates a new location. Just use [`location!`](crate::location!) instead. pub fn new(file: &'a str, line: u32, function: &'a str) -> Self { - Self {file, line, function, _more: ()} + Self { + file, + line, + function, + } } } @@ -66,7 +71,9 @@ #[doc(hidden)] #[macro_export] macro_rules! location { - () => { $crate::logging::Location::new(file!(), line!(), $crate::__function!()) } + () => { + $crate::logging::Location::new(file!(), line!(), $crate::__function!()) + }; } /// Here's the guts of the logger thingy. You shouldn't be using this! @@ -90,7 +97,7 @@ ::std::any::type_name::<T>() } f(p).trim_end_matches("::p") - }} + }}; } /// Logs a message at error level via the given PAM handle.