| use std::path::Path; |
| |
| #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] |
| #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
| /// An account based identity |
| pub struct Account { |
| /// The user's name |
| pub username: String, |
| /// The user's password |
| pub password: String, |
| } |
| |
| /// Returns true if the given `path` is owned by the user who is executing the current process. |
| /// |
| /// Note that this method is very specific to avoid having to deal with any operating system types. |
| pub fn is_path_owned_by_current_user(path: &Path) -> std::io::Result<bool> { |
| impl_::is_path_owned_by_current_user(path) |
| } |
| |
| #[cfg(not(windows))] |
| mod impl_ { |
| use std::path::Path; |
| |
| pub fn is_path_owned_by_current_user(path: &Path) -> std::io::Result<bool> { |
| fn owner_from_path(path: &Path) -> std::io::Result<u32> { |
| use std::os::unix::fs::MetadataExt; |
| let meta = std::fs::symlink_metadata(path)?; |
| Ok(meta.uid()) |
| } |
| |
| fn owner_of_current_process() -> std::io::Result<u32> { |
| // SAFETY: there is no documented possibility for failure |
| #[allow(unsafe_code)] |
| let uid = unsafe { libc::geteuid() }; |
| Ok(uid) |
| } |
| use std::str::FromStr; |
| |
| let owner_of_path = owner_from_path(path)?; |
| let owner_of_process = owner_of_current_process()?; |
| if owner_of_path == owner_of_process { |
| Ok(true) |
| } else if let Some(sudo_uid) = |
| std::env::var_os("SUDO_UID").and_then(|val| val.to_str().and_then(|val_str| u32::from_str(val_str).ok())) |
| { |
| Ok(owner_of_path == sudo_uid) |
| } else { |
| Ok(false) |
| } |
| } |
| } |
| |
| #[cfg(windows)] |
| mod impl_ { |
| use std::{ |
| io, |
| mem::MaybeUninit, |
| os::windows::io::{FromRawHandle as _, OwnedHandle}, |
| path::Path, |
| ptr, |
| }; |
| |
| macro_rules! error { |
| ($msg:expr) => {{ |
| let inner = io::Error::last_os_error(); |
| error!(inner, $msg); |
| }}; |
| ($inner:expr, $msg:expr) => {{ |
| return Err(io::Error::new($inner.kind(), $msg)); |
| }}; |
| } |
| |
| pub fn is_path_owned_by_current_user(path: &Path) -> io::Result<bool> { |
| use windows_sys::Win32::{ |
| Foundation::{GetLastError, LocalFree, ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS}, |
| Security::{ |
| Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT}, |
| CheckTokenMembership, EqualSid, GetTokenInformation, IsWellKnownSid, TokenOwner, |
| WinBuiltinAdministratorsSid, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, TOKEN_OWNER, |
| TOKEN_QUERY, |
| }, |
| System::Threading::{GetCurrentProcess, GetCurrentThread, OpenProcessToken, OpenThreadToken}, |
| }; |
| |
| if !path.exists() { |
| return Err(io::Error::new( |
| io::ErrorKind::NotFound, |
| format!("{path:?} does not exist."), |
| )); |
| } |
| |
| // Home is not actually owned by the corresponding user |
| // but it can be considered de-facto owned by the user |
| // Ignore errors here and just do the regular checks below |
| if gix_path::realpath(path).ok() == gix_path::env::home_dir() { |
| return Ok(true); |
| } |
| |
| #[allow(unsafe_code)] |
| unsafe { |
| let (folder_owner, descriptor) = { |
| let mut folder_owner = MaybeUninit::uninit(); |
| let mut pdescriptor = MaybeUninit::uninit(); |
| let result = GetNamedSecurityInfoW( |
| to_wide_path(path).as_ptr(), |
| SE_FILE_OBJECT, |
| OWNER_SECURITY_INFORMATION, |
| folder_owner.as_mut_ptr(), |
| ptr::null_mut(), |
| ptr::null_mut(), |
| ptr::null_mut(), |
| pdescriptor.as_mut_ptr(), |
| ); |
| |
| if result != ERROR_SUCCESS { |
| let inner = io::Error::from_raw_os_error(result as _); |
| error!( |
| inner, |
| format!( |
| "Couldn't get security information for path '{}' with err {inner}", |
| path.display() |
| ) |
| ); |
| } |
| |
| (folder_owner.assume_init(), pdescriptor.assume_init()) |
| }; |
| |
| struct Descriptor(PSECURITY_DESCRIPTOR); |
| |
| impl Drop for Descriptor { |
| fn drop(&mut self) { |
| #[allow(unsafe_code)] |
| // SAFETY: syscall only invoked if we have a valid descriptor |
| unsafe { |
| LocalFree(self.0 as _); |
| } |
| } |
| } |
| |
| let _descriptor = Descriptor(descriptor); |
| |
| let token = { |
| let mut token = MaybeUninit::uninit(); |
| |
| // Use the current thread token if possible, otherwise open the process token |
| if OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, 1, token.as_mut_ptr()) == 0 |
| && OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token.as_mut_ptr()) == 0 |
| { |
| error!("Couldn't acquire thread or process token"); |
| } |
| token.assume_init() |
| }; |
| |
| let _owned_token = OwnedHandle::from_raw_handle(token as _); |
| |
| let buf = 'token_buf: { |
| let mut buffer_size = 36; |
| let mut heap_buf = vec![0; 36]; |
| |
| loop { |
| if GetTokenInformation( |
| token, |
| TokenOwner, |
| heap_buf.as_mut_ptr().cast(), |
| heap_buf.len() as _, |
| &mut buffer_size, |
| ) != 0 |
| { |
| break 'token_buf heap_buf; |
| } |
| |
| if GetLastError() != ERROR_INSUFFICIENT_BUFFER { |
| error!("Couldn't acquire token ownership"); |
| } |
| |
| heap_buf.resize(buffer_size as _, 0); |
| } |
| }; |
| |
| let token_owner = (*buf.as_ptr().cast::<TOKEN_OWNER>()).Owner; |
| |
| // If the current user is the owner of the parent folder then they also |
| // own this file |
| if EqualSid(folder_owner, token_owner) != 0 { |
| return Ok(true); |
| } |
| |
| // Admin-group owned folders are considered owned by the current user, if they are in the admin group |
| if IsWellKnownSid(token_owner, WinBuiltinAdministratorsSid) == 0 { |
| return Ok(false); |
| } |
| |
| let mut is_member = 0; |
| if CheckTokenMembership(0, token_owner, &mut is_member) == 0 { |
| error!("Couldn't check if user is an administrator"); |
| } |
| |
| Ok(is_member != 0) |
| } |
| } |
| |
| fn to_wide_path(path: impl AsRef<Path>) -> Vec<u16> { |
| use std::os::windows::ffi::OsStrExt; |
| let mut wide_path: Vec<_> = path.as_ref().as_os_str().encode_wide().collect(); |
| wide_path.push(0); |
| wide_path |
| } |
| } |