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}