use anyhow::Result;
#[cfg(target_os = "windows")]
use tokio::net::windows::named_pipe::NamedPipeServer;
#[derive(Debug, Clone)]
pub struct PeerCredentials {
pub user: String,
pub session_id: u32,
}
#[cfg(target_os = "windows")]
pub fn resolve_peer(pipe: &NamedPipeServer) -> Result<PeerCredentials> {
windows_impl::resolve(pipe)
}
#[cfg(not(target_os = "windows"))]
pub fn resolve_peer<T>(_pipe: &T) -> Result<PeerCredentials> {
anyhow::bail!("KLP peer auth on non-Windows targets is not yet implemented");
}
#[cfg(target_os = "windows")]
mod windows_impl {
use super::PeerCredentials;
use anyhow::{Context, Result, bail};
use std::ffi::c_void;
use std::os::windows::io::AsRawHandle;
use tokio::net::windows::named_pipe::NamedPipeServer;
use windows::Win32::Foundation::{CloseHandle, HANDLE};
use windows::Win32::Security::{
GetTokenInformation, LookupAccountSidW, PSID, SidTypeUser, TOKEN_QUERY, TOKEN_USER,
TokenSessionId, TokenUser,
};
use windows::Win32::System::Pipes::GetNamedPipeClientProcessId;
use windows::Win32::System::Threading::{
OpenProcess, OpenProcessToken, PROCESS_QUERY_LIMITED_INFORMATION,
};
use windows::core::PWSTR;
pub(super) fn resolve(pipe: &NamedPipeServer) -> Result<PeerCredentials> {
let pipe_handle = HANDLE(pipe.as_raw_handle());
let mut pid: u32 = 0;
unsafe {
GetNamedPipeClientProcessId(pipe_handle, &mut pid)
.context("GetNamedPipeClientProcessId failed")?;
}
let proc_handle = unsafe {
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
.with_context(|| format!("OpenProcess(pid={pid}) failed"))?
};
let creds = read_token_info(proc_handle);
unsafe {
let _ = CloseHandle(proc_handle);
}
creds
}
fn read_token_info(proc_handle: HANDLE) -> Result<PeerCredentials> {
let mut token = HANDLE::default();
unsafe {
OpenProcessToken(proc_handle, TOKEN_QUERY, &mut token)
.context("OpenProcessToken failed")?;
}
let result = read_user_and_session(token);
unsafe {
let _ = CloseHandle(token);
}
result
}
fn read_user_and_session(token: HANDLE) -> Result<PeerCredentials> {
let mut session_id: u32 = 0;
let mut returned: u32 = 0;
unsafe {
GetTokenInformation(
token,
TokenSessionId,
Some(&mut session_id as *mut u32 as *mut c_void),
std::mem::size_of::<u32>() as u32,
&mut returned,
)
.context("GetTokenInformation(TokenSessionId) failed")?;
}
let mut needed: u32 = 0;
unsafe {
let _ = GetTokenInformation(token, TokenUser, None, 0, &mut needed);
}
if needed == 0 {
bail!("GetTokenInformation(TokenUser) returned zero size");
}
let mut buf = vec![0u8; needed as usize];
unsafe {
GetTokenInformation(
token,
TokenUser,
Some(buf.as_mut_ptr() as *mut c_void),
needed,
&mut needed,
)
.context("GetTokenInformation(TokenUser) failed")?;
}
let user = unsafe {
let token_user: TOKEN_USER = std::ptr::read_unaligned(buf.as_ptr().cast());
lookup_friendly(token_user.User.Sid).unwrap_or_else(|_| "<unknown>".into())
};
Ok(PeerCredentials { user, session_id })
}
unsafe fn lookup_friendly(sid: PSID) -> Result<String> {
unsafe {
let mut name_len: u32 = 0;
let mut domain_len: u32 = 0;
let mut sid_use = SidTypeUser;
let _ = LookupAccountSidW(
None,
sid,
None,
&mut name_len,
None,
&mut domain_len,
&mut sid_use,
);
if name_len == 0 || domain_len == 0 {
bail!("LookupAccountSidW returned zero sizes");
}
let mut name = vec![0u16; name_len as usize];
let mut domain = vec![0u16; domain_len as usize];
LookupAccountSidW(
None,
sid,
Some(PWSTR(name.as_mut_ptr())),
&mut name_len,
Some(PWSTR(domain.as_mut_ptr())),
&mut domain_len,
&mut sid_use,
)
.context("LookupAccountSidW (resize) failed")?;
let user = String::from_utf16_lossy(&name[..name_len as usize])
.trim_end_matches('\0')
.to_string();
let domain = String::from_utf16_lossy(&domain[..domain_len as usize])
.trim_end_matches('\0')
.to_string();
Ok(format!("{domain}\\{user}"))
}
}
}