1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
//! PAM environment list

/***********************************************************************
 * (c) 2021 Christoph Grenz <christophg+gitorious @ grenz-bonn.de>     *
 *                                                                     *
 * This Source Code Form is subject to the terms of the Mozilla Public *
 * License, v. 2.0. If a copy of the MPL was not distributed with this *
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.            *
 ***********************************************************************/

use crate::c_box::CBox;
use libc::c_char;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::ffi::{CStr, CString, OsStr, OsString};
use std::iter::FusedIterator;
use std::ops::Index;
use std::os::unix::ffi::OsStrExt;
use std::{fmt, slice};

/// Item in a PAM environment list.
///
/// A key-value pair representing an environment variable in a [`EnvList`],
/// convertible to `&CStr` and to a pair (key, value) of `&OsStr`'s.
///
/// As this struct is tightly coupled to the memory management of [`EnvList`]
/// no constructor for custom instances is provided.
#[repr(transparent)]
#[derive(Debug)]
pub struct EnvItem(CBox<c_char>);

impl EnvItem {
	/// Returns a [`CStr`] reference to the `"key=value"` representation.
	#[must_use]
	pub fn as_cstr(&self) -> &CStr {
		unsafe { CStr::from_ptr(self.0.as_ref()) }
	}

	/// Returns a pair of references to a `("key", "value")` representation.
	#[must_use]
	pub fn key_value(&self) -> (&OsStr, &OsStr) {
		let element = <&CStr>::from(self).to_bytes();
		let sep = element
			.iter()
			.position(|b| *b == b'=')
			.unwrap_or(element.len());
		(
			OsStr::from_bytes(&element[..sep]),
			OsStr::from_bytes(&element[sep + 1..]),
		)
	}
}

/// Display and string conversion of the environment variable.
///
/// Also causes `.to_string()` to be implemented.
impl fmt::Display for EnvItem {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "{}", <&CStr>::from(self).to_string_lossy())
	}
}

impl<'a> From<&'a EnvItem> for &'a CStr {
	#[inline]
	fn from(item: &'a EnvItem) -> Self {
		item.as_cstr()
	}
}

impl<'a> From<&'a EnvItem> for (&'a OsStr, &'a OsStr) {
	fn from(item: &'a EnvItem) -> Self {
		item.key_value()
	}
}

impl AsRef<CStr> for EnvItem {
	#[inline]
	fn as_ref(&self) -> &CStr {
		self.as_cstr()
	}
}

impl PartialEq for EnvItem {
	#[inline]
	fn eq(&self, other: &Self) -> bool {
		PartialEq::eq(self.as_cstr(), other.as_cstr())
	}
}

impl Eq for EnvItem {}

impl PartialOrd for EnvItem {
	#[inline]
	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
		PartialOrd::partial_cmp(self.as_cstr(), other.as_cstr())
	}
}

impl Ord for EnvItem {
	#[inline]
	fn cmp(&self, other: &Self) -> Ordering {
		Ord::cmp(self.as_cstr(), other.as_cstr())
	}
}

/// Serializes as a (key, value) OsStr tuple.
#[cfg(feature = "serde")]
impl serde::Serialize for EnvItem {
	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
		self.key_value().serialize(serializer)
	}
}

/// Helper function: determine the length of the array
unsafe fn count_items<T: ?Sized>(mut ptr: *const *const T) -> usize {
	let mut result: usize = 0;
	while !(*ptr).is_null() {
		ptr = ptr.add(1);
		result += 1;
	}
	result
}

/// A PAM environment list
///
/// The PAM environment represents the contents of the regular environment
/// variables of the authenticated user when service is granted and can be
/// used to prepare the environment of child processes run as the authenticated
/// user.
///
/// See [`Context::envlist()`][`crate::Context::envlist()`].
///
/// # Examples
/// ```rust
/// # use pam_client::Context;
/// # let handler = pam_client::conv_mock::Conversation::with_credentials("test".to_string(), "test".to_string());
/// # let mut context = Context::new("dummy", None, handler).unwrap();
/// // Print the PAM environment
/// for item in &context.envlist() {
///     println!("VAR: {}", item);
/// }
/// ```
///
/// The environment can be passed to [`std::process::Command`] by using [`EnvList::iter_tuples()`]:
/// ```no_run
/// use std::process::Command;
/// # use pam_client::Context;
/// # let handler = pam_client::conv_mock::Conversation::with_credentials("test".to_string(), "test".to_string());
/// # let mut context = Context::new("dummy", None, handler).unwrap();
///
/// // Spawn a process in the PAM environment
/// let command = Command::new("/usr/bin/some_program")
///                       .env_clear()
///                       .envs(context.envlist().iter_tuples());
/// ```
///
/// The environment can be passed to NIX's `execve` by using [`EnvList::as_ref()`]:
/// ```no_run
/// # // mock execve so that the doctest compiles
/// # pub mod nix { pub mod unistd {
/// #     use std::convert::Infallible;
/// #     use std::ffi::{CString, CStr};
/// #     pub fn execve<SA: AsRef<CStr>, SE: AsRef<CStr>>(path: &CStr, args: &[SA], env: &[SE]) -> Result<Infallible, ()> { panic!() }
/// # } }
/// # use std::ffi::CString;
/// # use pam_client::Context;
/// # let handler = pam_client::conv_mock::Conversation::with_credentials("test".to_string(), "test".to_string());
/// # let mut context = Context::new("dummy", None, handler).unwrap();
/// use nix::unistd::execve;
///
/// // Replace this process with another program in the PAM environment
/// execve(
///     &CString::new("/usr/bin/some_program").unwrap(),
///     &[CString::new("some_program").unwrap()],
///     context.envlist().as_ref()
/// ).expect("replacing the current process failed");
/// ```
#[derive(Debug)]
pub struct EnvList(CBox<[EnvItem]>);

impl EnvList {
	/// Creates an `EnvList` from a pointer as returned by
	/// `pam_getenvlist()`.
	///
	/// # Panics
	/// Panics if `data` is null.
	#[must_use]
	pub(crate) unsafe fn new(data: *mut *mut c_char) -> Self {
		assert!(!data.is_null());
		let len = count_items(data as *const *const c_char);
		Self(CBox::from_raw_slice(data.cast(), len))
	}

	/// Returns a reference to the value of the named environment variable.
	///
	/// Returns `None` if the variable doesn't exist in this list.
	#[must_use]
	pub fn get<T: AsRef<OsStr>>(&self, name: T) -> Option<&OsStr> {
		#[inline]
		fn get<'a>(list: &'a EnvList, name: &'_ OsStr) -> Option<&'a OsStr> {
			list.iter_tuples()
				.find_map(|(k, v)| if k == name { Some(v) } else { None })
		}
		get(self, name.as_ref())
	}

	/// Returns an iterator over all contained variables as [`EnvItem`]s.
	///
	/// The iteration happens in deterministic, but unspecified order.
	#[inline]
	pub fn iter(&self) -> Iter {
		self.0.iter()
	}

	/// Returns the count of environment variables in the list.
	#[inline]
	#[must_use]
	pub fn len(&self) -> usize {
		self.0.len()
	}

	/// Returns `true` if the environment list is empty.
	#[inline]
	#[must_use]
	pub fn is_empty(&self) -> bool {
		self.0.is_empty()
	}

	/// Returns an iterator over all contained variables as
	/// `(key: &OsStr, value: &OsStr)` tuples.
	///
	/// The iteration happens in deterministic, but unspecified order.
	///
	/// Provides compatibility with [`std::process::Command::envs()`].
	#[inline]
	pub fn iter_tuples(&self) -> TupleIter {
		TupleIter(self.0.iter())
	}
}

/// Display and string conversion of the environment list.
///
/// Also causes `.to_string()` to be implemented.
impl fmt::Display for EnvList {
	/// Formats the environment list as a multi-line string
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		for item in self.0.iter() {
			writeln!(f, "{}", item.as_cstr().to_string_lossy())?;
		}
		Ok(())
	}
}

/// Provide direct `for`-loop support.
///
/// See [`EnvList::iter()`].
impl<'a> IntoIterator for &'a EnvList {
	type Item = &'a EnvItem;
	type IntoIter = Iter<'a>;

	fn into_iter(self) -> Self::IntoIter {
		self.iter()
	}
}

/// Provide compatibility with the 3rd parameter of `nix::unistd::execve`.
impl AsRef<[EnvItem]> for EnvList {
	#[inline]
	fn as_ref(&self) -> &[EnvItem] {
		&self.0
	}
}

/// Conversion to a vector of (key, value) tuples.
impl From<EnvList> for Vec<(OsString, OsString)> {
	fn from(list: EnvList) -> Self {
		let mut vec = Vec::with_capacity(list.len());
		for (key, value) in list.iter_tuples() {
			vec.push((key.to_owned(), value.to_owned()));
		}
		vec
	}
}

/// Reference conversion to a vector of (&key, &value) tuples.
impl<'a> From<&'a EnvList> for Vec<(&'a OsStr, &'a OsStr)> {
	fn from(list: &'a EnvList) -> Self {
		list.iter_tuples().collect()
	}
}

/// Conversion to a vector of "key=value" `CString`s.
impl From<EnvList> for Vec<CString> {
	fn from(list: EnvList) -> Self {
		let mut vec = Vec::with_capacity(list.len());
		for item in list.0.iter() {
			vec.push(item.as_cstr().to_owned());
		}
		vec
	}
}

/// Reference conversion to a vector of "key=value" `&CStr`s.
impl<'a> From<&'a EnvList> for Vec<&'a CStr> {
	fn from(list: &'a EnvList) -> Self {
		let mut vec = Vec::with_capacity(list.len());
		for item in list.0.iter() {
			vec.push(item.as_cstr());
		}
		vec
	}
}

/// Conversion to a hash map
impl<S> From<EnvList> for HashMap<OsString, OsString, S>
where
	S: ::std::hash::BuildHasher + Default,
{
	fn from(list: EnvList) -> Self {
		let mut map = HashMap::<_, _, S>::with_capacity_and_hasher(list.len(), S::default());
		for (key, value) in list.iter_tuples() {
			map.insert(key.to_owned(), value.to_owned());
		}
		map
	}
}

/// Reference conversion to a referencing hash map
impl<'a, S> From<&'a EnvList> for HashMap<&'a OsStr, &'a OsStr, S>
where
	S: ::std::hash::BuildHasher + Default,
{
	fn from(list: &'a EnvList) -> Self {
		list.iter_tuples().collect()
	}
}

/// Indexing with `list[key]`
impl<T: AsRef<OsStr>> Index<T> for EnvList {
	type Output = OsStr;

	/// Returns a reference to the value of the named environment variable.
	///
	/// # Panics
	/// Panics if the environment variable is not present in the `EnvList`.
	fn index(&self, name: T) -> &Self::Output {
		self.get(name).expect("environment variable not found")
	}
}

/// Serializes as a list of (key, value) OsStr tuples.
#[cfg(feature = "serde")]
impl serde::Serialize for EnvList {
	fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
		self.0.serialize(serializer)
	}
}

/// Iterator over [`EnvItem`]s in an [`EnvList`].
pub type Iter<'a> = slice::Iter<'a, EnvItem>;

/// Iterator over [`EnvItem`]s converted to `(key: &OsStr, value: &OsStr)`
/// tuples.
///
/// Returned by [`EnvList::iter_tuples()`].
#[must_use]
#[derive(Debug)]
pub struct TupleIter<'a>(slice::Iter<'a, EnvItem>);

impl<'a> Iterator for TupleIter<'a> {
	type Item = (&'a OsStr, &'a OsStr);

	fn next(&mut self) -> Option<Self::Item> {
		self.0.next().map(EnvItem::key_value)
	}

	#[inline]
	fn size_hint(&self) -> (usize, Option<usize>) {
		self.0.size_hint()
	}
}

impl FusedIterator for TupleIter<'_> {}
impl ExactSizeIterator for TupleIter<'_> {}