comparison src/handle.rs @ 66:a674799a5cd3

Make `PamHandle` and `PamModuleHandle` traits. This creates traits for PAM functionality and pulls the definitions of that functionality out of the original `PamHandle` (renamed to `LibPamHandle`) and into those traits. This supports testing PAM module implementations using mock PAM library implementations. Also uses a better representation of opaque pointers.
author Paul Fisher <paul@pfish.zone>
date Tue, 27 May 2025 14:37:28 -0400
parents bbe84835d6db
children
comparison
equal deleted inserted replaced
65:8e507c7af9cf 66:a674799a5cd3
1 //! Where [PamHandle] lives. 1 //! The wrapper types and traits for handles into the PAM library.
2 use crate::constants::{ErrorCode, Result};
2 use crate::items::{Item, ItemType}; 3 use crate::items::{Item, ItemType};
3 use crate::{memory, pam_ffi, ErrorCode}; 4 use crate::{memory, pam_ffi};
4 use libc::c_char; 5 use libc::c_char;
5 use secure_string::SecureString; 6 use secure_string::SecureString;
6 use std::ffi::{c_int, CString}; 7 use std::ffi::{c_int, CString};
7 8 use std::mem;
8 /// Your interface to a PAM handle. 9
9 /// 10 /// Features of a PAM handle that are available to applications and modules.
10 /// This structure wraps an opaque PAM-provided pointer and gives you 11 ///
11 /// a safe and familiar struct-based API to interact with PAM. 12 /// You probably want [`LibPamHandle`]. This trait is intended to allow creating
12 #[repr(transparent)] 13 /// mock PAM handle types used for testing PAM modules and applications.
13 pub struct PamHandle(*mut libc::c_void); 14 pub trait PamHandle {
14
15 impl PamHandle {
16 /// Retrieves the name of the user who is authenticating or logging in. 15 /// Retrieves the name of the user who is authenticating or logging in.
17 /// 16 ///
18 /// This is effectively like `handle.get_item::<Item::User>()`. 17 /// This is effectively like `handle.get_item::<Item::User>()`.
19 /// See the [`pam_get_user` manual page][man] 18 /// See the [`pam_get_user` manual page][man]
20 /// or [`pam_get_user` in the Module Writer's Guide][mwg]. 19 /// or [`pam_get_user` in the Module Writer's Guide][mwg].
21 /// 20 ///
22 /// # Example 21 /// # Example
23 /// 22 ///
24 /// ```no_run 23 /// ```no_run
25 /// # use nonstick::PamHandle; 24 /// # use nonstick::PamHandle;
26 /// # fn _doc(handle: &PamHandle) -> Result<(), Box<dyn std::error::Error>> { 25 /// # fn _doc(handle: &impl PamHandle) -> Result<(), Box<dyn std::error::Error>> {
27 /// // Get the username using the default prompt. 26 /// // Get the username using the default prompt.
28 /// let user = handle.get_user(None)?; 27 /// let user = handle.get_user(None)?;
29 /// // Get the username using a custom prompt. 28 /// // Get the username using a custom prompt.
30 /// let user = handle.get_user(Some("who ARE you even???"))?; 29 /// let user = handle.get_user(Some("who ARE you even???"))?;
31 /// # Ok(()) 30 /// # Ok(())
32 /// # } 31 /// # }
33 /// ``` 32 /// ```
34 /// 33 ///
35 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html 34 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html
36 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_user 35 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_user
37 pub fn get_user(&self, prompt: Option<&str>) -> crate::Result<String> { 36 fn get_user(&self, prompt: Option<&str>) -> Result<String>;
38 let prompt = memory::option_cstr(prompt)?;
39 let mut output: *const c_char = std::ptr::null_mut();
40 let ret = unsafe {
41 pam_ffi::pam_get_user(self.0, &mut output, memory::prompt_ptr(prompt.as_ref()))
42 };
43 ErrorCode::result_from(ret)?;
44 memory::copy_pam_string(output)
45 }
46 37
47 /// Retrieves the authentication token from the user. 38 /// Retrieves the authentication token from the user.
48 /// 39 ///
49 /// This is essentially like `handle.get_item::<Item::AuthTok>()`. 40 /// This is essentially like `handle.get_item::<Item::AuthTok>()`.
50 /// 41 ///
53 /// 44 ///
54 /// # Example 45 /// # Example
55 /// 46 ///
56 /// ```no_run 47 /// ```no_run
57 /// # use nonstick::PamHandle; 48 /// # use nonstick::PamHandle;
58 /// # fn _doc(handle: &PamHandle) -> Result<(), Box<dyn std::error::Error>> { 49 /// # fn _doc(handle: &impl PamHandle) -> Result<(), Box<dyn std::error::Error>> {
59 /// // Get the user's password using the default prompt. 50 /// // Get the user's password using the default prompt.
60 /// let pass = handle.get_authtok(None)?; 51 /// let pass = handle.get_authtok(None)?;
61 /// // Get the user's password using a custom prompt. 52 /// // Get the user's password using a custom prompt.
62 /// let pass = handle.get_authtok(Some("Reveal your secrets!"))?; 53 /// let pass = handle.get_authtok(Some("Reveal your secrets!"))?;
63 /// Ok(()) 54 /// Ok(())
64 /// # } 55 /// # }
65 /// ``` 56 /// ```
66 /// 57 ///
67 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html 58 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html
68 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item 59 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item
69 pub fn get_authtok(&self, prompt: Option<&str>) -> crate::Result<SecureString> { 60 fn get_authtok(&self, prompt: Option<&str>) -> Result<SecureString>;
70 let prompt = memory::option_cstr(prompt)?;
71 let mut output: *const c_char = std::ptr::null_mut();
72 let res = unsafe {
73 pam_ffi::pam_get_authtok(
74 self.0,
75 ItemType::AuthTok.into(),
76 &mut output,
77 memory::prompt_ptr(prompt.as_ref()),
78 )
79 };
80 ErrorCode::result_from(res)?;
81 memory::copy_pam_string(output).map(SecureString::from)
82 }
83 61
84 /// Retrieves an [Item] that has been set, possibly by the PAM client. 62 /// Retrieves an [Item] that has been set, possibly by the PAM client.
85 /// 63 ///
86 /// These items are *references to PAM memory* 64 /// These items are *references to PAM memory*
87 /// which are *owned by the PAM session* 65 /// which are *owned by the PAM session*
94 /// 72 ///
95 /// ```no_run 73 /// ```no_run
96 /// # use nonstick::PamHandle; 74 /// # use nonstick::PamHandle;
97 /// use nonstick::items::Service; 75 /// use nonstick::items::Service;
98 /// 76 ///
99 /// # fn _doc(pam_handle: &PamHandle) -> Result<(), Box<dyn std::error::Error>> { 77 /// # fn _doc(pam_handle: &impl PamHandle) -> Result<(), Box<dyn std::error::Error>> {
100 /// let svc: Option<Service> = pam_handle.get_item()?; 78 /// let svc: Option<Service> = pam_handle.get_item()?;
101 /// match svc { 79 /// match svc {
102 /// Some(name) => eprintln!("The calling service name is {:?}", name.to_string_lossy()), 80 /// Some(name) => eprintln!("The calling service name is {:?}", name.to_string_lossy()),
103 /// None => eprintln!("Who knows what the calling service is?"), 81 /// None => eprintln!("Who knows what the calling service is?"),
104 /// } 82 /// }
106 /// # } 84 /// # }
107 /// ``` 85 /// ```
108 /// 86 ///
109 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html 87 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html
110 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item 88 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item
111 pub fn get_item<T: Item>(&self) -> crate::Result<Option<T>> { 89 fn get_item<T: Item>(&self) -> Result<Option<T>>;
90
91 /// Sets an item in the PAM context. It can be retrieved using [`get_item`](Self::get_item).
92 ///
93 /// See the [`pam_set_item` manual page][man]
94 /// or [`pam_set_item` in the Module Writer's Guide][mwg].
95 ///
96 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html
97 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item
98 fn set_item<T: Item>(&mut self, item: T) -> Result<()>;
99
100 /// Closes the PAM session on an owned PAM handle.
101 ///
102 /// This should be called with the result of the application's last call
103 /// into PAM services. Since this is only applicable to *owned* PAM handles,
104 /// a PAM module should never call this (and it will never be handed
105 /// an owned `PamHandle` that it can `close`).
106 ///
107 /// See the [`pam_end` manual page][man] for more information.
108 ///
109 /// ```no_run
110 /// # use nonstick::PamHandle;
111 /// # use std::error::Error;
112 /// # fn _doc(handle: impl PamHandle, auth_result: nonstick::Result<()>) -> Result<(), Box<dyn Error>> {
113 /// // Earlier: authentication was performed and the result was stored
114 /// // into auth_result.
115 /// handle.close(auth_result)?;
116 /// # Ok(())
117 /// # }
118 /// ```
119 ///
120 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html
121 fn close(self, status: Result<()>) -> Result<()>;
122 }
123
124 /// Functionality of a PAM handle that can be expected by a PAM module.
125 ///
126 /// If you are not writing a PAM module (e.g., you are writing an application),
127 /// you should not use any of the functionality exposed by this trait.
128 ///
129 /// Like [`PamHandle`], this is intended to allow creating mock implementations
130 /// of PAM for testing PAM modules.
131 pub trait PamModuleHandle: PamHandle {
132 /// Gets some pointer, identified by `key`, that has been set previously
133 /// using [`set_data`](Self::set_data).
134 ///
135 /// The data, if present, is still owned by the current PAM session.
136 ///
137 /// See the [`pam_get_data` manual page][man]
138 /// or [`pam_get_data` in the Module Writer's Guide][mwg].
139 ///
140 /// # Safety
141 ///
142 /// The data stored under the provided key must be of type `T`,
143 /// otherwise you'll get back a completely invalid `&T`
144 /// and further behavior is undefined.
145 ///
146 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html
147 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_data
148 unsafe fn get_data<T>(&self, key: &str) -> Result<Option<&T>>;
149
150 /// Stores a pointer that can be retrieved later with [`get_data`](Self::get_data).
151 ///
152 /// This data is accessible to this module and other PAM modules
153 /// (using the provided `key`), but is *not* accessible to the application.
154 /// The PAM session takes ownership of the data, and it will be dropped
155 /// when the session ends.
156 ///
157 /// See the [`pam_set_data` manual page][man]
158 /// or [`pam_set_data` in the Module Writer's Guide][mwg].
159 ///
160 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html
161 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_data
162 fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()>;
163 }
164
165 /// A [`PamHandle`] backed by `libpam`, i.e., a real PAM handle.
166 ///
167 /// This structure wraps an opaque PAM handle and gives you a nice Rusty
168 /// interface to use PAM.
169 #[repr(C)]
170 pub struct LibPamHandle(pam_ffi::Handle);
171
172 impl LibPamHandle {
173 /// Converts a pointer passed from PAM into a borrowed handle.
174 ///
175 /// # Safety
176 ///
177 /// It is your responsibility to provide a valid pointer.
178 pub unsafe fn from_ptr<'a>(ptr: *mut libc::c_void) -> &'a mut LibPamHandle {
179 &mut *(ptr as *mut LibPamHandle)
180 }
181 }
182
183 impl Drop for LibPamHandle {
184 /// Ends the PAM session with a zero error code.
185 fn drop(&mut self) {
186 unsafe {
187 pam_ffi::pam_end(&mut self.0, 0);
188 }
189 }
190 }
191
192 impl PamHandle for LibPamHandle {
193 fn get_user(&self, prompt: Option<&str>) -> crate::Result<String> {
194 let prompt = memory::option_cstr(prompt)?;
195 let mut output: *const c_char = std::ptr::null_mut();
196 let ret = unsafe {
197 pam_ffi::pam_get_user(&self.0, &mut output, memory::prompt_ptr(prompt.as_ref()))
198 };
199 ErrorCode::result_from(ret)?;
200 memory::copy_pam_string(output)
201 }
202
203 fn get_authtok(&self, prompt: Option<&str>) -> crate::Result<SecureString> {
204 let prompt = memory::option_cstr(prompt)?;
205 let mut output: *const c_char = std::ptr::null_mut();
206 let res = unsafe {
207 pam_ffi::pam_get_authtok(
208 &self.0,
209 ItemType::AuthTok.into(),
210 &mut output,
211 memory::prompt_ptr(prompt.as_ref()),
212 )
213 };
214 ErrorCode::result_from(res)?;
215 memory::copy_pam_string(output).map(SecureString::from)
216 }
217
218 fn get_item<T: Item>(&self) -> crate::Result<Option<T>> {
112 let mut ptr: *const libc::c_void = std::ptr::null(); 219 let mut ptr: *const libc::c_void = std::ptr::null();
113 let out = unsafe { 220 let out = unsafe {
114 let ret = pam_ffi::pam_get_item(self.0, T::type_id().into(), &mut ptr); 221 let ret = pam_ffi::pam_get_item(&self.0, T::type_id().into(), &mut ptr);
115 ErrorCode::result_from(ret)?; 222 ErrorCode::result_from(ret)?;
116 let typed_ptr: *const T::Raw = ptr.cast(); 223 let typed_ptr: *const T::Raw = ptr.cast();
117 match typed_ptr.is_null() { 224 match typed_ptr.is_null() {
118 true => None, 225 true => None,
119 false => Some(T::from_raw(typed_ptr)), 226 false => Some(T::from_raw(typed_ptr)),
120 } 227 }
121 }; 228 };
122 Ok(out) 229 Ok(out)
123 } 230 }
124 231
125 /// Sets an item in the PAM context. It can be retrieved using [`get_item`](Self::get_item). 232 fn set_item<T: Item>(&mut self, item: T) -> crate::Result<()> {
126 /// 233 let ret = unsafe {
127 /// See the [`pam_set_item` manual page][man] 234 pam_ffi::pam_set_item(&mut self.0, T::type_id().into(), item.into_raw().cast())
128 /// or [`pam_set_item` in the Module Writer's Guide][mwg]. 235 };
129 ///
130 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html
131 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item
132 pub fn set_item<T: Item>(&mut self, item: T) -> crate::Result<()> {
133 let ret =
134 unsafe { pam_ffi::pam_set_item(self.0, T::type_id().into(), item.into_raw().cast()) };
135 ErrorCode::result_from(ret) 236 ErrorCode::result_from(ret)
136 } 237 }
137 238
138 /// Gets some pointer, identified by `key`, that has been set previously 239 fn close(mut self, status: Result<()>) -> Result<()> {
139 /// using [`set_data`](Self::set_data). 240 let result = unsafe { pam_ffi::pam_end(&mut self.0, ErrorCode::result_to_c(status)) };
140 /// 241 // Since we've already `pam_end`ed this session, we don't want it to be
141 /// The data, if present, is still owned by the current PAM session. 242 // double-freed on drop.
142 /// 243 mem::forget(self);
143 /// See the [`pam_get_data` manual page][man] 244 ErrorCode::result_from(result)
144 /// or [`pam_get_data` in the Module Writer's Guide][mwg]. 245 }
145 /// 246 }
146 /// # Safety 247
147 /// 248 impl PamModuleHandle for LibPamHandle {
148 /// The data stored under the provided key must be of type `T`, 249 unsafe fn get_data<T>(&self, key: &str) -> crate::Result<Option<&T>> {
149 /// otherwise you'll get back a completely invalid `&T`
150 /// and further behavior is undefined.
151 ///
152 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html
153 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_data
154 pub unsafe fn get_data<T>(&self, key: &str) -> crate::Result<Option<&T>> {
155 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; 250 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
156 let mut ptr: *const libc::c_void = std::ptr::null(); 251 let mut ptr: *const libc::c_void = std::ptr::null();
157 ErrorCode::result_from(pam_ffi::pam_get_data(self.0, c_key.as_ptr(), &mut ptr))?; 252 ErrorCode::result_from(pam_ffi::pam_get_data(&self.0, c_key.as_ptr(), &mut ptr))?;
158 match ptr.is_null() { 253 match ptr.is_null() {
159 true => Ok(None), 254 true => Ok(None),
160 false => { 255 false => {
161 let typed_ptr = ptr.cast(); 256 let typed_ptr = ptr.cast();
162 Ok(Some(&*typed_ptr)) 257 Ok(Some(&*typed_ptr))
163 } 258 }
164 } 259 }
165 } 260 }
166 261
167 /// Stores a pointer that can be retrieved later with [`get_data`](Self::get_data). 262 fn set_data<T>(&mut self, key: &str, data: Box<T>) -> crate::Result<()> {
168 ///
169 /// This data is accessible to this module and other PAM modules
170 /// (using the provided `key`), but is *not* accessible to the application.
171 /// The PAM session takes ownership of the data, and it will be dropped
172 /// when the session ends.
173 ///
174 /// See the [`pam_set_data` manual page][man]
175 /// or [`pam_set_data` in the Module Writer's Guide][mwg].
176 ///
177 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html
178 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_data
179 pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> crate::Result<()> {
180 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; 263 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
181 let ret = unsafe { 264 let ret = unsafe {
182 pam_ffi::pam_set_data( 265 pam_ffi::pam_set_data(
183 self.0, 266 &mut self.0,
184 c_key.as_ptr(), 267 c_key.as_ptr(),
185 Box::into_raw(data).cast(), 268 Box::into_raw(data).cast(),
186 Self::set_data_cleanup::<T>, 269 set_data_cleanup::<T>,
187 ) 270 )
188 }; 271 };
189 ErrorCode::result_from(ret) 272 ErrorCode::result_from(ret)
190 } 273 }
191 274 }
192 /// Function called at the end of a PAM session that is called to clean up 275
193 /// a value previously provided to PAM in a `pam_set_data` call. 276 /// Function called at the end of a PAM session that is called to clean up
194 /// 277 /// a value previously provided to PAM in a `pam_set_data` call.
195 /// You should never call this yourself. 278 ///
196 extern "C" fn set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) { 279 /// You should never call this yourself.
197 unsafe { 280 extern "C" fn set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) {
198 let _data: Box<T> = Box::from_raw(c_data.cast()); 281 unsafe {
199 } 282 let _data: Box<T> = Box::from_raw(c_data.cast());
200 } 283 }
201 } 284 }
202
203 impl From<*mut libc::c_void> for PamHandle {
204 /// Wraps an internal Handle pointer.
205 fn from(value: *mut libc::c_void) -> Self {
206 Self(value)
207 }
208 }