use std::ffi::{OsStr, OsString};
use std::io;
use std::mem::MaybeUninit;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::path::Path;
use windows::Win32::Foundation::HLOCAL;
use windows::Win32::Security::{self, ACL, Authorization::TRUSTEE_W, PSID};
use super::{Owner, Permissions};
const BUF_SIZE: u32 = 256;
pub fn get_file_data(path: &Path) -> Result<(Owner, Permissions), io::Error> {
let windows_path = buf_from_os(path.as_os_str());
let mut owner_sid_ptr = MaybeUninit::uninit();
let mut group_sid_ptr = MaybeUninit::uninit();
let mut dacl_ptr = MaybeUninit::uninit();
let mut sd_ptr = MaybeUninit::uninit();
let error_code = unsafe {
Security::Authorization::GetNamedSecurityInfoW(
windows::core::PCWSTR::from_raw(windows_path.as_ptr()),
Security::Authorization::SE_FILE_OBJECT,
Security::OWNER_SECURITY_INFORMATION
| Security::GROUP_SECURITY_INFORMATION
| Security::DACL_SECURITY_INFORMATION,
Some(owner_sid_ptr.as_mut_ptr()),
Some(group_sid_ptr.as_mut_ptr()),
Some(dacl_ptr.as_mut_ptr()),
None,
sd_ptr.as_mut_ptr(),
)
};
if error_code.is_err() {
return Err(std::io::Error::from_raw_os_error(error_code.0 as i32));
}
let owner_sid_ptr = unsafe { owner_sid_ptr.assume_init() };
let group_sid_ptr = unsafe { group_sid_ptr.assume_init() };
let dacl_ptr = unsafe { dacl_ptr.assume_init() };
let sd_ptr = unsafe { sd_ptr.assume_init() };
let owner = match unsafe { lookup_account_sid(owner_sid_ptr) } {
Ok((n, d)) => {
let owner_name = os_from_buf(&n);
let owner_domain = os_from_buf(&d);
format!(
"{}\\{}",
owner_domain.to_string_lossy(),
&owner_name.to_string_lossy()
)
}
Err(_) => String::from("-"),
};
let group = match unsafe { lookup_account_sid(group_sid_ptr) } {
Ok((n, d)) => {
let group_name = os_from_buf(&n);
let group_domain = os_from_buf(&d);
format!(
"{}\\{}",
group_domain.to_string_lossy(),
&group_name.to_string_lossy()
)
}
Err(_) => String::from("-"),
};
let owner = Owner::new(owner, group);
let mut world_sid_len: u32 = unsafe { Security::GetSidLengthRequired(1) };
let mut world_sid = vec![0u8; world_sid_len as usize];
let world_sid_ptr = PSID(world_sid.as_mut_ptr() as *mut _);
let result = unsafe {
Security::CreateWellKnownSid(
Security::WinWorldSid,
None,
Some(world_sid_ptr),
&mut world_sid_len,
)
};
if result.is_err() {
unsafe {
windows::Win32::Foundation::LocalFree(Some(HLOCAL(sd_ptr.0)));
}
return Err(io::Error::from_raw_os_error(unsafe {
windows::Win32::Foundation::GetLastError().0
} as i32));
}
let owner_trustee = unsafe { trustee_from_sid(owner_sid_ptr) };
let group_trustee = unsafe { trustee_from_sid(group_sid_ptr) };
let world_trustee = unsafe { trustee_from_sid(world_sid_ptr) };
let owner_access_mask = unsafe { get_acl_access_mask(dacl_ptr, &owner_trustee) }?;
let group_access_mask = unsafe { get_acl_access_mask(dacl_ptr, &group_trustee) }?;
let world_access_mask = unsafe { get_acl_access_mask(dacl_ptr, &world_trustee) }?;
let permissions = {
use windows::Win32::Storage::FileSystem::{
FILE_ACCESS_RIGHTS, FILE_GENERIC_EXECUTE, FILE_GENERIC_READ, FILE_GENERIC_WRITE,
};
let has_bit = |field: u32, bit: FILE_ACCESS_RIGHTS| field & bit.0 != 0;
Permissions {
user_read: has_bit(owner_access_mask, FILE_GENERIC_READ),
user_write: has_bit(owner_access_mask, FILE_GENERIC_WRITE),
user_execute: has_bit(owner_access_mask, FILE_GENERIC_EXECUTE),
group_read: has_bit(group_access_mask, FILE_GENERIC_READ),
group_write: has_bit(group_access_mask, FILE_GENERIC_WRITE),
group_execute: has_bit(group_access_mask, FILE_GENERIC_EXECUTE),
other_read: has_bit(world_access_mask, FILE_GENERIC_READ),
other_write: has_bit(world_access_mask, FILE_GENERIC_WRITE),
other_execute: has_bit(world_access_mask, FILE_GENERIC_EXECUTE),
sticky: false,
setuid: false,
setgid: false,
}
};
unsafe {
windows::Win32::Foundation::LocalFree(Some(HLOCAL(sd_ptr.0)));
}
Ok((owner, permissions))
}
unsafe fn get_acl_access_mask(
acl_ptr: *const ACL,
trustee_ptr: *const TRUSTEE_W,
) -> Result<u32, io::Error> {
let mut access_mask = 0;
let err_code = unsafe {
Security::Authorization::GetEffectiveRightsFromAclW(acl_ptr, trustee_ptr, &mut access_mask)
};
if err_code.is_ok() {
Ok(access_mask)
} else {
Err(io::Error::from_raw_os_error(err_code.0 as i32))
}
}
unsafe fn trustee_from_sid<P: Into<PSID>>(sid_ptr: P) -> TRUSTEE_W {
let mut trustee = TRUSTEE_W::default();
unsafe {
Security::Authorization::BuildTrusteeWithSidW(&mut trustee, Some(sid_ptr.into()));
}
trustee
}
unsafe fn lookup_account_sid(sid: PSID) -> Result<(Vec<u16>, Vec<u16>), std::io::Error> {
let mut name_size: u32 = BUF_SIZE;
let mut domain_size: u32 = BUF_SIZE;
loop {
let mut name: Vec<u16> = vec![0; name_size as usize];
let mut domain: Vec<u16> = vec![0; domain_size as usize];
let old_name_size = name_size;
let old_domain_size = domain_size;
let mut sid_name_use = MaybeUninit::uninit();
let result = unsafe {
Security::LookupAccountSidW(
None,
sid,
Some(windows::core::PWSTR(name.as_mut_ptr())),
&mut name_size,
Some(windows::core::PWSTR(domain.as_mut_ptr())),
&mut domain_size,
sid_name_use.as_mut_ptr(),
)
};
if result.is_ok() {
return Ok((name, domain));
} else if name_size != old_name_size || domain_size != old_domain_size {
continue;
} else {
return Err(io::Error::from_raw_os_error(unsafe {
windows::Win32::Foundation::GetLastError().0 as i32
}));
}
}
}
fn os_from_buf(buf: &[u16]) -> OsString {
OsString::from_wide(
&buf.iter()
.cloned()
.take_while(|&n| n != 0)
.collect::<Vec<u16>>(),
)
}
fn buf_from_os(os: &OsStr) -> Vec<u16> {
let mut buf: Vec<u16> = os.encode_wide().collect();
buf.push(0);
buf
}
#[inline]
fn has_path_attribute(
path: &Path,
flags: windows::Win32::Storage::FileSystem::FILE_FLAGS_AND_ATTRIBUTES,
) -> bool {
let windows_path = buf_from_os(path.as_os_str());
let file_attributes = unsafe {
windows::Win32::Storage::FileSystem::GetFileAttributesW(windows::core::PCWSTR(
windows_path.as_ptr(),
))
};
file_attributes & flags.0 > 0
}
pub fn is_path_hidden(path: &Path) -> bool {
has_path_attribute(
path,
windows::Win32::Storage::FileSystem::FILE_ATTRIBUTE_HIDDEN,
)
}
pub fn is_path_system(path: &Path) -> bool {
has_path_attribute(
path,
windows::Win32::Storage::FileSystem::FILE_ATTRIBUTE_SYSTEM,
)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn basic_wtf16_behavior() {
let basic_os = OsString::from("TeSt");
let basic_buf = vec![0x54, 0x65, 0x53, 0x74, 0x00];
let basic_buf_nuls = vec![0x54, 0x65, 0x53, 0x74, 0x00, 0x00, 0x00, 0x00];
assert_eq!(os_from_buf(&basic_buf), basic_os);
assert_eq!(buf_from_os(&basic_os), basic_buf);
assert_eq!(os_from_buf(&basic_buf_nuls), basic_os);
let unicode_os = OsString::from("💩");
let unicode_buf = vec![0xd83d, 0xdca9, 0x0];
let unicode_buf_nuls = vec![0xd83d, 0xdca9, 0x0, 0x0, 0x0, 0x0, 0x0];
assert_eq!(os_from_buf(&unicode_buf), unicode_os);
assert_eq!(buf_from_os(&unicode_os), unicode_buf);
assert_eq!(os_from_buf(&unicode_buf_nuls), unicode_os);
}
#[test]
fn every_wtf16_codepair_roundtrip() {
for lsb in 0..256u16 {
let mut vec: Vec<u16> = Vec::with_capacity(257);
for msb in 0..=256u16 {
let val = (msb << 8) | lsb;
if val != 0 {
vec.push(val)
}
}
vec.push(0);
let os = os_from_buf(&vec);
let new_vec = buf_from_os(&os);
assert_eq!(&vec, &new_vec);
}
}
}