Skip to main content

pam_client2/
context.rs

1//! PAM context and related helpers
2
3/***********************************************************************
4 * (c) 2021-2022 Christoph Grenz <christophg+gitorious @ grenz-bonn.de>*
5 *                                                                     *
6 * This Source Code Form is subject to the terms of the Mozilla Public *
7 * License, v. 2.0. If a copy of the MPL was not distributed with this *
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.            *
9 ***********************************************************************/
10
11use crate::env_list::EnvList;
12use crate::error::{Error, ErrorCode};
13use crate::ffi::{from_pam_conv, into_pam_conv};
14use crate::session::{Session, SessionToken};
15use crate::{char_ptr_to_str, ConversationHandler};
16extern crate libc;
17extern crate pam_sys2 as pam_sys;
18
19use crate::{ExtResult, Flag, Result, PAM_SUCCESS};
20
21use libc::{c_char, c_int, c_void};
22use pam_sys::pam_conv as PamConversation;
23use pam_sys::pam_handle_t as RawPamHandle;
24use pam_sys::{
25	pam_acct_mgmt, pam_authenticate, pam_chauthtok, pam_close_session, pam_end, pam_get_item,
26	pam_getenv, pam_getenvlist, pam_open_session, pam_putenv, pam_set_item, pam_setcred, pam_start,
27};
28use std::cell::Cell;
29use std::ffi::{CStr, CString, OsStr};
30use std::marker::PhantomData;
31use std::mem::ManuallyDrop;
32use std::os::unix::ffi::OsStrExt;
33use std::ptr::NonNull;
34use std::{ptr, slice};
35
36/// Internal: Builds getters/setters for string-typed PAM items.
37macro_rules! impl_pam_str_item {
38	($name:ident, $set_name:ident, $item_type:expr$(, $doc:literal$(, $extdoc:literal)?)?$(,)?) => {
39		$(#[doc = "Returns "]#[doc = $doc]$(#[doc = "\n\n"]#[doc = $extdoc])?)?
40		pub fn $name(&self) -> Result<String> {
41			let ptr = self.get_item($item_type as c_int)?;
42			if ptr.is_null() {
43				return Err(Error::new(self.handle(), ErrorCode::PERM_DENIED));
44			}
45			let string = unsafe { CStr::from_ptr(ptr.cast()) }.to_string_lossy().into_owned();
46			return Ok(string);
47		}
48
49		$(#[doc = "Sets "]#[doc = $doc])?
50		pub fn $set_name(&mut self, value: Option<&str>) -> Result<()> {
51			match value {
52				None => unsafe { self.set_item($item_type as c_int, ptr::null()) },
53				Some(string) => {
54					let cstring = CString::new(string).map_err(|_| Error::new(self.handle(), ErrorCode::BUF_ERR))?;
55					unsafe { self.set_item($item_type as c_int, cstring.as_ptr().cast()) }
56				}
57			}
58		}
59	}
60}
61
62#[derive(Debug, Clone, Copy)]
63pub(crate) struct PamHandle(NonNull<RawPamHandle>);
64
65/// PAM handle wrapper
66impl PamHandle {
67	/// Create a PAM handle from a raw handle pointer.
68	///
69	/// # Safety
70	/// The argument `ptr` must be NULL or a valid non-dangling PAM handle created by `pam_start`.
71	#[inline]
72	pub unsafe fn new(ptr: *mut RawPamHandle) -> Option<Self> {
73		NonNull::new(ptr).map(Self)
74	}
75
76	#[inline]
77	pub const fn as_ptr(self) -> *mut RawPamHandle {
78		self.0.as_ptr()
79	}
80}
81
82impl From<PamHandle> for *mut RawPamHandle {
83	#[inline]
84	fn from(handle: PamHandle) -> Self {
85		handle.as_ptr()
86	}
87}
88
89impl From<PamHandle> for *const RawPamHandle {
90	#[inline]
91	fn from(handle: PamHandle) -> Self {
92		handle.as_ptr()
93	}
94}
95
96/// Special struct for the `PAM_XAUTHDATA` pam item
97///
98/// Uses const pointers as `pam_set_item` makes a copy of the data and
99/// never mutates through the pointers and `pam_get_item` by API contract
100/// states that returned data should not be modified.
101#[cfg(any(target_os = "linux", doc))]
102#[repr(C)]
103#[derive(Debug)]
104struct XAuthData {
105	/// Length of `name` in bytes excluding the trailing NUL
106	pub namelen: c_int,
107	/// Name of the authentication method as a null terminated string
108	pub name: *const c_char,
109	/// Length of `data` in bytes
110	pub datalen: c_int,
111	/// Authentication method-specific data
112	pub data: *const c_char,
113}
114
115/// Main struct for PAM interaction
116///
117/// Manages a PAM context holding the transaction state.
118///
119/// See the [crate documentation][`crate`] for examples.
120pub struct Context<ConvT> {
121	handle: PamHandle,
122	last_status: Cell<c_int>,
123	_conversation: PhantomData<ConvT>,
124}
125
126impl<ConvT> Context<ConvT>
127where
128	ConvT: ConversationHandler,
129{
130	/// Creates a PAM context and starts a PAM transaction.
131	///
132	/// # Parameters
133	/// - `service` – Name of the service. The policy for the service will be
134	///   read from the file /etc/pam.d/*service_name*, falling back to
135	///   /etc/pam.conf.
136	/// - `username` – Name of the target user. If `None`, the user will be
137	///   asked through the conversation handler if neccessary.
138	/// - `conversation` – A conversation handler through which the user can be
139	///   asked for his username, password, etc. Use
140	///   [`conv_cli::Conversation`][`crate::conv_cli::Conversation`] for a command line
141	///   default implementation, [`conv_mock::Conversation`][`crate::conv_mock::Conversation`]
142	///   for fixed credentials or implement the [`ConversationHandler`] trait
143	///   for custom behaviour.
144	///
145	/// # Errors
146	/// Expected error codes include:
147	/// - `ErrorCode::ABORT` – General failure
148	/// - `ErrorCode::BUF_ERR` – Memory allocation failure or null byte in
149	///   parameter.
150	/// - `ErrorCode::SYSTEM_ERR` – Other system error
151	#[rustversion::attr(since(1.48), doc(alias = "pam_start"))]
152	pub fn new(service: &str, username: Option<&str>, conversation: ConvT) -> Result<Self> {
153		// Wrap `conversation` in a box and delegate to `from_boxed_conv`
154		Self::from_boxed_conv(service, username, Box::new(conversation))
155	}
156
157	/// Creates a PAM context and starts a PAM transaction taking a boxed
158	/// conversation handler.
159	///
160	/// See [`new()`][`Self::new()`] for details.
161	pub fn from_boxed_conv(
162		service: &str,
163		username: Option<&str>,
164		boxed_conv: Box<ConvT>,
165	) -> Result<Self> {
166		let mut handle: *mut RawPamHandle = ptr::null_mut();
167
168		let c_service = CString::new(service).map_err(|_| Error::from(ErrorCode::BUF_ERR))?;
169		let c_username = match username {
170			None => None,
171			Some(name) => Some(CString::new(name).map_err(|_| Error::from(ErrorCode::BUF_ERR))?),
172		};
173
174		// Create callback struct for C code
175		let pam_conv = into_pam_conv(boxed_conv);
176
177		// Start the PAM context
178		match unsafe {
179			pam_start(
180				c_service.as_ptr(),
181				c_username.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
182				&pam_conv,
183				&mut handle,
184			)
185		} {
186			PAM_SUCCESS => {
187				// A null pointer should never happen on PAM_SUCCESS, but we need to check to make sure.
188				// Safety: The handle came from `pam_start` so it must be valid.
189				let handle = unsafe { PamHandle::new(handle) }
190					.ok_or_else(|| Error::from(ErrorCode::ABORT))?;
191				let mut result = Self {
192					handle,
193					last_status: Cell::new(PAM_SUCCESS),
194					_conversation: PhantomData,
195				};
196				// Initialize the conversation handler
197				result.conversation_mut().init(username);
198				Ok(result)
199			}
200			code => Err(ErrorCode::from_repr(code)
201				.unwrap_or(ErrorCode::ABORT)
202				.into()),
203		}
204	}
205
206	/// Authenticates a user.
207	///
208	/// The conversation handler may be called to ask the user for their name
209	/// (especially if no initial username was provided), their password and
210	/// possibly other tokens if e.g. two-factor authentication is required.
211	/// Conversely the conversation handler may not be called if authentication
212	/// is handled by other means, e.g. a fingerprint scanner.
213	///
214	/// Relevant `flags` are [`Flag::NONE`], [`Flag::SILENT`] and
215	/// [`Flag::DISALLOW_NULL_AUTHTOK`] (don't authenticate empty
216	/// passwords).
217	///
218	/// # Errors
219	/// Expected error codes include:
220	/// - `ABORT` – Serious failure; the application should exit.
221	/// - `AUTH_ERR` – The user was not authenticated.
222	/// - `CRED_INSUFFICIENT` – The application does not have sufficient
223	///   credentials to authenticate the user.
224	/// - `AUTHINFO_UNAVAIL` – Could not retrieve authentication information
225	///   due to e.g. network failure.
226	/// - `MAXTRIES` – At least one module reached its retry limit. Do not
227	/// - try again.
228	/// - `USER_UNKNOWN` – User not known.
229	/// - `INCOMPLETE` – The conversation handler returned `CONV_AGAIN`. Call
230	///   again after the asynchronous conversation finished.
231	#[rustversion::attr(since(1.48), doc(alias = "pam_authenticate"))]
232	pub fn authenticate(&mut self, flags: Flag) -> Result<()> {
233		self.wrap_pam_return(unsafe { pam_authenticate(self.handle().into(), flags.bits()) })
234	}
235
236	/// Validates user account authorization.
237	///
238	/// Determines if the account is valid, not expired, and verifies other
239	/// access restrictions. Usually used directly after authentication.
240	/// The conversation handler may be called by some PAM module.
241	///
242	/// Relevant `flags` are [`Flag::NONE`], [`Flag::SILENT`] and
243	/// [`Flag::DISALLOW_NULL_AUTHTOK`] (demand password change on empty
244	/// passwords).
245	///
246	/// # Errors
247	/// Expected error codes include:
248	/// - `ACCT_EXPIRED` – Account has expired.
249	/// - `AUTH_ERR` – Authentication failure.
250	/// - `NEW_AUTHTOK_REQD` – Password has expired. Use [`chauthtok()`] to let
251	///   the user change their password or abort.
252	/// - `PERM_DENIED` – Permission denied
253	/// - `USER_UNKNOWN` – User not known
254	/// - `INCOMPLETE` – The conversation handler returned `CONV_AGAIN`. Call
255	///   again after the asynchronous conversation finished.
256	///
257	/// [`chauthtok()`]: `Self::chauthtok`
258	#[rustversion::attr(since(1.48), doc(alias = "pam_acct_mgmt"))]
259	pub fn acct_mgmt(&mut self, flags: Flag) -> Result<()> {
260		self.wrap_pam_return(unsafe { pam_acct_mgmt(self.handle().into(), flags.bits()) })
261	}
262
263	/// Fully reinitializes the user's credentials (if established).
264	///
265	/// Reinitializes credentials like Kerberos tokens for when a session
266	/// is already managed by another process. This is e.g. used in
267	/// lockscreen applications to refresh the credentials of the desktop
268	/// session.
269	///
270	/// Relevant `flags` are [`Flag::NONE`] and [`Flag::SILENT`].
271	///
272	/// # Errors
273	/// Expected error codes include:
274	/// - `BUF_ERR` – Memory allocation error
275	/// - `CRED_ERR` – Setting credentials failed
276	/// - `CRED_UNAVAIL` – Failed to retrieve credentials
277	/// - `SYSTEM_ERR` – Other system error
278	/// - `USER_UNKNOWN` – User not known
279	pub fn reinitialize_credentials(&mut self, flags: Flag) -> Result<()> {
280		self.wrap_pam_return(unsafe {
281			pam_setcred(
282				self.handle().into(),
283				(Flag::REINITIALIZE_CRED | flags).bits(),
284			)
285		})
286	}
287
288	/// Changes a users password.
289	///
290	/// The conversation handler will be used to request the new password
291	/// and might query for the old one.
292	///
293	/// Relevant `flags` are [`Flag::NONE`], [`Flag::SILENT`] and
294	/// [`Flag::CHANGE_EXPIRED_AUTHTOK`] (only initiate change for
295	/// expired passwords).
296	///
297	/// # Errors
298	/// Expected error codes include:
299	/// - `AUTHTOK_ERR` – Unable to obtain the new password
300	/// - `AUTHTOK_RECOVERY_ERR` – Unable to obtain the old password
301	/// - `AUTHTOK_LOCK_BUSY` – Authentication token is currently locked
302	/// - `AUTHTOK_DISABLE_AGING` – Password aging is disabled (password may
303	///   be unchangeable in at least one module)
304	/// - `PERM_DENIED` – Permission denied
305	/// - `TRY_AGAIN` – Not all modules were able to prepare an authentication
306	///   token update. Nothing was changed.
307	/// - `USER_UNKNOWN` – User not known
308	/// - `INCOMPLETE` – The conversation handler returned `CONV_AGAIN`. Call
309	///   again after the asynchronous conversation finished.
310	#[rustversion::attr(since(1.48), doc(alias = "pam_chauthtok"))]
311	pub fn chauthtok(&mut self, flags: Flag) -> Result<()> {
312		self.wrap_pam_return(unsafe { pam_chauthtok(self.handle().into(), flags.bits()) })
313	}
314
315	/// Sets up a user session.
316	///
317	/// Establishes user credentials and performs various tasks to prepare
318	/// a login session, may create the home directory on first login, mount
319	/// user-specific directories, log access times, etc. The application
320	/// must usually have sufficient privileges to perform this task (e.g.
321	/// have EUID 0). The returned [`Session`] object closes the session and
322	/// deletes the established credentials on drop.
323	///
324	/// The user should already be [authenticated] and [authorized] at this
325	/// point, but this isn't enforced or strictly neccessary if the user
326	/// was authenticated by other means. In that case the conversation
327	/// handler might be called by e.g. crypt-mount modules to get a password.
328	///
329	/// Relevant `flags` are [`Flag::NONE`] and [`Flag::SILENT`].
330	///
331	/// # Errors
332	/// Expected error codes include:
333	/// - `ABORT` – Serious failure; the application should exit
334	/// - `BUF_ERR` – Memory allocation error
335	/// - `SESSION_ERR` – Some session initialization failed
336	/// - `CRED_ERR` – Setting credentials failed
337	/// - `CRED_UNAVAIL` – Failed to retrieve credentials
338	/// - `SYSTEM_ERR` – Other system error
339	/// - `USER_UNKNOWN` – User not known
340	/// - `INCOMPLETE` – The conversation handler returned `CONV_AGAIN`. Call
341	///   again after the asynchronous conversation finished.
342	///
343	/// [authenticated]: `Self::authenticate()`
344	/// [authorized]: `Self::acct_mgmt()`
345	#[rustversion::attr(since(1.48), doc(alias = "pam_open_session"))]
346	pub fn open_session(&mut self, flags: Flag) -> Result<Session<'_, ConvT>> {
347		let bits = flags.bits();
348		let handle = self.handle().as_ptr();
349		self.wrap_pam_return(unsafe {
350			pam_setcred(handle, (Flag::ESTABLISH_CRED | flags).bits())
351		})?;
352
353		if let Err(e) = self.wrap_pam_return(unsafe { pam_open_session(handle, bits) }) {
354			let _ = self.wrap_pam_return(unsafe {
355				pam_setcred(handle, (Flag::DELETE_CRED | flags).bits())
356			});
357			return Err(e);
358		}
359
360		// Reinitialize credentials after session opening. With this we try
361		// to circumvent different assumptions of PAM modules about when
362		// `setcred` is called, as the documentations of different PAM
363		// implementations differ. (OpenSSH does something similar too).
364		if let Err(e) = self.wrap_pam_return(unsafe {
365			pam_setcred(handle, (Flag::REINITIALIZE_CRED | flags).bits())
366		}) {
367			let _ = self.wrap_pam_return(unsafe { pam_close_session(handle, bits) });
368			let _ = self.wrap_pam_return(unsafe {
369				pam_setcred(handle, (Flag::DELETE_CRED | flags).bits())
370			});
371			return Err(e);
372		}
373
374		Ok(Session::new(self, true))
375	}
376
377	/// Maintains user credentials but don't set up a full user session.
378	///
379	/// Establishes user credentials and returns a [`Session`] object that
380	/// deletes the credentials on drop. It doesn't open a PAM session.
381	///
382	/// The user should already be [authenticated] and [authorized] at this
383	/// point, but this isn't enforced or strictly neccessary.
384	///
385	/// Depending on the platform this use case may not be fully supported.
386	///
387	/// Relevant `flags` are [`Flag::NONE`] and [`Flag::SILENT`].
388	///
389	/// # Errors
390	/// Expected error codes include:
391	/// - `BUF_ERR` – Memory allocation error
392	/// - `CRED_ERR` – Setting credentials failed
393	/// - `CRED_UNAVAIL` – Failed to retrieve credentials
394	/// - `SYSTEM_ERR` – Other system error
395	/// - `USER_UNKNOWN` – User not known
396	///
397	/// [authenticated]: Self::authenticate()
398	/// [authorized]: Self::acct_mgmt()
399	pub fn open_pseudo_session(&mut self, flags: Flag) -> Result<Session<'_, ConvT>> {
400		self.wrap_pam_return(unsafe {
401			pam_setcred(self.handle().into(), (Flag::ESTABLISH_CRED | flags).bits())
402		})?;
403
404		Ok(Session::new(self, false))
405	}
406
407	/// Resume a session from a [`SessionToken`].
408	pub fn unleak_session(&mut self, token: SessionToken) -> Session<'_, ConvT> {
409		Session::new(self, matches!(token, SessionToken::FullSession))
410	}
411}
412
413impl<ConvT> Context<ConvT> {
414	/// Internal: Gets the PAM handle.
415	#[inline]
416	pub(crate) fn handle(&self) -> PamHandle {
417		self.handle
418	}
419
420	/// Internal: Wraps a `ErrorCode` into a `Result` and sets `last_status`.
421	#[inline]
422	pub(crate) fn wrap_pam_return(&self, status: c_int) -> Result<()> {
423		self.last_status.set(status);
424		match status {
425			PAM_SUCCESS => Ok(()),
426			code => Err(Error::new(
427				self.handle(),
428				ErrorCode::from_repr(code).unwrap_or(ErrorCode::ABORT),
429			)),
430		}
431	}
432
433	/// Returns raw PAM information.
434	///
435	/// If possible, use the convenience wrappers [`service()`][`Self::service()`],
436	/// [`user()`][`Self::user()`], … instead.
437	///
438	/// # Errors
439	/// Expected error codes include:
440	/// - `BAD_ITEM` – Unsupported, undefined or inaccessible item
441	/// - `BUF_ERR` – Memory buffer error
442	/// - `PERM_DENIED` – The value was NULL/None
443	#[rustversion::attr(since(1.48), doc(alias = "pam_get_item"))]
444	pub fn get_item(&self, item_type: c_int) -> Result<*const c_void> {
445		let mut result: *const c_void = ptr::null();
446		self.wrap_pam_return(unsafe {
447			pam_get_item(self.handle().into(), item_type, &mut result)
448		})?;
449		Ok(result)
450	}
451
452	/// Updates raw PAM information.
453	///
454	/// If possible, use the convenience wrappers
455	/// [`set_service()`][`Self::set_service()`],
456	/// [`set_user()`][`Self::set_user()`], … instead.
457	///
458	/// # Errors
459	/// Expected error codes include:
460	/// - `BAD_ITEM` – Unsupported, undefined or inaccessible item
461	/// - `BUF_ERR` – Memory buffer error
462	///
463	/// # Safety
464	/// This method is unsafe. You must guarantee that the data pointed to by
465	/// `value` matches the type the PAM library expects. E.g. a null terminated
466	/// `*const c_char` for `PAM_SERVICE` or `*const PamXAuthData` for
467	/// `PAM_XAUTHDATA`.
468	#[rustversion::attr(since(1.48), doc(alias = "pam_set_item"))]
469	pub unsafe fn set_item(&mut self, item_type: c_int, value: *const c_void) -> Result<()> {
470		self.wrap_pam_return(pam_set_item(self.handle().into(), item_type, value))
471	}
472
473	/// Returns a pointer to the raw conversation handler
474	///
475	/// # Panics
476	/// May panic if the type of the handler isn't `ConvT` or if somehow
477	/// extracting the handler from the PAM handle fails.
478	#[inline]
479	fn conversation_raw(&self) -> *mut ConvT {
480		let ptr = self
481			.get_item(pam_sys::PAM_CONV as c_int)
482			.expect("Extracting the conversation handler should never fail")
483			.cast::<PamConversation>();
484		unsafe {
485			from_pam_conv(
486				ptr.as_ref()
487					.expect("Invalid state: conversation handler should never be null"),
488			)
489		}
490	}
491
492	/// Returns a reference to the conversation handler.
493	pub fn conversation(&self) -> &ConvT {
494		let ptr: *const ConvT = self.conversation_raw();
495		// Safety: the conversation handler is only set by `from_boxed_conv()` or `replace_handler()`
496		// and these maintain that the installed handler is valid and of the correct type.
497		unsafe { &*ptr }
498	}
499
500	/// Returns a mutable reference to the conversation handler.
501	pub fn conversation_mut(&mut self) -> &mut ConvT {
502		let ptr = self.conversation_raw();
503		// Safety: the conversation handler is only set by `from_boxed_conv()` or `replace_handler()`
504		// and these maintain that the installed handler is valid and of the correct type.
505		unsafe { &mut *ptr }
506	}
507
508	impl_pam_str_item!(
509		service,
510		set_service,
511		pam_sys::PAM_SERVICE,
512		"the service name"
513	);
514	impl_pam_str_item!(user, set_user, pam_sys::PAM_USER, "the username of the entity under whose identity service will be given",
515		"This value can be mapped by any module in the PAM stack, so don't assume it stays unchanged after calling other methods on `Self`.");
516	impl_pam_str_item!(
517		user_prompt,
518		set_user_prompt,
519		pam_sys::PAM_USER_PROMPT,
520		"the string used when prompting for a user's name"
521	);
522	impl_pam_str_item!(tty, set_tty, pam_sys::PAM_TTY, "the terminal name");
523	impl_pam_str_item!(
524		ruser,
525		set_ruser,
526		pam_sys::PAM_RUSER,
527		"the requesting user name"
528	);
529	impl_pam_str_item!(
530		rhost,
531		set_rhost,
532		pam_sys::PAM_RHOST,
533		"the requesting hostname"
534	);
535	#[cfg(any(target_os = "linux", doc))]
536	impl_pam_str_item!(
537		authtok_type,
538		set_authtok_type,
539		pam_sys::PAM_AUTHTOK_TYPE,
540		"the default password type in the prompt (Linux specific)",
541		"E.g. \"UNIX\" for \"Enter UNIX password:\""
542	);
543	#[cfg(any(target_os = "linux", doc))]
544	impl_pam_str_item!(
545		xdisplay,
546		set_xdisplay,
547		pam_sys::PAM_XDISPLAY,
548		"the name of the X display (Linux specific)"
549	);
550
551	/// Returns X authentication data as (name, value) pair (Linux specific).
552	#[cfg(any(target_os = "linux", doc))]
553	pub fn xauthdata(&self) -> Result<(&CStr, &[u8])> {
554		let handle = self.handle();
555		let ptr = self
556			.get_item(pam_sys::PAM_XAUTHDATA as c_int)?
557			.cast::<XAuthData>();
558		if ptr.is_null() {
559			return Err(Error::new(handle, ErrorCode::PERM_DENIED));
560		}
561		let data = unsafe { &*ptr };
562
563		// Safety checks: validate the length are non-negative and that
564		// the pointers are non-null
565		if data.namelen < 0 || data.datalen < 0 || data.name.is_null() || data.data.is_null() {
566			return Err(Error::new(handle, ErrorCode::BUF_ERR));
567		}
568
569		#[allow(clippy::cast_sign_loss)]
570		Ok((
571			CStr::from_bytes_with_nul(unsafe {
572				slice::from_raw_parts(data.name.cast(), data.namelen as usize + 1)
573			})
574			.map_err(|_| Error::new(handle, ErrorCode::BUF_ERR))?,
575			unsafe { slice::from_raw_parts(data.data.cast(), data.datalen as usize) },
576		))
577	}
578
579	/// Sets X authentication data (Linux specific).
580	///
581	/// # Errors
582	/// Expected error codes include:
583	/// - `BAD_ITEM` – Unsupported item
584	/// - `BUF_ERR` – Memory buffer error
585	#[cfg(any(target_os = "linux", doc))]
586	pub fn set_xauthdata(&mut self, value: Option<(&CStr, &[u8])>) -> Result<()> {
587		match value {
588			None => unsafe { self.set_item(pam_sys::PAM_XAUTHDATA as c_int, ptr::null()) },
589			Some((name, data)) => {
590				let name_bytes = name.to_bytes_with_nul();
591
592				if name_bytes.len() > i32::MAX as usize || data.len() > i32::MAX as usize {
593					return Err(Error::new(self.handle(), ErrorCode::BUF_ERR));
594				}
595
596				#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
597				let xauthdata = XAuthData {
598					namelen: name_bytes.len() as i32 - 1,
599					name: name_bytes.as_ptr().cast(),
600					datalen: data.len() as i32,
601					data: data.as_ptr().cast(),
602				};
603				unsafe {
604					self.set_item(
605						pam_sys::PAM_XAUTHDATA as c_int,
606						&xauthdata as *const _ as *const c_void,
607					)
608				}
609			}
610		}
611	}
612
613	/// Returns the value of a PAM environment variable.
614	///
615	/// Searches the environment list in this PAM context for an
616	/// item with key `name` and returns the value, if it exists.
617	#[must_use]
618	#[rustversion::attr(since(1.48), doc(alias = "pam_getenv"))]
619	pub fn getenv(&self, name: impl AsRef<OsStr>) -> Option<&str> {
620		let c_name = match CString::new(name.as_ref().as_bytes()) {
621			Err(_) => return None,
622			Ok(s) => s,
623		};
624		char_ptr_to_str(unsafe { pam_getenv(self.handle().into(), c_name.as_ptr()) })
625	}
626
627	/// Sets or unsets a PAM environment variable.
628	///
629	/// Modifies the environment list in this PAM context. The `name_value`
630	/// argument can take one of the following forms:
631	/// - *NAME*=*value* – Set a variable to a given value. If it was already
632	///   set it is overwritten.
633	/// - *NAME*= – Set a variable to the empty value. If it was already set
634	///   it is overwritten.
635	/// - *NAME* – Delete a variable, if it exists.
636	#[rustversion::attr(since(1.48), doc(alias = "pam_putenv"))]
637	pub fn putenv(&mut self, name_value: impl AsRef<OsStr>) -> Result<()> {
638		let c_name_value = CString::new(name_value.as_ref().as_bytes())
639			.map_err(|_| Error::from(ErrorCode::BUF_ERR))?;
640		self.wrap_pam_return(unsafe { pam_putenv(self.handle().into(), c_name_value.as_ptr()) })
641	}
642
643	/// Returns a copy of the PAM environment in this context.
644	///
645	/// The contained variables represent the contents of the regular
646	/// environment variables of the authenticated user when service is
647	/// granted.
648	///
649	/// The returned [`EnvList`] type is designed to ease handing the
650	/// environment to [`std::process::Command::envs()`] and
651	/// `nix::unistd::execve()`.
652	#[must_use]
653	#[rustversion::attr(since(1.48), doc(alias = "pam_getenvlist"))]
654	pub fn envlist(&self) -> EnvList {
655		unsafe { EnvList::new(pam_getenvlist(self.handle().into()).cast()) }
656	}
657
658	/// Swap the conversation handler.
659	///
660	/// Consumes the context, returns the new context and the old conversation
661	/// handler.
662	///
663	/// # Errors
664	/// Expected error codes include:
665	/// - `BAD_ITEM` – Swapping the conversation handler is unsupported
666	/// - `BUF_ERR` – Memory buffer error
667	///
668	/// The error payload contains the old context and the new handler.
669	pub fn replace_conversation<T: ConversationHandler>(
670		self,
671		new_handler: T,
672	) -> ExtResult<(Context<T>, ConvT), (Self, T)> {
673		match self.replace_conversation_boxed(new_handler.into()) {
674			Ok((context, boxed_old_conv)) => Ok((context, *boxed_old_conv)),
675			Err(error) => Err(error.map(|(ctx, b_conv)| (ctx, *b_conv))),
676		}
677	}
678	/// Swap the conversation handler (boxed variant).
679	///
680	/// See [`replace_conversation()`][`Self::replace_conversation()`].
681	pub fn replace_conversation_boxed<T: ConversationHandler>(
682		mut self,
683		new_handler: Box<T>,
684	) -> ExtResult<(Context<T>, Box<ConvT>), (Self, Box<T>)> {
685		// Get current username for handler initialization
686		let username = match self.user() {
687			Ok(u) => Some(u),
688			Err(e) => {
689				if e.code() != ErrorCode::PERM_DENIED {
690					return Err(e.into_with_payload((self, new_handler)));
691				}
692				None
693			}
694		};
695		// Get pointer to old handler
696		let old_handler_ptr = self.conversation_raw();
697
698		// Swap handler
699		let pam_conv = into_pam_conv(new_handler);
700		if let Err(e) = unsafe {
701			self.set_item(
702				pam_sys::PAM_CONV as c_int,
703				&pam_conv as *const _ as *const _,
704			)
705		} {
706			let new_handler = unsafe { Box::from_raw(from_pam_conv(&pam_conv)) };
707			Err(e.into_with_payload((self, new_handler)))
708		} else {
709			// Prevent dropping of the old context
710			let old = ManuallyDrop::new(self);
711
712			// Reconstruct old handler from saved pointer
713			// Safety: The handler was replaced in the context, so there
714			// is no other way to access the old handler anymore and
715			// the pointer was originally constructed with `Box::into_raw()`.
716			let old_handler = unsafe { Box::from_raw(old_handler_ptr) };
717
718			// Create new context
719			let mut context = Context::<T> {
720				handle: old.handle,
721				last_status: Cell::new(old.last_status.replace(PAM_SUCCESS)),
722				_conversation: PhantomData,
723			};
724
725			// Initialize handler
726			context.conversation_mut().init(username.as_deref());
727
728			// Return context and old handler
729			Ok((context, old_handler))
730		}
731	}
732}
733
734/// Destructor ending the PAM transaction and releasing the PAM context
735impl<ConvT> Drop for Context<ConvT> {
736	#[rustversion::attr(since(1.48), doc(alias = "pam_end"))]
737	fn drop(&mut self) {
738		let conv = self.conversation_raw();
739		unsafe { pam_end(self.handle.into(), self.last_status.get()) };
740		drop(unsafe { Box::from_raw(conv) });
741	}
742}
743
744// `Send` should be possible, as long as `ConvT` is `Send` too, as all memory
745// access is bound to an unique instance of `Context` (no copy/clone) and we
746// keep interior mutability bound to having a reference to the instance.
747unsafe impl<ConvT> Send for Context<ConvT> where ConvT: Send {}
748
749#[cfg(test)]
750mod tests {
751	use super::*;
752	use std::ffi::{OsStr, OsString};
753
754	#[test]
755	fn test_basic() {
756		let mut context =
757			Context::new("test", Some("user"), crate::conv_null::Conversation::new()).unwrap();
758		// Check if user name and service name are correctly saved
759		assert_eq!(context.service().unwrap(), "test");
760		assert_eq!(context.user().unwrap(), "user");
761		// Check basic properties of PamHandle
762		let h = context.handle();
763		assert_eq!(&h.0, &h.0);
764		assert!(format!("{:?}", h).contains(&format!("{:?}", h.as_ptr())));
765		// Check setting/getting of string items.
766		context.set_user_prompt(Some("Who art thou? ")).unwrap();
767		assert_eq!(context.user_prompt().unwrap(), "Who art thou? ");
768		context.set_tty(Some("/dev/tty")).unwrap();
769		assert_eq!(context.tty().unwrap(), "/dev/tty");
770		context.set_ruser(Some("nobody")).unwrap();
771		assert_eq!(context.ruser().unwrap(), "nobody");
772		context.set_rhost(Some("nowhere")).unwrap();
773		assert_eq!(context.rhost().unwrap(), "nowhere");
774		// Check linux specific items
775		#[cfg(target_os = "linux")]
776		{
777			context.set_authtok_type(Some("TEST")).unwrap();
778			assert_eq!(context.authtok_type().unwrap(), "TEST");
779			context.set_xdisplay(Some(":0")).unwrap();
780			assert_eq!(context.xdisplay().unwrap(), ":0");
781			let xauthname = CString::new("TEST_DATA").unwrap();
782			let xauthdata = [];
783			let _ = context.xauthdata();
784			context
785				.set_xauthdata(Some((&xauthname, &xauthdata)))
786				.unwrap();
787			let (resultname, resultdata) = context.xauthdata().unwrap();
788			assert_eq!(resultname, xauthname.as_c_str());
789			assert_eq!(resultdata, &xauthdata);
790		};
791		// Check accessing the conversation handler
792		assert_eq!(
793			context.conversation_mut() as *mut _ as *const _,
794			context.conversation() as *const _
795		);
796		context
797			.conversation_mut()
798			.text_info(&CString::new("").unwrap());
799		// Check getting an unaccessible item
800		assert!(context.get_item(pam_sys::PAM_AUTHTOK as c_int).is_err());
801		// Check environment setting/getting
802		context.putenv("TEST=1").unwrap();
803		context.putenv("TEST2=2").unwrap();
804		let _ = context.putenv("\0=\0").unwrap_err();
805		assert_eq!(context.getenv("TEST").unwrap(), "1");
806		assert!(context.getenv("TESTNONEXIST").is_none());
807		let env = context.envlist();
808		assert!(!env.is_empty());
809		let _ = env.get("TEST").unwrap();
810		let _ = env.get("TESTNONEXIST").is_none();
811		for (key, value) in env.iter_tuples() {
812			if key.to_string_lossy() == "TEST" {
813				assert_eq!(value.to_string_lossy(), "1");
814			}
815		}
816		assert!(format!("{:?}", &env.iter_tuples()).contains("EnvItem"));
817		for item in &env {
818			let string = item.to_string();
819			if string.starts_with("TEST=") {
820				assert_eq!(string, "TEST=1");
821				assert!(format!("{:?}", &item).contains("EnvItem"));
822			} else if string.starts_with("TEST2=") {
823				let (_, v): (&OsStr, &OsStr) = item.into();
824				assert_eq!(v.to_string_lossy(), "2");
825			}
826			let _ = item.as_ref();
827		}
828		let _ = format!("{:?}", &env);
829		assert!(!env.is_empty());
830		assert_eq!(env.len(), env.as_ref().len());
831		assert_eq!(env.as_ref(), context.envlist().as_ref());
832		assert_eq!(
833			env.as_ref().partial_cmp(context.envlist().as_ref()),
834			Some(std::cmp::Ordering::Equal)
835		);
836		assert_eq!(
837			env.as_ref().cmp(context.envlist().as_ref()),
838			std::cmp::Ordering::Equal
839		);
840		assert_eq!(&env["TEST"], "1");
841		assert_eq!(env.len(), env.iter_tuples().size_hint().0);
842		let list: std::vec::Vec<&CStr> = (&env).into();
843		assert_eq!(list.len(), env.len());
844		let list: std::vec::Vec<(&OsStr, _)> = (&env).into();
845		assert_eq!(list.len(), env.len());
846		let map: std::collections::HashMap<&OsStr, _> = (&env).into();
847		assert_eq!(map.len(), map.len());
848		assert_eq!(
849			map.get(&OsString::from("TEST".to_string()).as_ref()),
850			Some(&OsString::from("1".to_string()).as_ref())
851		);
852		assert!(env.to_string().contains("TEST=1"));
853		let list: std::vec::Vec<(std::ffi::OsString, _)> = context.envlist().into();
854		assert_eq!(list.len(), env.len());
855		let list: std::vec::Vec<CString> = context.envlist().into();
856		assert_eq!(list.len(), env.len());
857		let map: std::collections::HashMap<_, _> = context.envlist().into();
858		assert_eq!(map.len(), env.len());
859		assert_eq!(
860			map.get(&OsString::from("TEST".to_string())),
861			Some(&OsString::from("1".to_string()))
862		);
863		drop(context)
864	}
865
866	#[test]
867	fn test_conv_replace() {
868		let mut context =
869			Context::new("test", Some("user"), crate::conv_null::Conversation::new()).unwrap();
870		// Set username
871		context.set_user(Some("anybody")).unwrap();
872		// Replace conversation handler
873		let (mut context, old_conv) = context
874			.replace_conversation(crate::conv_mock::Conversation::default())
875			.unwrap();
876		// Check if set username was propagated to the new handler
877		assert_eq!(context.conversation().username, "anybody");
878		context.set_user(None).unwrap();
879		let (context, _) = context.replace_conversation(old_conv).unwrap();
880		// Check if username stays None after being set througout a replace.
881		assert!(context.user().is_err());
882	}
883
884	#[test]
885	fn test_dyn_ref() {
886		let mut handler_a = crate::conv_null::Conversation::new();
887		let mut handler_b = crate::conv_mock::Conversation::new();
888
889		let mut context = Context::new(
890			"test",
891			Some("user"),
892			&mut handler_a as &mut dyn ConversationHandler,
893		)
894		.unwrap();
895		// Set username
896		context.set_user(Some("anybody")).unwrap();
897		// Replace conversation handler and drop reference to `handler_a`
898		let (context, _) = context
899			.replace_conversation(
900				&mut handler_b as &mut dyn ConversationHandler
901			)
902			.unwrap();
903
904		// Assert that handler_a is accessible again
905		drop(handler_a);
906		
907		// drop context
908		drop(context);
909
910		// Check if set username was propagated to `handler_b`
911		assert_eq!(handler_b.username, "anybody");
912	}
913
914	#[test]
915	fn test_dyn() {
916		let mut context = Context::new(
917			"test",
918			Some("user"),
919			Box::new(crate::conv_null::Conversation::new()) as Box<dyn ConversationHandler>,
920		)
921		.unwrap();
922		// Set username
923		context.set_user(Some("anybody")).unwrap();
924		// Replace conversation handler
925		let (context, _) = context
926			.replace_conversation(
927				Box::new(crate::conv_mock::Conversation::new()) as Box<dyn ConversationHandler>
928			)
929			.unwrap();
930
931		// Check if set username was propagated to the new handler
932		// Safety: we know the type from four lines above, so this unchecked downcast is sound.
933		let mock_handler: &crate::conv_mock::Conversation =
934			unsafe { &*(&**context.conversation() as *const _ as *const _) };
935		assert_eq!(mock_handler.username, "anybody");
936	}
937
938	/// Shallowly tests a full authentication + password change + session cycle.
939	///
940	/// This will fail if the environment is not appropriately
941	/// prepared and the test process has no elevated rights.
942	/// Currently it is only checked if some function crashes
943	/// or panics, not if the authentication succeeds.
944	#[test]
945	#[cfg_attr(not(feature = "full_test"), ignore)]
946	fn test_full() {
947		let mut context = Context::new(
948			"test_rust_pam_client",
949			Some("nobody"),
950			crate::conv_null::Conversation::new(),
951		)
952		.unwrap();
953		let _ = context.authenticate(Flag::SILENT);
954		let _ = context.acct_mgmt(Flag::SILENT);
955		let _ = context.chauthtok(Flag::CHANGE_EXPIRED_AUTHTOK);
956		let _ = context.reinitialize_credentials(Flag::SILENT | Flag::NONE);
957		drop(context.open_session(Flag::SILENT));
958		drop(context.open_pseudo_session(Flag::SILENT));
959	}
960
961	/// Shallowly tests a full session cycle without authentication.
962	///
963	/// This will fail if the environment is not appropriately
964	/// prepared and the test process has no elevated rights.
965	/// Currently it is only checked if some function crashes
966	/// or panics, not if anything really succeeds.
967	///
968	/// Some operating systems besides Linux don't support this mode of operation.
969	#[test]
970	#[cfg_attr(not(feature = "full_test"), ignore)]
971	fn test_full_unauth() {
972		let mut context = Context::new(
973			"test_rust_pam_client",
974			Some("nobody"),
975			crate::conv_null::Conversation::new(),
976		)
977		.unwrap();
978		let _ = context.acct_mgmt(Flag::SILENT);
979		let _ = context.chauthtok(Flag::CHANGE_EXPIRED_AUTHTOK);
980		let _ = context.reinitialize_credentials(Flag::SILENT | Flag::NONE);
981		if let Ok(mut session) = context.open_session(Flag::SILENT) {
982			let _ = session.refresh_credentials(Flag::SILENT);
983			let _ = session.reinitialize_credentials(Flag::SILENT);
984			let _ = session.envlist();
985			let _ = session.close(Flag::SILENT);
986		};
987		if let Ok(mut session) = context.open_pseudo_session(Flag::SILENT) {
988			let _ = session.refresh_credentials(Flag::SILENT);
989		};
990	}
991}