Mercurial > crates > nonstick
changeset 72:47eb242a4f88
Fill out the PamHandle trait.
This updates the PamHandle trait to have methods for each Item,
and implements them on the LibPamHandle.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Wed, 04 Jun 2025 03:53:36 -0400 |
parents | 58f9d2a4df38 |
children | ac6881304c78 |
files | src/conv.rs src/handle.rs src/items.rs src/lib.rs src/module.rs src/pam_ffi/memory.rs src/pam_ffi/message.rs src/pam_ffi/mod.rs src/pam_ffi/response.rs |
diffstat | 9 files changed, 440 insertions(+), 257 deletions(-) [+] |
line wrap: on
line diff
--- a/src/conv.rs Tue Jun 03 21:54:58 2025 -0400 +++ b/src/conv.rs Wed Jun 04 03:53:36 2025 -0400 @@ -4,6 +4,7 @@ #![allow(dead_code)] use crate::constants::Result; +use crate::pam_ffi::LibPamConversation; use crate::pam_ffi::Message; use secure_string::SecureString; // TODO: In most cases, we should be passing around references to strings @@ -80,6 +81,12 @@ fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData>; } +impl Conversation for LibPamConversation { + fn send(&mut self, _: &[Message]) -> Result<Vec<Response>> { + todo!() + } +} + impl<D: DemuxedConversation> Conversation for D { fn send(&mut self, messages: &[Message]) -> Result<Vec<Response>> { messages
--- a/src/handle.rs Tue Jun 03 21:54:58 2025 -0400 +++ b/src/handle.rs Wed Jun 04 03:53:36 2025 -0400 @@ -1,101 +1,223 @@ //! The wrapper types and traits for handles into the PAM library. use crate::constants::{ErrorCode, Result}; -use crate::items::{Item, ItemType}; +use crate::conv::Conversation; +use crate::items::ItemType; +use crate::module::ConversationMux; use crate::pam_ffi; -use crate::pam_ffi::memory; -use secure_string::SecureString; -use std::ffi::{c_char, c_int, c_void, CString}; +use crate::pam_ffi::{memory, LibPamConversation, LibPamHandle}; +use std::ffi::{c_char, c_int}; use std::{mem, ptr}; +macro_rules! trait_item { + (get = $getter:ident, item = $item:literal $(, see = $see:path)? $(, $($doc:literal)*)?) => { + $( + $(#[doc = $doc])* + #[doc = ""] + )? + #[doc = concat!("Gets the `", $item, "` of the PAM handle.")] + $( + #[doc = concat!("See [`", stringify!($see), "`].")] + )? + #[doc = ""] + #[doc = "Returns a reference to the item's value, owned by PAM."] + #[doc = "The item is assumed to be valid UTF-8 text."] + #[doc = "If it is not, `ConversationError` is returned."] + #[doc = ""] + #[doc = "See the [`pam_get_item`][man] manual page,"] + #[doc = "[`pam_get_item` in the Module Writers' Guide][mwg], or"] + #[doc = "[`pam_get_item` in the Application Developers' Guide][adg]."] + #[doc = ""] + #[doc = "[man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html"] + #[doc = "[adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_get_item"] + #[doc = "[mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item"] + fn $getter(&mut self) -> Result<Option<&str>>; + }; + (set = $setter:ident, item = $item:literal $(, see = $see:path)? $(, $($doc:literal)*)?) => { + $( + $(#[doc = $doc])* + #[doc = ""] + )? + #[doc = concat!("Sets the `", $item, "` from the PAM handle.")] + $( + #[doc = concat!("See [`", stringify!($see), "`].")] + )? + #[doc = ""] + #[doc = "Sets the item's value. PAM copies the string's contents."] + #[doc = "If the string contains a null byte, this will return "] + #[doc = "a `ConversationError`."] + #[doc = ""] + #[doc = "See the [`pam_set_item`][man] manual page,"] + #[doc = "[`pam_set_item` in the Module Writers' Guide][mwg], or"] + #[doc = "[`pam_set_item` in the Application Developers' Guide][adg]."] + #[doc = ""] + #[doc = "[man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html"] + #[doc = "[adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_set_item"] + #[doc = "[mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item"] + fn $setter(&mut self, value: Option<&str>) -> Result<()>; + }; +} + /// Features of a PAM handle that are available to applications and modules. /// /// You probably want [`LibPamHandle`]. This trait is intended to allow creating /// mock PAM handle types used for testing PAM modules and applications. pub trait PamHandle { + type Conv: Conversation; /// Retrieves the name of the user who is authenticating or logging in. /// - /// This is effectively like `handle.get_item::<Item::User>()`. + /// If the username has previously been obtained, this uses that username; + /// otherwise it prompts the user with the first of these that is present: + /// + /// 1. The prompt string passed to this function. + /// 2. The string returned by `get_user_prompt_item`. + /// 3. The default prompt, `login: ` + /// /// See the [`pam_get_user` manual page][man] /// or [`pam_get_user` in the Module Writer's Guide][mwg]. /// /// # Example /// /// ```no_run - /// # use nonstick::PamHandle; - /// # fn _doc(handle: &mut impl PamHandle) -> Result<(), Box<dyn std::error::Error>> { + /// # use nonstick::PamModuleHandle; + /// # fn _doc(handle: &mut impl PamModuleHandle) -> Result<(), Box<dyn std::error::Error>> { /// // Get the username using the default prompt. /// let user = handle.get_user(None)?; /// // Get the username using a custom prompt. - /// let user = handle.get_user(Some("who ARE you even???"))?; + /// // 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???"))?; /// # 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<String>; + fn get_user(&mut self, prompt: Option<&str>) -> Result<Option<&str>>; + + trait_item!( + get = user_item, + item = "PAM_USER", + "The identity of the user for whom service is being requested." + "" + "While PAM usually sets this automatically during the course of " + "a [`get_user`](Self::get_user) call, it may be changed by a module " + "over the course of the PAM transaction." + "Applications should check it after each step of the PAM process." + ); + trait_item!( + set = set_user_item, + item = "PAM_USER", + see = Self::user_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 " + "or change it during the PAM process." + ); - /// Retrieves the authentication token from the user. - /// - /// This is essentially like `handle.get_item::<Item::AuthTok>()`. - /// - /// See the [`pam_get_authtok` manual page][man] - /// or [`pam_get_item` in the Module Writer's Guide][mwg]. - /// - /// # Example - /// - /// ```no_run - /// # use nonstick::PamHandle; - /// # fn _doc(handle: &mut impl PamHandle) -> Result<(), Box<dyn std::error::Error>> { - /// // Get the user's password using the default prompt. - /// let pass = handle.get_authtok(None)?; - /// // Get the user's password using a custom prompt. - /// let pass = handle.get_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<SecureString>; + trait_item!( + get = service, + item = "PAM_SERVICE", + "The service name, which identifies the PAM stack which is used " + "to perform authentication." + ); + trait_item!( + set = set_service, + item = "PAM_SERVICE", + see = Self::service, + "The service name, which identifies the PAM stack which is used " + "to perform authentication. It's probably a bad idea to change this." + ); + + trait_item!( + get = user_prompt, + item = "PAM_USER_PROMPT", + "The string used to prompt for a user's name." + "By default, this is a localized version of `login: `." + ); + trait_item!( + set = set_user_prompt, + item = "PAM_USER_PROMPT", + see = Self::user_prompt, + "Sets the string used to prompt for a user's name." + ); - /// Retrieves an [Item] that has been set, possibly by the PAM client. - /// - /// These items are *references to PAM memory* - /// which are *owned by the PAM session* - /// and you should never modify them. - /// - /// See the [`pam_get_item` manual page][man] - /// or [`pam_get_item` in the Module Writer's Guide][mwg]. - /// - /// # Example - /// - /// ```no_run - /// # use nonstick::PamHandle; - /// use nonstick::items::Service; - /// - /// # fn _doc(pam_handle: &mut impl PamHandle) -> Result<(), Box<dyn std::error::Error>> { - /// let svc: Option<Service> = pam_handle.get_item()?; - /// match svc { - /// Some(name) => eprintln!("The calling service name is {:?}", name.to_string_lossy()), - /// None => eprintln!("Who knows what the calling service is?"), - /// } - /// # Ok(()) - /// # } - /// ``` - /// - /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.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_item<T: Item>(&mut self) -> Result<Option<T>>; + trait_item!( + get = tty_name, + item = "PAM_TTY", + "\"The terminal name prefixed by /dev/ for device files.\"" + "" + "This is the terminal the user is logging in on." + "Very old applications may use this instead of `PAM_XDISPLAY`." + ); + trait_item!( + set = set_tty_name, + item = "PAM_TTY", + see = Self::tty_name, + "Sets the terminal name." + "" + "(TODO: See if libpam sets this itself or if the application does.)" + ); + + trait_item!( + get = remote_user, + item = "PAM_RUSER", + "If set, the identity of the remote user logging in." + "" + "This is only as trustworthy as the application calling PAM." + "Also see [`remote_host`](Self::remote_host)." + ); + trait_item!( + set = set_remote_user, + item = "PAM_RUSER", + "Sets the identity of the remote user logging in." + "" + "This is usually set by the application before making calls " + "into a PAM session. (TODO: check this!)" + ); - /// Sets an item in the PAM context. It can be retrieved using [`get_item`](Self::get_item). - /// - /// See the [`pam_set_item` manual page][man] - /// or [`pam_set_item` in the Module Writer's Guide][mwg]. - /// - /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html - /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item - fn set_item<T: Item>(&mut self, item: T) -> Result<()>; + trait_item!( + get = remote_host, + item = "PAM_RHOST", + "If set, the remote location where the user is coming from." + "" + "This is only as trustworthy as the application calling PAM. " + "This can be combined with [`Self::remote_user`] to identify " + "the account the user is attempting to log in from, " + "with `remote_user@remote_host`." + "" + "If unset, \"it is unclear where the authentication request " + "is originating from.\"" + ); + trait_item!( + set = set_remote_host, + item = "PAM_RHOST", + see = Self::remote_host, + "Sets the location where the user is coming from." + "" + "This is usually set by the application before making calls " + "into a PAM session. (TODO: check this!)" + ); + + trait_item!( + set = set_authtok_item, + item = "PAM_AUTHTOK", + see = PamModuleHandle::authtok_item, + "Sets the user's authentication token (e.g., password)." + "" + "This is usually set automatically when " + "[`get_authtok`](PamModuleHandle::get_authtok) is called, " + "but can be manually set." + ); + + trait_item!( + set = set_old_authtok_item, + item = "PAM_OLDAUTHTOK", + see = PamModuleHandle::old_authtok_item, + "Sets the user's \"old authentication token\" when changing passwords." + "" + "This is usually set automatically by PAM." + ); } /// Functionality of a PAM handle that can be expected by a PAM application. @@ -128,6 +250,9 @@ /// /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html fn close(self, status: Result<()>) -> Result<()>; + + /// Uses a new PAM conversation. + fn set_conversation(&mut self, conversation: Self::Conv) -> Result<()>; } /// Functionality of a PAM handle that can be expected by a PAM module. @@ -138,131 +263,215 @@ /// Like [`PamHandle`], this is intended to allow creating mock implementations /// of PAM for testing PAM modules. pub trait PamModuleHandle: PamHandle { - /// Gets some pointer, identified by `key`, that has been set previously - /// using [`set_data`](Self::set_data). + /// Gets a channel for communication with the user. + /// + /// The Conversation is the conduit which you use for all communication + /// with the user. + fn conversation(&mut self) -> Result<ConversationMux<'_, Self::Conv>>; + + /// Retrieves the authentication token from the user. + /// + /// This should only be used by *authentication* and *password-change* + /// PAM modules. + /// + /// See the [`pam_get_authtok` manual page][man] + /// or [`pam_get_item` in the Module Writer's Guide][mwg]. + /// + /// # Example + /// + /// ```no_run + /// # use nonstick::PamModuleHandle; + /// # fn _doc(handle: &mut impl PamModuleHandle) -> Result<(), Box<dyn std::error::Error>> { + /// // Get the user's password using the default prompt. + /// let pass = handle.get_authtok(None)?; + /// // Get the user's password using a custom prompt. + /// let pass = handle.get_authtok(Some("Reveal your secrets!"))?; + /// Ok(()) + /// # } + /// ``` /// - /// The data, if present, is still owned by the current PAM session. - /// - /// See the [`pam_get_data` manual page][man] - /// or [`pam_get_data` in the Module Writer's Guide][mwg]. + /// [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<Option<&str>>; + + trait_item!( + get = authtok_item, + item = "PAM_AUTHTOK", + see = Self::get_authtok, + "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." + "" + "Like `get_authtok`, this should only ever be called " + "by *authentication* and *password-change* PAM modules." + ); + + trait_item!( + get = old_authtok_item, + item = "PAM_OLDAUTHTOK", + see = PamHandle::set_old_authtok_item, + "Gets the user's old authentication token when changing passwords." + "" + "This should only ever be called by *password-change* PAM modules." + ); + + /* + TODO: Re-enable this at some point. + /// Gets some pointer, identified by `key`, that has been set previously + /// using [`set_data`](Self::set_data). + /// + /// The data, if present, is still owned by the current PAM session. + /// + /// See the [`pam_get_data` manual page][man] + /// or [`pam_get_data` in the Module Writer's Guide][mwg]. + /// + /// # Safety + /// + /// The data stored under the provided key must be of type `T`, + /// otherwise you'll get back a completely invalid `&T` + /// and further behavior is undefined. + /// + /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html + /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_data + unsafe fn get_data<T>(&mut self, key: &str) -> Result<Option<&T>>; + + /// Stores a pointer that can be retrieved later with [`get_data`](Self::get_data). + /// + /// This data is accessible to this module and other PAM modules + /// (using the provided `key`), but is *not* accessible to the application. + /// The PAM session takes ownership of the data, and it will be dropped + /// when the session ends. + /// + /// See the [`pam_set_data` manual page][man] + /// or [`pam_set_data` in the Module Writer's Guide][mwg]. + /// + /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html + /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_data + fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()>; + */ +} + + +impl LibPamHandle { + /// Gets a C string item. /// /// # Safety /// - /// The data stored under the provided key must be of type `T`, - /// otherwise you'll get back a completely invalid `&T` - /// and further behavior is undefined. + /// You better be requesting an item which is a C string. + unsafe fn get_cstr_item(&mut self, item_type: ItemType) -> Result<Option<&str>> { + let mut output = ptr::null(); + let ret = unsafe { pam_ffi::pam_get_item(self, item_type as c_int, &mut output) }; + ErrorCode::result_from(ret)?; + memory::wrap_string(output.cast()) + } + + /// Sets a C string item. /// - /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html - /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_data - unsafe fn get_data<T>(&mut self, key: &str) -> Result<Option<&T>>; - - /// Stores a pointer that can be retrieved later with [`get_data`](Self::get_data). - /// - /// This data is accessible to this module and other PAM modules - /// (using the provided `key`), but is *not* accessible to the application. - /// The PAM session takes ownership of the data, and it will be dropped - /// when the session ends. + /// # Safety /// - /// See the [`pam_set_data` manual page][man] - /// or [`pam_set_data` in the Module Writer's Guide][mwg]. - /// - /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html - /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_data - fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()>; + /// You better be setting an item which is a C string. + unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> { + let data_str = memory::option_cstr(data)?; + let ret = unsafe { + pam_ffi::pam_set_item( + self, + item_type as c_int, + memory::prompt_ptr(data_str.as_ref()).cast(), + ) + }; + ErrorCode::result_from(ret) + } } -/// A [`PamHandle`] backed by `libpam`, i.e., a real PAM handle. -/// -/// This structure wraps an opaque PAM handle and gives you a nice Rusty -/// interface to use PAM. -#[repr(C)] -pub struct LibPamHandle(pam_ffi::Handle); - impl Drop for LibPamHandle { /// Ends the PAM session with a zero error code. /// You probably want to call [`close`](Self::close) instead of /// letting this drop by itself. fn drop(&mut self) { unsafe { - pam_ffi::pam_end(&mut self.0, 0); + pam_ffi::pam_end(self, 0); } } } +macro_rules! cstr_item { + (get = $getter:ident, item = $item_type:path) => { + fn $getter(&mut self) -> Result<Option<&str>> { + unsafe { self.get_cstr_item($item_type) } + } + }; + (set = $setter:ident, item = $item_type:path) => { + fn $setter(&mut self, value: Option<&str>) -> Result<()> { + unsafe { self.set_cstr_item($item_type, value) } + } + }; +} + impl PamHandle for LibPamHandle { - fn get_user(&mut self, prompt: Option<&str>) -> crate::Result<String> { + type Conv = LibPamConversation; + fn get_user(&mut self, prompt: Option<&str>) -> Result<Option<&str>> { let prompt = memory::option_cstr(prompt)?; - let mut output: *const c_char = ptr::null_mut(); + let mut output: *const c_char = ptr::null(); let ret = unsafe { - pam_ffi::pam_get_user(&self.0, &mut output, memory::prompt_ptr(prompt.as_ref())) + pam_ffi::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref())) }; ErrorCode::result_from(ret)?; - unsafe {memory::copy_pam_string(output)} + unsafe { memory::wrap_string(output) } } - fn get_authtok(&mut self, prompt: Option<&str>) -> crate::Result<SecureString> { + cstr_item!(get = user_item, item = ItemType::User); + cstr_item!(set = set_user_item, item = ItemType::User); + cstr_item!(get = service, item = ItemType::Service); + cstr_item!(set = set_service, item = ItemType::Service); + cstr_item!(get = user_prompt, item = ItemType::UserPrompt); + cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt); + cstr_item!(get = tty_name, item = ItemType::Tty); + cstr_item!(set = set_tty_name, item = ItemType::Tty); + cstr_item!(get = remote_user, item = ItemType::RemoteUser); + cstr_item!(set = set_remote_user, item = ItemType::RemoteUser); + cstr_item!(get = remote_host, item = ItemType::RemoteHost); + cstr_item!(set = set_remote_host, item = ItemType::RemoteHost); + cstr_item!(set = set_authtok_item, item = ItemType::AuthTok); + cstr_item!(set = set_old_authtok_item, item = ItemType::OldAuthTok); +} + +impl PamApplicationHandle for LibPamHandle { + fn close(mut self, status: Result<()>) -> Result<()> { + let result = unsafe { pam_ffi::pam_end(&mut self, ErrorCode::result_to_c(status)) }; + // Since we've already `pam_end`ed this session, we don't want it to be + // double-freed on drop. + mem::forget(self); + ErrorCode::result_from(result) + } + + fn set_conversation(&mut self, conversation: Self::Conv) -> Result<()> { + todo!() + } +} + +impl PamModuleHandle for LibPamHandle { + fn conversation(&mut self) -> Result<ConversationMux<'_, Self::Conv>> { + todo!() + } + + fn get_authtok(&mut self, prompt: Option<&str>) -> Result<Option<&str>> { let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = ptr::null_mut(); let res = unsafe { pam_ffi::pam_get_authtok( - &self.0, + self, ItemType::AuthTok.into(), &mut output, memory::prompt_ptr(prompt.as_ref()), ) }; ErrorCode::result_from(res)?; - unsafe {memory::copy_pam_string(output)}.map(SecureString::from) - } - - fn get_item<T: Item>(&mut self) -> Result<Option<T>> { - let mut ptr: *const c_void = ptr::null(); - let out = unsafe { - let ret = pam_ffi::pam_get_item(&self.0, T::type_id().into(), &mut ptr); - ErrorCode::result_from(ret)?; - (ptr as *const T::Raw).as_ref().map(|p| T::from_raw(p)) - }; - Ok(out) + unsafe { memory::wrap_string(output) } } - fn set_item<T: Item>(&mut self, item: T) -> Result<()> { - let ret = unsafe { - pam_ffi::pam_set_item(&mut self.0, T::type_id().into(), item.into_raw().cast()) - }; - ErrorCode::result_from(ret) - } -} - -impl PamApplicationHandle for LibPamHandle { - fn close(mut self, status: Result<()>) -> Result<()> { - let result = unsafe { pam_ffi::pam_end(&mut self.0, ErrorCode::result_to_c(status)) }; - // Since we've already `pam_end`ed this session, we don't want it to be - // double-freed on drop. - mem::forget(self); - ErrorCode::result_from(result) - } -} - -impl PamModuleHandle for LibPamHandle { - unsafe fn get_data<T>(&mut self, key: &str) -> crate::Result<Option<&T>> { - let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; - let mut ptr: *const c_void = ptr::null(); - ErrorCode::result_from(pam_ffi::pam_get_data(&self.0, c_key.as_ptr(), &mut ptr))?; - Ok((ptr as *const T).as_ref()) - } - - fn set_data<T>(&mut self, key: &str, data: Box<T>) -> crate::Result<()> { - let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; - let ret = unsafe { - pam_ffi::pam_set_data( - &mut self.0, - c_key.as_ptr(), - Box::into_raw(data).cast(), - set_data_cleanup::<T>, - ) - }; - ErrorCode::result_from(ret) - } + cstr_item!(get = authtok_item, item = ItemType::AuthTok); + cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok); } /// Function called at the end of a PAM session that is called to clean up
--- a/src/items.rs Tue Jun 03 21:54:58 2025 -0400 +++ b/src/items.rs Wed Jun 04 03:53:36 2025 -0400 @@ -3,12 +3,10 @@ use crate::constants::InvalidEnum; use num_derive::FromPrimitive; use num_traits::FromPrimitive; -use std::ffi::{c_int, CStr}; +use std::ffi::c_int; -/// 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. +/// Identifies what is being gotten or set with `pam_get_item` +/// or `pam_set_item`. #[derive(FromPrimitive)] #[repr(i32)] #[non_exhaustive] // because C could give us anything! @@ -53,68 +51,3 @@ val as Self } } - -/// A type that can be requested by [`PamHandle::get_item`](crate::LibPamHandle::get_item). -pub trait Item { - /// The `repr(C)` type that is returned (by pointer) by the underlying `pam_get_item` function. - /// This memory is owned by the PAM session. - type Raw; - - /// The [ItemType] corresponding to this Rust type. - fn type_id() -> ItemType; - - /// The function to convert from the pointer to the C-representation to this safer wrapper type. - /// - /// # Safety - /// - /// This function assumes the pointer is a valid pointer to a [Self::Raw] instance. - unsafe fn from_raw(raw: *const Self::Raw) -> Self; - - /// The function to convert from this wrapper type to a C-compatible pointer. - fn into_raw(self) -> *const Self::Raw; -} - -/// Macro to generate PAM [Item]s represented as [CStr]s. -macro_rules! cstr_item { - ($name:ident) => { - #[doc = "The [CStr] representation of the "] - #[doc = concat!("[`", stringify!($name), "`](ItemType::", stringify!($name), ")")] - #[doc = " [Item]."] - #[derive(Debug)] - pub struct $name<'s>(pub &'s CStr); - - impl<'s> std::ops::Deref for $name<'s> { - type Target = &'s CStr; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl<'s> Item for $name<'s> { - type Raw = libc::c_char; - - fn type_id() -> ItemType { - ItemType::$name - } - - unsafe fn from_raw(raw: *const Self::Raw) -> Self { - Self(std::ffi::CStr::from_ptr(raw)) - } - - fn into_raw(self) -> *const Self::Raw { - self.0.as_ptr() - } - } - }; -} - -// Conversation is not included here since it's special. - -cstr_item!(Service); -cstr_item!(User); -cstr_item!(Tty); -cstr_item!(RemoteHost); -cstr_item!(AuthTok); -cstr_item!(OldAuthTok); -cstr_item!(RemoteUser); -cstr_item!(UserPrompt);
--- a/src/lib.rs Tue Jun 03 21:54:58 2025 -0400 +++ b/src/lib.rs Wed Jun 04 03:53:36 2025 -0400 @@ -22,9 +22,12 @@ //! //! [module-guide]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html +// Temporary until everything is fully wired up. +#![allow(dead_code)] + pub mod constants; -pub mod conv; -pub mod items; +mod conv; +mod items; pub mod module; mod handle; @@ -33,6 +36,8 @@ #[doc(inline)] pub use crate::{ constants::{ErrorCode, Flags, Result}, - handle::{LibPamHandle, PamApplicationHandle, PamHandle, PamModuleHandle}, + conv::{Conversation, DemuxedConversation, Response}, + handle::{PamApplicationHandle, PamHandle, PamModuleHandle}, module::PamModule, + pam_ffi::{LibPamHandle, Message}, };
--- a/src/module.rs Tue Jun 03 21:54:58 2025 -0400 +++ b/src/module.rs Wed Jun 04 03:53:36 2025 -0400 @@ -32,7 +32,7 @@ /// /// This is probably the first thing you want to implement. /// In most cases, you will want to get the user and password, - /// using [`LibPamHandle::get_user`] and [`LibPamHandle::get_authtok`], + /// using [`PamHandle::get_user`] and [`PamModuleHandle::get_authtok`], /// and verify them against something. /// /// See [the Module Writer's Guide entry for `pam_sm_authenticate`][mwg] @@ -255,15 +255,15 @@ /// let token = mux.masked_prompt("enter your one-time token")?; /// # Ok(()) /// # } -pub struct ConversationMux<C: Conversation>(pub C); +pub struct ConversationMux<'a, C: Conversation>(pub &'a mut C); -impl<C: Conversation> Conversation for ConversationMux<C> { +impl<C: Conversation> Conversation for ConversationMux<'_, C> { fn send(&mut self, messages: &[Message]) -> Result<Vec<Response>> { self.0.send(messages) } } -impl<C: Conversation> ConversationMux<C> { +impl<C: Conversation> ConversationMux<'_, C> { /// Prompts the user for something. pub fn prompt(&mut self, request: &str) -> Result<String> { let resp = self.send(&[Message::Prompt(request)])?.pop(); @@ -524,7 +524,9 @@ } } - let mut mux = ConversationMux(MuxTester); + let mut tester = MuxTester; + + let mut mux = ConversationMux(&mut tester); assert_eq!("answer", mux.prompt("question").unwrap()); assert_eq!( SecureString::from("open sesame"),
--- a/src/pam_ffi/memory.rs Tue Jun 03 21:54:58 2025 -0400 +++ b/src/pam_ffi/memory.rs Wed Jun 04 03:53:36 2025 -0400 @@ -28,6 +28,10 @@ /// Creates an owned copy of a string that is returned from a /// <code>pam_get_<var>whatever</var></code> function. +/// +/// # Safety +/// +/// It's on you to provide a valid string. pub unsafe fn copy_pam_string(result_ptr: *const libc::c_char) -> Result<String> { // We really shouldn't get a null pointer back here, but if we do, return nothing. if result_ptr.is_null() { @@ -40,6 +44,20 @@ .map_err(|_| ErrorCode::ConversationError) } +/// Wraps a string returned from PAM as an `Option<&str>`. +pub unsafe fn wrap_string<'a>(data: *const libc::c_char) -> Result<Option<&'a str>> { + let ret = if data.is_null() { + None + } else { + Some( + CStr::from_ptr(data) + .to_str() + .map_err(|_| ErrorCode::ConversationError)?, + ) + }; + Ok(ret) +} + /// Allocates a string with the given contents on the C heap. /// /// This is like [`CString::new`](std::ffi::CString::new), but: @@ -147,9 +165,9 @@ #[cfg(test)] mod tests { - use std::ffi::CString; + use super::{copy_pam_string, malloc_str, option_cstr, prompt_ptr, zero_c_string}; use crate::ErrorCode; - use super::{copy_pam_string, malloc_str, option_cstr, prompt_ptr, zero_c_string}; + use std::ffi::CString; #[test] fn test_strings() { let str = malloc_str("hello there").unwrap(); @@ -165,7 +183,7 @@ libc::free(str.cast()); } } - + #[test] fn test_option_str() { let good = option_cstr(Some("whatever")).unwrap(); @@ -175,7 +193,7 @@ let bad_str = option_cstr(Some("what\0ever")).unwrap_err(); assert_eq!(ErrorCode::ConversationError, bad_str); } - + #[test] fn test_prompt() { let prompt_cstr = CString::new("good").ok();
--- a/src/pam_ffi/message.rs Tue Jun 03 21:54:58 2025 -0400 +++ b/src/pam_ffi/message.rs Wed Jun 04 03:53:36 2025 -0400 @@ -18,31 +18,31 @@ pub enum Message<'a> { /// Requests information from the user; will be masked when typing. /// - /// Response: [`Response::MaskedText`] + /// Response: [`MaskedText`](crate::conv::Response::MaskedText) MaskedPrompt(&'a str), /// Requests information from the user; will not be masked. /// - /// Response: [`Response::Text`] + /// Response: [`Text`](crate::conv::Response::Text) Prompt(&'a str), /// "Yes/No/Maybe conditionals" (a Linux-PAM extension). /// - /// Response: [`Response::Text`] + /// Response: [`Text`](crate::conv::Response::Text) /// (Linux-PAM documentation doesn't define its contents.) RadioPrompt(&'a str), /// Raises an error message to the user. /// - /// Response: [`Response::NoResponse`] + /// Response: [`NoResponse`](crate::conv::Response::NoResponse) Error(&'a str), /// Sends an informational message to the user. /// - /// Response: [`Response::NoResponse`] + /// Response: [`NoResponse`](crate::conv::Response::NoResponse) Info(&'a str), /// Requests binary data from the client (a Linux-PAM extension). /// /// This is used for non-human or non-keyboard prompts (security key?). /// NOT part of the X/Open PAM specification. /// - /// Response: [`Response::Binary`] + /// Response: [`Binary`](crate::conv::Response::Binary) BinaryPrompt { /// Some binary data. data: &'a [u8],
--- a/src/pam_ffi/mod.rs Tue Jun 03 21:54:58 2025 -0400 +++ b/src/pam_ffi/mod.rs Wed Jun 04 03:53:36 2025 -0400 @@ -5,7 +5,7 @@ //! those data structures. //! //! Everything in here is hazmat. -//! +//! #![allow(dead_code)] @@ -15,13 +15,15 @@ use crate::pam_ffi::memory::Immovable; use crate::pam_ffi::message::OwnedMessages; +#[doc(inline)] pub use message::Message; +#[doc(inline)] pub use response::RawResponse; use std::ffi::{c_char, c_int, c_void}; /// An opaque structure that a PAM handle points to. #[repr(C)] -pub struct Handle { +pub struct LibPamHandle { _data: (), _marker: Immovable, } @@ -43,7 +45,7 @@ /// which PAM sets in response to a module's request. /// This is an array of structs, not an array of pointers to a struct. /// There should always be exactly as many `responses` as `num_msg`. -/// - `appdata` is the `appdata` field of the [`Conversation`] we were passed. +/// - `appdata` is the `appdata` field of the [`LibPamConversation`] we were passed. pub type ConversationCallback = extern "C" fn( num_msg: c_int, messages: &OwnedMessages, @@ -51,44 +53,51 @@ appdata: *const AppData, ) -> c_int; -/// A callback and the associated [`AppData`] pointer that needs to be passed back to it. +/// The type used by libpam to call back into a conversation. #[repr(C)] -pub struct Conversation { +pub struct LibPamConversation { + /// The function that is called to get information from the user. callback: ConversationCallback, + /// The pointer that will be passed as the last parameter + /// to the conversation callback. appdata: *const AppData, } #[link(name = "pam")] extern "C" { pub fn pam_get_data( - pamh: *const Handle, + pamh: *const LibPamHandle, module_data_name: *const c_char, data: &mut *const c_void, ) -> c_int; pub fn pam_set_data( - pamh: *mut Handle, + pamh: *mut LibPamHandle, module_data_name: *const c_char, data: *const c_void, cleanup: extern "C" fn(pamh: *const c_void, data: *mut c_void, error_status: c_int), ) -> c_int; - pub fn pam_get_item(pamh: *const Handle, item_type: c_int, item: &mut *const c_void) -> c_int; + pub fn pam_get_item( + pamh: *mut LibPamHandle, + item_type: c_int, + item: &mut *const c_void, + ) -> c_int; - pub fn pam_set_item(pamh: *mut Handle, item_type: c_int, item: *const c_void) -> c_int; + pub fn pam_set_item(pamh: *mut LibPamHandle, item_type: c_int, item: *const c_void) -> c_int; pub fn pam_get_user( - pamh: *const Handle, + pamh: *mut LibPamHandle, user: &mut *const c_char, prompt: *const c_char, ) -> c_int; pub fn pam_get_authtok( - pamh: *const Handle, + pamh: *mut LibPamHandle, item_type: c_int, data: &mut *const c_char, prompt: *const c_char, ) -> c_int; - pub fn pam_end(pamh: *mut Handle, status: c_int) -> c_int; + pub fn pam_end(pamh: *mut LibPamHandle, status: c_int) -> c_int; }
--- a/src/pam_ffi/response.rs Tue Jun 03 21:54:58 2025 -0400 +++ b/src/pam_ffi/response.rs Wed Jun 04 03:53:36 2025 -0400 @@ -136,7 +136,7 @@ OwnedResponses { // SAFETY: We are doing allocation here. base: unsafe { libc::calloc(count, size_of::<RawResponse>()) } as *mut RawResponse, - count: count, + count, } }