use crate::host::token::{self, Token};
use crate::host::{Elevation, Floor};
use std::path::PathBuf;
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
use windows_sys::Win32::Security::{
GetTokenInformation, IsWellKnownSid, TOKEN_ELEVATION, TOKEN_ELEVATION_TYPE, TOKEN_QUERY,
TOKEN_USER, TokenElevation, TokenElevationType, TokenUser, WinLocalServiceSid,
WinLocalSystemSid, WinNetworkServiceSid,
};
use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW;
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
pub(crate) fn login_name() -> Option<String> {
None
}
pub(crate) fn home() -> Option<PathBuf> {
None
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn is_the_accounts_own_home(_path: &std::path::Path) -> bool {
false
}
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn lies_in_the_accounts_own(path: &std::path::Path, below: &str) -> bool {
known_folder(&windows_sys::Win32::UI::Shell::FOLDERID_Profile)
.is_some_and(|home| crate::host::lies_in(path, &home.join(below)))
}
#[cfg(feature = "test-support")]
pub(crate) fn accounts_own_home() -> Option<PathBuf> {
known_folder(&windows_sys::Win32::UI::Shell::FOLDERID_Profile)
}
#[cfg(feature = "test-support")]
pub(crate) fn accounts_own_pitboard_home() -> Option<PathBuf> {
known_folder(&windows_sys::Win32::UI::Shell::FOLDERID_LocalAppData)
.map(|data| data.join("Pitboard"))
}
#[cfg(any(test, feature = "test-support"))]
fn known_folder(id: &windows_sys::core::GUID) -> Option<PathBuf> {
use std::os::windows::ffi::OsStringExt;
use windows_sys::Win32::UI::Shell::{KF_FLAG_DONT_VERIFY, SHGetKnownFolderPath};
let mut out: windows_sys::core::PWSTR = std::ptr::null_mut();
let flags = KF_FLAG_DONT_VERIFY as u32;
let hr = unsafe { SHGetKnownFolderPath(id, flags, std::ptr::null_mut(), &raw mut out) };
let path = (hr >= 0 && !out.is_null()).then(|| {
let mut len = 0;
while unsafe { *out.add(len) } != 0 {
len += 1;
}
let wide = unsafe { std::slice::from_raw_parts(out, len) };
PathBuf::from(std::ffi::OsString::from_wide(wide))
});
unsafe { windows_sys::Win32::System::Com::CoTaskMemFree(out.cast()) };
path
}
pub(crate) fn elevation(_sudo: bool) -> Elevation {
token::elevation(this_processs_token())
}
fn this_processs_token() -> Token {
let Some(opened) = Opened::this_process() else {
return Token::default();
};
Token {
elevated: opened.elevated(),
elevation_type: opened.elevation_type(),
service_account: opened.service_account(),
}
}
struct Opened(HANDLE);
impl Opened {
fn this_process() -> Option<Opened> {
let mut handle: HANDLE = std::ptr::null_mut();
let opened = unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle) };
(opened != 0).then_some(Opened(handle))
}
fn elevated(&self) -> Option<bool> {
let mut elevation = TOKEN_ELEVATION::default();
let mut written = 0u32;
let read = unsafe {
GetTokenInformation(
self.0,
TokenElevation,
(&raw mut elevation).cast(),
size_of::<TOKEN_ELEVATION>() as u32,
&mut written,
)
};
(read != 0).then_some(elevation.TokenIsElevated != 0)
}
fn elevation_type(&self) -> Option<u32> {
let mut kind: TOKEN_ELEVATION_TYPE = 0;
let mut written = 0u32;
let read = unsafe {
GetTokenInformation(
self.0,
TokenElevationType,
(&raw mut kind).cast(),
size_of::<TOKEN_ELEVATION_TYPE>() as u32,
&mut written,
)
};
(read != 0).then(|| u32::try_from(kind).unwrap_or(0))
}
fn service_account(&self) -> Option<bool> {
let mut needed = 0u32;
unsafe {
GetTokenInformation(self.0, TokenUser, std::ptr::null_mut(), 0, &mut needed);
}
if (needed as usize) < size_of::<TOKEN_USER>() {
return None;
}
let mut buffer = vec![0u64; (needed as usize).div_ceil(size_of::<u64>())];
let mut written = 0u32;
let read = unsafe {
GetTokenInformation(
self.0,
TokenUser,
buffer.as_mut_ptr().cast(),
needed,
&mut written,
)
};
if read == 0 {
return None;
}
let user = unsafe { (*buffer.as_ptr().cast::<TOKEN_USER>()).User.Sid };
let is = |kind| {
unsafe { IsWellKnownSid(user, kind) != 0 }
};
Some(is(WinLocalSystemSid) || is(WinLocalServiceSid) || is(WinNetworkServiceSid))
}
}
impl Drop for Opened {
fn drop(&mut self) {
unsafe {
CloseHandle(self.0);
}
}
}
pub(crate) fn floor() -> Floor {
crate::host::windows_floor(version())
}
fn version() -> Option<(u32, u32, u32)> {
let mut info = OSVERSIONINFOW {
dwOSVersionInfoSize: size_of::<OSVERSIONINFOW>() as u32,
..OSVERSIONINFOW::default()
};
let status = unsafe { windows_sys::Wdk::System::SystemServices::RtlGetVersion(&raw mut info) };
(status == 0).then_some((info.dwMajorVersion, info.dwMinorVersion, info.dwBuildNumber))
}
#[cfg(test)]
pub(crate) mod testing {
pub(crate) fn reaching_the_real_home() -> Reaching {
Reaching(())
}
pub(crate) struct Reaching(());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_profile_and_local_app_data_are_named() {
use windows_sys::Win32::UI::Shell::{FOLDERID_LocalAppData, FOLDERID_Profile};
for id in [FOLDERID_Profile, FOLDERID_LocalAppData] {
let folder = known_folder(&id).expect("a known folder");
assert!(folder.is_absolute(), "{}", folder.display());
}
}
#[test]
fn the_token_and_the_version_are_read() {
let token = this_processs_token();
assert!(token.elevated.is_some(), "{token:?}");
assert!(token.elevation_type.is_some(), "{token:?}");
assert_eq!(token.service_account, Some(false), "{token:?}");
assert_ne!(elevation(false), Elevation::Unknown);
let (major, _, build) = version().expect("RtlGetVersion answers");
assert_eq!(major, 10);
assert!(build > 0);
}
#[test]
fn the_build_is_the_one_windows_shows() {
let said = std::process::Command::new("cmd.exe")
.args(["/d", "/c", "ver"])
.output()
.expect("cmd runs");
let said = String::from_utf8_lossy(&said.stdout);
let shown = said
.split(['[', ']'])
.nth(1)
.and_then(|inside| inside.split_whitespace().last())
.and_then(|numbers| numbers.split('.').nth(2))
.and_then(|build| build.parse::<u32>().ok())
.unwrap_or_else(|| panic!("no build in {said:?}"));
assert_eq!(version().map(|(_, _, build)| build), Some(shown));
}
}