Compare commits

..
21 changed files with 101 additions and 271 deletions

No files matched your search

+4 -79
View File
@@ -711,20 +711,6 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
[[package]]
name = "embed-resource"
version = "3.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6168487b5e7eb2673e6efe1200b1848016e436a6ca0b8031bf04bd9d493a249f"
dependencies = [
"cc",
"memchr",
"rustc_version",
"toml",
"vswhom",
"winreg",
]
[[package]]
name = "encoding_rs"
version = "0.8.42"
@@ -3257,15 +3243,6 @@ dependencies = [
"zmij",
]
[[package]]
name = "serde_spanned"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c"
dependencies = [
"serde_core",
]
[[package]]
name = "sha1"
version = "0.11.0"
@@ -3398,7 +3375,6 @@ dependencies = [
"clap",
"core-foundation 0.10.1",
"directories",
"embed-resource",
"muda",
"percent-encoding",
"png",
@@ -3694,26 +3670,11 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "1.1.8+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "1.1.2+spec-1.1.0"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053"
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
@@ -3732,19 +3693,13 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.1.5+spec-1.1.0"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c"
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770"
[[package]]
name = "tower-layer"
version = "0.3.3"
@@ -3954,26 +3909,6 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vswhom"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
dependencies = [
"libc",
"vswhom-sys",
]
[[package]]
name = "vswhom-sys"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "walkdir"
version = "2.5.0"
@@ -4349,16 +4284,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
dependencies = [
"cfg-if",
"windows-sys 0.61.2",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
+1 -5
View File
@@ -129,7 +129,7 @@ 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_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Environment", "Win32_System_JobObjects", "Win32_System_LibraryLoader", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_UI_Controls", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
windows = { version = "0.62", features = ["Win32_Foundation", "Win32_Security", "Win32_Security_Authorization", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Environment", "Win32_System_JobObjects", "Win32_System_Pipes", "Win32_System_Threading", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
windows-registry = "0.6.1"
windows-service = "0.8.0"
@@ -139,7 +139,3 @@ tokio = { version = "1.53.1", features = ["macros", "rt-multi-thread"] }
[profile.dist]
inherits = "release"
lto = "thin"
# Only on Windows hosts, where the tray and service are built.
[target.'cfg(windows)'.build-dependencies]
embed-resource = "3.0.12"
-16
View File
@@ -1,16 +0,0 @@
//! The tray's task dialogs exist only in version 6 of the common controls, which takes a manifest.
//! Both machine-wide binaries also get the Strix icon.
fn main() {
println!("cargo:rerun-if-changed=src/bin/firewall-tray.manifest");
let msvc = std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("msvc");
if msvc && std::env::var_os("CARGO_FEATURE_MACHINE_WIDE").is_some() {
let manifest = concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin/firewall-tray.manifest");
println!("cargo:rustc-link-arg-bin=firewall-tray=/MANIFEST:EMBED");
println!("cargo:rustc-link-arg-bin=firewall-tray=/MANIFESTINPUT:{manifest}");
#[cfg(windows)]
embed_resource::compile_for("src/bin/firewall.rc", ["firewall-tray", "firewall-service"], embed_resource::NONE)
// Fails the build, rather than ship without the icon.
.manifest_required()
.unwrap();
}
}
+1
View File
@@ -11,4 +11,5 @@ pub static BUN: Manager = Manager {
("npm_config_registry", "http://registry.npmjs.org/"),
],
args: &[],
lockfile: None,
};
+1
View File
@@ -17,4 +17,5 @@ pub static CARGO: Manager = Manager {
"source.firewall.registry=\"sparse+http://index.crates.io/\"",
],
}],
lockfile: None,
};
+1
View File
@@ -8,4 +8,5 @@ pub static GEM: Manager = Manager {
when: &["install", "update", "fetch"],
flags: &["--clear-sources", "--source", "http://rubygems.org/"],
}],
lockfile: None,
};
+1
View File
@@ -11,4 +11,5 @@ pub static GO: Manager = Manager {
("GOFLAGS", "-mod=mod"),
],
args: &[],
lockfile: None,
};
+8
View File
@@ -36,6 +36,14 @@ pub struct Manager {
pub command: &'static str,
pub env: &'static [(&'static str, &'static str)],
pub args: &'static [Args],
/// Where the manager records registry URLs, which then carry our http:// override.
pub lockfile: Option<Lockfile>,
}
/// What to do after a run about http:// registry URLs left in the lockfile at this path.
pub enum Lockfile {
Restore(&'static str),
Warn(&'static str),
}
pub static MANAGERS: &[&Manager] =
+2 -1
View File
@@ -1,4 +1,4 @@
use super::{Manager, PROXY};
use super::{Lockfile, Manager, PROXY};
pub static NPM: Manager = Manager {
command: "npm",
@@ -8,4 +8,5 @@ pub static NPM: Manager = Manager {
("npm_config_registry", "http://registry.npmjs.org/"),
],
args: &[],
lockfile: Some(Lockfile::Restore("package-lock.json")),
};
+1
View File
@@ -13,4 +13,5 @@ pub static PDM: Manager = Manager {
("PDM_PYPI_VERIFY_SSL", "false"),
],
args: &[],
lockfile: None,
};
+1 -1
View File
@@ -12,5 +12,5 @@ pub static PIP: Manager = recipe("pip");
pub static PIP3: Manager = recipe("pip3");
const fn recipe(name: &'static str) -> Manager {
Manager { command: name, env: PIP_ENV, args: &[] }
Manager { command: name, env: PIP_ENV, args: &[], lockfile: None }
}
+1
View File
@@ -13,4 +13,5 @@ pub static PIPENV: Manager = Manager {
("PIP_TRUSTED_HOST", "pypi.org files.pythonhosted.org"),
],
args: &[],
lockfile: None,
};
+1
View File
@@ -9,4 +9,5 @@ pub static PNPM: Manager = Manager {
("pnpm_config_registry", "http://registry.npmjs.org/"),
],
args: &[],
lockfile: None,
};
+2 -1
View File
@@ -1,7 +1,8 @@
use super::{Manager, PROXY, PYPI_SIMPLE};
use super::{Lockfile, Manager, PROXY, PYPI_SIMPLE};
pub static UV: Manager = Manager {
command: "uv",
env: &[("HTTP_PROXY", PROXY), ("http_proxy", PROXY), ("UV_DEFAULT_INDEX", PYPI_SIMPLE)],
args: &[],
lockfile: Some(Lockfile::Warn("uv.lock")),
};
+35 -1
View File
@@ -6,9 +6,11 @@ mod windows;
pub use forward::spawn;
pub use managers::*;
use crate::policies::registries::BUILT_IN;
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};
use std::process::Command;
use tracing::{info, warn};
pub struct Invocation {
pub program: String,
@@ -50,10 +52,42 @@ pub fn run(port: u16, invocation: &Invocation, dir: Option<&Path>) -> std::io::R
let status = command.status();
#[cfg(windows)]
let status = status.map_err(|error| windows::explain(error, &program));
Ok(status?.code().unwrap_or(1))
let code = status?.code().unwrap_or(1);
let manager = MANAGERS.iter().find(|manager| manager.command == invocation.program);
match manager.and_then(|manager| manager.lockfile.as_ref()) {
Some(Lockfile::Restore(lockfile)) => restore_https(lockfile, dir),
Some(Lockfile::Warn(lockfile)) => warn_if_plaintext(lockfile, dir),
None => {}
}
Ok(code)
}
// Command::new only finds .exe on Windows; npm, pnpm and gem are .cmd shims found via PATHEXT.
fn resolve(program: &str) -> std::io::Result<PathBuf> {
which::which(program).map_err(|e| Error::new(ErrorKind::NotFound, e))
}
fn warn_if_plaintext(lockfile: &str, dir: Option<&Path>) {
let path = dir.unwrap_or(Path::new(".")).join(lockfile);
let Ok(text) = std::fs::read_to_string(path) else { return };
if BUILT_IN.iter().any(|registry| text.contains(&format!("http://{}/", registry.host()))) {
warn!("{lockfile} has http:// registry URLs; change them to https:// before committing");
}
}
fn restore_https(lockfile: &str, dir: Option<&Path>) {
let path = dir.unwrap_or(Path::new(".")).join(lockfile);
let Ok(text) = std::fs::read_to_string(&path) else { return };
let mut fixed = text.clone();
for registry in BUILT_IN {
let host = registry.host();
fixed = fixed.replace(&format!("http://{host}/"), &format!("https://{host}/"));
}
if fixed == text {
return;
}
match std::fs::write(&path, fixed) {
Ok(()) => info!("changed {lockfile}'s registry URLs back to https://"),
Err(e) => warn!("could not change {lockfile}'s registry URLs back to https://: {e}"),
}
}
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0"
processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
</assembly>
-2
View File
@@ -1,2 +0,0 @@
// The icon Explorer, UAC and the tray's popups show.
1 ICON "strix.ico"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

+6 -22
View File
@@ -1,5 +1,5 @@
//! How the tray asks the service what it blocked, and allows a new package, as the macOS app
//! messages its extension.
//! How the tray asks the service what it blocked, and allows a new package once, as the macOS
//! app messages its extension.
use super::{config_file, security_descriptor};
use crate::{blocks, Firewall};
@@ -11,8 +11,8 @@ use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::net::windows::named_pipe::{NamedPipeServer, ServerOptions};
use windows::core::{Owned, PWSTR};
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Security::*;
use windows::Win32::System::Pipes::{GetNamedPipeClientProcessId, ImpersonateNamedPipeClient};
use windows::Win32::Security::SECURITY_ATTRIBUTES;
use windows::Win32::System::Pipes::GetNamedPipeClientProcessId;
use windows::Win32::System::Threading::*;
pub(super) const PIPE: &str = r"\\.\pipe\StrixFirewall";
@@ -59,7 +59,8 @@ async fn answer(client: NamedPipeServer, firewall: &Firewall) -> Result<(), BoxE
// Only the app can message the macOS extension; here any program could connect.
let reply = if !client_is_tray(client.get_ref())? {
"error: only the tray can ask".to_owned()
} else if request.starts_with("allow ") && !client_is_admin(client.get_ref())? {
} else if request.starts_with("allow ") {
// Trusting writes the config for every user, so it is left to an administrator.
"error: trusting a package takes an administrator".to_owned()
} else {
blocks::answer(firewall, &config_file(), request)
@@ -83,20 +84,3 @@ fn client_is_tray(client: &NamedPipeServer) -> Result<bool, BoxError> {
// Windows paths ignore case.
Ok(path.to_lowercase() == tray.to_string_lossy().to_lowercase())
}
/// Elevated, as only an elevated token has the Administrators group enabled.
fn client_is_admin(client: &NamedPipeServer) -> windows::core::Result<bool> {
let mut sid = [0u32; SECURITY_MAX_SID_SIZE as usize / 4];
let admins = PSID(sid.as_mut_ptr().cast());
let mut size = SECURITY_MAX_SID_SIZE;
unsafe { CreateWellKnownSid(WinBuiltinAdministratorsSid, None, Some(admins), &mut size)? };
unsafe { ImpersonateNamedPipeClient(HANDLE(client.as_raw_handle()))? };
let mut member = false.into();
let checked = unsafe { CheckTokenMembership(None, admins, &mut member) };
// Carrying on as the client would be worse than stopping.
if unsafe { RevertToSelf() }.is_err() {
std::process::abort();
}
checked?;
Ok(member.as_bool())
}
+32 -133
View File
@@ -9,12 +9,10 @@ use std::os::windows::io::AsRawHandle;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::time::Duration;
use tray_icon::{Icon, MouseButtonState, TrayIcon, TrayIconBuilder, TrayIconEvent};
use windows::core::{w, HRESULT, PCWSTR};
use windows::core::{w, PCWSTR};
use windows::Win32::Foundation::*;
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::System::Pipes::GetNamedPipeServerProcessId;
use windows::Win32::System::Threading::*;
use windows::Win32::UI::Controls::*;
use windows::Win32::System::Threading::CreateMutexW;
use windows::Win32::UI::Shell::*;
use windows::Win32::UI::WindowsAndMessaging::*;
use windows_registry::CURRENT_USER;
@@ -25,8 +23,7 @@ const MENU_PAUSE: &str = "pause";
const MENU_RESTART: &str = "restart";
const MENU_LOG: &str = "log";
const MENU_QUIT: &str = "quit";
/// Also the service's requests, and `allow` the elevated `firewall-tray` command.
const ALLOW: &str = "allow";
/// Also the service's request.
const ALLOW_ONCE: &str = "allow-once";
const LOGO: &[u8] = include_bytes!("tray.png");
/// Where blocked packages are listed, below Open logs.
@@ -35,19 +32,6 @@ const BLOCKED_KEPT: usize = 10;
/// Runs until Quit, once per session.
pub fn run() -> Result<(), BoxError> {
let args: Vec<String> = std::env::args().skip(1).collect();
if let [verb, id, label @ ..] = &args[..] {
if verb == ALLOW {
// Run by `run_elevated`, so the service can tell an admin asked.
let trusted = ask_service(&format!("{ALLOW} {id}"))
.and_then(|reply| answered(&format!("Trusted {}", label.join(" ")), &reply));
if let Err(error) = trusted {
show_error(error);
std::process::exit(1);
}
return Ok(());
}
}
// Held until we exit.
let _mutex = unsafe { CreateMutexW(None, false, w!(r"Local\StrixFirewallTray"))? };
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
@@ -221,8 +205,8 @@ impl Tray {
let allowed = self.allowed.0.clone();
std::thread::spawn(move || {
for package in new {
if let Some(trust) = alert(&package) {
allow(package, trust, &allowed);
if alert(&package) {
allow_once(package, &allowed);
}
}
});
@@ -245,13 +229,11 @@ impl Tray {
package.reason.clone()
};
let reason = MenuItem::new(reason, false, None);
let trustable = package.trustable();
let once = MenuItem::with_id(format!("{ALLOW_ONCE}:{i}"), "Allow Once", trustable, None);
let always = MenuItem::with_id(format!("{ALLOW}:{i}"), "Trust this version", trustable, None);
let items: &[&dyn IsMenuItem] = if package.malicious() {
&[&reason]
let once = MenuItem::with_id(format!("{ALLOW_ONCE}:{i}"), "Allow Once", true, None);
let items: &[&dyn IsMenuItem] = if package.trustable() {
&[&reason, &once]
} else {
&[&reason, &once, &always]
&[&reason]
};
let label = format!("{verb} {}", package.label());
if let Ok(item) = Submenu::with_items(label, true, items) {
@@ -261,12 +243,11 @@ impl Tray {
}
fn on_menu(&self, id: &str) {
if let Some((verb @ (ALLOW | ALLOW_ONCE), i)) = id.split_once(':') {
let trust = verb == ALLOW;
if let Some(i) = id.strip_prefix(ALLOW_ONCE).and_then(|i| i.strip_prefix(':')) {
let package = i.parse().ok().and_then(|i: usize| self.blocked.get(i));
if let Some(package) = package.cloned() {
let allowed = self.allowed.0.clone();
std::thread::spawn(move || allow(package, trust, &allowed));
std::thread::spawn(move || allow_once(package, &allowed));
}
return;
}
@@ -295,124 +276,42 @@ impl Tray {
}
}
const ALLOW_ONCE_BUTTON: i32 = 100;
const TRUST_BUTTON: i32 = 101;
/// `firewall.rc`'s icon, embedded by `build.rs`.
const STRIX_ICON: PCWSTR = PCWSTR(1 as _);
/// `Some(true)` if the user chose to trust it, `Some(false)` to allow it once.
/// A task dialog, as a message box can't have both buttons.
fn alert(package: &BlockedPackage) -> Option<bool> {
/// Whether the user chose to allow it once.
fn alert(package: &BlockedPackage) -> bool {
let text = format!("{} {}.", package.label(), package.reason);
let (title, text, icon) = if package.malicious() {
let (title, text, style) = if package.malicious() {
let title = format!("Blocked malware: {}", package.label());
let text = text
+ "\n\nIt was not installed. Don't install it from another source. If you didn't expect this "
+ "package, a dependency may be compromised; tell your security team.";
(title, text, TD_ERROR_ICON)
(title, text, MB_ICONERROR)
} else if !package.trustable() {
("Held back a new package".to_owned(), text, MB_ICONINFORMATION)
} else {
("Held back a new package".to_owned(), text, STRIX_ICON)
let text = text + "\n\nAllow it once?";
("Held back a new package".to_owned(), text, MB_YESNO | MB_ICONINFORMATION)
};
let button = |id, text| TASKDIALOG_BUTTON { nButtonID: id, pszButtonText: text };
let allow = [button(ALLOW_ONCE_BUTTON, w!("Allow Once")), button(TRUST_BUTTON, w!("Trust this version"))];
let buttons: &[_] = if package.trustable() { &allow } else { &[] };
let (title, text) = (HSTRING::from(title), HSTRING::from(text));
let dialog = TASKDIALOGCONFIG {
cbSize: size_of::<TASKDIALOGCONFIG>() as u32,
// Where `STRIX_ICON` is loaded from; the `TD_` icons are Windows'.
hInstance: unsafe { GetModuleHandleW(None) }.ok()?.into(),
dwCommonButtons: TDCBF_CLOSE_BUTTON,
pszWindowTitle: w!("Strix Firewall"),
Anonymous1: TASKDIALOGCONFIG_0 { pszMainIcon: icon },
pszMainInstruction: PCWSTR(title.as_ptr()),
pszContent: PCWSTR(text.as_ptr()),
cButtons: buttons.len() as u32,
pButtons: buttons.as_ptr(),
pfCallback: Some(on_dialog),
..Default::default()
unsafe { MessageBoxW(None, &text, &title, style | MB_SETFOREGROUND) == IDYES }
}
/// Tells the user when the install had already stopped waiting.
fn allow_once(package: BlockedPackage, allowed: &Sender<BlockedPackage>) {
let reply = match ask_service(&format!("{ALLOW_ONCE} {}", package.id)) {
Ok(reply) => reply,
Err(error) => return show_error(error),
};
let mut chosen = 0;
unsafe { TaskDialogIndirect(&dialog, Some(&raw mut chosen), None, None) }.ok()?;
match chosen {
ALLOW_ONCE_BUTTON => Some(false),
TRUST_BUTTON => Some(true),
_ => None,
}
}
/// Asks for the foreground, as `MB_SETFOREGROUND` did; Windows may still leave it behind.
unsafe extern "system" fn on_dialog(
window: HWND,
event: TASKDIALOG_NOTIFICATIONS,
_: WPARAM,
_: LPARAM,
_: isize,
) -> HRESULT {
if event == TDN_CREATED {
let _ = unsafe { SetForegroundWindow(window) };
}
S_OK
}
/// Trusting writes the config for every user, so it goes through a UAC prompt.
/// The elevated process shows its own errors and late replies.
fn allow(package: BlockedPackage, trust: bool, allowed: &Sender<BlockedPackage>) {
let allowed_it = if trust {
run_elevated(&[ALLOW, &package.id.to_string(), &package.label()])
.map(|code| code == Some(0))
} else {
let title = format!("Allowed {} for the next minute", package.label());
ask_service(&format!("{ALLOW_ONCE} {}", package.id))
.and_then(|reply| answered(&title, &reply))
.map(|()| true)
};
match allowed_it {
Ok(true) => {
let _ = allowed.send(package);
}
Ok(false) => {}
Err(error) => show_error(error),
}
}
/// Tells the user, under `title`, when the install had already stopped waiting.
fn answered(title: &str, reply: &str) -> Result<(), BoxError> {
match reply {
"ok" => Ok(()),
match reply.as_str() {
"ok" => {}
"late" => {
let title = format!("Allowed {} for the next minute", package.label());
let text = "The install had already stopped waiting for it. Run it again to finish.";
let (title, text) = (HSTRING::from(title), HSTRING::from(text));
unsafe { MessageBoxW(None, &text, &title, MB_ICONINFORMATION | MB_SETFOREGROUND) };
Ok(())
}
other => Err(other.into()),
other => return show_error(other.into()),
}
}
/// This program as an admin, waiting for its exit code; `None` if the user said no.
fn run_elevated(args: &[&str]) -> Result<Option<u32>, BoxError> {
let exe = HSTRING::from(std::env::current_exe()?.as_path());
let parameters = HSTRING::from(args.join(" "));
let mut info = SHELLEXECUTEINFOW {
cbSize: size_of::<SHELLEXECUTEINFOW>() as u32,
fMask: SEE_MASK_NOCLOSEPROCESS,
lpVerb: w!("runas"),
lpFile: PCWSTR(exe.as_ptr()),
lpParameters: PCWSTR(parameters.as_ptr()),
nShow: SW_HIDE.0,
..Default::default()
};
if let Err(error) = unsafe { ShellExecuteExW(&mut info) } {
if error.code() == ERROR_CANCELLED.to_hresult() {
return Ok(None);
}
return Err(error.into());
}
let process = unsafe { Owned::new(info.hProcess) };
unsafe { WaitForSingleObject(*process, INFINITE) };
let mut code = 1;
unsafe { GetExitCodeProcess(*process, &mut code)? };
Ok(Some(code))
let _ = allowed.send(package);
}
/// One request per connection, as the service answers.
+3 -1
View File
@@ -41,7 +41,9 @@ impl Filter for Osv {
if hits.is_empty() {
Verdict::Allow
} else {
Verdict::Deny(format!("is flagged malicious by OSV ({})", hits.join(", ")))
let links: Vec<String> =
hits.iter().map(|id| format!("{id} https://osv.dev/vulnerability/{id}")).collect();
Verdict::Deny(format!("is flagged malicious by OSV: {}", links.join(", ")))
}
})
}