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, /// An OAuth refresh token that may accompany the password. It is to be treated confidentially, just like the password. pub oauth_refresh_token: Option, } /// 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 { impl_::is_path_owned_by_current_user(path) } // Wasi doesn't have a concept of a user, so this is implicitly true. #[cfg(target_os = "wasi")] mod impl_ { pub fn is_path_owned_by_current_user(_path: &std::path::Path) -> std::io::Result { Ok(true) } } #[cfg(all(not(windows), not(target_os = "wasi")))] mod impl_ { use std::path::Path; pub fn is_path_owned_by_current_user(path: &Path) -> std::io::Result { fn owner_from_path(path: &Path) -> std::io::Result { use std::os::unix::fs::MetadataExt; let meta = std::fs::symlink_metadata(path)?; Ok(meta.uid()) } fn owner_of_current_process() -> std::io::Result { // SAFETY: there is no documented possibility for failure #[expect(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, mem::MaybeUninit, os::windows::io::{AsRawHandle as _, 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(), format!("{}: {}", $msg, $inner))); }}; } fn token_information( token: windows_sys::Win32::Foundation::HANDLE, class: i32, class_name: &'static str, subject: &'static str, path: &Path, ) -> io::Result> { use windows_sys::Win32::{ Foundation::{ERROR_INSUFFICIENT_BUFFER, GetLastError}, Security::GetTokenInformation, }; #[expect(unsafe_code)] unsafe { let mut buffer_size = 36; let mut heap_buf = vec![0; 36]; loop { if GetTokenInformation( token, class, heap_buf.as_mut_ptr().cast(), heap_buf.len() as _, &mut buffer_size, ) != 0 { return Ok(heap_buf); } if GetLastError() != ERROR_INSUFFICIENT_BUFFER { error!(format!( "Couldn't acquire {class_name} for the {subject} while checking ownership of '{}'", path.display() )); } heap_buf.resize(buffer_size as _, 0); } } } /// Read a fixed-size token information record of type `T` with `GetTokenInformation`. /// /// Use this for token information classes whose result fits exactly into `T`. fn fixed_size_token_information( token: windows_sys::Win32::Foundation::HANDLE, class: i32, class_name: &'static str, subject: &'static str, path: &Path, ) -> io::Result { use windows_sys::Win32::Security::GetTokenInformation; #[expect(unsafe_code)] unsafe { let mut info = MaybeUninit::::uninit(); let mut returned_size = 0; if GetTokenInformation( token, class, info.as_mut_ptr().cast(), mem::size_of::() as u32, &mut returned_size, ) == 0 { error!(format!( "Couldn't acquire {class_name} for the {subject} while checking ownership of '{}'", path.display() )); } Ok(info.assume_init()) } } pub fn is_path_owned_by_current_user(path: &Path) -> io::Result { use windows_sys::Win32::{ Foundation::{ERROR_INVALID_FUNCTION, ERROR_SUCCESS, LocalFree}, Security::{ Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT}, CheckTokenMembership, EqualSid, IsWellKnownSid, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, TOKEN_ELEVATION_TYPE, TOKEN_LINKED_TOKEN, TOKEN_QUERY, TOKEN_USER, TokenElevationType, TokenElevationTypeLimited, TokenLinkedToken, TokenUser, WinBuiltinAdministratorsSid, }, 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); } #[expect(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 { if result == ERROR_INVALID_FUNCTION { // We cannot obtain security information, so we default to reduced trust // (false) rather than failing completely. return Ok(false); } let inner = io::Error::from_raw_os_error(result as _); error!( inner, format!("Couldn't get security information for path '{}'", path.display()) ); } (folder_owner.assume_init(), pdescriptor.assume_init()) }; struct Descriptor(PSECURITY_DESCRIPTOR); impl Drop for Descriptor { fn drop(&mut self) { #[expect(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!(format!( "Couldn't acquire a thread or process token while checking ownership of '{}'", path.display() )); } token.assume_init() }; let _owned_token = OwnedHandle::from_raw_handle(token as _); let user_info_buf = token_information(token, TokenUser, "TokenUser", "current token", path)?; let token_user_info = ptr::read_unaligned(user_info_buf.as_ptr().cast::()); let token_user = token_user_info.User.Sid; if EqualSid(folder_owner, token_user) != 0 { return Ok(true); } // Admin-group owned folders are considered owned by the current user, if they are in the admin group. if IsWellKnownSid(folder_owner, WinBuiltinAdministratorsSid) == 0 { return Ok(false); } let mut is_member = 0; if CheckTokenMembership(std::ptr::null_mut(), folder_owner, &mut is_member) == 0 { error!(format!( "Couldn't check whether the current token is in the Administrators group while checking ownership of '{}'", path.display() )); } if is_member != 0 { return Ok(true); } let elevation_type = fixed_size_token_information::( token, TokenElevationType, "TokenElevationType", "current token", path, )?; if elevation_type != TokenElevationTypeLimited { return Ok(false); } let linked_token_info = fixed_size_token_information::( token, TokenLinkedToken, "TokenLinkedToken", "limited current token", path, )?; let linked_token = linked_token_info.LinkedToken; let linked_token = OwnedHandle::from_raw_handle(linked_token as _); let mut is_member = 0; if CheckTokenMembership(linked_token.as_raw_handle() as _, folder_owner, &mut is_member) == 0 { error!(format!( "Couldn't check whether the linked elevated token is in the Administrators group while checking ownership of '{}'", path.display() )); } Ok(is_member != 0) } } fn to_wide_path(path: impl AsRef) -> Vec { 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 } }