pam_client/
env_list.rs

1//! PAM environment list
2
3/***********************************************************************
4 * (c) 2021 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::c_box::CBox;
12use libc::c_char;
13use std::cmp::Ordering;
14use std::collections::HashMap;
15use std::ffi::{CStr, CString, OsStr, OsString};
16use std::iter::FusedIterator;
17use std::ops::Index;
18use std::os::unix::ffi::OsStrExt;
19use std::{fmt, slice};
20
21/// Item in a PAM environment list.
22///
23/// A key-value pair representing an environment variable in a [`EnvList`],
24/// convertible to `&CStr` and to a pair (key, value) of `&OsStr`'s.
25///
26/// As this struct is tightly coupled to the memory management of [`EnvList`]
27/// no constructor for custom instances is provided.
28#[repr(transparent)]
29#[derive(Debug)]
30pub struct EnvItem(CBox<c_char>);
31
32impl EnvItem {
33	/// Returns a [`CStr`] reference to the `"key=value"` representation.
34	#[must_use]
35	pub fn as_cstr(&self) -> &CStr {
36		unsafe { CStr::from_ptr(self.0.as_ref()) }
37	}
38
39	/// Returns a pair of references to a `("key", "value")` representation.
40	#[must_use]
41	pub fn key_value(&self) -> (&OsStr, &OsStr) {
42		let element = <&CStr>::from(self).to_bytes();
43		let sep = element
44			.iter()
45			.position(|b| *b == b'=')
46			.unwrap_or(element.len());
47		(
48			OsStr::from_bytes(&element[..sep]),
49			OsStr::from_bytes(&element[sep + 1..]),
50		)
51	}
52}
53
54/// Display and string conversion of the environment variable.
55///
56/// Also causes `.to_string()` to be implemented.
57impl fmt::Display for EnvItem {
58	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59		write!(f, "{}", <&CStr>::from(self).to_string_lossy())
60	}
61}
62
63impl<'a> From<&'a EnvItem> for &'a CStr {
64	#[inline]
65	fn from(item: &'a EnvItem) -> Self {
66		item.as_cstr()
67	}
68}
69
70impl<'a> From<&'a EnvItem> for (&'a OsStr, &'a OsStr) {
71	fn from(item: &'a EnvItem) -> Self {
72		item.key_value()
73	}
74}
75
76impl AsRef<CStr> for EnvItem {
77	#[inline]
78	fn as_ref(&self) -> &CStr {
79		self.as_cstr()
80	}
81}
82
83impl PartialEq for EnvItem {
84	#[inline]
85	fn eq(&self, other: &Self) -> bool {
86		PartialEq::eq(self.as_cstr(), other.as_cstr())
87	}
88}
89
90impl Eq for EnvItem {}
91
92impl PartialOrd for EnvItem {
93	#[inline]
94	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
95		PartialOrd::partial_cmp(self.as_cstr(), other.as_cstr())
96	}
97}
98
99impl Ord for EnvItem {
100	#[inline]
101	fn cmp(&self, other: &Self) -> Ordering {
102		Ord::cmp(self.as_cstr(), other.as_cstr())
103	}
104}
105
106/// Serializes as a (key, value) OsStr tuple.
107#[cfg(feature = "serde")]
108impl serde::Serialize for EnvItem {
109	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
110		self.key_value().serialize(serializer)
111	}
112}
113
114/// Helper function: determine the length of the array
115unsafe fn count_items<T: ?Sized>(mut ptr: *const *const T) -> usize {
116	let mut result: usize = 0;
117	while !(*ptr).is_null() {
118		ptr = ptr.add(1);
119		result += 1;
120	}
121	result
122}
123
124/// A PAM environment list
125///
126/// The PAM environment represents the contents of the regular environment
127/// variables of the authenticated user when service is granted and can be
128/// used to prepare the environment of child processes run as the authenticated
129/// user.
130///
131/// See [`Context::envlist()`][`crate::Context::envlist()`].
132///
133/// # Examples
134/// ```rust
135/// # use pam_client::Context;
136/// # let handler = pam_client::conv_mock::Conversation::with_credentials("test".to_string(), "test".to_string());
137/// # let mut context = Context::new("dummy", None, handler).unwrap();
138/// // Print the PAM environment
139/// for item in &context.envlist() {
140///     println!("VAR: {}", item);
141/// }
142/// ```
143///
144/// The environment can be passed to [`std::process::Command`] by using [`EnvList::iter_tuples()`]:
145/// ```no_run
146/// use std::process::Command;
147/// # use pam_client::Context;
148/// # let handler = pam_client::conv_mock::Conversation::with_credentials("test".to_string(), "test".to_string());
149/// # let mut context = Context::new("dummy", None, handler).unwrap();
150///
151/// // Spawn a process in the PAM environment
152/// let command = Command::new("/usr/bin/some_program")
153///                       .env_clear()
154///                       .envs(context.envlist().iter_tuples());
155/// ```
156///
157/// The environment can be passed to NIX's `execve` by using [`EnvList::as_ref()`]:
158/// ```no_run
159/// # // mock execve so that the doctest compiles
160/// # pub mod nix { pub mod unistd {
161/// #     use std::convert::Infallible;
162/// #     use std::ffi::{CString, CStr};
163/// #     pub fn execve<SA: AsRef<CStr>, SE: AsRef<CStr>>(path: &CStr, args: &[SA], env: &[SE]) -> Result<Infallible, ()> { panic!() }
164/// # } }
165/// # use std::ffi::CString;
166/// # use pam_client::Context;
167/// # let handler = pam_client::conv_mock::Conversation::with_credentials("test".to_string(), "test".to_string());
168/// # let mut context = Context::new("dummy", None, handler).unwrap();
169/// use nix::unistd::execve;
170///
171/// // Replace this process with another program in the PAM environment
172/// execve(
173///     &CString::new("/usr/bin/some_program").unwrap(),
174///     &[CString::new("some_program").unwrap()],
175///     context.envlist().as_ref()
176/// ).expect("replacing the current process failed");
177/// ```
178#[derive(Debug)]
179pub struct EnvList(CBox<[EnvItem]>);
180
181impl EnvList {
182	/// Creates an `EnvList` from a pointer as returned by
183	/// `pam_getenvlist()`.
184	///
185	/// # Panics
186	/// Panics if `data` is null.
187	#[must_use]
188	pub(crate) unsafe fn new(data: *mut *mut c_char) -> Self {
189		assert!(!data.is_null());
190		let len = count_items(data as *const *const c_char);
191		Self(CBox::from_raw_slice(data.cast(), len))
192	}
193
194	/// Returns a reference to the value of the named environment variable.
195	///
196	/// Returns `None` if the variable doesn't exist in this list.
197	#[must_use]
198	pub fn get<T: AsRef<OsStr>>(&self, name: T) -> Option<&OsStr> {
199		#[inline]
200		fn get<'a>(list: &'a EnvList, name: &'_ OsStr) -> Option<&'a OsStr> {
201			list.iter_tuples()
202				.find_map(|(k, v)| if k == name { Some(v) } else { None })
203		}
204		get(self, name.as_ref())
205	}
206
207	/// Returns an iterator over all contained variables as [`EnvItem`]s.
208	///
209	/// The iteration happens in deterministic, but unspecified order.
210	#[inline]
211	pub fn iter(&self) -> Iter {
212		self.0.iter()
213	}
214
215	/// Returns the count of environment variables in the list.
216	#[inline]
217	#[must_use]
218	pub fn len(&self) -> usize {
219		self.0.len()
220	}
221
222	/// Returns `true` if the environment list is empty.
223	#[inline]
224	#[must_use]
225	pub fn is_empty(&self) -> bool {
226		self.0.is_empty()
227	}
228
229	/// Returns an iterator over all contained variables as
230	/// `(key: &OsStr, value: &OsStr)` tuples.
231	///
232	/// The iteration happens in deterministic, but unspecified order.
233	///
234	/// Provides compatibility with [`std::process::Command::envs()`].
235	#[inline]
236	pub fn iter_tuples(&self) -> TupleIter {
237		TupleIter(self.0.iter())
238	}
239}
240
241/// Display and string conversion of the environment list.
242///
243/// Also causes `.to_string()` to be implemented.
244impl fmt::Display for EnvList {
245	/// Formats the environment list as a multi-line string
246	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247		for item in self.0.iter() {
248			writeln!(f, "{}", item.as_cstr().to_string_lossy())?;
249		}
250		Ok(())
251	}
252}
253
254/// Provide direct `for`-loop support.
255///
256/// See [`EnvList::iter()`].
257impl<'a> IntoIterator for &'a EnvList {
258	type Item = &'a EnvItem;
259	type IntoIter = Iter<'a>;
260
261	fn into_iter(self) -> Self::IntoIter {
262		self.iter()
263	}
264}
265
266/// Provide compatibility with the 3rd parameter of `nix::unistd::execve`.
267impl AsRef<[EnvItem]> for EnvList {
268	#[inline]
269	fn as_ref(&self) -> &[EnvItem] {
270		&self.0
271	}
272}
273
274/// Conversion to a vector of (key, value) tuples.
275impl From<EnvList> for Vec<(OsString, OsString)> {
276	fn from(list: EnvList) -> Self {
277		let mut vec = Vec::with_capacity(list.len());
278		for (key, value) in list.iter_tuples() {
279			vec.push((key.to_owned(), value.to_owned()));
280		}
281		vec
282	}
283}
284
285/// Reference conversion to a vector of (&key, &value) tuples.
286impl<'a> From<&'a EnvList> for Vec<(&'a OsStr, &'a OsStr)> {
287	fn from(list: &'a EnvList) -> Self {
288		list.iter_tuples().collect()
289	}
290}
291
292/// Conversion to a vector of "key=value" `CString`s.
293impl From<EnvList> for Vec<CString> {
294	fn from(list: EnvList) -> Self {
295		let mut vec = Vec::with_capacity(list.len());
296		for item in list.0.iter() {
297			vec.push(item.as_cstr().to_owned());
298		}
299		vec
300	}
301}
302
303/// Reference conversion to a vector of "key=value" `&CStr`s.
304impl<'a> From<&'a EnvList> for Vec<&'a CStr> {
305	fn from(list: &'a EnvList) -> Self {
306		let mut vec = Vec::with_capacity(list.len());
307		for item in list.0.iter() {
308			vec.push(item.as_cstr());
309		}
310		vec
311	}
312}
313
314/// Conversion to a hash map
315impl<S> From<EnvList> for HashMap<OsString, OsString, S>
316where
317	S: ::std::hash::BuildHasher + Default,
318{
319	fn from(list: EnvList) -> Self {
320		let mut map = HashMap::<_, _, S>::with_capacity_and_hasher(list.len(), S::default());
321		for (key, value) in list.iter_tuples() {
322			map.insert(key.to_owned(), value.to_owned());
323		}
324		map
325	}
326}
327
328/// Reference conversion to a referencing hash map
329impl<'a, S> From<&'a EnvList> for HashMap<&'a OsStr, &'a OsStr, S>
330where
331	S: ::std::hash::BuildHasher + Default,
332{
333	fn from(list: &'a EnvList) -> Self {
334		list.iter_tuples().collect()
335	}
336}
337
338/// Indexing with `list[key]`
339impl<T: AsRef<OsStr>> Index<T> for EnvList {
340	type Output = OsStr;
341
342	/// Returns a reference to the value of the named environment variable.
343	///
344	/// # Panics
345	/// Panics if the environment variable is not present in the `EnvList`.
346	fn index(&self, name: T) -> &Self::Output {
347		self.get(name).expect("environment variable not found")
348	}
349}
350
351/// Serializes as a list of (key, value) OsStr tuples.
352#[cfg(feature = "serde")]
353impl serde::Serialize for EnvList {
354	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
355		self.0.serialize(serializer)
356	}
357}
358
359/// Iterator over [`EnvItem`]s in an [`EnvList`].
360pub type Iter<'a> = slice::Iter<'a, EnvItem>;
361
362/// Iterator over [`EnvItem`]s converted to `(key: &OsStr, value: &OsStr)`
363/// tuples.
364///
365/// Returned by [`EnvList::iter_tuples()`].
366#[must_use]
367#[derive(Debug)]
368pub struct TupleIter<'a>(slice::Iter<'a, EnvItem>);
369
370impl<'a> Iterator for TupleIter<'a> {
371	type Item = (&'a OsStr, &'a OsStr);
372
373	fn next(&mut self) -> Option<Self::Item> {
374		self.0.next().map(EnvItem::key_value)
375	}
376
377	#[inline]
378	fn size_hint(&self) -> (usize, Option<usize>) {
379		self.0.size_hint()
380	}
381}
382
383impl FusedIterator for TupleIter<'_> {}
384impl ExactSizeIterator for TupleIter<'_> {}