pam_client2/env_list.rs
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<'_> {}