blob: 9f660324697315e2e735019aed9b698d7dfa53b6 [file] [log] [blame]
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::path::Path;
fn err(msg: impl Into<String>) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::Other, msg.into())
}
pub fn is_path_owned_by_current_user(path: &Path) -> std::io::Result<bool> {
use windows::{
core::{Error, PCWSTR},
Win32::{
Foundation::{CloseHandle, LocalFree, BOOL, HANDLE, HLOCAL, PSID},
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},
},
};
let mut err_msg = None;
let mut is_owned = false;
if !path.exists() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("{:?} does not exist.", path),
));
}
// 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 mut folder_owner = PSID::default();
let mut pdescriptor = PSECURITY_DESCRIPTOR::default();
let result = GetNamedSecurityInfoW(
PCWSTR(to_wide_path(path).as_ptr()),
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION,
Some(&mut folder_owner),
None,
None,
None,
&mut pdescriptor,
);
// Workaround for https://github.com/microsoft/win32metadata/issues/884
if result.is_ok() {
let mut token = HANDLE::default();
// Use the current thread token if possible, otherwise open the process token
OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, true, &mut token)
.or_else(|_| OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token))?;
let mut buffer_size = 0;
let mut buffer = Vec::<u8>::new();
GetTokenInformation(token, TokenOwner, None, 0, &mut buffer_size).ok();
if buffer_size != 0 {
buffer.resize(buffer_size as usize, 0);
match GetTokenInformation(
token,
TokenOwner,
Some(buffer.as_mut_ptr() as *mut std::ffi::c_void),
buffer_size,
&mut buffer_size,
) {
Ok(()) => {
let token_owner = buffer.as_ptr() as *const TOKEN_OWNER;
let token_owner = (*token_owner).Owner;
is_owned = EqualSid(folder_owner, token_owner).is_ok();
// Admin-group owned folders are considered owned by the current user, if they are in the admin group
if !is_owned && IsWellKnownSid(token_owner, WinBuiltinAdministratorsSid).as_bool() {
let mut is_member = BOOL::default();
// TODO: re-use the handle
match CheckTokenMembership(HANDLE::default(), token_owner, &mut is_member) {
Err(e) => {
err_msg = Some(format!("Couldn't check if user is an administrator: {}", e))
}
Ok(()) => is_owned = is_member.as_bool(),
}
}
}
Err(err) => {
err_msg =
format!("Couldn't get actual token information for current process with err: {err}",)
.into();
}
}
} else {
err_msg = format!(
"Couldn't get token information size info for current process with err: {}",
Error::from_win32()
)
.into();
}
CloseHandle(token)?;
} else {
err_msg = format!(
"Couldn't get security information for path '{}' with err {}",
path.display(),
Error::from_win32()
)
.into();
}
LocalFree(HLOCAL(pdescriptor.0)).ok();
}
err_msg.map(|msg| Err(err(msg))).unwrap_or(Ok(is_owned))
}
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
}
}