comparison src/module.rs @ 60:05cc2c27334f

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`).
author Paul Fisher <paul@pfish.zone>
date Wed, 21 May 2025 19:00:51 -0400
parents 3f4a77aa88be
children bbe84835d6db
comparison
equal deleted inserted replaced
59:3f4a77aa88be 60:05cc2c27334f
1 //! Functions for use in pam modules. 1 //! Functions and types useful for implementing a PAM module.
2 2
3 use crate::constants::{ErrorCode, Flags, PamResult}; 3 use crate::constants::{ErrorCode, Flags, Result};
4 use crate::items::{Item, ItemType}; 4 use crate::items::{Item, ItemType};
5 use crate::memory;
5 use libc::c_char; 6 use libc::c_char;
6 use secure_string::SecureString; 7 use secure_string::SecureString;
7 use std::ffi::{c_int, CStr, CString}; 8 use std::ffi::{c_int, CStr, CString};
8 9
9 /// Opaque type, used as a pointer when making pam API calls. 10 use crate::pam_ffi;
10 ///
11 /// A module is invoked via an external function such as `pam_sm_authenticate`.
12 /// Such a call provides a pam handle pointer. The same pointer should be given
13 /// as an argument when making API calls.
14 #[repr(C)]
15 pub struct PamHandle {
16 _data: [u8; 0],
17 }
18
19 #[link(name = "pam")]
20 extern "C" {
21 fn pam_get_data(
22 pamh: *const PamHandle,
23 module_data_name: *const c_char,
24 data: &mut *const libc::c_void,
25 ) -> c_int;
26
27 fn pam_set_data(
28 pamh: *const PamHandle,
29 module_data_name: *const c_char,
30 data: *const libc::c_void,
31 cleanup: extern "C" fn(
32 pamh: *const PamHandle,
33 data: *mut libc::c_void,
34 error_status: c_int,
35 ),
36 ) -> c_int;
37
38 fn pam_get_item(
39 pamh: *const PamHandle,
40 item_type: c_int,
41 item: &mut *const libc::c_void,
42 ) -> c_int;
43
44 fn pam_set_item(pamh: *mut PamHandle, item_type: c_int, item: *const libc::c_void) -> c_int;
45
46 fn pam_get_user(
47 pamh: *const PamHandle,
48 user: &mut *const c_char,
49 prompt: *const c_char,
50 ) -> c_int;
51
52 fn pam_get_authtok(
53 pamh: *const PamHandle,
54 item_type: c_int,
55 data: &mut *const c_char,
56 prompt: *const c_char,
57 ) -> c_int;
58
59 }
60 11
61 /// Function called at the end of a PAM session that is called to clean up 12 /// Function called at the end of a PAM session that is called to clean up
62 /// a value previously provided to PAM in a `pam_set_data` call. 13 /// a value previously provided to PAM in a `pam_set_data` call.
63 /// 14 ///
64 /// You should never call this yourself. 15 /// You should never call this yourself.
65 extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut libc::c_void, _: c_int) { 16 extern "C" fn cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) {
66 unsafe { 17 unsafe {
67 let _data: Box<T> = Box::from_raw(c_data.cast()); 18 let _data: Box<T> = Box::from_raw(c_data.cast());
68 } 19 }
69 } 20 }
21
22 /// An opaque structure pointing to a PAM handle.
23 #[repr(transparent)]
24 pub struct PamHandle(*mut libc::c_void);
70 25
71 impl PamHandle { 26 impl PamHandle {
72 /// Gets some value, identified by `key`, that has been set by the module 27 /// Gets some value, identified by `key`, that has been set by the module
73 /// previously. 28 /// previously.
74 /// 29 ///
75 /// See the [`pam_get_data` manual page]( 30 /// See the [`pam_get_data` manual page](
76 /// https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html). 31 /// https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html).
77 /// 32 ///
78 /// # Errors
79 ///
80 /// Returns an error if the underlying PAM function call fails.
81 ///
82 /// # Safety 33 /// # Safety
83 /// 34 ///
84 /// The data stored under the provided key must be of type `T` otherwise the 35 /// The data stored under the provided key must be of type `T` otherwise the
85 /// behaviour of this function is undefined. 36 /// behaviour of this function is undefined.
86 /// 37 ///
87 /// The data, if present, is owned by the current PAM conversation. 38 /// The data, if present, is owned by the current PAM conversation.
88 pub unsafe fn get_data<T>(&self, key: &str) -> PamResult<Option<&T>> { 39 pub unsafe fn get_data<T>(&self, key: &str) -> Result<Option<&T>> {
89 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; 40 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
90 let mut ptr: *const libc::c_void = std::ptr::null(); 41 let mut ptr: *const libc::c_void = std::ptr::null();
91 ErrorCode::result_from(pam_get_data(self, c_key.as_ptr(), &mut ptr))?; 42 ErrorCode::result_from(pam_ffi::pam_get_data(self.0, c_key.as_ptr(), &mut ptr))?;
92 match ptr.is_null() { 43 match ptr.is_null() {
93 true => Ok(None), 44 true => Ok(None),
94 false => { 45 false => {
95 let typed_ptr = ptr.cast(); 46 let typed_ptr = ptr.cast();
96 Ok(Some(&*typed_ptr)) 47 Ok(Some(&*typed_ptr))
101 /// Stores a value that can be retrieved later with `get_data`. 52 /// Stores a value that can be retrieved later with `get_data`.
102 /// The conversation takes ownership of the data. 53 /// The conversation takes ownership of the data.
103 /// 54 ///
104 /// See the [`pam_set_data` manual page]( 55 /// See the [`pam_set_data` manual page](
105 /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html). 56 /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html).
106 /// 57 pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()> {
107 /// # Errors
108 ///
109 /// Returns an error if the underlying PAM function call fails.
110 pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> PamResult<()> {
111 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; 58 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
112 let ret = unsafe { 59 let ret = unsafe {
113 pam_set_data( 60 pam_ffi::pam_set_data(
114 self, 61 self.0,
115 c_key.as_ptr(), 62 c_key.as_ptr(),
116 Box::into_raw(data).cast(), 63 Box::into_raw(data).cast(),
117 cleanup::<T>, 64 cleanup::<T>,
118 ) 65 )
119 }; 66 };
126 /// These items are *references to PAM memory* 73 /// These items are *references to PAM memory*
127 /// which are *owned by the conversation*. 74 /// which are *owned by the conversation*.
128 /// 75 ///
129 /// See the [`pam_get_item` manual page]( 76 /// See the [`pam_get_item` manual page](
130 /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html). 77 /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html).
131 /// 78 pub fn get_item<T: crate::items::Item>(&self) -> Result<Option<T>> {
132 /// # Errors
133 ///
134 /// Returns an error if the underlying PAM function call fails.
135 pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> {
136 let mut ptr: *const libc::c_void = std::ptr::null(); 79 let mut ptr: *const libc::c_void = std::ptr::null();
137 let out = unsafe { 80 let out = unsafe {
138 let ret = pam_get_item(self, T::type_id().into(), &mut ptr); 81 let ret = pam_ffi::pam_get_item(self.0, T::type_id().into(), &mut ptr);
139 ErrorCode::result_from(ret)?; 82 ErrorCode::result_from(ret)?;
140 let typed_ptr: *const T::Raw = ptr.cast(); 83 let typed_ptr: *const T::Raw = ptr.cast();
141 match typed_ptr.is_null() { 84 match typed_ptr.is_null() {
142 true => None, 85 true => None,
143 false => Some(T::from_raw(typed_ptr)), 86 false => Some(T::from_raw(typed_ptr)),
148 91
149 /// Sets an item in the pam context. It can be retrieved using `get_item`. 92 /// Sets an item in the pam context. It can be retrieved using `get_item`.
150 /// 93 ///
151 /// See the [`pam_set_item` manual page]( 94 /// See the [`pam_set_item` manual page](
152 /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html). 95 /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html).
153 /// 96 pub fn set_item<T: Item>(&mut self, item: T) -> Result<()> {
154 /// # Errors 97 let ret =
155 /// 98 unsafe { pam_ffi::pam_set_item(self.0, T::type_id().into(), item.into_raw().cast()) };
156 /// Returns an error if the underlying PAM function call fails.
157 pub fn set_item<T: Item>(&mut self, item: T) -> PamResult<()> {
158 let ret = unsafe { pam_set_item(self, T::type_id().into(), item.into_raw().cast()) };
159 ErrorCode::result_from(ret) 99 ErrorCode::result_from(ret)
160 } 100 }
161 101
162 /// Retrieves the name of the user who is authenticating or logging in. 102 /// Retrieves the name of the user who is authenticating or logging in.
163 /// 103 ///
164 /// This is really a specialization of `get_item`. 104 /// This is really a specialization of `get_item`.
165 /// 105 ///
166 /// See the [`pam_get_user` manual page]( 106 /// See the [`pam_get_user` manual page](
167 /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html). 107 /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html).
168 /// 108 pub fn get_user(&self, prompt: Option<&str>) -> Result<String> {
169 /// # Errors 109 let prompt = memory::option_cstr(prompt)?;
170 ///
171 /// Returns an error if the underlying PAM function call fails.
172 pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> {
173 let prompt = option_cstr(prompt)?;
174 let mut output: *const c_char = std::ptr::null_mut(); 110 let mut output: *const c_char = std::ptr::null_mut();
175 let ret = unsafe { pam_get_user(self, &mut output, prompt_ptr(prompt.as_ref())) }; 111 let ret = unsafe {
112 pam_ffi::pam_get_user(self.0, &mut output, memory::prompt_ptr(prompt.as_ref()))
113 };
176 ErrorCode::result_from(ret)?; 114 ErrorCode::result_from(ret)?;
177 copy_pam_string(output) 115 memory::copy_pam_string(output)
178 } 116 }
179 117
180 /// Retrieves the authentication token from the user. 118 /// Retrieves the authentication token from the user.
181 /// 119 ///
182 /// This is really a specialization of `get_item`. 120 /// This is really a specialization of `get_item`.
183 /// 121 ///
184 /// See the [`pam_get_authtok` manual page]( 122 /// See the [`pam_get_authtok` manual page](
185 /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html). 123 /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html).
186 /// 124 pub fn get_authtok(&self, prompt: Option<&str>) -> Result<SecureString> {
187 /// # Errors 125 let prompt = memory::option_cstr(prompt)?;
188 ///
189 /// Returns an error if the underlying PAM function call fails.
190 pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<SecureString> {
191 let prompt = option_cstr(prompt)?;
192 let mut output: *const c_char = std::ptr::null_mut(); 126 let mut output: *const c_char = std::ptr::null_mut();
193 let res = unsafe { 127 let res = unsafe {
194 pam_get_authtok( 128 pam_ffi::pam_get_authtok(
195 self, 129 self.0,
196 ItemType::AuthTok.into(), 130 ItemType::AuthTok.into(),
197 &mut output, 131 &mut output,
198 prompt_ptr(prompt.as_ref()), 132 memory::prompt_ptr(prompt.as_ref()),
199 ) 133 )
200 }; 134 };
201 ErrorCode::result_from(res)?; 135 ErrorCode::result_from(res)?;
202 copy_pam_string(output).map(SecureString::from) 136 memory::copy_pam_string(output).map(SecureString::from)
203 } 137 }
204 } 138 }
205 139
206 /// Safely converts a `&str` option to a `CString` option. 140 impl From<*mut libc::c_void> for PamHandle {
207 fn option_cstr(prompt: Option<&str>) -> PamResult<Option<CString>> { 141 /// Wraps an internal Handle pointer.
208 prompt 142 fn from(value: *mut libc::c_void) -> Self {
209 .map(CString::new) 143 Self(value)
210 .transpose() 144 }
211 .map_err(|_| ErrorCode::ConversationError) 145 }
212 } 146
213 147 /// Trait representing what a PAM module can do.
214 /// The pointer to the prompt CString, or null if absent. 148 ///
215 pub(crate) fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { 149 /// By default, all the functions in this trait are ignored.
216 match prompt { 150 /// Implement any functions you wish to handle in your module.
217 Some(c_str) => c_str.as_ptr(), 151 /// After implementing this trait, use the [crate::pam_hooks!] macro
218 None => std::ptr::null(), 152 /// to export your functions.
219 } 153 ///
220 } 154 /// For more information, see [`pam(3)`’s root manual page][manpage]
221 155 /// and the [PAM Module Writer’s Guide][module-guide].
222 /// Creates an owned copy of a string that is returned from a 156 ///
223 /// <code>pam_get_<var>whatever</var></code> function. 157 /// [manpage]: https://www.man7.org/linux/man-pages/man3/pam.3.html
224 pub(crate) fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> { 158 /// [module-guide]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html
225 // We really shouldn't get a null pointer back here, but if we do, return nothing.
226 if result_ptr.is_null() {
227 return Ok(String::new());
228 }
229 let bytes = unsafe { CStr::from_ptr(result_ptr) };
230 bytes
231 .to_str()
232 .map(String::from)
233 .map_err(|_| ErrorCode::ConversationError)
234 }
235
236 /// Provides functions that are invoked by the entrypoints generated by the
237 /// [`pam_hooks!` macro](../macro.pam_hooks.html).
238 ///
239 /// All hooks are ignored by PAM dispatch by default given the default return value of `PAM_IGNORE`.
240 /// Override any functions that you want to handle with your module. See [PAM’s root manual page](
241 /// https://www.man7.org/linux/man-pages/man3/pam.3.html).
242 #[allow(unused_variables)] 159 #[allow(unused_variables)]
243 pub trait PamHooks { 160 pub trait PamModule {
244 /// This function performs the task of establishing whether the user is permitted to gain access at 161 /// This function performs the task of establishing whether the user is permitted to gain access at
245 /// this time. It should be understood that the user has previously been validated by an 162 /// this time. It should be understood that the user has previously been validated by an
246 /// authentication module. This function checks for other things. Such things might be: the time of 163 /// authentication module. This function checks for other things. Such things might be: the time of
247 /// day or the date, the terminal line, remote hostname, etc. This function may also determine 164 /// day or the date, the terminal line, remote hostname, etc. This function may also determine
248 /// things like the expiration on passwords, and respond that the user change it before continuing. 165 /// things like the expiration on passwords, and respond that the user change it before continuing.
249 fn acct_mgmt(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { 166 fn acct_mgmt(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
250 Err(ErrorCode::Ignore) 167 Err(ErrorCode::Ignore)
251 } 168 }
252 169
253 /// This function performs the task of authenticating the user. 170 /// This function performs the task of authenticating the user.
254 fn sm_authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { 171 fn sm_authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
255 Err(ErrorCode::Ignore) 172 Err(ErrorCode::Ignore)
256 } 173 }
257 174
258 /// This function is used to (re-)set the authentication token of the user. 175 /// This function is used to (re-)set the authentication token of the user.
259 /// 176 ///
260 /// The PAM library calls this function twice in succession. The first time with 177 /// The PAM library calls this function twice in succession. The first time with
261 /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with 178 /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with
262 /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is 179 /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is
263 /// (possibly) changed. 180 /// (possibly) changed.
264 fn sm_chauthtok(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { 181 fn sm_chauthtok(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
265 Err(ErrorCode::Ignore) 182 Err(ErrorCode::Ignore)
266 } 183 }
267 184
268 /// This function is called to terminate a session. 185 /// This function is called to terminate a session.
269 fn sm_close_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { 186 fn sm_close_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
270 Err(ErrorCode::Ignore) 187 Err(ErrorCode::Ignore)
271 } 188 }
272 189
273 /// This function is called to commence a session. 190 /// This function is called to commence a session.
274 fn sm_open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { 191 fn sm_open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
275 Err(ErrorCode::Ignore) 192 Err(ErrorCode::Ignore)
276 } 193 }
277 194
278 /// This function performs the task of altering the credentials of the user with respect to the 195 /// This function performs the task of altering the credentials of the user with respect to the
279 /// corresponding authorization scheme. Generally, an authentication module may have access to more 196 /// corresponding authorization scheme. Generally, an authentication module may have access to more
280 /// information about a user than their authentication token. This function is used to make such 197 /// information about a user than their authentication token. This function is used to make such
281 /// information available to the application. It should only be called after the user has been 198 /// information available to the application. It should only be called after the user has been
282 /// authenticated but before a session has been established. 199 /// authenticated but before a session has been established.
283 fn sm_setcred(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { 200 fn sm_setcred(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
284 Err(ErrorCode::Ignore) 201 Err(ErrorCode::Ignore)
285 } 202 }
286 } 203 }
204
205 /// Generates the dynamic library entry points for a [PamModule] implementation.
206 ///
207 /// Calling `pam_hooks!(SomeType)` on a type that implements [PamModule] will
208 /// generate the exported `extern "C"` functions that PAM uses to call into
209 /// your module.
210 ///
211 /// ## Examples:
212 ///
213 /// Here is full example of a PAM module that would authenticate and authorize everybody:
214 ///
215 /// ```
216 /// use nonstick::{Flags, PamHandle, PamModule, Result as PamResult, pam_hooks};
217 /// use std::ffi::CStr;
218 ///
219 /// # fn main() {}
220 /// struct MyPamModule;
221 /// pam_hooks!(MyPamModule);
222 ///
223 /// impl PamModule for MyPamModule {
224 /// fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
225 /// // You should use a Conversation to communicate with the user
226 /// // instead of writing to the console, but this is just an example.
227 /// eprintln!("Everybody is authorized!");
228 /// Ok(())
229 /// }
230 ///
231 /// fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
232 /// eprintln!("Everybody is authenticated!");
233 /// Ok(())
234 /// }
235 /// }
236 /// ```
237 #[macro_export]
238 macro_rules! pam_hooks {
239 ($ident:ident) => {
240 mod _pam_hooks_scope {
241 use std::ffi::{c_char, c_int, CStr};
242 use $crate::{ErrorCode, Flags, PamModule};
243
244 #[no_mangle]
245 extern "C" fn pam_sm_acct_mgmt(
246 pamh: *mut libc::c_void,
247 flags: Flags,
248 argc: c_int,
249 argv: *const *const c_char,
250 ) -> c_int {
251 let args = extract_argv(argc, argv);
252 ErrorCode::result_to_c(super::$ident::acct_mgmt(&mut pamh.into(), args, flags))
253 }
254
255 #[no_mangle]
256 extern "C" fn pam_sm_authenticate(
257 pamh: *mut libc::c_void,
258 flags: Flags,
259 argc: c_int,
260 argv: *const *const c_char,
261 ) -> c_int {
262 let args = extract_argv(argc, argv);
263 ErrorCode::result_to_c(super::$ident::sm_authenticate(
264 &mut pamh.into(),
265 args,
266 flags,
267 ))
268 }
269
270 #[no_mangle]
271 extern "C" fn pam_sm_chauthtok(
272 pamh: *mut libc::c_void,
273 flags: Flags,
274 argc: c_int,
275 argv: *const *const c_char,
276 ) -> c_int {
277 let args = extract_argv(argc, argv);
278 ErrorCode::result_to_c(super::$ident::sm_chauthtok(&mut pamh.into(), args, flags))
279 }
280
281 #[no_mangle]
282 extern "C" fn pam_sm_close_session(
283 pamh: *mut libc::c_void,
284 flags: Flags,
285 argc: c_int,
286 argv: *const *const c_char,
287 ) -> c_int {
288 let args = extract_argv(argc, argv);
289 ErrorCode::result_to_c(super::$ident::sm_close_session(
290 &mut pamh.into(),
291 args,
292 flags,
293 ))
294 }
295
296 #[no_mangle]
297 extern "C" fn pam_sm_open_session(
298 pamh: *mut libc::c_void,
299 flags: Flags,
300 argc: c_int,
301 argv: *const *const c_char,
302 ) -> c_int {
303 let args = extract_argv(argc, argv);
304 ErrorCode::result_to_c(super::$ident::sm_open_session(
305 &mut pamh.into(),
306 args,
307 flags,
308 ))
309 }
310
311 #[no_mangle]
312 extern "C" fn pam_sm_setcred(
313 pamh: *mut libc::c_void,
314 flags: Flags,
315 argc: c_int,
316 argv: *const *const c_char,
317 ) -> c_int {
318 let args = extract_argv(argc, argv);
319 ErrorCode::result_to_c(super::$ident::sm_setcred(&mut pamh.into(), args, flags))
320 }
321
322 /// Turns `argc`/`argv` into a [Vec] of [CStr]s.
323 ///
324 /// # Safety
325 ///
326 /// We use this only with arguments we get from `libpam`, which we kind of have to trust.
327 fn extract_argv<'a>(argc: c_int, argv: *const *const c_char) -> Vec<&'a CStr> {
328 (0..argc)
329 .map(|o| unsafe { CStr::from_ptr(*argv.offset(o as isize) as *const c_char) })
330 .collect()
331 }
332 }
333 };
334 }
335
336 #[cfg(test)]
337 pub mod test {
338 use crate::module::PamModule;
339
340 struct Foo;
341 impl PamModule for Foo {}
342
343 pam_hooks!(Foo);
344 }