Compare commits

..
Author SHA1 Message Date
Ian 4fc0d64771 Move the macOS uninstall out of the CLI into its own binary 2026-10-22 15:09:36 -04:00
15 changed files with 24 additions and 734 deletions

No files matched your search

+1 -168
View File
@@ -261,15 +261,6 @@ dependencies = [
"hybrid-array",
]
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]]
name = "borsh"
version = "1.8.1"
@@ -678,16 +669,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags 2.13.2",
"objc2",
]
[[package]]
name = "displaydoc"
version = "0.2.7"
@@ -699,12 +680,6 @@ dependencies = [
"syn 3.0.6",
]
[[package]]
name = "dpi"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
[[package]]
name = "either"
version = "1.18.0"
@@ -776,15 +751,6 @@ dependencies = [
"getrandom 0.4.3",
]
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.13"
@@ -805,7 +771,7 @@ checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
dependencies = [
"crc32fast",
"libz-sys",
"miniz_oxide 0.9.1",
"miniz_oxide",
"zlib-rs",
]
@@ -1486,16 +1452,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "keyboard-types"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d"
dependencies = [
"bitflags 2.13.2",
"serde",
]
[[package]]
name = "konst"
version = "0.2.20"
@@ -1639,16 +1595,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "miniz_oxide"
version = "0.9.1"
@@ -1690,24 +1636,6 @@ dependencies = [
"uuid",
]
[[package]]
name = "muda"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03"
dependencies = [
"crossbeam-channel",
"dpi",
"keyboard-types",
"objc2",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation",
"png",
"thiserror",
"windows-sys 0.61.2",
]
[[package]]
name = "multer"
version = "3.1.0"
@@ -1814,66 +1742,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "objc2"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-app-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
"bitflags 2.13.2",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.13.2",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags 2.13.2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.13.2",
"block2",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "oid-registry"
version = "0.8.1"
@@ -1996,19 +1864,6 @@ version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "png"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [
"bitflags 2.13.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide 0.8.9",
]
[[package]]
name = "portable-atomic"
version = "1.15.0"
@@ -3375,9 +3230,7 @@ dependencies = [
"clap",
"core-foundation 0.10.1",
"directories",
"muda",
"percent-encoding",
"png",
"rama",
"rand",
"rcgen",
@@ -3393,7 +3246,6 @@ dependencies = [
"tracing",
"tracing-appender",
"tracing-subscriber",
"tray-icon",
"which",
"windows",
"windows-registry",
@@ -3800,25 +3652,6 @@ dependencies = [
"tracing-serde",
]
[[package]]
name = "tray-icon"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f5cc5858ecf9574ddd8f0032a2ff7ffd6f7724d2b81e48b9374984b3fd6ddc9"
dependencies = [
"crossbeam-channel",
"muda",
"objc2",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-foundation",
"once_cell",
"png",
"thiserror",
"windows-sys 0.61.2",
]
[[package]]
name = "try-lock"
version = "0.2.5"
+11 -17
View File
@@ -3,7 +3,6 @@ name = "strix-firewall"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
authors = ["Strix"]
description = "Screens package downloads for known malware before they reach your machine"
repository = "https://github.com/usestrix/firewall"
homepage = "https://github.com/usestrix/firewall"
@@ -11,20 +10,14 @@ homepage = "https://github.com/usestrix/firewall"
# as an integration test; new tests/*.rs need an explicit [[test]] entry.
autotests = false
# Only Windows ships the service and tray.
# Only Windows ships the service.
[package.metadata.dist.binaries]
x86_64-pc-windows-msvc = ["firewall", "firewall-service", "firewall-tray"]
x86_64-pc-windows-msvc = ["firewall", "firewall-service"]
aarch64-apple-darwin = ["firewall"]
x86_64-apple-darwin = ["firewall"]
aarch64-unknown-linux-gnu = ["firewall"]
x86_64-unknown-linux-gnu = ["firewall"]
[package.metadata.wix]
upgrade-guid = "D944C2F6-BF55-4B21-A60E-1BBDB1A66293"
path-guid = "033B1050-23D0-4F5A-A0D3-F4757B4B67E0"
license = false
eula = false
[lib]
name = "firewall"
# The macOS extension links a staticlib, built on demand:
@@ -39,10 +32,6 @@ path = "src/main.rs"
name = "firewall-service"
path = "src/bin/firewall-service.rs"
[[bin]]
name = "firewall-tray"
path = "src/bin/firewall-tray.rs"
[[bin]]
name = "firewall-uninstall"
path = "src/bin/firewall-uninstall.rs"
@@ -111,14 +100,19 @@ security-framework = "3.7.0"
security-framework-sys = "2.17.0"
[target.'cfg(windows)'.dependencies]
muda = { version = "0.21.2", default-features = false }
png = "0.18.1"
rama = { version = "0.4.0", features = ["dns-hickory"] }
schannel = "0.1.29"
tokio = { version = "1.53.1", features = ["net"] }
tracing-appender = "0.2.5"
tray-icon = { version = "0.26.1", default-features = false }
windows = { version = "0.62", features = ["Win32_Foundation", "Win32_Security", "Win32_Security_Authorization", "Win32_Security_Cryptography", "Win32_System_Com", "Win32_System_Environment", "Win32_System_Threading", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
windows = { version = "0.62", features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Security_Authorization",
"Win32_Security_Cryptography",
"Win32_System_Com",
"Win32_System_Environment",
"Win32_UI_Shell",
] }
windows-registry = "0.6.1"
windows-service = "0.8.0"
+5 -9
View File
@@ -89,16 +89,12 @@ async fn handle(req: Request, firewall: &Firewall) -> Result<Response, Infallibl
}
info!(method = %req.method(), %url, "forwarding");
// Only an HTTPS registry was downgraded to reach us, so only it is upgraded back and rewritten.
let tls = firewall.is_https_url(&url);
let index = tls && firewall.is_index_url(&url);
let index = firewall.is_index_url(&url);
let (mut parts, body) = req.into_parts();
if tls {
let Ok(uri) = url.parse() else {
return Ok(text(400, "firewall: expected an absolute URL\n"));
};
parts.uri = uri;
}
let Ok(uri) = url.parse() else {
return Ok(text(400, "firewall: expected an absolute URL\n"));
};
parts.uri = uri;
let client = DecompressionLayer::new().into_layer(EasyHttpWebClient::default());
let response = match client.serve(Request::from_parts(parts, body)).await {
+5 -16
View File
@@ -2,25 +2,14 @@
#[cfg(windows)]
fn main() {
// Run by the MSI as it removes the firewall.
if std::env::args().nth(1).as_deref() == Some("uninstall") {
firewall::init_logging("info");
if let Err(e) = firewall::os::windows::uninstall() {
fail("uninstall", &format!("could not uninstall: {e}"))
}
} else if let Err(e) = firewall::os::windows::run_service() {
fail("service", &format!("could not run the service: {e}"))
if let Err(e) = firewall::os::windows::run_service() {
tracing::error!("could not run the service: {e}");
firewall::telemetry::error("service");
firewall::telemetry::flush();
std::process::exit(1)
}
}
#[cfg(windows)]
fn fail(kind: &'static str, message: &str) -> ! {
tracing::error!("{message}");
firewall::telemetry::error(kind);
firewall::telemetry::flush();
std::process::exit(1)
}
/// The service is Windows-only.
#[cfg(not(windows))]
fn main() {}
-11
View File
@@ -1,11 +0,0 @@
//! The notification-area icon, started at sign-in. A GUI app, so no console opens with it.
#![windows_subsystem = "windows"]
#[cfg(windows)]
fn main() -> Result<(), rama::error::BoxError> {
firewall::os::windows::run_tray()
}
/// The tray is Windows-only.
#[cfg(not(windows))]
fn main() {}
-5
View File
@@ -47,11 +47,6 @@ impl Firewall {
self.config.hosts()
}
pub fn is_https_url(&self, url: &str) -> bool {
let host = host_of(url);
self.config.registries.iter().any(|r| r.upstream.starts_with("https://") && r.host() == host)
}
pub fn is_index_url(&self, url: &str) -> bool {
self.config.registries.iter().any(|r| r.index && r.host() == host_of(url))
}
-15
View File
@@ -7,11 +7,9 @@ use schannel::cert_context::CertContext;
use schannel::cert_store::{CertAdd, CertStore};
use std::io;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::warn;
use windows::Win32::Security::Authorization::{GetNamedSecurityInfoW, SE_FILE_OBJECT};
use windows::Win32::Security::*;
use windows_registry::Type;
/// SYSTEM and admins write, users only read, so no one else can plant a CA for node.
const DATA_DIR_ACL: &str = concat!(
@@ -22,9 +20,6 @@ const DATA_DIR_ACL: &str = concat!(
"(A;OICI;FRFX;;;BU)", // allow Users read and execute only
);
/// 1970 in FILETIME, which counts 100 ns from 1601.
const FILETIME_UNIX_EPOCH: u128 = 116_444_736_000_000_000;
/// Trusted machine-wide so restarts keep it; replaced if the hosts change.
pub fn load_or_create_ca(
hosts: &[&str],
@@ -39,16 +34,6 @@ pub fn load_or_create_ca(
LOCAL_MACHINE
.create(REGISTRY_KEY)?
.set_string("ca", ca.to_blob(hosts))?;
// Windows discards CRLs it cached before now, in case an old CA's is cached at our URL.
let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_nanos() / 100
+ FILETIME_UNIX_EPOCH;
let chain = r"SOFTWARE\Microsoft\Cryptography\OID\EncodingType 0\CertDllCreateCertificateChainEngine\Config";
let resync = (now as u64).to_le_bytes();
LOCAL_MACHINE.create(chain)?.set_bytes(
"ChainCacheResyncFiletime",
Type::Bytes,
&resync,
)?;
ca
}
};
-4
View File
@@ -4,13 +4,9 @@
mod ca;
mod service;
mod tpm;
mod tray;
mod uninstall;
pub use service::run as run_service;
pub use tpm::CaKey;
pub use tray::run as run_tray;
pub use uninstall::uninstall;
use rama::error::BoxError;
use std::net::Ipv4Addr;
-7
View File
@@ -27,13 +27,6 @@ impl CaKey {
}
}
pub(super) fn delete_machine_key() -> Result<(), BoxError> {
if let Some(key) = open_key(KEY_NAME, NCRYPT_MACHINE_KEY_FLAG)? {
key.delete_from_tpm()?;
}
Ok(())
}
/// A per-user key under a random name, which needs no elevation; `delete_from_tpm` it after.
pub fn create_test_key() -> WinResult<Self> {
let name = format!("Strix Firewall test {:016x}", rand::random::<u64>());
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

-223
View File
@@ -1,223 +0,0 @@
//! The notification-area icon, started at sign-in as the menu bar app is on macOS.
use super::*;
use muda::{Menu, MenuEvent, MenuItem, PredefinedMenuItem};
use std::io::Cursor;
use std::time::Duration;
use tray_icon::{Icon, MouseButtonState, TrayIcon, TrayIconBuilder, TrayIconEvent};
use windows::core::{w, PCWSTR};
use windows::Win32::Foundation::*;
use windows::Win32::System::Threading::CreateMutexW;
use windows::Win32::UI::Shell::*;
use windows::Win32::UI::WindowsAndMessaging::*;
use windows_registry::CURRENT_USER;
use windows_service::service::{Service, ServiceAccess, ServiceState};
use windows_service::service_manager::{ServiceManager, ServiceManagerAccess};
const MENU_PAUSE: &str = "pause";
const MENU_RESTART: &str = "restart";
const MENU_LOG: &str = "log";
const MENU_QUIT: &str = "quit";
const LOGO: &[u8] = include_bytes!("tray.png");
/// Runs until Quit, once per session.
pub fn run() -> Result<(), BoxError> {
// Held until we exit.
let _mutex = unsafe { CreateMutexW(None, false, w!(r"Local\StrixFirewallTray"))? };
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
return Ok(());
}
let status = MenuItem::new("", false, None);
let pause = MenuItem::with_id(MENU_PAUSE, "", false, None);
let menu = Menu::with_items(&[
&status,
&PredefinedMenuItem::separator(),
&pause,
&MenuItem::with_id(MENU_RESTART, "Restart", true, None),
&MenuItem::with_id(MENU_LOG, "Open logs", true, None),
&PredefinedMenuItem::separator(),
&MenuItem::with_id(MENU_QUIT, "Quit", true, None),
])?;
let (logo, logo_size) = logo_mask()?;
let icon = TrayIconBuilder::new().with_menu(Box::new(menu)).build()?;
let window = HWND(icon.window_handle());
let mut tray = Tray {
icon,
logo,
logo_size,
drawn: None,
status,
pause,
state: None,
};
tray.update();
unsafe { SetTimer(None, 0, 2000, None) };
let mut message = MSG::default();
while unsafe { GetMessageW(&mut message, None, 0, 0) }.0 > 0 {
unsafe { DispatchMessageW(&message) };
// WM_CLOSE, as the MSI sends, destroys the icon's window.
if !unsafe { IsWindow(Some(window)) }.as_bool() {
break;
}
// Ours, not the icon window's own hover timer.
if message.message == WM_TIMER && message.hwnd.is_invalid() {
tray.update();
}
while let Ok(event) = TrayIconEvent::receiver().try_recv() {
// The menu opens on release, so this keeps it current.
if let TrayIconEvent::Click {
button_state: MouseButtonState::Down,
..
} = event
{
tray.update();
}
}
while let Ok(event) = MenuEvent::receiver().try_recv() {
if event.id == MENU_QUIT {
return Ok(());
}
tray.on_menu(event.id.as_ref());
}
}
Ok(())
}
struct Tray {
icon: TrayIcon,
/// The logo's alpha channel; it is square.
logo: Vec<u8>,
logo_size: u32,
/// Whether the taskbar was light and the service screening when the icon was last set.
drawn: Option<(bool, bool)>,
status: MenuItem,
pause: MenuItem,
/// As the menu last showed it.
state: Option<ServiceState>,
}
impl Tray {
fn update(&mut self) {
self.state = service_state();
let screening = self.state == Some(ServiceState::Running);
let light = light_taskbar();
if self.drawn != Some((light, screening)) {
let rgba = logo_rgba(&self.logo, light, screening);
if let Ok(icon) = Icon::from_rgba(rgba, self.logo_size, self.logo_size) {
let _ = self.icon.set_icon(Some(icon));
}
self.drawn = Some((light, screening));
}
let text = status_text(self.state);
let tip = format!("Strix Firewall: {text}");
let _ = self.icon.set_tooltip(Some(tip));
self.status.set_text(text);
let pause = if screening { "Pause" } else { "Resume" };
self.pause.set_text(pause);
self.pause.set_enabled(self.state.is_some());
}
fn on_menu(&self, id: &str) {
let state = self.state;
let screening = state == Some(ServiceState::Running);
let action: fn() -> Result<(), BoxError> = match id {
MENU_PAUSE if screening => || Ok(open_service()?.pause().map(drop)?),
MENU_PAUSE if state == Some(ServiceState::Paused) => {
|| Ok(open_service()?.resume().map(drop)?)
}
MENU_PAUSE => || Ok(open_service()?.start::<&str>(&[])?),
MENU_RESTART => {
// Off the UI thread, since stopping takes a while.
std::thread::spawn(|| restart().unwrap_or_else(show_error));
return;
}
MENU_LOG => {
let log = HSTRING::from(data_dir().as_path());
let none = PCWSTR::null();
unsafe { ShellExecuteW(None, w!("open"), &log, none, none, SW_SHOWNORMAL) };
return;
}
_ => return,
};
action().unwrap_or_else(show_error);
}
}
fn show_error(error: BoxError) {
let text = HSTRING::from(error.to_string());
unsafe { MessageBoxW(None, &text, w!("Strix Firewall"), MB_ICONERROR) };
}
/// `None` when the service is not installed.
fn service_state() -> Option<ServiceState> {
let manager =
ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT).ok()?;
let service = manager
.open_service(SERVICE_NAME, ServiceAccess::QUERY_STATUS)
.ok()?;
Some(service.query_status().ok()?.current_state)
}
fn light_taskbar() -> bool {
let personalize = r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
let Ok(key) = CURRENT_USER.open(personalize) else {
return false;
};
key.get_u32("SystemUsesLightTheme").is_ok_and(|light| light == 1)
}
fn status_text(state: Option<ServiceState>) -> &'static str {
match state {
Some(ServiceState::Running) => "Screening downloads",
Some(ServiceState::Paused) => "Paused",
_ => "Not running",
}
}
fn open_service() -> Result<Service, BoxError> {
let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)?;
let access = ServiceAccess::QUERY_STATUS
| ServiceAccess::START
| ServiceAccess::STOP
| ServiceAccess::PAUSE_CONTINUE;
Ok(manager.open_service(SERVICE_NAME, access)?)
}
/// Starting again re-reads the config.
fn restart() -> Result<(), BoxError> {
let service = open_service()?;
if service.query_status()?.current_state != ServiceState::Stopped {
service.stop()?;
}
for _ in 0..60 {
if service.query_status()?.current_state == ServiceState::Stopped {
return Ok(service.start::<&str>(&[])?);
}
std::thread::sleep(Duration::from_millis(500));
}
Err("The service did not stop.".into())
}
/// The alpha channel of the RGBA logo, and its width.
fn logo_mask() -> Result<(Vec<u8>, u32), BoxError> {
let mut reader = png::Decoder::new(Cursor::new(LOGO)).read_info()?;
let mut rgba = vec![0; reader.output_buffer_size().ok_or("logo too large")?];
let info = reader.next_frame(&mut rgba)?;
let mut alpha = Vec::with_capacity(rgba.len() / 4);
for pixel in rgba.chunks(4) {
alpha.push(pixel[3]);
}
Ok((alpha, info.width))
}
/// White on a dark taskbar and black on a light one, as macOS draws a template image; dimmed
/// when not screening.
fn logo_rgba(mask: &[u8], light: bool, screening: bool) -> Vec<u8> {
let shade = if light { 0 } else { 255 };
let mut rgba = Vec::with_capacity(mask.len() * 4);
for &alpha in mask {
let alpha = if screening { alpha } else { alpha / 2 };
rgba.extend([shade, shade, shade, alpha]);
}
rgba
}
-41
View File
@@ -1,41 +0,0 @@
use super::ca::remove_our_cas_except;
use super::tpm::CaKey;
use super::*;
use std::io::ErrorKind;
use std::path::Path;
use tracing::info;
/// Run by the MSI after it deletes the service, which would restore the hosts entries. Does
/// nothing for what is already gone.
pub fn uninstall() -> Result<(), BoxError> {
write_hosts_entries(&[])?;
remove_our_cas_except(None)?;
none_if_missing(LOCAL_MACHINE.remove_tree(REGISTRY_KEY))?;
CaKey::delete_machine_key()?;
let entries = match std::fs::read_dir(data_dir()) {
Err(error) if error.kind() == ErrorKind::NotFound => Vec::new(),
entries => entries?.collect::<Result<Vec<_>, _>>()?,
};
for path in entries.into_iter().map(|entry| entry.path()) {
// The admin's config stays for a reinstall, as on macOS.
if path != config_file() {
remove_path(&path)?;
}
}
// Fails while the config is in it.
let _ = std::fs::remove_dir(data_dir());
info!("removed the CA, its key and the data folder");
Ok(())
}
/// A file or a folder, if it is there.
fn remove_path(path: &Path) -> std::io::Result<()> {
let removed = match path.is_dir() {
true => std::fs::remove_dir_all(path),
false => std::fs::remove_file(path),
};
match removed {
Err(error) if error.kind() == ErrorKind::NotFound => Ok(()),
removed => removed,
}
}
+1 -22
View File
@@ -1,9 +1,7 @@
use firewall::aliasing::forward::downgrade;
use firewall::http::https_url;
use firewall::{aliasing, Firewall, FirewallConfig};
use firewall::Firewall;
use rama::http::{Body, Request};
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
fn request(target: &str, host: Option<&str>) -> Request {
let mut builder = Request::builder().method("GET").uri(target);
@@ -37,25 +35,6 @@ fn requests_always_go_out_over_tls() {
assert_eq!(https_url(&request("/six", None)), None);
}
#[test]
fn a_plain_http_upstream_is_not_upgraded() {
let upstream = TcpListener::bind("127.0.0.1:0").expect("bind");
let addr = upstream.local_addr().expect("address");
std::thread::spawn(move || {
let (mut stream, _) = upstream.accept().expect("accept");
let _ = stream.read(&mut [0; 4096]);
let _ = stream.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok");
});
let port = aliasing::spawn(0, FirewallConfig::default()).expect("spawn");
let mut proxy = TcpStream::connect(("127.0.0.1", port)).expect("connect");
let request = format!("GET http://{addr}/x HTTP/1.1\r\nHost: {addr}\r\nConnection: close\r\n\r\n");
proxy.write_all(request.as_bytes()).expect("write");
let mut response = String::new();
proxy.read_to_string(&mut response).expect("read");
assert!(response.starts_with("HTTP/1.1 200") && response.ends_with("ok"), "{response}");
}
#[tokio::test]
async fn downgrade_touches_known_hosts_in_text_only() {
let text = b"https://pypi.org/x https://example.com/y".to_vec();
-193
View File
@@ -1,193 +0,0 @@
<?xml version='1.0' encoding='windows-1252'?>
<!--
Copyright (C) 2017 Christopher R. Field.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi' xmlns:util='http://schemas.microsoft.com/wix/UtilExtension'>
<Product
Id='*'
Name='strix-firewall'
UpgradeCode='D944C2F6-BF55-4B21-A60E-1BBDB1A66293'
Manufacturer='Strix'
Language='1033'
Codepage='1252'
Version='$(var.Version)'>
<Package Id='*'
Keywords='Installer'
Description='Screens package downloads for known malware before they reach your machine'
Manufacturer='Strix'
InstallerVersion='500'
Languages='1033'
Compressed='yes'
InstallScope='perMachine'
SummaryCodepage='1252'
/>
<MajorUpgrade
Schedule='afterInstallInitialize'
DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>
<Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1'/>
<Property Id='DiskPrompt' Value='strix-firewall Installation'/>
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
<Directory Id='APPLICATIONFOLDER' Name='Strix Firewall'>
<Component Id='Path' Guid='033B1050-23D0-4F5A-A0D3-F4757B4B67E0' KeyPath='yes'>
<Environment
Id='PATH'
Name='PATH'
Value='[APPLICATIONFOLDER]'
Permanent='no'
Part='last'
Action='set'
System='yes'/>
</Component>
<Component Id='binary0' Guid='*'>
<File
Id='exe0'
Name='firewall.exe'
DiskId='1'
Source='$(var.CargoTargetBinDir)\firewall.exe'
KeyPath='yes'/>
</Component>
<Component Id='binary1' Guid='*'>
<File
Id='exe1'
Name='firewall-service.exe'
DiskId='1'
Source='$(var.CargoTargetBinDir)\firewall-service.exe'
KeyPath='yes'/>
<ServiceInstall
Name='StrixFirewall'
DisplayName='Strix Firewall'
Description='Screens package downloads for known malware'
Type='ownProcess'
Start='auto'
ErrorControl='normal'
Vital='yes'>
<!-- The default, except that interactive users (IU) may also start (RP), stop (WP) and pause (DT) it, as the tray does. -->
<PermissionEx Sddl='D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWRPWPDTLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)'/>
</ServiceInstall>
<!-- Waits, so a service that cannot start fails the install. -->
<ServiceControl Id='StrixFirewall' Name='StrixFirewall' Start='install' Stop='both' Remove='uninstall' Wait='yes'/>
<!-- For clients that read no trust store. SSL_CERT_FILE would replace OpenSSL's bundle. -->
<Environment Id='NODE_EXTRA_CA_CERTS' Name='NODE_EXTRA_CA_CERTS' Value='[CommonAppDataFolder]Strix Firewall\ca.pem' Action='set' System='yes'/>
<Environment Id='UV_SYSTEM_CERTS' Name='UV_SYSTEM_CERTS' Value='1' Action='set' System='yes'/>
</Component>
<Component Id='binary2' Guid='*'>
<File
Id='exe2'
Name='firewall-tray.exe'
DiskId='1'
Source='$(var.CargoTargetBinDir)\firewall-tray.exe'
KeyPath='yes'/>
<RegistryValue
Root='HKLM'
Key='SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
Name='Strix Firewall'
Type='string'
Value='"[#exe2]"'/>
</Component>
</Directory>
</Directory>
</Directory>
<Feature
Id='Binaries'
Title='Application'
Description='Installs the firewall and its service.'
Level='1'
ConfigurableDirectory='APPLICATIONFOLDER'
AllowAdvertise='no'
Display='expand'
Absent='disallow'>
<ComponentRef Id='binary0'/>
<ComponentRef Id='binary1'/>
<ComponentRef Id='binary2'/>
<Feature
Id='Environment'
Title='PATH Environment Variable'
Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.'
Level='1'
Absent='allow'>
<ComponentRef Id='Path'/>
</Feature>
</Feature>
<!-- Removing the app must not leave the hosts file pointing at a service that is gone. -->
<CustomAction
Id='UninstallFirewall'
FileKey='exe1'
ExeCommand='uninstall'
Execute='deferred'
Impersonate='no'
Return='check'/>
<!--
Registry hosts lead nowhere while the service is down, so bring it back, also when it fails to
start. Through sc.exe, as MsiServiceConfigFailureActions cannot restart a service.
-->
<CustomAction
Id='SetFailureActions'
Directory='APPLICATIONFOLDER'
ExeCommand='"[System64Folder]sc.exe" failure StrixFirewall reset= 3600 actions= restart/5000/restart/5000/restart/5000'
Execute='deferred'
Impersonate='no'
Return='check'/>
<CustomAction
Id='SetFailureFlag'
Directory='APPLICATIONFOLDER'
ExeCommand='"[System64Folder]sc.exe" failureflag StrixFirewall 1'
Execute='deferred'
Impersonate='no'
Return='check'/>
<!-- From the exit dialog, which runs as the signed-in user rather than elevated. -->
<CustomAction
Id='StartTray'
Directory='APPLICATIONFOLDER'
ExeCommand='"[#exe2]"'
Return='asyncNoWait'/>
<!-- Before its file is replaced or removed. Trays WM_CLOSE does not reach, as in other sessions, are terminated. -->
<util:CloseApplication Id='CloseTray' Target='firewall-tray.exe' CloseMessage='yes' RebootPrompt='no' TerminateProcess='0'/>
<InstallExecuteSequence>
<!-- An upgrade removes the old version too, but should keep screening. -->
<Custom Action='UninstallFirewall' After='DeleteServices'>REMOVE~="ALL" AND NOT UPGRADINGPRODUCTCODE</Custom>
<Custom Action='SetFailureActions' After='InstallServices'>NOT REMOVE~="ALL"</Custom>
<Custom Action='SetFailureFlag' After='SetFailureActions'>NOT REMOVE~="ALL"</Custom>
<!-- The default, before InstallFiles, is after RemoveFiles, which would find the tray still running. -->
<Custom Action='WixCloseApplications' Before='RemoveFiles'/>
</InstallExecuteSequence>
<SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize'/>
<Property Id='ARPHELPLINK' Value='https://github.com/usestrix/firewall'/>
<UI>
<UIRef Id='WixUI_FeatureTree'/>
<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>
<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>
<Publish Dialog='ExitDialog' Control='Finish' Event='DoAction' Value='StartTray' Order='1'>NOT Installed</Publish>
</UI>
</Product>
</Wix>
+1 -3
View File
@@ -4,9 +4,7 @@ members = ["cargo:core"]
[dist]
cargo-dist-version = "0.32.0"
ci = "github"
installers = ["shell", "powershell", "homebrew", "msi"]
# core/wix/main.wxs is edited to install the service.
allow-dirty = ["msi"]
installers = ["shell", "powershell", "homebrew"]
tap = "usestrix/homebrew-tap"
publish-jobs = ["homebrew", "./publish-crates"]
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]