diff options
Diffstat (limited to 'vendor/security-framework/src/authorization.rs')
-rw-r--r-- | vendor/security-framework/src/authorization.rs | 819 |
1 files changed, 819 insertions, 0 deletions
diff --git a/vendor/security-framework/src/authorization.rs b/vendor/security-framework/src/authorization.rs new file mode 100644 index 000000000..f19f8ecc9 --- /dev/null +++ b/vendor/security-framework/src/authorization.rs @@ -0,0 +1,819 @@ +//! Authorization Services support. + +/// # Potential improvements +/// +/// * When generic specialization stabilizes prevent copying from `CString` +/// arguments. +/// * `AuthorizationCopyRightsAsync` +/// * Provide constants for well known item names +use crate::base::{Error, Result}; +#[cfg(all(target_os = "macos", feature = "job-bless"))] +use core_foundation::base::Boolean; +use core_foundation::base::{CFTypeRef, TCFType}; +use core_foundation::bundle::CFBundleRef; +use core_foundation::dictionary::{CFDictionary, CFDictionaryRef}; +#[cfg(all(target_os = "macos", feature = "job-bless"))] +use core_foundation::error::CFError; +#[cfg(all(target_os = "macos", feature = "job-bless"))] +use core_foundation::error::CFErrorRef; +use core_foundation::string::{CFString, CFStringRef}; +use security_framework_sys::authorization as sys; +use security_framework_sys::base::errSecConversionError; +use std::convert::TryFrom; +use std::ffi::{CStr, CString}; +use std::fs::File; +use std::mem::MaybeUninit; +use std::os::raw::c_void; +use std::ptr::addr_of; +use std::{convert::TryInto, marker::PhantomData}; +use sys::AuthorizationExternalForm; + +macro_rules! optional_str_to_cfref { + ($string:ident) => {{ + $string + .map(CFString::new) + .map_or(std::ptr::null(), |cfs| cfs.as_concrete_TypeRef()) + }}; +} + +macro_rules! cstring_or_err { + ($x:expr) => {{ + CString::new($x).map_err(|_| Error::from_code(errSecConversionError)) + }}; +} + +bitflags::bitflags! { + /// The flags used to specify authorization options. + pub struct Flags: sys::AuthorizationFlags { + /// An empty flag set that you use as a placeholder when you don't want + /// any of the other flags. + const DEFAULTS = sys::kAuthorizationFlagDefaults; + + /// A flag that permits user interaction as needed. + const INTERACTION_ALLOWED = sys::kAuthorizationFlagInteractionAllowed; + + /// A flag that permits the Security Server to attempt to grant the + /// rights requested. + const EXTEND_RIGHTS = sys::kAuthorizationFlagExtendRights; + + /// A flag that permits the Security Server to grant rights on an + /// individual basis. + const PARTIAL_RIGHTS = sys::kAuthorizationFlagPartialRights; + + /// A flag that instructs the Security Server to revoke authorization. + const DESTROY_RIGHTS = sys::kAuthorizationFlagDestroyRights; + + /// A flag that instructs the Security Server to preauthorize the rights + /// requested. + const PREAUTHORIZE = sys::kAuthorizationFlagPreAuthorize; + } +} + +impl Default for Flags { + #[inline(always)] + fn default() -> Flags { + Flags::DEFAULTS + } +} + +/// Information about an authorization right or the environment. +#[repr(C)] +pub struct AuthorizationItem(sys::AuthorizationItem); + +impl AuthorizationItem { + /// The required name of the authorization right or environment data. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + #[must_use] pub fn name(&self) -> &str { + unsafe { + CStr::from_ptr(self.0.name) + .to_str() + .expect("AuthorizationItem::name failed to convert &str to CStr") + } + } + + /// The information pertaining to the name field. Do not rely on NULL + /// termination of string data. + #[inline] + #[must_use] pub fn value(&self) -> Option<&[u8]> { + if self.0.value.is_null() { + return None; + } + + let value = + unsafe { std::slice::from_raw_parts(self.0.value as *const u8, self.0.valueLength) }; + + Some(value) + } +} + +/// A set of authorization items returned and owned by the Security Server. +#[derive(Debug)] +#[repr(C)] +pub struct AuthorizationItemSet<'a> { + inner: *const sys::AuthorizationItemSet, + phantom: PhantomData<&'a sys::AuthorizationItemSet>, +} + +impl<'a> Drop for AuthorizationItemSet<'a> { + #[inline] + fn drop(&mut self) { + unsafe { + sys::AuthorizationFreeItemSet(self.inner as *mut sys::AuthorizationItemSet); + } + } +} + +/// Used by `AuthorizationItemSetBuilder` to store data pointed to by +/// `sys::AuthorizationItemSet`. +#[derive(Debug)] +pub struct AuthorizationItemSetStorage { + /// The layout of this is a little awkward because of the requirements of + /// Apple's APIs. `items` contains pointers to data owned by `names` and + /// `values`, so we must not modify them once `items` has been set up. + names: Vec<CString>, + values: Vec<Option<Vec<u8>>>, + items: Vec<sys::AuthorizationItem>, + + /// Must not be given to APIs which would attempt to modify it. + /// + /// See `AuthorizationItemSet` for sets owned by the Security Server which + /// are writable. + pub set: sys::AuthorizationItemSet, +} + +impl Default for AuthorizationItemSetStorage { + #[inline] + fn default() -> Self { + AuthorizationItemSetStorage { + names: Vec::new(), + values: Vec::new(), + items: Vec::new(), + set: sys::AuthorizationItemSet { + count: 0, + items: std::ptr::null_mut(), + }, + } + } +} + +/// A convenience `AuthorizationItemSetBuilder` builder which enabled you to use +/// rust types. All names and values passed in will be copied. +#[derive(Debug, Default)] +pub struct AuthorizationItemSetBuilder { + storage: AuthorizationItemSetStorage, +} + +// Stores AuthorizationItems contiguously, and their items separately +impl AuthorizationItemSetBuilder { + /// Creates a new `AuthorizationItemSetStore`, which simplifies creating + /// owned vectors of `AuthorizationItem`s. + #[inline(always)] + #[must_use] + pub fn new() -> AuthorizationItemSetBuilder { + Default::default() + } + + /// Adds an `AuthorizationItem` with the name set to a right and an empty + /// value. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn add_right<N: Into<Vec<u8>>>(mut self, name: N) -> Result<Self> { + self.storage.names.push(cstring_or_err!(name)?); + self.storage.values.push(None); + Ok(self) + } + + /// Adds an `AuthorizationItem` with arbitrary data. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn add_data<N, V>(mut self, name: N, value: V) -> Result<Self> + where + N: Into<Vec<u8>>, + V: Into<Vec<u8>>, + { + self.storage.names.push(cstring_or_err!(name)?); + self.storage.values.push(Some(value.into())); + Ok(self) + } + + /// Adds an `AuthorizationItem` with NULL terminated string data. + /// + /// If `name` or `value` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn add_string<N, V>(mut self, name: N, value: V) -> Result<Self> + where + N: Into<Vec<u8>>, + V: Into<Vec<u8>>, + { + self.storage.names.push(cstring_or_err!(name)?); + self.storage + .values + .push(Some(cstring_or_err!(value)?.to_bytes().to_vec())); + Ok(self) + } + + /// Creates the `sys::AuthorizationItemSet`, and gives you ownership of the + /// data it points to. + #[must_use] + pub fn build(mut self) -> AuthorizationItemSetStorage { + self.storage.items = self + .storage + .names + .iter() + .zip(self.storage.values.iter()) + .map(|(n, v)| sys::AuthorizationItem { + name: n.as_ptr(), + value: v + .as_ref() + .map_or(std::ptr::null_mut(), |v| v.as_ptr() as *mut c_void), + valueLength: v.as_ref().map_or(0, |v| v.len()), + flags: 0, + }) + .collect(); + + self.storage.set = sys::AuthorizationItemSet { + count: self.storage.items.len() as u32, + items: self.storage.items.as_ptr() as *mut sys::AuthorizationItem, + }; + + self.storage + } +} + +/// Used by `Authorization::set_item` to define the rules of he right. +#[derive(Copy, Clone)] +pub enum RightDefinition<'a> { + /// The dictionary will contain the keys and values that define the rules. + FromDictionary(&'a CFDictionary<CFStringRef, CFTypeRef>), + + /// The specified right's rules will be duplicated. + FromExistingRight(&'a str), +} + +/// A wrapper around `AuthorizationCreate` and functions which operate on an +/// `AuthorizationRef`. +#[derive(Debug)] +pub struct Authorization { + handle: sys::AuthorizationRef, + free_flags: Flags, +} + +impl TryFrom<AuthorizationExternalForm> for Authorization { + type Error = Error; + + /// Internalizes the external representation of an authorization reference. + #[cold] + fn try_from(external_form: AuthorizationExternalForm) -> Result<Self> { + let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit(); + + let status = unsafe { + sys::AuthorizationCreateFromExternalForm(&external_form, handle.as_mut_ptr()) + }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from_code(status)); + } + + let auth = Authorization { + handle: unsafe { handle.assume_init() }, + free_flags: Default::default(), + }; + + Ok(auth) + } +} + +impl Authorization { + /// Creates an authorization object which has no environment or associated + /// rights. + #[inline] + pub fn default() -> Result<Self> { + Self::new(None, None, Default::default()) + } + + /// Creates an authorization reference and provides an option to authorize + /// or preauthorize rights. + /// + /// `rights` should be the names of the rights you want to create. + /// + /// `environment` is used when authorizing or preauthorizing rights. Not + /// used in OS X v10.2 and earlier. In macOS 10.3 and later, you can pass + /// icon or prompt data to be used in the authentication dialog box. In + /// macOS 10.4 and later, you can also pass a user name and password in + /// order to authorize a user without user interaction. + pub fn new( + // FIXME: this should have been by reference + rights: Option<AuthorizationItemSetStorage>, + environment: Option<AuthorizationItemSetStorage>, + flags: Flags, + ) -> Result<Self> { + let rights_ptr = rights.as_ref().map_or(std::ptr::null(), |r| { + addr_of!(r.set) as *const sys::AuthorizationItemSet + }); + + let env_ptr = environment.as_ref().map_or(std::ptr::null(), |e| { + addr_of!(e.set) as *const sys::AuthorizationItemSet + }); + + let mut handle = MaybeUninit::<sys::AuthorizationRef>::uninit(); + + let status = unsafe { + sys::AuthorizationCreate(rights_ptr, env_ptr, flags.bits(), handle.as_mut_ptr()) + }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from_code(status)); + } + + Ok(Authorization { + handle: unsafe { handle.assume_init() }, + free_flags: Default::default(), + }) + } + + /// Internalizes the external representation of an authorization reference. + #[deprecated(since = "2.0.1", note = "Please use the TryFrom trait instead")] + pub fn from_external_form(external_form: sys::AuthorizationExternalForm) -> Result<Self> { + external_form.try_into() + } + + /// By default the rights acquired will be retained by the Security Server. + /// Use this to ensure they are destroyed and to prevent shared rights' + /// continued used by other processes. + #[inline(always)] + pub fn destroy_rights(mut self) { + self.free_flags = Flags::DESTROY_RIGHTS; + } + + /// Retrieve's the right's definition as a dictionary. Use `right_exists` + /// if you want to avoid retrieving the dictionary. + /// + /// `name` can be a wildcard right name. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn get_right<T: Into<Vec<u8>>>(name: T) -> Result<CFDictionary<CFString, CFTypeRef>> { + let name = cstring_or_err!(name)?; + let mut dict = MaybeUninit::<CFDictionaryRef>::uninit(); + + let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), dict.as_mut_ptr()) }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from_code(status)); + } + + let dict = unsafe { CFDictionary::wrap_under_create_rule(dict.assume_init()) }; + + Ok(dict) + } + + /// Checks if a right exists within the policy database. This is the same as + /// `get_right`, but avoids a dictionary allocation. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn right_exists<T: Into<Vec<u8>>>(name: T) -> Result<bool> { + let name = cstring_or_err!(name)?; + + let status = unsafe { sys::AuthorizationRightGet(name.as_ptr(), std::ptr::null_mut()) }; + + Ok(status == sys::errAuthorizationSuccess) + } + + /// Removes a right from the policy database. + /// + /// `name` cannot be a wildcard right name. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn remove_right<T: Into<Vec<u8>>>(&self, name: T) -> Result<()> { + let name = cstring_or_err!(name)?; + + let status = unsafe { sys::AuthorizationRightRemove(self.handle, name.as_ptr()) }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from_code(status)); + } + + Ok(()) + } + + /// Creates or updates a right entry in the policy database. Your process + /// must have a code signature in order to be able to add rights to the + /// authorization database. + /// + /// `name` cannot be a wildcard right. + /// + /// `definition` can be either a `CFDictionaryRef` containing keys defining + /// the rules or a `CFStringRef` representing the name of another right + /// whose rules you wish to duplicaate. + /// + /// `description` is a key which can be used to look up localized + /// descriptions. + /// + /// `bundle` will be used to get localizations from if not the main bundle. + /// + /// `localeTableName` will be used to get localizations if provided. + /// + /// If `name` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn set_right<T: Into<Vec<u8>>>( + &self, + name: T, + definition: RightDefinition<'_>, + description: Option<&str>, + bundle: Option<CFBundleRef>, + locale: Option<&str>, + ) -> Result<()> { + let name = cstring_or_err!(name)?; + + let definition_cfstring: CFString; + let definition_ref = match definition { + RightDefinition::FromDictionary(def) => def.as_CFTypeRef(), + RightDefinition::FromExistingRight(def) => { + definition_cfstring = CFString::new(def); + definition_cfstring.as_CFTypeRef() + } + }; + + let status = unsafe { + sys::AuthorizationRightSet( + self.handle, + name.as_ptr(), + definition_ref, + optional_str_to_cfref!(description), + bundle.unwrap_or(std::ptr::null_mut()), + optional_str_to_cfref!(locale), + ) + }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from_code(status)); + } + + Ok(()) + } + + /// An authorization plugin can store the results of an authentication + /// operation by calling the `SetContextValue` function. You can then + /// retrieve this supporting data, such as the user name. + /// + /// `tag` should specify the type of data the Security Server should return. + /// If `None`, all available information is retreieved. + /// + /// If `tag` isn't convertable to a `CString` it will return + /// Err(errSecConversionError). + pub fn copy_info<T: Into<Vec<u8>>>(&self, tag: Option<T>) -> Result<AuthorizationItemSet<'_>> { + let tag_with_nul: CString; + + let tag_ptr = match tag { + Some(tag) => { + tag_with_nul = cstring_or_err!(tag)?; + tag_with_nul.as_ptr() + } + None => std::ptr::null(), + }; + + let mut inner = MaybeUninit::<*mut sys::AuthorizationItemSet>::uninit(); + + let status = + unsafe { sys::AuthorizationCopyInfo(self.handle, tag_ptr, inner.as_mut_ptr()) }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from(status)); + } + + let set = AuthorizationItemSet { + inner: unsafe { inner.assume_init() }, + phantom: PhantomData, + }; + + Ok(set) + } + + /// Creates an external representation of an authorization reference so that + /// you can transmit it between processes. + pub fn make_external_form(&self) -> Result<sys::AuthorizationExternalForm> { + let mut external_form = MaybeUninit::<sys::AuthorizationExternalForm>::uninit(); + + let status = + unsafe { sys::AuthorizationMakeExternalForm(self.handle, external_form.as_mut_ptr()) }; + + if status != sys::errAuthorizationSuccess { + return Err(Error::from(status)); + } + + Ok(unsafe { external_form.assume_init() }) + } + + /// Runs an executable tool with root privileges. + /// Discards executable's output + #[cfg(target_os = "macos")] + #[inline(always)] + pub fn execute_with_privileges<P, S, I>( + &self, + command: P, + arguments: I, + flags: Flags, + ) -> Result<()> + where + P: AsRef<std::path::Path>, + I: IntoIterator<Item = S>, + S: AsRef<std::ffi::OsStr>, + { + use std::os::unix::ffi::OsStrExt; + + let arguments = arguments + .into_iter().flat_map(|a| CString::new(a.as_ref().as_bytes())) + .collect::<Vec<_>>(); + self.execute_with_privileges_internal(command.as_ref().as_os_str().as_bytes(), &arguments, flags, false)?; + Ok(()) + } + + /// Runs an executable tool with root privileges, + /// and returns a `File` handle to its communication pipe + #[cfg(target_os = "macos")] + #[inline(always)] + pub fn execute_with_privileges_piped<P, S, I>( + &self, + command: P, + arguments: I, + flags: Flags, + ) -> Result<File> + where + P: AsRef<std::path::Path>, + I: IntoIterator<Item = S>, + S: AsRef<std::ffi::OsStr>, + { + use std::os::unix::ffi::OsStrExt; + + let arguments = arguments + .into_iter().flat_map(|a| CString::new(a.as_ref().as_bytes())) + .collect::<Vec<_>>(); + Ok(self.execute_with_privileges_internal(command.as_ref().as_os_str().as_bytes(), &arguments, flags, true)?.unwrap()) + } + + /// Submits the executable for the given label as a `launchd` job. + #[cfg(all(target_os = "macos", feature = "job-bless"))] + pub fn job_bless(&self, label: &str) -> Result<(), CFError> { + #[link(name = "ServiceManagement", kind = "framework")] + extern "C" { + static kSMDomainSystemLaunchd: CFStringRef; + + fn SMJobBless( + domain: CFStringRef, + executableLabel: CFStringRef, + auth: sys::AuthorizationRef, + error: *mut CFErrorRef, + ) -> Boolean; + } + + unsafe { + let mut error = std::ptr::null_mut(); + SMJobBless( + kSMDomainSystemLaunchd, + CFString::new(label).as_concrete_TypeRef(), + self.handle, + &mut error, + ); + if !error.is_null() { + return Err(CFError::wrap_under_create_rule(error)); + } + + Ok(()) + } + } + + // Runs an executable tool with root privileges. + #[cfg(target_os = "macos")] + fn execute_with_privileges_internal( + &self, + command: &[u8], + arguments: &[CString], + flags: Flags, + make_pipe: bool, + ) -> Result<Option<File>> { + use std::os::unix::io::{FromRawFd, RawFd}; + + let c_cmd = cstring_or_err!(command)?; + + let mut c_args = arguments.iter().map(|a| a.as_ptr() as _).collect::<Vec<_>>(); + c_args.push(std::ptr::null_mut()); + + let mut pipe: *mut libc::FILE = std::ptr::null_mut(); + + let status = unsafe { + sys::AuthorizationExecuteWithPrivileges( + self.handle, + c_cmd.as_ptr(), + flags.bits(), + c_args.as_ptr(), + if make_pipe { &mut pipe } else { std::ptr::null_mut() }, + ) + }; + + crate::cvt(status)?; + Ok(if make_pipe { + if pipe.is_null() { + return Err(Error::from_code(32)); // EPIPE? + } + Some(unsafe { File::from_raw_fd(libc::fileno(pipe) as RawFd) }) + } else { + None + }) + } +} + +impl Drop for Authorization { + #[inline] + fn drop(&mut self) { + unsafe { + sys::AuthorizationFree(self.handle, self.free_flags.bits()); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core_foundation::string::CFString; + + #[test] + fn test_create_default_authorization() { + Authorization::default().unwrap(); + } + + #[test] + fn test_create_allowed_authorization() -> Result<()> { + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.hdd.smart")? + .add_right("system.login.done")? + .build(); + + Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap(); + + Ok(()) + } + + #[test] + fn test_create_then_destroy_allowed_authorization() -> Result<()> { + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.hdd.smart")? + .add_right("system.login.done")? + .build(); + + let auth = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap(); + auth.destroy_rights(); + + Ok(()) + } + + #[test] + fn test_create_authorization_requiring_interaction() -> Result<()> { + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.privilege.admin")? + .build(); + + let error = Authorization::new(Some(rights), None, Flags::EXTEND_RIGHTS).unwrap_err(); + + assert_eq!(error.code(), sys::errAuthorizationInteractionNotAllowed); + + Ok(()) + } + + fn create_credentials_env() -> Result<AuthorizationItemSetStorage> { + let set = AuthorizationItemSetBuilder::new() + .add_string( + "username", + option_env!("USER").expect("You must set the USER environment variable"), + )? + .add_string( + "password", + option_env!("PASSWORD").expect("You must set the PASSWORD environment varible"), + )? + .build(); + + Ok(set) + } + + #[test] + fn test_create_authorization_with_bad_credentials() -> Result<()> { + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.privilege.admin")? + .build(); + + let env = AuthorizationItemSetBuilder::new() + .add_string("username", "Tim Apple")? + .add_string("password", "butterfly")? + .build(); + + let error = + Authorization::new(Some(rights), Some(env), Flags::INTERACTION_ALLOWED).unwrap_err(); + + assert_eq!(error.code(), sys::errAuthorizationDenied); + + Ok(()) + } + + #[test] + fn test_create_authorization_with_credentials() -> Result<()> { + if option_env!("PASSWORD").is_none() { + return Ok(()); + } + + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.privilege.admin")? + .build(); + + let env = create_credentials_env()?; + + Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap(); + + Ok(()) + } + + #[test] + fn test_query_authorization_database() -> Result<()> { + assert!(Authorization::right_exists("system.hdd.smart")?); + assert!(!Authorization::right_exists("EMPTY")?); + + let dict = Authorization::get_right("system.hdd.smart").unwrap(); + + let key = CFString::from_static_string("class"); + assert!(dict.contains_key(&key)); + + let invalid_key = CFString::from_static_string("EMPTY"); + assert!(!dict.contains_key(&invalid_key)); + + Ok(()) + } + + /// This test will only pass if its process has a valid code signature. + #[test] + fn test_modify_authorization_database() -> Result<()> { + if option_env!("PASSWORD").is_none() { + return Ok(()); + } + + let rights = AuthorizationItemSetBuilder::new() + .add_right("config.modify.")? + .build(); + + let env = create_credentials_env()?; + + let auth = Authorization::new(Some(rights), Some(env), Flags::EXTEND_RIGHTS).unwrap(); + + assert!(!Authorization::right_exists("TEST_RIGHT")?); + + auth.set_right( + "TEST_RIGHT", + RightDefinition::FromExistingRight("system.hdd.smart"), + None, + None, + None, + ) + .unwrap(); + + assert!(Authorization::right_exists("TEST_RIGHT")?); + + auth.remove_right("TEST_RIGHT").unwrap(); + + assert!(!Authorization::right_exists("TEST_RIGHT")?); + + Ok(()) + } + + /// This test will succeed if authorization popup is approved. + #[test] + fn test_execute_with_privileges() -> Result<()> { + if option_env!("PASSWORD").is_none() { + return Ok(()); + } + + let rights = AuthorizationItemSetBuilder::new() + .add_right("system.privilege.admin")? + .build(); + + let auth = Authorization::new( + Some(rights), + None, + Flags::DEFAULTS + | Flags::INTERACTION_ALLOWED + | Flags::PREAUTHORIZE + | Flags::EXTEND_RIGHTS, + )?; + + let file = auth.execute_with_privileges_piped("/bin/ls", ["/"], Flags::DEFAULTS)?; + + use std::io::{self, BufRead}; + for line in io::BufReader::new(file).lines() { + let _ = line.unwrap(); + } + + Ok(()) + } +} |