# HG changeset patch # User Paul Fisher # Date 1747868451 14400 # Node ID 05cc2c27334f7abe5fa8efd4c41e33c7caeb3875 # Parent 3f4a77aa88be6b20ddb858cfed2d47d6a8e207e8 The Big Refactor: clean up docs and exports. - Brings the most important symbols in the library to the root with `pub use` statements. - Expands and updates documentation. - Rearranges things extensively to make the external interface nicer and make the structure easier to understand. - Renames a few things (e.g. `Result`). diff -r 3f4a77aa88be -r 05cc2c27334f src/constants.rs --- a/src/constants.rs Wed May 21 00:27:18 2025 -0400 +++ b/src/constants.rs Wed May 21 19:00:51 2025 -0400 @@ -1,24 +1,37 @@ +//! Constants and enum values from the PAM library. + use bitflags::bitflags; use libc::{c_int, c_uint}; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use std::any; use std::marker::PhantomData; -// TODO: Import constants from C header file at compile time. -// The Linux-PAM flags -// see /usr/include/security/_pam_types.h bitflags! { + /// The available PAM flags. + /// + /// See `/usr/include/security/_pam_types.h` for more details. #[derive(Debug, PartialEq)] #[repr(transparent)] pub struct Flags: c_uint { + /// Authentication service should not generate any messages. const SILENT = 0x8000; + /// The service should return [ErrorCode::AuthError] if the user + /// has a null authentication token. const DISALLOW_NULL_AUTHTOK = 0x0001; + /// Set user credentials for an authentication service. const ESTABLISH_CRED = 0x0002; + /// Delete user credentials associated with + /// an authentication service. const DELETE_CRED = 0x0004; + /// Reinitialize user credentials. const REINITIALIZE_CRED = 0x0008; + /// Extend the lifetime of user credentials. const REFRESH_CRED = 0x0010; - const CHANGE_EXPIRED_AUTHTOK= 0x0020; + /// The password service should only update those passwords + /// that have aged. If this flag is _not_ passed, + /// the password service should update all passwords. + const CHANGE_EXPIRED_AUTHTOK = 0x0020; } } @@ -43,7 +56,7 @@ impl TryFrom for MessageStyle { type Error = InvalidEnum; - fn try_from(value: c_int) -> Result { + fn try_from(value: c_int) -> std::result::Result { Self::from_i32(value).ok_or(value.into()) } } @@ -54,8 +67,8 @@ } } -/// The Linux-PAM error return values. -/// Success is instead represented by the `Ok` entry of a `Result`. +/// The Linux-PAM error return values. Success is an Ok [Result]. +/// /// Most abbreviations (except `AuthTok` and `Max`) are now full words. /// For more detailed information, see /// `/usr/include/security/_pam_types.h`. @@ -127,19 +140,21 @@ Incomplete = 31, } -pub type PamResult = Result; +/// A PAM-specific Result type with an [ErrorCode] error. +pub type Result = std::result::Result; + impl ErrorCode { - /// Converts a PamResult into the result code that C wants. - pub fn result_to_c(value: PamResult<()>) -> c_int { + /// Converts this [Result] into a C-compatible result code. + pub fn result_to_c(value: Result) -> c_int { match value { Ok(_) => 0, // PAM_SUCCESS Err(otherwise) => otherwise.into(), } } - /// Converts a C result code into a PamResult, with success as Ok. + /// Converts a C result code into a [Result], with success as Ok. /// Invalid values are returned as a [Self::SystemError]. - pub fn result_from(value: c_int) -> PamResult<()> { + pub fn result_from(value: c_int) -> Result<()> { match value { 0 => Ok(()), value => Err(value.try_into().unwrap_or(Self::SystemError)), @@ -150,7 +165,7 @@ impl TryFrom for ErrorCode { type Error = InvalidEnum; - fn try_from(value: c_int) -> Result { + fn try_from(value: c_int) -> std::result::Result { Self::from_i32(value).ok_or(value.into()) } } @@ -163,12 +178,18 @@ /// Error returned when attempting to coerce an invalid C integer into an enum. #[derive(thiserror::Error)] -#[error("{} is not a valid {}", .0, any::type_name::())] +#[error("{0} is not a valid {type}", type = any::type_name::())] #[derive(Debug, PartialEq)] -pub struct InvalidEnum(std::ffi::c_int, PhantomData); +pub struct InvalidEnum(c_int, PhantomData); -impl From for InvalidEnum { - fn from(value: std::ffi::c_int) -> Self { +impl From> for c_int { + fn from(value: InvalidEnum) -> Self { + value.0 + } +} + +impl From for InvalidEnum { + fn from(value: c_int) -> Self { Self(value, PhantomData) } } @@ -180,9 +201,12 @@ #[test] fn test_enums() { assert_eq!(Ok(MessageStyle::ErrorMsg), 3.try_into()); - assert_eq!(Err(999.into()), ErrorCode::try_from(999)); + assert_eq!(Err(InvalidEnum::from(999)), ErrorCode::try_from(999)); assert_eq!(Ok(()), ErrorCode::result_from(0)); assert_eq!(Err(ErrorCode::Abort), ErrorCode::result_from(26)); assert_eq!(Err(ErrorCode::SystemError), ErrorCode::result_from(423)); + assert!(InvalidEnum::(33, PhantomData) + .to_string() + .starts_with("33 is not a valid ")); } } diff -r 3f4a77aa88be -r 05cc2c27334f src/conv.rs --- a/src/conv.rs Wed May 21 00:27:18 2025 -0400 +++ b/src/conv.rs Wed May 21 19:00:51 2025 -0400 @@ -1,10 +1,12 @@ +//! The [Conversation] struct, for interacting with the user. + use libc::{c_char, c_int}; use std::ffi::{CStr, CString}; use std::ptr; use crate::constants::ErrorCode; use crate::constants::MessageStyle; -use crate::constants::PamResult; +use crate::constants::Result; use crate::items::Item; #[repr(C)] @@ -19,6 +21,7 @@ resp_retcode: libc::c_int, // Unused - always zero } +#[doc(hidden)] #[repr(C)] pub struct Inner { conv: extern "C" fn( @@ -30,30 +33,24 @@ appdata_ptr: *const libc::c_void, } -/// A `Conv`ersation channel with the user. +/// A communication channel with the user. /// -/// Communication is mediated by the PAM client (the application that invoked -/// pam). Messages sent will be relayed to the user by the client, and response -/// will be relayed back. -pub struct Conv<'a>(&'a Inner); +/// Use this to communicate with the user, if needed, beyond the standard +/// things you can get/set with `get_user`/`get_authtok` and friends. +/// The PAM client (i.e., the application that is logging in) will present +/// the messages you send to the user and ask for responses. +pub struct Conversation<'a>(&'a Inner); -impl Conv<'_> { - /// Sends a message to the pam client. +impl Conversation<'_> { + /// Sends a message to the PAM client. /// /// This will typically result in the user seeing a message or a prompt. - /// There are several message styles available: + /// For details, see what [MessageStyle]s are available. /// - /// - PAM_PROMPT_ECHO_OFF - /// - PAM_PROMPT_ECHO_ON - /// - PAM_ERROR_MSG - /// - PAM_TEXT_INFO - /// - PAM_RADIO_TYPE - /// - PAM_BINARY_PROMPT - /// - /// Note that the user experience will depend on how the client implements - /// these message styles - and not all applications implement all message - /// styles. - pub fn send(&self, style: MessageStyle, msg: &str) -> PamResult> { + /// Note that the user experience will depend on how each style + /// is implemented by the client, and that not all clients + /// will implement all message styles. + pub fn send(&self, style: MessageStyle, msg: &str) -> Result> { let mut resp_ptr: *const Response = ptr::null(); let msg_cstr = CString::new(msg).unwrap(); let msg = Message { @@ -74,7 +71,7 @@ } } -impl Item for Conv<'_> { +impl Item for Conversation<'_> { type Raw = Inner; fn type_id() -> crate::items::ItemType { diff -r 3f4a77aa88be -r 05cc2c27334f src/items.rs --- a/src/items.rs Wed May 21 00:27:18 2025 -0400 +++ b/src/items.rs Wed May 21 19:00:51 2025 -0400 @@ -1,37 +1,43 @@ +//! Things that can be gotten with the `pam_get_item` function. + use crate::constants::InvalidEnum; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use std::ffi::{c_int, CStr}; +/// Enum identifying what a `pam_get_item` return is. +/// +/// Generally, you shouldn’t have to worry about this, and instead +/// just use the various [Item] implementations. #[derive(FromPrimitive)] #[repr(i32)] #[non_exhaustive] // because C could give us anything! pub enum ItemType { - /// The service name + /// The PAM service name. Service = 1, - /// The user name + /// The user's login name. User = 2, - /// The tty name + /// The TTY name. Tty = 3, - /// The remote host name + /// The remote host (if applicable). RemoteHost = 4, - /// The pam_conv structure + /// The [crate::Conversation] structure. Conversation = 5, - /// The authentication token (password) + /// The authentication token (password). AuthTok = 6, - /// The old authentication token + /// The old authentication token (when changing passwords). OldAuthTok = 7, - /// The remote user name + /// The remote user's name. RemoteUser = 8, - /// the prompt for getting a username + /// The prompt shown when requesting a username. UserPrompt = 9, - /// app supplied function to override failure delays + /// App-supplied function to override failure delays. FailDelay = 10, - /// X :display name + /// X display name. XDisplay = 11, - /// X :server authentication data + /// X server authentication data. XAuthData = 12, - /// The type for pam_get_authtok + /// The type of `pam_get_authtok`. AuthTokType = 13, } @@ -48,7 +54,7 @@ } } -/// A type that can be requested by [crate::Handle::get_item]. +/// A type that can be requested by [crate::PamHandle::get_item]. pub trait Item { /// The `repr(C)` type that is returned (by pointer) by the underlying `pam_get_item` function. type Raw; @@ -69,7 +75,8 @@ macro_rules! cstr_item { ($name:ident) => { - ///A `CStr`-based item from a PAM conversation. + #[doc = concat!("The [ItemType::", stringify!($name), "]")] + #[doc = " item, represented as a [CStr]."] #[derive(Debug)] pub struct $name<'s>(pub &'s CStr); @@ -98,11 +105,12 @@ }; } +// Conversation is not included here since it's special. + cstr_item!(Service); cstr_item!(User); cstr_item!(Tty); cstr_item!(RemoteHost); -// Conversation is not included here since it's special. cstr_item!(AuthTok); cstr_item!(OldAuthTok); cstr_item!(RemoteUser); diff -r 3f4a77aa88be -r 05cc2c27334f src/lib.rs --- a/src/lib.rs Wed May 21 00:27:18 2025 -0400 +++ b/src/lib.rs Wed May 21 19:00:51 2025 -0400 @@ -1,31 +1,38 @@ -//! Interface to the pluggable authentication module framework (PAM). +//! A safe, nonstick interface to PAM. +//! +//! This implements a type-safe library to interact with PAM. +//! Currently, it implements the subset of PAM useful for implementing a module. +//! +//! To write a new PAM module using this crate: //! -//! The goal of this library is to provide a type-safe API that can be used to -//! interact with PAM. The library is incomplete - currently it supports -//! a subset of functions for use in a pam authentication module. A pam module -//! is a shared library that is invoked to authenticate a user, or to perform -//! other functions. +//! 1. Create a `dylib` crate. +//! 2. Implement a subset of the functions in the [PamModule] trait +//! corresponding to what you want your module to do. +//! In the simplest case (for a password-authentication system), +//! this will be the [PamModule::sm_authenticate] function. +//! 3. Export your PAM module using the [pam_hooks!] macro. +//! 4. Build and install the dynamic library. +//! This usually entails placing it at +//! `/usr/lib/security/pam_your_module.so`, +//! or maybe +//! /usr/lib/your-architecture/security/pam_your_module.so. //! -//! For general information on writing pam modules, see +//! For general information on writing PAM modules, see //! [The Linux-PAM Module Writers' Guide][module-guide] //! //! [module-guide]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html -//! -//! A typical authentication module will define an external function called -//! `pam_sm_authenticate()`, which will use functions in this library to -//! interrogate the program that requested authentication for more information, -//! and to render a result. -//! -//! Note that constants that are normally read from pam header files are -//! hard-coded in the `constants` module. The values there are taken from -//! a Linux system. That means that it might take some work to get this library -//! to work on other platforms. - -extern crate libc; pub mod constants; pub mod conv; pub mod items; -#[doc(hidden)] -pub mod macros; pub mod module; + +mod memory; +mod pam_ffi; + +#[doc(inline)] +pub use crate::{ + constants::{ErrorCode, Flags, MessageStyle, Result}, + conv::Conversation, + module::{PamHandle, PamModule}, +}; diff -r 3f4a77aa88be -r 05cc2c27334f src/macros.rs --- a/src/macros.rs Wed May 21 00:27:18 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,125 +0,0 @@ -/// Macro to generate the `extern "C"` entrypoint bindings needed by PAM -/// -/// You can call `pam_hooks!(SomeType);` for any type that implements `PamHooks` -/// -/// ## Examples: -/// -/// Here is full example of a PAM module that would authenticate and authorize everybody: -/// -/// ``` -/// #[macro_use] extern crate nonstick; -/// -/// use nonstick::module::{PamHooks, PamHandle}; -/// use nonstick::constants::{PamResult, Flags}; -/// use std::ffi::CStr; -/// -/// # fn main() {} -/// struct MyPamModule; -/// pam_hooks!(MyPamModule); -/// -/// impl PamHooks for MyPamModule { -/// fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { -/// println!("Everybody is authorized!"); -/// Ok(()) -/// } -/// -/// fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { -/// println!("Everybody is authenticated!"); -/// Ok(()) -/// } -/// } -/// ``` -#[macro_export] -macro_rules! pam_hooks { - ($ident:ident) => { - pub use self::pam_hooks_scope::*; - mod pam_hooks_scope { - use std::ffi::CStr; - use std::os::raw::{c_char, c_int}; - use $crate::constants::{ErrorCode, Flags}; - use $crate::module::{PamHandle, PamHooks}; - - 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() - } - - #[no_mangle] - pub extern "C" fn pam_sm_acct_mgmt( - pamh: &mut PamHandle, - flags: Flags, - argc: c_int, - argv: *const *const c_char, - ) -> c_int { - let args = extract_argv(argc, argv); - ErrorCode::result_to_c(super::$ident::acct_mgmt(pamh, args, flags)) - } - - #[no_mangle] - pub extern "C" fn pam_sm_authenticate( - pamh: &mut PamHandle, - flags: Flags, - argc: c_int, - argv: *const *const c_char, - ) -> c_int { - let args = extract_argv(argc, argv); - ErrorCode::result_to_c(super::$ident::sm_authenticate(pamh, args, flags)) - } - - #[no_mangle] - pub extern "C" fn pam_sm_chauthtok( - pamh: &mut PamHandle, - flags: Flags, - argc: c_int, - argv: *const *const c_char, - ) -> c_int { - let args = extract_argv(argc, argv); - ErrorCode::result_to_c(super::$ident::sm_chauthtok(pamh, args, flags)) - } - - #[no_mangle] - pub extern "C" fn pam_sm_close_session( - pamh: &mut PamHandle, - flags: Flags, - argc: c_int, - argv: *const *const c_char, - ) -> c_int { - let args = extract_argv(argc, argv); - ErrorCode::result_to_c(super::$ident::sm_close_session(pamh, args, flags)) - } - - #[no_mangle] - pub extern "C" fn pam_sm_open_session( - pamh: &mut PamHandle, - flags: Flags, - argc: c_int, - argv: *const *const c_char, - ) -> c_int { - let args = extract_argv(argc, argv); - ErrorCode::result_to_c(super::$ident::sm_open_session(pamh, args, flags)) - } - - #[no_mangle] - pub extern "C" fn pam_sm_setcred( - pamh: &mut PamHandle, - flags: Flags, - argc: c_int, - argv: *const *const c_char, - ) -> c_int { - let args = extract_argv(argc, argv); - ErrorCode::result_to_c(super::$ident::sm_setcred(pamh, args, flags)) - } - } - }; -} - -#[cfg(test)] -pub mod test { - use crate::module::PamHooks; - - struct Foo; - impl PamHooks for Foo {} - - pam_hooks!(Foo); -} diff -r 3f4a77aa88be -r 05cc2c27334f src/memory.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/memory.rs Wed May 21 19:00:51 2025 -0400 @@ -0,0 +1,35 @@ +//! Utility functions for dealing with memory copying and stuff. + +use crate::ErrorCode; +use libc::c_char; +use std::ffi::{CStr, CString}; + +/// Safely converts a `&str` option to a `CString` option. +pub fn option_cstr(prompt: Option<&str>) -> crate::Result> { + prompt + .map(CString::new) + .transpose() + .map_err(|_| ErrorCode::ConversationError) +} + +/// Gets the pointer to the given CString, or a null pointer if absent. +pub fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { + match prompt { + Some(c_str) => c_str.as_ptr(), + None => std::ptr::null(), + } +} + +/// Creates an owned copy of a string that is returned from a +/// pam_get_whatever function. +pub fn copy_pam_string(result_ptr: *const c_char) -> crate::Result { + // We really shouldn't get a null pointer back here, but if we do, return nothing. + if result_ptr.is_null() { + return Ok(String::new()); + } + let bytes = unsafe { CStr::from_ptr(result_ptr) }; + bytes + .to_str() + .map(String::from) + .map_err(|_| ErrorCode::ConversationError) +} diff -r 3f4a77aa88be -r 05cc2c27334f src/module.rs --- a/src/module.rs Wed May 21 00:27:18 2025 -0400 +++ b/src/module.rs Wed May 21 19:00:51 2025 -0400 @@ -1,73 +1,28 @@ -//! Functions for use in pam modules. +//! Functions and types useful for implementing a PAM module. -use crate::constants::{ErrorCode, Flags, PamResult}; +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}; -/// Opaque type, used as a pointer when making pam API calls. -/// -/// A module is invoked via an external function such as `pam_sm_authenticate`. -/// Such a call provides a pam handle pointer. The same pointer should be given -/// as an argument when making API calls. -#[repr(C)] -pub struct PamHandle { - _data: [u8; 0], -} - -#[link(name = "pam")] -extern "C" { - fn pam_get_data( - pamh: *const PamHandle, - module_data_name: *const c_char, - data: &mut *const libc::c_void, - ) -> c_int; - - fn pam_set_data( - pamh: *const PamHandle, - module_data_name: *const c_char, - data: *const libc::c_void, - cleanup: extern "C" fn( - pamh: *const PamHandle, - data: *mut libc::c_void, - error_status: c_int, - ), - ) -> c_int; - - fn pam_get_item( - pamh: *const PamHandle, - item_type: c_int, - item: &mut *const libc::c_void, - ) -> c_int; - - fn pam_set_item(pamh: *mut PamHandle, item_type: c_int, item: *const libc::c_void) -> c_int; - - fn pam_get_user( - pamh: *const PamHandle, - user: &mut *const c_char, - prompt: *const c_char, - ) -> c_int; - - fn pam_get_authtok( - pamh: *const PamHandle, - item_type: c_int, - data: &mut *const c_char, - prompt: *const c_char, - ) -> c_int; - -} +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. /// /// You should never call this yourself. -extern "C" fn cleanup(_: *const PamHandle, c_data: *mut libc::c_void, _: c_int) { +extern "C" fn cleanup(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) { unsafe { let _data: Box = Box::from_raw(c_data.cast()); } } +/// 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. @@ -75,20 +30,16 @@ /// See the [`pam_get_data` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html). /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - /// /// # Safety /// /// The data stored under the provided key must be of type `T` otherwise the /// behaviour of this function is undefined. /// /// The data, if present, is owned by the current PAM conversation. - pub unsafe fn get_data(&self, key: &str) -> PamResult> { + pub unsafe fn get_data(&self, key: &str) -> Result> { let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; let mut ptr: *const libc::c_void = std::ptr::null(); - ErrorCode::result_from(pam_get_data(self, c_key.as_ptr(), &mut ptr))?; + ErrorCode::result_from(pam_ffi::pam_get_data(self.0, c_key.as_ptr(), &mut ptr))?; match ptr.is_null() { true => Ok(None), false => { @@ -103,15 +54,11 @@ /// /// See the [`pam_set_data` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn set_data(&mut self, key: &str, data: Box) -> PamResult<()> { + pub fn set_data(&mut self, key: &str, data: Box) -> Result<()> { let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; let ret = unsafe { - pam_set_data( - self, + pam_ffi::pam_set_data( + self.0, c_key.as_ptr(), Box::into_raw(data).cast(), cleanup::, @@ -128,14 +75,10 @@ /// /// See the [`pam_get_item` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn get_item(&self) -> PamResult> { + pub fn get_item(&self) -> Result> { let mut ptr: *const libc::c_void = std::ptr::null(); let out = unsafe { - let ret = pam_get_item(self, T::type_id().into(), &mut ptr); + 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() { @@ -150,12 +93,9 @@ /// /// See the [`pam_set_item` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn set_item(&mut self, item: T) -> PamResult<()> { - let ret = unsafe { pam_set_item(self, T::type_id().into(), item.into_raw().cast()) }; + pub fn set_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) } @@ -165,16 +105,14 @@ /// /// See the [`pam_get_user` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn get_user(&self, prompt: Option<&str>) -> PamResult { - let prompt = option_cstr(prompt)?; + pub fn get_user(&self, prompt: Option<&str>) -> Result { + let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = std::ptr::null_mut(); - let ret = unsafe { pam_get_user(self, &mut output, prompt_ptr(prompt.as_ref())) }; + let ret = unsafe { + pam_ffi::pam_get_user(self.0, &mut output, memory::prompt_ptr(prompt.as_ref())) + }; ErrorCode::result_from(ret)?; - copy_pam_string(output) + memory::copy_pam_string(output) } /// Retrieves the authentication token from the user. @@ -183,75 +121,54 @@ /// /// See the [`pam_get_authtok` manual page]( /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html). - /// - /// # Errors - /// - /// Returns an error if the underlying PAM function call fails. - pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult { - let prompt = option_cstr(prompt)?; + pub fn get_authtok(&self, prompt: Option<&str>) -> Result { + let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = std::ptr::null_mut(); let res = unsafe { - pam_get_authtok( - self, + pam_ffi::pam_get_authtok( + self.0, ItemType::AuthTok.into(), &mut output, - prompt_ptr(prompt.as_ref()), + memory::prompt_ptr(prompt.as_ref()), ) }; ErrorCode::result_from(res)?; - copy_pam_string(output).map(SecureString::from) + memory::copy_pam_string(output).map(SecureString::from) } } -/// Safely converts a `&str` option to a `CString` option. -fn option_cstr(prompt: Option<&str>) -> PamResult> { - prompt - .map(CString::new) - .transpose() - .map_err(|_| ErrorCode::ConversationError) -} - -/// The pointer to the prompt CString, or null if absent. -pub(crate) fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { - match prompt { - Some(c_str) => c_str.as_ptr(), - None => std::ptr::null(), +impl From<*mut libc::c_void> for PamHandle { + /// Wraps an internal Handle pointer. + fn from(value: *mut libc::c_void) -> Self { + Self(value) } } -/// Creates an owned copy of a string that is returned from a -/// pam_get_whatever function. -pub(crate) fn copy_pam_string(result_ptr: *const c_char) -> PamResult { - // We really shouldn't get a null pointer back here, but if we do, return nothing. - if result_ptr.is_null() { - return Ok(String::new()); - } - let bytes = unsafe { CStr::from_ptr(result_ptr) }; - bytes - .to_str() - .map(String::from) - .map_err(|_| ErrorCode::ConversationError) -} - -/// Provides functions that are invoked by the entrypoints generated by the -/// [`pam_hooks!` macro](../macro.pam_hooks.html). +/// 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. /// -/// All hooks are ignored by PAM dispatch by default given the default return value of `PAM_IGNORE`. -/// Override any functions that you want to handle with your module. See [PAM’s root manual page]( -/// https://www.man7.org/linux/man-pages/man3/pam.3.html). +/// 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 PamHooks { +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) -> PamResult<()> { + fn acct_mgmt(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) -> PamResult<()> { + fn sm_authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } @@ -261,17 +178,17 @@ /// `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) -> PamResult<()> { + fn sm_chauthtok(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) -> PamResult<()> { + fn sm_close_session(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) -> PamResult<()> { + fn sm_open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> { Err(ErrorCode::Ignore) } @@ -280,7 +197,148 @@ /// 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) -> PamResult<()> { + fn sm_setcred(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: +/// +/// ``` +/// 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 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!"); +/// Ok(()) +/// } +/// +/// fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { +/// eprintln!("Everybody is authenticated!"); +/// 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::acct_mgmt(&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::sm_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::sm_chauthtok(&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::sm_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::sm_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::sm_setcred(&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); +} diff -r 3f4a77aa88be -r 05cc2c27334f src/pam_ffi.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pam_ffi.rs Wed May 21 19:00:51 2025 -0400 @@ -0,0 +1,49 @@ +//! Functions exported by the PAM FFI. + +use libc::c_char; +use std::ffi::c_int; + +#[link(name = "pam")] +extern "C" { + pub fn pam_get_data( + pamh: *const libc::c_void, + module_data_name: *const c_char, + data: &mut *const libc::c_void, + ) -> c_int; + + pub fn pam_set_data( + pamh: *mut libc::c_void, + module_data_name: *const c_char, + data: *const libc::c_void, + cleanup: extern "C" fn( + pamh: *const libc::c_void, + data: *mut libc::c_void, + error_status: c_int, + ), + ) -> c_int; + + pub fn pam_get_item( + pamh: *const libc::c_void, + item_type: c_int, + item: &mut *const libc::c_void, + ) -> c_int; + + pub fn pam_set_item( + pamh: *mut libc::c_void, + item_type: c_int, + item: *const libc::c_void, + ) -> c_int; + + pub fn pam_get_user( + pamh: *const libc::c_void, + user: &mut *const c_char, + prompt: *const c_char, + ) -> c_int; + + pub fn pam_get_authtok( + pamh: *const libc::c_void, + item_type: c_int, + data: &mut *const c_char, + prompt: *const c_char, + ) -> c_int; +}