pam_kanidm/pam/
module.rs

1//! Functions for use in pam modules.
2
3use std::ffi::{CStr, CString};
4use std::{mem, ptr};
5
6use libc::c_char;
7
8use crate::pam::constants::{
9    PamFlag, PamItemType, PamResultCode, PAM_PROMPT_ECHO_OFF, PAM_TEXT_INFO,
10};
11use crate::pam::conv::PamConv;
12use crate::pam::items::{PamAuthTok, PamRHost, PamService, PamTty};
13
14use crate::core::PamHandler;
15use kanidm_unix_common::unix_proto::DeviceAuthorizationResponse;
16use kanidm_unix_common::unix_proto::PamServiceInfo;
17
18/// Opaque type, used as a pointer when making pam API calls.
19///
20/// A module is invoked via an external function such as `pam_sm_authenticate`.
21/// Such a call provides a pam handle pointer.  The same pointer should be given
22/// as an argument when making API calls.
23#[allow(missing_copy_implementations)]
24pub enum PamHandle {}
25
26#[allow(missing_copy_implementations)]
27enum PamItemT {}
28
29#[allow(missing_copy_implementations)]
30pub enum PamDataT {}
31
32#[link(name = "pam")]
33extern "C" {
34    /*
35    fn pam_get_data(
36        pamh: *const PamHandle,
37        module_data_name: *const c_char,
38        data: &mut *const PamDataT,
39    ) -> PamResultCode;
40
41    fn pam_set_data(
42        pamh: *const PamHandle,
43        module_data_name: *const c_char,
44        data: *mut PamDataT,
45        cleanup: unsafe extern "C" fn(
46            pamh: *const PamHandle,
47            data: *mut PamDataT,
48            error_status: PamResultCode,
49        ),
50    ) -> PamResultCode;
51    */
52
53    fn pam_get_item(
54        pamh: *const PamHandle,
55        item_type: PamItemType,
56        item: &mut *const PamItemT,
57    ) -> PamResultCode;
58
59    /*
60    fn pam_set_item(pamh: *mut PamHandle, item_type: PamItemType, item: &PamItemT)
61        -> PamResultCode;
62    */
63
64    fn pam_get_user(
65        pamh: *const PamHandle,
66        user: &mut *const c_char,
67        prompt: *const c_char,
68    ) -> PamResultCode;
69}
70
71/// # Safety
72///
73/// We're doing what we can for this one, but it's FFI.
74pub unsafe extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut PamDataT, _: PamResultCode) {
75    let c_data = Box::from_raw(c_data);
76    let data: Box<T> = mem::transmute(c_data);
77    mem::drop(data);
78}
79
80pub type PamResult<T> = Result<T, PamResultCode>;
81
82/// # Safety
83///
84/// Type-level mapping for safely retrieving values with `get_item`.
85///
86/// See `pam_get_item` in
87/// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
88pub trait PamItem {
89    /// Maps a Rust type to a pam constant.
90    ///
91    /// For example, the type PamConv maps to the constant PAM_CONV.  The pam
92    /// API contract specifies that when the API function `pam_get_item` is
93    /// called with the constant PAM_CONV, it will return a value of type
94    /// `PamConv`.
95    fn item_type() -> PamItemType;
96}
97
98impl PamHandle {
99    /*
100    /// # Safety
101    ///
102    /// Gets some value, identified by `key`, that has been set by the module
103    /// previously.
104    ///
105    /// See `pam_get_data` in
106    /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
107    unsafe fn get_data<'a, T>(&'a self, key: &str) -> PamResult<&'a T> {
108        let c_key = CString::new(key).unwrap();
109        let mut ptr: *const PamDataT = ptr::null();
110        let res = pam_get_data(self, c_key.as_ptr(), &mut ptr);
111        if PamResultCode::PAM_SUCCESS == res && !ptr.is_null() {
112            let typed_ptr: *const T = ptr as *const T;
113            let data: &T = &*typed_ptr;
114            Ok(data)
115        } else {
116            Err(res)
117        }
118    }
119
120    /// Stores a value that can be retrieved later with `get_data`.  The value lives
121    /// as long as the current pam cycle.
122    ///
123    /// See `pam_set_data` in
124    /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
125    fn set_data<T>(&self, key: &str, data: Box<T>) -> PamResult<()> {
126        let c_key = CString::new(key).unwrap();
127        let res = unsafe {
128            let c_data: Box<PamDataT> = mem::transmute(data);
129            let c_data = Box::into_raw(c_data);
130            pam_set_data(self, c_key.as_ptr(), c_data, cleanup::<T>)
131        };
132        if PamResultCode::PAM_SUCCESS == res {
133            Ok(())
134        } else {
135            Err(res)
136        }
137    }
138    */
139
140    /// Retrieves a value that has been set, possibly by the pam client.  This is
141    /// particularly useful for getting a `PamConv` reference.
142    ///
143    /// See `pam_get_item` in
144    /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
145    fn get_item<'a, T: PamItem>(&self) -> PamResult<&'a T> {
146        let mut ptr: *const PamItemT = ptr::null();
147        let (res, item) = unsafe {
148            let r = pam_get_item(self, T::item_type(), &mut ptr);
149            let typed_ptr: *const T = ptr as *const T;
150            let t: &T = &*typed_ptr;
151            (r, t)
152        };
153        if PamResultCode::PAM_SUCCESS == res {
154            Ok(item)
155        } else {
156            Err(res)
157        }
158    }
159
160    fn get_item_string<T: PamItem>(&self) -> PamResult<Option<String>> {
161        let mut ptr: *const PamItemT = ptr::null();
162        let (res, item) = unsafe {
163            let r = pam_get_item(self, T::item_type(), &mut ptr);
164            let t = if PamResultCode::PAM_SUCCESS == r && !ptr.is_null() {
165                let typed_ptr: *const c_char = ptr as *const c_char;
166                Some(CStr::from_ptr(typed_ptr).to_string_lossy().into_owned())
167            } else {
168                None
169            };
170            (r, t)
171        };
172        if PamResultCode::PAM_SUCCESS == res {
173            Ok(item)
174        } else {
175            Err(res)
176        }
177    }
178
179    /*
180    /// Sets a value in the pam context. The value can be retrieved using
181    /// `get_item`.
182    ///
183    /// Note that all items are strings, except `PAM_CONV` and `PAM_FAIL_DELAY`.
184    ///
185    /// See `pam_set_item` in
186    /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
187    fn set_item_str<T: PamItem>(&mut self, item: &str) -> PamResult<()> {
188        let c_item = CString::new(item).unwrap();
189
190        let res = unsafe {
191            pam_set_item(
192                self,
193                T::item_type(),
194                // unwrapping is okay here, as c_item will not be a NULL
195                // pointer
196                (c_item.as_ptr() as *const PamItemT).as_ref().unwrap(),
197            )
198        };
199        if PamResultCode::PAM_SUCCESS == res {
200            Ok(())
201        } else {
202            Err(res)
203        }
204    }
205    */
206
207    /// Retrieves the name of the user who is authenticating or logging in.
208    ///
209    /// This is really a specialization of `get_item`.
210    ///
211    /// See `pam_get_user` in
212    /// <http://www.linux-pam.org/Linux-PAM-html/mwg-expected-by-module-item.html>
213    fn get_user(&self, prompt: Option<&str>) -> PamResult<String> {
214        let mut ptr: *const c_char = ptr::null_mut();
215        let res = match prompt {
216            Some(p) => {
217                let c_prompt = CString::new(p).map_err(|_| PamResultCode::PAM_CONV_ERR)?;
218                unsafe { pam_get_user(self, &mut ptr, c_prompt.as_ptr()) }
219            }
220            None => unsafe { pam_get_user(self, &mut ptr, ptr::null()) },
221        };
222
223        if PamResultCode::PAM_SUCCESS == res {
224            if ptr.is_null() {
225                Err(PamResultCode::PAM_AUTHINFO_UNAVAIL)
226            } else {
227                let bytes = unsafe { CStr::from_ptr(ptr).to_bytes() };
228                String::from_utf8(bytes.to_vec()).map_err(|_| PamResultCode::PAM_CONV_ERR)
229            }
230        } else {
231            Err(res)
232        }
233    }
234
235    fn get_authtok(&self) -> PamResult<Option<String>> {
236        self.get_item_string::<PamAuthTok>()
237    }
238
239    fn get_tty(&self) -> PamResult<Option<String>> {
240        self.get_item_string::<PamTty>()
241    }
242
243    fn get_rhost(&self) -> PamResult<Option<String>> {
244        self.get_item_string::<PamRHost>()
245    }
246
247    fn get_service(&self) -> PamResult<Option<String>> {
248        self.get_item_string::<PamService>()
249    }
250
251    fn get_pam_info(&self) -> PamResult<PamServiceInfo> {
252        let maybe_tty = self.get_tty()?;
253        let maybe_rhost = self.get_rhost()?;
254        let maybe_service = self.get_service()?;
255
256        tracing::debug!(?maybe_tty, ?maybe_rhost, ?maybe_service);
257
258        match (maybe_tty, maybe_rhost, maybe_service) {
259            (tty, rhost, Some(service)) => Ok(PamServiceInfo {
260                service,
261                tty,
262                rhost,
263            }),
264            _ => Err(PamResultCode::PAM_CONV_ERR),
265        }
266    }
267
268    fn get_conv(&self) -> PamResult<&PamConv> {
269        self.get_item::<PamConv>()
270    }
271}
272
273impl PamHandler for PamHandle {
274    fn account_id(&self) -> PamResult<String> {
275        self.get_user(None)
276    }
277
278    fn service_info(&self) -> PamResult<PamServiceInfo> {
279        self.get_pam_info()
280    }
281
282    fn authtok(&self) -> PamResult<Option<String>> {
283        self.get_authtok()
284    }
285
286    fn message(&self, msg: &str) -> PamResult<()> {
287        let conv = self.get_conv()?;
288        conv.send(PAM_TEXT_INFO, msg).map(|_| ())
289    }
290
291    fn prompt_for_password(&self) -> PamResult<Option<String>> {
292        let conv = self.get_conv()?;
293        conv.send(PAM_PROMPT_ECHO_OFF, "Password: ")
294    }
295
296    fn prompt_for_mfacode(&self) -> PamResult<Option<String>> {
297        let conv = self.get_conv()?;
298        conv.send(PAM_PROMPT_ECHO_OFF, "Code: ")
299    }
300
301    fn prompt_for_pin(&self, msg: Option<&str>) -> PamResult<Option<String>> {
302        let conv = self.get_conv()?;
303        let msg = msg.unwrap_or("PIN: ");
304        conv.send(PAM_PROMPT_ECHO_OFF, msg)
305    }
306
307    fn message_device_grant(&self, data: &DeviceAuthorizationResponse) -> PamResult<()> {
308        let conv = self.get_conv()?;
309        let msg = match &data.message {
310            Some(msg) => msg.clone(),
311            None => format!(
312                "Using a browser on another device, visit:\n{}\nAnd enter the code:\n{}",
313                data.verification_uri, data.user_code
314            ),
315        };
316        conv.send(PAM_TEXT_INFO, &msg).map(|_| ())
317    }
318}
319
320/// Provides functions that are invoked by the entrypoints generated by the
321/// [`pam_hooks!` macro](../macro.pam_hooks.html).
322///
323/// All of hooks are ignored by PAM dispatch by default given the default return value of `PAM_IGNORE`.
324/// Override any functions that you want to handle with your module. See `man pam(3)`.
325#[allow(unused_variables)]
326pub trait PamHooks {
327    /// This function performs the task of establishing whether the user is permitted to gain access at
328    /// this time. It should be understood that the user has previously been validated by an
329    /// authentication module. This function checks for other things. Such things might be: the time of
330    /// day or the date, the terminal line, remote hostname, etc. This function may also determine
331    /// things like the expiration on passwords, and respond that the user change it before continuing.
332    fn acct_mgmt(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
333        PamResultCode::PAM_IGNORE
334    }
335
336    /// This function performs the task of authenticating the user.
337    fn sm_authenticate(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
338        PamResultCode::PAM_IGNORE
339    }
340
341    /// This function is used to (re-)set the authentication token of the user.
342    ///
343    /// The PAM library calls this function twice in succession. The first time with
344    /// PAM_PRELIM_CHECK and then, if the module does not return PAM_TRY_AGAIN, subsequently with
345    /// PAM_UPDATE_AUTHTOK. It is only on the second call that the authorization token is
346    /// (possibly) changed.
347    fn sm_chauthtok(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
348        PamResultCode::PAM_IGNORE
349    }
350
351    /// This function is called to terminate a session.
352    fn sm_close_session(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
353        PamResultCode::PAM_IGNORE
354    }
355
356    /// This function is called to commence a session.
357    fn sm_open_session(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
358        PamResultCode::PAM_IGNORE
359    }
360
361    /// This function performs the task of altering the credentials of the user with respect to the
362    /// corresponding authorization scheme. Generally, an authentication module may have access to more
363    /// information about a user than their authentication token. This function is used to make such
364    /// information available to the application. It should only be called after the user has been
365    /// authenticated but before a session has been established.
366    fn sm_setcred(pamh: &PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
367        PamResultCode::PAM_IGNORE
368    }
369}