use std::sync::atomic::{AtomicBool, Ordering};
use windows::Win32::Security::{
AddAccessAllowedAceEx, InitializeAcl, InitializeSecurityDescriptor, LookupAccountNameW,
SetFileSecurityW, SetSecurityDescriptorDacl, ACE_REVISION, ACL, CONTAINER_INHERIT_ACE,
DACL_SECURITY_INFORMATION, OBJECT_INHERIT_ACE, PSECURITY_DESCRIPTOR, PSID, SECURITY_DESCRIPTOR,
SID_NAME_USE,
};
const SECURITY_DESCRIPTOR_REVISION: u32 = 1;
const FILE_ALL_ACCESS: u32 = 0x001F01FF;
const FILE_MODIFY_ACCESS: u32 = 0x001301FF;
const FILE_READ_EXECUTE_ACCESS: u32 = 0x001200A9;
const ERROR_NONE_MAPPED: u32 = 1332;
const LOOKUP_MAX_RETRIES: u32 = 3;
static LSA_AVAILABLE: AtomicBool = AtomicBool::new(false);
fn is_error_none_mapped(err: &windows::core::Error) -> bool {
let code = err.code().0 as u32;
code == ERROR_NONE_MAPPED || code == (0x8007_0000 | ERROR_NONE_MAPPED)
}
fn lsa_is_available() -> bool {
if LSA_AVAILABLE.load(Ordering::Relaxed) {
return true;
}
let probed = match crate::win32::get_process_user() {
Ok(user) => {
let name_w: Vec<u16> = user.encode_utf16().chain(std::iter::once(0)).collect();
lookup_sid_once(&name_w).is_ok()
}
Err(_) => false,
};
if probed {
LSA_AVAILABLE.store(true, Ordering::Relaxed);
}
probed
}
pub fn lookup_sid(principal: &str) -> Result<Vec<u8>, String> {
let name_w: Vec<u16> = principal.encode_utf16().chain(std::iter::once(0)).collect();
for attempt in 0..LOOKUP_MAX_RETRIES {
match lookup_sid_once(&name_w) {
Ok(sid) => {
LSA_AVAILABLE.store(true, Ordering::Relaxed);
return Ok(sid);
}
Err(e) => {
let lsa_still_initializing = is_error_none_mapped(&e) && !lsa_is_available();
if lsa_still_initializing && attempt < LOOKUP_MAX_RETRIES - 1 {
std::thread::sleep(std::time::Duration::from_secs(1 << attempt));
continue;
}
return Err(format!("Could not look up account '{principal}': {e}"));
}
}
}
unreachable!("loop returns on the final attempt")
}
fn lookup_sid_once(name_w: &[u16]) -> Result<Vec<u8>, windows::core::Error> {
let mut sid_size: u32 = 0;
let mut domain_size: u32 = 0;
let mut sid_type = SID_NAME_USE::default();
let size_query = unsafe {
LookupAccountNameW(
None,
windows::core::PCWSTR(name_w.as_ptr()),
Some(PSID(std::ptr::null_mut())),
&mut sid_size,
Some(windows::core::PWSTR(std::ptr::null_mut())),
&mut domain_size,
&mut sid_type,
)
};
if sid_size == 0 {
return Err(size_query
.err()
.unwrap_or_else(windows::core::Error::from_thread));
}
let mut sid_buf = vec![0u8; sid_size as usize];
let mut domain_buf = vec![0u16; domain_size as usize];
unsafe {
LookupAccountNameW(
None,
windows::core::PCWSTR(name_w.as_ptr()),
Some(PSID(sid_buf.as_mut_ptr() as *mut _)),
&mut sid_size,
Some(windows::core::PWSTR(domain_buf.as_mut_ptr())),
&mut domain_size,
&mut sid_type,
)?;
}
Ok(sid_buf)
}
fn build_dacl(
principals_full_control: &[&str],
principals_modify_access: &[&str],
principals_read_execute: &[&str],
) -> Result<Vec<u8>, String> {
let inherit = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
let mut ace_entries: Vec<(Vec<u8>, u32)> = Vec::new();
for &principal in principals_full_control {
ace_entries.push((lookup_sid(principal)?, FILE_ALL_ACCESS));
}
for &principal in principals_modify_access {
ace_entries.push((lookup_sid(principal)?, FILE_MODIFY_ACCESS));
}
for &principal in principals_read_execute {
ace_entries.push((lookup_sid(principal)?, FILE_READ_EXECUTE_ACCESS));
}
let acl_header_size = std::mem::size_of::<ACL>();
let mut acl_size = acl_header_size;
for (sid, _) in &ace_entries {
let ace_size = 8 + sid.len();
acl_size += (ace_size + 3) & !3; }
let mut acl_buf = vec![0u8; acl_size];
unsafe {
InitializeAcl(
acl_buf.as_mut_ptr() as *mut ACL,
acl_size as u32,
ACE_REVISION(2),
)
.map_err(|e| format!("InitializeAcl failed: {e}"))?;
}
for (sid, mask) in &ace_entries {
unsafe {
AddAccessAllowedAceEx(
acl_buf.as_mut_ptr() as *mut ACL,
ACE_REVISION(2),
inherit,
*mask,
PSID(sid.as_ptr() as *mut _),
)
.map_err(|e| format!("AddAccessAllowedAceEx failed: {e}"))?;
}
}
Ok(acl_buf)
}
pub fn set_permissions(
path: &str,
principals_full_control: &[&str],
principals_modify_access: &[&str],
principals_read_execute: &[&str],
) -> Result<(), String> {
let acl_buf = build_dacl(
principals_full_control,
principals_modify_access,
principals_read_execute,
)?;
let mut sd = SECURITY_DESCRIPTOR::default();
unsafe {
InitializeSecurityDescriptor(
PSECURITY_DESCRIPTOR(&mut sd as *mut _ as *mut _),
SECURITY_DESCRIPTOR_REVISION,
)
.map_err(|e| format!("InitializeSecurityDescriptor failed: {e}"))?;
SetSecurityDescriptorDacl(
PSECURITY_DESCRIPTOR(&mut sd as *mut _ as *mut _),
true,
Some(acl_buf.as_ptr() as *const ACL),
false,
)
.map_err(|e| format!("SetSecurityDescriptorDacl failed: {e}"))?;
}
let path_w: Vec<u16> = path.encode_utf16().chain(std::iter::once(0)).collect();
let ret = unsafe {
SetFileSecurityW(
windows::core::PCWSTR(path_w.as_ptr()),
DACL_SECURITY_INFORMATION,
PSECURITY_DESCRIPTOR(&sd as *const _ as *mut _),
)
};
if !ret.as_bool() {
return Err(format!(
"SetFileSecurityW failed for '{path}': {}",
std::io::Error::last_os_error()
));
}
Ok(())
}
pub fn set_permissions_protected(
path: &str,
principals_full_control: &[&str],
principals_modify_access: &[&str],
principals_read_execute: &[&str],
) -> Result<(), String> {
use windows::Win32::Security::Authorization::{SetNamedSecurityInfoW, SE_FILE_OBJECT};
use windows::Win32::Security::PROTECTED_DACL_SECURITY_INFORMATION;
let acl_buf = build_dacl(
principals_full_control,
principals_modify_access,
principals_read_execute,
)?;
let path_w: Vec<u16> = path.encode_utf16().chain(std::iter::once(0)).collect();
let result = unsafe {
SetNamedSecurityInfoW(
windows::core::PCWSTR(path_w.as_ptr()),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
None, None, Some(acl_buf.as_ptr() as *const ACL),
None, )
};
if result.is_err() {
return Err(format!(
"SetNamedSecurityInfoW failed for '{path}': WIN32_ERROR({})",
result.0
));
}
Ok(())
}