use ::windows::Win32::Foundation::{E_UNEXPECTED, HANDLE, HLOCAL, LocalFree};
use ::windows::Win32::NetworkManagement::WindowsFirewall::{
INetFwPolicy2, INetFwRule, INetFwRule3, NET_FW_ACTION_ALLOW, NET_FW_IP_PROTOCOL_ANY,
NET_FW_IP_PROTOCOL_TCP, NET_FW_IP_PROTOCOL_UDP, NET_FW_PROFILE_TYPE2, NET_FW_RULE_DIR_IN,
NetFwPolicy2,
};
use ::windows::Win32::Security::Authorization::ConvertSidToStringSidW;
use ::windows::Win32::Security::{
GetTokenInformation, SECURITY_MAX_SID_SIZE, TOKEN_QUERY, TOKEN_USER, TokenUser,
};
use ::windows::Win32::System::Com::{
CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx, CoUninitialize,
};
use ::windows::Win32::System::Ole::IEnumVARIANT;
use ::windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
use ::windows::Win32::System::Variant::{VARIANT, VT_DISPATCH, VariantClear};
use ::windows::core::{Interface, Owned, PWSTR, Result as ComResult};
use super::rules::{Action, Direction, Protocol, Rule, evaluate};
use super::{CheckError, Problem, Profile, Profiles};
const MDNS_PORT: u16 = 5353;
impl From<::windows::core::Error> for CheckError {
fn from(error: ::windows::core::Error) -> Self {
Self::Query(error.code().0)
}
}
struct ComScope;
impl ComScope {
fn enter() -> ComResult<Self> {
unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) }.ok()?;
Ok(Self)
}
}
impl Drop for ComScope {
fn drop(&mut self) {
unsafe { CoUninitialize() };
}
}
pub(super) fn check(port: u16) -> Result<Vec<Problem>, CheckError> {
let program = std::env::current_exe()?;
let _com = ComScope::enter()?;
let policy = policy()?;
let active = active_profiles(&policy)?;
if active == Profiles::default() {
return Ok(Vec::new());
}
let user_sid = current_user_sid()?;
let rules = read_rules(&policy)?;
Ok(evaluate(
&rules,
active,
&program,
&[port, MDNS_PORT],
&user_sid,
))
}
fn current_user_sid() -> ComResult<String> {
const BUFFER_LEN: usize =
(size_of::<TOKEN_USER>() + SECURITY_MAX_SID_SIZE as usize).div_ceil(size_of::<usize>());
let mut token = HANDLE::default();
let mut buffer = [0usize; BUFFER_LEN];
let mut needed = 0;
let mut sid = PWSTR::null();
unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &raw mut token) }?;
let token = unsafe { Owned::new(token) };
unsafe {
GetTokenInformation(
*token,
TokenUser,
Some(buffer.as_mut_ptr().cast()),
u32::try_from(size_of_val(&buffer))
.expect("the fixed TOKEN_USER and maximum SID buffer size fits in u32"),
&raw mut needed,
)?;
let user = &*buffer.as_ptr().cast::<TOKEN_USER>();
ConvertSidToStringSidW(user.User.Sid, &raw mut sid)?;
let text = String::from_utf16_lossy(sid.as_wide());
let _ = LocalFree(Some(HLOCAL(sid.0.cast())));
Ok(text)
}
}
fn policy() -> ComResult<INetFwPolicy2> {
unsafe { CoCreateInstance(&NetFwPolicy2, None, CLSCTX_INPROC_SERVER) }
}
fn active_profiles(policy: &INetFwPolicy2) -> ComResult<Profiles> {
let current = unsafe { policy.CurrentProfileTypes() }?;
let mut active = 0;
for profile in Profile::ALL {
let bit = profile.bit();
if u32::try_from(current).unwrap_or_default() & bit == 0 {
continue;
}
let type2 = NET_FW_PROFILE_TYPE2(i32::try_from(bit).unwrap_or_default());
if unsafe { policy.get_FirewallEnabled(type2) }?.as_bool() {
active |= bit;
}
}
Ok(Profiles(active))
}
fn read_rules(policy: &INetFwPolicy2) -> ComResult<Vec<Rule>> {
let rules = unsafe { policy.Rules() }?;
let enumerator: IEnumVARIANT = unsafe { rules._NewEnum() }?.cast()?;
let mut read = Vec::new();
loop {
let mut item = [VARIANT::default()];
let mut fetched = 0;
unsafe { enumerator.Next(&mut item, &raw mut fetched) }.ok()?;
if fetched == 0 {
return Ok(read);
}
let [variant] = item;
if let Ok(rule) = rule_of(variant).and_then(|rule| read_rule(&rule)) {
read.push(rule);
}
}
}
fn rule_of(mut variant: VARIANT) -> ComResult<INetFwRule> {
let dispatch = unsafe {
let inner = &variant.Anonymous.Anonymous;
let dispatch = (inner.vt == VT_DISPATCH)
.then(|| (*inner.Anonymous.pdispVal).clone())
.flatten();
VariantClear(&raw mut variant)?;
dispatch
};
dispatch
.ok_or_else(|| ::windows::core::Error::from(E_UNEXPECTED))?
.cast()
}
fn read_rule(rule: &INetFwRule) -> ComResult<Rule> {
unsafe {
let program = rule.ApplicationName()?.to_string();
let (local_users, package_id) = match rule.cast::<INetFwRule3>() {
Ok(scoped) => (
scoped.LocalUserAuthorizedList()?.to_string(),
scoped.LocalAppPackageId()?.to_string(),
),
Err(_) => (String::new(), String::new()),
};
Ok(Rule {
name: rule.Name()?.to_string(),
enabled: rule.Enabled()?.as_bool(),
direction: if rule.Direction()? == NET_FW_RULE_DIR_IN {
Direction::Inbound
} else {
Direction::Outbound
},
action: if rule.Action()? == NET_FW_ACTION_ALLOW {
Action::Allow
} else {
Action::Block
},
protocol: protocol(rule.Protocol()?),
local_ports: rule.LocalPorts()?.to_string(),
remote_addresses: rule.RemoteAddresses()?.to_string(),
local_users,
package_id,
program: (!program.is_empty()).then(|| program.into()),
profiles: Profiles(u32::try_from(rule.Profiles()?).unwrap_or_default()),
})
}
}
fn protocol(number: i32) -> Protocol {
match number {
n if n == NET_FW_IP_PROTOCOL_UDP.0 => Protocol::Udp,
n if n == NET_FW_IP_PROTOCOL_TCP.0 => Protocol::Tcp,
n if n == NET_FW_IP_PROTOCOL_ANY.0 => Protocol::Any,
_ => Protocol::Other,
}
}