Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
//! Module containing utilities to query the currently running antivirus / EDR software on the
|
||||
//! user's machine.
|
||||
|
||||
mod telemetry;
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
use warpui::{Entity, ModelContext, SingletonEntity};
|
||||
|
||||
/// Singleton model that reports the currently running antivirus software.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AntivirusInfo(Option<String>);
|
||||
|
||||
impl AntivirusInfo {
|
||||
pub fn new(_ctx: &mut ModelContext<Self>) -> Self {
|
||||
#[cfg(windows)]
|
||||
_ctx.spawn(async move { Self::scan().await }, Self::on_scan_complete);
|
||||
|
||||
Self(None)
|
||||
}
|
||||
|
||||
/// Returns the currently running antivirus software if any.
|
||||
/// If called before the antivirus is computed (i.e. before
|
||||
/// [`AntivirusInfoEvent::ScannedComplete`] is emitted), this function returns [`None`].
|
||||
///
|
||||
/// ## Platform-specific
|
||||
/// This function always returns `None` on non-Windows platforms.
|
||||
pub fn get(&self) -> Option<&str> {
|
||||
self.0.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AntivirusInfoEvent {
|
||||
#[allow(dead_code)]
|
||||
ScannedComplete,
|
||||
}
|
||||
|
||||
impl Entity for AntivirusInfo {
|
||||
type Event = AntivirusInfoEvent;
|
||||
}
|
||||
|
||||
impl SingletonEntity for AntivirusInfo {}
|
||||
@@ -0,0 +1,69 @@
|
||||
use serde_json::{json, Value};
|
||||
use strum_macros::EnumDiscriminants;
|
||||
use strum_macros::EnumIter;
|
||||
use warp_core::telemetry::{EnablementState, TelemetryEvent, TelemetryEventDesc};
|
||||
|
||||
#[derive(Debug, EnumDiscriminants)]
|
||||
#[strum_discriminants(derive(EnumIter))]
|
||||
#[cfg_attr(not(windows), expect(dead_code))]
|
||||
pub(super) enum AntivirusInfoTelemetryEvent {
|
||||
AntivirusDetected { name: String },
|
||||
}
|
||||
|
||||
impl TelemetryEvent for AntivirusInfoTelemetryEvent {
|
||||
fn name(&self) -> &'static str {
|
||||
AntivirusInfoTelemetryEventDiscriminants::from(self).name()
|
||||
}
|
||||
|
||||
fn payload(&self) -> Option<Value> {
|
||||
match self {
|
||||
AntivirusInfoTelemetryEvent::AntivirusDetected { name } => Some(json!({
|
||||
"antivirus_name": name,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
AntivirusInfoTelemetryEventDiscriminants::from(self).description()
|
||||
}
|
||||
|
||||
fn enablement_state(&self) -> EnablementState {
|
||||
AntivirusInfoTelemetryEventDiscriminants::from(self).enablement_state()
|
||||
}
|
||||
|
||||
fn contains_ugc(&self) -> bool {
|
||||
match self {
|
||||
AntivirusInfoTelemetryEvent::AntivirusDetected { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn event_descs() -> impl Iterator<Item = Box<dyn TelemetryEventDesc>> {
|
||||
warp_core::telemetry::enum_events::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
impl TelemetryEventDesc for AntivirusInfoTelemetryEventDiscriminants {
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
AntivirusInfoTelemetryEventDiscriminants::AntivirusDetected => {
|
||||
"Identified Antivirus Software"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
AntivirusInfoTelemetryEventDiscriminants::AntivirusDetected => {
|
||||
"Identified running antivirus software on the user's machine"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enablement_state(&self) -> EnablementState {
|
||||
match self {
|
||||
AntivirusInfoTelemetryEventDiscriminants::AntivirusDetected => EnablementState::Always,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warp_core::register_telemetry_event!(AntivirusInfoTelemetryEvent);
|
||||
@@ -0,0 +1,101 @@
|
||||
use crate::antivirus::telemetry::AntivirusInfoTelemetryEvent;
|
||||
use crate::antivirus::{AntivirusInfo, AntivirusInfoEvent};
|
||||
use warp_core::send_telemetry_from_ctx;
|
||||
use warpui::ModelContext;
|
||||
use windows::Win32::System::Com::{
|
||||
CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_APARTMENTTHREADED,
|
||||
};
|
||||
use windows::Win32::System::SecurityCenter::*;
|
||||
|
||||
impl AntivirusInfo {
|
||||
#[cfg(windows)]
|
||||
pub(super) async fn scan() -> anyhow::Result<Option<String>> {
|
||||
unsafe {
|
||||
com_initialized()?;
|
||||
|
||||
// Read out all of the registered antivirus products.
|
||||
let pl: IWSCProductList = CoCreateInstance(&WSCProductList, None, CLSCTX_ALL)?;
|
||||
pl.Initialize(WSC_SECURITY_PROVIDER_ANTIVIRUS)?;
|
||||
|
||||
let n = pl.Count().unwrap_or(0) as u32;
|
||||
for i in 0..n {
|
||||
let Ok(p) = pl.get_Item(i) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// If the product is on (meaning it's running), return it.
|
||||
if let Ok(WSC_SECURITY_PRODUCT_STATE_ON) = p.ProductState() {
|
||||
return Ok(p.ProductName().ok().map(|s| s.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(super) fn on_scan_complete(
|
||||
&mut self,
|
||||
software: anyhow::Result<Option<String>>,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let software = match software {
|
||||
Ok(software) => software,
|
||||
Err(err) => {
|
||||
log::warn!("Failed to scan for antivirus / EDR software: {err:#}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match software.as_ref() {
|
||||
None => {
|
||||
log::info!("No antivirus / EDR software detected");
|
||||
}
|
||||
Some(software) => {
|
||||
log::info!("Detected antivirus / EDR software {software:#?}");
|
||||
send_telemetry_from_ctx!(
|
||||
AntivirusInfoTelemetryEvent::AntivirusDetected {
|
||||
name: software.into()
|
||||
},
|
||||
ctx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.0 = software;
|
||||
|
||||
ctx.emit(AntivirusInfoEvent::ScannedComplete);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct to properly enforce reference counting of the Windows COM library.
|
||||
///
|
||||
/// Per the Windows docs (https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex)
|
||||
/// each call to [`CoInitializeEx`] must be paired with a call to [`CoUninitialize`] in order for
|
||||
/// the COM library to be gracefully uninitialized.
|
||||
// TODO(alokedesai): Move this to a shared place in `core` so we can use it in other places in the
|
||||
// app.
|
||||
struct ComInitialized;
|
||||
|
||||
impl Drop for ComInitialized {
|
||||
fn drop(&mut self) {
|
||||
unsafe { CoUninitialize() };
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static COM_INITIALIZED: Result<ComInitialized, windows::core::Error> = {
|
||||
unsafe {
|
||||
CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok()?;
|
||||
Ok(ComInitialized)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn com_initialized() -> Result<(), windows::core::Error> {
|
||||
COM_INITIALIZED.with(|initialized| {
|
||||
initialized
|
||||
.as_ref()
|
||||
.map(|_| ())
|
||||
.map_err(|error| error.clone())
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user