pam/module.rs
1//! Functions for use in pam modules.
2
3use libc::{c_char, c_int};
4use std::ffi::{CStr, CString};
5use std::marker::PhantomData;
6
7use crate::constants::{PamFlag, PamResultCode};
8
9/// Opaque type, used as a pointer when making pam API calls.
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)]
15pub struct PamHandle {
16 _data: [u8; 0],
17 /// Force `!Send + !Sync`.
18 ///
19 /// PAM handles are not thread-safe. From the [man page for `pam(3)`][1]:
20 /// > The libpam interfaces are only thread-safe if each thread within
21 /// > the multithreaded application uses its own PAM handle.
22 ///
23 /// [1]: https://man7.org/linux/man-pages/man3/pam.3.html
24 _marker: PhantomData<*const ()>,
25}
26
27#[link(name = "pam")]
28unsafe extern "C" {
29 fn pam_get_data(
30 pamh: *const PamHandle,
31 module_data_name: *const c_char,
32 data: &mut *const libc::c_void,
33 ) -> c_int;
34
35 fn pam_set_data(
36 pamh: *mut PamHandle,
37 module_data_name: *const c_char,
38 data: *mut libc::c_void,
39 cleanup: extern "C" fn(pamh: *mut PamHandle, data: *mut libc::c_void, error_status: c_int),
40 ) -> c_int;
41
42 fn pam_get_item(
43 pamh: *const PamHandle,
44 item_type: c_int,
45 item: &mut *const libc::c_void,
46 ) -> c_int;
47
48 fn pam_set_item(pamh: *mut PamHandle, item_type: c_int, item: *const libc::c_void) -> c_int;
49
50 fn pam_get_user(pamh: *mut PamHandle, user: &mut *const c_char, prompt: *const c_char)
51 -> c_int;
52}
53
54extern "C" fn cleanup<T>(_: *mut PamHandle, c_data: *mut libc::c_void, _: c_int) {
55 // Defensive null check, PAM shouldn't normally hand us null here
56 if c_data.is_null() {
57 return;
58 }
59 // A panic on Drop for T must not unwind across the C boundary
60 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
61 let _data: Box<T> = Box::from_raw(c_data.cast::<T>());
62 }));
63 // Dropping the above result can itself panic, and is surprisingly difficult to get right.
64 // From the docs for catch_unwind:
65 // > Finally, be careful in how you drop the result of this function. If it is Err, it contains the panic payload, and dropping that may in turn panic!
66 // See: https://internals.rust-lang.org/t/some-thoughts-on-a-less-slippery-catch-unwind/16902/4
67 if let Err(payload) = result {
68 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(payload)))
69 .map_err(std::mem::forget);
70 }
71}
72
73pub type PamResult<T> = Result<T, PamResultCode>;
74
75impl PamHandle {
76 /// Gets some value, identified by `key`, that has been set by the module
77 /// previously.
78 ///
79 /// See `pam_get_data` in
80 /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
81 ///
82 /// # Errors
83 ///
84 /// - [`PamResultCode`] if the lookup itself fails.
85 /// - [`PamResultCode::PAM_BUF_ERR`] if the key string bytes contain an internal 0 byte.
86 /// - [`PamResultCode::PAM_SYSTEM_ERR`] if PAM reports success but yields a null pointer.
87 ///
88 /// # Safety
89 ///
90 /// The data stored under the provided key must be of type `T` otherwise the
91 /// behaviour of this function is undefined.
92 pub unsafe fn get_data<'a, T>(&'a self, key: &str) -> PamResult<&'a T> {
93 let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_BUF_ERR)?;
94 let mut ptr: *const libc::c_void = std::ptr::null();
95 let res = PamResultCode::from_raw(unsafe { pam_get_data(self, c_key.as_ptr(), &mut ptr) });
96 if PamResultCode::PAM_SUCCESS != res {
97 return Err(res);
98 }
99 if ptr.is_null() {
100 return Err(PamResultCode::PAM_SYSTEM_ERR);
101 }
102 let typed_ptr = ptr.cast::<T>();
103 let data: &T = unsafe { &*typed_ptr };
104 Ok(data)
105 }
106
107 /// Stores a value that can be retrieved later with `get_data`. The value lives
108 /// as long as the current pam cycle.
109 ///
110 /// See `pam_set_data` in
111 /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
112 ///
113 /// # Errors
114 ///
115 /// - [`PamResultCode`] if the store itself fails.
116 /// - [`PamResultCode::PAM_BUF_ERR`] if the key string contains a 0 byte.
117 pub fn set_data<T: 'static>(&mut self, key: &str, data: Box<T>) -> PamResult<()> {
118 let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_BUF_ERR)?;
119 let ptr = Box::into_raw(data);
120 let res = PamResultCode::from_raw(unsafe {
121 pam_set_data(
122 self,
123 c_key.as_ptr(),
124 ptr.cast::<libc::c_void>(),
125 cleanup::<T>,
126 )
127 });
128 if PamResultCode::PAM_SUCCESS == res {
129 Ok(())
130 } else {
131 drop(unsafe { Box::from_raw(ptr) });
132 Err(res)
133 }
134 }
135
136 /// Retrieves a value that has been set, possibly by the pam client. This is
137 /// particularly useful for getting a `PamConv` reference.
138 ///
139 /// See `pam_get_item` in
140 /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
141 ///
142 /// # Errors
143 ///
144 /// Returns an error if the underlying PAM function call fails.
145 pub fn get_item<'a, T: crate::items::Item<'a>>(&'a self) -> PamResult<Option<T>> {
146 let mut ptr: *const libc::c_void = std::ptr::null();
147 let res =
148 PamResultCode::from_raw(unsafe { pam_get_item(self, T::type_id() as c_int, &mut ptr) });
149 if PamResultCode::PAM_SUCCESS != res {
150 return Err(res);
151 }
152 let typed_ptr = ptr.cast::<T::Raw>();
153 if typed_ptr.is_null() {
154 Ok(None)
155 } else {
156 Ok(Some(unsafe { T::from_raw(typed_ptr) }))
157 }
158 }
159
160 /// Sets a value in the pam context. The value can be retrieved using
161 /// `get_item`.
162 ///
163 /// Note that all items are strings, except `PAM_CONV` and `PAM_FAIL_DELAY`.
164 ///
165 /// See `pam_set_item` in
166 /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
167 ///
168 /// # Errors
169 ///
170 /// Returns an error if the underlying PAM function call fails.
171 pub fn set_item_str<'a, T: crate::items::Item<'a>>(&mut self, item: T) -> PamResult<()> {
172 let res = PamResultCode::from_raw(unsafe {
173 pam_set_item(
174 self,
175 T::type_id() as c_int,
176 item.into_raw().cast::<libc::c_void>(),
177 )
178 });
179 if PamResultCode::PAM_SUCCESS == res {
180 Ok(())
181 } else {
182 Err(res)
183 }
184 }
185
186 /// Retrieves the name of the user who is authenticating or logging in.
187 ///
188 /// This is really a specialization of `get_item`.
189 ///
190 /// This may prompt via the conversation and set the `PAM_USER` item.
191 /// It therefore takes `&mut self`, unlike the read-only item accessors.
192 ///
193 /// See `pam_get_user` in
194 /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
195 ///
196 /// # Errors
197 ///
198 /// - [`PamResultCode`] if the lookup itself fails.
199 /// - [`PamResultCode::PAM_BUF_ERR`] if the prompt string contains a 0 byte.
200 /// - [`PamResultCode::PAM_SYSTEM_ERR`] if PAM reports success but yields a null pointer.
201 /// - [`PamResultCode::PAM_SYSTEM_ERR`] if the returned username is not valid UTF-8.
202 pub fn get_user(&mut self, prompt: Option<&str>) -> PamResult<String> {
203 let mut ptr: *const c_char = std::ptr::null();
204 let prompt_string = prompt
205 .map(CString::new)
206 .transpose()
207 .map_err(|_| PamResultCode::PAM_BUF_ERR)?;
208 let c_prompt = prompt_string
209 .as_ref()
210 .map_or(std::ptr::null(), |s| s.as_ptr());
211 let res = PamResultCode::from_raw(unsafe { pam_get_user(self, &mut ptr, c_prompt) });
212 if PamResultCode::PAM_SUCCESS != res {
213 return Err(res);
214 }
215 if ptr.is_null() {
216 return Err(PamResultCode::PAM_SYSTEM_ERR);
217 }
218 let bytes = unsafe { CStr::from_ptr(ptr).to_bytes() };
219 String::from_utf8(bytes.to_vec()).map_err(|_| PamResultCode::PAM_SYSTEM_ERR)
220 }
221}
222
223/// Provides functions that are invoked by the entrypoints generated by the
224/// [`pam_hooks!` macro](../macro.pam_hooks.html).
225///
226/// All of hooks are ignored by PAM dispatch by default given the default return value of `PAM_IGNORE`.
227/// Override any functions that you want to handle with your module. See `man pam(3)`.
228#[allow(unused_variables)]
229pub trait PamHooks {
230 /// This function performs the task of establishing whether the user is permitted to gain access at
231 /// this time. It should be understood that the user has previously been validated by an
232 /// authentication module. This function checks for other things. Such things might be: the time of
233 /// day or the date, the terminal line, remote hostname, etc. This function may also determine
234 /// things like the expiration on passwords, and respond that the user change it before continuing.
235 fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
236 PamResultCode::PAM_IGNORE
237 }
238
239 /// This function performs the task of authenticating the user.
240 fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
241 PamResultCode::PAM_IGNORE
242 }
243
244 /// This function is used to (re-)set the authentication token of the user.
245 ///
246 /// The PAM library calls this function twice in succession. The first time with
247 /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with
248 /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is
249 /// (possibly) changed.
250 fn sm_chauthtok(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
251 PamResultCode::PAM_IGNORE
252 }
253
254 /// This function is called to terminate a session.
255 fn sm_close_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
256 PamResultCode::PAM_IGNORE
257 }
258
259 /// This function is called to commence a session.
260 fn sm_open_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
261 PamResultCode::PAM_IGNORE
262 }
263
264 /// This function performs the task of altering the credentials of the user with respect to the
265 /// corresponding authorization scheme. Generally, an authentication module may have access to more
266 /// information about a user than their authentication token. This function is used to make such
267 /// information available to the application. It should only be called after the user has been
268 /// authenticated but before a session has been established.
269 fn sm_setcred(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
270 PamResultCode::PAM_IGNORE
271 }
272}
273
274#[cfg(test)]
275#[allow(clippy::panic)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn cleanup_disaster_scenarios() {
281 // PAM is not expected to call cleanup with null data, but we must handle it regardless
282 cleanup::<String>(std::ptr::null_mut(), std::ptr::null_mut(), 0);
283
284 // Scenario 1
285 // T::drop panics
286 {
287 struct Bomb;
288 impl Drop for Bomb {
289 fn drop(&mut self) {
290 panic!();
291 }
292 }
293 let ptr = Box::into_raw(Box::new(Bomb)).cast::<libc::c_void>();
294 cleanup::<Bomb>(std::ptr::null_mut(), ptr, 0);
295 }
296
297 // Scenario 2
298 // Every payload's Drop spawns another panic
299 {
300 struct BombRecursive;
301 impl Drop for BombRecursive {
302 fn drop(&mut self) {
303 std::panic::panic_any(Self);
304 }
305 }
306 let ptr = Box::into_raw(Box::new(BombRecursive)).cast::<libc::c_void>();
307 cleanup::<BombRecursive>(std::ptr::null_mut(), ptr, 0);
308 }
309 }
310}