Skip to main content

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}