use std::path::Path; #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]/// An account based identitypub 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<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)} // 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<bool> { 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<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 #[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<Vec<u8>> { 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<T: Copy>( token: windows_sys::Win32::Foundation::HANDLE, class: i32, class_name: &'static str, subject: &'static str, path: &Path, ) -> io::Result<T> { use windows_sys::Win32::Security::GetTokenInformation; #[expect(unsafe_code)] unsafe { let mut info = MaybeUninit::<T>::uninit(); let mut returned_size = 0; if GetTokenInformation( token, class, info.as_mut_ptr().cast(), mem::size_of::<T>() 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<bool> { 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::<TOKEN_USER>()); 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_ELEVATION_TYPE>( token, TokenElevationType, "TokenElevationType", "current token", path, )?; if elevation_type != TokenElevationTypeLimited { return Ok(false); } let linked_token_info = fixed_size_token_information::<TOKEN_LINKED_TOKEN>( 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<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 }}
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<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)
}
// 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<bool> {
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<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
#[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<Vec<u8>> {
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<T: Copy>(
token: windows_sys::Win32::Foundation::HANDLE,
class: i32,
class_name: &'static str,
subject: &'static str,
path: &Path,
) -> io::Result<T> {
use windows_sys::Win32::Security::GetTokenInformation;
#[expect(unsafe_code)]
unsafe {
let mut info = MaybeUninit::<T>::uninit();
let mut returned_size = 0;
if GetTokenInformation(
token,
class,
info.as_mut_ptr().cast(),
mem::size_of::<T>() 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<bool> {
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::<TOKEN_USER>());
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_ELEVATION_TYPE>(
token,
TokenElevationType,
"TokenElevationType",
"current token",
path,
)?;
if elevation_type != TokenElevationTypeLimited {
return Ok(false);
}
let linked_token_info = fixed_size_token_information::<TOKEN_LINKED_TOKEN>(
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<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
}
}